forked from yoone/WEB
1
0
Fork 0
WEB/src/pages/Site/Shop/Orders/index.tsx

449 lines
14 KiB
TypeScript

import { ORDER_STATUS_ENUM } from '@/constants';
import { HistoryOrder } from '@/pages/Statistics/Order';
import styles from '@/style/order-list.css';
import { DeleteFilled, EllipsisOutlined } from '@ant-design/icons';
import {
ActionType,
ModalForm,
PageContainer,
ProColumns,
ProFormTextArea,
ProTable,
} from '@ant-design/pro-components';
import { request, useParams } from '@umijs/max';
import { App, Button, Dropdown, Popconfirm, Tabs, TabsProps } from 'antd';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
BatchEditOrders,
CreateOrder,
EditOrder,
OrderNote,
} from '../components/Order/Forms';
const OrdersPage: React.FC = () => {
const actionRef = useRef<ActionType>();
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [activeKey, setActiveKey] = useState<string>('all');
const [count, setCount] = useState<any[]>([]);
const [activeLine, setActiveLine] = useState<number>(-1);
const { siteId } = useParams<{ siteId: string }>();
const { message } = App.useApp();
useEffect(() => {
actionRef.current?.reload();
}, [siteId]);
const tabs: TabsProps['items'] = useMemo(() => {
// 统计全部数量,依赖状态统计数组
const total = count.reduce((acc, cur) => acc + Number(cur.count), 0);
const tabs = [
{ key: 'pending', label: '待确认' },
{ key: 'processing', label: '待发货' },
{ key: 'completed', label: '已完成' },
{ key: 'cancelled', label: '已取消' },
{ key: 'refunded', label: '已退款' },
{ key: 'failed', label: '失败' },
{ key: 'after_sale_pending', label: '售后处理中' },
{ key: 'pending_reshipment', label: '待补发' },
// 退款相关状态
{ key: 'refund_requested', label: '已申请退款' },
{ key: 'refund_approved', label: '退款申请已通过' },
{ key: 'refund_cancelled', label: '已取消退款' },
].map((v) => {
// 根据状态键匹配统计数量
const number = count.find((el) => el.status === v.key)?.count || '0';
return {
label: `${v.label}(${number})`,
key: v.key,
};
});
return [{ key: 'all', label: `全部(${total})` }, ...tabs];
}, [count]);
const columns: ProColumns<API.Order>[] = [
{
title: '订单号',
dataIndex: 'id',
hideInSearch: true,
},
{
title: '状态',
dataIndex: 'status',
valueType: 'select',
valueEnum: ORDER_STATUS_ENUM,
},
{
title: '订单日期',
dataIndex: 'date_created',
hideInSearch: true,
valueType: 'dateTime',
},
{
title: '金额',
dataIndex: 'total',
hideInSearch: true,
},
{
title: '币种',
dataIndex: 'currency',
hideInSearch: true,
},
{
title: '客户邮箱',
dataIndex: 'email',
},
{
title: '客户姓名',
dataIndex: 'customer_name',
hideInSearch: true,
},
{
title: '商品',
dataIndex: 'line_items',
hideInSearch: true,
width: 200,
ellipsis: true,
render: (_, record) => {
// 检查 record.line_items 是否是数组并且有内容
if (Array.isArray(record.line_items) && record.line_items.length > 0) {
// 遍历 line_items 数组, 显示每个商品的名称和数量
return (
<div>
{record.line_items.map((item: any) => (
<div key={item.id}>{`${item.name} x ${item.quantity}`}</div>
))}
</div>
);
}
// 如果 line_items 不存在或不是数组, 则显示占位符
return '-';
},
},
{
title: '支付方式',
dataIndex: 'payment_method',
},
{
title: '联系电话',
hideInSearch: true,
render: (_, record) => record.shipping?.phone || record.billing?.phone,
},
{
title: '账单地址',
dataIndex: 'billing_full_address',
hideInSearch: true,
width: 200,
ellipsis: true,
copyable: true,
},
{
title: '收货地址',
dataIndex: 'shipping_full_address',
hideInSearch: true,
width: 200,
ellipsis: true,
copyable: true,
},
{
title: '操作',
dataIndex: 'option',
valueType: 'option',
fixed: 'right',
width: '200',
render: (_, record) => {
return (
<>
<EditOrder
key={record.id}
record={record}
tableRef={actionRef}
orderId={record.id as number}
setActiveLine={setActiveLine}
siteId={siteId}
/>
<Dropdown
menu={{
items: [
// Sync button removed
{
key: 'history',
label: (
<HistoryOrder
email={(record as any).email}
tableRef={actionRef}
/>
),
},
{
key: 'note',
label: (
<OrderNote id={record.id as number} siteId={siteId} />
),
},
],
}}
>
<Button type="text" icon={<EllipsisOutlined />} />
</Dropdown>
<Popconfirm
title="确定删除订单?"
onConfirm={async () => {
try {
const res = await request(
`/site-api/${siteId}/orders/${record.id}`,
{ method: 'DELETE' },
);
if (res.success) {
message.success('删除成功');
actionRef.current?.reload();
} else {
message.error(res.message || '删除失败');
}
} catch (e) {
message.error('删除失败');
}
}}
>
<Button type="link" danger title="删除" icon={<DeleteFilled />} />
</Popconfirm>
</>
);
},
},
];
return (
<PageContainer ghost header={{ title: null, breadcrumb: undefined }}>
<Tabs items={tabs} activeKey={activeKey} onChange={setActiveKey} />
<ProTable
columns={columns}
params={{ status: activeKey }}
headerTitle="查询表格"
scroll={{ x: 'max-content' }}
actionRef={actionRef}
rowKey="id"
rowSelection={{ selectedRowKeys, onChange: setSelectedRowKeys }}
rowClassName={(record) => {
return record.id === activeLine
? styles['selected-line-order-protable']
: '';
}}
pagination={{
pageSizeOptions: ['10', '20', '50', '100', '1000'],
showSizeChanger: true,
defaultPageSize: 10,
}}
toolBarRender={() => [
<CreateOrder tableRef={actionRef} siteId={siteId} />,
<BatchEditOrders
tableRef={actionRef}
selectedRowKeys={selectedRowKeys}
setSelectedRowKeys={setSelectedRowKeys}
siteId={siteId}
/>,
<Button
title="批量删除"
danger
icon={<DeleteFilled />}
disabled={!selectedRowKeys.length}
onClick={async () => {
if (!siteId) return;
const res = await request(`/site-api/${siteId}/orders/batch`, {
method: 'POST',
data: { delete: selectedRowKeys },
});
setSelectedRowKeys([]);
actionRef.current?.reload();
if (res.success) {
message.success('批量删除成功');
} else {
message.warning(res.message || '部分删除失败');
}
}}
/>,
<Button
onClick={async () => {
if (!siteId) return;
const idsParam = selectedRowKeys.length
? (selectedRowKeys as any[]).join(',')
: undefined;
const res = await request(`/site-api/${siteId}/orders/export`, {
params: { ids: idsParam },
});
if (res?.success && res?.data?.csv) {
const blob = new Blob([res.data.csv], {
type: 'text/csv;charset=utf-8;',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'orders.csv';
a.click();
URL.revokeObjectURL(url);
} else {
message.error(res.message || '导出失败');
}
}}
>
</Button>,
<ModalForm
title="批量导入订单"
trigger={
<Button type="primary" ghost>
</Button>
}
width={600}
modalProps={{ destroyOnHidden: true }}
onFinish={async (values) => {
if (!siteId) return false;
const csv = values.csv || '';
const items = values.items || [];
const res = await request(`/site-api/${siteId}/orders/import`, {
method: 'POST',
data: { csv, items },
});
if (res.success) {
message.success('导入完成');
actionRef.current?.reload();
return true;
}
message.error(res.message || '导入失败');
return false;
}}
>
<ProFormTextArea
name="csv"
label="CSV文本"
placeholder="粘贴CSV,首行为表头"
/>
</ModalForm>,
]}
request={async (params, sort, filter) => {
const p: any = params || {};
const current = p.current;
const pageSize = p.pageSize;
const date = p.date;
const status = p.status;
const {
current: _c,
pageSize: _ps,
date: _d,
status: _s,
...rest
} = p;
const where: Record<string, any> = { ...(filter || {}), ...rest };
if (status && status !== 'all') {
where.status = status;
}
if (date) {
const [startDate, endDate] = date;
// 将日期范围转为后端筛选参数
where.startDate = `${startDate} 00:00:00`;
where.endDate = `${endDate} 23:59:59`;
}
let orderObj: Record<string, 'asc' | 'desc'> | undefined = undefined;
if (sort && typeof sort === 'object') {
const [field, dir] = Object.entries(sort)[0] || [];
if (field && dir) {
orderObj = { [field]: dir === 'descend' ? 'desc' : 'asc' };
}
}
const response = await request(`/site-api/${siteId}/orders`, {
params: {
page: current,
page_size: pageSize,
where,
...(orderObj ? { order: orderObj } : {}),
},
});
if (!response.success) {
message.error(response.message || '获取订单列表失败');
return {
data: [],
success: false,
};
}
const { data } = response;
// 计算顶部状态数量,通过按状态并发查询站点接口
if (siteId) {
try {
// 定义需要统计的状态键集合
const statusKeys: string[] = [
'pending',
'processing',
'completed',
'cancelled',
'refunded',
'failed',
// 站点接口不支持的扩展状态,默认统计为0
'after_sale_pending',
'pending_reshipment',
'refund_requested',
'refund_approved',
'refund_cancelled',
];
// 构造基础筛选参数,移除当前状态避免重复过滤
const { status: _status, ...baseWhere } = where;
// 并发请求各状态的总数,对站点接口不支持的状态使用0
const results = await Promise.all(
statusKeys.map(async (key) => {
// 将前端退款状态映射为站点接口可能识别的原始状态
const mapToRawStatus: Record<string, string> = {
refund_requested: 'return-requested',
refund_approved: 'return-approved',
refund_cancelled: 'return-cancelled',
};
const rawStatus = mapToRawStatus[key] || key;
// 对扩展状态直接返回0,减少不必要的请求
const unsupported = [
'after_sale_pending',
'pending_reshipment',
];
if (unsupported.includes(key)) {
return { status: key, count: 0 };
}
try {
const res = await request(`/site-api/${siteId}/orders`, {
params: {
page: 1,
per_page: 1,
where: { ...baseWhere, status: rawStatus },
},
});
const totalCount = Number(res?.data?.total || 0);
return { status: key, count: totalCount };
} catch (err) {
// 请求失败时该状态数量记为0
return { status: key, count: 0 };
}
}),
);
setCount(results);
} catch (e) {
// 统计失败时不影响列表展示
}
}
if (data) {
return {
total: data?.total || 0,
data: data?.items || [],
success: true,
};
}
return {
data: [],
success: false,
};
}}
/>
</PageContainer>
);
};
export default OrdersPage;