369 lines
10 KiB
TypeScript
369 lines
10 KiB
TypeScript
|
|
import React, { useState } from 'react';
|
|||
|
|
import { Card, Layout, 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 'umi';
|
|||
|
|
|
|||
|
|
const { Content } = Layout;
|
|||
|
|
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',
|
|||
|
|
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',
|
|||
|
|
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',
|
|||
|
|
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: 'inactive' as const,
|
|||
|
|
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 (
|
|||
|
|
<Content style={{ padding: 24, margin: 0, minHeight: 280, background: '#fff' }}>
|
|||
|
|
<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>
|
|||
|
|
</Content>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default AutoAdjustment;
|