diff --git a/packages/app-client/src/app.config.ts b/packages/app-client/src/app.config.ts index 401a590..278534c 100644 --- a/packages/app-client/src/app.config.ts +++ b/packages/app-client/src/app.config.ts @@ -36,7 +36,7 @@ config = { list: [ { pagePath: "pages/main/index/index", - text: "首页", + text: "工作台", }, { pagePath: "pages/main/menu/index", diff --git a/packages/app-client/src/components/biz/CustomTabBar.tsx b/packages/app-client/src/components/biz/CustomTabBar.tsx index 3b84703..0f5a2e6 100644 --- a/packages/app-client/src/components/biz/CustomTabBar.tsx +++ b/packages/app-client/src/components/biz/CustomTabBar.tsx @@ -34,7 +34,7 @@ export default function CustomTabBar(props: ICustomTabBarProps) { iconPath: ( ), - text: "首页", + text: "工作台", }, { pagePath: "/pages/main/menu/index", diff --git a/packages/app-client/src/components/purchase/module/OrderPackage.tsx b/packages/app-client/src/components/purchase/module/OrderPackage.tsx index cfc5e0a..34f8f0e 100644 --- a/packages/app-client/src/components/purchase/module/OrderPackage.tsx +++ b/packages/app-client/src/components/purchase/module/OrderPackage.tsx @@ -37,41 +37,7 @@ export default forwardRef( // 初始化数据 useEffect(() => { setSupplierVO(value); - const orderPackageList = value.orderPackageList || []; setOrderPackageList(value.orderPackageList || []); - - // 根据orderPackageList和supplierVO属性初始化 packageTypeEnabled - const initialPackageTypeEnabled = { - USED: undefined, - EXTRA_USED: undefined, - EXTRA: undefined, - REMAIN: undefined, - OWN: undefined, - }; - - // 根据orderPackageList设置已有的纸箱类型 - if (orderPackageList.some((item) => item.boxType === "USED")) { - // @ts-ignore - initialPackageTypeEnabled.USED = true; - } - if (orderPackageList.some((item) => item.boxType === "EXTRA_USED")) { - // @ts-ignore - initialPackageTypeEnabled.EXTRA_USED = true; - } - if (orderPackageList.some((item) => item.boxType === "EXTRA")) { - // @ts-ignore - initialPackageTypeEnabled.EXTRA = true; - } - if (orderPackageList.some((item) => item.boxType === "REMAIN")) { - // @ts-ignore - initialPackageTypeEnabled.REMAIN = true; - } - if (orderPackageList.some((item) => item.boxType === "OWN")) { - // @ts-ignore - initialPackageTypeEnabled.OWN = true; - } - - setPackageTypeEnabled(initialPackageTypeEnabled); }, []); const [orderPackageList, setOrderPackageList] = useState< @@ -90,20 +56,64 @@ export default forwardRef( // 确定当前供应商需要检查哪些纸箱类型 if (supplierVO.isPaper && supplierVO.isLast) { - requiredTypes = ["OWN", "USED", "EXTRA_USED", "REMAIN"]; + // 空磅包含纸箱 + 最后一个瓜农 + if (packageTypeEnabled.OWN === 1) { + requiredTypes = ["OWN", "USED"]; + + if (packageTypeEnabled.REMAIN === 1) { + requiredTypes.push("REMAIN"); + } + } else if (packageTypeEnabled.OWN === 2) { + requiredTypes = ["USED", "OWN", "EXTRA_USED"]; + + if (packageTypeEnabled.REMAIN === 1) { + requiredTypes.push("REMAIN"); + } + } } else if (supplierVO.isPaper && !supplierVO.isLast) { - requiredTypes = ["OWN", "USED", "EXTRA"]; + // 空磅包含纸箱 + 非最后一个瓜农 + if (packageTypeEnabled.OWN === 1) { + requiredTypes = ["OWN", "USED"]; + } else if (packageTypeEnabled.OWN === 2) { + requiredTypes = ["USED", "OWN", "EXTRA"]; + } } else if (!supplierVO.isPaper) { - requiredTypes = ["OWN", "USED"]; + // 空磅不包含纸箱 + if (packageTypeEnabled.OWN === 1) { + requiredTypes = ["OWN", "USED"]; + } else if (packageTypeEnabled.OWN === 2) { + requiredTypes = ["USED", "OWN"]; + } } - // 检查所需选项是否都已做出选择(不是true就是false,不能是undefined) + // 检查所需选项是否都已做出选择(不是1就是2,不能是0) + console.log("requiredTypes", requiredTypes, packageTypeEnabled) const allRequiredAnswered = requiredTypes.every( (type) => - packageTypeEnabled[type] === true || - packageTypeEnabled[type] === false, + packageTypeEnabled[type] === 1 || + packageTypeEnabled[type] === 2, ); + // 检查必须回答的问题 + let requiredQuestionsAnswered = true; + if (supplierVO.isPaper && supplierVO.isLast) { + // 最后一个瓜农需要回答"空车带来的纸箱用完了吗?" + requiredQuestionsAnswered = packageTypeEnabled.REMAIN !== 0; + } else if (supplierVO.isPaper && !supplierVO.isLast) { + // 非最后一个瓜农不需要回答额外问题 + } else if (!supplierVO.isPaper) { + // 空磅不包含纸箱的情况 + } + + if (!requiredQuestionsAnswered) { + Toast.show("toast", { + icon: "fail", + title: "提示", + content: "请回答所有必答问题", + }); + return false; + } + if (!allRequiredAnswered) { Toast.show("toast", { icon: "fail", @@ -116,14 +126,14 @@ export default forwardRef( // 检查所有启用的纸箱类型是否都填写了信息 const enabledTypes = Object.entries(packageTypeEnabled) .filter( - ([type, enabled]) => requiredTypes.includes(type) && enabled === true, + ([type, enabled]) => requiredTypes.includes(type) && enabled === 1, ) .map(([type, _]) => type); console.log("enabledTypes", enabledTypes); // 如果没有任何纸箱类型被启用,提示用户至少选择一种 - if (enabledTypes.length === 0) { + if (requiredTypes.length > 0 && enabledTypes.length === 0) { Toast.show("toast", { icon: "fail", title: "提示", @@ -140,11 +150,11 @@ export default forwardRef( if (!hasPackagesOfType) { const typeLabels = { - USED: "本次拉来的纸箱", - EXTRA_USED: "额外拿来的纸箱", - EXTRA: "额外拿来的纸箱", - REMAIN: "车上剩下的纸箱", - OWN: "瓜农自己的纸箱", + USED: "记录装车用的纸箱", + EXTRA_USED: "记录额外运输来的所有纸箱", + EXTRA: "记录额外运输来的所有纸箱", + REMAIN: "记录车上没用完的纸箱", + OWN: "记录装车用的瓜农的纸箱", }; Toast.show("toast", { @@ -253,12 +263,13 @@ export default forwardRef( useState("USED"); // 新增状态:跟踪每种纸箱类型的启用状态 + // 0: 未选, 1: 选择是, 2: 选择否 const [packageTypeEnabled, setPackageTypeEnabled] = useState({ - USED: undefined, // 默认不启用任何纸箱类型 - EXTRA_USED: undefined, - EXTRA: undefined, - REMAIN: undefined, - OWN: undefined, // 添加OWN类型 + USED: 0, + EXTRA_USED: 0, + EXTRA: 0, + REMAIN: 0, + OWN: 0, }); // 监听供应商信息变化 @@ -291,7 +302,7 @@ export default forwardRef( const handleBatchBrandSelect = (brand: BoxBrand) => { // 检查当前boxType下是否已存在该品牌 const isBrandAlreadySelected = orderPackageList.some( - (pkg) => pkg.boxType === boxType && pkg.boxBrandId === brand.boxBrandId + (pkg) => pkg.boxType === boxType && pkg.boxBrandId === brand.boxBrandId, ); if (isBrandAlreadySelected) { @@ -302,7 +313,7 @@ export default forwardRef( }); return; } - + setSelectedBrand(brand); // 初始化所有产品的数量为0 const initialCounts = new Map(); @@ -396,11 +407,11 @@ export default forwardRef( // 处理纸箱类型启用状态切换 const togglePackageType = ( type: BusinessAPI.OrderPackage["boxType"], - enabled: boolean, + value: number, // 0: 未选, 1: 选择是, 2: 选择否 ) => { setPackageTypeEnabled((prev) => ({ ...prev, - [type]: enabled, + [type]: value, })); }; @@ -551,10 +562,13 @@ export default forwardRef( if (editingItem && editingItemIndex !== null) { // 检查是否尝试更改为已存在的品牌(排除自身) const isBrandAlreadySelected = orderPackageList.some( - (pkg) => - pkg.boxType === editingItem.boxType && + (pkg) => + pkg.boxType === editingItem.boxType && pkg.boxBrandId === editingItem.boxBrandId && - !(pkg.boxBrandId === editingItem.boxBrandId && pkg.boxType === editingItem.boxType) + !( + pkg.boxBrandId === editingItem.boxBrandId && + pkg.boxType === editingItem.boxType + ), ); if (isBrandAlreadySelected) { @@ -565,7 +579,7 @@ export default forwardRef( }); return; } - + // 使用convertBoxBrandToOrderPackages转换编辑后的数据 const updatedOrderPackages = convertBoxBrandToOrderPackages( editingItem, @@ -690,23 +704,18 @@ export default forwardRef( // 根据启用的纸箱类型渲染对应的内容 const renderPackageByType = (type: keyof typeof packageTypeEnabled) => { - // 如果该类型未启用,不渲染任何内容 - if (!packageTypeEnabled[type]) { - return null; - } - // 获取该类型下所有的纸箱 const packagesOfType = convertOrderPackagesToBoxBrands( orderPackageList.filter((pkg) => pkg.boxType === type), ); - // 定义类型显示名称 + // 根据类型设置显示标题 const typeLabels = { - USED: "本次拉来的纸箱", - EXTRA_USED: "额外拿来的纸箱", - EXTRA: "额外拿来的纸箱", - REMAIN: "车上剩下的纸箱", - OWN: "瓜农自己的纸箱", // 添加OWN类型标签 + USED: "记录装车用的纸箱", + EXTRA_USED: "记录用的额外运输纸箱纸箱", + EXTRA: "记录额外运输来的所有纸箱", + REMAIN: "记录车上没用完的纸箱", + OWN: "记录装车用的瓜农的纸箱", }; return ( @@ -739,7 +748,7 @@ export default forwardRef( setShowBatchModal(true); }} > - 添加{typeLabels[type]} + 添加纸箱 ); @@ -816,15 +825,6 @@ export default forwardRef( )} - {/*/!* 未设置数量时的提示 *!/*/} - {/*{selectedBrand && !hasAnyProductWithCount && (*/} - {/* */} - {/* */} - {/* 请为至少一个纸箱规格设置数量*/} - {/* */} - {/* */} - {/*)}*/} - {/* 产品展示 */} {selectedBrand && ( @@ -1110,278 +1110,262 @@ export default forwardRef( {/* 是否使用瓜农自己的纸箱 */} - 有没有用瓜农的纸箱? + 瓜农包纸箱吗? - {/* 所有选项都可以自由选择,不再受瓜农自己的纸箱选项影响 */} - <> - {supplierVO.isPaper && supplierVO.isLast && ( - <> - - 有没有用本次拉来的纸箱? - - - - - + {/* 根据供应商属性显示不同的问题 */} + {supplierVO.isPaper && supplierVO.isLast && ( + /* 当前为最后一个瓜农 */ + <> + {packageTypeEnabled.OWN === 1 && ( + /* 当选择了使用自己的纸箱时 */ + <> + {renderPackageByType("OWN")} - - 有没有用额外拿来的纸箱? - - - + onClick={() => { + togglePackageType("REMAIN", 2); + // 如果关闭,也需要清除相关数据 + if (packageTypeEnabled.REMAIN === 1) { + setOrderPackageList((list) => + list.filter((item) => item.boxType !== "REMAIN"), + ); + } + }} + > + 用完了 + + + - - - 车上还剩纸箱吗? - - - - - - - )} + {packageTypeEnabled.REMAIN === 1 && + renderPackageByType("REMAIN")} + + )} - {supplierVO.isPaper && !supplierVO.isLast && ( - <> - - 有没有用本次拉来的纸箱? - - - - - + {packageTypeEnabled.OWN === 2 && ( + /* 当没有选择使用自己的纸箱时 */ + <> + {renderPackageByType("USED")} - - 有没有用额外拿来的纸箱? - - - + onClick={() => { + togglePackageType("REMAIN", 2); + // 如果关闭,也需要清除相关数据 + if (packageTypeEnabled.REMAIN === 1) { + setOrderPackageList((list) => + list.filter((item) => item.boxType !== "REMAIN"), + ); + } + }} + > + 用完了 + + + - - - )} - {!supplierVO.isPaper && ( - <> - - 有没有用本次拉来的纸箱? - - - + onClick={() => togglePackageType("EXTRA_USED", 1)} + > + 用了 + + + - - - )} - + + {packageTypeEnabled.EXTRA_USED === 1 && + renderPackageByType("EXTRA_USED")} + + )} + + )} + + {supplierVO.isPaper && !supplierVO.isLast && ( + /* 非最后一个瓜农 */ + <> + {packageTypeEnabled.OWN === 1 && ( + /* 当选择了使用自己的纸箱时 */ + <> + {renderPackageByType("OWN")} + + )} + + {packageTypeEnabled.OWN === 2 && ( + /* 当没有选择使用自己的纸箱时 */ + <> + {renderPackageByType("USED")} + + {/* 有额外运输纸箱过来吗? */} + + 有额外运输纸箱过来吗? + + + + + + + {packageTypeEnabled.EXTRA === 1 && + renderPackageByType("EXTRA")} + + )} + + )} + + {!supplierVO.isPaper && ( + /* 空磅不包含纸箱 */ + <> + {packageTypeEnabled.OWN === 1 && ( + /* 当选择了使用自己的纸箱时 */ + <> + {renderPackageByType("OWN")} + + )} + + {packageTypeEnabled.OWN === 2 && ( + /* 当没有选择使用自己的纸箱时 */ + <>{renderPackageByType("EXTRA")} + )} + + )} - {/* 根据启用的类型和supplierVO属性渲染对应的内容 */} - {renderPackageByType("OWN")} - {supplierVO.isPaper && supplierVO.isLast && ( - <> - {renderPackageByType("USED")} - {renderPackageByType("EXTRA_USED")} - {renderPackageByType("REMAIN")} - - )} - {supplierVO.isPaper && !supplierVO.isLast && ( - <> - {renderPackageByType("USED")} - {renderPackageByType("EXTRA")} - - )} - {!supplierVO.isPaper && renderPackageByType("USED")} - {/* 当选择了使用自己的纸箱时才渲染 */} {/* 批量添加弹窗 */} {renderBatchAddModal()} {/* 编辑弹窗 */} diff --git a/packages/app-client/src/components/purchase/module/OrderVehicle.tsx b/packages/app-client/src/components/purchase/module/OrderVehicle.tsx index df8b80e..de9a9d7 100644 --- a/packages/app-client/src/components/purchase/module/OrderVehicle.tsx +++ b/packages/app-client/src/components/purchase/module/OrderVehicle.tsx @@ -488,7 +488,7 @@ export default forwardRef( const vehicleExtraction = async (message: string) => { // 简单校验message,避免无效的数据抽取 if (!message || message.trim() === "") { - Taro.showToast({ + Toast.show("toast", { title: "请输入有效内容", icon: "none", }); diff --git a/packages/app-client/src/constant/workbench.ts b/packages/app-client/src/constant/workbench.ts index 16b926c..8c61af6 100644 --- a/packages/app-client/src/constant/workbench.ts +++ b/packages/app-client/src/constant/workbench.ts @@ -101,6 +101,7 @@ const quickActionMap = { icon: "folder", iconColor: "var(--color-blue-600)", bgColorClass: "bg-blue-100", + path: "/pages/purchase/order/list", }, { id: "history", @@ -108,6 +109,7 @@ const quickActionMap = { icon: "clipboard-list", iconColor: "var(--color-green-600)", bgColorClass: "bg-green-100", + path: "/pages/purchase/order/list", }, { id: "invoiceUpload", @@ -124,6 +126,7 @@ const quickActionMap = { icon: "folder", iconColor: "var(--color-blue-600)", bgColorClass: "bg-blue-100", + path: "/pages/purchase/order/list", }, { id: "history", @@ -131,6 +134,7 @@ const quickActionMap = { icon: "clipboard-list", iconColor: "var(--color-green-600)", bgColorClass: "bg-green-100", + path: "/pages/purchase/order/list", }, { id: "invoiceUpload", diff --git a/packages/app-client/src/pages/ship/document/create.tsx b/packages/app-client/src/pages/ship/document/create.tsx index 8c75a38..48fbfda 100644 --- a/packages/app-client/src/pages/ship/document/create.tsx +++ b/packages/app-client/src/pages/ship/document/create.tsx @@ -5,12 +5,13 @@ import { Text, View } from "@tarojs/components"; import { Button, DatePicker, + Dialog, Input, PickerOption, - PickerValue, SafeArea, Step, Steps, + Toast, } from "@nutui/nutui-react-taro"; import shipOrder from "@/constant/shipOrder"; import Taro from "@tarojs/taro"; @@ -28,14 +29,6 @@ export default hocAuth(function Page(props: CommonComponent) { const startDate = new Date(currentYear, 0, 1); // 当年第一天 const endDate = new Date(); // 今天 const [show, setShow] = useState(false); - const confirm = (_values: PickerValue[]) => { - setShipOrderVO((prev) => { - return { - ...prev!, - estimatedArrivalDate: dayjs(_values.join("-")).format("YYYY-MM-DD"), - }; - }); - }; const formatter = (type: string, option: PickerOption) => { switch (type) { @@ -62,13 +55,20 @@ export default hocAuth(function Page(props: CommonComponent) { const [scale, setScale] = useState(0.5); // 缩放比例 const [step, setStep] = useState(1); - const [moduleList, setModuleList] = useState([]); - const [height, setHeight] = useState(); - const [shipOrderVO, setShipOrderVO] = useState(); const [deliveryTemplate, setDeliveryTemplate] = useState(); + const [previewUrl, setPreviewUrl] = useState(); + + // 表单错误状态 + const [formErrors, setFormErrors] = useState<{ + watermelonGrade?: boolean; + shippingAddress?: boolean; + estimatedArrivalDate?: boolean; + remark?: boolean; + itemGrades?: { [key: string]: boolean }; + }>({}); const init = async (shipOrderId: BusinessAPI.ShipOrderVO["shipOrderId"]) => { const { data } = await business.shipOrder.showShipOrder({ @@ -216,140 +216,328 @@ export default hocAuth(function Page(props: CommonComponent) { {column.title} {column.dataIndex === "requiredWatermelonGrade" && ( - - { - setShipOrderVO((prev) => { - return { - ...prev!, - watermelonGrade: value, - }; - }); - }} - placeholder={ - column.fieldProps?.placeholder || `请${column.title}` - } - type="text" - className="flex-1" - /> + + + { + // 清除错误状态 + if (formErrors.watermelonGrade && value) { + setFormErrors((prev) => ({ + ...prev, + watermelonGrade: false, + })); + } + + setShipOrderVO((prev) => { + return { + ...prev!, + watermelonGrade: value, + }; + }); + }} + onBlur={() => { + // 失焦时校验 + if (!shipOrderVO?.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" + /> + + + {formErrors.watermelonGrade && ( + + {`请${column.title}`} + + )} )} {column.dataIndex === "requiredShippingFrom" && ( - - { - setShipOrderVO((prev) => { - return { - ...prev!, - shippingAddress: value, - }; - }); - }} - placeholder={ - column.fieldProps?.placeholder || `请${column.title}` - } - type="text" - className="flex-1" - /> + + + { + // 清除错误状态 + if (formErrors.shippingAddress && value) { + setFormErrors((prev) => ({ + ...prev, + shippingAddress: false, + })); + } + + setShipOrderVO((prev) => { + return { + ...prev!, + shippingAddress: value, + }; + }); + }} + onBlur={() => { + // 失焦时校验 + if (!shipOrderVO?.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" + /> + + + {formErrors.shippingAddress && ( + + {`请${column.title}`} + + )} )} {column.dataIndex === "requiredEstimatedArrivalTime" && ( - - setShow(true)} - /> - setShow(false)} - onConfirm={(_, values) => confirm(values)} - /> + + + setShow(true)} + onBlur={() => { + // 失焦时校验 + if (!shipOrderVO?.estimatedArrivalDate) { + setFormErrors((prev) => ({ + ...prev, + estimatedArrivalDate: true, + })); + Toast.show("toast", { + icon: "fail", + title: + column.fieldProps?.placeholder || + `请选择${column.title}`, + }); + } + }} + /> + setShow(false)} + onConfirm={(_, values) => { + // 选择日期后清除错误状态 + if (formErrors.estimatedArrivalDate) { + setFormErrors((prev) => ({ + ...prev, + estimatedArrivalDate: false, + })); + } + + setShipOrderVO((prev) => { + return { + ...prev!, + estimatedArrivalDate: dayjs( + values.join("-"), + ).format("YYYY-MM-DD"), + }; + }); + }} + /> + + + {formErrors.estimatedArrivalDate && ( + + {`请${column.title}`} + + )} )} {column.dataIndex === "requiredRemarks" && ( - - { - setShipOrderVO((prev) => { - return { - ...prev!, - remark: value, - }; - }); - }} - placeholder={ - column.fieldProps?.placeholder || `请${column.title}` - } - type="text" - className="flex-1" - /> + + + { + // 清除错误状态 + if (formErrors.remark && value) { + setFormErrors((prev) => ({ + ...prev, + remark: false, + })); + } + + setShipOrderVO((prev) => { + return { + ...prev!, + remark: value, + }; + }); + }} + onBlur={() => { + // 失焦时校验 + if (!shipOrderVO?.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" + /> + + + {formErrors.remark && ( + + {`请${column.title}`} + + )} )} {column.dataIndex === "requiredGrade" && ( <> - {shipOrderVO?.shipOrderItemList?.map((shipOrderItem) => { - return ( - - - 单价:{shipOrderItem.unitPrice} 元/斤 + {shipOrderVO?.shipOrderItemList?.map( + (shipOrderItem, index) => { + return ( + + + + 单价:{shipOrderItem.unitPrice} 元/斤 + + + + { + // 清除该项的错误状态 + if ( + formErrors.itemGrades?.[ + shipOrderItem.itemId + ] && + value + ) { + setFormErrors((prev) => ({ + ...prev, + itemGrades: { + ...prev.itemGrades, + [shipOrderItem.itemId]: false, + }, + })); + } + + setShipOrderVO((prev) => { + return { + ...prev!, + shipOrderItemList: + prev?.shipOrderItemList?.map((item) => { + 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" + /> + + + + {formErrors.itemGrades?.[shipOrderItem.itemId] && ( + + {`请${column.title}`} + + )} - - { - setShipOrderVO((prev) => { - return { - ...prev!, - shipOrderItemList: - prev?.shipOrderItemList?.map((item) => { - if ( - item.itemId === shipOrderItem.itemId - ) { - return { - ...item, - watermelonGrade: value, - }; - } - return item; - }), - }; - }); - }} - placeholder={ - column.fieldProps?.placeholder || - `请${column.title}` - } - type="text" - className="flex-1" - /> - - - ); - })} + ); + }, + )} )} @@ -359,12 +547,126 @@ export default hocAuth(function Page(props: CommonComponent) { ); }; - // 下一步 - const nextStep = () => { + // 表单校验 + const validateForm = () => { + const errors: any = {}; + let hasErrors = false; + + // 检查各模块中的必填字段是否已填写 + for (const module of moduleList) { + const contentSchema = module.schemas.find( + (schema) => schema.title === "内容配置", + ); + + if ( + !contentSchema || + !contentSchema.columns || + contentSchema.columns.length === 0 + ) { + continue; + } + + for (const column of contentSchema.columns) { + // 检查西瓜品级字段是否开启且已填写 + if (column.dataIndex === "requiredWatermelonGrade") { + if (!shipOrderVO?.watermelonGrade) { + errors.watermelonGrade = true; + hasErrors = true; + Toast.show("toast", { + icon: "fail", + title: column.fieldProps?.placeholder || `请填写${column.title}`, + }); + } + } + // 检查发货地字段是否开启且已填写 + else if (column.dataIndex === "requiredShippingFrom") { + if (!shipOrderVO?.shippingAddress) { + errors.shippingAddress = true; + hasErrors = true; + Toast.show("toast", { + icon: "fail", + title: column.fieldProps?.placeholder || `请填写${column.title}`, + }); + } + } + // 检查预计到仓时间字段是否开启且已填写 + else if (column.dataIndex === "requiredEstimatedArrivalTime") { + if (!shipOrderVO?.estimatedArrivalDate) { + errors.estimatedArrivalDate = true; + hasErrors = true; + Toast.show("toast", { + icon: "fail", + title: column.fieldProps?.placeholder || `请选择${column.title}`, + }); + } + } + // 检查备注字段是否开启且已填写 + else if (column.dataIndex === "requiredRemarks") { + if (!shipOrderVO?.remark) { + errors.remark = true; + hasErrors = true; + Toast.show("toast", { + icon: "fail", + title: column.fieldProps?.placeholder || `请填写${column.title}`, + }); + } + } + // 检查品级字段是否开启且已填写 + else if (column.dataIndex === "requiredGrade") { + if (shipOrderVO?.shipOrderItemList) { + const itemGradesErrors: { [key: string]: boolean } = {}; + for (let i = 0; i < shipOrderVO.shipOrderItemList.length; i++) { + const item = shipOrderVO.shipOrderItemList[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; + }; + + // 预览确认 + const previewAndConfirm = () => { console.log(step); + if (step === 1) { + // 在第一步时校验表单 + if (!validateForm()) { + return; + } + } + if (step === 2) { - // 截图预览内容 - capturePreview(); + // 显示确认对话框 + Dialog.open("dialog", { + title: "生成发货单据", + content: "即将生成发货单据,请确认发货单据是否正确。", + confirmText: "确认生成", + cancelText: "取消", + onConfirm: async () => { + // 截图预览内容 + await capturePreview(); + Dialog.close("dialog"); + // 进入第三步 + setStep(3); + }, + onCancel: () => { + Dialog.close("dialog"); + }, + }); return; } @@ -373,25 +675,592 @@ export default hocAuth(function Page(props: CommonComponent) { } }; - // 截图预览内容 - const capturePreview = () => { - Taro.createSelectorQuery() - .select("#preview") - .boundingClientRect((rect) => { - if (rect) { - // Taro 能获取到html 吗 - } - }) - .exec(); + // 生成发货单据 + const generateShippingDocument = async () => { + // 显示确认对话框 + Dialog.open("dialog", { + title: "生成发货单据", + content: "即将生成发货单据,请确认发货单据是否正确。", + confirmText: "确认生成", + cancelText: "取消", + onConfirm: async () => { + // 截图预览内容 + await capturePreview(); + Dialog.close("dialog"); + // 进入第三步 + setStep(3); + }, + onCancel: () => { + Dialog.close("dialog"); + }, + }); }; - // 上一步 - const prevStep = () => { + // 将预览内容转换为HTML字符串的函数 + const generateHtmlString = () => { + let htmlString = ` + +
+
+ `; + + moduleList.forEach((module) => { + const config = module.config; + + if (module.type === "title") { + htmlString += ` +
+
+ ${config.title || ""} +
+
+ `; + } + + if (module.type === "dealerInfo") { + htmlString += ` +
+
+ `; + + if (config.showDealerName || config.showWatermelonGrade) { + htmlString += ` +
+ ${ + config.showWatermelonGrade + ? `${config.dealerName || ""}-${config.watermelonGrade || ""}` + : config.dealerName || "" + } +
+ `; + } else { + htmlString += `
`; + } + + if (config.showDestination || config.showVehicleNumber) { + htmlString += ` +
+ ${config.destination || ""} ${config.vehicleNumber || ""} +
+ `; + } else { + htmlString += `
`; + } + + htmlString += ` +
+
+ `; + } + + if (module.type === "shippingInfo") { + htmlString += `
`; + + if (config.showShippingFrom) { + htmlString += ` +
发货地:
+
+ ${config.shippingFrom || ""} +
+ `; + } + + if (config.showDate) { + htmlString += ` +
日期:
+
+ ${config.date || ""} +
+ `; + } + + htmlString += `
`; + } + + if (module.type === "weightInfo") { + if (config.data) { + config.data.forEach((item: any) => { + htmlString += `
`; + + if (config.showNetWeight) { + htmlString += ` +
+
净重:
+
+ ${item.netWeight || ""} +
+
+ ${config.netWeightUnit === "1" ? "斤" : "公斤"} +
+
+ `; + } + + if (config.showBoxWeight) { + htmlString += ` +
+
箱重:
+
+ ${item.boxWeight || ""} +
+
+ ${config.boxWeightUnit === "1" ? "斤" : "公斤"} +
+
+ `; + } + + if (config.showGrossWeight) { + htmlString += ` +
+
毛重:
+
+ ${item.grossWeight || ""} +
+
+ ${config.grossWeightUnit === "1" ? "斤" : "公斤"} +
+
+ `; + } + + if (config.showUnitPrice) { + htmlString += ` +
+
单价:
+
+ ${item.unitPrice || ""} +
+
+ ${config.unitPriceUnit === "1" ? "元/斤" : "元/公斤"} +
+
+ `; + } + + if (config.showAmount) { + htmlString += ` +
+
金额:
+
+ ${item.totalAmount || ""} +
+
+
+ `; + } + + if (config.showGrade) { + htmlString += ` +
+
品级:
+
+ ${item.watermelonGrade || ""} +
+
+ `; + } + + htmlString += `
`; + }); + } + + htmlString += `
`; + + if (config.showAccountCompany) { + htmlString += ` +
+
入账公司:
+
+ ${config.accountCompany || ""} +
+
+ `; + } + + if (config.showSumAmount) { + htmlString += ` +
+
总计:
+
+ ${config.sumAmount || ""} +
+
+
+ `; + } + + htmlString += `
`; + } + + if (module.type === "packingSpec") { + htmlString += `
`; + // 计算需要显示的列数 + const visibleColumnCount = + [ + config.showBoxType, + config.showQuantity, + config.showUnitPrice, + config.showAmount, + config.showUnitWeight, + config.showWeight, + ].filter(Boolean).length + 1; // +1 是因为"规格:"列总是显示 + + const gridClass = `grid w-full gap-0 text-base grid-cols-${visibleColumnCount}`; + + htmlString += ` +
+
规格:
+
+ `; + + htmlString += `
`; + + if (config.columns) { + config.columns.forEach((column: any, index: number) => { + if (index === 0) { + htmlString += `
 
`; + return; + } + + if ( + (column.dataIndex === "boxType" && config.showBoxType) || + (column.dataIndex === "quantity" && config.showQuantity) || + (column.dataIndex === "unitPrice" && config.showUnitPrice) || + (column.dataIndex === "amount" && config.showAmount) || + (column.dataIndex === "unitWeight" && config.showUnitWeight) || + (column.dataIndex === "weight" && config.showWeight) + ) { + htmlString += ` +
${column.title || ""}
+ `; + } + }); + } + + htmlString += `
`; + htmlString += `
`; + + if (config.data) { + config.data.forEach((item: any, index: number) => { + htmlString += ` +
+
+ ${ + item.boxCategory === "FOUR_GRAIN" + ? "4粒装" + : item.boxCategory === "TWO_GRAIN" + ? "2粒装" + : "未知" + } +
+ `; + + htmlString += ` +
${item.boxType || ""}
+ `; + + if (config.showQuantity) { + htmlString += ` +
${item.quantity || ""}
+ `; + } + + if (config.showUnitPrice) { + htmlString += ` +
${item.unitPrice || ""}
+ `; + } + + if (config.showAmount) { + htmlString += ` +
${item.amount || ""}
+ `; + } + + if (config.showUnitWeight) { + htmlString += ` +
${item.unitWeight || ""}
+ `; + } + + if (config.showWeight) { + htmlString += ` +
${item.weight || ""}
+ `; + } + + htmlString += `
`; + }); + } + + htmlString += ` +
+
总件数
+ `; + + if (config.showQuantity) { + htmlString += ` +
+ ${ + config.data?.reduce( + (acc: any, cur: any) => acc + Number(cur.quantity || 0), + 0, + ) || 0 + } +
+ `; + } + + if (config.showUnitPrice) { + htmlString += ` +
+
+ `; + } + + if (config.showAmount) { + htmlString += ` +
+ ${ + config.data?.reduce( + (acc: any, cur: any) => acc + Number(cur.amount || 0), + 0, + ) || 0 + } +
+ `; + } + + if (config.showUnitWeight) { + htmlString += ` +
+
+ `; + } + + if (config.showWeight) { + htmlString += ` +
+ ${ + config.data?.reduce( + (acc: any, cur: any) => acc + Number(cur.weight || 0), + 0, + ) || 0 + } +
+ `; + } + + htmlString += ` +
+
+ `; + htmlString += `
`; + } + + if (module.type === "vehicleInfo") { + htmlString += `
`; + + if (config.showDriverPhone) { + htmlString += ` +
司机号码:
+
+ ${config.driverPhone || ""} +
+ `; + } + + if (config.showLicensePlate) { + htmlString += ` +
车牌:
+
+ ${config.licensePlate || ""} +
+ `; + } + + if (config.showEstimatedArrivalTime) { + htmlString += ` +
预计到仓时间:
+
+ ${config.estimatedArrivalTime || ""} +
+ `; + } + + if (config.showRemarks) { + htmlString += ` +
备注:
+
+ ${config.remarks || ""} +
+ `; + } + + if (config.showFreightDebt) { + htmlString += ` +
+ ${config.freightDebtTitle || "运费欠"}: +
+
+ ${config.freightDebt || ""} +
+ `; + } + + if (config.showStrawMatDebt) { + htmlString += ` +
草帘欠:
+
+ ${config.strawMatDebt || ""} +
+ `; + } + + htmlString += `
`; + } + + if (module.type === "otherFees") { + htmlString += `
`; + + if (config.feeItems) { + config.feeItems.forEach((feeType: any) => { + htmlString += ` +
+ ${(config.feeLabels && config.feeLabels[feeType]) || ""}: +
+
+ ${config[feeType] || ""}元 +
+ `; + }); + } + + htmlString += `
`; + } + + if (module.type === "totalAmount") { + htmlString += `
`; + + if (config.showTotalAmount) { + htmlString += ` +
+ ${config.sumTitle || "合计金额"}: +
+
+ ${config.amount || ""} +
+
+ `; + } + + if (config.showFarmer) { + htmlString += ` +
瓜农:
+
+ ${config.farmer || ""} +
+ `; + } + + htmlString += `
`; + } + + if (module.type === "otherInfo") { + htmlString += ` +
+
产地:
+
+ ${config.origin || ""} +
+
供应商:
+
+ ${config.supplier || ""} +
+
发车时间:
+
+ ${config.departureTime || ""} +
+
到达时间:
+
+ ${config.arrivalTime || ""} +
+
产品名称:
+
+ ${config.productName || ""} +
+
+ `; + } + }); + + htmlString += `
`; + return htmlString; + }; + + // 截图预览内容 + const capturePreview = async () => { + // 请求 poster.qilincloud168.com/api/v1/pdfs 接口 请求参数是 html 内容 的json 格式响应结果为 + // 调用API生成PDF + const { data } = await Taro.request({ + url: "https://poster.qilincloud168.com/api/v1/pdf", + method: "POST", + header: { + "content-type": "application/json", + }, + data: { + html: generateHtmlString(), + }, + }); + + if (data && data.path) { + setPreviewUrl(data.path); + // 保存到系统相册 + Taro.downloadFile({ + url: data.path, + }).then((downloadRes) => { + if (downloadRes.tempFilePath) { + Taro.saveImageToPhotosAlbum({ + filePath: downloadRes.tempFilePath, + }) + .then(() => { + Taro.showToast({ + title: "发货单据已保存到相册,您可以将生成的PDF发送给好友", + icon: "none", + duration: 3000, + }); + }) + .catch((e) => { + console.log("e", e); + Taro.showToast({ + title: "保存失败", + icon: "none", + duration: 3000, + }); + }); + } + }); + } + }; + + // 返回上一步 + const goToPrevStep = () => { if (step > 0) { setStep(step - 1); } }; + // 重置表单 + const resetForm = () => { + setStep(1); + }; + + // 重新生成单据 + const regenerateDocument = () => { + setStep(1); + }; + // 放大 const zoomIn = () => { setScale((prev) => Math.min(prev + 0.1, 3)); // 最大放大到3倍 @@ -466,6 +1335,44 @@ export default hocAuth(function Page(props: CommonComponent) { const topBarRef = useRef(null); const bottomBarRef = useRef(null); + // 处理下载功能 + const handleDownload = async () => { + Taro.showToast({ + title: "正在生成并下载发货单据", + icon: "none", + duration: 2000, + }); + // 调用capturePreview函数生成并下载PDF + await capturePreview(); + }; + + // 处理分享功能 + const handleShare = async () => { + Taro.showToast({ + title: "正在分享到微信", + icon: "none", + duration: 2000, + }); + + if (previewUrl) { + // 保存到系统相册 + Taro.downloadFile({ + url: previewUrl, + }).then((downloadRes) => { + console.log("downloadRes", downloadRes); + if (downloadRes.tempFilePath) { + // 下载完成后转发 + Taro.shareFileMessage({ + filePath: downloadRes.tempFilePath, + complete: (res) => { + console.log("complete", res); + }, + }); + } + }); + } + }; + useEffect(() => { // 获取 topBar 和 bottomBar 的实际高度 const queryTopBar = Taro.createSelectorQuery(); @@ -599,7 +1506,7 @@ export default hocAuth(function Page(props: CommonComponent) { "preview grid w-full grid-cols-8 gap-0 text-2xl font-bold" } > - + {config.title} @@ -616,7 +1523,7 @@ export default hocAuth(function Page(props: CommonComponent) { > {config.showDealerName || config.showWatermelonGrade ? ( - + {config.showWatermelonGrade ? `${config.dealerName}-${config.watermelonGrade}` : config.dealerName} @@ -625,7 +1532,7 @@ export default hocAuth(function Page(props: CommonComponent) { )} {config.showDestination || config.showVehicleNumber ? ( - + {config.destination} {config.vehicleNumber} @@ -647,20 +1554,20 @@ export default hocAuth(function Page(props: CommonComponent) { > {config.showShippingFrom && ( <> - + 发货地: - + {config.shippingFrom} )} {config.showDate && ( <> - + 日期: - + {config.date} @@ -825,7 +1732,7 @@ export default hocAuth(function Page(props: CommonComponent) { })} >
规格:
@@ -869,7 +1776,7 @@ export default hocAuth(function Page(props: CommonComponent) { return (
{column.title}
@@ -898,9 +1805,7 @@ export default hocAuth(function Page(props: CommonComponent) { }, )} > - + {item.boxCategory === "FOUR_GRAIN" ? "4粒装" : item.boxCategory === "TWO_GRAIN" @@ -908,17 +1813,13 @@ export default hocAuth(function Page(props: CommonComponent) { : "未知"} - {config.showBoxType && ( - - {item.boxType} - - )} + + {item.boxType} + {config.showQuantity && ( {item.quantity} @@ -926,7 +1827,7 @@ export default hocAuth(function Page(props: CommonComponent) { {config.showUnitPrice && ( {item.unitPrice} @@ -934,7 +1835,7 @@ export default hocAuth(function Page(props: CommonComponent) { {config.showAmount && ( {item.amount} @@ -942,7 +1843,7 @@ export default hocAuth(function Page(props: CommonComponent) { {config.showUnitWeight && ( {item.unitWeight} @@ -950,7 +1851,7 @@ export default hocAuth(function Page(props: CommonComponent) { {config.showWeight && ( {item.weight} @@ -974,19 +1875,53 @@ export default hocAuth(function Page(props: CommonComponent) { )} > 总件数 - - {config.data?.reduce( - (acc: any, cur: any) => - acc + Number(cur.quantity), - 0, - )} - + {config.showQuantity && ( + + {config.data?.reduce( + (acc: any, cur: any) => + acc + Number(cur.quantity), + 0, + )} + + )} + {config.showUnitPrice && ( + + )} + {config.showAmount && ( + + {config.data?.reduce( + (acc: any, cur: any) => + acc + Number(cur.amount), + 0, + )} + + )} + {config.showUnitWeight && ( + + )} + {config.showWeight && ( + + {config.data?.reduce( + (acc: any, cur: any) => + acc + Number(cur.weight), + 0, + )} + + )}
@@ -1003,60 +1938,60 @@ export default hocAuth(function Page(props: CommonComponent) { > {config.showDriverPhone && ( <> - + 司机号码: - + {config.driverPhone} )} {config.showLicensePlate && ( <> - + 车牌: - + {config.licensePlate} )} {config.showEstimatedArrivalTime && ( <> - + 预计到仓时间: - + {config.estimatedArrivalTime} )} {config.showRemarks && ( <> - + 备注: - + {config.remarks} )} {config.showFreightDebt && ( <> - + {config.freightDebtTitle || "运费欠"}: - + {config.freightDebt} )} {config.showStrawMatDebt && ( <> - + 草帘欠: - + {config.strawMatDebt} @@ -1075,10 +2010,10 @@ export default hocAuth(function Page(props: CommonComponent) { > {config.feeItems?.map((feeType: any) => ( <> - + {config.feeLabels[feeType]}: - + {config[feeType]}元 @@ -1097,23 +2032,23 @@ export default hocAuth(function Page(props: CommonComponent) { > {config.showTotalAmount && ( <> - + {config.sumTitle || "合计金额"}: - + {config.amount} - + )} {config.showFarmer && ( <> - + 瓜农: - + {config.farmer} @@ -1130,34 +2065,34 @@ export default hocAuth(function Page(props: CommonComponent) { "preview grid w-full grid-cols-8 gap-0 text-base" } > - + 产地: - + {config.origin} - + 供应商: - + {config.supplier} - + 发车时间: - + {config.departureTime} - + 到达时间: - + {config.arrivalTime} - + 产品名称: - + {config.productName} @@ -1168,6 +2103,49 @@ export default hocAuth(function Page(props: CommonComponent) { )} + + {step === 3 && previewUrl && ( + + + + + + 发货单据生成成功 + + + 您的发货单据已生成,可以下载或转发给好友 + + + + + + + + + + + + + 提示:您可以随时在发货单据列表中重新下载 + + + )} {/* 步骤控制按钮 */} @@ -1179,32 +2157,71 @@ export default hocAuth(function Page(props: CommonComponent) { {step == 1 && ( - )} {step == 1 && ( - )} {step == 2 && ( - )} {step == 2 && ( - )} + {step == 3 && ( + + + + )} + {step == 3 && ( + + + + )} diff --git a/packages/app-client/src/services/business/typings.d.ts b/packages/app-client/src/services/business/typings.d.ts index e4f3c55..d5e1719 100644 --- a/packages/app-client/src/services/business/typings.d.ts +++ b/packages/app-client/src/services/business/typings.d.ts @@ -2299,7 +2299,7 @@ declare namespace BusinessAPI { /** 销售单价(元/个) */ boxSalePrice?: number; /** 箱子类型:1_本次使用;2_额外运输;3_已使用额外运输;4_车上剩余; */ - boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN"; + boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN" | "OWN"; }; type OrderRebate = { diff --git a/packages/app-client/src/utils/format.ts b/packages/app-client/src/utils/format.ts index cd26d4d..36be95b 100644 --- a/packages/app-client/src/utils/format.ts +++ b/packages/app-client/src/utils/format.ts @@ -54,10 +54,7 @@ export function convertSeconds(seconds, outputUnit) { // 格式化金额显示 export const formatCurrency = (value: number) => { - if (value === null) { - return "0.00"; - } - return Number(value).toFixed(2)?.toLocaleString(); + return Number(value || 0)?.toLocaleString(); }; export const validatePrice = (value: string) => {