720 lines
21 KiB
TypeScript
720 lines
21 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
||
import {
|
||
Button,
|
||
Descriptions,
|
||
Drawer,
|
||
Input,
|
||
InputNumber,
|
||
message,
|
||
Modal,
|
||
Popconfirm,
|
||
Space,
|
||
Switch,
|
||
Table,
|
||
Tabs,
|
||
Tag,
|
||
Typography,
|
||
} from 'antd';
|
||
import {
|
||
CheckSquareOutlined,
|
||
FileDoneOutlined,
|
||
ToolOutlined,
|
||
} from '@ant-design/icons';
|
||
import type { TableProps } from 'antd';
|
||
import XinTable from '@/components/XinTable';
|
||
import type {
|
||
XinTableColumn,
|
||
XinTableInstance,
|
||
XinTableProps,
|
||
} from '@/components/XinTable/typings.ts';
|
||
import type IReconciliation from '@/domain/iReconciliation.ts';
|
||
import type { IReconDiff, IReconciliationItem } from '@/domain/iReconciliation.ts';
|
||
import { RECON_STATUS_MAP } from '@/domain/iReconciliation.ts';
|
||
import type IProductCategory from '@/domain/iProductCategory.ts';
|
||
import type ISupplier from '@/domain/iSupplier.ts';
|
||
import { getCategoryTree } from '@/api/product/category.ts';
|
||
import { getSupplierOptions } from '@/api/customer/supplier.ts';
|
||
import {
|
||
buildRecon,
|
||
getReconDiff,
|
||
remarkReconItem,
|
||
settleRecon,
|
||
toggleReconItem,
|
||
updateReconItem,
|
||
} from '@/api/recon/list.ts';
|
||
import { Get } from '@/api/common/table.ts';
|
||
import AuthButton from '@/components/AuthButton';
|
||
|
||
const { Title, Text } = Typography;
|
||
|
||
interface EditingItem {
|
||
product_name: string;
|
||
quantity: number;
|
||
weight: number;
|
||
publish_amount: number;
|
||
actual_amount: number;
|
||
}
|
||
|
||
/**
|
||
* 财务对账(D1/D2 筛选建单、D4 明细修改、D5 差额对比、D6 备注、D8 标记、D9 结算)
|
||
*/
|
||
const ReconListPage: React.FC = () => {
|
||
const tableRef = useRef<XinTableInstance<IReconciliation>>(null);
|
||
const [categoryTree, setCategoryTree] = useState<IProductCategory[]>([]);
|
||
const [suppliers, setSuppliers] = useState<ISupplier[]>([]);
|
||
|
||
// 工作台抽屉
|
||
const [workOpen, setWorkOpen] = useState(false);
|
||
const [workLoading, setWorkLoading] = useState(false);
|
||
const [recon, setRecon] = useState<IReconciliation | null>(null);
|
||
const [editing, setEditing] = useState<Record<number, EditingItem>>({});
|
||
const [savingItemId, setSavingItemId] = useState<number | null>(null);
|
||
const [diff, setDiff] = useState<IReconDiff | null>(null);
|
||
|
||
// 备注弹窗
|
||
const [remarkOpen, setRemarkOpen] = useState(false);
|
||
const [remarkTarget, setRemarkTarget] = useState<IReconciliationItem | null>(null);
|
||
const [remarkValue, setRemarkValue] = useState('');
|
||
|
||
useEffect(() => {
|
||
getCategoryTree().then((res) => setCategoryTree(res.data.data ?? []));
|
||
getSupplierOptions().then((res) => setSuppliers(res.data.data ?? []));
|
||
}, []);
|
||
|
||
const loadRecon = async (id: number) => {
|
||
const res = await Get<IReconciliation>('/recon/list', id);
|
||
const data = res.data.data ?? null;
|
||
setRecon(data);
|
||
const editingMap: Record<number, EditingItem> = {};
|
||
data?.items?.forEach((item) => {
|
||
if (item.id !== undefined) {
|
||
editingMap[item.id] = {
|
||
product_name: item.product_name ?? '',
|
||
quantity: Number(item.quantity ?? 0),
|
||
weight: Number(item.weight ?? 0),
|
||
publish_amount: Number(item.publish_amount ?? 0),
|
||
actual_amount: Number(item.actual_amount ?? 0),
|
||
};
|
||
}
|
||
});
|
||
setEditing(editingMap);
|
||
return data;
|
||
};
|
||
|
||
const openWorkbench = async (id: number) => {
|
||
setWorkOpen(true);
|
||
setWorkLoading(true);
|
||
setDiff(null);
|
||
try {
|
||
await loadRecon(id);
|
||
} finally {
|
||
setWorkLoading(false);
|
||
}
|
||
};
|
||
|
||
const loadDiff = async (id: number) => {
|
||
const res = await getReconDiff(id);
|
||
setDiff(res.data.data ?? null);
|
||
};
|
||
|
||
const handleBuild = async (record: IReconciliation) => {
|
||
const res = await buildRecon(record.id!);
|
||
message.success(`已生成 ${res.data.data?.count} 条对账明细`);
|
||
await tableRef.current?.reload();
|
||
};
|
||
|
||
const handleSettle = async (record: IReconciliation) => {
|
||
const res = await settleRecon(record.id!);
|
||
message.success(`已生成 ${res.data.data?.count} 张结算表`);
|
||
await tableRef.current?.reload();
|
||
};
|
||
|
||
const isItemDirty = (item: IReconciliationItem): boolean => {
|
||
const edit = editing[item.id!];
|
||
if (!edit) {
|
||
return false;
|
||
}
|
||
return (
|
||
edit.product_name !== (item.product_name ?? '') ||
|
||
edit.quantity !== Number(item.quantity ?? 0) ||
|
||
edit.weight !== Number(item.weight ?? 0) ||
|
||
edit.publish_amount !== Number(item.publish_amount ?? 0) ||
|
||
edit.actual_amount !== Number(item.actual_amount ?? 0)
|
||
);
|
||
};
|
||
|
||
const saveItem = async (item: IReconciliationItem) => {
|
||
const edit = editing[item.id!];
|
||
if (!edit || !isItemDirty(item)) {
|
||
return;
|
||
}
|
||
setSavingItemId(item.id!);
|
||
try {
|
||
const res = await updateReconItem(item.id!, {
|
||
product_name: edit.product_name,
|
||
quantity: edit.quantity,
|
||
weight: edit.weight,
|
||
publish_amount: edit.publish_amount,
|
||
actual_amount: edit.actual_amount,
|
||
});
|
||
message.success(`已保存,差额 ¥${res.data.data?.diff_amount}`);
|
||
await loadRecon(recon!.id!);
|
||
await loadDiff(recon!.id!);
|
||
} finally {
|
||
setSavingItemId(null);
|
||
}
|
||
};
|
||
|
||
const handleToggle = async (item: IReconciliationItem) => {
|
||
await toggleReconItem(item.id!);
|
||
await loadRecon(recon!.id!);
|
||
};
|
||
|
||
const openRemark = (item: IReconciliationItem) => {
|
||
setRemarkTarget(item);
|
||
setRemarkValue(item.store_remark ?? '');
|
||
setRemarkOpen(true);
|
||
};
|
||
|
||
const saveRemark = async () => {
|
||
await remarkReconItem(remarkTarget!.id!, remarkValue);
|
||
message.success('备注已保存');
|
||
setRemarkOpen(false);
|
||
await loadRecon(recon!.id!);
|
||
};
|
||
|
||
const readonly = recon?.status === 2;
|
||
|
||
const itemColumns: TableProps<IReconciliationItem>['columns'] = [
|
||
{
|
||
title: '品名',
|
||
dataIndex: 'product_name',
|
||
width: 160,
|
||
render: (_, record) =>
|
||
readonly ? (
|
||
record.product_name
|
||
) : (
|
||
<Input
|
||
size="small"
|
||
value={editing[record.id!]?.product_name}
|
||
onChange={(e) =>
|
||
setEditing((prev) => ({
|
||
...prev,
|
||
[record.id!]: { ...prev[record.id!], product_name: e.target.value },
|
||
}))
|
||
}
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
title: '门店',
|
||
dataIndex: 'store',
|
||
width: 130,
|
||
render: (_, record) => record.store?.name ?? `门店#${record.store_id}`,
|
||
},
|
||
{
|
||
title: '订货量',
|
||
dataIndex: 'quantity',
|
||
width: 110,
|
||
render: (_, record) =>
|
||
readonly ? (
|
||
record.quantity
|
||
) : (
|
||
<InputNumber
|
||
size="small"
|
||
min={0}
|
||
precision={2}
|
||
value={editing[record.id!]?.quantity}
|
||
onChange={(v) =>
|
||
setEditing((prev) => ({
|
||
...prev,
|
||
[record.id!]: { ...prev[record.id!], quantity: v ?? 0 },
|
||
}))
|
||
}
|
||
className="!w-20"
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
title: '称重',
|
||
dataIndex: 'weight',
|
||
width: 110,
|
||
render: (_, record) =>
|
||
readonly ? (
|
||
record.weight
|
||
) : (
|
||
<InputNumber
|
||
size="small"
|
||
min={0}
|
||
precision={3}
|
||
value={editing[record.id!]?.weight}
|
||
onChange={(v) =>
|
||
setEditing((prev) => ({
|
||
...prev,
|
||
[record.id!]: { ...prev[record.id!], weight: v ?? 0 },
|
||
}))
|
||
}
|
||
className="!w-20"
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
title: '公布金额',
|
||
dataIndex: 'publish_amount',
|
||
width: 120,
|
||
render: (_, record) =>
|
||
readonly ? (
|
||
`¥${record.publish_amount}`
|
||
) : (
|
||
<InputNumber
|
||
size="small"
|
||
min={0}
|
||
precision={2}
|
||
prefix="¥"
|
||
value={editing[record.id!]?.publish_amount}
|
||
onChange={(v) =>
|
||
setEditing((prev) => ({
|
||
...prev,
|
||
[record.id!]: { ...prev[record.id!], publish_amount: v ?? 0 },
|
||
}))
|
||
}
|
||
className="!w-24"
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
title: '实际金额',
|
||
dataIndex: 'actual_amount',
|
||
width: 120,
|
||
render: (_, record) =>
|
||
readonly ? (
|
||
`¥${record.actual_amount}`
|
||
) : (
|
||
<InputNumber
|
||
size="small"
|
||
min={0}
|
||
precision={2}
|
||
prefix="¥"
|
||
value={editing[record.id!]?.actual_amount}
|
||
onChange={(v) =>
|
||
setEditing((prev) => ({
|
||
...prev,
|
||
[record.id!]: { ...prev[record.id!], actual_amount: v ?? 0 },
|
||
}))
|
||
}
|
||
className="!w-24"
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
title: '差额',
|
||
dataIndex: 'diff_amount',
|
||
width: 100,
|
||
align: 'right',
|
||
render: (v) => {
|
||
const num = Number(v ?? 0);
|
||
return (
|
||
<Text type={num === 0 ? 'secondary' : 'danger'} strong={num !== 0}>
|
||
¥{String(v)}
|
||
</Text>
|
||
);
|
||
},
|
||
},
|
||
{
|
||
title: '对账',
|
||
dataIndex: 'is_reconciled',
|
||
width: 80,
|
||
align: 'center',
|
||
render: (_, record) => (
|
||
<Switch
|
||
size="small"
|
||
disabled={readonly}
|
||
checked={record.is_reconciled === 1}
|
||
onChange={() => handleToggle(record)}
|
||
/>
|
||
),
|
||
},
|
||
{
|
||
title: '门店备注',
|
||
dataIndex: 'store_remark',
|
||
width: 120,
|
||
ellipsis: true,
|
||
render: (_, record) =>
|
||
record.store_remark || <Text type="secondary">—</Text>,
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
width: 130,
|
||
fixed: 'right',
|
||
render: (_, record) =>
|
||
readonly ? null : (
|
||
<Space size={4}>
|
||
<AuthButton auth="recon.item.item.update">
|
||
<Button
|
||
size="small"
|
||
type="link"
|
||
disabled={!isItemDirty(record)}
|
||
loading={savingItemId === record.id}
|
||
onClick={() => saveItem(record)}
|
||
>
|
||
保存
|
||
</Button>
|
||
</AuthButton>
|
||
<AuthButton auth="recon.item.item.update">
|
||
<Button size="small" type="link" onClick={() => openRemark(record)}>
|
||
备注
|
||
</Button>
|
||
</AuthButton>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
const diffColumns = (nameTitle: string, nameKey: 'store_name' | 'product_name') => [
|
||
{ title: nameTitle, dataIndex: nameKey, render: (v: string) => v || '-' },
|
||
{ title: '公布金额', dataIndex: 'publish', align: 'right' as const, render: (v: number) => `¥${v}` },
|
||
{ title: '实际金额', dataIndex: 'actual', align: 'right' as const, render: (v: number) => `¥${v}` },
|
||
{
|
||
title: '差额',
|
||
dataIndex: 'diff',
|
||
align: 'right' as const,
|
||
render: (v: number) => (
|
||
<Text type={v === 0 ? 'secondary' : 'danger'} strong={v !== 0}>
|
||
¥{v}
|
||
</Text>
|
||
),
|
||
},
|
||
];
|
||
|
||
const columns: XinTableColumn<IReconciliation>[] = [
|
||
{
|
||
title: '对账单号',
|
||
dataIndex: 'recon_no',
|
||
valueType: 'text',
|
||
hideInForm: true,
|
||
},
|
||
{
|
||
title: '标题',
|
||
dataIndex: 'title',
|
||
valueType: 'text',
|
||
required: true,
|
||
rules: [{ required: true, message: '请输入对账标题' }],
|
||
},
|
||
{
|
||
title: '对账周期',
|
||
dataIndex: 'period',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
render: (_, record) => `${record.period_start} ~ ${record.period_end}`,
|
||
},
|
||
{
|
||
title: '开始日期',
|
||
dataIndex: 'period_start',
|
||
valueType: 'date',
|
||
hideInTable: true,
|
||
required: true,
|
||
rules: [{ required: true, message: '请选择开始日期' }],
|
||
},
|
||
{
|
||
title: '结束日期',
|
||
dataIndex: 'period_end',
|
||
valueType: 'date',
|
||
hideInTable: true,
|
||
required: true,
|
||
rules: [{ required: true, message: '请选择结束日期' }],
|
||
},
|
||
{
|
||
title: '商品分类',
|
||
dataIndex: 'category_id',
|
||
valueType: 'treeSelect',
|
||
hideInTable: true,
|
||
initialValue: 0,
|
||
fieldProps: {
|
||
treeData: [{ id: 0, name: '全部分类', children: categoryTree }],
|
||
fieldNames: { label: 'name', value: 'id', children: 'children' },
|
||
treeDefaultExpandAll: true,
|
||
},
|
||
},
|
||
{
|
||
title: '供应商',
|
||
dataIndex: 'supplier_id',
|
||
valueType: 'select',
|
||
hideInTable: true,
|
||
initialValue: 0,
|
||
fieldProps: {
|
||
options: [
|
||
{ label: '全部供应商', value: 0 },
|
||
...suppliers.map((s) => ({ label: s.name, value: s.id })),
|
||
],
|
||
},
|
||
},
|
||
{
|
||
title: '公布金额',
|
||
dataIndex: 'publish_amount',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
align: 'right',
|
||
render: (_, record) => `¥${record.publish_amount}`,
|
||
},
|
||
{
|
||
title: '实际金额',
|
||
dataIndex: 'actual_amount',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
align: 'right',
|
||
render: (_, record) => `¥${record.actual_amount}`,
|
||
},
|
||
{
|
||
title: '差额',
|
||
dataIndex: 'diff_amount',
|
||
hideInForm: true,
|
||
hideInSearch: true,
|
||
align: 'right',
|
||
render: (_, record) => {
|
||
const num = Number(record.diff_amount ?? 0);
|
||
return (
|
||
<Text type={num === 0 ? 'secondary' : 'danger'} strong={num !== 0}>
|
||
¥{record.diff_amount}
|
||
</Text>
|
||
);
|
||
},
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
valueType: 'select',
|
||
hideInForm: true,
|
||
fieldProps: {
|
||
options: Object.entries(RECON_STATUS_MAP).map(([value, item]) => ({
|
||
value: Number(value),
|
||
label: item.text,
|
||
})),
|
||
},
|
||
render: (_, record) => {
|
||
const item = RECON_STATUS_MAP[record.status ?? 0];
|
||
return <Tag color={item?.color}>{item?.text}</Tag>;
|
||
},
|
||
align: 'center',
|
||
},
|
||
{
|
||
title: '备注',
|
||
dataIndex: 'remark',
|
||
valueType: 'textarea',
|
||
hideInTable: true,
|
||
hideInSearch: true,
|
||
fieldProps: { rows: 2 },
|
||
},
|
||
];
|
||
|
||
const operateRender: XinTableProps<IReconciliation>['operateRender'] = (record, dom) => [
|
||
<AuthButton key="build" auth="recon.list.build">
|
||
<Popconfirm
|
||
title="生成对账明细?"
|
||
description="按周期/品类/供应商拉取已分摊的采购数据,重复生成会清空现有明细。"
|
||
disabled={record.status === 2}
|
||
onConfirm={() => handleBuild(record)}
|
||
>
|
||
<Button size="small" disabled={record.status === 2}>
|
||
生成明细
|
||
</Button>
|
||
</Popconfirm>
|
||
</AuthButton>,
|
||
<Button
|
||
key="workbench"
|
||
size="small"
|
||
type="primary"
|
||
ghost
|
||
icon={<ToolOutlined />}
|
||
disabled={record.status === 0}
|
||
onClick={() => openWorkbench(record.id!)}
|
||
>
|
||
工作台
|
||
</Button>,
|
||
<AuthButton key="settle" auth="recon.list.settle">
|
||
<Popconfirm
|
||
title="生成结算表?"
|
||
description="按门店聚合对账明细生成结算表,对账单将变为已结算且不可再修改。"
|
||
disabled={record.status !== 1}
|
||
onConfirm={() => handleSettle(record)}
|
||
>
|
||
<Button
|
||
size="small"
|
||
type="primary"
|
||
icon={<FileDoneOutlined />}
|
||
disabled={record.status !== 1}
|
||
>
|
||
生成结算表
|
||
</Button>
|
||
</Popconfirm>
|
||
</AuthButton>,
|
||
// 编辑/删除由 XinTable 默认提供;删除仅草稿可用,由后端校验拦截
|
||
dom.edit,
|
||
dom.del,
|
||
];
|
||
|
||
const tableProps: XinTableProps<IReconciliation> = {
|
||
api: '/recon/list',
|
||
columns,
|
||
rowKey: 'id',
|
||
accessName: 'recon.list',
|
||
tableRef,
|
||
operateRender,
|
||
scroll: { x: 1300 },
|
||
formProps: {
|
||
grid: true,
|
||
colProps: { span: 12 },
|
||
layout: 'vertical',
|
||
},
|
||
modalProps: { width: 720 },
|
||
};
|
||
|
||
return (
|
||
<>
|
||
<div className="mb-5">
|
||
<Title level={3}>财务对账</Title>
|
||
<Text type="secondary">
|
||
按周期/品类/供应商建立对账单 → 生成明细(采购分摊数据)→ 核对修改 → 差额对比 → 生成结算表。
|
||
</Text>
|
||
</div>
|
||
<XinTable<IReconciliation> {...tableProps} />
|
||
|
||
{/* 对账工作台 */}
|
||
<Drawer
|
||
title={recon ? `对账工作台 · ${recon.recon_no}` : '对账工作台'}
|
||
open={workOpen}
|
||
onClose={() => setWorkOpen(false)}
|
||
width={1200}
|
||
loading={workLoading}
|
||
>
|
||
{recon ? (
|
||
<>
|
||
<Descriptions column={4} size="small" bordered>
|
||
<Descriptions.Item label="标题">{recon.title}</Descriptions.Item>
|
||
<Descriptions.Item label="周期">
|
||
{recon.period_start} ~ {recon.period_end}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="状态">
|
||
<Tag color={RECON_STATUS_MAP[recon.status ?? 0]?.color}>
|
||
{RECON_STATUS_MAP[recon.status ?? 0]?.text}
|
||
</Tag>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="差额">
|
||
<Text
|
||
type={Number(recon.diff_amount) === 0 ? 'secondary' : 'danger'}
|
||
strong
|
||
>
|
||
¥{recon.diff_amount}
|
||
</Text>
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
|
||
<Tabs
|
||
className="mt-4"
|
||
items={[
|
||
{
|
||
key: 'items',
|
||
label: (
|
||
<span>
|
||
<CheckSquareOutlined /> 明细核对({recon.items?.length ?? 0})
|
||
</span>
|
||
),
|
||
children: (
|
||
<>
|
||
{!readonly ? (
|
||
<div className="mb-2 text-gray-500">
|
||
可修改订货量/称重/公布金额/实际金额,保存后自动重算差额与对账单汇总;开关标记单品对账状态。
|
||
</div>
|
||
) : null}
|
||
<Table<IReconciliationItem>
|
||
rowKey="id"
|
||
size="small"
|
||
columns={itemColumns}
|
||
dataSource={recon.items ?? []}
|
||
pagination={{ pageSize: 15, showSizeChanger: false }}
|
||
scroll={{ x: 1250 }}
|
||
/>
|
||
</>
|
||
),
|
||
},
|
||
{
|
||
key: 'diff',
|
||
label: '差额对比',
|
||
children: (
|
||
<>
|
||
<Space className="mb-3">
|
||
<Button onClick={() => loadDiff(recon.id!)}>刷新对比数据</Button>
|
||
{diff ? (
|
||
<Text type="secondary">
|
||
合计:公布 ¥{diff.total.publish} / 实际 ¥{diff.total.actual} /{' '}
|
||
<Text type={diff.total.diff === 0 ? 'secondary' : 'danger'} strong>
|
||
差额 ¥{diff.total.diff}
|
||
</Text>
|
||
</Text>
|
||
) : null}
|
||
</Space>
|
||
{diff ? (
|
||
<div className="grid grid-cols-2 gap-4">
|
||
<div>
|
||
<Title level={5}>按门店</Title>
|
||
<Table
|
||
rowKey={(row) => String(row.store_id)}
|
||
size="small"
|
||
columns={diffColumns('门店', 'store_name')}
|
||
dataSource={diff.by_store}
|
||
pagination={false}
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Title level={5}>按商品</Title>
|
||
<Table
|
||
rowKey={(row) => String(row.product_id)}
|
||
size="small"
|
||
columns={diffColumns('商品', 'product_name')}
|
||
dataSource={diff.by_product}
|
||
pagination={false}
|
||
/>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<Button type="primary" onClick={() => loadDiff(recon.id!)}>
|
||
加载差额对比
|
||
</Button>
|
||
)}
|
||
</>
|
||
),
|
||
},
|
||
]}
|
||
/>
|
||
</>
|
||
) : null}
|
||
</Drawer>
|
||
|
||
{/* 门店备注弹窗 */}
|
||
<Modal
|
||
title="单品门店备注"
|
||
open={remarkOpen}
|
||
onCancel={() => setRemarkOpen(false)}
|
||
onOk={saveRemark}
|
||
okText="保存备注"
|
||
destroyOnHidden
|
||
>
|
||
<div className="mb-2 text-gray-500">
|
||
{remarkTarget?.product_name}
|
||
{remarkTarget?.store ? ` · ${remarkTarget.store.name}` : ''}
|
||
</div>
|
||
<Input.TextArea
|
||
rows={3}
|
||
maxLength={255}
|
||
showCount
|
||
value={remarkValue}
|
||
onChange={(e) => setRemarkValue(e.target.value)}
|
||
placeholder="填写该单品针对该门店的备注(如质量异常、补货说明等)"
|
||
/>
|
||
</Modal>
|
||
</>
|
||
);
|
||
};
|
||
|
||
export default ReconListPage;
|