WEB/src/pages/Order/List/OrderDetailDrawer.tsx

381 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
App,
Button,
Card,
Divider,
Drawer,
Empty,
Popconfirm,
Space,
Tag,
} from 'antd';
import { ActionType } from '@ant-design/pro-components';
import { CopyOutlined, DownOutlined, FileDoneOutlined } from '@ant-design/icons';
// 服务器 API 引用(保持与原 index.tsx 一致)
import {
ordercontrollerChangestatus,
ordercontrollerGetorderdetail,
ordercontrollerSyncorderbyid,
} from '@/servers/api/order';
import { logisticscontrollerDelshipment } from '@/servers/api/logistics';
// 工具与子组件
import { formatShipmentState, formatSource } from '@/utils/format';
import RelatedOrders from './RelatedOrders';
// 中文注释:为保持原文件结构简单,此处从 index.tsx 引入的子组件仍由原文件导出或保持原状
// 若后续需要彻底解耦,可将 OrderNote / Shipping / SalesChange 也独立到文件
// 当前按你的要求仅抽离详情 Drawer
type OrderRecord = API.Order;
interface OrderDetailDrawerProps {
tableRef: React.MutableRefObject<ActionType | undefined>; // 中文注释:列表刷新引用
orderId: number; // 中文注释:订单主键 ID
record: OrderRecord; // 中文注释:订单行记录
open: boolean; // 中文注释:是否打开抽屉
onClose: () => void; // 中文注释:关闭抽屉回调
setActiveLine: (id: number) => void; // 中文注释:高亮当前行
OrderNoteComponent: React.ComponentType<any>; // 中文注释:备注组件(从外部注入)
SalesChangeComponent: React.ComponentType<any>; // 中文注释:换货组件(从外部注入)
}
const OrderDetailDrawer: React.FC<OrderDetailDrawerProps> = ({
tableRef,
orderId,
record,
open,
onClose,
setActiveLine,
OrderNoteComponent,
SalesChangeComponent,
}) => {
const { message } = App.useApp();
const ref = useRef<ActionType>();
const [detail, setDetail] = useState<any | null>(null);
// 中文注释:加载详情数据(与 index.tsx 中完全保持一致)
const initRequest = async () => {
const { data, success }: API.OrderDetailRes =
await ordercontrollerGetorderdetail({ orderId });
if (!success || !data) return null;
// 中文注释:销售项合并 SKU展示数量汇总
data.sales = data.sales?.reduce(
(acc: API.OrderSale[], cur: API.OrderSale) => {
const idx = acc.findIndex((v: any) => v.productId === cur.productId);
if (idx === -1) acc.push(cur);
else acc[idx].quantity += cur.quantity;
return acc;
},
[],
);
return data;
};
useEffect(() => {
(async () => {
if (open && orderId) {
try {
const data = await initRequest();
setDetail(data);
} catch (e) {
setDetail(null);
}
} else {
setDetail(null);
}
})();
}, [open, orderId]);
return (
<Drawer
title="订单详情"
open={open}
destroyOnHidden
size="large"
onClose={onClose}
footer={[
// 中文注释:备注组件(外部传入以避免循环依赖)
<OrderNoteComponent key="order-note" id={orderId} descRef={ref} />,
...(['after_sale_pending', 'pending_reshipment'].includes(
record.orderStatus,
)
? []
: [
<Divider key="divider-sync" type="vertical" />,
<Button
key="btn-sync"
type="primary"
onClick={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerSyncorderbyid({
siteId: record.siteId as string,
orderId: record.externalOrderId as string,
});
if (!success) throw new Error(errMsg);
message.success('同步成功');
tableRef.current?.reload();
} catch (error) {
message.error(error?.message || '同步失败');
}
}}
>
</Button>,
]),
...([
'processing',
'pending_reshipment',
'completed',
'pending_refund',
].includes(record.orderStatus)
? [
<Divider key="divider-after-sale" type="vertical" />,
<Popconfirm
key="btn-after-sale"
title="转至售后"
description="确认转至售后?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{ id: record.id },
{ status: 'after_sale_pending' },
);
if (!success) throw new Error(errMsg);
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
]
: []),
...(record.orderStatus === 'after_sale_pending'
? [
<Divider key="divider-cancel" type="vertical" />,
<Popconfirm
key="btn-cancel"
title="转至取消"
description="确认转至取消?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{ id: record.id },
{ status: 'cancelled' },
);
if (!success) throw new Error(errMsg);
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
<Divider key="divider-refund" type="vertical" />,
<Popconfirm
key="btn-refund"
title="转至退款"
description="确认转至退款?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{ id: record.id },
{ status: 'refund_requested' },
);
if (!success) throw new Error(errMsg);
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
退
</Button>
</Popconfirm>,
<Divider key="divider-completed" type="vertical" />,
<Popconfirm
key="btn-completed"
title="转至完成"
description="确认转至完成?"
onConfirm={async () => {
try {
const { success, message: errMsg } =
await ordercontrollerChangestatus(
{ id: record.id },
{ status: 'completed' },
);
if (!success) throw new Error(errMsg);
tableRef.current?.reload();
} catch (error: any) {
message.error(error.message);
}
}}
>
<Button type="primary" ghost>
</Button>
</Popconfirm>,
]
: []),
]}
>
{(() => { setActiveLine(record.id as number); return null; })()}
{/** 中文注释:优先使用详情数据,其次使用列表行数据 */}
{(() => {
const drec: any = detail || record;
{/* 中文注释:基本信息展示(保持原有格式) */}
return (<>
<div style={{ marginBottom: 12 }}>
<Space>
<Tag>{drec?.orderStatus}</Tag>
<Tag>{formatSource(drec.source_type, drec.utm_source)}</Tag>
</Space>
</div>
{/* 中文注释:原始订单行项目 */}
<div style={{ marginBottom: 12 }}>
<div style={{ fontWeight: 600, marginBottom: 6 }}></div>
{Array.isArray((drec as any)?.items) && drec.items.length > 0 ? (
<ul>
{drec.items.map((item: any) => (
<li key={item.id}>
{item.name}{item.quantity}
</li>
))}
</ul>
) : (
<Empty description="暂无数据" />
)}
</div>
{/* 中文注释:关联(订阅/订单),使用已存在的 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>
{drec.sales.map((item: any) => (
<li key={item.id}>
{item.name}{item.quantity}
</li>
))}
</ul>
) : (
<Empty description="暂无数据" />
)}
</div>
{/* 中文注释:备注 */}
<div style={{ marginBottom: 12 }}>
<div style={{ fontWeight: 600, marginBottom: 6 }}></div>
{Array.isArray((drec as any)?.notes) && drec.notes.length > 0 ? (
<div style={{ width: '100%' }}>
{drec.notes.map((note: any) => (
<div style={{ marginBottom: 10 }} key={note.id}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>{note.username}</span>
<span>{note.createdAt}</span>
</div>
<div>{note.content}</div>
</div>
))}
</div>
) : (
<Empty description="暂无备注" />
)}
</div>
{/* 中文注释:物流信息 */}
<div style={{ marginBottom: 12 }}>
<div style={{ fontWeight: 600, marginBottom: 6 }}></div>
{Array.isArray((drec as any)?.shipment) && drec.shipment.length > 0 ? (
<div style={{ display: 'flex', flexDirection: 'column', width: '100%' }}>
{(drec as any).shipment.map((v: any) => (
<Card
key={v.id}
style={{ marginBottom: '10px' }}
extra={formatShipmentState(v.state)}
title={
<>
{v.tracking_provider}
{v.primary_tracking_number}
<CopyOutlined
onClick={async () => {
try {
await navigator.clipboard.writeText(v.tracking_url);
message.success('复制成功!');
} catch (err) {
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>
))}
</div>
) : (
<Empty description="暂无物流信息" />
)}
</div>
{/* 中文注释:换货(外部传入组件) */}
<div style={{ marginBottom: 12 }}>
<SalesChangeComponent detailRef={ref} id={drec.id as number} />
</div>
</>);
})()}
</Drawer>
);
};
export default OrderDetailDrawer;