ERPTurbo_Client/packages/app-client/src/components/order/package/PackageCreate.tsx
shenyifei cb24afa3ee feat(message): 实现消息中心功能并优化UI交互
- 集成消息服务API,实现消息列表分页加载功能
- 添加消息未读数量实时更新,每30秒自动刷新一次
- 实现消息标记已读、删除消息等操作功能
- 优化消息界面UI,支持无限滚动加载更多消息
- 添加消息内容预览和跳转到相关业务单据功能
- 在个人中心页面增加调试模式开关快捷入口
- 修复订单车辆组件中草帘价格验证逻辑问题
- 更新供应商称重模块,新增计价方式和定金支付验证
- 调整包装创建页面品牌选择样式布局
- 更新应用版本号至v0.0.76
- 集成经销商对账记录等相关业务服务模块
2026-01-15 16:26:15 +08:00

427 lines
14 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 {
Button,
Image,
Input,
Popup,
SafeArea,
Toast,
} from "@nutui/nutui-react-taro";
import { Icon } from "@/components";
import classNames from "classnames";
import { convertBoxBrandToOrderPackages } from "@/utils";
import { useEffect, useState } from "react";
import { BoxBrand } from "@/types/typings";
interface IPackageCreateProps {
visible: boolean;
// 添加编辑模式的支持
editMode?: boolean;
onClose: () => void;
// 选中的纸箱品牌,编辑的时候使用
boxBrand?: BoxBrand;
// 全部纸箱品牌列表
boxBrandList: BoxBrand[];
orderPackageList: BusinessAPI.OrderPackage[];
onSave: (orderPackageList: BusinessAPI.OrderPackage[]) => void;
boxType: BusinessAPI.OrderPackage["boxType"];
}
export default function PackageCreate(props: IPackageCreateProps) {
const {
visible,
editMode = false,
boxBrand,
boxBrandList,
orderPackageList,
onClose,
onSave,
boxType,
} = props;
// 批量添加纸箱相关状态
const [selectedBrand, setSelectedBrand] = useState<BoxBrand | null>(null);
const [productCounts, setProductCounts] = useState<Map<string, number>>(
new Map(),
); // 产品数量映射
// 获取费用列表
useEffect(() => {
if (visible) {
// 如果是编辑模式并且有初始数据,在获取费用列表后设置初始状态
if (editMode && boxBrand) {
console.log("boxBrand", boxBrand);
// 查找对应的费用类型
const brand = boxBrandList?.find(
(c) => c.boxBrandId === boxBrand.boxBrandId,
);
if (brand) {
setSelectedBrand(brand);
// 初始化费用项目数量
const initialCounts = new Map<string, number>();
brand.boxSpecList?.forEach((boxSpec) => {
boxSpec.boxProductList.forEach((product) => {
const count = boxBrand.boxSpecList
.find((pkg) => pkg.boxSpecId === product.boxSpecId)
?.boxProductList.find(
(pkg) => pkg.boxProductId === product.boxProductId,
)?.boxCount;
initialCounts.set(product.boxProductId, count || 0);
});
});
setProductCounts(initialCounts);
}
}
}
}, [visible, editMode, boxBrandList]);
// 批量添加包装信息
const addBatchPackageInfo = () => {
if (!selectedBrand) {
return;
}
// 使用convertBoxBrandToOrderPackages转换数据
const newOrderPackages = convertBoxBrandToOrderPackages(
selectedBrand,
boxType,
);
// 过滤掉数量为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.boxProductId) || 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.boxProductId) || 0 : 0;
const orderPackage = orderPackageList.find((pkg) => {
return (
pkg.boxType === boxType && pkg.boxProductId === product?.boxProductId
);
});
return {
...(orderPackage || pkg)!,
boxCount: count,
};
});
onSave([
...(orderPackageList.filter(
(pkg) =>
pkg.boxType !== boxType ||
(pkg.boxType === boxType &&
pkg.boxBrandId !== selectedBrand?.boxBrandId),
) || []),
...updatedOrderPackages,
]);
// 重置选择并关闭弹窗
setSelectedBrand(null);
setProductCounts(new Map());
onClose();
};
// 处理批量添加时的品牌选择
const handleBatchBrandSelect = (brand: BoxBrand) => {
// 检查当前boxType下是否已存在该品牌
const isBrandAlreadySelected = orderPackageList.some(
(pkg) => pkg.boxType === boxType && 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 renderBrandSelection = () => {
// 编辑模式下不允许更改费用类型
if (editMode) {
return (
<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">
{selectedBrand?.boxBrandImage && (
<View className="border-primary mr-3 h-16 w-16 overflow-hidden rounded-xl border-4 object-cover">
<Image
src={selectedBrand.boxBrandImage}
className="h-full w-full"
mode="aspectFill"
/>
</View>
)}
<View className="text-base font-medium text-gray-800">
{selectedBrand?.boxBrandName}
</View>
</View>
</View>
);
}
return (
<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) => {
return (
item.boxBrandType === "THIRD_PARTY_BOX" ||
item.boxBrandType === "FARMER_BOX" ||
item.boxBrandType === "OUR_BOX"
);
})
?.map((boxBrand) => (
<View
key={boxBrand.id}
className={"flex flex-col items-center justify-center gap-1"}
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="aspectFit"
alt={boxBrand.boxBrandImage}
/>
</View>
<View className="text-center text-xs text-nowrap">
{boxBrand.boxBrandName}
</View>
</View>
))}
</View>
</ScrollView>
</View>
);
};
return (
<Popup
duration={150}
style={{
minHeight: "auto",
}}
visible={visible}
position="bottom"
title={editMode ? "批量编辑纸箱" : "批量添加纸箱"}
onClose={() => {
onClose();
setSelectedBrand(null);
setProductCounts(new Map());
}}
onOverlayClick={onClose}
lockScroll
>
<View className="p-2.5">
<ScrollView
scrollY
style={{
height: "65vh",
width: "100%",
}}
>
{renderBrandSelection()}
{/* 未选择品牌时的提示 */}
{!selectedBrand && !editMode && (
<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.boxProductId) || 0;
// 计算总重量
const totalWeight = boxProduct.boxProductWeight
? (boxProduct.boxProductWeight * currentCount).toFixed(
1,
)
: 0;
return (
<View
key={boxProduct.boxProductId}
className="flex items-center justify-between rounded-lg bg-gray-50 p-3"
>
<View className="flex flex-col">
<View className="text-gray-800">
{boxProduct.boxProductName}
</View>
<View className={"flex flex-row gap-2.5"}>
{boxProduct.boxProductWeight > 0 && (
<View className="text-xs text-gray-500">
{boxProduct.boxProductWeight}
</View>
)}
{currentCount > 0 &&
boxProduct.boxProductWeight > 0 && (
<View className="text-primary text-xs font-medium">
{totalWeight}
</View>
)}
</View>
</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.boxProductId,
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.boxProductId,
num,
);
}
}}
/>
</View>
<View
className="flex h-8 w-8 items-center justify-center rounded-r bg-gray-200"
onClick={() => {
handleProductCountChange(
boxProduct.boxProductId,
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={() => {
onClose();
setSelectedBrand(null);
setProductCounts(new Map());
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size={"large"}
block
type={"primary"}
disabled={
!selectedBrand ||
Array.from(productCounts.values()).every((count) => count === 0)
}
onClick={addBatchPackageInfo}
>
{editMode ? "保存" : "添加"}
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
</Popup>
);
}