ERPTurbo_Client/packages/app-client/src/components/purchase/module/OrderCost.tsx
shenyifei 19cf99863f style(components): 调整组件样式和布局
- 统一调整多个 Picker 组件的内边距类名从 p-4 到 p-2.5
- 修改 OrderCost 和 OrderPackage 中图片容器的尺寸类名以适应全屏显示
- 移除 TicketUpload 中冗余的查看计算公式相关 UI 元素
- 更新 DealerInfoSection 中 DealerPicker 的手动输入属性为 false
- 优化 PackagingCostSection 中固定成本选择器的可见性控制逻辑
- 调整 SupplierPicker 内部列表项的内边距类名
- 移除 center 页面中角色选中状态的勾选图标显示逻辑
- 格式化 invoice 页面中的导入语句以提高可读性
2025-11-19 22:43:11 +08:00

1534 lines
52 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 { ScrollView, View } from "@tarojs/components";
import { Icon } from "@/components";
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
import { business } from "@/services";
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";
import { Decimal } from "decimal.js";
// 定义ref暴露的方法接口
export interface OrderCostRef {
validate: () => boolean;
}
export interface IOrderCostProps {
value: CostItem[];
supplierVO: SupplierVO;
onChange?: (costItemList: CostItem[]) => void;
emptyBoxList: BusinessAPI.OrderPackage[];
onEmptyBoxChange?: (orderPackageList: BusinessAPI.OrderPackage[]) => void;
onAdd: () => void;
costItemVOList: BusinessAPI.CostItemVO[];
foreman: string;
setForeman: (foreman: string) => void;
}
export default forwardRef<OrderCostRef, IOrderCostProps>(
function OrderCost(IOrderCostProps, ref) {
const {
value,
supplierVO,
onChange,
onAdd,
onEmptyBoxChange,
emptyBoxList: defaultEmptyBoxList,
costItemVOList,
foreman,
setForeman,
} = IOrderCostProps;
console.log("defaultEmptyBoxList", defaultEmptyBoxList);
const [costItemList, setCostItemList] = useState<CostItem[]>();
// 新增状态:跟踪每种纸箱类型的启用状态
// 0: 未选, 1: 选择是
const [packageTypeEnabled, setPackageTypeEnabled] = useState({
EMPTY: 0,
});
// 空箱相关状态
const [emptyBoxList, setEmptyBoxList] = useState<
BusinessAPI.OrderPackage[]
>([]);
const [boxBrandList, setBoxBrandList] = useState<BoxBrand[]>();
const [emptyBoxCostItem, setEmptyBoxCostItem] = useState<CostItem>();
// 批量添加空箱相关状态
const [selectedBrand, setSelectedBrand] = useState<BoxBrand | null>(null);
const [productCounts, setProductCounts] = useState<Map<string, number>>(
new Map(),
);
const [showBatchModal, setShowBatchModal] = useState(false);
// 编辑空箱弹窗状态
const [showEditModal, setShowEditModal] = useState(false);
const [editingItem, setEditingItem] = useState<BoxBrand | null>(null);
// 处理纸箱类型启用状态切换
const togglePackageType = (
type: BusinessAPI.OrderPackage["boxType"],
value: number, // 0: 未选, 1: 选择是, 2: 选择否
) => {
setPackageTypeEnabled((prev) => ({
...prev,
[type]: value,
}));
};
// 当空箱列表变化时,生成空箱费固定费用项
useEffect(() => {
// 检查是否启用了空箱
if (packageTypeEnabled.EMPTY === 1) {
Decimal.set({
precision: 20,
rounding: 0, // 向下舍入(更保守的计算方式)
toExpNeg: -7,
toExpPos: 21,
});
// 计算空箱总数量
const totalEmptyBoxCount = emptyBoxList.reduce(
(sum, pkg) => new Decimal(sum).plus(pkg.boxCount || 0).toNumber(),
0,
);
// 如果有空箱且还没有空箱费项目,则添加空箱费固定费用项
if (totalEmptyBoxCount > 0) {
setCostItemList((prev) => {
if (!prev) return prev;
// 检查是否已存在空箱费项目
const hasEmptyBoxFee = prev.some(
(item) =>
item.name === "空箱费" && item.costType === "FIXED_COST",
);
const price = emptyBoxList?.reduce(
(sum, pkg) =>
new Decimal(sum)
.plus(
new Decimal(pkg.boxCount || 0).mul(pkg.boxCostPrice || 0),
)
.toNumber(),
0,
);
// 如果不存在空箱费项目,则添加
if (!hasEmptyBoxFee) {
const emptyBoxFeeItem: CostItem = {
orderCostId: generateShortId(),
itemId: emptyBoxCostItem?.itemId || "",
name: "空箱费",
price: price,
unit: "项",
selected: true,
count: 1,
payerType: "US",
principal: "",
costType: "FIXED_COST",
requireQuantityAndPrice: false,
};
return [...prev, emptyBoxFeeItem];
} else {
// 如果已存在空箱费项目,更新其数量
return prev.map((item) => {
if (item.name === "空箱费" && item.costType === "FIXED_COST") {
return {
...item,
price: price,
count: 1,
};
}
return item;
});
}
});
}
}
// 通知父组件空箱列表变化
onEmptyBoxChange?.(
emptyBoxList?.map((item) => {
return {
...item,
id: generateShortId(),
};
}),
);
}, [emptyBoxList, packageTypeEnabled.EMPTY]);
// 工头姓名错误状态
const [foremanError, setForemanError] = useState<boolean>(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 (costItemVOList) => {
if (costItemVOList) {
setEmptyBoxCostItem({
orderCostId: generateShortId(),
count: 0,
selected: true,
...costItemVOList?.find(
(item) => item.name === "空箱费" && item.costType === "FIXED_COST",
)!,
});
// 人工辅料选中
const initialList =
costItemVOList
?.filter(
(item) =>
item.costType === "HUMAN_COST" ||
item.costType === "PACKAGING_MATERIALS",
)
.map((item) => {
// 查找是否在purchaseOrder中已有该item的记录
const existingItem = value?.find(
(costItem) => costItem.itemId === item.itemId,
);
return {
orderCostId: existingItem?.orderCostId || generateShortId(),
itemId: item.itemId,
name: item.name,
price: item.price,
unit: item.unit,
selected: existingItem
? existingItem.selected && existingItem.count > 0
: false,
count: existingItem ? existingItem.count : 1,
payerType: existingItem ? existingItem.payerType : undefined,
principal: existingItem ? existingItem.principal : "",
costType: item.costType,
requireQuantityAndPrice: existingItem
? existingItem.requireQuantityAndPrice
: false,
};
}) || [];
setCostItemList([
...(initialList || []),
...(value
?.filter(
(item) =>
item.costType === "WORKER_ADVANCE" ||
item.costType === "PRODUCTION_ADVANCE" ||
item.costType === "FIXED_COST",
)
.map((item) => {
return {
orderCostId: item.orderCostId,
itemId: item.itemId,
name: item.name,
price: item.price,
unit: item.unit,
selected: item.selected,
count: item.count,
payerType: item.payerType,
principal: item.principal,
costType: item.costType,
requireQuantityAndPrice: item.requireQuantityAndPrice,
};
}) || []),
]);
}
};
// 当传入的value发生变化时重新初始化列表
useEffect(() => {
init(costItemVOList).then();
initBoxBrandList().then();
setEmptyBoxList(defaultEmptyBoxList);
if (defaultEmptyBoxList.length > 0) {
// 根据当前供应商确定需要检查的纸箱类型
let requiredTypes: string[] = ["EMPTY"];
requiredTypes.map((type) => {
setPackageTypeEnabled((prev) => {
return {
...prev,
[type]: defaultEmptyBoxList.some((item) => item.boxType === type)
? 1
: 0,
};
});
});
}
}, []);
// 当内部状态发生变化时,通知父组件更新
useEffect(() => {
if (costItemList && onChange) {
// 更新所有启用且费用承担方为"我方"的人工费用项的工头姓名
const updatedList = costItemList.map((item) => {
if (
item.costType === "HUMAN_COST" &&
item.selected &&
item.payerType === "US"
) {
return { ...item, principal: foreman };
}
return item;
});
onChange(updatedList);
}
}, [costItemList, foreman]);
// 错误状态
const [countError, setCountError] = useState<{ [key: string]: boolean }>(
{},
);
const [payerTypeError, setPayerTypeError] = useState<{
[key: string]: boolean;
}>({});
// 设置人工项目选择状态
const setArtificialSelect = (id: string, selected: boolean) => {
if (!costItemList) return;
const newList = costItemList.map((item) =>
item.orderCostId === id ? { ...item, selected } : item,
);
setCostItemList(newList);
};
// 处理数量变化
const handleCountChange = (id: string, value: number) => {
if (!costItemList) return;
const newList = costItemList.map((item) =>
item.orderCostId === id ? { ...item, count: value } : item,
);
setCostItemList(newList);
// 校验数量
if (costItemList.find((item) => item.orderCostId === id)?.selected) {
validateCount(id, value);
}
};
// 校验数量
const validateCount = (id: string, value: number) => {
const isValid = value > 0;
setCountError((prev) => ({
...prev,
[id]: !isValid,
}));
return isValid;
};
// 校验费用承担方
const validatePayerType = (id: string, value: CostItem["payerType"]) => {
const isValid = value === "US" || value === "OTHER";
setPayerTypeError((prev) => ({
...prev,
[id]: !isValid,
}));
return isValid;
};
// 校验工头姓名
const validatePrincipal = (value: string) => {
const isValid = value.trim().length > 0;
setForemanError(!isValid);
return isValid;
};
// 处理费用承担方变化
const handlePayerTypeChange = (
id: string,
value: CostItem["payerType"],
) => {
if (!costItemList) return;
const newList = costItemList.map((item) =>
item.orderCostId === id ? { ...item, payerType: value } : item,
);
setCostItemList(newList);
// 校验费用承担方
if (costItemList.find((item) => item.orderCostId === id)?.selected) {
validatePayerType(id, value);
}
};
// 处理工头姓名变化
const handleForemanChange = (value: string) => {
setForeman(value);
// 如果有启用且费用承担方为"我方"的项目,则校验工头姓名
const enabledUsItems = costItemList?.filter(
(item) =>
item.costType === "HUMAN_COST" &&
item.selected &&
item.payerType === "US",
);
if (enabledUsItems && enabledUsItems.length > 0) {
validatePrincipal(value);
}
};
// 失去焦点时校验数量
const handleCountBlur = (id: string, value: number) => {
validateCount(id, value);
};
// 失去焦点时校验工头姓名
const handlePrincipalBlur = (value: string) => {
validatePrincipal(value);
};
// 对外暴露的校验方法
const validate = () => {
if (!costItemList) return true;
let isValid = true;
// 校验人工和辅料费用
costItemList.forEach((item) => {
if (item.selected) {
// 校验数量
if (!validateCount(item.orderCostId, item.count)) {
isValid = false;
}
if (item.costType === "HUMAN_COST") {
// 校验费用承担方
if (!validatePayerType(item.orderCostId, item.payerType)) {
isValid = false;
}
}
}
});
// 校验总的工头姓名(如果有启用且费用承担方为"我方"的项目)
const enabledUsItems = costItemList.filter(
(item) =>
item.costType === "HUMAN_COST" &&
item.selected &&
item.payerType === "US",
);
isValid = true;
if (enabledUsItems.length > 0) {
if (!validatePrincipal(foreman)) {
isValid = false;
}
}
if (!isValid) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "请完善人工信息后再进行下一步操作",
});
}
return isValid;
};
useImperativeHandle(ref, () => ({
validate,
}));
// 获取指定类型的项目列表
const getItemsByCostType = (costType: string) => {
return costItemList?.filter((item) => item.costType === costType) || [];
};
// 渲染项目列表
const renderItemList = (items: CostItem[], type: string) => {
return items.map((item) => (
<View key={item.orderCostId}>
<View className={"flex flex-col gap-2.5 rounded-lg bg-white p-2.5"}>
<View className={"flex flex-row justify-between"}>
<View className="block text-sm font-normal text-[#000000]">
{item.name}
</View>
<Checkbox
className={"flex flex-row items-center"}
checked={item.selected}
onChange={(checked) => {
setArtificialSelect(item.orderCostId, checked);
}}
>
<View className={"text-sm font-normal text-[#000000]"}>
</View>
</Checkbox>
</View>
{item.selected && type === "HUMAN_COST" && (
<View>
{/* 费用承担方改为按钮形式参考OrderPackage中的样式 */}
<View className="flex items-center justify-between">
<View className="flex-shrink-0 text-sm">:</View>
<View className="flex gap-2">
<Button
size="small"
type={item.payerType === "US" ? "primary" : "default"}
onClick={() => {
handlePayerTypeChange(item.orderCostId, "US");
}}
>
</Button>
<Button
size="small"
type={item.payerType === "OTHER" ? "primary" : "default"}
onClick={() => {
handlePayerTypeChange(item.orderCostId, "OTHER");
}}
>
</Button>
</View>
</View>
{payerTypeError[item.orderCostId] && item.selected && (
<View className="mt-1 text-xs text-red-500">
</View>
)}
</View>
)}
{item.selected && (
<View>
<View className="flex items-center justify-between gap-2">
<View className="flex-shrink-0 text-sm">:</View>
<View className="flex items-center">
<View
className="rounded-l-button flex !size-12 flex-none items-center justify-center bg-gray-200 text-sm"
onClick={() => {
const count = item.count || 1;
handleCountChange(
item.orderCostId,
Math.max(1, count - 1),
);
}}
>
<Icon name="minus" size={20} />
</View>
<View
className={`flex h-12 w-full shrink items-center border-4 border-gray-200`}
>
<Input
type="number"
value={item.count.toString()}
align={"center"}
placeholder={"用了多少"}
onChange={(value) => {
handleCountChange(item.orderCostId, Number(value));
}}
formatter={(value) =>
Math.max(1, Number(value)).toString()
}
onBlur={() =>
handleCountBlur(item.orderCostId, item.count)
}
/>
</View>
<View
className="rounded-r-button flex !size-12 flex-none items-center justify-center bg-gray-200 text-sm"
onClick={() => {
const count = item.count || 1;
handleCountChange(item.orderCostId, count + 1);
}}
>
<Icon name="plus" size={20} />
</View>
<View className={"ml-2.5 text-sm text-gray-500"}>
{item.unit}
</View>
</View>
</View>
{countError[item.orderCostId] && item.selected && (
<View className="mt-1 text-xs text-red-500">
</View>
)}
</View>
)}
</View>
</View>
));
};
// 检查是否有人工费用项被启用且费用承担方为"我方"
const shouldShowPrincipalInput = () => {
return (
costItemList?.some(
(item) =>
item.costType === "HUMAN_COST" &&
item.selected &&
item.payerType === "US",
) || false
);
};
// 处理批量添加时的品牌选择
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<string, number>();
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) => {
console.log("removeEmptyBoxInfo", boxBrandId);
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 (
<View
className="mb-2.5 overflow-hidden rounded-xl border border-gray-200 bg-white p-2.5 shadow-sm"
key={item.id}
>
{/* 品牌背景水印 */}
{brandInfo?.boxBrandImage && (
<View className="absolute top-2 right-2 opacity-10">
<Image
src={brandInfo.boxBrandImage}
className="h-full w-full"
mode="aspectFit"
/>
</View>
)}
{/* 品牌信息和操作按钮放在同一行 */}
<View className="mb-3 flex items-start justify-between">
<View className="flex items-center">
{brandInfo?.boxBrandImage && (
<View className="mr-3 h-10 w-10 overflow-hidden rounded-lg border border-gray-200">
<Image
src={brandInfo.boxBrandImage}
className="h-full w-full"
mode="aspectFit"
/>
</View>
)}
<View>
<View className="text-lg font-bold text-gray-800">
{item.boxBrandName || "未选择品牌"}
</View>
</View>
</View>
{/* 操作按钮与品牌信息放在同一行 */}
<View className="flex gap-2">
<View
className="cursor-pointer rounded-lg bg-blue-100 px-3 py-1 text-xs font-medium text-blue-600 transition-colors hover:bg-blue-200"
onClick={() => handleEditItem(item)}
>
</View>
<View
className="cursor-pointer rounded-lg bg-red-100 px-3 py-1 text-xs font-medium text-red-600 transition-colors hover:bg-red-200"
onClick={() => {
console.log("removeEmptyBoxInfo", item.boxBrandId);
removeEmptyBoxInfo(item.boxBrandId);
}}
>
</View>
</View>
</View>
{/* 详细信息展示,按分类显示 */}
<View className="space-y-2">
{item.boxSpecList &&
item.boxSpecList.map((boxSpec, boxIndex) => (
<View
key={boxIndex}
className="rounded-lg bg-gray-50 p-3 text-sm text-gray-600"
>
<View className="mb-1 font-medium text-gray-700">
{boxSpec.boxSpecName}
</View>
<View>
{boxSpec.boxProductList
.map(
(detail) =>
`${detail.boxProductName}${detail.boxCount}`,
)
.join("")}
</View>
</View>
))}
</View>
</View>
);
};
// 渲染批量添加空箱弹窗
const renderBatchAddModal = () => {
// 检查是否至少有一个产品的数量大于0
const hasAnyProductWithCount = selectedBrand
? Array.from(productCounts.values()).some((count) => count > 0)
: false;
return (
<Popup
duration={150}
style={{
minHeight: "auto",
}}
visible={showBatchModal}
position="bottom"
onClose={() => {
setShowBatchModal(false);
setSelectedBrand(null);
setProductCounts(new Map());
}}
title={"批量添加空箱"}
round
>
<View className="p-2.5">
<ScrollView
scrollY
style={{
height: "65vh",
width: "100%",
}}
>
{/* 品牌选择 */}
<View className="mb-4">
<View className="mb-2 text-sm text-gray-600"></View>
<ScrollView className="mb-2.5" scrollX>
<View className="flex w-fit flex-row gap-2.5">
{boxBrandList
?.filter((item) => item.boxBrandType !== "FARMER_BOX")
?.map((boxBrand) => (
<View
key={boxBrand.id}
className={
"flex flex-col items-center justify-center"
}
onClick={() => handleBatchBrandSelect(boxBrand)}
>
<View
className={classNames(
"border-primary box-content !size-16 overflow-hidden rounded-xl border-4 object-cover",
{
"border-primary":
selectedBrand?.id === boxBrand.id,
"border-transparent":
selectedBrand?.id !== boxBrand.id,
},
)}
>
<Image
src={boxBrand.boxBrandImage}
className="h-full w-full"
mode={"aspectFill"}
alt={boxBrand.boxBrandImage}
/>
</View>
<View className="text-center text-xs">
{boxBrand.boxBrandName}
</View>
</View>
))}
</View>
</ScrollView>
</View>
{/* 未选择品牌时的提示 */}
{!selectedBrand && (
<View className="mb-4 rounded-lg bg-yellow-50 p-4 text-center">
<View className="text-yellow-800"></View>
</View>
)}
{/* 产品展示 */}
{selectedBrand && (
<View className="mb-4">
<View className="mb-2 text-sm text-gray-600">
+/- 0使
</View>
{selectedBrand.boxSpecList?.map((boxSpec) => (
<View key={boxSpec.id} className="mb-4">
<View className="mb-2 text-base font-medium">
{boxSpec.boxSpecName}
</View>
<View className="space-y-3">
{boxSpec.boxProductList.map((boxProduct) => {
const currentCount =
productCounts.get(boxProduct.id) || 0;
return (
<View
key={boxProduct.id}
className="flex items-center justify-between rounded-lg bg-gray-50 p-3"
>
<View className="text-gray-800">
{boxProduct.boxProductName}
</View>
<View className="flex items-center">
<View
className="flex h-8 w-8 items-center justify-center rounded-l bg-gray-200"
onClick={() => {
handleProductCountChange(
boxProduct.id,
Math.max(0, currentCount - 1),
);
}}
>
<Icon name="minus" size={16} />
</View>
<View className="flex h-8 w-12 items-center justify-center border-y border-gray-200">
<Input
type="number"
value={currentCount.toString()}
align={"center"}
className="!h-8 !w-12 !p-0 !text-center"
onChange={(value) => {
const num = Number(value);
if (!Number.isNaN(num) && num >= 0) {
handleProductCountChange(
boxProduct.id,
num,
);
}
}}
/>
</View>
<View
className="flex h-8 w-8 items-center justify-center rounded-r bg-gray-200"
onClick={() => {
handleProductCountChange(
boxProduct.id,
currentCount + 1,
);
}}
>
<Icon name="plus" size={16} />
</View>
<View className="ml-2 text-gray-800"></View>
</View>
</View>
);
})}
</View>
</View>
))}
</View>
)}
</ScrollView>
{/* 底部按钮 */}
<View className="flex gap-2 pt-4">
<View className="flex-1">
<Button
type={"default"}
size={"large"}
block
onClick={() => {
setShowBatchModal(false);
setSelectedBrand(null);
setProductCounts(new Map());
}}
>
</Button>
</View>
<View className="flex-1">
<Button
type={"primary"}
size={"large"}
block
disabled={!selectedBrand || !hasAnyProductWithCount}
onClick={addBatchEmptyBoxInfo}
>
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
</Popup>
);
};
// 渲染编辑空箱弹窗
const renderEditModal = () => {
if (!editingItem) return null;
// 获取品牌信息
const brandInfo = boxBrandList?.find(
(brand) => brand.boxBrandId === editingItem.boxBrandId,
);
return (
<Popup
duration={150}
style={{
minHeight: "auto",
}}
visible={showEditModal}
position="bottom"
onClose={() => setShowEditModal(false)}
title={"编辑空箱信息"}
round
>
<View className="p-2.5">
<ScrollView
scrollY
style={{
height: "65vh",
width: "100%",
}}
>
{/* 品牌信息 */}
<View className="mb-4">
<View className="mb-2 text-sm text-gray-600"></View>
<View className="flex items-center rounded-lg bg-gray-50 p-3">
{brandInfo?.boxBrandImage && (
<View className="border-primary mr-3 h-16 w-16 overflow-hidden rounded-xl border-4 object-cover">
<Image
src={brandInfo.boxBrandImage}
className="h-full w-full"
mode="aspectFill"
/>
</View>
)}
<View className="text-base font-medium text-gray-800">
{editingItem.boxBrandName}
</View>
</View>
</View>
{/* 详细信息 */}
<View className="mb-4">
<View className="mb-2 text-sm text-gray-600"></View>
<View className="space-y-3">
{editingItem?.boxSpecList &&
editingItem.boxSpecList.map((boxSpec, specId) => (
<View key={specId} className="rounded-lg bg-gray-50 p-3">
<View className="mb-2 text-base font-medium text-gray-800">
{boxSpec.boxSpecName}
</View>
<View className="space-y-2">
{boxSpec.boxProductList.map((detail, detailIndex) => {
const currentCount = detail?.boxCount || 0;
return (
<View
key={detailIndex}
className="flex items-center justify-between py-1"
>
<View className="text-gray-600">
{detail.boxProductName}
</View>
<View className="flex items-center">
<View
className="flex h-8 w-8 items-center justify-center rounded-l bg-gray-200"
onClick={() => {
const newCount = Math.max(
0,
currentCount - 1,
);
updateEditingItemCount(
specId,
detailIndex,
newCount,
);
}}
>
<Icon name="minus" size={16} />
</View>
<View className="flex h-8 w-12 items-center justify-center border-y border-gray-200">
<Input
type="number"
value={currentCount.toString()}
align={"center"}
className="!h-8 !w-12 !p-0 !text-center"
onChange={(value) => {
const num = Number(value);
if (!Number.isNaN(num) && num >= 0) {
updateEditingItemCount(
specId,
detailIndex,
num,
);
}
}}
/>
</View>
<View
className="flex h-8 w-8 items-center justify-center rounded-r bg-gray-200"
onClick={() => {
updateEditingItemCount(
specId,
detailIndex,
currentCount + 1,
);
}}
>
<Icon name="plus" size={16} />
</View>
<View className="ml-2 font-medium text-gray-800">
</View>
</View>
</View>
);
})}
</View>
</View>
))}
</View>
</View>
</ScrollView>
{/* 底部按钮 */}
<View className="flex gap-2 pt-4">
<View className="flex-1">
<Button
size={"large"}
type={"default"}
block
onClick={() => setShowEditModal(false)}
>
</Button>
</View>
<View className="flex-1">
<Button
type="primary"
size={"large"}
block
onClick={saveEditedItem}
>
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
</Popup>
);
};
// 获取空箱列表
const emptyBoxes = convertOrderPackagesToBoxBrands(emptyBoxList);
return (
<View className="flex flex-1 flex-col gap-2.5 bg-[#D1D5DB] p-2.5 pt-2.5">
<View className={"flex flex-1 flex-col gap-2.5"}>
<View className="text-sm font-bold"></View>
<View className="flex items-center rounded-lg border border-blue-200 bg-blue-50 p-2.5">
<Icon
className={"mr-1"}
name="circle-info"
color={"var(--color-blue-700)"}
size={18}
/>
<View className={"text-sm text-blue-700"}></View>
</View>
{renderItemList(getItemsByCostType("HUMAN_COST"), "HUMAN_COST")}
{/* 总的工头姓名输入框 */}
{shouldShowPrincipalInput() && (
<View>
<View className="mb-1 text-sm font-medium"></View>
<View
className={`flex h-12 items-center rounded-md px-3 ${foremanError ? "border-2 border-red-500 bg-red-100" : "border-2 border-gray-300 bg-white"}`}
>
<Input
className="text-base"
type="text"
placeholder={"工头的名字"}
value={foreman}
onChange={(value) => {
handleForemanChange(value);
}}
onBlur={() => handlePrincipalBlur(foreman)}
/>
</View>
{foremanError && (
<View className="mt-1 text-xs text-red-500">
</View>
)}
</View>
)}
</View>
<View className={"flex flex-1 flex-col gap-2.5"}>
<View className="text-sm font-bold"></View>
<View className="flex items-center rounded-lg border border-blue-200 bg-blue-50 p-2.5">
<Icon
className={"mr-1"}
name="circle-info"
color={"var(--color-blue-700)"}
size={18}
/>
<View className={"text-sm text-blue-700"}></View>
</View>
{renderItemList(
getItemsByCostType("PACKAGING_MATERIALS"),
"PACKAGING_MATERIALS",
)}
</View>
<View className={"flex flex-1 flex-col gap-2.5"}>
<View className="text-sm font-bold"></View>
<View className="flex flex-col gap-2.5 rounded-lg bg-white p-2.5 shadow-sm">
<View className="flex items-center justify-between">
<View className="text-sm"></View>
<View className="flex flex-shrink-0 gap-2">
<Checkbox
className={"flex flex-row items-center"}
checked={packageTypeEnabled.EMPTY === 1}
onChange={(checked) => {
togglePackageType("EMPTY", checked ? 1 : 0);
if (!checked) {
setEmptyBoxList([]);
}
}}
>
<View className={"text-sm font-normal text-[#000000]"}>
</View>
</Checkbox>
</View>
</View>
{/* 空箱信息展示 */}
{packageTypeEnabled.EMPTY === 1 && (
<View>
{emptyBoxes.length > 0 ? (
emptyBoxes.map((item) => renderEmptyBoxItem(item))
) : (
<View className="mb-4 rounded-lg bg-gray-50 p-4 text-center text-gray-500">
</View>
)}
<Button
icon={<Icon name={"plus"} size={20} />}
type={"primary"}
size={"large"}
fill={"outline"}
block
className="border-primary text-primary flex w-full items-center justify-center !border-2 !bg-white"
onClick={() => {
setShowBatchModal(true);
}}
>
<View></View>
</Button>
</View>
)}
</View>
</View>
{/* 只有当用户选择"否"时才显示添加按钮 */}
{!supplierVO.isLast && (
<Button
icon={<Icon name={"plus"} size={20} />}
type={"primary"}
size={"xlarge"}
fill={"outline"}
block
className="border-primary text-primary flex w-full items-center justify-center !border-4 !bg-white"
onClick={() => {
onAdd();
}}
>
<View></View>
</Button>
)}
{/* 批量添加弹窗 */}
{renderBatchAddModal()}
{/* 编辑弹窗 */}
{renderEditModal()}
</View>
);
},
);