- 移除extension模块,将功能迁移至node-agent - 修复类型导出问题,使用export type明确类型导出 - 统一数据库连接方式,从直接导入改为使用config/database - 更新文档中的项目结构描述 - 添加多个服务的实用方法,如getForecast、getBalances等 - 修复类型错误和TS1205警告 - 优化RedisService调用方式 - 添加新的实体类型定义 - 更新审计日志格式,统一字段命名
369 lines
10 KiB
TypeScript
369 lines
10 KiB
TypeScript
import React, { useState } from 'react';
|
||
import { Card, Typography, Row, Col, Form, Input, Select, Button, Table, Switch, DatePicker, TimePicker, message, Alert, Modal } from 'antd';
|
||
import { PlusOutlined, EditOutlined, DeleteOutlined, SaveOutlined, PlayCircleOutlined, PauseCircleOutlined, HistoryOutlined } from '@ant-design/icons';
|
||
import { Link } from 'react-router-dom';
|
||
import { marketingDataSource, Ad } from '@/services/marketingDataSource';
|
||
|
||
const { Title, Text, Paragraph } = Typography;
|
||
const { Option } = Select;
|
||
const { Item } = Form;
|
||
const { RangePicker } = DatePicker;
|
||
|
||
interface Strategy {
|
||
id: string;
|
||
name: string;
|
||
adId: string;
|
||
adName: string;
|
||
type: string;
|
||
condition: string;
|
||
action: string;
|
||
status: 'active' | 'inactive' | 'paused';
|
||
createdAt: string;
|
||
lastExecuted: string;
|
||
executionCount: number;
|
||
}
|
||
|
||
interface ExecutionHistory {
|
||
id: string;
|
||
strategyId: string;
|
||
strategyName: string;
|
||
executionTime: string;
|
||
status: 'success' | 'failed' | 'pending';
|
||
result: string;
|
||
details: string;
|
||
}
|
||
|
||
const AutoAdjustment: React.FC = () => {
|
||
const [strategies, setStrategies] = useState<Strategy[]>([]);
|
||
const [executionHistory, setExecutionHistory] = useState<ExecutionHistory[]>([]);
|
||
const [isModalVisible, setIsModalVisible] = useState<boolean>(false);
|
||
const [editingStrategy, setEditingStrategy] = useState<Strategy | null>(null);
|
||
const [form] = Form.useForm();
|
||
|
||
const mockStrategies: Strategy[] = [
|
||
{
|
||
id: '1',
|
||
name: 'ROI自动调整',
|
||
adId: 'AD001',
|
||
adName: '智能手表推广',
|
||
type: 'ROI',
|
||
condition: 'ROI < 3.0',
|
||
action: '降低CPC 10%',
|
||
status: 'active' as const,
|
||
createdAt: '2026-03-10',
|
||
lastExecuted: '2026-03-18 10:30',
|
||
executionCount: 15,
|
||
},
|
||
{
|
||
id: '2',
|
||
name: '点击率优化',
|
||
adId: 'AD002',
|
||
adName: '无线耳机促销',
|
||
type: 'CTR',
|
||
condition: 'CTR < 2.0%',
|
||
action: '调整创意',
|
||
status: 'paused' as const,
|
||
createdAt: '2026-03-08',
|
||
lastExecuted: '2026-03-15 14:20',
|
||
executionCount: 8,
|
||
},
|
||
{
|
||
id: '3',
|
||
name: '预算自动分配',
|
||
adId: 'AD003',
|
||
adName: '智能音箱新品',
|
||
type: 'Budget',
|
||
condition: '日花费 < 预算的80%',
|
||
action: '增加预算10%',
|
||
status: 'active' as const,
|
||
createdAt: '2026-03-05',
|
||
lastExecuted: '2026-03-18 09:15',
|
||
executionCount: 20,
|
||
},
|
||
];
|
||
|
||
const mockExecutionHistory: ExecutionHistory[] = [
|
||
{
|
||
id: '1',
|
||
strategyId: '1',
|
||
strategyName: 'ROI自动调整',
|
||
executionTime: '2026-03-18 10:30',
|
||
status: 'success',
|
||
result: 'CPC降低10%',
|
||
details: 'ROI为2.8,低于阈值3.0,执行降低CPC操作',
|
||
},
|
||
{
|
||
id: '2',
|
||
strategyId: '3',
|
||
strategyName: '预算自动分配',
|
||
executionTime: '2026-03-18 09:15',
|
||
status: 'success',
|
||
result: '预算增加10%',
|
||
details: '日花费为预算的75%,低于阈值80%,执行增加预算操作',
|
||
},
|
||
{
|
||
id: '3',
|
||
strategyId: '1',
|
||
strategyName: 'ROI自动调整',
|
||
executionTime: '2026-03-17 16:45',
|
||
status: 'failed',
|
||
result: '操作失败',
|
||
details: 'API调用失败,无法执行调整操作',
|
||
},
|
||
];
|
||
|
||
const handleAddStrategy = () => {
|
||
setEditingStrategy(null);
|
||
form.resetFields();
|
||
setIsModalVisible(true);
|
||
};
|
||
|
||
const handleEditStrategy = (strategy: Strategy) => {
|
||
setEditingStrategy(strategy);
|
||
form.setFieldsValue(strategy);
|
||
setIsModalVisible(true);
|
||
};
|
||
|
||
const handleDeleteStrategy = (id: string) => {
|
||
const updatedStrategies = strategies.filter(item => item.id !== id);
|
||
setStrategies(updatedStrategies);
|
||
message.success('策略已删除');
|
||
};
|
||
|
||
const handleToggleStrategy = (id: string) => {
|
||
const updatedStrategies = strategies.map(item =>
|
||
item.id === id
|
||
? { ...item, status: item.status === 'active' ? 'paused' : 'active' }
|
||
: item
|
||
);
|
||
setStrategies(updatedStrategies);
|
||
message.success('策略状态已更新');
|
||
};
|
||
|
||
const handleSaveStrategy = () => {
|
||
form.validateFields().then(values => {
|
||
if (editingStrategy) {
|
||
const updatedStrategies = strategies.map(item =>
|
||
item.id === editingStrategy.id ? { ...item, ...values } : item
|
||
);
|
||
setStrategies(updatedStrategies);
|
||
message.success('策略已更新');
|
||
} else {
|
||
const newStrategy: Strategy = {
|
||
id: (strategies.length + 1).toString(),
|
||
...values,
|
||
status: (values.status || 'inactive') as 'active' | 'inactive' | 'paused',
|
||
createdAt: new Date().toISOString().split('T')[0],
|
||
lastExecuted: '-',
|
||
executionCount: 0,
|
||
};
|
||
setStrategies([...strategies, newStrategy]);
|
||
message.success('策略已创建');
|
||
}
|
||
setIsModalVisible(false);
|
||
});
|
||
};
|
||
|
||
const columns = [
|
||
{
|
||
title: '策略名称',
|
||
dataIndex: 'name',
|
||
key: 'name',
|
||
},
|
||
{
|
||
title: '广告',
|
||
dataIndex: 'adName',
|
||
key: 'adName',
|
||
},
|
||
{
|
||
title: '类型',
|
||
dataIndex: 'type',
|
||
key: 'type',
|
||
},
|
||
{
|
||
title: '条件',
|
||
dataIndex: 'condition',
|
||
key: 'condition',
|
||
},
|
||
{
|
||
title: '动作',
|
||
dataIndex: 'action',
|
||
key: 'action',
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
render: (status: string) => (
|
||
<span style={{
|
||
color: status === 'active' ? '#52c41a' :
|
||
status === 'paused' ? '#faad14' : '#d9d9d9'
|
||
}}>
|
||
{status === 'active' ? '活跃' :
|
||
status === 'paused' ? '暂停' : '未激活'}
|
||
</span>
|
||
),
|
||
},
|
||
{
|
||
title: '执行次数',
|
||
dataIndex: 'executionCount',
|
||
key: 'executionCount',
|
||
},
|
||
{
|
||
title: '上次执行',
|
||
dataIndex: 'lastExecuted',
|
||
key: 'lastExecuted',
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
render: (_: any, record: Strategy) => (
|
||
<div style={{ display: 'flex', gap: 8 }}>
|
||
<Button
|
||
icon={record.status === 'active' ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
|
||
size="small"
|
||
onClick={() => handleToggleStrategy(record.id)}
|
||
>
|
||
{record.status === 'active' ? '暂停' : '激活'}
|
||
</Button>
|
||
<Button
|
||
icon={<EditOutlined />}
|
||
size="small"
|
||
onClick={() => handleEditStrategy(record)}
|
||
>
|
||
编辑
|
||
</Button>
|
||
<Button
|
||
icon={<DeleteOutlined />}
|
||
size="small"
|
||
danger
|
||
onClick={() => handleDeleteStrategy(record.id)}
|
||
>
|
||
删除
|
||
</Button>
|
||
</div>
|
||
),
|
||
},
|
||
];
|
||
|
||
const historyColumns = [
|
||
{
|
||
title: '策略名称',
|
||
dataIndex: 'strategyName',
|
||
key: 'strategyName',
|
||
},
|
||
{
|
||
title: '执行时间',
|
||
dataIndex: 'executionTime',
|
||
key: 'executionTime',
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
key: 'status',
|
||
render: (status: string) => (
|
||
<span style={{
|
||
color: status === 'success' ? '#52c41a' :
|
||
status === 'failed' ? '#ff4d4f' : '#faad14'
|
||
}}>
|
||
{status === 'success' ? '成功' :
|
||
status === 'failed' ? '失败' : '待执行'}
|
||
</span>
|
||
),
|
||
},
|
||
{
|
||
title: '结果',
|
||
dataIndex: 'result',
|
||
key: 'result',
|
||
},
|
||
{
|
||
title: '详情',
|
||
dataIndex: 'details',
|
||
key: 'details',
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 24 }}>
|
||
<Title level={4}>自动调整策略配置</Title>
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={handleAddStrategy}>
|
||
添加策略
|
||
</Button>
|
||
</div>
|
||
|
||
{/* 策略列表 */}
|
||
<Card title="策略列表" style={{ marginBottom: 24 }}>
|
||
<Table
|
||
columns={columns}
|
||
dataSource={mockStrategies}
|
||
rowKey="id"
|
||
pagination={{ pageSize: 10 }}
|
||
/>
|
||
</Card>
|
||
|
||
{/* 执行历史 */}
|
||
<Card title="执行历史">
|
||
<Table
|
||
columns={historyColumns}
|
||
dataSource={mockExecutionHistory}
|
||
rowKey="id"
|
||
pagination={{ pageSize: 10 }}
|
||
/>
|
||
</Card>
|
||
|
||
{/* 策略编辑模态框 */}
|
||
<Modal
|
||
title={editingStrategy ? "编辑策略" : "添加策略"}
|
||
open={isModalVisible}
|
||
onCancel={() => setIsModalVisible(false)}
|
||
footer={null}
|
||
>
|
||
<Form
|
||
form={form}
|
||
layout="vertical"
|
||
initialValues={{
|
||
name: '',
|
||
adId: 'AD001',
|
||
type: 'ROI',
|
||
condition: '',
|
||
action: '',
|
||
}}
|
||
>
|
||
<Item label="策略名称" name="name" rules={[{ required: true, message: '请输入策略名称' }]}>
|
||
<Input placeholder="输入策略名称" />
|
||
</Item>
|
||
<Item label="广告" name="adId">
|
||
<Select>
|
||
<Option value="AD001">智能手表推广</Option>
|
||
<Option value="AD002">无线耳机促销</Option>
|
||
<Option value="AD003">智能音箱新品</Option>
|
||
</Select>
|
||
</Item>
|
||
<Item label="策略类型" name="type">
|
||
<Select>
|
||
<Option value="ROI">ROI优化</Option>
|
||
<Option value="CTR">点击率优化</Option>
|
||
<Option value="Budget">预算管理</Option>
|
||
<Option value="Conversion">转化优化</Option>
|
||
</Select>
|
||
</Item>
|
||
<Item label="触发条件" name="condition" rules={[{ required: true, message: '请输入触发条件' }]}>
|
||
<Input placeholder="例如:ROI < 3.0" />
|
||
</Item>
|
||
<Item label="执行动作" name="action" rules={[{ required: true, message: '请输入执行动作' }]}>
|
||
<Input placeholder="例如:降低CPC 10%" />
|
||
</Item>
|
||
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 12, marginTop: 24 }}>
|
||
<Button onClick={() => setIsModalVisible(false)}>取消</Button>
|
||
<Button type="primary" icon={<SaveOutlined />} onClick={handleSaveStrategy}>
|
||
保存
|
||
</Button>
|
||
</div>
|
||
</Form>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default AutoAdjustment; |