feat: 添加前端页面和业务说明书
refactor(server): 重构服务层代码结构 feat(server): 添加基础设施、跨境电商、AI决策等核心服务 docs: 完善前端业务说明书和开发进度文档 style: 格式化代码和文档
This commit is contained in:
360
dashboard/src/pages/Product/ProfitMonitor/index.tsx
Normal file
360
dashboard/src/pages/Product/ProfitMonitor/index.tsx
Normal file
@@ -0,0 +1,360 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card, Layout, Typography, Row, Col, Select, Button, Table, Statistic, Spin, message, Alert, Badge } from 'antd';
|
||||
import { LineChart, Line, AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||
import { AlertOutlined, TrendingUpOutlined, TrendingDownOutlined, ReloadOutlined, EyeOutlined } from '@ant-design/icons';
|
||||
import { Link } from 'umi';
|
||||
|
||||
const { Content } = Layout;
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { Option } = Select;
|
||||
|
||||
interface ProfitData {
|
||||
id: string;
|
||||
date: string;
|
||||
profit: number;
|
||||
sales: number;
|
||||
cost: number;
|
||||
roi: number;
|
||||
}
|
||||
|
||||
interface AlertItem {
|
||||
id: string;
|
||||
productId: string;
|
||||
productName: string;
|
||||
type: string;
|
||||
message: string;
|
||||
severity: 'info' | 'warning' | 'error';
|
||||
date: string;
|
||||
}
|
||||
|
||||
interface ProductProfit {
|
||||
id: string;
|
||||
productId: string;
|
||||
productName: string;
|
||||
currentProfit: number;
|
||||
previousProfit: number;
|
||||
change: number;
|
||||
roi: number;
|
||||
status: 'up' | 'down' | 'stable';
|
||||
}
|
||||
|
||||
const ProfitMonitor: React.FC = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [profitData, setProfitData] = useState<ProfitData[]>([]);
|
||||
const [alerts, setAlerts] = useState<AlertItem[]>([]);
|
||||
const [productProfits, setProductProfits] = useState<ProductProfit[]>([]);
|
||||
const [selectedProduct, setSelectedProduct] = useState<string>('');
|
||||
const [timeRange, setTimeRange] = useState<string>('7d');
|
||||
|
||||
const mockProfitData: ProfitData[] = [
|
||||
{ id: '1', date: '2026-03-12', profit: 14900, sales: 12000, cost: 8000, roi: 99.33 },
|
||||
{ id: '2', date: '2026-03-13', profit: 15200, sales: 12500, cost: 8200, roi: 100.67 },
|
||||
{ id: '3', date: '2026-03-14', profit: 14800, sales: 11800, cost: 8100, roi: 98.67 },
|
||||
{ id: '4', date: '2026-03-15', profit: 16500, sales: 13500, cost: 8500, roi: 103.33 },
|
||||
{ id: '5', date: '2026-03-16', profit: 17200, sales: 14000, cost: 8800, roi: 105.33 },
|
||||
{ id: '6', date: '2026-03-17', profit: 16800, sales: 13800, cost: 8700, roi: 104.00 },
|
||||
{ id: '7', date: '2026-03-18', profit: 18500, sales: 15000, cost: 9200, roi: 108.33 },
|
||||
];
|
||||
|
||||
const mockAlerts: AlertItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
productId: 'P001',
|
||||
productName: '智能手表',
|
||||
type: '利润下降',
|
||||
message: '连续3天利润下降,当前利润低于预期15%',
|
||||
severity: 'warning',
|
||||
date: '2026-03-18 10:30',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
productId: 'P002',
|
||||
productName: '无线耳机',
|
||||
type: 'ROI异常',
|
||||
message: 'ROI突然上升30%,可能存在定价问题',
|
||||
severity: 'info',
|
||||
date: '2026-03-18 09:15',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
productId: 'P003',
|
||||
productName: '智能音箱',
|
||||
type: '成本上升',
|
||||
message: '成本上升10%,建议调整售价',
|
||||
severity: 'error',
|
||||
date: '2026-03-17 16:45',
|
||||
},
|
||||
];
|
||||
|
||||
const mockProductProfits: ProductProfit[] = [
|
||||
{
|
||||
id: '1',
|
||||
productId: 'P001',
|
||||
productName: '智能手表',
|
||||
currentProfit: 14900,
|
||||
previousProfit: 15200,
|
||||
change: -2.0,
|
||||
roi: 99.33,
|
||||
status: 'down',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
productId: 'P002',
|
||||
productName: '无线耳机',
|
||||
currentProfit: 23800,
|
||||
previousProfit: 21500,
|
||||
change: 10.7,
|
||||
roi: 148.75,
|
||||
status: 'up',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
productId: 'P003',
|
||||
productName: '智能音箱',
|
||||
currentProfit: 9500,
|
||||
previousProfit: 9400,
|
||||
change: 1.1,
|
||||
roi: 86.36,
|
||||
status: 'stable',
|
||||
},
|
||||
];
|
||||
|
||||
const fetchProfitData = () => {
|
||||
setLoading(true);
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
setProfitData(mockProfitData);
|
||||
setAlerts(mockAlerts);
|
||||
setProductProfits(mockProductProfits);
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfitData();
|
||||
}, []);
|
||||
|
||||
const handleProductChange = (value: string) => {
|
||||
setSelectedProduct(value);
|
||||
};
|
||||
|
||||
const handleTimeRangeChange = (value: string) => {
|
||||
setTimeRange(value);
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
fetchProfitData();
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '商品ID',
|
||||
dataIndex: 'productId',
|
||||
key: 'productId',
|
||||
},
|
||||
{
|
||||
title: '商品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
},
|
||||
{
|
||||
title: '当前利润',
|
||||
dataIndex: 'currentProfit',
|
||||
key: 'currentProfit',
|
||||
render: (text: number) => `¥${text}`,
|
||||
},
|
||||
{
|
||||
title: '变化率',
|
||||
dataIndex: 'change',
|
||||
key: 'change',
|
||||
render: (text: number) => (
|
||||
<span style={{ color: text > 0 ? '#52c41a' : text < 0 ? '#ff4d4f' : '#1890ff' }}>
|
||||
{text > 0 ? '+' : ''}{text}%
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'ROI',
|
||||
dataIndex: 'roi',
|
||||
key: 'roi',
|
||||
render: (text: number) => `${text}%`,
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: string) => (
|
||||
<Badge
|
||||
status={status === 'up' ? 'success' : status === 'down' ? 'error' : 'default'}
|
||||
text={status === 'up' ? '上升' : status === 'down' ? '下降' : '稳定'}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const alertColumns = [
|
||||
{
|
||||
title: '商品ID',
|
||||
dataIndex: 'productId',
|
||||
key: 'productId',
|
||||
},
|
||||
{
|
||||
title: '商品名称',
|
||||
dataIndex: 'productName',
|
||||
key: 'productName',
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
},
|
||||
{
|
||||
title: '消息',
|
||||
dataIndex: 'message',
|
||||
key: 'message',
|
||||
},
|
||||
{
|
||||
title: '严重程度',
|
||||
dataIndex: 'severity',
|
||||
key: 'severity',
|
||||
render: (severity: string) => (
|
||||
<Badge
|
||||
status={severity === 'info' ? 'default' : severity === 'warning' ? 'warning' : 'error'}
|
||||
text={severity === 'info' ? '信息' : severity === 'warning' ? '警告' : '错误'}
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '时间',
|
||||
dataIndex: 'date',
|
||||
key: 'date',
|
||||
},
|
||||
];
|
||||
|
||||
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>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
<Select
|
||||
placeholder="选择商品"
|
||||
style={{ width: 200 }}
|
||||
onChange={handleProductChange}
|
||||
>
|
||||
<Option value="">全部商品</Option>
|
||||
<Option value="P001">智能手表</Option>
|
||||
<Option value="P002">无线耳机</Option>
|
||||
<Option value="P003">智能音箱</Option>
|
||||
</Select>
|
||||
<Select
|
||||
defaultValue="7d"
|
||||
style={{ width: 120 }}
|
||||
onChange={handleTimeRangeChange}
|
||||
>
|
||||
<Option value="7d">近7天</Option>
|
||||
<Option value="30d">近30天</Option>
|
||||
<Option value="90d">近90天</Option>
|
||||
</Select>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={handleRefresh}
|
||||
loading={loading}
|
||||
>
|
||||
刷新
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 利润概览 */}
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="总利润"
|
||||
value={mockProductProfits.reduce((sum, item) => sum + item.currentProfit, 0)}
|
||||
prefix="¥"
|
||||
valueStyle={{ color: '#3f8600' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="平均ROI"
|
||||
value={mockProductProfits.reduce((sum, item) => sum + item.roi, 0) / mockProductProfits.length}
|
||||
suffix="%"
|
||||
valueStyle={{ color: '#3f8600' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="利润预警"
|
||||
value={mockAlerts.length}
|
||||
prefix={<AlertOutlined />}
|
||||
valueStyle={{ color: '#faad14' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Card>
|
||||
<Statistic
|
||||
title="监控商品数"
|
||||
value={mockProductProfits.length}
|
||||
prefix={<EyeOutlined />}
|
||||
valueStyle={{ color: '#1890ff' }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* 利润趋势图表 */}
|
||||
<Card title="利润趋势" style={{ marginBottom: 24 }}>
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<AreaChart data={profitData}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Area type="monotone" dataKey="profit" name="利润" fill="#52c41a" stroke="#52c41a" />
|
||||
<Area type="monotone" dataKey="sales" name="销售额" fill="#1890ff" stroke="#1890ff" />
|
||||
<Area type="monotone" dataKey="cost" name="成本" fill="#faad14" stroke="#faad14" />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
{/* 商品利润列表 */}
|
||||
<Col span={12}>
|
||||
<Card title="商品利润详情">
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={productProfits}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 5 }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* 利润预警 */}
|
||||
<Col span={12}>
|
||||
<Card title="利润预警">
|
||||
<Table
|
||||
columns={alertColumns}
|
||||
dataSource={alerts}
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
pagination={{ pageSize: 5 }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfitMonitor;
|
||||
Reference in New Issue
Block a user