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