From fb71cf003ea494aec0b716f41d81e191eb98d23d Mon Sep 17 00:00:00 2001 From: shenyifei Date: Tue, 18 Nov 2025 10:10:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(purchase):=20=E6=B7=BB=E5=8A=A0=E7=A9=BA?= =?UTF-8?q?=E7=AE=B1=E7=AE=A1=E7=90=86=E5=92=8C=E8=AE=A2=E5=8D=95=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=BF=BD=E8=B8=AA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 OrderCost 组件中实现空箱品牌选择、批量添加和编辑功能 - 添加空箱类型启用状态管理及 UI 展示 - 实现空箱数据转换工具函数 convertBoxBrandToOrderPackages 和 convertOrderPackagesToBoxBrands - 在创建订单页面添加修改标识状态管理,追踪车辆、瓜农、称重、包装、票证和成本信息变更 - 添加 lodash 用于深度比较对象变更 - 移除多个调试用 console.log语句 - 优化保存逻辑,仅在信息变更时调用接口 --- .../purchase/module/MelonFarmer.tsx | 3 +- .../components/purchase/module/OrderCost.tsx | 944 +++++++++++++++++- .../purchase/module/OrderPackage.tsx | 2 - .../src/pages/purchase/purchaser/create.tsx | 276 ++++- .../src/services/business/typings.d.ts | 6 +- packages/app-client/src/types/typings.ts | 2 +- .../src/utils/SupplierWeightCalculator.ts | 10 +- 7 files changed, 1210 insertions(+), 33 deletions(-) diff --git a/packages/app-client/src/components/purchase/module/MelonFarmer.tsx b/packages/app-client/src/components/purchase/module/MelonFarmer.tsx index 207b869..d34b472 100644 --- a/packages/app-client/src/components/purchase/module/MelonFarmer.tsx +++ b/packages/app-client/src/components/purchase/module/MelonFarmer.tsx @@ -1,12 +1,12 @@ import { Text, View } from "@tarojs/components"; import { Button, + Dialog, Input, Radio, Toast, Uploader, UploaderFileItem, - Dialog, } from "@nutui/nutui-react-taro"; import { Icon, SupplierPicker } from "@/components"; import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; @@ -47,7 +47,6 @@ export default forwardRef( } = props; const [supplierVO, setSupplierVO] = useState(); - console.log("supplierVO", supplierVO); // 初始化数据 useEffect(() => { diff --git a/packages/app-client/src/components/purchase/module/OrderCost.tsx b/packages/app-client/src/components/purchase/module/OrderCost.tsx index ce3b398..fa973ed 100644 --- a/packages/app-client/src/components/purchase/module/OrderCost.tsx +++ b/packages/app-client/src/components/purchase/module/OrderCost.tsx @@ -1,10 +1,30 @@ -import { View } from "@tarojs/components"; +import { ScrollView, View } from "@tarojs/components"; import { Icon } from "@/components"; import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { business } from "@/services"; -import { Button, Checkbox, Input, Toast } from "@nutui/nutui-react-taro"; -import { CostItem, SupplierVO } from "@/types/typings"; +import { + Button, + Checkbox, + Dialog, + Image, + Input, + Popup, + SafeArea, + Toast, +} from "@nutui/nutui-react-taro"; +import { + BoxBrand, + BoxProduct, + BoxSpec, + CostItem, + SupplierVO, +} from "@/types/typings"; import { generateShortId } from "@/utils/generateShortId"; +import { + convertBoxBrandToOrderPackages, + convertOrderPackagesToBoxBrands, +} from "@/utils/orderPackage"; +import classNames from "classnames"; // 定义ref暴露的方法接口 export interface OrderCostRef { @@ -15,20 +35,152 @@ export interface IOrderCostProps { value: CostItem[]; supplierVO: SupplierVO; onChange?: (costItemList: CostItem[]) => void; + emptyBoxList: BusinessAPI.OrderPackage[]; + onEmptyBoxChange?: (orderPackageList: BusinessAPI.OrderPackage[]) => void; onAdd: () => void; } export default forwardRef( function OrderCost(IOrderCostProps, ref) { - const { value, supplierVO, onChange, onAdd } = IOrderCostProps; + const { + value, + supplierVO, + onChange, + onAdd, + onEmptyBoxChange, + emptyBoxList: defaultEmptyBoxList, + } = IOrderCostProps; const [costItemVOList, setCostItemVOList] = useState(); + // 新增状态:跟踪每种纸箱类型的启用状态 + // 0: 未选, 1: 选择是 + const [packageTypeEnabled, setPackageTypeEnabled] = useState({ + EMPTY: 0, + }); + + // 空箱相关状态 + const [emptyBoxList, setEmptyBoxList] = useState< + BusinessAPI.OrderPackage[] + >([]); + const [boxBrandList, setBoxBrandList] = useState(); + + // 批量添加空箱相关状态 + const [selectedBrand, setSelectedBrand] = useState(null); + const [productCounts, setProductCounts] = useState>( + new Map(), + ); + const [showBatchModal, setShowBatchModal] = useState(false); + + // 编辑空箱弹窗状态 + const [showEditModal, setShowEditModal] = useState(false); + const [editingItem, setEditingItem] = useState(null); + + // 处理纸箱类型启用状态切换 + const togglePackageType = ( + type: BusinessAPI.OrderPackage["boxType"], + value: number, // 0: 未选, 1: 选择是, 2: 选择否 + ) => { + setPackageTypeEnabled((prev) => ({ + ...prev, + [type]: value, + })); + }; + useEffect(() => { + onEmptyBoxChange?.( + emptyBoxList?.map((item) => { + return { + ...item, + id: generateShortId(), + }; + }), + ); + }, [emptyBoxList]); + // 总的工头姓名状态 const [principal, setPrincipal] = useState(""); // 工头姓名错误状态 const [principalError, setPrincipalError] = useState(false); + // 初始化空箱品牌数据 + const initBoxBrandList = async () => { + const { data } = await business.boxBrand.listBoxBrand({ + boxBrandListQry: { + status: true, + withProduct: true, + }, + }); + + const boxBrandList = + data.data + ?.filter( + (boxBrand) => + boxBrand.status && + boxBrand.boxProductVOList && + boxBrand.boxProductVOList.length > 0, + ) + .map((boxBrand) => { + // 将产品按规格分类 + const boxSpecList: BoxSpec[] = + boxBrand.boxSpecVOList?.map((item) => { + return { + id: generateShortId(), + boxSpecId: item.specId, + boxSpecName: item.name, + boxProductList: [], + }; + }) || []; + const boxProductList = boxBrand.boxProductVOList?.map( + (boxProductVO) => { + const boxProduct: BoxProduct = { + id: generateShortId(), + boxProductId: boxProductVO.productId, + boxProductName: boxProductVO.name, + boxProductWeight: boxProductVO.weight, + boxSpecId: boxProductVO.specId, + boxSpecName: boxProductVO.specName, + boxCostPrice: boxProductVO.costPrice, + boxSalePrice: boxProductVO.salePrice, + boxBrandId: boxBrand.brandId, + boxBrandName: boxBrand.name, + boxCount: 0, + }; + + return boxProduct; + }, + ); + + return { + id: generateShortId(), + boxBrandId: boxBrand.brandId, + boxBrandName: boxBrand.name, + boxBrandImage: boxBrand.image, + boxBrandType: boxBrand.type, + boxSpecList: + boxSpecList + .map((boxSpec) => { + return { + id: generateShortId(), + boxSpecId: boxSpec.boxSpecId, + boxSpecName: boxSpec.boxSpecName, + boxProductList: + boxProductList?.filter( + (boxProduct) => + boxProduct.boxSpecId === boxSpec.boxSpecId, + ) || [], + }; + }) + .filter( + (boxSpec) => + boxSpec.boxProductList && + boxSpec.boxProductList.length > 0, + ) || [], + }; + }) || []; + + setBoxBrandList(boxBrandList as any); + }; + const init = async () => { const { data } = await business.costItem.listCostItem({ costItemListQry: { @@ -113,6 +265,8 @@ export default forwardRef( // 当传入的value发生变化时,重新初始化列表 useEffect(() => { init().then(); + initBoxBrandList().then(); + setEmptyBoxList(defaultEmptyBoxList); }, []); // 当内部状态发生变化时,通知父组件更新 @@ -240,6 +394,7 @@ export default forwardRef( let isValid = true; + // 校验人工和辅料费用 costItemVOList.forEach((item) => { if (item.selected) { // 校验数量 @@ -422,6 +577,723 @@ export default forwardRef( ); }; + // 处理批量添加时的品牌选择 + const handleBatchBrandSelect = (brand: BoxBrand) => { + // 检查是否已存在该品牌 + const isBrandAlreadySelected = emptyBoxList.some( + (pkg) => pkg.boxBrandId === brand.boxBrandId, + ); + + if (isBrandAlreadySelected) { + Toast.show("toast", { + icon: "fail", + title: "提示", + content: "该空箱品牌已存在,请选择其他品牌或编辑现有信息", + }); + return; + } + + setSelectedBrand(brand); + // 初始化所有产品的数量为0 + const initialCounts = new Map(); + brand.boxSpecList?.forEach((boxSpec) => { + boxSpec.boxProductList.forEach((product) => { + initialCounts.set(product.id, 0); + }); + }); + setProductCounts(initialCounts); + }; + + // 处理产品数量变化 + const handleProductCountChange = (productId: string, count: number) => { + setProductCounts((prev) => { + const newCounts = new Map(prev); + newCounts.set(productId, Math.max(0, count)); // 允许为0,表示不使用 + return newCounts; + }); + + // 同时更新selectedBrand中的产品数量 + if (selectedBrand) { + const updatedBrand = { ...selectedBrand }; + + updatedBrand.boxSpecList = updatedBrand.boxSpecList?.map((boxSpec) => { + const updatedProducts = boxSpec.boxProductList.map((product) => { + if (product.id === productId) { + return { ...product, boxCount: count }; + } + return product; + }); + return { ...boxSpec, boxProductList: updatedProducts }; + }); + setSelectedBrand(updatedBrand); + } + }; + + // 批量添加空箱信息 + const addBatchEmptyBoxInfo = () => { + if (!selectedBrand) { + return; + } + + // 使用convertBoxBrandToOrderPackages转换数据 + const newOrderPackages = convertBoxBrandToOrderPackages( + selectedBrand, + "EMPTY", + ); + + // 过滤掉数量为0的项目 + const filteredOrderPackages = newOrderPackages.filter((pkg) => { + // 从productCounts中获取对应产品的数量 + const product = selectedBrand.boxSpecList + ?.flatMap((c) => c.boxProductList) + .find((p) => p.id === pkg.orderPackageId); + const count = product ? productCounts.get(product.id) || 0 : 0; + return count > 0; + }); + + // 更新数量信息 + const updatedOrderPackages = filteredOrderPackages.map((pkg) => { + // 从productCounts中获取对应产品的数量 + const product = selectedBrand.boxSpecList + ?.flatMap((c) => c.boxProductList) + .find((p) => p.id === pkg.orderPackageId); + const count = product ? productCounts.get(product.id) || 0 : 0; + return { + ...pkg, + boxCount: count, + }; + }); + + // 添加到emptyBoxList中 + setEmptyBoxList((prev) => [...prev, ...updatedOrderPackages]); + + // 重置选择并关闭弹窗 + setSelectedBrand(null); + setProductCounts(new Map()); + setShowBatchModal(false); + }; + + // 删除空箱信息 + const removeEmptyBoxInfo = (boxBrandId: string) => { + Dialog.open("dialog", { + title: "提示", + content: "确定要移除此空箱品牌吗?", + confirmText: "确定", + cancelText: "取消", + onConfirm: () => { + const newList = [ + ...emptyBoxList.filter((item) => item.boxBrandId !== boxBrandId), + ]; + setEmptyBoxList(newList); + Dialog.close("dialog"); + }, + onCancel: () => { + Dialog.close("dialog"); + }, + }); + }; + + // 处理编辑按钮点击 + const handleEditItem = (item: BoxBrand) => { + // 从emptyBoxList中找到完整的品牌信息用于编辑 + const brandToEdit = convertOrderPackagesToBoxBrands( + emptyBoxList.filter((pkg) => pkg.boxBrandId === item.boxBrandId), + )[0]; + + // 获取完整品牌信息(包括所有规格) + const fullBrandInfo = boxBrandList?.find( + (brand) => brand.boxBrandId === item.boxBrandId, + ); + + // 合并已有的数据和完整品牌信息 + const mergedBrandInfo = fullBrandInfo + ? ({ + ...fullBrandInfo, + boxType: "EMPTY", // 保持boxType + boxSpecList: fullBrandInfo.boxSpecList?.map((boxSpec) => { + // 找到对应的已存在分类数据 + const existingSpec = brandToEdit?.boxSpecList?.find( + (spec) => spec.boxSpecId === boxSpec.boxSpecId, + ); + + return { + ...boxSpec, + boxProductList: boxSpec.boxProductList.map((product) => { + // 找到对应的产品数据 + const existingProduct = existingSpec?.boxProductList.find( + (prod) => prod.boxProductId === product.boxProductId, + ); + + return { + ...product, + boxCount: existingProduct?.boxCount || 0, + }; + }), + }; + }), + } as BoxBrand) + : brandToEdit; + + if (mergedBrandInfo) { + setEditingItem(mergedBrandInfo); + setShowEditModal(true); + } + }; + + // 更新编辑项中的数量 + const updateEditingItemCount = ( + specIndex: number, + detailIndex: number, + count: number, + ) => { + if (!editingItem) return; + + // 获取完整品牌信息 + const fullBrandInfo = boxBrandList?.find( + (brand) => brand.boxBrandId === editingItem.boxBrandId, + ); + + if (!fullBrandInfo) return; + + const updatedItem = { ...editingItem }; + + // 确保boxSpecList存在 + if (!updatedItem.boxSpecList) { + updatedItem.boxSpecList = fullBrandInfo.boxSpecList.map((boxSpec) => ({ + ...boxSpec, + boxProductList: boxSpec.boxProductList.map((product) => ({ + ...product, + boxCount: 0, + })), + })); + } + + // 确保spec存在 + if (!updatedItem.boxSpecList[specIndex]) { + updatedItem.boxSpecList[specIndex] = { + ...fullBrandInfo.boxSpecList[specIndex], + boxProductList: fullBrandInfo.boxSpecList[ + specIndex + ].boxProductList.map((product) => ({ + ...product, + boxCount: 0, + })), + }; + } + + // 更新对应的产品数量 + const boxSpec = { ...updatedItem.boxSpecList[specIndex] }; + + // 确保boxProductList存在 + if (!boxSpec.boxProductList) { + boxSpec.boxProductList = fullBrandInfo.boxSpecList[ + specIndex + ].boxProductList.map((product) => ({ + ...product, + boxCount: 0, + })); + } + + const boxProductList = [...boxSpec.boxProductList]; + + // 确保产品存在 + if (!boxProductList[detailIndex]) { + boxProductList[detailIndex] = { + ...fullBrandInfo.boxSpecList[specIndex].boxProductList[detailIndex], + boxCount: 0, + }; + } + + boxProductList[detailIndex] = { + ...boxProductList[detailIndex], + boxCount: count, + }; + + boxSpec.boxProductList = boxProductList; + const updatedSpecList = [...updatedItem.boxSpecList]; + updatedSpecList[specIndex] = boxSpec; + updatedItem.boxSpecList = updatedSpecList; + + setEditingItem(updatedItem); + }; + + // 保存编辑的空箱信息 + const saveEditedItem = () => { + if (editingItem) { + // 检查是否尝试更改为已存在的品牌(排除自身) + const isBrandAlreadySelected = emptyBoxList.some( + (pkg) => + pkg.boxBrandId === editingItem.boxBrandId && + !(pkg.boxBrandId === editingItem.boxBrandId), + ); + + if (isBrandAlreadySelected) { + Toast.show("toast", { + icon: "fail", + title: "提示", + content: "该空箱品牌已存在,请选择其他品牌", + }); + return; + } + + // 使用convertBoxBrandToOrderPackages转换编辑后的数据 + const updatedOrderPackages = convertBoxBrandToOrderPackages( + editingItem, + "EMPTY", + ).filter((pkg) => pkg.boxCount > 0); + + // 更新emptyBoxList + const newList = [...emptyBoxList]; + // 找到对应品牌和类型的现有项目并替换 + const startIndex = newList.findIndex( + (pkg) => pkg.boxBrandId === editingItem.boxBrandId, + ); + + if (startIndex !== -1) { + // 移除旧的项目 + const filteredList = newList.filter( + (pkg) => !(pkg.boxBrandId === editingItem.boxBrandId), + ); + + // 添加更新后的项目 + setEmptyBoxList([...filteredList, ...updatedOrderPackages]); + } else { + // 如果没有找到,直接添加 + setEmptyBoxList((prev) => [...prev, ...updatedOrderPackages]); + } + } + setShowEditModal(false); + setEditingItem(null); + }; + + // 渲染单个空箱信息模块 + const renderEmptyBoxItem = (item: BoxBrand) => { + // 获取品牌信息用于展示品牌图片 + const brandInfo = boxBrandList?.find( + (brand) => brand.boxBrandId === item.boxBrandId, + ); + + return ( + + {/* 品牌背景水印 */} + {brandInfo?.boxBrandImage && ( + + + + )} + + {/* 品牌信息和操作按钮放在同一行 */} + + + {brandInfo?.boxBrandImage && ( + + + + )} + + + {item.boxBrandName || "未选择品牌"} + + + + + {/* 操作按钮与品牌信息放在同一行 */} + + handleEditItem(item)} + > + 编辑 + + removeEmptyBoxInfo(item.boxBrandId)} + > + 移除 + + + + + {/* 详细信息展示,按分类显示 */} + + {item.boxSpecList && + item.boxSpecList.map((boxSpec, boxIndex) => ( + + + {boxSpec.boxSpecName} + + + {boxSpec.boxProductList + .map( + (detail) => + `${detail.boxProductName} 共 ${detail.boxCount} 个`, + ) + .join(",")} + + + ))} + + + ); + }; + + // 渲染批量添加空箱弹窗 + const renderBatchAddModal = () => { + // 检查是否至少有一个产品的数量大于0 + const hasAnyProductWithCount = selectedBrand + ? Array.from(productCounts.values()).some((count) => count > 0) + : false; + + return ( + { + setShowBatchModal(false); + setSelectedBrand(null); + setProductCounts(new Map()); + }} + title={"批量添加空箱"} + round + > + + + {/* 品牌选择 */} + + 选择品牌 + + + {boxBrandList + ?.filter((item) => item.boxBrandType !== "OUR_BOX") + ?.map((boxBrand) => ( + handleBatchBrandSelect(boxBrand)} + > + + {boxBrand.boxBrandImage} + + + {boxBrand.boxBrandName} + + + ))} + + + + + {/* 未选择品牌时的提示 */} + {!selectedBrand && ( + + 请先选择一个品牌 + + )} + + {/* 产品展示 */} + {selectedBrand && ( + + + 空箱规格(点击 +/- 修改数量,0表示不使用) + + {selectedBrand.boxSpecList?.map((boxSpec) => ( + + + {boxSpec.boxSpecName} + + + {boxSpec.boxProductList.map((boxProduct) => { + const currentCount = + productCounts.get(boxProduct.id) || 0; + return ( + + + {boxProduct.boxProductName} + + + { + handleProductCountChange( + boxProduct.id, + Math.max(0, currentCount - 1), + ); + }} + > + + + + { + const num = Number(value); + if (!Number.isNaN(num) && num >= 0) { + handleProductCountChange( + boxProduct.id, + num, + ); + } + }} + /> + + { + handleProductCountChange( + boxProduct.id, + currentCount + 1, + ); + }} + > + + + + + + ); + })} + + + ))} + + )} + + + {/* 底部按钮 */} + + + + + + + + + + + + ); + }; + + // 渲染编辑空箱弹窗 + const renderEditModal = () => { + if (!editingItem) return null; + + // 获取品牌信息 + const brandInfo = boxBrandList?.find( + (brand) => brand.boxBrandId === editingItem.boxBrandId, + ); + + return ( + setShowEditModal(false)} + title={"编辑空箱信息"} + round + > + + + {/* 品牌信息 */} + + 品牌信息 + + {brandInfo?.boxBrandImage && ( + + + + )} + + {editingItem.boxBrandName} + + + + + {/* 详细信息 */} + + 空箱详情 + + {editingItem?.boxSpecList && + editingItem.boxSpecList.map((boxSpec, specId) => ( + + + {boxSpec.boxSpecName} + + + {boxSpec.boxProductList.map((detail, detailIndex) => { + const currentCount = detail?.boxCount || 0; + + return ( + + + {detail.boxProductName} + + + { + const newCount = Math.max( + 0, + currentCount - 1, + ); + updateEditingItemCount( + specId, + detailIndex, + newCount, + ); + }} + > + + + + { + const num = Number(value); + if (!Number.isNaN(num) && num >= 0) { + updateEditingItemCount( + specId, + detailIndex, + num, + ); + } + }} + /> + + { + updateEditingItemCount( + specId, + detailIndex, + currentCount + 1, + ); + }} + > + + + + 个 + + + + ); + })} + + + ))} + + + + + {/* 底部按钮 */} + + + + + + + + + + + + ); + }; + + // 获取空箱列表 + const emptyBoxes = convertOrderPackagesToBoxBrands(emptyBoxList); + return ( @@ -479,14 +1351,57 @@ export default forwardRef( "PACKAGING_MATERIALS", )} - {/**/} - {/* 空纸箱费用*/} - {/* {renderItemList(getItemsByCostType("OTHER_COST"), "OTHER_COST")}*/} - {/**/} - {/**/} - {/* 空礼盒费用*/} - {/* {renderItemList(getItemsByCostType("OTHER_COST"), "OTHER_COST")}*/} - {/**/} + + 空箱费用 + + + 带空箱了吗? + + { + togglePackageType("EMPTY", checked ? 1 : 0); + if (!checked) { + setEmptyBoxList([]); + } + }} + > + + 带了 + + + + + + {/* 空箱信息展示 */} + {packageTypeEnabled.EMPTY === 1 && ( + + {emptyBoxes.length > 0 ? ( + emptyBoxes.map((item) => renderEmptyBoxItem(item)) + ) : ( + + 还没有添加空箱 + + )} + + + + )} + + {/* 只有当用户选择"否"时才显示添加按钮 */} {!supplierVO.isLast && ( @@ -504,6 +1419,11 @@ export default forwardRef( 添加另一个瓜农 )} + + {/* 批量添加弹窗 */} + {renderBatchAddModal()} + {/* 编辑弹窗 */} + {renderEditModal()} ); }, diff --git a/packages/app-client/src/components/purchase/module/OrderPackage.tsx b/packages/app-client/src/components/purchase/module/OrderPackage.tsx index 6118b60..3cbc5ad 100644 --- a/packages/app-client/src/components/purchase/module/OrderPackage.tsx +++ b/packages/app-client/src/components/purchase/module/OrderPackage.tsx @@ -785,8 +785,6 @@ export default forwardRef( {boxBrandList ?.filter((item) => { - console.log("item: ", item); - console.log("boxType: ", boxType); if (boxType === "OWN") { return item.boxBrandType === "FARMER_BOX"; } else { diff --git a/packages/app-client/src/pages/purchase/purchaser/create.tsx b/packages/app-client/src/pages/purchase/purchaser/create.tsx index 0485eb0..7e8b1e8 100644 --- a/packages/app-client/src/pages/purchase/purchaser/create.tsx +++ b/packages/app-client/src/pages/purchase/purchaser/create.tsx @@ -30,6 +30,7 @@ import { generateShortId } from "@/utils/generateShortId"; import Taro from "@tarojs/taro"; import buildUrl from "@/utils/buildUrl"; import { SupplierWeightCalculator } from "@/utils/SupplierWeightCalculator"; +import _ from "lodash"; const defaultSupplierList: SupplierVO[] = [ { @@ -59,6 +60,16 @@ const defaultSupplierList: SupplierVO[] = [ }, ]; +// 添加修改标识的类型定义 +type ModifiedFlags = { + vehicle?: boolean; // 车辆信息是否修改 + farmer?: boolean; // 瓜农信息是否修改 + weigh?: boolean; // 称重信息是否修改 + package?: boolean; // 包装信息是否修改 + ticket?: boolean; // 票证信息是否修改 + cost?: boolean; // 人工辅料信息是否修改 +}; + export default hocAuth(function Page(props: CommonComponent) { const { router, role, setLoading } = props; @@ -103,6 +114,20 @@ export default hocAuth(function Page(props: CommonComponent) { console.log("purchaseOrder", purchaseOrder); + // 存储初始状态用于比较 + const [initialState, setInitialState] = useState<{ + orderVehicle?: BusinessAPI.PurchaseOrderCreateCmd["orderVehicle"]; + orderDealer?: BusinessAPI.PurchaseOrderCreateCmd["orderDealer"]; + orderSupplierList?: BusinessAPI.PurchaseOrderCreateCmd["orderSupplierList"]; + orderCostList?: BusinessAPI.PurchaseOrderCreateCmd["orderCostList"]; + orderPackageList?: BusinessAPI.PurchaseOrderCreateCmd["orderPackageList"]; + }>({}); + + // 添加修改标识状态 + const [modifiedFlags, setModifiedFlags] = useState({}); + + console.log("modifiedFlags", modifiedFlags); + const [step, setStep] = useState(); const [active, setActive] = useState(1); @@ -113,6 +138,9 @@ export default hocAuth(function Page(props: CommonComponent) { const [orderSupplierList, setOrderSupplierList] = useState(defaultSupplierList); const [orderCostList, setOrderCostList] = useState([]); + const [orderPackageList, setOrderPackageList] = useState< + BusinessAPI.OrderPackage[] + >([]); useEffect(() => { if (orderId) { @@ -137,12 +165,14 @@ export default hocAuth(function Page(props: CommonComponent) { setPurchaseOrder(purchaseOrder); setOrderVehicle(purchaseOrder.orderVehicle); + setInitialState((prev) => ({ + ...prev, + orderVehicle: _.cloneDeep(purchaseOrder.orderVehicle), + orderDealer: _.cloneDeep(purchaseOrder.orderDealer), + orderSupplierList: _.cloneDeep(purchaseOrder.orderSupplierList), + orderCostList: _.cloneDeep(purchaseOrder.orderCostList), + })); const orderSupplierList1 = purchaseOrder.orderSupplierList as any; - console.log( - "orderSupplierList1", - orderSupplierList1, - defaultSupplierId, - ); if (defaultSupplierId) { const index = orderSupplierList1.findIndex( (item: any) => item.supplierId === defaultSupplierId, @@ -179,6 +209,9 @@ export default hocAuth(function Page(props: CommonComponent) { .finally(() => { setLoading(false); }); + } else { + // 对于新订单,设置初始状态为空对象 + setInitialState({}); } }, []); @@ -207,6 +240,12 @@ export default hocAuth(function Page(props: CommonComponent) { ...prev!, orderVehicle: orderVehicle as BusinessAPI.OrderVehicle, })); + + // 检查车辆信息是否发生变化 + if (initialState.orderVehicle || orderVehicle) { + const isModified = !_.isEqual(initialState.orderVehicle, orderVehicle); + setModifiedFlags((prev) => ({ ...prev, vehicle: isModified })); + } }, [orderVehicle]); useEffect(() => { @@ -214,6 +253,12 @@ export default hocAuth(function Page(props: CommonComponent) { ...prev!, orderDealer: orderDealer as BusinessAPI.OrderDealer, })); + + // 检查经销商信息是否发生变化 + if (initialState.orderDealer || orderDealer) { + const isModified = !_.isEqual(initialState.orderDealer, orderDealer); + setModifiedFlags((prev) => ({ ...prev, vehicle: isModified })); // 注意:这里vehicle是正确的,因为dealer和vehicle属于同一组 + } }, [orderDealer]); useEffect(() => { @@ -221,8 +266,141 @@ export default hocAuth(function Page(props: CommonComponent) { ...prev!, orderCostList: orderCostList || [], })); + + // 检查成本列表是否发生变化 + if (initialState.orderCostList || orderCostList) { + const isModified = !_.isEqual(initialState.orderCostList, orderCostList); + setModifiedFlags((prev) => ({ ...prev, cost: isModified })); + } }, [orderCostList]); + useEffect(() => { + setPurchaseOrder((prev) => ({ + ...prev!, + orderPackageList: orderPackageList || [], + })); + + // 检查成本列表是否发生变化 + if (initialState.orderPackageList || orderPackageList) { + const isModified = !_.isEqual( + initialState.orderPackageList, + orderPackageList, + ); + setModifiedFlags((prev) => ({ ...prev, cost: isModified })); + } + }, [orderPackageList]); + + // 检查供应商信息是否发生变化的辅助函数 + const checkSupplierModified = ( + currentList: SupplierVO[], + initialList: SupplierVO[] | undefined, + ) => { + if (!initialList) { + // 如果没有初始状态,则检查当前列表是否有实质内容 + return currentList.some( + (supplier) => + supplier.supplierId || + supplier.name || + supplier.idCard || + supplier.bankCard || + supplier.phone || + supplier.emptyWeight || + supplier.totalWeight || + supplier.purchasePrice || + (supplier.orderPackageList && supplier.orderPackageList.length > 0), + ); + } + + return !_.isEqual(currentList, initialList); + }; + + // 当供应商列表改变时检查修改状态 + useEffect(() => { + const isModified = checkSupplierModified( + orderSupplierList, + initialState.orderSupplierList as any, + ); + + // 检查具体哪个部分被修改了 + if (isModified && initialState.orderSupplierList) { + const hasFarmerChanges = !_.isEqual( + orderSupplierList.map((s) => ({ + supplierId: s.supplierId, + name: s.name, + idCard: s.idCard, + bankCard: s.bankCard, + phone: s.phone, + })), + initialState.orderSupplierList.map((s) => ({ + supplierId: s.supplierId, + name: s.name, + idCard: s.idCard, + bankCard: s.bankCard, + phone: s.phone, + })), + ); + + const hasWeighChanges = !_.isEqual( + orderSupplierList.map((s) => ({ + emptyWeight: s.emptyWeight, + totalWeight: s.totalWeight, + purchasePrice: s.purchasePrice, + isPaper: s.isPaper, + })), + initialState.orderSupplierList.map((s) => ({ + emptyWeight: s.emptyWeight, + totalWeight: s.totalWeight, + purchasePrice: s.purchasePrice, + isPaper: s.isPaper, + })), + ); + + const hasPackageChanges = !_.isEqual( + orderSupplierList.map((s) => s.orderPackageList), + initialState.orderSupplierList.map((s) => s.orderPackageList), + ); + + const hasTicketChanges = !_.isEqual( + orderSupplierList.map((s) => ({ + emptyWeightImg: s.emptyWeightImg, + totalWeightImg: s.totalWeightImg, + wechatQr: s.wechatQr, + })), + initialState.orderSupplierList.map((s) => ({ + emptyWeightImg: s.emptyWeightImg, + totalWeightImg: s.totalWeightImg, + wechatQr: s.wechatQr, + })), + ); + + setModifiedFlags((prev) => ({ + ...prev, + farmer: hasFarmerChanges, + weigh: hasWeighChanges, + package: hasPackageChanges, + ticket: hasTicketChanges, + })); + } else if (isModified) { + // 如果没有初始状态但有修改,标记所有为已修改 + setModifiedFlags((prev) => ({ + ...prev, + farmer: true, + weigh: true, + package: true, + ticket: true, + })); + } else { + // 没有修改 + setModifiedFlags((prev) => ({ + ...prev, + farmer: false, + weigh: false, + package: false, + ticket: false, + })); + } + }, [orderSupplierList]); + if (step === undefined) { return; } @@ -266,6 +444,11 @@ export default hocAuth(function Page(props: CommonComponent) { // 车辆信息和经销商信息的保存 const saveVehicleAndDealerInfo = async () => { + // 如果没有修改过车辆信息,则不调用接口 + if (!modifiedFlags.vehicle) { + return true; + } + if (!purchaseOrder) { Toast.show("toast", { icon: "warn", @@ -287,6 +470,14 @@ export default hocAuth(function Page(props: CommonComponent) { }); if (data.success) { + // 保存成功后更新初始状态 + setInitialState((prev) => ({ + ...prev, + orderVehicle: _.cloneDeep(purchaseOrder.orderVehicle), + orderDealer: _.cloneDeep(purchaseOrder.orderDealer), + orderCostList: _.cloneDeep(purchaseOrder.orderCostList), + })); + setModifiedFlags((prev) => ({ ...prev, vehicle: false })); return true; } else { Toast.show("toast", { @@ -306,6 +497,14 @@ export default hocAuth(function Page(props: CommonComponent) { if (data.success) { setOrderId(data.data?.orderId); + // 保存成功后更新初始状态 + setInitialState((prev) => ({ + ...prev, + orderVehicle: _.cloneDeep(purchaseOrder.orderVehicle), + orderDealer: _.cloneDeep(purchaseOrder.orderDealer), + orderCostList: _.cloneDeep(purchaseOrder.orderCostList), + })); + setModifiedFlags((prev) => ({ ...prev, vehicle: false })); return true; } else { Toast.show("toast", { @@ -329,6 +528,18 @@ export default hocAuth(function Page(props: CommonComponent) { // 供应商信息的保存(基础信息,称重信息,包材信息) const saveSupplierInfo = async () => { + // 检查是否有任何供应商相关信息被修改 + const hasSupplierModification = + modifiedFlags.farmer || + modifiedFlags.weigh || + modifiedFlags.package || + modifiedFlags.ticket; + + // 如果没有任何供应商相关信息被修改,则不调用接口 + if (!hasSupplierModification) { + return true; + } + if (!purchaseOrder || !orderId) { Toast.show("toast", { icon: "warn", @@ -347,6 +558,19 @@ export default hocAuth(function Page(props: CommonComponent) { }); if (data.success) { + // 保存成功后更新初始状态 + setInitialState((prev) => ({ + ...prev, + orderSupplierList: _.cloneDeep(purchaseOrder.orderSupplierList), + })); + // 保存成功后重置所有供应商相关的修改标识 + setModifiedFlags((prev) => ({ + ...prev, + farmer: false, + weigh: false, + package: false, + ticket: false, + })); return true; } else { Toast.show("toast", { @@ -369,6 +593,11 @@ export default hocAuth(function Page(props: CommonComponent) { // 人工和辅料等保存 const saveCostInfo = async () => { + // 如果没有修改过人工辅料信息,则不调用接口 + if (!modifiedFlags.cost) { + return true; + } + if (!purchaseOrder || !orderId) { Toast.show("toast", { icon: "warn", @@ -388,6 +617,13 @@ export default hocAuth(function Page(props: CommonComponent) { }); if (data.success) { + // 保存成功后更新初始状态 + setInitialState((prev) => ({ + ...prev, + orderCostList: _.cloneDeep(purchaseOrder.orderCostList), + })); + // 保存成功后重置修改标识 + setModifiedFlags((prev) => ({ ...prev, cost: false })); return true; } else { Toast.show("toast", { @@ -551,11 +787,18 @@ export default hocAuth(function Page(props: CommonComponent) { { + setOrderVehicle(orderVehicle); + // 修改标识将在useEffect中自动更新 + }} orderDealer={orderDealer!} - setOrderDealer={setOrderDealer} + setOrderDealer={(orderDealer) => { + setOrderDealer(orderDealer); + // 修改标识将在useEffect中自动更新 + }} orderCostList={orderCostList} setOrderCostList={(costItemList: CostItem[]) => { + // 修改标识将在useEffect中自动更新 setOrderCostList(costItemList); }} /> @@ -620,10 +863,13 @@ export default hocAuth(function Page(props: CommonComponent) { temp[0].selected = true; setOrderSupplierList(temp); + // 修改标识将在useEffect中自动更新 }} onChange={(supplierVO: SupplierVO) => { orderSupplierList[index] = { ...item, ...supplierVO }; setOrderSupplierList([...orderSupplierList]); + console.log("supplierVO", supplierVO); + // 修改标识将在useEffect中自动更新 }} isLast={index === orderSupplierList.length - 1} selectedSupplierIds={selectedSupplierIds} @@ -650,6 +896,7 @@ export default hocAuth(function Page(props: CommonComponent) { return item; }), ]); + // 修改标识将在useEffect中自动更新 }} changeProduct={(productVO: BusinessAPI.ProductVO) => { setOrderSupplierList([ @@ -711,6 +958,7 @@ export default hocAuth(function Page(props: CommonComponent) { ], }; }); + // 修改标识将在useEffect中自动更新 }} isFirst={index === 0} key={item.orderSupplierId} @@ -723,6 +971,7 @@ export default hocAuth(function Page(props: CommonComponent) { onChange={(supplierVO: SupplierVO) => { orderSupplierList[index] = { ...item, ...supplierVO }; setOrderSupplierList([...orderSupplierList]); + // 修改标识将在useEffect中自动更新 }} /> ); @@ -749,6 +998,7 @@ export default hocAuth(function Page(props: CommonComponent) { onChange={(supplierVO: SupplierVO) => { orderSupplierList[index] = { ...item, ...supplierVO }; setOrderSupplierList([...orderSupplierList]); + // 修改标识将在useEffect中自动更新 }} /> ); @@ -763,6 +1013,7 @@ export default hocAuth(function Page(props: CommonComponent) { onChange={(supplierVO: SupplierVO) => { orderSupplierList[index] = { ...item, ...supplierVO }; setOrderSupplierList([...orderSupplierList]); + // 修改标识将在useEffect中自动更新 }} /> ); @@ -780,9 +1031,14 @@ export default hocAuth(function Page(props: CommonComponent) { }} ref={orderCostRef} value={orderCostList} - onChange={(costItemList: CostItem[]) => - setOrderCostList(costItemList) - } + onChange={(costItemList: CostItem[]) => { + // 修改标识将在useEffect中自动更新 + setOrderCostList(costItemList); + }} + emptyBoxList={orderPackageList} + onEmptyBoxChange={(emptyBoxList: BusinessAPI.OrderPackage[]) => { + setOrderPackageList(emptyBoxList); + }} /> )} diff --git a/packages/app-client/src/services/business/typings.d.ts b/packages/app-client/src/services/business/typings.d.ts index cc4376b..cd1eeb8 100644 --- a/packages/app-client/src/services/business/typings.d.ts +++ b/packages/app-client/src/services/business/typings.d.ts @@ -2576,7 +2576,7 @@ declare namespace BusinessAPI { /** 销售单价(元/个) */ boxSalePrice?: number; /** 箱子类型:1_本次使用;2_额外运输;3_已使用额外运输;4_车上剩余; */ - boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN" | "OWN"; + boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN" | "OWN" | "EMPTY"; }; type OrderRebate = { @@ -3362,6 +3362,8 @@ declare namespace BusinessAPI { orderSupplierList: OrderSupplier[]; /** 采购订单费用信息 */ orderCostList: OrderCost[]; + /** 采购订单空箱费用 */ + orderPackageList: OrderPackage[]; }; type PurchaseOrderDestroyCmd = { @@ -3552,6 +3554,8 @@ declare namespace BusinessAPI { orderSupplierList: OrderSupplier[]; /** 采购订单费用信息 */ orderCostList: OrderCost[]; + /** 采购订单空箱费用 */ + orderPackageList: OrderPackage[]; }; type PurchaseOrderWithdrawReviewCmd = { diff --git a/packages/app-client/src/types/typings.ts b/packages/app-client/src/types/typings.ts index 370db65..1084796 100644 --- a/packages/app-client/src/types/typings.ts +++ b/packages/app-client/src/types/typings.ts @@ -36,7 +36,7 @@ export interface BoxBrand { boxBrandName: string; boxBrandImage: string; boxBrandType: "OUR_BOX" | "FARMER_BOX" | "THIRD_PARTY_BOX" | "GIFT_BOX"; - boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN" | "OWN" | "DEFAULT"; + boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN" | "OWN" | "EMPTY"; boxSpecList: BoxSpec[]; } diff --git a/packages/app-client/src/utils/SupplierWeightCalculator.ts b/packages/app-client/src/utils/SupplierWeightCalculator.ts index c098c83..6072734 100644 --- a/packages/app-client/src/utils/SupplierWeightCalculator.ts +++ b/packages/app-client/src/utils/SupplierWeightCalculator.ts @@ -69,19 +69,19 @@ export class SupplierWeightCalculator { if (!supplier.isPaper) { // 如果不是纸箱包装,直接使用原始重量(kg转斤) - supplier.grossWeight = new Decimal(supplier.totalWeight) - .sub(supplier.emptyWeight) + supplier.grossWeight = new Decimal(supplier.totalWeight || 0) + .sub(supplier.emptyWeight || 0) .mul(2) .toNumber(); - supplier.netWeight = new Decimal(supplier.grossWeight) + supplier.netWeight = new Decimal(supplier.grossWeight || 0) .sub(usedBoxesWeight) .sub(extraUsedBoxesWeight) .toNumber(); previousTotalWeight = supplier.totalWeight; - supplier.invoiceAmount = new Decimal(supplier.netWeight) - .mul(supplier.purchasePrice) + supplier.invoiceAmount = new Decimal(supplier.netWeight || 0) + .mul(supplier.purchasePrice || 0) .toNumber(); continue; }