ERPTurbo_Client/packages/app-client/src/components/delivery/Step1Form.tsx
shenyifei 72a0e06da6 refactor(delivery): 重构发货单相关功能实现
- 优化 State 组件渲染逻辑,增加状态判断避免无效渲染
- 在 Step1Form 组件中添加调试日志便于排查问题
- 调整 DeliveryFormSection 中 convertShipOrderVOToExamplesFormat 方法参数
- 重命名常量文件 shipOrder.ts 为 orderShip.ts 并扩展状态枚举值
- 新增草稿(DRAFT)和待发货(WAIT_SHIPMENT)两种订单状态配置
- 更新发货单页面导入模块路径并调整数据处理逻辑
- 修改发货单文档页面初始化方法参数并增强类型安全
- 重构发货单列表页删除冗余弹窗及生成单据逻辑
- 调整发票上传页面筛选条件限制仅允许特定状态下操作
- 优化审批结果页面发货单据下载流程简化交互步骤
- 补充业务接口定义完善 OrderShip 和 OrderSupplier 类型声明
- 更新工具函数中 purchaseOrderConverter 和 shipOrderConverter 实现细节
- 调整应用路由配置同步页面文件名变更影响范围
2025-12-16 14:48:21 +08:00

573 lines
19 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 { forwardRef, ReactNode, useImperativeHandle, useState } from "react";
import { Text, View } from "@tarojs/components";
import {
DatePicker,
Input,
PickerOption,
Toast,
} from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import { Icon } from "@/components";
interface Step1FormProps {
readOnly?: boolean;
moduleList: any[];
orderShip: any;
setOrderShip: (value: any) => void;
}
export interface Step1FormRef {
validateForm: () => boolean;
}
const Step1Form = forwardRef<Step1FormRef, Step1FormProps>((props, ref) => {
const { moduleList, orderShip, setOrderShip, readOnly = false } = props;
console.log("moduleList", moduleList, orderShip);
// 当天和未来10天
const startDate = new Date();
const endDate = new Date(startDate.getTime() + 86400000 * 10);
const [show, setShow] = useState(false);
const formatter = (type: string, option: PickerOption) => {
switch (type) {
case "year":
option.label += "年";
break;
case "month":
option.label += "月";
break;
case "day":
option.label += "日";
break;
case "hour":
option.label += "时";
break;
case "minute":
option.label += "分";
break;
default:
break;
}
return option;
};
// 表单错误状态
const [formErrors, setFormErrors] = useState<{
watermelonGrade?: boolean;
shippingAddress?: boolean;
estimatedArrivalDate?: boolean;
remark?: boolean;
itemGrades?: { [key: string]: boolean };
}>({});
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
validateForm,
}));
// 渲染内容配置表单字段
const renderContentFields = (module: any) => {
const contentSchema = module.schemas.find(
(schema: any) => schema.title === "内容配置",
);
if (
!contentSchema ||
!contentSchema.columns ||
contentSchema.columns.length === 0
) {
return null;
}
const contentFields: ReactNode[] = [];
contentSchema.columns.forEach((column: any, index: number) => {
if (
column.dataIndex === "requiredWatermelonGrade" &&
module.config.showWatermelonGrade
) {
contentFields.push(
<View key={index} className="mb-2.5">
<View className="mb-1 block text-sm font-normal text-[#000000]">
{column.title}
</View>
<View className="mb-2.5">
<View
className={`flex h-10 w-full items-center rounded-md ${formErrors.watermelonGrade ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
>
<Input
disabled={readOnly}
value={orderShip?.watermelonGrade || ""}
onChange={(value) => {
// 清除错误状态
if (formErrors.watermelonGrade && value) {
setFormErrors((prev) => ({
...prev,
watermelonGrade: false,
}));
}
setOrderShip({
...orderShip!,
watermelonGrade: value,
});
}}
onBlur={() => {
// 失焦时校验
if (!orderShip?.watermelonGrade) {
setFormErrors((prev) => ({
...prev,
watermelonGrade: true,
}));
Toast.show("toast", {
icon: "fail",
title:
column.fieldProps?.placeholder ||
`请填写${column.title}`,
});
}
}}
placeholder={
column.fieldProps?.placeholder || `${column.title}`
}
type="text"
className="flex-1"
/>
</View>
{formErrors.watermelonGrade && (
<View className="mt-1 text-xs text-red-500">
{`${column.title}`}
</View>
)}
</View>
</View>,
);
}
if (
column.dataIndex === "requiredShippingFrom" &&
module.config.showShippingFrom
) {
contentFields.push(
<View key={index} className="mb-2.5">
<View className="mb-1 block text-sm font-normal text-[#000000]">
{column.title}
</View>
<View className="mb-2.5">
<View
className={`flex h-10 w-full items-center rounded-md ${formErrors.shippingAddress ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
>
<Input
disabled={readOnly}
value={orderShip?.shippingAddress || ""}
onChange={(value) => {
// 清除错误状态
if (formErrors.shippingAddress && value) {
setFormErrors((prev) => ({
...prev,
shippingAddress: false,
}));
}
setOrderShip({
...orderShip!,
shippingAddress: value,
});
}}
onBlur={() => {
// 失焦时校验
if (!orderShip?.shippingAddress) {
setFormErrors((prev) => ({
...prev,
shippingAddress: true,
}));
Toast.show("toast", {
icon: "fail",
title:
column.fieldProps?.placeholder ||
`请填写${column.title}`,
});
}
}}
placeholder={
column.fieldProps?.placeholder || `${column.title}`
}
type="text"
className="flex-1"
/>
</View>
{formErrors.shippingAddress && (
<View className="mt-1 text-xs text-red-500">
{`${column.title}`}
</View>
)}
</View>
</View>,
);
}
if (
column.dataIndex === "requiredEstimatedArrivalTime" &&
module.config.showEstimatedArrivalTime
) {
contentFields.push(
<View key={index} className="mb-2.5">
<View className="mb-1 block text-sm font-normal text-[#000000]">
{column.title}
</View>
<View className="mb-2.5">
<View
className={`flex h-10 w-full items-center rounded-md ${formErrors.estimatedArrivalDate ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
>
<View
className={
"flex flex-1 flex-row items-center justify-between px-5" +
(readOnly ? " opacity-50" : "")
}
style={{
color: "var(--nutui-color-title, #1a1a1a)",
}}
onClick={() => readOnly || setShow(true)}
>
<View className={"text-sm"}>
{orderShip?.estimatedArrivalDate
? dayjs(orderShip?.estimatedArrivalDate).format(
"YYYY年MM月DD日",
)
: column.fieldProps?.placeholder || `${column.title}`}
</View>
<Icon name={"chevron-down"} />
</View>
<DatePicker
title="发货时间选择"
type="date"
startDate={startDate}
endDate={endDate}
visible={show}
defaultValue={new Date()}
formatter={formatter}
onClose={() => setShow(false)}
onConfirm={(_, values) => {
// 选择日期后清除错误状态
if (formErrors.estimatedArrivalDate) {
setFormErrors((prev) => ({
...prev,
estimatedArrivalDate: false,
}));
}
setOrderShip({
...orderShip!,
estimatedArrivalDate: dayjs(values.join("-")).format(
"YYYY-MM-DD",
),
});
}}
/>
</View>
{formErrors.estimatedArrivalDate && (
<View className="mt-1 text-xs text-red-500">
{`${column.title}`}
</View>
)}
</View>
</View>,
);
}
if (column.dataIndex === "requiredRemarks" && module.config.showRemarks) {
contentFields.push(
<View key={index} className="mb-2.5">
<View className="mb-1 block text-sm font-normal text-[#000000]">
{column.title}
</View>
<View className="mb-2.5">
<View
className={`flex h-10 w-full items-center rounded-md ${formErrors.remark ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
>
<Input
disabled={readOnly}
value={orderShip?.remark || ""}
onChange={(value) => {
// 清除错误状态
if (formErrors.remark && value) {
setFormErrors((prev) => ({
...prev,
remark: false,
}));
}
setOrderShip({
...orderShip!,
remark: value,
});
}}
onBlur={() => {
// 失焦时校验
if (!orderShip?.remark) {
setFormErrors((prev) => ({
...prev,
remark: true,
}));
Toast.show("toast", {
icon: "fail",
title:
column.fieldProps?.placeholder ||
`请填写${column.title}`,
});
}
}}
placeholder={
column.fieldProps?.placeholder || `${column.title}`
}
type="text"
className="flex-1"
/>
</View>
{formErrors.remark && (
<View className="mt-1 text-xs text-red-500">
{`${column.title}`}
</View>
)}
</View>
</View>,
);
}
if (column.dataIndex === "requiredGrade" && module.config.showGrade) {
contentFields.push(
<View key={index} className="mb-2.5">
<View className="mb-1 block text-sm font-normal text-[#000000]">
{column.title}
</View>
{orderShip?.orderShipItemList?.map(
(shipOrderItem: any, index: number) => {
return (
<View key={"shipOrderItem" + index} className="mb-2.5">
<View
key={shipOrderItem.itemId}
className={"flex flex-row gap-2.5"}
>
<View className="flex flex-1 items-center text-sm font-normal text-[#000000]">
{shipOrderItem.unitPrice} /
</View>
<View
className={`flex h-10 items-center rounded-md ${formErrors.itemGrades?.[shipOrderItem.itemId] ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
>
<Input
disabled={readOnly}
value={shipOrderItem.watermelonGrade || ""}
onChange={(value) => {
// 清除该项的错误状态
if (
formErrors.itemGrades?.[shipOrderItem.itemId] &&
value
) {
setFormErrors((prev) => ({
...prev,
itemGrades: {
...prev.itemGrades,
[shipOrderItem.itemId]: false,
},
}));
}
setOrderShip({
...orderShip!,
orderShipItemList:
orderShip?.orderShipItemList?.map(
(item: any) => {
if (item.itemId === shipOrderItem.itemId) {
return {
...item,
watermelonGrade: value,
};
}
return item;
},
),
});
}}
onBlur={() => {
// 失焦时校验
if (!shipOrderItem.watermelonGrade) {
setFormErrors((prev) => ({
...prev,
itemGrades: {
...prev.itemGrades,
[shipOrderItem.itemId]: true,
},
}));
Toast.show("toast", {
icon: "fail",
title:
column.fieldProps?.placeholder ||
`请填写第${index + 1}项的品级`,
});
}
}}
placeholder={
column.fieldProps?.placeholder ||
`${column.title}`
}
type="text"
className="flex-1"
/>
</View>
</View>
{formErrors.itemGrades?.[shipOrderItem.itemId] && (
<View className="mt-1 text-xs text-red-500">
{`${column.title}`}
</View>
)}
</View>
);
},
)}
</View>,
);
}
});
if (contentFields.length > 0) {
return contentFields;
}
return null;
};
// 表单校验
const validateForm = () => {
const errors: any = {};
let hasErrors = false;
// 检查各模块中的必填字段是否已填写
for (const module of moduleList) {
const contentSchema = module.schemas.find(
(schema: any) => schema.title === "内容配置",
);
if (
!contentSchema ||
!contentSchema.columns ||
contentSchema.columns.length === 0
) {
continue;
}
for (const column of contentSchema.columns) {
// 检查西瓜品级字段是否开启且已填写
if (
column.dataIndex === "requiredWatermelonGrade" &&
module.config.showWatermelonGrade
) {
if (!orderShip?.watermelonGrade) {
errors.watermelonGrade = true;
hasErrors = true;
Toast.show("toast", {
icon: "fail",
title: column.fieldProps?.placeholder || `请填写${column.title}`,
});
}
}
// 检查发货地字段是否开启且已填写
else if (
column.dataIndex === "requiredShippingFrom" &&
module.config.showShippingFrom
) {
if (!orderShip?.shippingAddress) {
errors.shippingAddress = true;
hasErrors = true;
Toast.show("toast", {
icon: "fail",
title: column.fieldProps?.placeholder || `请填写${column.title}`,
});
}
}
// 检查预计到仓时间字段是否开启且已填写
else if (
column.dataIndex === "requiredEstimatedArrivalTime" &&
module.config.showEstimatedArrivalTime
) {
if (!orderShip?.estimatedArrivalDate) {
errors.estimatedArrivalDate = true;
hasErrors = true;
Toast.show("toast", {
icon: "fail",
title: column.fieldProps?.placeholder || `请选择${column.title}`,
});
}
}
// 检查备注字段是否开启且已填写
else if (
column.dataIndex === "requiredRemarks" &&
module.config.showRemarks
) {
if (!orderShip?.remark) {
errors.remark = true;
hasErrors = true;
Toast.show("toast", {
icon: "fail",
title: column.fieldProps?.placeholder || `请填写${column.title}`,
});
}
}
// 检查品级字段是否开启且已填写
else if (
column.dataIndex === "requiredGrade" &&
module.config.showGrade
) {
console.log(
"orderShip?.orderShipItemList",
orderShip?.orderShipItemList,
);
if (orderShip?.orderShipItemList) {
const itemGradesErrors: { [key: string]: boolean } = {};
for (let i = 0; i < orderShip.orderShipItemList.length; i++) {
const item = orderShip.orderShipItemList[i];
if (!item.watermelonGrade) {
itemGradesErrors[item.itemId] = true;
hasErrors = true;
Toast.show("toast", {
icon: "fail",
title: `请填写第${i + 1}项的品级`,
});
}
}
if (Object.keys(itemGradesErrors).length > 0) {
errors.itemGrades = itemGradesErrors;
}
}
}
}
}
setFormErrors(errors);
return !hasErrors;
};
return moduleList.map((module) => {
const contentFields = renderContentFields(module);
// 如果没有内容配置字段,则不渲染该模块
if (!contentFields) return null;
return (
<View key={module.id} className="flex flex-col gap-2.5">
<View className="border-b border-b-gray-200 pb-2.5">
<Text className="text-base font-semibold">{module.title}</Text>
</View>
{contentFields}
</View>
);
});
});
export default Step1Form;