feat: 新增多模块功能与服务实现

新增广告计划、用户资产、B2B交易、合规规则等核心模型
实现爬虫工作器、贸易服务、现金流预测等业务服务
添加RBAC权限测试、压力测试等测试用例
完善扩展程序的消息处理与内容脚本功能
重构应用入口与文档生成器
更新项目规则与业务闭环分析文档
This commit is contained in:
2026-03-18 09:38:09 +08:00
parent 72cd7f6f45
commit 037e412aad
158 changed files with 50217 additions and 1313 deletions

View File

@@ -0,0 +1,544 @@
import React, { useState, useEffect } from 'react';
import {
Card,
Table,
Tag,
Button,
Space,
Modal,
Form,
Input,
Select,
DatePicker,
message,
Tooltip,
Row,
Col,
Statistic,
Badge,
Dropdown,
Menu,
Tabs,
} from 'antd';
import {
EyeOutlined,
SyncOutlined,
DownloadOutlined,
ReloadOutlined,
FilterOutlined,
CheckCircleOutlined,
ClockCircleOutlined,
TruckOutlined,
CloseCircleOutlined,
MoreOutlined,
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
const { Option } = Select;
const { RangePicker } = DatePicker;
const { TabPane } = Tabs;
interface Order {
id: string;
orderId: string;
platform: 'AMAZON' | 'EBAY' | 'SHOPIFY' | 'SHOPEE' | 'LAZADA';
customerName: string;
customerEmail: string;
totalAmount: number;
currency: string;
status: 'PULLED' | 'PENDING_REVIEW' | 'CONFIRMED' | 'ALLOCATED' | 'READY_TO_SHIP' | 'SHIPPED' | 'DELIVERED' | 'CANCELLED';
paymentStatus: 'PENDING' | 'PAID' | 'REFUNDED';
itemCount: number;
createdAt: string;
updatedAt: string;
shopId: string;
shopName: string;
}
const ORDER_STATUS_MAP: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
PULLED: { color: 'default', text: 'Pulled', icon: <ClockCircleOutlined /> },
PENDING_REVIEW: { color: 'processing', text: 'Pending Review', icon: <SyncOutlined spin /> },
CONFIRMED: { color: 'blue', text: 'Confirmed', icon: <CheckCircleOutlined /> },
ALLOCATED: { color: 'cyan', text: 'Allocated', icon: <CheckCircleOutlined /> },
READY_TO_SHIP: { color: 'geekblue', text: 'Ready to Ship', icon: <TruckOutlined /> },
SHIPPED: { color: 'purple', text: 'Shipped', icon: <TruckOutlined /> },
DELIVERED: { color: 'success', text: 'Delivered', icon: <CheckCircleOutlined /> },
CANCELLED: { color: 'error', text: 'Cancelled', icon: <CloseCircleOutlined /> },
};
const PAYMENT_STATUS_MAP: Record<string, { color: string; text: string }> = {
PENDING: { color: 'warning', text: 'Pending' },
PAID: { color: 'success', text: 'Paid' },
REFUNDED: { color: 'default', text: 'Refunded' },
};
const PLATFORM_MAP: 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' },
};
export const OrderList: React.FC = () => {
const [loading, setLoading] = useState(false);
const [orders, setOrders] = useState<Order[]>([]);
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
const [detailModalVisible, setDetailModalVisible] = useState(false);
const [activeTab, setActiveTab] = useState('all');
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [stats, setStats] = useState({
total: 0,
pending: 0,
processing: 0,
shipped: 0,
totalAmount: 0,
});
useEffect(() => {
fetchOrders();
}, []);
const fetchOrders = async () => {
setLoading(true);
try {
const mockOrders: Order[] = [
{
id: '1',
orderId: 'ORD-2026-001',
platform: 'AMAZON',
customerName: 'John Smith',
customerEmail: 'john.smith@email.com',
totalAmount: 159.99,
currency: 'USD',
status: 'SHIPPED',
paymentStatus: 'PAID',
itemCount: 3,
createdAt: '2026-03-18 10:30:00',
updatedAt: '2026-03-18 14:00:00',
shopId: 'SHOP_001',
shopName: 'Main Store',
},
{
id: '2',
orderId: 'ORD-2026-002',
platform: 'EBAY',
customerName: 'Emma Wilson',
customerEmail: 'emma.wilson@email.com',
totalAmount: 89.50,
currency: 'USD',
status: 'PENDING_REVIEW',
paymentStatus: 'PAID',
itemCount: 2,
createdAt: '2026-03-18 09:15:00',
updatedAt: '2026-03-18 09:15:00',
shopId: 'SHOP_002',
shopName: 'eBay Outlet',
},
{
id: '3',
orderId: 'ORD-2026-003',
platform: 'SHOPIFY',
customerName: 'Michael Brown',
customerEmail: 'michael.brown@email.com',
totalAmount: 245.00,
currency: 'USD',
status: 'CONFIRMED',
paymentStatus: 'PAID',
itemCount: 5,
createdAt: '2026-03-17 16:45:00',
updatedAt: '2026-03-18 08:00:00',
shopId: 'SHOP_003',
shopName: 'Brand Store',
},
{
id: '4',
orderId: 'ORD-2026-004',
platform: 'SHOPEE',
customerName: 'Sarah Davis',
customerEmail: 'sarah.davis@email.com',
totalAmount: 78.00,
currency: 'USD',
status: 'DELIVERED',
paymentStatus: 'PAID',
itemCount: 1,
createdAt: '2026-03-15 11:20:00',
updatedAt: '2026-03-17 15:30:00',
shopId: 'SHOP_004',
shopName: 'Shopee Mall',
},
{
id: '5',
orderId: 'ORD-2026-005',
platform: 'AMAZON',
customerName: 'David Lee',
customerEmail: 'david.lee@email.com',
totalAmount: 320.00,
currency: 'USD',
status: 'PULLED',
paymentStatus: 'PENDING',
itemCount: 4,
createdAt: '2026-03-18 12:00:00',
updatedAt: '2026-03-18 12:00:00',
shopId: 'SHOP_001',
shopName: 'Main Store',
},
];
setOrders(mockOrders);
calculateStats(mockOrders);
} catch (error) {
message.error('Failed to load orders');
} finally {
setLoading(false);
}
};
const calculateStats = (orderList: Order[]) => {
setStats({
total: orderList.length,
pending: orderList.filter((o) => o.status === 'PENDING_REVIEW' || o.status === 'PULLED').length,
processing: orderList.filter((o) => ['CONFIRMED', 'ALLOCATED', 'READY_TO_SHIP'].includes(o.status)).length,
shipped: orderList.filter((o) => o.status === 'SHIPPED').length,
totalAmount: orderList.reduce((sum, o) => sum + o.totalAmount, 0),
});
};
const handleViewDetail = (order: Order) => {
setSelectedOrder(order);
setDetailModalVisible(true);
};
const handleStatusChange = async (orderId: string, newStatus: string) => {
setLoading(true);
try {
console.log('Updating order status:', { orderId, newStatus });
await new Promise((resolve) => setTimeout(resolve, 500));
message.success('Status updated successfully');
fetchOrders();
} catch (error) {
message.error('Failed to update status');
} finally {
setLoading(false);
}
};
const handleBatchAction = async (action: string) => {
if (selectedRowKeys.length === 0) {
message.warning('Please select orders first');
return;
}
setLoading(true);
try {
console.log('Batch action:', { action, orderIds: selectedRowKeys });
await new Promise((resolve) => setTimeout(resolve, 500));
message.success(`Batch ${action} completed for ${selectedRowKeys.length} orders`);
setSelectedRowKeys([]);
fetchOrders();
} catch (error) {
message.error('Batch action failed');
} finally {
setLoading(false);
}
};
const handleExport = () => {
message.info('Exporting orders...');
};
const getFilteredOrders = () => {
if (activeTab === 'all') return orders;
if (activeTab === 'pending') return orders.filter((o) => ['PULLED', 'PENDING_REVIEW'].includes(o.status));
if (activeTab === 'processing') return orders.filter((o) => ['CONFIRMED', 'ALLOCATED', 'READY_TO_SHIP'].includes(o.status));
if (activeTab === 'shipped') return orders.filter((o) => o.status === 'SHIPPED');
if (activeTab === 'delivered') return orders.filter((o) => o.status === 'DELIVERED');
return orders;
};
const columns: ColumnsType<Order> = [
{
title: 'Order ID',
dataIndex: 'orderId',
key: 'orderId',
width: 140,
render: (text: string, record) => (
<a onClick={() => handleViewDetail(record)}>{text}</a>
),
},
{
title: 'Platform',
dataIndex: 'platform',
key: 'platform',
width: 100,
render: (platform: string) => {
const config = PLATFORM_MAP[platform];
return <Tag color={config.color}>{config.text}</Tag>;
},
},
{
title: 'Customer',
dataIndex: 'customerName',
key: 'customerName',
width: 140,
},
{
title: 'Items',
dataIndex: 'itemCount',
key: 'itemCount',
width: 70,
align: 'center',
},
{
title: 'Amount',
dataIndex: 'totalAmount',
key: 'totalAmount',
width: 100,
render: (amount: number) => (
<span style={{ fontWeight: 'bold' }}>${amount.toFixed(2)}</span>
),
},
{
title: 'Payment',
dataIndex: 'paymentStatus',
key: 'paymentStatus',
width: 90,
render: (status: string) => {
const config = PAYMENT_STATUS_MAP[status];
return <Tag color={config.color}>{config.text}</Tag>;
},
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
width: 130,
render: (status: string) => {
const config = ORDER_STATUS_MAP[status];
return <Tag icon={config.icon} color={config.color}>{config.text}</Tag>;
},
},
{
title: 'Shop',
dataIndex: 'shopName',
key: 'shopName',
width: 120,
},
{
title: 'Created',
dataIndex: 'createdAt',
key: 'createdAt',
width: 160,
},
{
title: 'Actions',
key: 'action',
width: 120,
fixed: 'right',
render: (_, record) => (
<Space>
<Tooltip title="View Details">
<Button type="link" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)} />
</Tooltip>
<Dropdown
overlay={
<Menu>
{record.status === 'PULLED' && (
<Menu.Item key="review" onClick={() => handleStatusChange(record.orderId, 'PENDING_REVIEW')}>
Submit for Review
</Menu.Item>
)}
{record.status === 'PENDING_REVIEW' && (
<Menu.Item key="confirm" onClick={() => handleStatusChange(record.orderId, 'CONFIRMED')}>
Confirm Order
</Menu.Item>
)}
{record.status === 'CONFIRMED' && (
<Menu.Item key="allocate" onClick={() => handleStatusChange(record.orderId, 'ALLOCATED')}>
Allocate Stock
</Menu.Item>
)}
<Menu.Item key="cancel" onClick={() => handleStatusChange(record.orderId, 'CANCELLED')}>
Cancel Order
</Menu.Item>
</Menu>
}
>
<Button type="link" icon={<MoreOutlined />} />
</Dropdown>
</Space>
),
},
];
const rowSelection = {
selectedRowKeys,
onChange: setSelectedRowKeys,
};
return (
<div className="order-list-page">
<Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={4}>
<Card>
<Statistic title="Total Orders" value={stats.total} />
</Card>
</Col>
<Col span={4}>
<Card>
<Statistic
title="Pending"
value={stats.pending}
valueStyle={{ color: '#faad14' }}
prefix={<ClockCircleOutlined />}
/>
</Card>
</Col>
<Col span={4}>
<Card>
<Statistic
title="Processing"
value={stats.processing}
valueStyle={{ color: '#1890ff' }}
prefix={<SyncOutlined spin />}
/>
</Card>
</Col>
<Col span={4}>
<Card>
<Statistic
title="Shipped"
value={stats.shipped}
valueStyle={{ color: '#722ed1' }}
prefix={<TruckOutlined />}
/>
</Card>
</Col>
<Col span={8}>
<Card>
<Statistic
title="Total Amount"
value={stats.totalAmount}
precision={2}
prefix="$"
valueStyle={{ color: '#52c41a' }}
/>
</Card>
</Col>
</Row>
<Card
title="Order Management"
extra={
<Space>
<Button icon={<ReloadOutlined />} onClick={fetchOrders}>
Refresh
</Button>
<Button icon={<DownloadOutlined />} onClick={handleExport}>
Export
</Button>
</Space>
}
>
<Tabs activeKey={activeTab} onChange={setActiveTab}>
<TabPane tab="All Orders" key="all" />
<TabPane tab={<Badge count={stats.pending} offset={[10, 0]} size="small">Pending</Badge>} key="pending" />
<TabPane tab="Processing" key="processing" />
<TabPane tab="Shipped" key="shipped" />
<TabPane tab="Delivered" key="delivered" />
</Tabs>
<div style={{ marginBottom: 16 }}>
<Space>
<Button
type="primary"
disabled={selectedRowKeys.length === 0}
onClick={() => handleBatchAction('confirm')}
>
Batch Confirm ({selectedRowKeys.length})
</Button>
<Button
disabled={selectedRowKeys.length === 0}
onClick={() => handleBatchAction('allocate')}
>
Batch Allocate
</Button>
<Button
danger
disabled={selectedRowKeys.length === 0}
onClick={() => handleBatchAction('cancel')}
>
Batch Cancel
</Button>
</Space>
</div>
<Table
rowSelection={rowSelection}
columns={columns}
dataSource={getFilteredOrders()}
rowKey="id"
loading={loading}
scroll={{ x: 1300 }}
pagination={{
showSizeChanger: true,
showTotal: (total) => `Total ${total} orders`,
}}
/>
</Card>
<Modal
title="Order Details"
open={detailModalVisible}
onCancel={() => setDetailModalVisible(false)}
footer={[
<Button key="close" onClick={() => setDetailModalVisible(false)}>
Close
</Button>,
]}
width={700}
>
{selectedOrder && (
<>
<Row gutter={16}>
<Col span={12}>
<Card size="small" title="Order Info">
<p><strong>Order ID:</strong> {selectedOrder.orderId}</p>
<p><strong>Platform:</strong> <Tag color={PLATFORM_MAP[selectedOrder.platform].color}>{PLATFORM_MAP[selectedOrder.platform].text}</Tag></p>
<p><strong>Status:</strong> <Tag color={ORDER_STATUS_MAP[selectedOrder.status].color}>{ORDER_STATUS_MAP[selectedOrder.status].text}</Tag></p>
<p><strong>Payment:</strong> <Tag color={PAYMENT_STATUS_MAP[selectedOrder.paymentStatus].color}>{PAYMENT_STATUS_MAP[selectedOrder.paymentStatus].text}</Tag></p>
</Card>
</Col>
<Col span={12}>
<Card size="small" title="Customer Info">
<p><strong>Name:</strong> {selectedOrder.customerName}</p>
<p><strong>Email:</strong> {selectedOrder.customerEmail}</p>
<p><strong>Shop:</strong> {selectedOrder.shopName}</p>
</Card>
</Col>
</Row>
<Card size="small" title="Amount" style={{ marginTop: 16 }}>
<Row>
<Col span={12}>
<Statistic
title="Total Amount"
value={selectedOrder.totalAmount}
precision={2}
prefix={selectedOrder.currency === 'USD' ? '$' : selectedOrder.currency}
/>
</Col>
<Col span={12}>
<Statistic title="Items" value={selectedOrder.itemCount} />
</Col>
</Row>
</Card>
<Card size="small" title="Timestamps" style={{ marginTop: 16 }}>
<p><strong>Created:</strong> {selectedOrder.createdAt}</p>
<p><strong>Updated:</strong> {selectedOrder.updatedAt}</p>
</Card>
</>
)}
</Modal>
</div>
);
};
export default OrderList;