686 lines
25 KiB
TypeScript
686 lines
25 KiB
TypeScript
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 { BILL_PAY_STATE_MAP, 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<number, { status: number; label: string; }> = {
|
||
0: { status: 1, label: '接单' },
|
||
};
|
||
|
||
/** 批量流转可选目标状态(弹窗单选,后端全量校验,任一不允许则整批中止) */
|
||
const BATCH_TARGET_OPTIONS = [
|
||
{ value: 1, label: '接单(待接单 → 已接单)' },
|
||
{ value: 9, 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);
|
||
|
||
/** 勾选行(批量流转/生成采购单用) */
|
||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||
const [batchOpen, setBatchOpen] = useState(false);
|
||
const [batchTarget, setBatchTarget] = useState<number | null>(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<IStoreOrder>[] = [
|
||
{
|
||
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 (
|
||
<Space orientation={'vertical'}>
|
||
<div>
|
||
<Text type={'secondary'}>订单号:</Text>
|
||
<Text copyable={{ text: record.order_no }}>{record.order_no}</Text>
|
||
</div>
|
||
<div><Text type={'secondary'}>订货时间:</Text>{record.created_at}</div>
|
||
<div>
|
||
<Text type={'secondary'}>订单状态:</Text>
|
||
<Tag color={STORE_ORDER_STATUS_MAP[record.status ?? 0]?.color}>
|
||
{STORE_ORDER_STATUS_MAP[record.status ?? 0]?.text}
|
||
</Tag>
|
||
</div>
|
||
</Space>
|
||
)
|
||
}
|
||
},
|
||
{
|
||
title: '商品信息',
|
||
dataIndex: 'items',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
defaultHiddenInTable: true,
|
||
width: 550,
|
||
render: (_, record) => {
|
||
return (
|
||
<Space wrap size={20}>
|
||
{ record.items && record.items.length > 0 && record.items.map(item => (
|
||
<div className={'flex'}>
|
||
<Image src={item.image} width={60} height={60} ></Image>
|
||
<div className={'ml-2.5'}>
|
||
<div>{item.product_name}</div>
|
||
<div className={'text-[12px] text-[#999]'}>{item.product_spec} {item.unit}</div>
|
||
<div className={'text-[12px] text-[#999]'}>
|
||
<span className={'text-[red]'}>{ item.price } ¥</span> × { item.quantity }
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</Space>
|
||
)
|
||
}
|
||
},
|
||
{
|
||
title: '门店',
|
||
dataIndex: 'store_id',
|
||
valueType: 'select',
|
||
hideInForm: true,
|
||
hideInTable: true,
|
||
fieldProps: {
|
||
options: [{ label: '全部', value: '' }, ...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) => (
|
||
<Space orientation={'vertical'}>
|
||
<div>
|
||
<Text type={'secondary'}>订货门店:</Text>
|
||
<Tag color="blue">{record.store?.name ?? `门店#${record.store_id}`}</Tag>
|
||
</div>
|
||
<div><Text type={'secondary'}>联系人:</Text>{ record.store?.contact ?? '-' }</div>
|
||
<div><Text type={'secondary'}>联系电话:</Text>{ record.store?.phone ?? '-' }</div>
|
||
<div><Text type={'secondary'}>门店地址:</Text>{ record.store?.address ?? '-' }</div>
|
||
</Space>
|
||
),
|
||
},
|
||
{
|
||
title: '订货日期',
|
||
dataIndex: 'order_date',
|
||
valueType: 'dateRange',
|
||
hideInForm: true,
|
||
hideInTable: true,
|
||
align: 'center',
|
||
fieldProps: {
|
||
presets: [
|
||
{ label: '今天', value: [dayjs(), dayjs()] },
|
||
{ label: '昨天', value: [dayjs().subtract(1, 'day'), dayjs().subtract(1, 'day')] },
|
||
{ label: '前天', value: [dayjs().subtract(2, 'day'), dayjs().subtract(2, 'day')] },
|
||
{ label: '本周', value: [dayjs().startOf('week'), dayjs().endOf('week')] },
|
||
{ label: '本月', value: [dayjs().startOf('month'), dayjs().endOf('month')] },
|
||
],
|
||
},
|
||
},
|
||
{
|
||
title: '附加信息',
|
||
hideInForm: true,
|
||
dataIndex: 'status',
|
||
hideInSearch: true,
|
||
width: 200,
|
||
render: (_, record) => (
|
||
<Space orientation={'vertical'}>
|
||
<div>
|
||
<Text type={'secondary'}>订货数量:</Text>
|
||
{record.total_quantity}
|
||
</div>
|
||
<div>
|
||
<Text type={'secondary'}>总重量:</Text>
|
||
{record.total_weight}斤
|
||
</div>
|
||
</Space>
|
||
)
|
||
},
|
||
{
|
||
title: '订单金额',
|
||
hideInForm: true,
|
||
dataIndex: 'total_amount',
|
||
hideInSearch: true,
|
||
width: 200,
|
||
render: (text) => (
|
||
<span className={'text-[red] text-[16px]'}>{text} ¥</span>
|
||
)
|
||
},
|
||
{
|
||
title: '订单状态',
|
||
dataIndex: 'status',
|
||
valueType: 'select',
|
||
hideInForm: true,
|
||
align: 'center',
|
||
hideInTable: true,
|
||
fieldProps: {
|
||
options: [
|
||
{ label: '全部', value: '' },
|
||
...Object.entries(STORE_ORDER_STATUS_MAP).map(([value, item]) => ({
|
||
value: Number(value),
|
||
label: item.text,
|
||
})),
|
||
],
|
||
}
|
||
},
|
||
{
|
||
title: '采购单号',
|
||
dataIndex: 'purchase_no',
|
||
valueType: 'text',
|
||
hideInForm: true,
|
||
hideInTable: true,
|
||
fieldProps: {
|
||
placeholder: '采购单号',
|
||
allowClear: true,
|
||
},
|
||
},
|
||
{
|
||
title: '采购单',
|
||
dataIndex: 'purchase_id',
|
||
valueType: 'digit',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
render: (_, record) => record.purchase ? (
|
||
<Space orientation={'vertical'}>
|
||
<div>
|
||
<Text type={'secondary'}>采购单号:</Text>
|
||
{record.purchase.purchase_no}
|
||
</div>
|
||
<div>
|
||
<Text type={'secondary'}>采购单日期:</Text>
|
||
{record.purchase.purchase_date}
|
||
</div>
|
||
<div>
|
||
<Text type={'secondary'}>采购单状态:</Text>
|
||
<Tag color={PURCHASE_STATUS_MAP[record.purchase.status ?? 0]?.color}>
|
||
{PURCHASE_STATUS_MAP[record.purchase.status ?? 0]?.text}
|
||
</Tag>
|
||
</div>
|
||
</Space>
|
||
) : '-'
|
||
},
|
||
{
|
||
title: '账单信息',
|
||
dataIndex: 'bill_id',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
width: 240,
|
||
render: (_, record) => record.bill ? (
|
||
<Space orientation={'vertical'}>
|
||
<div>
|
||
<Text type={'secondary'}>账单号:</Text>
|
||
{record.bill.bill_no}
|
||
</div>
|
||
<div>
|
||
<Text type={'secondary'}>支付状态:</Text>
|
||
<Tag color={BILL_PAY_STATE_MAP[record.bill.pay_state ?? 0]?.color}>
|
||
{record.bill.pay_state_name ?? BILL_PAY_STATE_MAP[record.bill.pay_state ?? 0]?.text}
|
||
</Tag>
|
||
</div>
|
||
</Space>
|
||
) : '-'
|
||
},
|
||
{
|
||
title: '操作栏',
|
||
dataIndex: 'operate',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
width: 180,
|
||
render: (_, record) => (
|
||
<Space size={5} wrap>
|
||
<Button
|
||
type={'primary'}
|
||
icon={<UnorderedListOutlined />}
|
||
onClick={() => openDetail(record.id!)}
|
||
/>
|
||
{ NEXT_STATUS[record.status!] && (
|
||
<AuthButton auth="order.store.update">
|
||
<Popconfirm
|
||
title={`确认将订单状态更新为「${NEXT_STATUS[record.status!].label}」?`}
|
||
onConfirm={() => handleStatusChange(record.id!, NEXT_STATUS[record.status!].status)}
|
||
>
|
||
<Button type={'primary'} color='green' variant="solid">
|
||
{NEXT_STATUS[record.status!].label}
|
||
</Button>
|
||
</Popconfirm>
|
||
</AuthButton>
|
||
)}
|
||
{ record.status === 0 && (
|
||
<AuthButton auth="order.store.update">
|
||
<Popconfirm
|
||
title={`确认取消该订单吗?`}
|
||
onConfirm={() => handleStatusChange(record.id!, 9)}
|
||
>
|
||
<Button type={'primary'} color='danger' variant="solid">
|
||
取消订单
|
||
</Button>
|
||
</Popconfirm>
|
||
</AuthButton>
|
||
)}
|
||
{ record.status === 9 && (
|
||
<AuthButton auth="order.store.delete">
|
||
<Popconfirm
|
||
title="确认删除该订单吗?"
|
||
description="仅已取消订单可删除,删除后后台与小程序端均不可见"
|
||
onConfirm={() => handleDelete(record.id!)}
|
||
>
|
||
<Button icon={<DeleteOutlined />} color='danger' variant="solid" />
|
||
</Popconfirm>
|
||
</AuthButton>
|
||
)}
|
||
</Space>
|
||
)
|
||
}
|
||
];
|
||
|
||
const tableProps: XinTableProps<IStoreOrder> = {
|
||
api: '/order/store',
|
||
columns,
|
||
rowKey: 'id',
|
||
accessName: 'order.store',
|
||
tableRef,
|
||
operateShow: false,
|
||
formProps: false,
|
||
searchDefaultOpen: true,
|
||
rowSelection: {
|
||
selectedRowKeys,
|
||
onChange: (keys) => setSelectedRowKeys(keys),
|
||
},
|
||
actionBarRender: (dom) => [
|
||
dom.search,
|
||
<AuthButton key="generate" auth="purchase.order.generate">
|
||
<Button type="primary" onClick={() => setGenerateOpen(true)}>
|
||
生成采购单
|
||
</Button>
|
||
</AuthButton>,
|
||
<AuthButton key="batch" auth="order.store.update">
|
||
<Button
|
||
type="primary"
|
||
disabled={selectedRowKeys.length === 0}
|
||
onClick={() => setBatchOpen(true)}
|
||
>
|
||
批量操作{selectedRowKeys.length > 0 ? `(${selectedRowKeys.length})` : ''}
|
||
</Button>
|
||
</AuthButton>,
|
||
dom.keywordSearch,
|
||
],
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<div className="mb-5">
|
||
<Title level={3}>门店订单</Title>
|
||
<Text type="secondary">
|
||
小程序下单为待接单;接单后通过「生成采购单」合并为采购单并转采购中,采购单完成后订单自动转配送中,生成账单后订单自动完成。
|
||
</Text>
|
||
</div>
|
||
<XinTable<IStoreOrder> {...tableProps} />
|
||
|
||
<Drawer
|
||
title="订单详情"
|
||
open={detailOpen}
|
||
onClose={() => setDetailOpen(false)}
|
||
size={1000}
|
||
loading={detailLoading}
|
||
footer={
|
||
detail ? (
|
||
<Space className="flex justify-end" wrap>
|
||
{ NEXT_STATUS[detail.status!] && (
|
||
<AuthButton auth="order.store.update">
|
||
<Popconfirm
|
||
title={`确认将订单状态更新为「${NEXT_STATUS[detail.status!].label}」?`}
|
||
onConfirm={() => handleStatusChange(detail.id!, NEXT_STATUS[detail.status!].status)}
|
||
>
|
||
<Button type={'primary'} color='green' variant="solid">
|
||
{NEXT_STATUS[detail.status!].label}
|
||
</Button>
|
||
</Popconfirm>
|
||
</AuthButton>
|
||
)}
|
||
{ detail.status === 0 && (
|
||
<AuthButton auth="order.store.update">
|
||
<Popconfirm
|
||
title={`确认取消该订单吗?`}
|
||
onConfirm={() => handleStatusChange(detail.id!, 9)}
|
||
>
|
||
<Button type={'primary'} color='danger' variant="solid">
|
||
取消订单
|
||
</Button>
|
||
</Popconfirm>
|
||
</AuthButton>
|
||
)}
|
||
{ detail.status === 9 && (
|
||
<AuthButton auth="order.store.delete">
|
||
<Popconfirm
|
||
title="确认删除该订单吗?"
|
||
description="仅已取消订单可删除,删除后后台与小程序端均不可见"
|
||
onConfirm={() => handleDelete(detail.id!)}
|
||
>
|
||
<Button icon={<DeleteOutlined />} color='danger' variant="solid">
|
||
删除
|
||
</Button>
|
||
</Popconfirm>
|
||
</AuthButton>
|
||
)}
|
||
</Space>
|
||
) : null
|
||
}
|
||
>
|
||
{detail ? (
|
||
<>
|
||
{/* 门店信息 */}
|
||
<Descriptions title="门店信息" column={3} size="small" bordered>
|
||
<Descriptions.Item label="门店名称">
|
||
{detail.store?.name ?? `门店#${detail.store_id}`}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="联系人">{detail.store?.contact ?? '-'}</Descriptions.Item>
|
||
<Descriptions.Item label="联系电话">{detail.store?.phone ?? '-'}</Descriptions.Item>
|
||
<Descriptions.Item label="门店地址" span={3}>
|
||
{detail.store?.address ?? '-'}
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
|
||
{/* 订单信息 */}
|
||
<Descriptions title="订单信息" column={3} size="small" bordered className="mt-4!">
|
||
<Descriptions.Item label="订单号">{detail.order_no}</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_weight}斤</Descriptions.Item>
|
||
<Descriptions.Item label="账单ID">{detail.bill_id ?? '-'}</Descriptions.Item>
|
||
{detail.remark ? (
|
||
<Descriptions.Item label="备注" span={3}>
|
||
{detail.remark}
|
||
</Descriptions.Item>
|
||
) : null}
|
||
</Descriptions>
|
||
|
||
{/* 关联账单信息(生成账单后展示) */}
|
||
{detail.bill ? (
|
||
<Descriptions title="账单信息" column={3} size="small" bordered className="mt-4!">
|
||
<Descriptions.Item label="账单号">{detail.bill.bill_no}</Descriptions.Item>
|
||
<Descriptions.Item label="账单日期">{detail.bill.bill_date ?? '-'}</Descriptions.Item>
|
||
<Descriptions.Item label="支付状态">
|
||
<Tag color={BILL_PAY_STATE_MAP[detail.bill.pay_state ?? 0]?.color}>
|
||
{detail.bill.pay_state_name ?? BILL_PAY_STATE_MAP[detail.bill.pay_state ?? 0]?.text}
|
||
</Tag>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="付款时间">{detail.bill.paid_at ?? '-'}</Descriptions.Item>
|
||
<Descriptions.Item label="付款备注">{detail.bill.pay_remark || '-'}</Descriptions.Item>
|
||
</Descriptions>
|
||
) : null}
|
||
|
||
{/* 商品明细(商城模式:首图 + 单价 × 订货量 + 金额) */}
|
||
<Title level={5} className="mt-6! mb-3!">
|
||
商品明细
|
||
</Title>
|
||
<div className="overflow-hidden rounded border border-gray-200">
|
||
<div className="flex bg-gray-50 px-4 py-2 text-sm text-gray-500">
|
||
<div className="flex-1">商品信息</div>
|
||
<div className="w-30 shrink-0 text-center">供应商</div>
|
||
<div className="w-30 shrink-0 text-center">单价</div>
|
||
<div className="w-26 shrink-0 text-center">订货量</div>
|
||
<div className="w-33 shrink-0 text-center">订货金额</div>
|
||
<div className="w-33 shrink-0 text-center">重量</div>
|
||
</div>
|
||
{(detail.items ?? []).map((item) => (
|
||
<div key={item.id} className="flex items-center border-t border-gray-100 px-4 py-3">
|
||
<div className="flex min-w-0 flex-1 items-center">
|
||
<Image.PreviewGroup>
|
||
{item.image ? (
|
||
<Image
|
||
src={item.image}
|
||
width={64}
|
||
height={64}
|
||
style={{ objectFit: 'cover', borderRadius: 4 }}
|
||
/>
|
||
) : (
|
||
<div className="flex h-16 w-16 shrink-0 items-center justify-center rounded bg-gray-100 text-xs text-gray-400">
|
||
暂无图片
|
||
</div>
|
||
)}
|
||
</Image.PreviewGroup>
|
||
<div className="ml-3 min-w-0">
|
||
<div className="text-sm font-medium">{item.product_name}</div>
|
||
<div className="mt-0.5 text-xs text-gray-500">
|
||
{item.product_spec || '-'} {item.unit || '-'}
|
||
<div>成本:¥{item.cost_price ?? '0.00'}</div>
|
||
</div>
|
||
{item.remark ? (
|
||
<div className="mt-0.5 truncate text-xs text-gray-500">备注:{item.remark}</div>
|
||
) : null}
|
||
</div>
|
||
</div>
|
||
<div className="w-30 shrink-0 text-center">{item.supplier?.name ?? '-'}</div>
|
||
<div className="w-30 shrink-0 text-center">¥{item.price}</div>
|
||
<div className="w-26 shrink-0 text-center">{item.quantity}</div>
|
||
<div className="w-33 shrink-0 text-center">
|
||
<Text strong>¥{item.amount}</Text>
|
||
</div>
|
||
<div className="w-33 shrink-0 text-center">
|
||
{item.weight ?? '-'} 斤
|
||
</div>
|
||
</div>
|
||
))}
|
||
<div className="flex bg-gray-50 px-4 py-3 text-sm">
|
||
<div className="flex-1">总计</div>
|
||
<div className="w-30 shrink-0 text-center"></div>
|
||
<div className="w-30 shrink-0 text-center"></div>
|
||
<div className="w-26 shrink-0 text-center">{detail.total_quantity}</div>
|
||
<div className="w-33 shrink-0 text-center text-[red] text-[16px]">¥{detail.total_amount}</div>
|
||
<div className="w-33 shrink-0 text-center">{detail.total_weight}斤</div>
|
||
</div>
|
||
{(detail.items ?? []).length === 0 && (
|
||
<Empty
|
||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||
description="暂无商品明细"
|
||
className="py-8!"
|
||
/>
|
||
)}
|
||
</div>
|
||
</>
|
||
) : null}
|
||
</Drawer>
|
||
|
||
{/* 生成采购单:合并已接单订单 */}
|
||
<Modal
|
||
title="生成采购单"
|
||
open={generateOpen}
|
||
onCancel={() => setGenerateOpen(false)}
|
||
onOk={() => generateForm.submit()}
|
||
confirmLoading={generateLoading}
|
||
okText="确认生成"
|
||
destroyOnHidden
|
||
>
|
||
<div className="py-2 text-gray-500">
|
||
{selectedRowKeys.length > 0
|
||
? `将合并选中的 ${selectedRowKeys.length} 笔订单生成一张采购单(须均为已接单状态,否则整批中止)。`
|
||
: '未勾选订单时,将合并全部「已接单」订单生成一张采购单。'}
|
||
生成后源订单自动转为「采购中」。
|
||
</div>
|
||
<Form form={generateForm} layout="vertical" onFinish={handleGeneratePurchase}>
|
||
<Form.Item
|
||
label="采购日期"
|
||
name="purchase_date"
|
||
initialValue={dayjs()}
|
||
rules={[{ required: true, message: '请选择采购日期' }]}
|
||
>
|
||
<DatePicker className="w-full" allowClear={false} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 批量状态流转 */}
|
||
<Modal
|
||
title="批量状态流转"
|
||
open={batchOpen}
|
||
onCancel={() => setBatchOpen(false)}
|
||
onOk={handleBatchStatus}
|
||
confirmLoading={batchLoading}
|
||
okText="确认流转"
|
||
destroyOnHidden
|
||
>
|
||
<div className="py-2 text-gray-500">
|
||
已勾选 {selectedRowKeys.length} 笔订单;任一订单不满足流转条件时,本次批量操作将整批中止,不做任何修改。
|
||
</div>
|
||
<Radio.Group
|
||
className="w-full py-2"
|
||
value={batchTarget}
|
||
onChange={(e) => setBatchTarget(e.target.value as number)}
|
||
>
|
||
<Space orientation="vertical">
|
||
{BATCH_TARGET_OPTIONS.map((opt) => (
|
||
<Radio key={opt.value} value={opt.value}>
|
||
{opt.label}
|
||
</Radio>
|
||
))}
|
||
</Space>
|
||
</Radio.Group>
|
||
</Modal>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default StoreOrderPage;
|