refactor(订单): 重构订单详情抽屉组件并移动相关文件

将订单详情抽屉组件从 Order/List 移动到 Subscription/Orders 目录
提取 RelatedOrders 组件为独立文件
重构订单详情逻辑,保持功能不变但提高可维护性
This commit is contained in:
tikkhun 2025-11-21 15:21:42 +08:00
parent 96426a64b1
commit 34fd506e31
3 changed files with 117 additions and 193 deletions

View File

@ -80,8 +80,7 @@ import {
Tag, Tag,
} from 'antd'; } from 'antd';
import Item from 'antd/es/list/Item'; import Item from 'antd/es/list/Item';
import RelatedOrders from './RelatedOrders'; import RelatedOrders from '../../Subscription/Orders/RelatedOrders';
import OrderDetailDrawer from './OrderDetailDrawer';
import React, { useMemo, useRef, useState } from 'react'; import React, { useMemo, useRef, useState } from 'react';
import { printPDF } from '@/utils/util'; import { printPDF } from '@/utils/util';
@ -90,9 +89,6 @@ const ListPage: React.FC = () => {
const [activeKey, setActiveKey] = useState<string>('all'); const [activeKey, setActiveKey] = useState<string>('all');
const [count, setCount] = useState<any[]>([]); const [count, setCount] = useState<any[]>([]);
const [activeLine, setActiveLine] = useState<number>(-1); const [activeLine, setActiveLine] = useState<number>(-1);
const [detailOpen, setDetailOpen] = useState(false);
const [detailRecord, setDetailRecord] = useState<any | null>(null);
const [detailOrderId, setDetailOrderId] = useState<number | null>(null);
const tabs: TabsProps['items'] = useMemo(() => { const tabs: TabsProps['items'] = useMemo(() => {
const total = count.reduce((acc, cur) => acc + Number(cur.count), 0); const total = count.reduce((acc, cur) => acc + Number(cur.count), 0);
const tabs = [ const tabs = [
@ -316,31 +312,13 @@ const ListPage: React.FC = () => {
) : ( ) : (
<></> <></>
)} )}
<Button <Detail
key={record.id} key={record.id}
type="primary" record={record}
onClick={() => { tableRef={actionRef}
setDetailRecord(record); orderId={record.id as number}
setDetailOrderId(record.id as number); setActiveLine={setActiveLine}
setDetailOpen(true); />
setActiveLine(record.id);
}}
>
<FileDoneOutlined />
</Button>
{detailRecord && detailOrderId !== null && (
<OrderDetailDrawer
open={detailOpen}
onClose={() => setDetailOpen(false)}
tableRef={actionRef}
orderId={detailOrderId as number}
record={detailRecord as any}
setActiveLine={setActiveLine}
OrderNoteComponent={OrderNote}
SalesChangeComponent={SalesChange}
/>
)}
<Divider type="vertical" /> <Divider type="vertical" />
<Dropdown <Dropdown
menu={{ menu={{

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { import {
App, App,
Button, Button,
@ -10,8 +10,8 @@ import {
Space, Space,
Tag, Tag,
} from 'antd'; } from 'antd';
import { ActionType } from '@ant-design/pro-components'; import { ActionType, ProDescriptions } from '@ant-design/pro-components';
import { CopyOutlined, DownOutlined, FileDoneOutlined } from '@ant-design/icons'; import { CopyOutlined, DeleteFilled } from '@ant-design/icons';
// 服务器 API 引用(保持与原 index.tsx 一致) // 服务器 API 引用(保持与原 index.tsx 一致)
import { import {
@ -20,10 +20,12 @@ import {
ordercontrollerSyncorderbyid, ordercontrollerSyncorderbyid,
} from '@/servers/api/order'; } from '@/servers/api/order';
import { logisticscontrollerDelshipment } from '@/servers/api/logistics'; import { logisticscontrollerDelshipment } from '@/servers/api/logistics';
import { sitecontrollerAll } from '@/servers/api/site';
// 工具与子组件 // 工具与子组件
import { formatShipmentState, formatSource } from '@/utils/format'; import { formatShipmentState, formatSource } from '@/utils/format';
import RelatedOrders from './RelatedOrders'; import RelatedOrders from './RelatedOrders';
import { ORDER_STATUS_ENUM } from '@/constants';
// 中文注释:为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状 // 中文注释:为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状
// 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件 // 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件
@ -54,40 +56,25 @@ const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({
}) => { }) => {
const { message } = App.useApp(); const { message } = App.useApp();
const ref = useRef<ActionType>(); const ref = useRef<ActionType>();
const [detail, setDetail] = useState<any | null>(null);
// 中文注释:加载详情数据(与 index.tsx 中完全保持一致) // 中文注释:加载详情数据(与 index.tsx 中完全保持一致)
const initRequest = async () => { const initRequest = async () => {
const { data, success }: API.OrderDetailRes = const { data, success }: API.OrderDetailRes = await ordercontrollerGetorderdetail({ orderId });
await ordercontrollerGetorderdetail({ orderId }); if (!success || !data) return { data: {} } as any;
if (!success || !data) return null; data.sales = data.sales?.reduce((acc: API.OrderSale[], cur: API.OrderSale) => {
// 中文注释:销售项合并 SKU展示数量汇总 const idx = acc.findIndex((v: any) => v.productId === cur.productId);
data.sales = data.sales?.reduce( if (idx === -1) acc.push(cur);
(acc: API.OrderSale[], cur: API.OrderSale) => { else acc[idx].quantity += cur.quantity;
const idx = acc.findIndex((v: any) => v.productId === cur.productId); return acc;
if (idx === -1) acc.push(cur); }, []);
else acc[idx].quantity += cur.quantity; return { data } as any;
return acc;
},
[],
);
return data;
}; };
useEffect(() => { useEffect(() => {
(async () => { if (open && record?.id) {
if (open && orderId) { setActiveLine(record.id as number);
try { }
const data = await initRequest(); }, [open, record?.id]);
setDetail(data);
} catch (e) {
setDetail(null);
}
} else {
setDetail(null);
}
})();
}, [open, orderId]);
return ( return (
<Drawer <Drawer
@ -233,147 +220,106 @@ const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({
: []), : []),
]} ]}
> >
{(() => { setActiveLine(record.id as number); return null; })()} <ProDescriptions labelStyle={{ width: '100px' }} actionRef={ref} request={initRequest}>
{/** 中文注释:优先使用详情数据,其次使用列表行数据 */} <ProDescriptions.Item label="站点" dataIndex="siteId" valueType="select" request={async () => {
{(() => { const { data = [] } = await sitecontrollerAll();
const drec: any = detail || record; return data.map((item) => ({ label: item.siteName, value: item.id }));
{/* 中文注释:基本信息展示(保持原有格式) */} }} />
return (<> <ProDescriptions.Item label="订单日期" dataIndex="date_created" valueType="dateTime" />
<div style={{ marginBottom: 12 }}> <ProDescriptions.Item label="订单状态" dataIndex="orderStatus" valueType="select" valueEnum={ORDER_STATUS_ENUM as any} />
<Space> <ProDescriptions.Item label="金额" dataIndex="total" />
<Tag>{drec?.orderStatus}</Tag> <ProDescriptions.Item label="客户邮箱" dataIndex="customer_email" />
<Tag>{formatSource(drec.source_type, drec.utm_source)}</Tag> <ProDescriptions.Item label="联系电话" span={3} render={(_, r: any) => (
</Space> <div><span>{r?.shipping?.phone || r?.billing?.phone || '-'}</span></div>
</div> )} />
<ProDescriptions.Item label="交易Id" dataIndex="transaction_id" />
{/* 中文注释:原始订单行项目 */} <ProDescriptions.Item label="IP" dataIndex="customer_id_address" />
<div style={{ marginBottom: 12 }}> <ProDescriptions.Item label="设备" dataIndex="device_type" />
<div style={{ fontWeight: 600, marginBottom: 6 }}></div> <ProDescriptions.Item label="来源" render={(_, r: any) => formatSource(r.source_type, r.utm_source)} />
{Array.isArray((drec as any)?.items) && drec.items.length > 0 ? ( <ProDescriptions.Item label="原订单状态" dataIndex="status" valueType="select" valueEnum={ORDER_STATUS_ENUM as any} />
<ProDescriptions.Item label="支付链接" dataIndex="payment_url" span={3} copyable />
<ProDescriptions.Item label="客户备注" dataIndex="customer_note" span={3} />
<ProDescriptions.Item label="发货信息" span={3} render={(_, r: any) => (
<div>
<div>company<span>{r?.shipping?.company || r?.billing?.company || '-'}</span></div>
<div>first_name<span>{r?.shipping?.first_name || r?.billing?.first_name || '-'}</span></div>
<div>last_name<span>{r?.shipping?.last_name || r?.billing?.last_name || '-'}</span></div>
<div>country<span>{r?.shipping?.country || r?.billing?.country || '-'}</span></div>
<div>state<span>{r?.shipping?.state || r?.billing?.state || '-'}</span></div>
<div>city<span>{r?.shipping?.city || r?.billing?.city || '-'}</span></div>
<div>postcode<span>{r?.shipping?.postcode || r?.billing?.postcode || '-'}</span></div>
<div>phone<span>{r?.shipping?.phone || r?.billing?.phone || '-'}</span></div>
<div>address_1<span>{r?.shipping?.address_1 || r?.billing?.address_1 || '-'}</span></div>
</div>
)} />
<ProDescriptions.Item label="原始订单" span={3} render={(_, r: any) => (
<ul> <ul>
{drec.items.map((item: any) => ( {(r?.items || []).map((item: any) => (
<li key={item.id}> <li key={item.id}>{item.name}{item.quantity}</li>
{item.name}{item.quantity}
</li>
))} ))}
</ul> </ul>
) : ( )} />
<Empty description="暂无数据" /> <ProDescriptions.Item label="关联" span={3} render={(_, r: any) => (
)} <RelatedOrders data={r?.related} />
</div> )} />
<ProDescriptions.Item label="订单内容" span={3} render={(_, r: any) => (
{/* 中文注释:关联(订阅/订单),使用已存在的 RelatedOrders 组件 */}
<div style={{ marginBottom: 12 }}>
<div style={{ fontWeight: 600, marginBottom: 6 }}></div>
<RelatedOrders data={(drec as any)?.related} />
</div>
{/* 中文注释:订单内容(销售项) */}
<div style={{ marginBottom: 12 }}>
<div style={{ fontWeight: 600, marginBottom: 6 }}></div>
{Array.isArray((drec as any)?.sales) && drec.sales.length > 0 ? (
<ul> <ul>
{drec.sales.map((item: any) => ( {(r?.sales || []).map((item: any) => (
<li key={item.id}> <li key={item.id}>{item.name}{item.quantity}</li>
{item.name}{item.quantity}
</li>
))} ))}
</ul> </ul>
) : ( )} />
<Empty description="暂无数据" /> <ProDescriptions.Item label="换货" span={3} render={(_, r: any) => (
)} <SalesChangeComponent detailRef={ref} id={r.id as number} />
</div> )} />
<ProDescriptions.Item label="备注" span={3} render={(_, r: any) => {
{/* 中文注释:备注 */} if (!r.notes || r.notes.length === 0) return (<Empty description="暂无备注" />);
<div style={{ marginBottom: 12 }}> return (
<div style={{ fontWeight: 600, marginBottom: 6 }}></div> <div style={{ width: '100%' }}>
{Array.isArray((drec as any)?.notes) && drec.notes.length > 0 ? ( {r.notes.map((note: any) => (
<div style={{ width: '100%' }}> <div style={{ marginBottom: 10 }} key={note.id}>
{drec.notes.map((note: any) => ( <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<div style={{ marginBottom: 10 }} key={note.id}> <span>{note.username}</span>
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <span>{note.createdAt}</span>
<span>{note.username}</span> </div>
<span>{note.createdAt}</span> <div>{note.content}</div>
</div> </div>
<div>{note.content}</div> ))}
</div> </div>
))} );
</div> }} />
) : ( <ProDescriptions.Item label="物流信息" span={3} render={(_, r: any) => {
<Empty description="暂无备注" /> if (!r.shipment || r.shipment.length === 0) return (<Empty description="暂无物流信息" />);
)} return (
</div> <div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
{r.shipment.map((v: any) => (
{/* 中文注释:物流信息 */} <Card key={v.id} style={{ marginBottom: '10px' }} extra={formatShipmentState(v.state)} title={<>
<div style={{ marginBottom: 12 }}> {v.tracking_provider}
<div style={{ fontWeight: 600, marginBottom: 6 }}></div> {v.primary_tracking_number}
{Array.isArray((drec as any)?.shipment) && drec.shipment.length > 0 ? ( <CopyOutlined onClick={async () => {
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}> try { await navigator.clipboard.writeText(v.tracking_url); message.success('复制成功!'); }
{(drec as any).shipment.map((v: any) => ( catch { message.error('复制失败!'); }
<Card }} />
key={v.id} </>}
style={{ marginBottom: '10px' }} actions={ (v.state === 'waiting-for-scheduling' || v.state === 'waiting-for-transit') ? [
extra={formatShipmentState(v.state)} <Popconfirm key="action-cancel" title="取消运单" description="确认取消运单?" onConfirm={async () => {
title={ try { const { success, message: errMsg } = await logisticscontrollerDelshipment({ id: v.id }); if (!success) throw new Error(errMsg); tableRef.current?.reload(); ref.current?.reload?.(); }
<> catch (error: any) { message.error(error.message); }
{v.tracking_provider} }}>
{v.primary_tracking_number} <DeleteFilled />
<CopyOutlined </Popconfirm>
onClick={async () => { ] : [] }
try { >
await navigator.clipboard.writeText(v.tracking_url); <div> {Array.isArray(v?.orderIds) ? v.orderIds.join(',') : '-'}</div>
message.success('复制成功!'); {Array.isArray(v?.items) && v.items.map((item: any) => (
} catch (err) { <div key={item.id}>{item.name}: {item.quantity}</div>
message.error('复制失败!');
}
}}
/>
</>
}
actions={
v.state === 'waiting-for-scheduling' || v.state === 'waiting-for-transit'
? [
<Popconfirm
key="action-cancel"
title="取消运单"
description="确认取消运单?"
onConfirm={async () => {
try {
const { success, message: errMsg } = await logisticscontrollerDelshipment({ id: v.id });
if (!success) throw new Error(errMsg);
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
</Popconfirm>,
]
: []
}
>
<div> {Array.isArray(v?.orderIds) ? v.orderIds.join(',') : '-'}</div>
{Array.isArray(v?.items) &&
v.items.map((item: any) => (
<div key={item.id}>
{item.name}: {item.quantity}
</div>
))} ))}
</Card> </Card>
))} ))}
</div> </div>
) : ( );
<Empty description="暂无物流信息" /> }} />
)} </ProDescriptions>
</div>
{/* 中文注释:换货(外部传入组件) */}
<div style={{ marginBottom: 12 }}>
<SalesChangeComponent detailRef={ref} id={drec.id as number} />
</div>
</>);
})()}
</Drawer> </Drawer>
); );
}; };