修复BUG

This commit is contained in:
liu
2026-08-27 14:20:09 +08:00
parent 513cc568a9
commit 52dc6efadf
203 changed files with 576 additions and 1130 deletions
+5 -1
View File
@@ -25,12 +25,16 @@ export interface PurchaseCellUpdateParams {
weight?: number;
}
/** 生成账单:单个门店的配送费/周转筐/托盘数量 */
/** 生成账单:单个门店的配送费/周转筐/托盘数量与售后/备注 */
export interface BillGenerateStoreParams {
store_id: number;
delivery_fee: number;
box_num: number;
tray_num: number;
/** 售后说明(文本,仅记录) */
after_sale?: string;
/** 备注 */
remark?: string;
}
+17 -4
View File
@@ -17,6 +17,7 @@ import type {XinTableProps, XinTableInstance, RequestParams, FormMode} from "./t
import SearchForm from "./SearchForm";
import XinForm, {type XinFormRef} from "@/components/XinForm";
import {type ReactNode, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from "react";
import dayjs from "dayjs";
import type {FormColumn} from "@/components/XinFormField/FieldRender/typings";
import {Delete, List, Create, Update} from "@/api/common/table.ts";
import {isArray, isEmpty, omit} from "lodash";
@@ -60,6 +61,7 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
editShow = true,
deleteShow = true,
searchShow = true,
searchDefaultOpen = false,
operateShow = true,
paginationShow = true,
keywordSearchShow = true,
@@ -89,7 +91,7 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
const [searchRef] = Form.useForm<T>();
const [columnsChecked, setColumnsChecked] = useState<any[]>([]);
const [searchRender, setSearchRender] = useState<boolean>(false);
const [searchRender, setSearchRender] = useState<boolean>(searchDefaultOpen);
// 表单模式状态
const [formMode, setFormMode] = useState<FormMode>('create');
@@ -219,9 +221,17 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
/** 搜索表单提交 */
const handleSearch = async () => {
const searchValues: T = searchRef.getFieldsValue();
const dateSearch: any = {}
// 移除 空值
Object.keys(searchValues).forEach((key) => {
if (searchValues[key] === '' || searchValues[key] === undefined) {
const value = searchValues[key];
if (value === '' || value === undefined || value === null) {
delete searchValues[key];
} else if (dayjs.isDayjs(value)) {
dateSearch[key] = value.format('YYYY-MM-DD');
delete searchValues[key];
} else if (isArray(value) && value.length > 0 && value.every((item) => dayjs.isDayjs(item))) {
dateSearch[key] = value.map((item) => item.format('YYYY-MM-DD'));
delete searchValues[key];
}
});
@@ -229,6 +239,7 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
page: 1,
...requestParams,
...searchValues,
...dateSearch,
});
};
@@ -256,9 +267,11 @@ export default function XinTable<T extends Record<string, any> = any>(props: Xin
.map(column => omit(column, ['hideInTable', 'hideInForm', 'hideInSearch', 'search']));
}, [columns]);
/** 初始化列设置树数据 */
/** 初始化列设置树数据defaultHiddenInTable 的列默认不勾选,可在列设置中手动开启) */
useEffect(() => {
const dataIndexList = defaultTableColumns.map(item => item.dataIndex!);
const dataIndexList = defaultTableColumns
.filter(item => !item.defaultHiddenInTable)
.map(item => item.dataIndex!);
setColumnsChecked(dataIndexList);
}, [defaultTableColumns]);
+4
View File
@@ -22,6 +22,8 @@ export type XinTableColumn<T = any> = Omit<TableColumnType<T>, 'dataIndex'> & {
hideInSearch?: boolean;
hideInForm?: boolean;
hideInTable?: boolean;
/** 列设置中默认不勾选(表格默认隐藏,用户可在列设置中手动开启) */
defaultHiddenInTable?: boolean;
hideInUpdate?: boolean;
hideInCreate?: boolean;
search?: FormColumn<T>;
@@ -127,6 +129,8 @@ export interface XinTableProps<T = any> extends Omit<TableProps<T>, 'columns' |
deleteShow?: boolean | ((record: T) => boolean);
/** 搜索栏显示 */
searchShow?: boolean;
/** 搜索栏默认展开 */
searchDefaultOpen?: boolean;
/** 表格操作列显示 */
operateShow?: boolean;
/** 分页显示 */
+2
View File
@@ -29,6 +29,8 @@ export default interface IProduct {
spec?: string;
/** 计价单位 */
unit?: string;
/** 单价单位(如:元/斤、元/箱) */
price_unit?: string;
/** 封面 */
image_ids?: string;
images_arr?: ISysFileInfo[];
+3 -1
View File
@@ -25,7 +25,7 @@ export interface IPurchaseDetailRow {
quantity: number;
/** 合计实际称重 */
weight: number;
/** 合计订货金额(Σ明细 amount;参考零售价 = amount÷quantity÷包规数值) */
/** 合计订货金额(Σ明细 amount;价 = amount÷quantity÷包规数值) */
amount: string;
cells: Record<number, number>;
}
@@ -122,6 +122,8 @@ export interface IBill {
paid_operator_id?: number;
order_count?: number;
remark?: string;
/** 售后说明(生成账单时按门店填写) */
after_sale?: string;
created_at?: string;
/** 门店账单列表/详情接口附带 */
store?: { id: number; name: string; address?: string; contact?: string; phone?: string } | null;
+31 -5
View File
@@ -185,6 +185,7 @@ const StoreOrderPage: React.FC = () => {
dataIndex: 'items',
hideInForm: true,
hideInSearch: true,
defaultHiddenInTable: true,
width: 550,
render: (_, record) => {
return (
@@ -212,7 +213,7 @@ const StoreOrderPage: React.FC = () => {
hideInForm: true,
hideInTable: true,
fieldProps: {
options: stores.map((s) => ({ label: s.name, value: s.id })),
options: [{ label: '全部', value: '' }, ...stores.map((s) => ({ label: s.name, value: s.id }))],
showSearch: true,
optionFilterProp: 'label',
},
@@ -243,6 +244,15 @@ const StoreOrderPage: React.FC = () => {
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: '附加信息',
@@ -281,17 +291,32 @@ const StoreOrderPage: React.FC = () => {
align: 'center',
hideInTable: true,
fieldProps: {
options: Object.entries(STORE_ORDER_STATUS_MAP).map(([value, item]) => ({
value: Number(value),
label: item.text,
})),
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>
@@ -393,6 +418,7 @@ const StoreOrderPage: React.FC = () => {
tableRef,
operateShow: false,
formProps: false,
searchDefaultOpen: true,
rowSelection: {
selectedRowKeys,
onChange: (keys) => setSelectedRowKeys(keys),
+9
View File
@@ -387,6 +387,15 @@ const ProductGoodsPage: React.FC = () => {
initialValue: '斤',
align: "center",
},
{
title: '单价单位',
dataIndex: 'price_unit',
valueType: 'text',
hideInTable: true,
hideInSearch: true,
initialValue: '元/斤',
fieldProps: { placeholder: '如:元/斤、元/箱', maxLength: 20 },
},
{
title: '排序',
dataIndex: 'sort',
+65 -11
View File
@@ -127,6 +127,8 @@ const PurchaseOrderPage: React.FC = () => {
// 供应商采购明细页签
const [supplierId, setSupplierId] = useState<number>(0);
/** 市场筛选('' = 全部市场) */
const [marketFilter, setMarketFilter] = useState<string>('');
// 导出弹窗:商品明细(供应商筛选)/ 门店购买详情 / 供应商采购明细
const [itemExportOpen, setItemExportOpen] = useState(false);
@@ -147,11 +149,23 @@ const PurchaseOrderPage: React.FC = () => {
return Array.from(map, ([id, name]) => ({ id, name }));
}, [detail?.items]);
/** 供应商采购明细行:按供应商过滤矩阵行,金额=数量×成本价(成本口径 */
/** 当前供应商采购单内出现的市场列表(市场筛选下拉选项 */
const supplierMarkets = useMemo(() => {
const markets = new Set<string>();
(detail?.items ?? []).forEach((row) => {
if (row.supplier_id === supplierId && row.market) {
markets.add(row.market);
}
});
return Array.from(markets);
}, [detail?.items, supplierId]);
/** 供应商采购明细行:按供应商+市场过滤矩阵行,金额=数量×成本价(成本口径) */
const supplierItems = useMemo<IPurchaseSupplierItem[]>(() => {
if (!detail || supplierId <= 0) return [];
return detail.items
.filter((row) => row.supplier_id === supplierId)
.filter((row) => marketFilter === '' || (row.market ?? '') === marketFilter)
.map((row) => ({
product_id: row.product_id,
product_name: row.product_name,
@@ -163,7 +177,7 @@ const PurchaseOrderPage: React.FC = () => {
weight: String(row.weight),
amount: (row.quantity * Number(row.cost_price)).toFixed(2),
}));
}, [detail, supplierId]);
}, [detail, supplierId, marketFilter]);
// 生成账单(已完成采购单按门店生成:填写配送费/周转筐/托盘数量,金额只读)
const [billOpen, setBillOpen] = useState(false);
@@ -426,6 +440,8 @@ const PurchaseOrderPage: React.FC = () => {
delivery_fee: row.bill ? Number(row.bill.delivery_fee) : 0,
box_num: row.bill ? row.bill.box_num : 0,
tray_num: row.bill ? row.bill.tray_num : 0,
after_sale: row.bill?.after_sale ?? '',
remark: row.bill?.remark ?? '',
})),
});
} finally {
@@ -464,7 +480,7 @@ const PurchaseOrderPage: React.FC = () => {
}
};
/** 明细矩阵列:品名/供应商/参考零售价/包规/单位/成本 + 每门店一列(数量)+ 合计 + 操作 */
/** 明细矩阵列:品名/供应商/价/包规/单位/成本 + 每门店一列(数量)+ 合计 + 操作 */
const buildItemColumns = (): TableProps<IPurchaseDetailRow>['columns'] => {
const fixed: NonNullable<TableProps<IPurchaseDetailRow>['columns']> = [
{ title: '品名', dataIndex: 'product_name', width: 120, align: 'center' },
@@ -477,7 +493,7 @@ const PurchaseOrderPage: React.FC = () => {
},
{ title: '市场', dataIndex: 'market', width: 90, align: 'center', render: (v) => v || '-' },
{
title: '参考零售价',
title: '价',
key: 'retail_price',
width: 100,
align: 'center',
@@ -578,12 +594,12 @@ const PurchaseOrderPage: React.FC = () => {
);
};
/** 门店购买详情列:商品/市场/购买参考零售价(单价÷包规)/包规/单位/单价/数量/预计金额 + 操作 */
/** 门店购买详情列:商品/市场/购买价(单价÷包规)/包规/单位/单价/数量/预计金额 + 操作 */
const storeColumns: TableProps<IPurchaseStoreItem>['columns'] = [
{ title: '商品', dataIndex: 'product_name', width: 160, align: 'center' },
{ title: '市场', dataIndex: 'market', width: 90, align: 'center', render: (v) => v || '-' },
{
title: '参考零售价',
title: '价',
key: 'retail_price',
width: 130,
align: 'center',
@@ -1082,13 +1098,26 @@ const PurchaseOrderPage: React.FC = () => {
<Text></Text>
<Select
value={supplierId || undefined}
onChange={(value) => setSupplierId(value)}
onChange={(value) => {
setSupplierId(value);
setMarketFilter('');
}}
placeholder="选择供应商"
className="w-60!"
showSearch
optionFilterProp="label"
options={purchaseSuppliers.map((s) => ({ value: s.id, label: s.name }))}
/>
<Text></Text>
<Select
value={marketFilter}
onChange={setMarketFilter}
className="w-40!"
options={[
{ value: '', label: '全部市场' },
...supplierMarkets.map((m) => ({ value: m, label: m })),
]}
/>
</div>
<AuthButton auth="purchase.order.export">
<Button
@@ -1404,14 +1433,14 @@ const PurchaseOrderPage: React.FC = () => {
confirmLoading={billSaving}
okText="确认生成"
okButtonProps={{ disabled: billAllGenerated }}
width={960}
width={1280}
destroyOnHidden
>
<Spin spinning={billLoading}>
{billPrepare && (
<>
<div className="py-2 text-gray-500">
/==
/==/
{billAllGenerated ? '该采购单已全部生成账单,仅可查看。' : '生成后采购单中的全部订单将关联到对应门店账单。'}
</div>
<Form form={billForm} layout="vertical" onFinish={handleBillSave}>
@@ -1424,6 +1453,8 @@ const PurchaseOrderPage: React.FC = () => {
<div className="w-32 shrink-0 text-center"></div>
<div className="w-32 shrink-0 text-center">¥{billPrepare.stores[0]?.box_price ?? '0.00'}/</div>
<div className="w-32 shrink-0 text-center">¥{billPrepare.stores[0]?.tray_price ?? '0.00'}/</div>
<div className="w-36 shrink-0 text-center"></div>
<div className="w-36 shrink-0 text-center"></div>
<div className="w-28 shrink-0 text-center"></div>
<div className="flex-1 text-center"></div>
</div>
@@ -1482,6 +1513,26 @@ const PurchaseOrderPage: React.FC = () => {
placeholder="正压负回"
/>
</Form.Item>
<Form.Item
className="m-0! w-36 shrink-0 px-1!"
name={[field.name, 'after_sale']}
>
<Input
maxLength={255}
disabled={billed}
placeholder="售后说明(可选)"
/>
</Form.Item>
<Form.Item
className="m-0! w-36 shrink-0 px-1!"
name={[field.name, 'remark']}
>
<Input
maxLength={255}
disabled={billed}
placeholder="备注(可选)"
/>
</Form.Item>
<div className="w-28 shrink-0 text-center">
¥{(billed ? Number(row.bill!.added_amount) : billPreview[field.name]?.added ?? 0).toFixed(2)}
</div>
@@ -1638,7 +1689,7 @@ const PurchaseOrderPage: React.FC = () => {
/>
</Modal>
{/* 供应商采购明细导出:当前供应商 / 全部供应商(工作表) */}
{/* 供应商采购明细导出:当前供应商 / 全部供应商(按「供应商×市场」拆分工作表) */}
<Modal
title="导出供应商采购明细"
open={supplierExportOpen}
@@ -1655,13 +1706,16 @@ const PurchaseOrderPage: React.FC = () => {
okText="导出"
destroyOnHidden
>
<div className="py-2 text-gray-500">
XLSX ×
</div>
<Radio.Group
className="py-2"
value={supplierExportScope}
onChange={(e) => setSupplierExportScope(e.target.value)}
options={[
{ value: 'current', label: `当前供应商(${purchaseSuppliers.find((s) => s.id === supplierId)?.name ?? '-'}` },
{ value: 'all', label: '全部供应商(合并为一个 XLSX,每供应商一个工作表)' },
{ value: 'all', label: '全部供应商(合并为一个 XLSX,每供应商×市场一个工作表)' },
]}
/>
</Modal>
+3
View File
@@ -403,6 +403,9 @@ const BillPage: React.FC = () => {
{detail.bill.pay_remark ? (
<Descriptions.Item label="付款备注" span={3}>{detail.bill.pay_remark}</Descriptions.Item>
) : null}
{detail.bill.after_sale ? (
<Descriptions.Item label="售后" span={3}>{detail.bill.after_sale}</Descriptions.Item>
) : null}
{detail.bill.remark ? (
<Descriptions.Item label="备注" span={3}>{detail.bill.remark}</Descriptions.Item>
) : null}