2026-03-19 01:39:34 +08:00
|
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
2026-03-18 09:38:09 +08:00
|
|
|
|
import {
|
|
|
|
|
|
Card,
|
|
|
|
|
|
Table,
|
|
|
|
|
|
Tag,
|
|
|
|
|
|
Button,
|
|
|
|
|
|
Space,
|
|
|
|
|
|
Modal,
|
|
|
|
|
|
Form,
|
|
|
|
|
|
Input,
|
|
|
|
|
|
Select,
|
|
|
|
|
|
DatePicker,
|
|
|
|
|
|
message,
|
|
|
|
|
|
Tooltip,
|
|
|
|
|
|
Row,
|
|
|
|
|
|
Col,
|
|
|
|
|
|
Statistic,
|
|
|
|
|
|
Badge,
|
|
|
|
|
|
Dropdown,
|
|
|
|
|
|
Menu,
|
|
|
|
|
|
Tabs,
|
2026-03-19 01:39:34 +08:00
|
|
|
|
InputNumber,
|
|
|
|
|
|
Timeline,
|
|
|
|
|
|
Alert,
|
|
|
|
|
|
Drawer,
|
|
|
|
|
|
Divider,
|
|
|
|
|
|
Typography,
|
|
|
|
|
|
Image,
|
|
|
|
|
|
Steps,
|
2026-03-18 09:38:09 +08:00
|
|
|
|
} from 'antd';
|
|
|
|
|
|
import {
|
|
|
|
|
|
EyeOutlined,
|
|
|
|
|
|
SyncOutlined,
|
|
|
|
|
|
CheckCircleOutlined,
|
|
|
|
|
|
ClockCircleOutlined,
|
|
|
|
|
|
TruckOutlined,
|
|
|
|
|
|
CloseCircleOutlined,
|
|
|
|
|
|
MoreOutlined,
|
2026-03-19 01:39:34 +08:00
|
|
|
|
SearchOutlined,
|
|
|
|
|
|
ExportOutlined,
|
|
|
|
|
|
WarningOutlined,
|
|
|
|
|
|
FileTextOutlined,
|
|
|
|
|
|
EnvironmentOutlined,
|
|
|
|
|
|
CheckOutlined,
|
|
|
|
|
|
UndoOutlined,
|
|
|
|
|
|
CustomerServiceOutlined,
|
|
|
|
|
|
DollarOutlined,
|
|
|
|
|
|
FilterOutlined,
|
|
|
|
|
|
SortAscendingOutlined,
|
|
|
|
|
|
PrinterOutlined,
|
|
|
|
|
|
MessageOutlined,
|
|
|
|
|
|
ExclamationCircleOutlined,
|
2026-03-18 09:38:09 +08:00
|
|
|
|
} from '@ant-design/icons';
|
2026-03-19 01:39:34 +08:00
|
|
|
|
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
|
|
|
|
|
|
import type { FilterValue, SorterResult, TableCurrentDataSource } from 'antd/es/table/interface';
|
|
|
|
|
|
import moment from 'moment';
|
2026-03-18 09:38:09 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const { Title, Text } = Typography;
|
2026-03-18 09:38:09 +08:00
|
|
|
|
const { Option } = Select;
|
|
|
|
|
|
const { RangePicker } = DatePicker;
|
|
|
|
|
|
const { TabPane } = Tabs;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const { Step } = Steps;
|
|
|
|
|
|
const { Search } = Input;
|
2026-03-18 09:38:09 +08:00
|
|
|
|
|
|
|
|
|
|
interface Order {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
orderId: string;
|
|
|
|
|
|
platform: 'AMAZON' | 'EBAY' | 'SHOPIFY' | 'SHOPEE' | 'LAZADA';
|
|
|
|
|
|
customerName: string;
|
|
|
|
|
|
customerEmail: string;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
customerPhone: string;
|
|
|
|
|
|
customerAddress: string;
|
2026-03-18 09:38:09 +08:00
|
|
|
|
totalAmount: number;
|
|
|
|
|
|
currency: string;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
status: 'PENDING' | 'CONFIRMED' | 'SHIPPED' | 'DELIVERED' | 'COMPLETED' | 'CANCELLED' | 'REFUNDING' | 'REFUNDED';
|
2026-03-18 09:38:09 +08:00
|
|
|
|
paymentStatus: 'PENDING' | 'PAID' | 'REFUNDED';
|
|
|
|
|
|
itemCount: number;
|
|
|
|
|
|
createdAt: string;
|
|
|
|
|
|
updatedAt: string;
|
|
|
|
|
|
shopId: string;
|
|
|
|
|
|
shopName: string;
|
2026-03-19 01:39:34 +08:00
|
|
|
|
trackingNumber?: string;
|
|
|
|
|
|
carrier?: string;
|
|
|
|
|
|
estimatedDelivery?: string;
|
|
|
|
|
|
actualDelivery?: string;
|
|
|
|
|
|
exceptionReason?: string;
|
|
|
|
|
|
items: OrderItem[];
|
2026-03-18 09:38:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
interface OrderItem {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
productName: string;
|
|
|
|
|
|
sku: string;
|
|
|
|
|
|
quantity: number;
|
|
|
|
|
|
price: number;
|
|
|
|
|
|
image: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface FilterState {
|
|
|
|
|
|
keyword: string;
|
|
|
|
|
|
status: string[];
|
|
|
|
|
|
platform: string[];
|
|
|
|
|
|
paymentStatus: string[];
|
|
|
|
|
|
dateRange: [moment.Moment, moment.Moment] | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface SortState {
|
|
|
|
|
|
field: string;
|
|
|
|
|
|
order: 'ascend' | 'descend' | null;
|
|
|
|
|
|
}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const STATUS_CONFIG: Record<string, { color: string; text: string; icon: React.ReactNode; step: number }> = {
|
|
|
|
|
|
PENDING: { color: 'default', text: '待处理', icon: <ClockCircleOutlined />, step: 0 },
|
|
|
|
|
|
CONFIRMED: { color: 'blue', text: '已确认', icon: <CheckCircleOutlined />, step: 1 },
|
|
|
|
|
|
SHIPPED: { color: 'processing', text: '已发货', icon: <TruckOutlined />, step: 2 },
|
|
|
|
|
|
DELIVERED: { color: 'cyan', text: '已送达', icon: <EnvironmentOutlined />, step: 3 },
|
|
|
|
|
|
COMPLETED: { color: 'success', text: '已完成', icon: <CheckCircleOutlined />, step: 4 },
|
|
|
|
|
|
CANCELLED: { color: 'error', text: '已取消', icon: <CloseCircleOutlined />, step: -1 },
|
|
|
|
|
|
REFUNDING: { color: 'warning', text: '退款中', icon: <DollarOutlined />, step: -1 },
|
|
|
|
|
|
REFUNDED: { color: 'default', text: '已退款', icon: <UndoOutlined />, step: -1 },
|
2026-03-18 09:38:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const PLATFORM_CONFIG: Record<string, { color: string; text: string }> = {
|
2026-03-18 09:38:09 +08:00
|
|
|
|
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' },
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const MOCK_ORDERS: Order[] = [
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '1',
|
|
|
|
|
|
orderId: 'ORD-2026-001',
|
|
|
|
|
|
platform: 'AMAZON',
|
|
|
|
|
|
customerName: '张三',
|
|
|
|
|
|
customerEmail: 'zhangsan@email.com',
|
|
|
|
|
|
customerPhone: '+86-138-0000-0001',
|
|
|
|
|
|
customerAddress: '北京市朝阳区xxx街道xxx号',
|
|
|
|
|
|
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: '主店铺',
|
|
|
|
|
|
trackingNumber: '1Z999AA10123456784',
|
|
|
|
|
|
carrier: 'UPS',
|
|
|
|
|
|
estimatedDelivery: '2026-03-22',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ id: 'i1', productName: '工业温度传感器', sku: 'TP-TEMP-001', quantity: 2, price: 89.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
{ id: 'i2', productName: '连接线 1m', sku: 'TP-CABLE-001', quantity: 1, price: 19.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '2',
|
|
|
|
|
|
orderId: 'ORD-2026-002',
|
|
|
|
|
|
platform: 'EBAY',
|
|
|
|
|
|
customerName: '李四',
|
|
|
|
|
|
customerEmail: 'lisi@email.com',
|
|
|
|
|
|
customerPhone: '+86-138-0000-0002',
|
|
|
|
|
|
customerAddress: '上海市浦东新区xxx路xxx号',
|
|
|
|
|
|
totalAmount: 299.99,
|
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
|
status: 'PENDING',
|
|
|
|
|
|
paymentStatus: 'PAID',
|
|
|
|
|
|
itemCount: 1,
|
|
|
|
|
|
createdAt: '2026-03-18 09:15:00',
|
|
|
|
|
|
updatedAt: '2026-03-18 09:15:00',
|
|
|
|
|
|
shopId: 'SHOP_002',
|
|
|
|
|
|
shopName: 'eBay店铺',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ id: 'i3', productName: 'PLC控制器', sku: 'TP-PLC-001', quantity: 1, price: 299.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '3',
|
|
|
|
|
|
orderId: 'ORD-2026-003',
|
|
|
|
|
|
platform: 'SHOPIFY',
|
|
|
|
|
|
customerName: '王五',
|
|
|
|
|
|
customerEmail: 'wangwu@email.com',
|
|
|
|
|
|
customerPhone: '+86-138-0000-0003',
|
|
|
|
|
|
customerAddress: '广州市天河区xxx大道xxx号',
|
|
|
|
|
|
totalAmount: 459.98,
|
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
|
status: 'CONFIRMED',
|
|
|
|
|
|
paymentStatus: 'PAID',
|
|
|
|
|
|
itemCount: 2,
|
|
|
|
|
|
createdAt: '2026-03-17 16:45:00',
|
|
|
|
|
|
updatedAt: '2026-03-18 08:00:00',
|
|
|
|
|
|
shopId: 'SHOP_003',
|
|
|
|
|
|
shopName: '独立站',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ id: 'i4', productName: '压力传感器', sku: 'TP-PRES-001', quantity: 2, price: 129.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
{ id: 'i5', productName: '安装支架', sku: 'TP-BRACKET-001', quantity: 2, price: 29.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '4',
|
|
|
|
|
|
orderId: 'ORD-2026-004',
|
|
|
|
|
|
platform: 'SHOPEE',
|
|
|
|
|
|
customerName: '赵六',
|
|
|
|
|
|
customerEmail: 'zhaoliu@email.com',
|
|
|
|
|
|
customerPhone: '+86-138-0000-0004',
|
|
|
|
|
|
customerAddress: '深圳市南山区xxx街xxx号',
|
|
|
|
|
|
totalAmount: 89.99,
|
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
|
status: 'DELIVERED',
|
|
|
|
|
|
paymentStatus: 'PAID',
|
|
|
|
|
|
itemCount: 1,
|
|
|
|
|
|
createdAt: '2026-03-15 11:20:00',
|
|
|
|
|
|
updatedAt: '2026-03-18 09:30:00',
|
|
|
|
|
|
shopId: 'SHOP_004',
|
|
|
|
|
|
shopName: 'Shopee店铺',
|
|
|
|
|
|
trackingNumber: 'SF1234567890',
|
|
|
|
|
|
carrier: '顺丰',
|
|
|
|
|
|
actualDelivery: '2026-03-18',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ id: 'i6', productName: '温度传感器标准版', sku: 'TP-TEMP-002', quantity: 1, price: 89.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '5',
|
|
|
|
|
|
orderId: 'ORD-2026-005',
|
|
|
|
|
|
platform: 'AMAZON',
|
|
|
|
|
|
customerName: '钱七',
|
|
|
|
|
|
customerEmail: 'qianqi@email.com',
|
|
|
|
|
|
customerPhone: '+86-138-0000-0005',
|
|
|
|
|
|
customerAddress: '杭州市西湖区xxx路xxx号',
|
|
|
|
|
|
totalAmount: 599.99,
|
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
|
status: 'COMPLETED',
|
|
|
|
|
|
paymentStatus: 'PAID',
|
|
|
|
|
|
itemCount: 3,
|
|
|
|
|
|
createdAt: '2026-03-10 14:00:00',
|
|
|
|
|
|
updatedAt: '2026-03-16 10:00:00',
|
|
|
|
|
|
shopId: 'SHOP_001',
|
|
|
|
|
|
shopName: '主店铺',
|
|
|
|
|
|
trackingNumber: '1Z888BB20234567890',
|
|
|
|
|
|
carrier: 'UPS',
|
|
|
|
|
|
actualDelivery: '2026-03-16',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ id: 'i7', productName: '流量计', sku: 'TP-FLOW-001', quantity: 1, price: 299.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
{ id: 'i8', productName: '显示屏', sku: 'TP-DISP-001', quantity: 2, price: 149.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '6',
|
|
|
|
|
|
orderId: 'ORD-2026-006',
|
|
|
|
|
|
platform: 'EBAY',
|
|
|
|
|
|
customerName: '孙八',
|
|
|
|
|
|
customerEmail: 'sunba@email.com',
|
|
|
|
|
|
customerPhone: '+86-138-0000-0006',
|
|
|
|
|
|
customerAddress: '成都市武侯区xxx街xxx号',
|
|
|
|
|
|
totalAmount: 129.99,
|
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
|
status: 'CANCELLED',
|
|
|
|
|
|
paymentStatus: 'REFUNDED',
|
|
|
|
|
|
itemCount: 1,
|
|
|
|
|
|
createdAt: '2026-03-17 09:00:00',
|
|
|
|
|
|
updatedAt: '2026-03-17 15:30:00',
|
|
|
|
|
|
shopId: 'SHOP_002',
|
|
|
|
|
|
shopName: 'eBay店铺',
|
|
|
|
|
|
exceptionReason: '客户取消订单',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ id: 'i9', productName: '电磁阀', sku: 'TP-VALVE-001', quantity: 1, price: 129.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
id: '7',
|
|
|
|
|
|
orderId: 'ORD-2026-007',
|
|
|
|
|
|
platform: 'SHOPIFY',
|
|
|
|
|
|
customerName: '周九',
|
|
|
|
|
|
customerEmail: 'zhoujiu@email.com',
|
|
|
|
|
|
customerPhone: '+86-138-0000-0007',
|
|
|
|
|
|
customerAddress: '武汉市江汉区xxx路xxx号',
|
|
|
|
|
|
totalAmount: 349.99,
|
|
|
|
|
|
currency: 'USD',
|
|
|
|
|
|
status: 'REFUNDING',
|
|
|
|
|
|
paymentStatus: 'PAID',
|
|
|
|
|
|
itemCount: 2,
|
|
|
|
|
|
createdAt: '2026-03-14 13:30:00',
|
|
|
|
|
|
updatedAt: '2026-03-18 11:00:00',
|
|
|
|
|
|
shopId: 'SHOP_003',
|
|
|
|
|
|
shopName: '独立站',
|
|
|
|
|
|
exceptionReason: '商品质量问题',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{ id: 'i10', productName: '步进电机', sku: 'TP-MOTOR-001', quantity: 1, price: 199.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
{ id: 'i11', productName: '驱动器', sku: 'TP-DRIVER-001', quantity: 1, price: 149.99, image: 'https://via.placeholder.com/60x60' },
|
|
|
|
|
|
],
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
2026-03-18 09:38:09 +08:00
|
|
|
|
export const OrderList: React.FC = () => {
|
|
|
|
|
|
const [orders, setOrders] = useState<Order[]>([]);
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [selectedRows, setSelectedRows] = useState<Order[]>([]);
|
|
|
|
|
|
const [currentOrder, setCurrentOrder] = useState<Order | null>(null);
|
|
|
|
|
|
const [filterVisible, setFilterVisible] = useState(false);
|
|
|
|
|
|
const [sortDrawerVisible, setSortDrawerVisible] = useState(false);
|
|
|
|
|
|
const [detailDrawerVisible, setDetailDrawerVisible] = useState(false);
|
|
|
|
|
|
const [confirmModalVisible, setConfirmModalVisible] = useState(false);
|
|
|
|
|
|
const [shipModalVisible, setShipModalVisible] = useState(false);
|
|
|
|
|
|
const [cancelModalVisible, setCancelModalVisible] = useState(false);
|
|
|
|
|
|
const [afterSalesModalVisible, setAfterSalesModalVisible] = useState(false);
|
2026-03-18 09:38:09 +08:00
|
|
|
|
const [activeTab, setActiveTab] = useState('all');
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const [shipForm] = Form.useForm();
|
|
|
|
|
|
const [cancelForm] = Form.useForm();
|
|
|
|
|
|
const [afterSalesForm] = Form.useForm();
|
|
|
|
|
|
|
|
|
|
|
|
const [filters, setFilters] = useState<FilterState>({
|
|
|
|
|
|
keyword: '',
|
|
|
|
|
|
status: [],
|
|
|
|
|
|
platform: [],
|
|
|
|
|
|
paymentStatus: [],
|
|
|
|
|
|
dateRange: null,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const [sort, setSort] = useState<SortState>({
|
|
|
|
|
|
field: 'createdAt',
|
|
|
|
|
|
order: 'descend',
|
2026-03-18 09:38:09 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
fetchOrders();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const fetchOrders = useCallback(async () => {
|
2026-03-18 09:38:09 +08:00
|
|
|
|
setLoading(true);
|
2026-03-19 01:39:34 +08:00
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
|
|
|
|
setOrders(MOCK_ORDERS);
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const handleFilterChange = (key: keyof FilterState, value: any) => {
|
|
|
|
|
|
setFilters(prev => ({ ...prev, [key]: value }));
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSortChange = (field: string, order: 'ascend' | 'descend') => {
|
|
|
|
|
|
setSort({ field, order });
|
|
|
|
|
|
setSortDrawerVisible(false);
|
|
|
|
|
|
message.success(`已按 ${field} ${order === 'ascend' ? '升序' : '降序'} 排序`);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleTableChange = (
|
|
|
|
|
|
pagination: TablePaginationConfig,
|
|
|
|
|
|
filters: Record<string, FilterValue | null>,
|
|
|
|
|
|
sorter: SorterResult<Order> | SorterResult<Order>[],
|
|
|
|
|
|
extra: TableCurrentDataSource<Order>
|
|
|
|
|
|
) => {
|
|
|
|
|
|
if (!Array.isArray(sorter) && sorter.field) {
|
|
|
|
|
|
setSort({
|
|
|
|
|
|
field: sorter.field as string,
|
|
|
|
|
|
order: sorter.order || null,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleSearch = (value: string) => {
|
|
|
|
|
|
handleFilterChange('keyword', value);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleResetFilters = () => {
|
|
|
|
|
|
setFilters({
|
|
|
|
|
|
keyword: '',
|
|
|
|
|
|
status: [],
|
|
|
|
|
|
platform: [],
|
|
|
|
|
|
paymentStatus: [],
|
|
|
|
|
|
dateRange: null,
|
|
|
|
|
|
});
|
|
|
|
|
|
message.success('筛选条件已重置');
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleConfirmOrder = (record: Order) => {
|
|
|
|
|
|
setCurrentOrder(record);
|
|
|
|
|
|
setConfirmModalVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleConfirmSubmit = () => {
|
|
|
|
|
|
if (currentOrder) {
|
|
|
|
|
|
updateOrderStatus(currentOrder.id, 'CONFIRMED');
|
|
|
|
|
|
setConfirmModalVisible(false);
|
|
|
|
|
|
message.success('订单确认成功');
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleShipOrder = (record: Order) => {
|
|
|
|
|
|
setCurrentOrder(record);
|
|
|
|
|
|
setShipModalVisible(true);
|
|
|
|
|
|
shipForm.resetFields();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleShipSubmit = async () => {
|
2026-03-18 09:38:09 +08:00
|
|
|
|
try {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const values = await shipForm.validateFields();
|
|
|
|
|
|
if (currentOrder) {
|
|
|
|
|
|
updateOrderStatus(currentOrder.id, 'SHIPPED');
|
|
|
|
|
|
setShipModalVisible(false);
|
|
|
|
|
|
message.success('订单发货成功');
|
|
|
|
|
|
}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
} catch (error) {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
console.error('Form validation failed:', error);
|
2026-03-18 09:38:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const handleCompleteOrder = (record: Order) => {
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: '确认完成订单',
|
|
|
|
|
|
content: `确定要完成订单 "${record.orderId}" 吗?`,
|
|
|
|
|
|
onOk: () => {
|
|
|
|
|
|
updateOrderStatus(record.id, 'COMPLETED');
|
|
|
|
|
|
message.success('订单已完成');
|
|
|
|
|
|
},
|
2026-03-18 09:38:09 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const handleCancelOrder = (record: Order) => {
|
|
|
|
|
|
setCurrentOrder(record);
|
|
|
|
|
|
setCancelModalVisible(true);
|
|
|
|
|
|
cancelForm.resetFields();
|
2026-03-18 09:38:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const handleCancelSubmit = async () => {
|
2026-03-18 09:38:09 +08:00
|
|
|
|
try {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const values = await cancelForm.validateFields();
|
|
|
|
|
|
if (currentOrder) {
|
|
|
|
|
|
updateOrderStatus(currentOrder.id, 'CANCELLED');
|
|
|
|
|
|
setCancelModalVisible(false);
|
|
|
|
|
|
message.success('订单已取消');
|
|
|
|
|
|
}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
} catch (error) {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
console.error('Form validation failed:', error);
|
2026-03-18 09:38:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const handleAfterSales = (record: Order) => {
|
|
|
|
|
|
setCurrentOrder(record);
|
|
|
|
|
|
setAfterSalesModalVisible(true);
|
|
|
|
|
|
afterSalesForm.resetFields();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const handleAfterSalesSubmit = async () => {
|
2026-03-18 09:38:09 +08:00
|
|
|
|
try {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const values = await afterSalesForm.validateFields();
|
|
|
|
|
|
if (currentOrder) {
|
|
|
|
|
|
updateOrderStatus(currentOrder.id, 'REFUNDING');
|
|
|
|
|
|
setAfterSalesModalVisible(false);
|
|
|
|
|
|
message.success('售后申请已提交');
|
|
|
|
|
|
}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
} catch (error) {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
console.error('Form validation failed:', error);
|
2026-03-18 09:38:09 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const handleViewDetail = (record: Order) => {
|
|
|
|
|
|
setCurrentOrder(record);
|
|
|
|
|
|
setDetailDrawerVisible(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
));
|
2026-03-18 09:38:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
const handleTabChange = (key: string) => {
|
|
|
|
|
|
setActiveTab(key);
|
|
|
|
|
|
if (key === 'all') {
|
|
|
|
|
|
setFilters(prev => ({ ...prev, status: [] }));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setFilters(prev => ({ ...prev, status: [key.toUpperCase()] }));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const filteredOrders = orders.filter(order => {
|
|
|
|
|
|
if (filters.keyword && !order.orderId.toLowerCase().includes(filters.keyword.toLowerCase()) &&
|
|
|
|
|
|
!order.customerName.toLowerCase().includes(filters.keyword.toLowerCase())) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (filters.status.length > 0 && !filters.status.includes(order.status)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (filters.platform.length > 0 && !filters.platform.includes(order.platform)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (filters.paymentStatus.length > 0 && !filters.paymentStatus.includes(order.paymentStatus)) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const sortedOrders = [...filteredOrders].sort((a, b) => {
|
|
|
|
|
|
const field = sort.field as keyof Order;
|
|
|
|
|
|
const aValue = a[field];
|
|
|
|
|
|
const bValue = b[field];
|
|
|
|
|
|
|
|
|
|
|
|
if (sort.order === 'ascend') {
|
|
|
|
|
|
return aValue > bValue ? 1 : -1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return aValue < bValue ? 1 : -1;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const stats = {
|
|
|
|
|
|
total: orders.length,
|
|
|
|
|
|
pending: orders.filter(o => o.status === 'PENDING').length,
|
|
|
|
|
|
processing: orders.filter(o => ['CONFIRMED', 'SHIPPED'].includes(o.status)).length,
|
|
|
|
|
|
completed: orders.filter(o => o.status === 'COMPLETED').length,
|
|
|
|
|
|
exception: orders.filter(o => ['CANCELLED', 'REFUNDING', 'REFUNDED'].includes(o.status)).length,
|
|
|
|
|
|
totalAmount: orders.reduce((sum, o) => sum + o.totalAmount, 0),
|
2026-03-18 09:38:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const columns: ColumnsType<Order> = [
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '订单号',
|
2026-03-18 09:38:09 +08:00
|
|
|
|
dataIndex: 'orderId',
|
|
|
|
|
|
key: 'orderId',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
render: (text, record) => (
|
|
|
|
|
|
<Space direction="vertical" size={0}>
|
|
|
|
|
|
<Text strong>{text}</Text>
|
|
|
|
|
|
<Tag color={PLATFORM_CONFIG[record.platform].color} size="small">
|
|
|
|
|
|
{PLATFORM_CONFIG[record.platform].text}
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
</Space>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '客户信息',
|
|
|
|
|
|
key: 'customer',
|
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
|
<Space direction="vertical" size={0}>
|
|
|
|
|
|
<Text>{record.customerName}</Text>
|
|
|
|
|
|
<Text type="secondary" style={{ fontSize: 12 }}>{record.customerPhone}</Text>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
),
|
2026-03-18 09:38:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '金额',
|
|
|
|
|
|
dataIndex: 'totalAmount',
|
|
|
|
|
|
key: 'totalAmount',
|
|
|
|
|
|
render: (value, record) => (
|
|
|
|
|
|
<Space direction="vertical" size={0}>
|
|
|
|
|
|
<Text strong>${value.toFixed(2)}</Text>
|
|
|
|
|
|
<Text type="secondary" style={{ fontSize: 12 }}>{record.currency}</Text>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
),
|
|
|
|
|
|
sorter: true,
|
2026-03-18 09:38:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '商品数',
|
2026-03-18 09:38:09 +08:00
|
|
|
|
dataIndex: 'itemCount',
|
|
|
|
|
|
key: 'itemCount',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
render: (value) => <Badge count={value} style={{ backgroundColor: '#1890ff' }} />,
|
2026-03-18 09:38:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '状态',
|
|
|
|
|
|
dataIndex: 'status',
|
|
|
|
|
|
key: 'status',
|
|
|
|
|
|
render: (status) => {
|
|
|
|
|
|
const config = STATUS_CONFIG[status];
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Tag color={config.color} icon={config.icon}>
|
|
|
|
|
|
{config.text}
|
|
|
|
|
|
</Tag>
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
filters: Object.entries(STATUS_CONFIG).map(([key, config]) => ({
|
|
|
|
|
|
text: config.text,
|
|
|
|
|
|
value: key,
|
|
|
|
|
|
})),
|
2026-03-18 09:38:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '支付状态',
|
2026-03-18 09:38:09 +08:00
|
|
|
|
dataIndex: 'paymentStatus',
|
|
|
|
|
|
key: 'paymentStatus',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
render: (status) => {
|
|
|
|
|
|
const config = {
|
|
|
|
|
|
PENDING: { color: 'warning', text: '待支付' },
|
|
|
|
|
|
PAID: { color: 'success', text: '已支付' },
|
|
|
|
|
|
REFUNDED: { color: 'default', text: '已退款' },
|
|
|
|
|
|
}[status];
|
2026-03-18 09:38:09 +08:00
|
|
|
|
return <Tag color={config.color}>{config.text}</Tag>;
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '店铺',
|
2026-03-18 09:38:09 +08:00
|
|
|
|
dataIndex: 'shopName',
|
|
|
|
|
|
key: 'shopName',
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '创建时间',
|
2026-03-18 09:38:09 +08:00
|
|
|
|
dataIndex: 'createdAt',
|
|
|
|
|
|
key: 'createdAt',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
sorter: true,
|
2026-03-18 09:38:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
{
|
2026-03-19 01:39:34 +08:00
|
|
|
|
title: '操作',
|
2026-03-18 09:38:09 +08:00
|
|
|
|
key: 'action',
|
|
|
|
|
|
fixed: 'right',
|
2026-03-19 01:39:34 +08:00
|
|
|
|
width: 200,
|
|
|
|
|
|
render: (_, record) => {
|
|
|
|
|
|
const menu = (
|
|
|
|
|
|
<Menu>
|
|
|
|
|
|
<Menu.Item key="view" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)}>
|
|
|
|
|
|
查看详情
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
{record.status === 'PENDING' && (
|
|
|
|
|
|
<Menu.Item key="confirm" icon={<CheckCircleOutlined />} onClick={() => handleConfirmOrder(record)}>
|
|
|
|
|
|
确认订单
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{record.status === 'CONFIRMED' && (
|
|
|
|
|
|
<Menu.Item key="ship" icon={<TruckOutlined />} onClick={() => handleShipOrder(record)}>
|
|
|
|
|
|
发货
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{record.status === 'DELIVERED' && (
|
|
|
|
|
|
<Menu.Item key="complete" icon={<CheckOutlined />} onClick={() => handleCompleteOrder(record)}>
|
|
|
|
|
|
完成订单
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{(record.status === 'PENDING' || record.status === 'CONFIRMED') && (
|
|
|
|
|
|
<Menu.Item key="cancel" icon={<CloseCircleOutlined />} onClick={() => handleCancelOrder(record)}>
|
|
|
|
|
|
取消订单
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{(record.status === 'COMPLETED' || record.status === 'DELIVERED') && (
|
|
|
|
|
|
<Menu.Item key="aftersales" icon={<CustomerServiceOutlined />} onClick={() => handleAfterSales(record)}>
|
|
|
|
|
|
售后申请
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Menu.Divider />
|
|
|
|
|
|
<Menu.Item key="print" icon={<PrinterOutlined />}>
|
|
|
|
|
|
打印订单
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
<Menu.Item key="message" icon={<MessageOutlined />}>
|
|
|
|
|
|
联系客户
|
|
|
|
|
|
</Menu.Item>
|
|
|
|
|
|
</Menu>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-19 14:19:01 +08:00
|
|
|
|
// 根据订单状态显示主要操作按钮
|
|
|
|
|
|
const getPrimaryActionButton = () => {
|
|
|
|
|
|
switch (record.status) {
|
|
|
|
|
|
case 'PENDING':
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Button type="primary" size="small" onClick={() => handleConfirmOrder(record)}>
|
|
|
|
|
|
确认
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
);
|
|
|
|
|
|
case 'CONFIRMED':
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Button type="primary" size="small" onClick={() => handleShipOrder(record)}>
|
|
|
|
|
|
发货
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
);
|
|
|
|
|
|
case 'DELIVERED':
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Button type="primary" size="small" onClick={() => handleCompleteOrder(record)}>
|
|
|
|
|
|
完成
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
);
|
|
|
|
|
|
case 'COMPLETED':
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Button type="link" size="small" onClick={() => handleAfterSales(record)}>
|
|
|
|
|
|
售后
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
);
|
|
|
|
|
|
default:
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
return (
|
2026-03-19 14:19:01 +08:00
|
|
|
|
<Space size="small">
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Button type="link" size="small" onClick={() => handleViewDetail(record)}>
|
|
|
|
|
|
查看
|
|
|
|
|
|
</Button>
|
2026-03-19 14:19:01 +08:00
|
|
|
|
{getPrimaryActionButton()}
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Dropdown overlay={menu} placement="bottomRight">
|
|
|
|
|
|
<Button type="link" size="small" icon={<MoreOutlined />}>
|
|
|
|
|
|
更多
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Dropdown>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
2026-03-18 09:38:09 +08:00
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
const rowSelection = {
|
2026-03-19 01:39:34 +08:00
|
|
|
|
selectedRowKeys: selectedRows.map(r => r.id),
|
|
|
|
|
|
onChange: (selectedRowKeys: React.Key[], selectedRows: Order[]) => {
|
|
|
|
|
|
setSelectedRows(selectedRows);
|
|
|
|
|
|
},
|
2026-03-18 09:38:09 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Card>
|
|
|
|
|
|
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
|
|
|
|
|
<Col span={24}>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={4}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic title="总订单" value={stats.total} />
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={4}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic title="待处理" value={stats.pending} valueStyle={{ color: '#faad14' }} />
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={4}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic title="处理中" value={stats.processing} valueStyle={{ color: '#1890ff' }} />
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={4}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic title="已完成" value={stats.completed} valueStyle={{ color: '#52c41a' }} />
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={4}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic title="异常" value={stats.exception} valueStyle={{ color: '#ff4d4f' }} />
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={4}>
|
|
|
|
|
|
<Card size="small">
|
|
|
|
|
|
<Statistic title="总金额" value={`$${stats.totalAmount.toFixed(2)}`} />
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
</Col>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</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>
|
|
|
|
|
|
|
|
|
|
|
|
<Row gutter={[16, 16]} style={{ marginBottom: 16 }}>
|
|
|
|
|
|
<Col span={24}>
|
2026-03-19 14:19:01 +08:00
|
|
|
|
<Card>
|
|
|
|
|
|
<Row gutter={16}>
|
|
|
|
|
|
<Col span={12}>
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Search
|
|
|
|
|
|
placeholder="搜索订单号或客户"
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
enterButton
|
|
|
|
|
|
onSearch={handleSearch}
|
|
|
|
|
|
style={{ width: 300 }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<Button icon={<FilterOutlined />} onClick={() => setFilterVisible(true)}>
|
|
|
|
|
|
筛选
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button icon={<SortAscendingOutlined />} onClick={() => setSortDrawerVisible(true)}>
|
|
|
|
|
|
排序
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
<Col span={12} style={{ textAlign: 'right' }}>
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
{selectedRows.length > 0 && (
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button onClick={() => message.info('批量确认功能开发中')}>
|
|
|
|
|
|
批量确认
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button onClick={() => message.info('批量发货功能开发中')}>
|
|
|
|
|
|
批量发货
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button danger onClick={() => message.info('批量取消功能开发中')}>
|
|
|
|
|
|
批量取消
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<Button icon={<ExportOutlined />}>
|
|
|
|
|
|
导出
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button icon={<SyncOutlined />} onClick={fetchOrders}>
|
|
|
|
|
|
刷新
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
</Card>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
</Col>
|
|
|
|
|
|
</Row>
|
|
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Table
|
|
|
|
|
|
rowSelection={rowSelection}
|
|
|
|
|
|
columns={columns}
|
|
|
|
|
|
dataSource={sortedOrders}
|
|
|
|
|
|
loading={loading}
|
|
|
|
|
|
rowKey="id"
|
|
|
|
|
|
onChange={handleTableChange}
|
2026-03-19 14:19:01 +08:00
|
|
|
|
scroll={{ x: 1300 }}
|
2026-03-19 01:39:34 +08:00
|
|
|
|
pagination={{
|
|
|
|
|
|
total: sortedOrders.length,
|
|
|
|
|
|
pageSize: 10,
|
|
|
|
|
|
showSizeChanger: true,
|
|
|
|
|
|
showQuickJumper: true,
|
|
|
|
|
|
showTotal: (total) => `共 ${total} 条`,
|
2026-03-19 14:19:01 +08:00
|
|
|
|
pageSizeOptions: ['10', '20', '50', '100'],
|
2026-03-19 01:39:34 +08:00
|
|
|
|
}}
|
2026-03-19 14:19:01 +08:00
|
|
|
|
size="middle"
|
|
|
|
|
|
bordered
|
2026-03-19 01:39:34 +08:00
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
|
|
<Drawer
|
|
|
|
|
|
title="筛选条件"
|
|
|
|
|
|
placement="right"
|
|
|
|
|
|
onClose={() => setFilterVisible(false)}
|
|
|
|
|
|
visible={filterVisible}
|
|
|
|
|
|
width={400}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Form layout="vertical">
|
|
|
|
|
|
<Form.Item label="订单状态">
|
|
|
|
|
|
<Select
|
|
|
|
|
|
mode="multiple"
|
|
|
|
|
|
placeholder="选择状态"
|
|
|
|
|
|
value={filters.status}
|
|
|
|
|
|
onChange={(value) => handleFilterChange('status', value)}
|
|
|
|
|
|
style={{ width: '100%' }}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
{Object.entries(STATUS_CONFIG).map(([key, config]) => (
|
|
|
|
|
|
<Option key={key} value={key}>{config.text}</Option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item label="平台">
|
|
|
|
|
|
<Select
|
|
|
|
|
|
mode="multiple"
|
|
|
|
|
|
placeholder="选择平台"
|
|
|
|
|
|
value={filters.platform}
|
|
|
|
|
|
onChange={(value) => handleFilterChange('platform', value)}
|
|
|
|
|
|
style={{ width: '100%' }}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
{Object.entries(PLATFORM_CONFIG).map(([key, config]) => (
|
|
|
|
|
|
<Option key={key} value={key}>{config.text}</Option>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item label="支付状态">
|
|
|
|
|
|
<Select
|
|
|
|
|
|
mode="multiple"
|
|
|
|
|
|
placeholder="选择支付状态"
|
|
|
|
|
|
value={filters.paymentStatus}
|
|
|
|
|
|
onChange={(value) => handleFilterChange('paymentStatus', value)}
|
|
|
|
|
|
style={{ width: '100%' }}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Option value="PENDING">待支付</Option>
|
|
|
|
|
|
<Option value="PAID">已支付</Option>
|
|
|
|
|
|
<Option value="REFUNDED">已退款</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item label="创建时间">
|
|
|
|
|
|
<RangePicker
|
|
|
|
|
|
value={filters.dateRange}
|
|
|
|
|
|
onChange={(dates) => handleFilterChange('dateRange', dates)}
|
|
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, padding: 16, borderTop: '1px solid #f0f0f0', background: '#fff' }}>
|
|
|
|
|
|
<Space style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
|
|
|
|
<Button onClick={handleResetFilters}>重置</Button>
|
|
|
|
|
|
<Button type="primary" onClick={() => setFilterVisible(false)}>确定</Button>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</Drawer>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Drawer
|
|
|
|
|
|
title="排序设置"
|
|
|
|
|
|
placement="right"
|
|
|
|
|
|
onClose={() => setSortDrawerVisible(false)}
|
|
|
|
|
|
visible={sortDrawerVisible}
|
|
|
|
|
|
width={300}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Space direction="vertical" style={{ width: '100%' }}>
|
|
|
|
|
|
{[
|
|
|
|
|
|
{ key: 'orderId', label: '订单号' },
|
|
|
|
|
|
{ key: 'totalAmount', label: '金额' },
|
|
|
|
|
|
{ key: 'itemCount', label: '商品数' },
|
|
|
|
|
|
{ key: 'createdAt', label: '创建时间' },
|
|
|
|
|
|
{ key: 'updatedAt', label: '更新时间' },
|
|
|
|
|
|
].map(item => (
|
|
|
|
|
|
<Card key={item.key} size="small" hoverable>
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
|
|
|
|
<span>{item.label}</span>
|
|
|
|
|
|
<Space>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
type={sort.field === item.key && sort.order === 'ascend' ? 'primary' : 'default'}
|
|
|
|
|
|
onClick={() => handleSortChange(item.key, 'ascend')}
|
|
|
|
|
|
>
|
|
|
|
|
|
升序
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
type={sort.field === item.key && sort.order === 'descend' ? 'primary' : 'default'}
|
|
|
|
|
|
onClick={() => handleSortChange(item.key, 'descend')}
|
|
|
|
|
|
>
|
|
|
|
|
|
降序
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
</Drawer>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
|
2026-03-19 01:39:34 +08:00
|
|
|
|
<Drawer
|
|
|
|
|
|
title={`订单详情 - ${currentOrder?.orderId}`}
|
|
|
|
|
|
placement="right"
|
|
|
|
|
|
onClose={() => setDetailDrawerVisible(false)}
|
|
|
|
|
|
visible={detailDrawerVisible}
|
|
|
|
|
|
width={600}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
{currentOrder && (
|
|
|
|
|
|
<Space direction="vertical" style={{ width: '100%' }} size="large">
|
|
|
|
|
|
<Card title="订单状态">
|
|
|
|
|
|
<Steps current={STATUS_CONFIG[currentOrder.status].step} size="small">
|
|
|
|
|
|
<Step title="待处理" icon={<ClockCircleOutlined />} />
|
|
|
|
|
|
<Step title="已确认" icon={<CheckCircleOutlined />} />
|
|
|
|
|
|
<Step title="已发货" icon={<TruckOutlined />} />
|
|
|
|
|
|
<Step title="已送达" icon={<EnvironmentOutlined />} />
|
|
|
|
|
|
<Step title="已完成" icon={<CheckCircleOutlined />} />
|
|
|
|
|
|
</Steps>
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Card title="商品信息">
|
|
|
|
|
|
{currentOrder.items.map(item => (
|
|
|
|
|
|
<div key={item.id} style={{ display: 'flex', marginBottom: 16 }}>
|
|
|
|
|
|
<Image src={item.image} width={80} height={80} style={{ marginRight: 16 }} />
|
|
|
|
|
|
<div style={{ flex: 1 }}>
|
|
|
|
|
|
<div style={{ fontWeight: 500 }}>{item.productName}</div>
|
|
|
|
|
|
<div style={{ color: '#999', fontSize: 12 }}>SKU: {item.sku}</div>
|
|
|
|
|
|
<div style={{ marginTop: 8 }}>
|
|
|
|
|
|
<Text type="secondary">数量: {item.quantity}</Text>
|
|
|
|
|
|
<Text strong style={{ marginLeft: 16 }}>${item.price.toFixed(2)}</Text>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
|
|
<Card title="客户信息">
|
|
|
|
|
|
<p><strong>姓名:</strong> {currentOrder.customerName}</p>
|
|
|
|
|
|
<p><strong>电话:</strong> {currentOrder.customerPhone}</p>
|
|
|
|
|
|
<p><strong>邮箱:</strong> {currentOrder.customerEmail}</p>
|
|
|
|
|
|
<p><strong>地址:</strong> {currentOrder.customerAddress}</p>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
</Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
|
|
|
|
|
|
<Card title="物流信息">
|
|
|
|
|
|
{currentOrder.trackingNumber ? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<p><strong>物流单号:</strong> {currentOrder.trackingNumber}</p>
|
|
|
|
|
|
<p><strong>承运商:</strong> {currentOrder.carrier}</p>
|
|
|
|
|
|
{currentOrder.estimatedDelivery && (
|
|
|
|
|
|
<p><strong>预计送达:</strong> {currentOrder.estimatedDelivery}</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{currentOrder.actualDelivery && (
|
|
|
|
|
|
<p><strong>实际送达:</strong> {currentOrder.actualDelivery}</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Text type="secondary">暂无物流信息</Text>
|
|
|
|
|
|
)}
|
2026-03-18 09:38:09 +08:00
|
|
|
|
</Card>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</Space>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
)}
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</Drawer>
|
|
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="确认订单"
|
|
|
|
|
|
visible={confirmModalVisible}
|
|
|
|
|
|
onOk={handleConfirmSubmit}
|
|
|
|
|
|
onCancel={() => setConfirmModalVisible(false)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<p>确定要确认订单 "{currentOrder?.orderId}" 吗?</p>
|
|
|
|
|
|
<p>客户: {currentOrder?.customerName}</p>
|
|
|
|
|
|
<p>金额: ${currentOrder?.totalAmount.toFixed(2)}</p>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="订单发货"
|
|
|
|
|
|
visible={shipModalVisible}
|
|
|
|
|
|
onOk={handleShipSubmit}
|
|
|
|
|
|
onCancel={() => setShipModalVisible(false)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form form={shipForm} layout="vertical">
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="carrier"
|
|
|
|
|
|
label="承运商"
|
|
|
|
|
|
rules={[{ required: true, message: '请选择承运商' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select placeholder="选择承运商">
|
|
|
|
|
|
<Option value="UPS">UPS</Option>
|
|
|
|
|
|
<Option value="FedEx">FedEx</Option>
|
|
|
|
|
|
<Option value="DHL">DHL</Option>
|
|
|
|
|
|
<Option value="顺丰">顺丰</Option>
|
|
|
|
|
|
<Option value="中通">中通</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="trackingNumber"
|
|
|
|
|
|
label="物流单号"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入物流单号' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input placeholder="输入物流单号" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="取消订单"
|
|
|
|
|
|
visible={cancelModalVisible}
|
|
|
|
|
|
onOk={handleCancelSubmit}
|
|
|
|
|
|
onCancel={() => setCancelModalVisible(false)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form form={cancelForm} layout="vertical">
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="reason"
|
|
|
|
|
|
label="取消原因"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入取消原因' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input.TextArea rows={4} placeholder="输入取消原因" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
title="售后申请"
|
|
|
|
|
|
visible={afterSalesModalVisible}
|
|
|
|
|
|
onOk={handleAfterSalesSubmit}
|
|
|
|
|
|
onCancel={() => setAfterSalesModalVisible(false)}
|
|
|
|
|
|
width={600}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form form={afterSalesForm} layout="vertical">
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="type"
|
|
|
|
|
|
label="售后类型"
|
|
|
|
|
|
rules={[{ required: true, message: '请选择售后类型' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select placeholder="选择售后类型">
|
|
|
|
|
|
<Option value="return">退货退款</Option>
|
|
|
|
|
|
<Option value="exchange">换货</Option>
|
|
|
|
|
|
<Option value="refund">仅退款</Option>
|
|
|
|
|
|
</Select>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="reason"
|
|
|
|
|
|
label="售后原因"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入售后原因' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input.TextArea rows={4} placeholder="输入售后原因" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="amount"
|
|
|
|
|
|
label="退款金额"
|
|
|
|
|
|
rules={[{ required: true, message: '请输入退款金额' }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<InputNumber
|
|
|
|
|
|
min={0}
|
|
|
|
|
|
max={currentOrder?.totalAmount}
|
|
|
|
|
|
precision={2}
|
|
|
|
|
|
style={{ width: '100%' }}
|
|
|
|
|
|
placeholder="输入退款金额"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
</Modal>
|
2026-03-19 01:39:34 +08:00
|
|
|
|
</Card>
|
2026-03-18 09:38:09 +08:00
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default OrderList;
|