refactor(server): 重构服务层代码结构 feat(server): 添加基础设施、跨境电商、AI决策等核心服务 docs: 完善前端业务说明书和开发进度文档 style: 格式化代码和文档
325 lines
8.1 KiB
TypeScript
325 lines
8.1 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Card, Layout, Typography, Row, Col, DatePicker, Select, Button, Table, Statistic, Spin, message } from 'antd';
|
|
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, LineChart, Line } from 'recharts';
|
|
import { ArrowUpOutlined, ArrowDownOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
import { Link } from 'umi';
|
|
|
|
const { Content } = Layout;
|
|
const { Title, Text } = Typography;
|
|
const { RangePicker } = DatePicker;
|
|
const { Option } = Select;
|
|
|
|
interface ROIItem {
|
|
id: string;
|
|
productId: string;
|
|
productName: string;
|
|
cost: number;
|
|
price: number;
|
|
profit: number;
|
|
roi: number;
|
|
salesVolume: number;
|
|
date: string;
|
|
}
|
|
|
|
const ROIAnalysis: React.FC = () => {
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
const [roiData, setRoiData] = useState<ROIItem[]>([]);
|
|
const [timeRange, setTimeRange] = useState<any>(null);
|
|
const [productId, setProductId] = useState<string>('');
|
|
const [summary, setSummary] = useState<{
|
|
totalSales: number;
|
|
totalProfit: number;
|
|
averageROI: number;
|
|
bestProduct: string;
|
|
}>({
|
|
totalSales: 0,
|
|
totalProfit: 0,
|
|
averageROI: 0,
|
|
bestProduct: '',
|
|
});
|
|
|
|
const mockROIData: ROIItem[] = [
|
|
{
|
|
id: '1',
|
|
productId: 'P001',
|
|
productName: '智能手表',
|
|
cost: 150,
|
|
price: 299,
|
|
profit: 149,
|
|
roi: 99.33,
|
|
salesVolume: 120,
|
|
date: '2026-03-01',
|
|
},
|
|
{
|
|
id: '2',
|
|
productId: 'P001',
|
|
productName: '智能手表',
|
|
cost: 150,
|
|
price: 299,
|
|
profit: 149,
|
|
roi: 99.33,
|
|
salesVolume: 110,
|
|
date: '2026-03-02',
|
|
},
|
|
{
|
|
id: '3',
|
|
productId: 'P001',
|
|
productName: '智能手表',
|
|
cost: 150,
|
|
price: 299,
|
|
profit: 149,
|
|
roi: 99.33,
|
|
salesVolume: 130,
|
|
date: '2026-03-03',
|
|
},
|
|
{
|
|
id: '4',
|
|
productId: 'P002',
|
|
productName: '无线耳机',
|
|
cost: 80,
|
|
price: 199,
|
|
profit: 119,
|
|
roi: 148.75,
|
|
salesVolume: 200,
|
|
date: '2026-03-01',
|
|
},
|
|
{
|
|
id: '5',
|
|
productId: 'P002',
|
|
productName: '无线耳机',
|
|
cost: 80,
|
|
price: 199,
|
|
profit: 119,
|
|
roi: 148.75,
|
|
salesVolume: 210,
|
|
date: '2026-03-02',
|
|
},
|
|
{
|
|
id: '6',
|
|
productId: 'P002',
|
|
productName: '无线耳机',
|
|
cost: 80,
|
|
price: 199,
|
|
profit: 119,
|
|
roi: 148.75,
|
|
salesVolume: 190,
|
|
date: '2026-03-03',
|
|
},
|
|
];
|
|
|
|
const fetchROIData = () => {
|
|
setLoading(true);
|
|
// 模拟API请求
|
|
setTimeout(() => {
|
|
setRoiData(mockROIData);
|
|
// 计算汇总数据
|
|
const totalSales = mockROIData.reduce((sum, item) => sum + item.salesVolume, 0);
|
|
const totalProfit = mockROIData.reduce((sum, item) => sum + item.profit * item.salesVolume, 0);
|
|
const averageROI = mockROIData.reduce((sum, item) => sum + item.roi, 0) / mockROIData.length;
|
|
const bestProduct = mockROIData.reduce((best, current) =>
|
|
current.roi > best.roi ? current : best
|
|
).productName;
|
|
|
|
setSummary({
|
|
totalSales,
|
|
totalProfit,
|
|
averageROI: parseFloat(averageROI.toFixed(2)),
|
|
bestProduct,
|
|
});
|
|
setLoading(false);
|
|
}, 1000);
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchROIData();
|
|
}, []);
|
|
|
|
const handleTimeRangeChange = (dates: any) => {
|
|
setTimeRange(dates);
|
|
};
|
|
|
|
const handleProductChange = (value: string) => {
|
|
setProductId(value);
|
|
};
|
|
|
|
const handleRefresh = () => {
|
|
fetchROIData();
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: '商品ID',
|
|
dataIndex: 'productId',
|
|
key: 'productId',
|
|
},
|
|
{
|
|
title: '商品名称',
|
|
dataIndex: 'productName',
|
|
key: 'productName',
|
|
},
|
|
{
|
|
title: '成本',
|
|
dataIndex: 'cost',
|
|
key: 'cost',
|
|
render: (text: number) => `¥${text}`,
|
|
},
|
|
{
|
|
title: '售价',
|
|
dataIndex: 'price',
|
|
key: 'price',
|
|
render: (text: number) => `¥${text}`,
|
|
},
|
|
{
|
|
title: '利润',
|
|
dataIndex: 'profit',
|
|
key: 'profit',
|
|
render: (text: number) => `¥${text}`,
|
|
},
|
|
{
|
|
title: 'ROI',
|
|
dataIndex: 'roi',
|
|
key: 'roi',
|
|
render: (text: number) => `${text}%`,
|
|
},
|
|
{
|
|
title: '销量',
|
|
dataIndex: 'salesVolume',
|
|
key: 'salesVolume',
|
|
},
|
|
{
|
|
title: '日期',
|
|
dataIndex: 'date',
|
|
key: 'date',
|
|
},
|
|
];
|
|
|
|
// 准备图表数据
|
|
const chartData = mockROIData.map(item => ({
|
|
date: item.date,
|
|
销量: item.salesVolume,
|
|
利润: item.profit * item.salesVolume,
|
|
}));
|
|
|
|
const roiTrendData = mockROIData.map(item => ({
|
|
date: item.date,
|
|
ROI: item.roi,
|
|
}));
|
|
|
|
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}>商品ROI分析</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>
|
|
</Select>
|
|
<RangePicker onChange={handleTimeRangeChange} />
|
|
<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={summary.totalSales}
|
|
prefix={<ArrowUpOutlined />}
|
|
suffix="件"
|
|
valueStyle={{ color: '#3f8600' }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Card>
|
|
<Statistic
|
|
title="总利润"
|
|
value={summary.totalProfit}
|
|
prefix="¥"
|
|
valueStyle={{ color: '#3f8600' }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Card>
|
|
<Statistic
|
|
title="平均ROI"
|
|
value={summary.averageROI}
|
|
suffix="%"
|
|
valueStyle={{ color: '#3f8600' }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
<Col span={6}>
|
|
<Card>
|
|
<Statistic
|
|
title="最佳商品"
|
|
value={summary.bestProduct}
|
|
valueStyle={{ color: '#1890ff' }}
|
|
/>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
{/* 图表区域 */}
|
|
<Row gutter={[16, 16]} style={{ marginBottom: 24 }}>
|
|
<Col span={12}>
|
|
<Card title="销量与利润趋势">
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<BarChart data={chartData}>
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|
<XAxis dataKey="date" />
|
|
<YAxis />
|
|
<Tooltip />
|
|
<Legend />
|
|
<Bar dataKey="销量" fill="#1890ff" />
|
|
<Bar dataKey="利润" fill="#52c41a" />
|
|
</BarChart>
|
|
</ResponsiveContainer>
|
|
</Card>
|
|
</Col>
|
|
<Col span={12}>
|
|
<Card title="ROI趋势">
|
|
<ResponsiveContainer width="100%" height={300}>
|
|
<LineChart data={roiTrendData}>
|
|
<CartesianGrid strokeDasharray="3 3" />
|
|
<XAxis dataKey="date" />
|
|
<YAxis />
|
|
<Tooltip />
|
|
<Legend />
|
|
<Line type="monotone" dataKey="ROI" stroke="#ff4d4f" strokeWidth={2} />
|
|
</LineChart>
|
|
</ResponsiveContainer>
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
|
|
{/* 详细数据表格 */}
|
|
<Card title="ROI详细数据">
|
|
<Table
|
|
columns={columns}
|
|
dataSource={roiData}
|
|
rowKey="id"
|
|
loading={loading}
|
|
pagination={{ pageSize: 10 }}
|
|
/>
|
|
</Card>
|
|
</Content>
|
|
);
|
|
};
|
|
|
|
export default ROIAnalysis; |