253 lines
8.0 KiB
TypeScript
253 lines
8.0 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import {
|
|
Button,
|
|
Descriptions,
|
|
Drawer,
|
|
message,
|
|
Popconfirm,
|
|
Space,
|
|
Table,
|
|
Tag,
|
|
Typography,
|
|
} from 'antd';
|
|
import type { TableProps } from 'antd';
|
|
import XinTable from '@/components/XinTable';
|
|
import type {
|
|
XinTableColumn,
|
|
XinTableInstance,
|
|
XinTableProps,
|
|
} from '@/components/XinTable/typings.ts';
|
|
import type IStoreOrder from '@/domain/iStoreOrder.ts';
|
|
import type { IStoreOrderItem } from '@/domain/iStoreOrder.ts';
|
|
import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts';
|
|
import { getStoreOrder, updateOrderStatus } from '@/api/order/store.ts';
|
|
import { getStoreOptions } from '@/api/customer/store.ts';
|
|
import type IStore from '@/domain/iStore.ts';
|
|
import AuthButton from '@/components/AuthButton';
|
|
|
|
const { Title, Text } = Typography;
|
|
|
|
/**
|
|
* 状态流转合法路径:待汇总→配送中/取消;已汇总→配送中;配送中→已完成
|
|
*/
|
|
const NEXT_STATUS: Record<number, { status: number; label: string; danger?: boolean }[]> = {
|
|
0: [
|
|
{ status: 2, label: '开始配送' },
|
|
{ status: 9, label: '取消订单', danger: true },
|
|
],
|
|
1: [{ status: 2, label: '开始配送' }],
|
|
2: [{ status: 3, label: '完成订单' }],
|
|
};
|
|
|
|
/**
|
|
* 门店订单管理(只读 + 状态流转,订单由小程序端创建)
|
|
*/
|
|
const StoreOrderPage: React.FC = () => {
|
|
const tableRef = useRef<XinTableInstance<IStoreOrder>>(null);
|
|
const [stores, setStores] = useState<IStore[]>([]);
|
|
|
|
const [detailOpen, setDetailOpen] = useState(false);
|
|
const [detail, setDetail] = useState<IStoreOrder | null>(null);
|
|
const [detailLoading, setDetailLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
getStoreOptions().then((res) => setStores(res.data.data ?? []));
|
|
}, []);
|
|
|
|
const openDetail = async (id: number) => {
|
|
setDetailOpen(true);
|
|
setDetailLoading(true);
|
|
try {
|
|
const res = await getStoreOrder(id);
|
|
setDetail(res.data.data ?? null);
|
|
} finally {
|
|
setDetailLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleStatusChange = async (id: number, status: number) => {
|
|
await updateOrderStatus(id, status);
|
|
message.success('状态已更新');
|
|
setDetailOpen(false);
|
|
await tableRef.current?.reload();
|
|
};
|
|
|
|
const itemColumns: TableProps<IStoreOrderItem>['columns'] = [
|
|
{ title: '品名', dataIndex: 'product_name' },
|
|
{ title: '规格', dataIndex: 'product_spec', render: (v) => v || '-' },
|
|
{ title: '单价', dataIndex: 'price', align: 'right', render: (v) => `¥${v}` },
|
|
{ title: '订货量', dataIndex: 'quantity', align: 'right' },
|
|
{ title: '金额', dataIndex: 'amount', align: 'right', render: (v) => `¥${v}` },
|
|
{ title: '备注', dataIndex: 'remark', render: (v) => v || '-' },
|
|
];
|
|
|
|
const columns: XinTableColumn<IStoreOrder>[] = [
|
|
{
|
|
title: '订单号',
|
|
dataIndex: 'order_no',
|
|
valueType: 'text',
|
|
hideInForm: true,
|
|
render: (_, record) => <Text copyable={{ text: record.order_no }}>{record.order_no}</Text>,
|
|
},
|
|
{
|
|
title: '门店',
|
|
dataIndex: 'store_id',
|
|
valueType: 'select',
|
|
hideInForm: true,
|
|
fieldProps: {
|
|
options: stores.map((s) => ({ label: s.name, value: s.id })),
|
|
showSearch: true,
|
|
optionFilterProp: 'label',
|
|
},
|
|
render: (_, record) => record.store?.name ?? '-',
|
|
},
|
|
{
|
|
title: '订货日期',
|
|
dataIndex: 'order_date',
|
|
valueType: 'dateRange',
|
|
hideInForm: true,
|
|
align: 'center',
|
|
render: (_, record) => record.order_date,
|
|
},
|
|
{
|
|
title: '订货总量',
|
|
dataIndex: 'total_quantity',
|
|
hideInForm: true,
|
|
hideInSearch: true,
|
|
align: 'right',
|
|
},
|
|
{
|
|
title: '订单金额',
|
|
dataIndex: 'total_amount',
|
|
hideInForm: true,
|
|
hideInSearch: true,
|
|
align: 'right',
|
|
render: (_, record) => <Text strong>¥{record.total_amount}</Text>,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
valueType: 'select',
|
|
hideInForm: true,
|
|
fieldProps: {
|
|
options: Object.entries(STORE_ORDER_STATUS_MAP).map(([value, item]) => ({
|
|
value: Number(value),
|
|
label: item.text,
|
|
})),
|
|
},
|
|
render: (_, record) => {
|
|
const item = STORE_ORDER_STATUS_MAP[record.status ?? 0];
|
|
return <Tag color={item?.color}>{item?.text}</Tag>;
|
|
},
|
|
align: 'center',
|
|
},
|
|
{
|
|
title: '备注',
|
|
dataIndex: 'remark',
|
|
hideInForm: true,
|
|
hideInSearch: true,
|
|
ellipsis: true,
|
|
render: (_, record) => record.remark || '-',
|
|
},
|
|
];
|
|
|
|
const operateRender: XinTableProps<IStoreOrder>['operateRender'] = (record) => [
|
|
<Button key="detail" size="small" onClick={() => openDetail(record.id!)}>
|
|
详情
|
|
</Button>,
|
|
];
|
|
|
|
const tableProps: XinTableProps<IStoreOrder> = {
|
|
api: '/order/store',
|
|
columns,
|
|
rowKey: 'id',
|
|
accessName: 'order.store',
|
|
tableRef,
|
|
operateRender,
|
|
formProps: false,
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="mb-5">
|
|
<Title level={3}>门店订单</Title>
|
|
<Text type="secondary">
|
|
小程序下单汇总;待汇总订单将进入采购单生成,订单创建与取消在小程序端完成。
|
|
</Text>
|
|
</div>
|
|
<XinTable<IStoreOrder> {...tableProps} />
|
|
|
|
<Drawer
|
|
title="订单详情"
|
|
open={detailOpen}
|
|
onClose={() => setDetailOpen(false)}
|
|
width={720}
|
|
loading={detailLoading}
|
|
footer={
|
|
detail && NEXT_STATUS[detail.status ?? -1] ? (
|
|
<Space className="flex justify-end">
|
|
{NEXT_STATUS[detail.status!].map((action) => (
|
|
<AuthButton key={action.status} auth="order.store.update">
|
|
<Popconfirm
|
|
title={`确认将订单状态更新为「${action.label}」?`}
|
|
onConfirm={() => handleStatusChange(detail.id!, action.status)}
|
|
>
|
|
<Button type={action.danger ? undefined : 'primary'} danger={action.danger}>
|
|
{action.label}
|
|
</Button>
|
|
</Popconfirm>
|
|
</AuthButton>
|
|
))}
|
|
</Space>
|
|
) : null
|
|
}
|
|
>
|
|
{detail ? (
|
|
<>
|
|
<Descriptions column={2} size="small" bordered>
|
|
<Descriptions.Item label="订单号">{detail.order_no}</Descriptions.Item>
|
|
<Descriptions.Item label="门店">{detail.store?.name}</Descriptions.Item>
|
|
<Descriptions.Item label="订货日期">{detail.order_date}</Descriptions.Item>
|
|
<Descriptions.Item label="状态">
|
|
<Tag color={STORE_ORDER_STATUS_MAP[detail.status ?? 0]?.color}>
|
|
{STORE_ORDER_STATUS_MAP[detail.status ?? 0]?.text}
|
|
</Tag>
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="订货总量">{detail.total_quantity}</Descriptions.Item>
|
|
<Descriptions.Item label="订单金额">¥{detail.total_amount}</Descriptions.Item>
|
|
{detail.remark ? (
|
|
<Descriptions.Item label="备注" span={2}>
|
|
{detail.remark}
|
|
</Descriptions.Item>
|
|
) : null}
|
|
</Descriptions>
|
|
<Title level={5} className="!mt-6 !mb-3">
|
|
商品明细
|
|
</Title>
|
|
<Table<IStoreOrderItem>
|
|
rowKey="id"
|
|
size="small"
|
|
columns={itemColumns}
|
|
dataSource={detail.items ?? []}
|
|
pagination={false}
|
|
summary={() => (
|
|
<Table.Summary.Row>
|
|
<Table.Summary.Cell index={0} colSpan={4} align="right">
|
|
合计
|
|
</Table.Summary.Cell>
|
|
<Table.Summary.Cell index={1} align="right">
|
|
<Text strong>¥{detail.total_amount}</Text>
|
|
</Table.Summary.Cell>
|
|
<Table.Summary.Cell index={2} />
|
|
</Table.Summary.Row>
|
|
)}
|
|
/>
|
|
</>
|
|
) : null}
|
|
</Drawer>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default StoreOrderPage;
|