import React, { useEffect, useRef, useState } from 'react'; import { Button, DatePicker, Descriptions, Drawer, Empty, Form, message, Modal, Popconfirm, Radio, Space, Tag, Typography, Image, } from 'antd'; import dayjs from 'dayjs'; import XinTable from '@/components/XinTable'; import type { XinTableColumn, XinTableInstance, XinTableProps, } from '@/components/XinTable/typings.ts'; import type IStoreOrder from '@/domain/iStoreOrder.ts'; import { STORE_ORDER_STATUS_MAP } from '@/domain/iStoreOrder.ts'; import { getStoreOrder, updateOrderStatus, batchUpdateOrderStatus, deleteStoreOrder, } from '@/api/order/store.ts'; import { generatePurchase } from '@/api/purchase/order.ts'; import { getStoreOptions } from '@/api/customer/store.ts'; import type IStore from '@/domain/iStore.ts'; import AuthButton from '@/components/AuthButton'; import { DeleteOutlined, UnorderedListOutlined } from '@ant-design/icons'; import {PURCHASE_STATUS_MAP} from "@/domain/iPurchaseOrder.ts"; const { Title, Text } = Typography; /** * 状态流转合法路径:手动流转仅保留「接单」「取消订单」 * (已接单→采购中 由「生成采购单」推进;采购单完成→配送中、生成账单→已完成 由业务链路自动推进) */ const NEXT_STATUS: Record = { 0: { status: 1, label: '接单' }, }; /** 批量流转可选目标状态(弹窗单选,后端全量校验,任一不允许则整批中止) */ const BATCH_TARGET_OPTIONS = [ { value: 1, label: '接单(待接单 → 已接单)' }, { value: 9, label: '取消订单(待接单 → 已取消)' }, ]; /** * 门店订单管理(只读 + 状态流转,订单由小程序端创建) */ const StoreOrderPage: React.FC = () => { const tableRef = useRef>(null); const [stores, setStores] = useState([]); const [detailOpen, setDetailOpen] = useState(false); const [detail, setDetail] = useState(null); const [detailLoading, setDetailLoading] = useState(false); /** 勾选行(批量流转/生成采购单用) */ const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [batchOpen, setBatchOpen] = useState(false); const [batchTarget, setBatchTarget] = useState(null); const [batchLoading, setBatchLoading] = useState(false); /** 生成采购单(合并已接单订单,生成后源订单转采购中) */ const [generateOpen, setGenerateOpen] = useState(false); const [generateLoading, setGenerateLoading] = useState(false); const [generateForm] = Form.useForm<{ purchase_date: dayjs.Dayjs }>(); 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 handleBatchStatus = async () => { if (batchTarget === null) { message.warning('请选择要流转的目标状态'); return; } setBatchLoading(true); try { const res = await batchUpdateOrderStatus(selectedRowKeys as number[], batchTarget); message.success(`已批量流转 ${res.data.data?.success ?? 0} 单`); setBatchOpen(false); setBatchTarget(null); setSelectedRowKeys([]); await tableRef.current?.reload(); } finally { setBatchLoading(false); } }; /** 生成采购单:勾选时合并所选订单(须均为已接单),未勾选时合并全部已接单订单 */ const handleGeneratePurchase = async (values: { purchase_date: dayjs.Dayjs }) => { setGenerateLoading(true); try { const orderIds = selectedRowKeys.length > 0 ? selectedRowKeys.map(Number) : undefined; const res = await generatePurchase(values.purchase_date.format('YYYY-MM-DD'), orderIds); message.success(`采购单 ${res.data.data?.purchase_no} 已生成,源订单已转为采购中`); setGenerateOpen(false); setSelectedRowKeys([]); await tableRef.current?.reload(); } finally { setGenerateLoading(false); } }; /** 删除订单(软删除,仅已取消订单可删;删除后后台/小程序端均不可见) */ const handleDelete = async (id: number) => { await deleteStoreOrder(id); message.success('订单已删除'); setDetailOpen(false); await tableRef.current?.reload(); }; const columns: XinTableColumn[] = [ { title: '订单号', hideInTable: true, dataIndex: 'order_no', valueType: 'text', hideInForm: true }, { title: '商品名称', hideInTable: true, dataIndex: 'product_name', valueType: 'text', hideInForm: true }, { title: '基本信息', dataIndex: 'order_no', hideInForm: true, hideInSearch: true, width: 300, render: (_, record) => { return (
订单号: {record.order_no}
订货时间:{record.created_at}
订单状态: {STORE_ORDER_STATUS_MAP[record.status ?? 0]?.text}
) } }, { title: '商品信息', dataIndex: 'items', hideInForm: true, hideInSearch: true, width: 550, render: (_, record) => { return ( { record.items && record.items.length > 0 && record.items.map(item => (
{item.product_name}
{item.product_spec} {item.unit}
{ item.price } ¥ × { item.quantity }
))}
) } }, { title: '门店', dataIndex: 'store_id', valueType: 'select', hideInForm: true, hideInTable: true, fieldProps: { options: stores.map((s) => ({ label: s.name, value: s.id })), showSearch: true, optionFilterProp: 'label', }, render: (_, record) => record.store?.name ?? '-', }, { title: '门店信息', dataIndex: 'store_id', hideInForm: true, hideInSearch: true, width: 300, render: (_, record) => (
订货门店: {record.store?.name ?? `门店#${record.store_id}`}
联系人:{ record.store?.contact ?? '-' }
联系电话:{ record.store?.phone ?? '-' }
门店地址:{ record.store?.address ?? '-' }
), }, { title: '订货日期', dataIndex: 'order_date', valueType: 'dateRange', hideInForm: true, hideInTable: true, align: 'center', }, { title: '附加信息', hideInForm: true, dataIndex: 'status', hideInSearch: true, width: 200, render: (_, record) => (
订货数量: {record.total_quantity}
总重量: {record.total_weight}斤
) }, { title: '订单金额', hideInForm: true, dataIndex: 'total_amount', hideInSearch: true, width: 200, render: (text) => ( {text} ¥ ) }, { title: '订单状态', dataIndex: 'status', valueType: 'select', hideInForm: true, align: 'center', hideInTable: true, fieldProps: { options: Object.entries(STORE_ORDER_STATUS_MAP).map(([value, item]) => ({ value: Number(value), label: item.text, })), } }, { title: '采购单', dataIndex: 'purchase_id', valueType: 'digit', hideInForm: true, render: (_, record) => record.purchase ? (
采购单号: {record.purchase.purchase_no}
采购单日期: {record.purchase.purchase_date}
采购单状态: {PURCHASE_STATUS_MAP[record.purchase.status ?? 0]?.text}
) : '-' }, { title: '账单ID', dataIndex: 'bill_id', valueType: 'digit', hideInForm: true, }, { title: '操作栏', dataIndex: 'operate', hideInForm: true, hideInSearch: true, width: 180, render: (_, record) => ( )} { record.status === 0 && ( handleStatusChange(record.id!, 9)} > )} { record.status === 9 && ( handleDelete(record.id!)} > , , dom.keywordSearch, ], }; return ( <>
门店订单 小程序下单为待接单;接单后通过「生成采购单」合并为采购单并转采购中,采购单完成后订单自动转配送中,生成账单后订单自动完成。
{...tableProps} /> setDetailOpen(false)} size={1000} loading={detailLoading} footer={ detail ? ( { NEXT_STATUS[detail.status!] && ( handleStatusChange(detail.id!, NEXT_STATUS[detail.status!].status)} > )} { detail.status === 0 && ( handleStatusChange(detail.id!, 9)} > )} { detail.status === 9 && ( handleDelete(detail.id!)} > )} ) : null } > {detail ? ( <> {/* 门店信息 */} {detail.store?.name ?? `门店#${detail.store_id}`} {detail.store?.contact ?? '-'} {detail.store?.phone ?? '-'} {detail.store?.address ?? '-'} {/* 订单信息 */} {detail.order_no} {detail.order_date} {STORE_ORDER_STATUS_MAP[detail.status ?? 0]?.text} {detail.total_quantity} {detail.total_weight}斤 {detail.bill_id ?? '-'} {detail.remark ? ( {detail.remark} ) : null} {/* 商品明细(商城模式:首图 + 单价 × 订货量 + 金额) */} 商品明细
商品信息
供应商
单价
订货量
订货金额
重量
{(detail.items ?? []).map((item) => (
{item.image ? ( ) : (
暂无图片
)}
{item.product_name}
{item.product_spec || '-'} {item.unit || '-'}
成本:¥{item.cost_price ?? '0.00'}
{item.remark ? (
备注:{item.remark}
) : null}
{item.supplier?.name ?? '-'}
¥{item.price}
{item.quantity}
¥{item.amount}
{item.weight ?? '-'} 斤
))}
总计
{detail.total_quantity}
¥{detail.total_amount}
{detail.total_weight}斤
{(detail.items ?? []).length === 0 && ( )}
) : null}
{/* 生成采购单:合并已接单订单 */} setGenerateOpen(false)} onOk={() => generateForm.submit()} confirmLoading={generateLoading} okText="确认生成" destroyOnHidden >
{selectedRowKeys.length > 0 ? `将合并选中的 ${selectedRowKeys.length} 笔订单生成一张采购单(须均为已接单状态,否则整批中止)。` : '未勾选订单时,将合并全部「已接单」订单生成一张采购单。'} 生成后源订单自动转为「采购中」。
{/* 批量状态流转 */} setBatchOpen(false)} onOk={handleBatchStatus} confirmLoading={batchLoading} okText="确认流转" destroyOnHidden >
已勾选 {selectedRowKeys.length} 笔订单;任一订单不满足流转条件时,本次批量操作将整批中止,不做任何修改。
setBatchTarget(e.target.value as number)} > {BATCH_TARGET_OPTIONS.map((opt) => ( {opt.label} ))}
); }; export default StoreOrderPage;