feat: 添加前端页面和业务说明书
refactor(server): 重构服务层代码结构 feat(server): 添加基础设施、跨境电商、AI决策等核心服务 docs: 完善前端业务说明书和开发进度文档 style: 格式化代码和文档
This commit is contained in:
297
dashboard/src/pages/Product/AIPricing/index.tsx
Normal file
297
dashboard/src/pages/Product/AIPricing/index.tsx
Normal file
@@ -0,0 +1,297 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Card, Layout, Typography, Row, Col, Form, Input, Select, Button, Table, Statistic, Spin, message, Alert } from 'antd';
|
||||
import { CalculatorOutlined, SaveOutlined, HistoryOutlined, LineChartOutlined } from '@ant-design/icons';
|
||||
import { Link } from 'umi';
|
||||
|
||||
const { Content } = Layout;
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
const { Option } = Select;
|
||||
const { Item } = Form;
|
||||
|
||||
interface PricingSuggestion {
|
||||
id: string;
|
||||
scenario: string;
|
||||
suggestedPrice: number;
|
||||
expectedProfit: number;
|
||||
expectedROI: number;
|
||||
expectedSales: number;
|
||||
confidence: number;
|
||||
}
|
||||
|
||||
interface PriceHistory {
|
||||
id: string;
|
||||
date: string;
|
||||
oldPrice: number;
|
||||
newPrice: number;
|
||||
reason: string;
|
||||
operator: string;
|
||||
}
|
||||
|
||||
const AIPricing: React.FC = () => {
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [pricingSuggestions, setPricingSuggestions] = useState<PricingSuggestion[]>([]);
|
||||
const [priceHistory, setPriceHistory] = useState<PriceHistory[]>([]);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const mockPricingSuggestions: PricingSuggestion[] = [
|
||||
{
|
||||
id: '1',
|
||||
scenario: '激进定价',
|
||||
suggestedPrice: 349,
|
||||
expectedProfit: 199,
|
||||
expectedROI: 132.67,
|
||||
expectedSales: 80,
|
||||
confidence: 0.85,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
scenario: '均衡定价',
|
||||
suggestedPrice: 299,
|
||||
expectedProfit: 149,
|
||||
expectedROI: 99.33,
|
||||
expectedSales: 120,
|
||||
confidence: 0.95,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
scenario: '保守定价',
|
||||
suggestedPrice: 279,
|
||||
expectedProfit: 129,
|
||||
expectedROI: 86.00,
|
||||
expectedSales: 150,
|
||||
confidence: 0.90,
|
||||
},
|
||||
];
|
||||
|
||||
const mockPriceHistory: PriceHistory[] = [
|
||||
{
|
||||
id: '1',
|
||||
date: '2026-03-15',
|
||||
oldPrice: 279,
|
||||
newPrice: 299,
|
||||
reason: '基于市场竞争分析',
|
||||
operator: 'AI系统',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
date: '2026-03-10',
|
||||
oldPrice: 259,
|
||||
newPrice: 279,
|
||||
reason: '成本上升',
|
||||
operator: '运营专员',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
date: '2026-03-01',
|
||||
oldPrice: 239,
|
||||
newPrice: 259,
|
||||
reason: '新品上市',
|
||||
operator: '运营专员',
|
||||
},
|
||||
];
|
||||
|
||||
const handleCalculatePrice = () => {
|
||||
setLoading(true);
|
||||
// 模拟API请求
|
||||
setTimeout(() => {
|
||||
setPricingSuggestions(mockPricingSuggestions);
|
||||
setPriceHistory(mockPriceHistory);
|
||||
setLoading(false);
|
||||
message.success('智能定价计算完成');
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
const handleApplyPrice = (scenario: string, price: number) => {
|
||||
message.success(`已应用${scenario}定价策略,新价格:¥${price}`);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '场景',
|
||||
dataIndex: 'scenario',
|
||||
key: 'scenario',
|
||||
},
|
||||
{
|
||||
title: '建议售价',
|
||||
dataIndex: 'suggestedPrice',
|
||||
key: 'suggestedPrice',
|
||||
render: (text: number) => `¥${text}`,
|
||||
},
|
||||
{
|
||||
title: '预期利润',
|
||||
dataIndex: 'expectedProfit',
|
||||
key: 'expectedProfit',
|
||||
render: (text: number) => `¥${text}`,
|
||||
},
|
||||
{
|
||||
title: '预期ROI',
|
||||
dataIndex: 'expectedROI',
|
||||
key: 'expectedROI',
|
||||
render: (text: number) => `${text}%`,
|
||||
},
|
||||
{
|
||||
title: '预期销量',
|
||||
dataIndex: 'expectedSales',
|
||||
key: 'expectedSales',
|
||||
},
|
||||
{
|
||||
title: '置信度',
|
||||
dataIndex: 'confidence',
|
||||
key: 'confidence',
|
||||
render: (text: number) => `${(text * 100).toFixed(0)}%`,
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
render: (_: any, record: PricingSuggestion) => (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
onClick={() => handleApplyPrice(record.scenario, record.suggestedPrice)}
|
||||
>
|
||||
应用
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const historyColumns = [
|
||||
{
|
||||
title: '日期',
|
||||
dataIndex: 'date',
|
||||
key: 'date',
|
||||
},
|
||||
{
|
||||
title: '旧价格',
|
||||
dataIndex: 'oldPrice',
|
||||
key: 'oldPrice',
|
||||
render: (text: number) => `¥${text}`,
|
||||
},
|
||||
{
|
||||
title: '新价格',
|
||||
dataIndex: 'newPrice',
|
||||
key: 'newPrice',
|
||||
render: (text: number) => `¥${text}`,
|
||||
},
|
||||
{
|
||||
title: '调整原因',
|
||||
dataIndex: 'reason',
|
||||
key: 'reason',
|
||||
},
|
||||
{
|
||||
title: '操作人',
|
||||
dataIndex: 'operator',
|
||||
key: 'operator',
|
||||
},
|
||||
];
|
||||
|
||||
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}>AI智能定价</Title>
|
||||
<Button type="primary" icon={<HistoryOutlined />}>
|
||||
<Link to="/Product/PriceHistory">价格历史</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* 定价参数表单 */}
|
||||
<Card title="定价参数" style={{ marginBottom: 24 }}>
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
initialValues={{
|
||||
productId: 'P001',
|
||||
cost: 150,
|
||||
currentPrice: 299,
|
||||
competitorPrice: 329,
|
||||
salesVolume: 120,
|
||||
pricingStrategy: 'balanced',
|
||||
}}
|
||||
>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={6}>
|
||||
<Item label="商品ID" name="productId">
|
||||
<Input placeholder="输入商品ID" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Item label="成本" name="cost" rules={[{ required: true, message: '请输入成本' }]}>
|
||||
<Input type="number" prefix="¥" placeholder="输入成本" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Item label="当前售价" name="currentPrice" rules={[{ required: true, message: '请输入当前售价' }]}>
|
||||
<Input type="number" prefix="¥" placeholder="输入当前售价" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Item label="竞品价格" name="competitorPrice">
|
||||
<Input type="number" prefix="¥" placeholder="输入竞品价格" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Item label="月销量" name="salesVolume">
|
||||
<Input type="number" placeholder="输入月销量" />
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<Item label="定价策略" name="pricingStrategy">
|
||||
<Select>
|
||||
<Option value="aggressive">激进定价</Option>
|
||||
<Option value="balanced">均衡定价</Option>
|
||||
<Option value="conservative">保守定价</Option>
|
||||
</Select>
|
||||
</Item>
|
||||
</Col>
|
||||
<Col span={12} style={{ display: 'flex', alignItems: 'flex-end' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CalculatorOutlined />}
|
||||
onClick={handleCalculatePrice}
|
||||
loading={loading}
|
||||
style={{ width: '100%' }}
|
||||
>
|
||||
计算智能定价
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
{/* 定价建议 */}
|
||||
<Card title="智能定价建议" style={{ marginBottom: 24 }}>
|
||||
{loading ? (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '40px 0' }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
) : pricingSuggestions.length > 0 ? (
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={pricingSuggestions}
|
||||
rowKey="id"
|
||||
pagination={false}
|
||||
/>
|
||||
) : (
|
||||
<Alert
|
||||
message="请输入定价参数并点击计算按钮"
|
||||
type="info"
|
||||
showIcon
|
||||
style={{ margin: '20px 0' }}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 价格历史 */}
|
||||
<Card title="价格调整历史">
|
||||
<Table
|
||||
columns={historyColumns}
|
||||
dataSource={priceHistory}
|
||||
rowKey="id"
|
||||
pagination={{ pageSize: 5 }}
|
||||
/>
|
||||
</Card>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default AIPricing;
|
||||
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;
|
||||
325
dashboard/src/pages/Product/ROIAnalysis/index.tsx
Normal file
325
dashboard/src/pages/Product/ROIAnalysis/index.tsx
Normal file
@@ -0,0 +1,325 @@
|
||||
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;
|
||||
38
dashboard/src/pages/Product/index.tsx
Normal file
38
dashboard/src/pages/Product/index.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Card, Layout, Typography, Row, Col, Button } from 'antd';
|
||||
import { PlusOutlined, UploadOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { Link } from 'umi';
|
||||
|
||||
const { Content } = Layout;
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
const ProductManagement: React.FC = () => {
|
||||
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>
|
||||
<Button type="primary" icon={<PlusOutlined />} style={{ marginRight: 8 }}>
|
||||
<Link to="/Product/ProductPublishForm">发布商品</Link>
|
||||
</Button>
|
||||
<Button icon={<UploadOutlined />} style={{ marginRight: 8 }}>
|
||||
<Link to="/Product/MaterialUpload">素材上传</Link>
|
||||
</Button>
|
||||
<Button icon={<EditOutlined />}>
|
||||
<Link to="/Product/ProductDetail">编辑商品</Link>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Card title="商品列表">
|
||||
<Text>商品管理页面内容</Text>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProductManagement;
|
||||
Reference in New Issue
Block a user