Files
makemd/dashboard/src/pages/Ad/AutoAdjustment/index.tsx
wurenzhi 15ee1758f5 refactor: 重构项目结构并优化类型定义
- 移除extension模块,将功能迁移至node-agent
- 修复类型导出问题,使用export type明确类型导出
- 统一数据库连接方式,从直接导入改为使用config/database
- 更新文档中的项目结构描述
- 添加多个服务的实用方法,如getForecast、getBalances等
- 修复类型错误和TS1205警告
- 优化RedisService调用方式
- 添加新的实体类型定义
- 更新审计日志格式,统一字段命名
2026-03-21 15:04:06 +08:00

369 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;