diff --git a/packages/app-client/src/components/order/index.ts b/packages/app-client/src/components/order/index.ts index 3cae39e..e88b19f 100644 --- a/packages/app-client/src/components/order/index.ts +++ b/packages/app-client/src/components/order/index.ts @@ -34,8 +34,11 @@ export { default as OrderWithdrawReview } from "./button/OrderWithdrawReview"; export { default as OrderFinalApprove } from "./button/OrderFinalApprove"; export { default as CompanyInfoSection } from "./section/CompanyInfoSection"; +export type { CompanyInfoSectionRef } from "./section/CompanyInfoSection"; export { default as DealerInfoSection } from "./section/DealerInfoSection"; +export type { DealerInfoSectionRef } from "./section/DealerInfoSection"; export { default as BasicInfoSection } from "./section/BasicInfoSection"; +export type { BasicInfoSectionRef } from "./section/BasicInfoSection"; export { default as SupplierInfoSection } from "./section/SupplierInfoSection"; export { default as PurchaseCostInfoSection } from "./section/PurchaseCostInfoSection"; export { default as PackageInfoSection } from "./section/PackageInfoSection"; @@ -44,6 +47,7 @@ export { default as PackagingCostSection } from "./section/PackagingCostSection" export { default as ProductionLossSection } from "./section/ProductionLossSection"; export { default as CostSummarySection } from "./section/CostSummarySection"; export { default as MarketPriceSection } from "./section/MarketPriceSection"; +export type { MarketPriceSectionRef } from "./section/MarketPriceSection"; export { default as RebateCalcSection } from "./section/RebateCalcSection"; export { default as TaxSubsidySection } from "./section/TaxSubsidySection"; export { default as TaxProvisionSection } from "./section/TaxProvisionSection"; @@ -52,6 +56,7 @@ export { default as MaterialCostSection } from "./section/MaterialCostSection"; export { default as ProductionAdvanceSection } from "./section/ProductionAdvanceSection"; export { default as WorkerAdvanceSection } from "./section/WorkerAdvanceSection"; export { default as DeliveryFormSection } from "./section/DeliveryFormSection"; +export type { DeliveryFormSectionRef } from "./section/DeliveryFormSection"; export { default as PurchaseFormSection } from "./section/PurchaseFormSection"; export { default as PurchaseStep1Form } from "./document/Step1Form"; diff --git a/packages/app-client/src/components/order/section/BasicInfoSection.tsx b/packages/app-client/src/components/order/section/BasicInfoSection.tsx index 7fc69f8..334834d 100644 --- a/packages/app-client/src/components/order/section/BasicInfoSection.tsx +++ b/packages/app-client/src/components/order/section/BasicInfoSection.tsx @@ -9,21 +9,45 @@ import { SafeArea, } from "@nutui/nutui-react-taro"; import dayjs from "dayjs"; -import { useEffect, useState } from "react"; +import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import businessServices from "@/services/business"; import { Icon } from "@/components"; import { generateShortId } from "@/utils"; -export default function BasicInfoSection(props: { - orderVO: BusinessAPI.OrderVO; - onChange?: (orderVO: BusinessAPI.OrderVO) => void; - costList: BusinessAPI.CostVO[]; - readOnly?: boolean; -}) { +export interface BasicInfoSectionRef { + validateForm: () => string | null; +} + +export default forwardRef< + BasicInfoSectionRef, + { + orderVO: BusinessAPI.OrderVO; + onChange?: (orderVO: BusinessAPI.OrderVO) => void; + costList: BusinessAPI.CostVO[]; + readOnly?: boolean; + } +>((props, ref) => { const { orderVO, onChange, readOnly, costList } = props; const { orderVehicle } = orderVO; + // 暴露 validateForm 方法 + useImperativeHandle(ref, () => ({ + validateForm: () => { + // 校验本车次号 + if (!orderVehicle?.vehicleNo) { + return "请输入本车次号"; + } + + // 校验运费类型 + if (!orderVehicle?.priceType) { + return "请选择运费类型"; + } + + return null; + }, + })); + // 当天和未来10天 const startDate = new Date(); const endDate = new Date(startDate.getTime() + 86400000 * 10); @@ -809,4 +833,4 @@ export default function BasicInfoSection(props: { ); -} +}); diff --git a/packages/app-client/src/components/order/section/CompanyInfoSection.tsx b/packages/app-client/src/components/order/section/CompanyInfoSection.tsx index 3c3d6d5..7061646 100644 --- a/packages/app-client/src/components/order/section/CompanyInfoSection.tsx +++ b/packages/app-client/src/components/order/section/CompanyInfoSection.tsx @@ -1,15 +1,33 @@ -import { useEffect, useState } from "react"; +import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { View } from "@tarojs/components"; import { CompanyPicker, Icon } from "@/components"; import { Button } from "@nutui/nutui-react-taro"; -export default function CompanyInfoSection(props: { - orderVO: BusinessAPI.OrderVO; - onChange?: (orderVO: BusinessAPI.OrderVO) => void; - readOnly?: boolean; -}) { +export interface CompanyInfoSectionRef { + validateForm: () => string | null; +} + +export default forwardRef< + CompanyInfoSectionRef, + { + orderVO: BusinessAPI.OrderVO; + onChange?: (orderVO: BusinessAPI.OrderVO) => void; + readOnly?: boolean; + } +>((props, ref) => { const { orderVO, onChange, readOnly } = props; + // 暴露 validateForm 方法 + useImperativeHandle(ref, () => ({ + validateForm: () => { + // 校验销售方 + if (!orderVO?.orderCompany?.companyId) { + return "请选择销售方"; + } + return null; + }, + })); + const [orderCompany, setOrderCompany] = useState(); // 当 orderVO 变化时,更新默认显示的公司信息 @@ -84,4 +102,4 @@ export default function CompanyInfoSection(props: { )} ); -} +}); diff --git a/packages/app-client/src/components/order/section/DealerInfoSection.tsx b/packages/app-client/src/components/order/section/DealerInfoSection.tsx index 58ebb16..c7e3c0a 100644 --- a/packages/app-client/src/components/order/section/DealerInfoSection.tsx +++ b/packages/app-client/src/components/order/section/DealerInfoSection.tsx @@ -1,15 +1,33 @@ -import { useEffect, useState } from "react"; +import { forwardRef, useEffect, useImperativeHandle, useState } from "react"; import { View } from "@tarojs/components"; import { DealerPicker, Icon } from "@/components"; import { Button } from "@nutui/nutui-react-taro"; -export default function DealerInfoSection(props: { - orderVO: BusinessAPI.OrderVO; - onChange?: (orderDealer: BusinessAPI.OrderVO) => void; - readOnly?: boolean; -}) { +export interface DealerInfoSectionRef { + validateForm: () => string | null; +} + +export default forwardRef< + DealerInfoSectionRef, + { + orderVO: BusinessAPI.OrderVO; + onChange?: (orderDealer: BusinessAPI.OrderVO) => void; + readOnly?: boolean; + } +>((props, ref) => { const { orderVO, onChange, readOnly } = props; + // 暴露 validateForm 方法 + useImperativeHandle(ref, () => ({ + validateForm: () => { + // 校验经销商 + if (!orderVO?.orderDealer?.dealerId) { + return "请选择经销商"; + } + return null; + }, + })); + const [orderDealer, setOrderDealer] = useState(); // 添加经销商名称状态(用于只有名称没有ID的情况) @@ -87,4 +105,4 @@ export default function DealerInfoSection(props: { )} ); -} +}); diff --git a/packages/app-client/src/components/order/section/DeliveryFormSection.tsx b/packages/app-client/src/components/order/section/DeliveryFormSection.tsx index 36b3941..7f36bc9 100644 --- a/packages/app-client/src/components/order/section/DeliveryFormSection.tsx +++ b/packages/app-client/src/components/order/section/DeliveryFormSection.tsx @@ -1,16 +1,34 @@ -import { useEffect, useState } from "react"; -import { DeliveryStep1Form, DeliveryStep2Preview, Icon } from "@/components"; +import { + forwardRef, + useEffect, + useImperativeHandle, + useRef, + useState, +} from "react"; +import { + DeliveryStep1Form, + DeliveryStep1FormRef, + DeliveryStep2Preview, + Icon, +} from "@/components"; import { convertOrderShipVOToExamplesFormat } from "@/utils"; import { business } from "@/services"; import { Button, Popup } from "@nutui/nutui-react-taro"; import { View } from "@tarojs/components"; -export default function DeliveryFormSection(props: { - orderVO: BusinessAPI.OrderVO; - onChange?: (orderDealer: BusinessAPI.OrderVO) => void; - readOnly?: boolean; - costList: BusinessAPI.CostVO[]; -}) { +export interface DeliveryFormSectionRef { + validateForm: () => string | null; +} + +export default forwardRef< + DeliveryFormSectionRef, + { + orderVO: BusinessAPI.OrderVO; + onChange?: (orderDealer: BusinessAPI.OrderVO) => void; + readOnly?: boolean; + costList: BusinessAPI.CostVO[]; + } +>((props, ref) => { const { orderVO, onChange, readOnly, costList } = props; const [template, setTemplate] = useState([]); @@ -90,6 +108,21 @@ export default function DeliveryFormSection(props: { setPreviewVisible(true); }; + const step1FormRef = useRef(null); + + // 暴露 validateForm 方法 + useImperativeHandle(ref, () => ({ + validateForm: () => { + // 调用 step1FormRef 的 validateForm 方法 + if (step1FormRef.current && step1FormRef.current.validateForm) { + if (!step1FormRef.current.validateForm()) { + return "发货单复核填写错误"; + } + } + return null; + }, + })); + if (!template) { return; } @@ -99,6 +132,7 @@ export default function DeliveryFormSection(props: { return ( <> ); -} +}); diff --git a/packages/app-client/src/components/order/section/MarketPriceSection.tsx b/packages/app-client/src/components/order/section/MarketPriceSection.tsx index 82c0100..f2912a0 100644 --- a/packages/app-client/src/components/order/section/MarketPriceSection.tsx +++ b/packages/app-client/src/components/order/section/MarketPriceSection.tsx @@ -3,15 +3,42 @@ import { Radio } from "@nutui/nutui-react-taro"; import { OrderCalculator } from "@/utils"; import { Icon, PriceEditor } from "@/components"; import classNames from "classnames"; +import { forwardRef, useImperativeHandle } from "react"; -export default function MarketPriceSection(props: { - orderVO: BusinessAPI.OrderVO; - onChange?: (orderVO: BusinessAPI.OrderVO) => void; - readOnly?: boolean; - calculator: OrderCalculator; -}) { +export interface MarketPriceSectionRef { + validateForm: () => string | null; +} + +export default forwardRef< + MarketPriceSectionRef, + { + orderVO: BusinessAPI.OrderVO; + onChange?: (orderVO: BusinessAPI.OrderVO) => void; + readOnly?: boolean; + calculator: OrderCalculator; + } +>((props, ref) => { const { orderVO, onChange, readOnly, calculator } = props; + // 暴露 validateForm 方法 + useImperativeHandle(ref, () => ({ + validateForm: () => { + // 校验报价方式 + if (!orderVO?.pricingMethod) { + return "请选择市场报价的报价方式"; + } + + // 校验销售单价 + for (const supplier of orderVO.orderSupplierList || []) { + if (!supplier.salePrice || supplier.salePrice <= 0) { + return `请填写${supplier.name}的销售单价`; + } + } + + return null; + }, + })); + // 销售金额 const saleAmount = calculator.getSalesAmount(); @@ -242,4 +269,4 @@ export default function MarketPriceSection(props: { ); -} +}); diff --git a/packages/app-client/src/constant/app.ts b/packages/app-client/src/constant/app.ts index 2399494..d55325f 100644 --- a/packages/app-client/src/constant/app.ts +++ b/packages/app-client/src/constant/app.ts @@ -1,2 +1,2 @@ // App 相关常量 -export const APP_VERSION = "v0.0.64"; +export const APP_VERSION = "v0.0.65"; diff --git a/packages/app-client/src/pages/approval/audit.tsx b/packages/app-client/src/pages/approval/audit.tsx index 0c4fad1..1b6ab8b 100644 --- a/packages/app-client/src/pages/approval/audit.tsx +++ b/packages/app-client/src/pages/approval/audit.tsx @@ -260,25 +260,27 @@ export default hocAuth(function Page(props: CommonComponent) { )} - {orderVO.orderDealer?.taxProvision && ( - - 计提税金 - - ¥{orderVO.orderDealer?.taxProvision} 元 + {orderVO.orderDealer?.taxProvision && + orderVO.orderDealer?.taxProvision > 0 && ( + + 计提税金 + + ¥{orderVO.orderDealer?.taxProvision} 元 + - - )} + )} - {orderVO.orderDealer?.taxSubsidy && ( - - 公司返点 - - ¥{orderVO.orderDealer?.taxSubsidy} 元 + {orderVO.orderDealer?.taxSubsidy && + orderVO.orderDealer?.taxSubsidy > 0 && ( + + 公司返点 + + ¥{orderVO.orderDealer?.taxSubsidy} 元 + - - )} + )} - {orderVO.orderRebate?.amount && ( + {orderVO.orderRebate?.amount && orderVO.orderRebate?.amount > 0 && ( 个人返点 @@ -287,14 +289,15 @@ export default hocAuth(function Page(props: CommonComponent) { )} - {orderVO.orderDealer?.costDifference && ( - - 调诚信志远分成 - - ¥{orderVO.orderDealer?.costDifference} 元 + {orderVO.orderDealer?.costDifference && + orderVO.orderDealer?.costDifference > 0 && ( + + 调诚信志远分成 + + ¥{orderVO.orderDealer?.costDifference} 元 + - - )} + )} diff --git a/packages/app-client/src/pages/audit/audit.tsx b/packages/app-client/src/pages/audit/audit.tsx index 565ce52..20c4d4b 100644 --- a/packages/app-client/src/pages/audit/audit.tsx +++ b/packages/app-client/src/pages/audit/audit.tsx @@ -2,7 +2,7 @@ import hocAuth from "@/hocs/auth"; import { CommonComponent } from "@/types/typings"; import Taro from "@tarojs/taro"; import { business } from "@/services"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { View } from "@tarojs/components"; import { ActionSheet, @@ -14,14 +14,19 @@ import { } from "@nutui/nutui-react-taro"; import { BasicInfoSection, + BasicInfoSectionRef, CompanyInfoSection, + CompanyInfoSectionRef, CostDifferenceSection, CostSummarySection, DealerInfoSection, + DealerInfoSectionRef, DeliveryFormSection, + DeliveryFormSectionRef, EmptyBoxInfoSection, Icon, MarketPriceSection, + MarketPriceSectionRef, MaterialCostSection, OrderRejectApprove, PackageInfoSection, @@ -47,6 +52,15 @@ import classNames from "classnames"; import { DecimalUtils } from "@/utils/classes/calculators/core/DecimalUtils"; import order from "@/constant/order"; +// Section ref 类型 +type SectionRef = + | BasicInfoSectionRef + | CompanyInfoSectionRef + | DealerInfoSectionRef + | MarketPriceSectionRef + | DeliveryFormSectionRef + | null; + const defaultSections = [ "marketPrice", "supplierInfo", @@ -234,6 +248,9 @@ export default hocAuth(function Page(props: CommonComponent) { const orderId = router.params.orderId as BusinessAPI.OrderVO["orderId"]; const auditId = router.params.auditId as BusinessAPI.AuditVO["auditId"]; + // Section refs 映射 + const sectionRefs = useRef>({}); + // 费用项目列表 const [costList, setCostList] = useState([]); @@ -313,13 +330,24 @@ export default hocAuth(function Page(props: CommonComponent) { // 关闭对话框 setSubmitDialogVisible(false); - // 表单校验 - const errorMsg = validateForm(); - if (errorMsg) { + // 先调用所有 section 的 validateForm 方法进行校验 + const sectionErrors: string[] = []; + Object.entries(sectionRefs.current).forEach( + ([_sectionName, sectionRef]) => { + if (sectionRef && sectionRef.validateForm) { + const error = sectionRef.validateForm(); + if (error) { + sectionErrors.push(error); + } + } + }, + ); + + if (sectionErrors.length > 0) { Toast.show("toast", { icon: "fail", title: "校验失败", - content: errorMsg, + content: sectionErrors[0], }); return; } @@ -435,43 +463,6 @@ export default hocAuth(function Page(props: CommonComponent) { } }; - // 表单校验 - const validateForm = () => { - // 校验销售方 - if (!orderVO?.orderCompany?.companyId) { - return "请选择销售方"; - } - - // 校验经销商 - if (!orderVO?.orderDealer?.dealerId) { - return "请选择经销商"; - } - - // 校验本车次号 - if (!orderVO?.orderVehicle?.vehicleNo) { - return "请输入本车次号"; - } - - // 校验运费类型 - if (!orderVO?.orderVehicle?.priceType) { - return "请选择运费类型"; - } - - // 校验市场报价的报价方式 - if (!orderVO?.pricingMethod) { - return "请选择市场报价的报价方式"; - } - - // 校验市场报价的销售单价 - orderVO.orderSupplierList.forEach((supplier: BusinessAPI.OrderSupplier) => { - if (!supplier.salePrice || supplier.salePrice <= 0) { - return "请填写市场报价的销售单价"; - } - }); - - return null; - }; - const init = async ( orderId: BusinessAPI.OrderVO["orderId"], auditId: BusinessAPI.AuditVO["auditId"], @@ -824,6 +815,11 @@ export default hocAuth(function Page(props: CommonComponent) { className={`overflow-x-auto rounded-md rounded-b-lg bg-white p-2.5 shadow-sm`} > { + if (ref) { + sectionRefs.current[section.name] = ref; + } + }} readOnly={ !( orderVO.state === "AUDITING" &&