ERPTurbo_Client/packages/app-client/src/components/purchase/section/PackageInfoSection.tsx
shenyifei d5502a7aac feat(purchase): 优化采购单各费用模块的展示与计算逻辑
- 统一金额显示格式,去除小数点后多余零
- 优化各费用项编辑弹窗的布局与交互体验
- 调整包装费用、生产垫付、工头垫付等模块的展示结构
- 移除表格中的成本单价列及相关编辑功能
- 更新默认数量与单位的初始化逻辑
- 简化辅料费用编辑值的初始化流程
- 修复页面重复初始化的问题
- 升级应用版本至 v0.0.26
2025-11-21 14:51:33 +08:00

599 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { formatCurrency, validatePrice } from "@/utils/format";
import { useEffect, useState } from "react";
import { Button, Input, Popup, SafeArea, Table } from "@nutui/nutui-react-taro";
import { Icon } from "@/components";
import { View } from "@tarojs/components";
export default function PackageInfoSection(props: {
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly } = props;
const defaultColumns = [
{
title: "纸箱型号",
key: "boxProductName",
fixed: "left",
},
{
title: "个数",
key: "boxProductCount",
},
{
title: "销售单价(元/斤)",
key: "boxSalePrice",
render: (
value: BusinessAPI.OrderPackage & {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => {
// 合计行不显示编辑按钮
if (value.isTotalRow) {
return formatCurrency(value.boxSalePrice as number);
}
return (
<View
className="flex w-full items-center justify-between"
onClick={(e) => {
if (!readOnly) {
e.stopPropagation();
// 设置临时编辑值为当前值
setTempEditValues((prev) => ({
...prev,
[value.orderPackageId || ""]:
editValues[value.orderPackageId || ""],
}));
setVisiblePopup((prev) => ({
...prev,
[value.orderPackageId || ""]: true,
}));
}
}}
>
<View className={!readOnly ? "cursor-pointer underline" : ""}>
{formatCurrency(value.boxSalePrice as number)}
</View>
{!readOnly && (
<View className="-m-2 ml-2 flex items-center justify-center p-2">
<Icon name={"pen-to-square"} size={16} color={"#1a73e8"} />
</View>
)}
</View>
);
},
},
{
title: "销售金额(元)",
key: "boxSalePayment",
render: (
value: BusinessAPI.OrderPackage & {
boxProductCount: number;
isTotalRow?: boolean;
},
) =>
formatCurrency(
Number((value?.boxSalePrice || 0) * value.boxProductCount) as number,
),
},
{
title: "箱重(斤)",
key: "boxProductWeight",
render: (
value: BusinessAPI.OrderPackage & {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => {
// 合计行不显示编辑按钮
if (value.isTotalRow) {
return formatCurrency(value.boxProductWeight);
}
return (
<View
className="flex w-full items-center justify-between"
onClick={(e) => {
if (!readOnly) {
e.stopPropagation();
// 设置临时编辑值为当前值
setTempEditValues((prev) => ({
...prev,
[value.orderPackageId || ""]:
editValues[value.orderPackageId || ""],
}));
setVisiblePopup((prev) => ({
...prev,
[value.orderPackageId || ""]: true,
}));
}
}}
>
<View className={!readOnly ? "cursor-pointer underline" : ""}>
{formatCurrency(value.boxProductWeight)}
</View>
{!readOnly && (
<View className="-m-2 ml-2 flex items-center justify-center p-2">
<Icon name={"pen-to-square"} size={16} color={"#1a73e8"} />
</View>
)}
</View>
);
},
},
{
title: "品牌",
key: "boxBrandName",
fixed: "left",
},
];
const [columns, setColumns] = useState<any[]>(defaultColumns);
useEffect(() => {
if (!readOnly) {
setColumns([...defaultColumns]);
}
}, [readOnly]);
// 弹窗可见状态
const [visiblePopup, setVisiblePopup] = useState<{ [key: string]: boolean }>(
{},
);
// 编辑值的状态
const [editValues, setEditValues] = useState<{
[key: string]: {
boxCostPrice?: number;
boxSalePrice?: number;
boxProductWeight?: number;
};
}>({});
// 临时编辑值的状态(用于在保存前暂存编辑的值)
const [tempEditValues, setTempEditValues] = useState<{
[key: string]: {
boxCostPrice?: number;
boxSalePrice?: number;
boxProductWeight?: number;
};
}>({});
// 初始化编辑值
const initEditValues = (
pkgId: string,
boxCostPrice?: number,
boxSalePrice?: number,
boxProductWeight?: number,
) => {
const updates: {
editValuesUpdate?: {
boxCostPrice?: number;
boxSalePrice?: number;
boxProductWeight?: number;
};
tempEditValuesUpdate?: {
boxCostPrice?: number;
boxSalePrice?: number;
boxProductWeight?: number;
};
} = {};
if (!editValues[pkgId]) {
updates.editValuesUpdate = {
boxCostPrice,
boxSalePrice,
boxProductWeight,
};
}
// 同时初始化临时编辑值
if (!tempEditValues[pkgId]) {
updates.tempEditValuesUpdate = {
boxCostPrice,
boxSalePrice,
boxProductWeight,
};
}
return updates;
};
// 将所有包装信息合并并按品牌、型号、规格分组
const groupedPackageData = purchaseOrderVO.orderSupplierList?.reduce(
(acc, supplier) => {
supplier.orderPackageList?.forEach((pkg) => {
// 生成分组键
const key = `${pkg.boxBrandName}-${pkg.boxProductName}-${pkg.boxSpecId}`;
// 转换规格字段
const boxSpecId = pkg.boxSpecId;
const boxSpecName = pkg.boxSpecName;
if (!acc[key]) {
acc[key] = {
...pkg,
boxSpecId,
boxSpecName,
boxProductCount: 0,
};
}
// 累加数量
acc[key].boxProductCount += pkg.boxCount || 0;
});
return acc;
},
{} as Record<string, any>,
);
// 转换为数组格式
const packageData = Object.values(groupedPackageData || {});
// 计算合计数据
const calculateTotals = () => {
if (!packageData || packageData.length === 0) {
return {};
}
// 计算各项合计
let totalBoxProductCount = 0;
let totalBoxSalePayment = 0;
let totalBoxCostPayment = 0;
let totalBoxProductWeight = 0;
packageData.forEach((pkg: any) => {
totalBoxProductCount += pkg.boxProductCount || 0;
totalBoxSalePayment +=
Number((pkg?.boxSalePrice || 0) * pkg.boxProductCount) || 0;
totalBoxCostPayment +=
Number((pkg?.boxCostPrice || 0) * pkg.boxProductCount) || 0;
totalBoxProductWeight +=
Number((pkg?.boxProductWeight || 0) * pkg.boxProductCount) || 0;
});
return {
boxProductName: "合计",
boxProductCount: totalBoxProductCount,
boxSalePayment: totalBoxSalePayment,
boxProductWeight: totalBoxProductWeight,
boxCostPrice: totalBoxCostPayment,
isTotalRow: true, // 标记这是合计行
};
};
const totalsData = calculateTotals();
// 初始化所有包装项的编辑值
useEffect(() => {
const newEditValues = { ...editValues };
const newTempEditValues = { ...tempEditValues };
let hasEditValuesChanged = false;
let hasTempEditValuesChanged = false;
packageData.forEach((pkg: BusinessAPI.OrderPackage) => {
const pkgId = pkg.orderPackageId || "";
const updates = initEditValues(
pkgId,
pkg.boxCostPrice,
pkg.boxSalePrice,
pkg.boxProductWeight,
);
if (updates.editValuesUpdate) {
newEditValues[pkgId] = updates.editValuesUpdate;
hasEditValuesChanged = true;
}
if (updates.tempEditValuesUpdate) {
newTempEditValues[pkgId] = updates.tempEditValuesUpdate;
hasTempEditValuesChanged = true;
}
});
if (hasEditValuesChanged) {
setEditValues(newEditValues);
}
if (hasTempEditValuesChanged) {
setTempEditValues(newTempEditValues);
}
}, [packageData]);
// 当editValues发生变化时更新purchaseOrderVO
useEffect(() => {
// 只有当onChange存在时才更新
if (onChange) {
// 创建新的purchaseOrderVO对象
const newPurchaseOrderVO = { ...purchaseOrderVO };
// 更新供应商列表中的包装信息
if (newPurchaseOrderVO.orderSupplierList) {
newPurchaseOrderVO.orderSupplierList =
newPurchaseOrderVO.orderSupplierList.map((supplier) => {
if (supplier.orderPackageList) {
const newOrderPackageList = supplier.orderPackageList.map(
(pkg) => {
const editValue = editValues[pkg.orderPackageId || ""];
if (editValue) {
return {
...pkg,
boxCostPrice:
editValue.boxCostPrice !== undefined
? editValue.boxCostPrice
: pkg.boxCostPrice,
boxSalePrice:
editValue.boxSalePrice !== undefined
? editValue.boxSalePrice
: pkg.boxSalePrice,
boxProductWeight:
editValue.boxProductWeight !== undefined
? editValue.boxProductWeight
: pkg.boxProductWeight,
};
}
return pkg;
},
);
return {
...supplier,
orderPackageList: newOrderPackageList,
};
}
return supplier;
});
}
// 调用onChange回调
onChange(newPurchaseOrderVO);
}
}, [editValues]);
// 自定义合计行渲染
const renderTableWithTotals = () => {
// 创建包含合计行的数据
const dataWithTotals = [...packageData];
if (Object.keys(totalsData).length > 0) {
dataWithTotals.push(totalsData);
}
// 自定义列配置,对合计行特殊处理
const columnsWithTotalsRender = columns.map((column) => {
if (column.key === "boxProductName") {
// 品牌列显示"合计"
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{rowData.boxProductName}
</span>
);
}
return rowData.boxProductName;
},
};
} else if (column.key === "boxProductCount") {
// 个数列显示合计
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{rowData.boxProductCount}
</span>
);
}
return rowData.boxProductCount;
},
};
} else if (column.key === "boxSalePrice") {
// 销售单价列合计行处理
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePrice as number)}
</span>
);
}
return column.render(rowData, rowData);
},
};
} else if (column.key === "boxSalePayment") {
// 销售金额列显示合计
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePayment)}
</span>
);
}
return formatCurrency(
Number(
(rowData?.boxSalePrice || 0) * rowData.boxProductCount,
) as number,
);
},
};
} else if (column.key === "boxProductWeight") {
// 重量列合计行处理
return {
...column,
render: (rowData: any) => {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxProductWeight)}
</span>
);
}
return column.render(rowData, rowData);
},
};
}
// 其他列保持原有render函数或者默认显示
return column;
});
return (
<Table
className={"table-sum"}
columns={columnsWithTotalsRender}
data={dataWithTotals}
striped
/>
);
};
return (
<>
{renderTableWithTotals()}
{/* 纸箱价格编辑弹窗 */}
{packageData.map((pkg: BusinessAPI.OrderPackage) => (
<Popup
duration={150}
style={{
minHeight: "auto",
}}
key={pkg.orderPackageId}
visible={visiblePopup[pkg.orderPackageId || ""]}
position="bottom"
title="编辑纸箱信息"
onClose={() =>
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}))
}
onOverlayClick={() =>
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}))
}
lockScroll
>
<View className="flex flex-col gap-3 p-2.5">
<View className="text-neutral-darkest flex flex-row text-sm font-medium">
<Icon name="money-bill" size={16} className="mr-1" />
</View>
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
<Input
className="placeholder:text-neutral-dark"
placeholder="请输入销售单价"
type="digit"
value={
tempEditValues[
pkg.orderPackageId || ""
]?.boxSalePrice?.toString() || ""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[pkg.orderPackageId || ""]: {
...prev[pkg.orderPackageId || ""],
boxSalePrice: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
<View className="text-neutral-darkest flex flex-row text-sm font-medium">
<Icon name="weight-scale" size={16} className="mr-1" />
</View>
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
<Input
className="placeholder:text-neutral-dark"
placeholder="请输入箱重"
type="digit"
value={
tempEditValues[
pkg.orderPackageId || ""
]?.boxProductWeight?.toString() || ""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[pkg.orderPackageId || ""]: {
...prev[pkg.orderPackageId || ""],
boxProductWeight: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</View>
<View className="flex w-full flex-col bg-white">
<View className="flex flex-row gap-2 p-3">
<View className="flex-1">
<Button
size="large"
block
type="default"
onClick={() =>
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}))
}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[pkg.orderPackageId || ""]: {
...tempEditValues[pkg.orderPackageId || ""],
},
}));
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[pkg.orderPackageId || ""]: false,
}));
}}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</Popup>
))}
</>
);
}