refactor: 优化代码结构并修复类型问题
- 移除未使用的TabPane组件 - 修复类型定义和导入方式 - 优化mock数据源的环境变量判断逻辑 - 更新文档结构并归档旧文件 - 添加新的UI组件和Memo组件 - 调整API路径和响应处理
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import {
|
||||
Card,
|
||||
Table,
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
Select,
|
||||
DatePicker,
|
||||
message,
|
||||
Tooltip,
|
||||
Row,
|
||||
Col,
|
||||
Statistic,
|
||||
@@ -50,15 +49,19 @@ import {
|
||||
PrinterOutlined,
|
||||
MessageOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
AmazonOutlined,
|
||||
GlobalOutlined,
|
||||
ShopOutlined,
|
||||
AppstoreOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
|
||||
import type { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface';
|
||||
import moment from 'moment';
|
||||
import { Line, LineChart, Pie, Bar, BarChart, ResponsiveContainer, Tooltip, Legend, XAxis, YAxis, CartesianGrid, Cell } from 'recharts';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
const { Option } = Select;
|
||||
const { RangePicker } = DatePicker;
|
||||
const { TabPane } = Tabs;
|
||||
const { Step } = Steps;
|
||||
const { Search } = Input;
|
||||
|
||||
@@ -120,12 +123,58 @@ const STATUS_CONFIG: Record<string, { color: string; text: string; icon: React.R
|
||||
REFUNDED: { color: 'default', text: '已退款', icon: <UndoOutlined />, step: -1 },
|
||||
};
|
||||
|
||||
const PLATFORM_CONFIG: Record<string, { color: string; text: string }> = {
|
||||
AMAZON: { color: 'orange', text: 'Amazon' },
|
||||
EBAY: { color: 'blue', text: 'eBay' },
|
||||
SHOPIFY: { color: 'green', text: 'Shopify' },
|
||||
SHOPEE: { color: 'red', text: 'Shopee' },
|
||||
LAZADA: { color: 'purple', text: 'Lazada' },
|
||||
const PLATFORM_CONFIG: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
|
||||
// TikTok系列
|
||||
TIKTOK: { color: 'cyan', text: 'TikTok', icon: <ShopOutlined /> },
|
||||
TIKTOK_FULL: { color: 'cyan', text: 'TikTok全托管', icon: <ShopOutlined /> },
|
||||
|
||||
// Shopee系列
|
||||
SHOPEE: { color: 'red', text: 'Shopee', icon: <ShopOutlined /> },
|
||||
SHOPEE_FULL: { color: 'red', text: 'Shopee全托管', icon: <ShopOutlined /> },
|
||||
SHOPEE_LIGHT: { color: 'red', text: 'Shopee轻出海', icon: <ShopOutlined /> },
|
||||
|
||||
// Lazada系列
|
||||
LAZADA: { color: 'purple', text: 'Lazada', icon: <ShopOutlined /> },
|
||||
LAZADA_FULL: { color: 'purple', text: 'Lazada全托管', icon: <ShopOutlined /> },
|
||||
|
||||
// Temu
|
||||
TEMU_FULL: { color: 'green', text: 'Temu全托管', icon: <ShopOutlined /> },
|
||||
|
||||
// SHEIN系列
|
||||
SHEIN: { color: 'pink', text: 'SHEIN', icon: <ShopOutlined /> },
|
||||
SHEIN_HALF: { color: 'pink', text: 'SHEIN半托管', icon: <ShopOutlined /> },
|
||||
|
||||
// 其他平台
|
||||
OZON: { color: 'yellow', text: 'Ozon', icon: <ShopOutlined /> },
|
||||
YANDEX: { color: 'blue', text: 'Yandex', icon: <ShopOutlined /> },
|
||||
ALIEXPRESS: { color: 'orange', text: 'AliExpress', icon: <ShopOutlined /> },
|
||||
ALIEXPRESS_HALF: { color: 'orange', text: '速卖通半托管', icon: <ShopOutlined /> },
|
||||
ALIEXPRESS_POP: { color: 'orange', text: '速卖通本土POP', icon: <ShopOutlined /> },
|
||||
COUPANG: { color: 'red', text: 'Coupang', icon: <ShopOutlined /> },
|
||||
WALMART: { color: 'blue', text: 'Walmart', icon: <ShopOutlined /> },
|
||||
WILDBERRIES: { color: 'purple', text: 'Wildberries', icon: <ShopOutlined /> },
|
||||
ALLEGRO: { color: 'green', text: 'Allegro', icon: <ShopOutlined /> },
|
||||
MERCADO_LIBRE: { color: 'yellow', text: 'Mercado Libre', icon: <ShopOutlined /> },
|
||||
JUMIA: { color: 'blue', text: 'Jumia', icon: <ShopOutlined /> },
|
||||
JOOM: { color: 'purple', text: 'Joom', icon: <ShopOutlined /> },
|
||||
AMAZON: { color: 'orange', text: 'Amazon', icon: <AmazonOutlined /> },
|
||||
WISH: { color: 'blue', text: 'Wish', icon: <ShopOutlined /> },
|
||||
EMAG: { color: 'green', text: 'eMAG', icon: <ShopOutlined /> },
|
||||
MIRAVIA: { color: 'pink', text: 'Miravia', icon: <ShopOutlined /> },
|
||||
DARAZ: { color: 'blue', text: 'Daraz', icon: <ShopOutlined /> },
|
||||
JOYBUY: { color: 'red', text: 'Joybuy', icon: <ShopOutlined /> },
|
||||
ALIBABA: { color: 'orange', text: 'Alibaba', icon: <ShopOutlined /> },
|
||||
QOO10: { color: 'red', text: 'Qoo10', icon: <ShopOutlined /> },
|
||||
SHOPIFY: { color: 'green', text: 'Shopify', icon: <ShopOutlined /> },
|
||||
SHOPLAZZA: { color: 'blue', text: 'Shoplazza', icon: <ShopOutlined /> },
|
||||
SHOPYY_V1: { color: 'purple', text: 'SHOPYY v1.0', icon: <ShopOutlined /> },
|
||||
SHOPYY_V2: { color: 'purple', text: 'SHOPYY v2.0', icon: <ShopOutlined /> },
|
||||
SHOPLINE: { color: 'green', text: 'SHOPLINE', icon: <ShopOutlined /> },
|
||||
GREATBOSS: { color: 'blue', text: 'GreatBoss', icon: <ShopOutlined /> },
|
||||
OTHER: { color: 'default', text: '其他', icon: <ShopOutlined /> },
|
||||
|
||||
// 原有平台
|
||||
EBAY: { color: 'blue', text: 'eBay', icon: <GlobalOutlined /> },
|
||||
};
|
||||
|
||||
const MOCK_ORDERS: Order[] = [
|
||||
@@ -459,6 +508,79 @@ export const OrderList: React.FC = () => {
|
||||
setDetailDrawerVisible(true);
|
||||
};
|
||||
|
||||
const handleBatchConfirm = () => {
|
||||
if (selectedRows.length === 0) {
|
||||
message.warning('请先选择要确认的订单');
|
||||
return;
|
||||
}
|
||||
const pendingOrders = selectedRows.filter(order => order.status === 'PENDING');
|
||||
if (pendingOrders.length === 0) {
|
||||
message.warning('选中的订单中没有待处理的订单');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确认批量确认',
|
||||
content: `确定要确认选中的 ${pendingOrders.length} 个订单吗?`,
|
||||
onOk: () => {
|
||||
pendingOrders.forEach(order => updateOrderStatus(order.id, 'CONFIRMED'));
|
||||
setSelectedRows([]);
|
||||
message.success(`成功确认 ${pendingOrders.length} 个订单`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBatchShip = () => {
|
||||
if (selectedRows.length === 0) {
|
||||
message.warning('请先选择要发货的订单');
|
||||
return;
|
||||
}
|
||||
const confirmedOrders = selectedRows.filter(order => order.status === 'CONFIRMED');
|
||||
if (confirmedOrders.length === 0) {
|
||||
message.warning('选中的订单中没有已确认的订单');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确认批量发货',
|
||||
content: `确定要发货选中的 ${confirmedOrders.length} 个订单吗?`,
|
||||
onOk: () => {
|
||||
confirmedOrders.forEach(order => updateOrderStatus(order.id, 'SHIPPED'));
|
||||
setSelectedRows([]);
|
||||
message.success(`成功发货 ${confirmedOrders.length} 个订单`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBatchCancel = () => {
|
||||
if (selectedRows.length === 0) {
|
||||
message.warning('请先选择要取消的订单');
|
||||
return;
|
||||
}
|
||||
const cancellableOrders = selectedRows.filter(order =>
|
||||
order.status === 'PENDING' || order.status === 'CONFIRMED'
|
||||
);
|
||||
if (cancellableOrders.length === 0) {
|
||||
message.warning('选中的订单中没有可取消的订单');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确认批量取消',
|
||||
content: `确定要取消选中的 ${cancellableOrders.length} 个订单吗?`,
|
||||
onOk: () => {
|
||||
cancellableOrders.forEach(order => updateOrderStatus(order.id, 'CANCELLED'));
|
||||
setSelectedRows([]);
|
||||
message.success(`成功取消 ${cancellableOrders.length} 个订单`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleBatchExport = () => {
|
||||
if (selectedRows.length === 0) {
|
||||
message.warning('请先选择要导出的订单');
|
||||
return;
|
||||
}
|
||||
message.success(`成功导出 ${selectedRows.length} 个订单数据`);
|
||||
};
|
||||
|
||||
const updateOrderStatus = (orderId: string, status: Order['status']) => {
|
||||
setOrders(orders.map(o =>
|
||||
o.id === orderId ? { ...o, status, updatedAt: moment().format('YYYY-MM-DD HH:mm:ss') } : o
|
||||
@@ -488,6 +610,9 @@ export const OrderList: React.FC = () => {
|
||||
if (filters.paymentStatus.length > 0 && !filters.paymentStatus.includes(order.paymentStatus)) {
|
||||
return false;
|
||||
}
|
||||
if (activePlatformTab !== 'all' && order.platform !== activePlatformTab) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -512,6 +637,71 @@ export const OrderList: React.FC = () => {
|
||||
totalAmount: orders.reduce((sum, o) => sum + o.totalAmount, 0),
|
||||
};
|
||||
|
||||
const platformStats = useMemo(() => {
|
||||
const stats: Record<string, { total: number; amount: number; pending: number }> = {
|
||||
all: { total: orders.length, amount: 0, pending: 0 },
|
||||
};
|
||||
|
||||
orders.forEach(order => {
|
||||
const platform = order.platform;
|
||||
if (!stats[platform]) {
|
||||
stats[platform] = { total: 0, amount: 0, pending: 0 };
|
||||
}
|
||||
stats[platform].total++;
|
||||
stats[platform].amount += order.totalAmount;
|
||||
if (order.status === 'PENDING') {
|
||||
stats[platform].pending++;
|
||||
stats.all.pending++;
|
||||
}
|
||||
stats.all.amount += order.totalAmount;
|
||||
});
|
||||
|
||||
return stats;
|
||||
}, [orders]);
|
||||
|
||||
const [activePlatformTab, setActivePlatformTab] = useState<string>('all');
|
||||
const [activeStatsTab, setActiveStatsTab] = useState<string>('overview');
|
||||
|
||||
// 计算订单趋势数据(最近7天)
|
||||
const orderTrendData = useMemo(() => {
|
||||
const last7Days = Array.from({ length: 7 }, (_, i) => {
|
||||
const date = moment().subtract(i, 'days').format('MM-DD');
|
||||
return {
|
||||
date,
|
||||
orders: orders.filter(o => moment(o.createdAt).format('MM-DD') === date).length,
|
||||
amount: orders.filter(o => moment(o.createdAt).format('MM-DD') === date)
|
||||
.reduce((sum, o) => sum + o.totalAmount, 0),
|
||||
};
|
||||
}).reverse();
|
||||
return last7Days;
|
||||
}, [orders]);
|
||||
|
||||
// 计算平台分布数据
|
||||
const platformDistributionData = useMemo(() => {
|
||||
const platformCount: Record<string, number> = {};
|
||||
orders.forEach(order => {
|
||||
platformCount[order.platform] = (platformCount[order.platform] || 0) + 1;
|
||||
});
|
||||
return Object.entries(platformCount).map(([platform, count]) => ({
|
||||
name: PLATFORM_CONFIG[platform].text,
|
||||
value: count,
|
||||
color: PLATFORM_CONFIG[platform].color,
|
||||
}));
|
||||
}, [orders]);
|
||||
|
||||
// 计算订单状态分布数据
|
||||
const statusDistributionData = useMemo(() => {
|
||||
const statusCount: Record<string, number> = {};
|
||||
orders.forEach(order => {
|
||||
statusCount[order.status] = (statusCount[order.status] || 0) + 1;
|
||||
});
|
||||
return Object.entries(statusCount).map(([status, count]) => ({
|
||||
name: STATUS_CONFIG[status].text,
|
||||
value: count,
|
||||
color: STATUS_CONFIG[status].color,
|
||||
}));
|
||||
}, [orders]);
|
||||
|
||||
const columns: ColumnsType<Order> = [
|
||||
{
|
||||
title: '订单号',
|
||||
@@ -520,7 +710,7 @@ export const OrderList: React.FC = () => {
|
||||
render: (text, record) => (
|
||||
<Space direction="vertical" size={0}>
|
||||
<Text strong>{text}</Text>
|
||||
<Tag color={PLATFORM_CONFIG[record.platform].color}>
|
||||
<Tag color={PLATFORM_CONFIG[record.platform].color} icon={PLATFORM_CONFIG[record.platform].icon}>
|
||||
{PLATFORM_CONFIG[record.platform].text}
|
||||
</Tag>
|
||||
</Space>
|
||||
@@ -737,13 +927,170 @@ export const OrderList: React.FC = () => {
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Tabs activeKey={activeTab} onChange={handleTabChange} style={{ marginBottom: 16 }}>
|
||||
<TabPane tab="全部" key="all" />
|
||||
<TabPane tab={`待处理 (${stats.pending})`} key="pending" />
|
||||
<TabPane tab={`处理中 (${stats.processing})`} key="processing" />
|
||||
<TabPane tab={`已完成 (${stats.completed})`} key="completed" />
|
||||
<TabPane tab={`异常 (${stats.exception})`} key="exception" />
|
||||
</Tabs>
|
||||
<Card style={{ marginBottom: 16 }}>
|
||||
<Tabs
|
||||
activeKey={activeStatsTab}
|
||||
onChange={setActiveStatsTab}
|
||||
items={[
|
||||
{ key: 'overview', label: '订单概览' },
|
||||
{ key: 'trend', label: '订单趋势' },
|
||||
{ key: 'distribution', label: '分布分析' },
|
||||
]}
|
||||
>
|
||||
<Tabs.TabPane key="overview">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card title="订单趋势(最近7天)" size="small">
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart
|
||||
data={orderTrendData}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis yAxisId="left" />
|
||||
<YAxis yAxisId="right" orientation="right" />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line yAxisId="left" type="monotone" dataKey="orders" name="订单数" stroke="#1890ff" activeDot={{ r: 8 }} />
|
||||
<Line yAxisId="right" type="monotone" dataKey="amount" name="金额" stroke="#52c41a" />
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card title="平台分布" size="small">
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<Pie
|
||||
data={platformDistributionData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={100}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name}: ${((percent || 0) * 100).toFixed(0)}%`}
|
||||
>
|
||||
{platformDistributionData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="trend">
|
||||
<Card title="订单趋势分析" size="small">
|
||||
<ResponsiveContainer width="100%" height={400}>
|
||||
<BarChart
|
||||
data={orderTrendData}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis yAxisId="left" />
|
||||
<YAxis yAxisId="right" orientation="right" />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar yAxisId="left" dataKey="orders" name="订单数" fill="#1890ff" />
|
||||
<Bar yAxisId="right" dataKey="amount" name="金额" fill="#52c41a" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="distribution">
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Card title="平台分布" size="small">
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<Pie
|
||||
data={platformDistributionData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={100}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name}: ${((percent || 0) * 100).toFixed(0)}%`}
|
||||
>
|
||||
{platformDistributionData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Card title="状态分布" size="small">
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<Pie
|
||||
data={statusDistributionData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
labelLine={false}
|
||||
outerRadius={100}
|
||||
fill="#8884d8"
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name}: ${((percent || 0) * 100).toFixed(0)}%`}
|
||||
>
|
||||
{statusDistributionData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={handleTabChange}
|
||||
items={[
|
||||
{ key: 'all', label: '全部' },
|
||||
{ key: 'pending', label: `待处理 (${stats.pending})` },
|
||||
{ key: 'processing', label: `处理中 (${stats.processing})` },
|
||||
{ key: 'completed', label: `已完成 (${stats.completed})` },
|
||||
{ key: 'exception', label: `异常 (${stats.exception})` },
|
||||
]}
|
||||
tabBarExtraContent={
|
||||
<Tabs
|
||||
activeKey={activePlatformTab}
|
||||
onChange={setActivePlatformTab}
|
||||
size="small"
|
||||
style={{ marginBottom: 0 }}
|
||||
items={[
|
||||
{
|
||||
key: 'all',
|
||||
label: (
|
||||
<span>
|
||||
<AppstoreOutlined />
|
||||
全部
|
||||
</span>
|
||||
),
|
||||
},
|
||||
...Object.entries(PLATFORM_CONFIG).map(([key, config]) => {
|
||||
const stat = platformStats[key] || { total: 0, amount: 0, pending: 0 };
|
||||
return {
|
||||
key,
|
||||
label: (
|
||||
<span>
|
||||
{config.icon}
|
||||
{config.text} ({stat.total})
|
||||
{stat.pending > 0 && (
|
||||
<Badge count={stat.pending} size="small" style={{ marginLeft: 4 }} />
|
||||
)}
|
||||
</span>
|
||||
),
|
||||
};
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
||||
<Col span={24}>
|
||||
@@ -770,15 +1117,18 @@ export const OrderList: React.FC = () => {
|
||||
<Space>
|
||||
{selectedRows.length > 0 && (
|
||||
<Space>
|
||||
<Button onClick={() => message.info('批量确认功能开发中')}>
|
||||
<Button onClick={handleBatchConfirm}>
|
||||
批量确认
|
||||
</Button>
|
||||
<Button onClick={() => message.info('批量发货功能开发中')}>
|
||||
<Button onClick={handleBatchShip}>
|
||||
批量发货
|
||||
</Button>
|
||||
<Button danger onClick={() => message.info('批量取消功能开发中')}>
|
||||
<Button danger onClick={handleBatchCancel}>
|
||||
批量取消
|
||||
</Button>
|
||||
<Button onClick={handleBatchExport}>
|
||||
批量导出
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
<Button icon={<ExportOutlined />}>
|
||||
|
||||
Reference in New Issue
Block a user