- 将采购相关组件移至 module 目录统一管理 - 优化 OrderCost 组件的人工费用处理逻辑,改为统一管理工头姓名 - 改进 OrderPackage 组件的纸箱类型渲染逻辑,根据供应商属性动态显示 - 更新 Weigh 组件,支持多供应商场景下的纸箱选择展示 - 调整采购模块导出路径,适配新的目录结构 - 优化表单交互文案,提升用户体验一致性
510 lines
16 KiB
TypeScript
510 lines
16 KiB
TypeScript
import { View } from "@tarojs/components";
|
||
import {
|
||
Input,
|
||
Radio,
|
||
Toast,
|
||
Uploader,
|
||
UploaderFileItem,
|
||
} from "@nutui/nutui-react-taro";
|
||
import { Icon } from "@/components";
|
||
import { SupplierVO } from "@/types/typings";
|
||
import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
|
||
import { uploadFile } from "@/utils/uploader";
|
||
import { validatePrice } from "@/utils/format";
|
||
|
||
// 定义ref暴露的方法接口
|
||
export interface WeighRef {
|
||
validate: () => boolean;
|
||
}
|
||
|
||
interface IWeightProps {
|
||
value: SupplierVO;
|
||
onChange: (supplierVO: SupplierVO) => void;
|
||
supplierCount: number;
|
||
}
|
||
|
||
export default forwardRef<WeighRef, IWeightProps>(function Weigh(props, ref) {
|
||
const { value, onChange, supplierCount } = props;
|
||
|
||
const [supplierVO, setSupplierVO] = useState<SupplierVO>();
|
||
|
||
// 初始化数据
|
||
useEffect(() => {
|
||
setSupplierVO(value);
|
||
|
||
// 初始化空磅照片和总磅照片
|
||
if (value.emptyWeightImg) {
|
||
setEmptyWeightImgList([
|
||
{
|
||
url: value.emptyWeightImg,
|
||
name: "empty-weight-image",
|
||
status: "success",
|
||
},
|
||
]);
|
||
}
|
||
|
||
if (value.totalWeightImg) {
|
||
setTotalWeightImgList([
|
||
{
|
||
url: value.totalWeightImg,
|
||
name: "total-weight-image",
|
||
status: "success",
|
||
},
|
||
]);
|
||
}
|
||
}, []);
|
||
|
||
// 监听供应商信息变化
|
||
useEffect(() => {
|
||
if (supplierVO) {
|
||
onChange(supplierVO);
|
||
}
|
||
}, [supplierVO]);
|
||
|
||
const [emptyWeightImgList, setEmptyWeightImgList] = useState<
|
||
UploaderFileItem[]
|
||
>([]);
|
||
const [totalWeightImgList, setTotalWeightImgList] = useState<
|
||
UploaderFileItem[]
|
||
>([]);
|
||
|
||
const [isPaperError, setIsPaperError] = useState<{
|
||
[key: string]: boolean;
|
||
}>({}); // 添加是否为最后一个瓜农的错误状态
|
||
|
||
// 添加三个字段的错误状态
|
||
const [emptyWeightError, setEmptyWeightError] = useState<{
|
||
[key: string]: boolean;
|
||
}>({});
|
||
const [totalWeightError, setTotalWeightError] = useState<{
|
||
[key: string]: boolean;
|
||
}>({});
|
||
const [priceError, setPriceError] = useState<{
|
||
[key: string]: boolean;
|
||
}>({});
|
||
|
||
// 空磅照片变更处理函数
|
||
const handleEmptyWeightImgChange = (files: UploaderFileItem[]) => {
|
||
setEmptyWeightImgList(files);
|
||
|
||
// 如果有文件且上传成功,保存URL到supplierVO
|
||
if (files.length > 0 && files[0].url) {
|
||
setSupplierVO((prev) => ({
|
||
...prev!,
|
||
emptyWeightImg: files[0].url,
|
||
}));
|
||
} else {
|
||
// 如果没有文件,清空URL
|
||
setSupplierVO((prev) => ({
|
||
...prev!,
|
||
emptyWeightImg: undefined,
|
||
}));
|
||
}
|
||
};
|
||
|
||
// 总磅照片变更处理函数
|
||
const handleTotalWeightImgChange = (files: UploaderFileItem[]) => {
|
||
setTotalWeightImgList(files);
|
||
|
||
// 如果有文件且上传成功,保存URL到supplierVO
|
||
if (files.length > 0 && files[0].url) {
|
||
setSupplierVO((prev) => ({
|
||
...prev!,
|
||
totalWeightImg: files[0].url,
|
||
}));
|
||
} else {
|
||
// 如果没有文件,清空URL
|
||
setSupplierVO((prev) => ({
|
||
...prev!,
|
||
totalWeightImg: undefined,
|
||
}));
|
||
}
|
||
};
|
||
|
||
// 将校验方法暴露给父组件
|
||
useImperativeHandle(ref, () => ({
|
||
validate,
|
||
}));
|
||
|
||
if (!supplierVO) {
|
||
return;
|
||
}
|
||
// 校验是否为最后一个瓜农函数
|
||
const validateIsPaper = (isPaper: any) => {
|
||
// 必须选择是或否
|
||
return isPaper !== undefined;
|
||
};
|
||
|
||
// 校验空磅重量
|
||
const validateEmptyWeight = (value: any) => {
|
||
if (value === "" || value === undefined || value === null) {
|
||
setEmptyWeightError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: true,
|
||
}));
|
||
return false;
|
||
}
|
||
|
||
const numValue = Number(value);
|
||
if (Number.isNaN(numValue) || numValue < 0) {
|
||
setEmptyWeightError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: true,
|
||
}));
|
||
return false;
|
||
}
|
||
|
||
setEmptyWeightError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: false,
|
||
}));
|
||
return true;
|
||
};
|
||
|
||
// 校验总磅重量
|
||
const validateTotalWeight = (value: any) => {
|
||
if (value === "" || value === undefined || value === null) {
|
||
setTotalWeightError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: true,
|
||
}));
|
||
return false;
|
||
}
|
||
|
||
const numValue = Number(value);
|
||
if (Number.isNaN(numValue) || numValue < 0) {
|
||
setTotalWeightError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: true,
|
||
}));
|
||
return false;
|
||
}
|
||
|
||
setTotalWeightError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: false,
|
||
}));
|
||
return true;
|
||
};
|
||
|
||
// 校验采购单价
|
||
const validatePurchasePrice = (value: any) => {
|
||
if (value === "" || value === undefined || value === null) {
|
||
setPriceError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: true,
|
||
}));
|
||
return false;
|
||
}
|
||
|
||
const numValue = Number(value);
|
||
if (Number.isNaN(numValue) || numValue < 0) {
|
||
setPriceError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: true,
|
||
}));
|
||
return false;
|
||
}
|
||
|
||
setPriceError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: false,
|
||
}));
|
||
return true;
|
||
};
|
||
|
||
// 对外暴露的校验方法
|
||
const validate = () => {
|
||
const id = supplierVO?.orderSupplierId;
|
||
if (!id) return false;
|
||
|
||
const isEmptyWeightValid = validateEmptyWeight(supplierVO.emptyWeight);
|
||
const isTotalWeightValid = validateTotalWeight(supplierVO.totalWeight);
|
||
const isPurchasePriceValid = validatePurchasePrice(
|
||
supplierVO.purchasePrice,
|
||
);
|
||
const isPaperValid = validateIsPaper(supplierVO.isPaper);
|
||
|
||
// 更新错误状态
|
||
setIsPaperError((prev) => ({
|
||
...prev,
|
||
[id]: !isPaperValid,
|
||
}));
|
||
|
||
setPriceError((prev) => ({
|
||
...prev,
|
||
[id]: !isPurchasePriceValid,
|
||
}));
|
||
|
||
setTotalWeightError((prev) => ({
|
||
...prev,
|
||
[id]: !isTotalWeightValid,
|
||
}));
|
||
|
||
setEmptyWeightError((prev) => ({
|
||
...prev,
|
||
[id]: !isEmptyWeightValid,
|
||
}));
|
||
|
||
const isValid =
|
||
isEmptyWeightValid &&
|
||
isTotalWeightValid &&
|
||
isPurchasePriceValid &&
|
||
isPaperValid;
|
||
|
||
if (!isValid) {
|
||
Toast.show("toast", {
|
||
icon: "fail",
|
||
title: "提示",
|
||
content: "请完善称重信息后再进行下一步操作",
|
||
});
|
||
}
|
||
|
||
return isValid;
|
||
};
|
||
|
||
return (
|
||
<View className="flex flex-1 flex-col gap-2.5 p-2.5">
|
||
<View className="border-primary rounded-lg border-4 bg-white p-2.5 shadow-sm">
|
||
<View className={"flex items-center justify-between gap-2.5"}>
|
||
<View className="text-sm">空车来的时候带纸箱了吗?</View>
|
||
<View className="text-neutral-darkest text-sm font-medium">
|
||
{supplierCount == 1 ? (
|
||
<Radio.Group
|
||
direction="horizontal"
|
||
value={
|
||
supplierVO.isPaper === true
|
||
? "true"
|
||
: supplierVO.isPaper === false
|
||
? "false"
|
||
: undefined
|
||
}
|
||
onChange={(value) => {
|
||
// 清除错误状态
|
||
setIsPaperError((prev) => ({
|
||
...prev,
|
||
[supplierVO.orderSupplierId]: false,
|
||
}));
|
||
|
||
// 根据用户选择设置是否包含空纸箱
|
||
const isPaperValue =
|
||
value === "true"
|
||
? true
|
||
: value === "false"
|
||
? false
|
||
: undefined;
|
||
setSupplierVO({
|
||
...supplierVO,
|
||
isPaper: isPaperValue,
|
||
});
|
||
}}
|
||
>
|
||
<Radio value="true">带了</Radio>
|
||
<Radio value="false">没带</Radio>
|
||
</Radio.Group>
|
||
) : supplierVO.isPaper === true ? (
|
||
"带了"
|
||
) : (
|
||
"没带"
|
||
)}
|
||
</View>
|
||
</View>
|
||
{isPaperError[supplierVO.orderSupplierId] && (
|
||
<View className="mt-1 text-xs text-red-500">
|
||
请选择车来的时候有没有带纸箱
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
<View className="border-primary rounded-lg border-4 bg-white p-2.5 shadow-sm">
|
||
<View className="text-primary mb-2.5 text-base font-bold">
|
||
{supplierVO.name}的称重信息
|
||
</View>
|
||
|
||
<View className="mb-2.5">
|
||
<View className="mb-2.5 flex gap-2.5">
|
||
<View className="flex-1 rounded-lg bg-blue-50 p-2.5">
|
||
<View className="mb-2.5 flex items-center">
|
||
<Icon
|
||
name={"truck"}
|
||
className="mr-2"
|
||
size={20}
|
||
color={"var(--color-blue-500)"}
|
||
/>
|
||
<View className="text-sm text-gray-700">空磅重量 (kg)</View>
|
||
</View>
|
||
|
||
<View
|
||
className={`flex h-10 w-full items-center rounded-md ${emptyWeightError[supplierVO.orderSupplierId] ? "border-4 border-red-500" : "border-4 border-blue-300"}`}
|
||
>
|
||
<Input
|
||
placeholder="0"
|
||
type="digit"
|
||
value={supplierVO.emptyWeight?.toString()}
|
||
onChange={(value) => {
|
||
const numValue = validatePrice(value);
|
||
if (numValue !== undefined) {
|
||
setSupplierVO({
|
||
...supplierVO,
|
||
emptyWeight: numValue as any,
|
||
});
|
||
// 校验输入值
|
||
validateEmptyWeight(numValue);
|
||
}
|
||
}}
|
||
onBlur={() => {
|
||
// 失去焦点时进行校验
|
||
validateEmptyWeight(supplierVO.emptyWeight);
|
||
}}
|
||
/>
|
||
</View>
|
||
{emptyWeightError[supplierVO.orderSupplierId] && (
|
||
<View className="mt-1 text-xs text-red-500">
|
||
请输入有效的空磅重量
|
||
</View>
|
||
)}
|
||
<View className="mt-2.5 flex flex-row text-xs text-blue-600">
|
||
<Icon
|
||
name={"circle-info"}
|
||
className="mr-1"
|
||
color={"rgb(--color-blue-600)"}
|
||
size={12}
|
||
/>
|
||
<View>自动带入上一个瓜农的总磅重量</View>
|
||
</View>
|
||
</View>
|
||
<View className="flex-1 rounded-lg bg-green-50 p-2.5">
|
||
<View className="mb-2.5 flex items-center">
|
||
<Icon
|
||
name={"weight-scale"}
|
||
className="mr-2"
|
||
size={20}
|
||
color={"var(--color-green-300)"}
|
||
/>
|
||
<View className="text-sm text-gray-700">总磅重量 (kg)</View>
|
||
</View>
|
||
<View
|
||
className={`flex h-10 w-full items-center rounded-md ${totalWeightError[supplierVO.orderSupplierId] ? "border-4 border-red-500" : "border-4 border-green-300"}`}
|
||
>
|
||
<Input
|
||
placeholder="0"
|
||
type="digit"
|
||
value={supplierVO.totalWeight?.toString()}
|
||
onChange={(value) => {
|
||
const numValue = validatePrice(value);
|
||
if (numValue !== undefined) {
|
||
setSupplierVO({
|
||
...supplierVO,
|
||
totalWeight: numValue as any,
|
||
});
|
||
// 校验输入值
|
||
validateTotalWeight(numValue);
|
||
}
|
||
}}
|
||
onBlur={() => {
|
||
// 失去焦点时进行校验
|
||
validateTotalWeight(supplierVO.totalWeight);
|
||
}}
|
||
/>
|
||
</View>
|
||
{totalWeightError[supplierVO.orderSupplierId] && (
|
||
<View className="mt-1 text-xs text-red-500">
|
||
请输入有效的总磅重量
|
||
</View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
<View className="mb-2.5 space-y-2.5">
|
||
<View className="rounded-lg bg-yellow-50 p-2.5">
|
||
<View className="mb-2.5 flex items-center">
|
||
<Icon
|
||
name={"money-bill"}
|
||
className="mr-2"
|
||
size={20}
|
||
color={"var(--color-yellow-500)"}
|
||
/>
|
||
<View className="text-sm text-gray-700">
|
||
采购单价设置 (元/斤)
|
||
</View>
|
||
</View>
|
||
<View
|
||
className={`flex h-10 w-full items-center rounded-md ${priceError[supplierVO.orderSupplierId] ? "border-4 border-red-500" : "border-4 border-yellow-300"}`}
|
||
>
|
||
<Input
|
||
placeholder="0.00"
|
||
type="digit"
|
||
value={supplierVO.purchasePrice?.toString()}
|
||
onChange={(value) => {
|
||
const numValue = validatePrice(value);
|
||
if (numValue !== undefined) {
|
||
setSupplierVO({
|
||
...supplierVO,
|
||
purchasePrice: numValue as any,
|
||
});
|
||
// 校验输入值
|
||
validatePurchasePrice(numValue);
|
||
}
|
||
}}
|
||
onBlur={() => {
|
||
// 失去焦点时进行校验
|
||
validatePurchasePrice(supplierVO.purchasePrice);
|
||
}}
|
||
/>
|
||
</View>
|
||
{priceError[supplierVO.orderSupplierId] && (
|
||
<View className="mt-1 text-xs text-red-500">
|
||
请输入有效的采购单价
|
||
</View>
|
||
)}
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
|
||
<View className={"flex flex-row gap-2.5"}>
|
||
<View className="border-primary flex-1 rounded-lg border-4 bg-white p-2.5 shadow-sm">
|
||
<View className={`flex w-full border-gray-300`}>
|
||
<Uploader
|
||
className={"w-full"}
|
||
value={emptyWeightImgList}
|
||
onChange={handleEmptyWeightImgChange}
|
||
sourceType={["album", "camera"]}
|
||
uploadIcon={<Icon name={"camera"} size={36} />}
|
||
uploadLabel={
|
||
<View className={"flex flex-col items-center"}>
|
||
<View className="text-sm">拍照上传空磅照片</View>
|
||
</View>
|
||
}
|
||
maxCount={1}
|
||
//@ts-ignore
|
||
upload={uploadFile}
|
||
multiple
|
||
/>
|
||
</View>
|
||
</View>
|
||
|
||
<View className="border-primary flex-1 rounded-lg border-4 bg-white p-2.5 shadow-sm">
|
||
<View className={`flex w-full border-gray-300`}>
|
||
<Uploader
|
||
className={"w-full"}
|
||
value={totalWeightImgList}
|
||
onChange={handleTotalWeightImgChange}
|
||
sourceType={["album", "camera"]}
|
||
uploadIcon={<Icon name={"camera"} size={36} />}
|
||
uploadLabel={
|
||
<View className={"flex flex-col items-center"}>
|
||
<View className="text-sm">拍照上传总磅照片</View>
|
||
</View>
|
||
}
|
||
maxCount={1}
|
||
//@ts-ignore
|
||
upload={uploadFile}
|
||
multiple
|
||
/>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
</View>
|
||
);
|
||
});
|