ERPTurbo_Client/packages/app-client/src/pages/purchase/order/audit.tsx
shenyifei 1ac1564ec2 feat(purchase): 添加拼车选择功能并优化UI细节
- 在MelonFarmer组件中新增supplierCount属性用于判断是否显示拼车选项
- 修改"是否为最后一个瓜农"为"是否要拼车"的逻辑与文案
- 更新多个图标引用,包括新增address-book图标
- 调整输入框样式,增加图标前缀提升用户体验
- 优化称重信息页面的提示文字,使其更清晰易懂
- 增加OrderPackage相关类型定义及转换工具函数
- 更新页面审核和创建流程中的样式与交互逻辑
- 升级iconfont字体文件版本,支持新图标
- 修复部分组件样式问题,如SupplierList底部间距等
2025-11-04 22:36:45 +08:00

545 lines
17 KiB
TypeScript

import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import Taro, { useDidShow } from "@tarojs/taro";
import { business } from "@/services";
import { useEffect, useState } from "react";
import { View } from "@tarojs/components";
import dayjs from "dayjs";
import purchaseOrder from "@/constant/purchaseOrder";
import {
Button,
Dialog,
SafeArea,
Toast,
Input,
} from "@nutui/nutui-react-taro";
import { formatCurrency } from "@/utils/format";
import {
BasicInfoSection,
CompanyInfoSection,
CostSummarySection,
DealerInfoSection,
EmptyBoxInfoSection,
LaborInfoSection,
MarketPriceSection,
PackageInfoSection,
PackagingCostSection,
ProfitCalcSection,
PurchaseCostInfoSection,
PurchaseOrderRejectApprove,
PurchaseOrderRejectFinal,
PurchaseOrderFinalApprove,
SupplierInfoSection,
RebateCalcSection,
} from "@/components";
import {
getMelonCost1,
getTotalGrossWeight,
getTotalNetWeight,
} from "@/utils/calcutePurchaseOrder";
const sectionList = {
// 销售方信息卡片
supplierInfo: CompanyInfoSection,
// 下游经销商信息
dealerInfo: DealerInfoSection,
// 基础信息
basicInfo: BasicInfoSection,
// 瓜农信息
farmerInfo: SupplierInfoSection,
// 采购成本
purchaseCostInfo: PurchaseCostInfoSection,
// 包装纸箱费
packageInfo: PackageInfoSection,
// 空箱费用
emptyBoxInfo: EmptyBoxInfoSection,
// 用工信息
laborInfo: LaborInfoSection,
// 包装费
packagingCost: PackagingCostSection,
// 成本合计
costSummary: CostSummarySection,
// 市场报价
marketPrice: MarketPriceSection,
// 返点计算
rebateCalc: RebateCalcSection,
// 利润计算
profitCalc: ProfitCalcSection,
};
const sectionConfig = {
supplierInfo: {
title: "销售方信息",
containerClass: "border-l-8 border-l-blue-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
dealerInfo: {
title: "下游经销商信息",
containerClass: "border-l-8 border-l-green-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
basicInfo: {
title: "基础信息",
containerClass: "border-l-8 border-l-yellow-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
farmerInfo: {
title: "瓜农信息",
containerClass: "border-l-8 border-l-purple-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
purchaseCostInfo: {
title: "采购成本",
containerClass: "border-l-8 border-l-red-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
packageInfo: {
title: "包装纸箱费",
containerClass: "border-l-8 border-l-indigo-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
emptyBoxInfo: {
title: "空箱费用",
containerClass: "border-l-8 border-l-pink-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
laborInfo: {
title: "用工信息",
containerClass: "border-l-8 border-l-teal-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
packagingCost: {
title: "包装费",
containerClass: "border-l-8 border-l-orange-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
costSummary: {
title: "成本合计",
containerClass: "border-l-8 border-l-cyan-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
marketPrice: {
title: "市场报价",
containerClass: "border-l-8 border-l-lime-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
rebateCalc: {
title: "返点计算",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
profitCalc: {
title: "利润计算",
containerClass: "border-l-8 border-l-emerald-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
};
export default hocAuth(function Page(props: CommonComponent) {
const { router, isInitialized, setIsInitialized, role } = props;
const orderId = router.params
.orderId as BusinessAPI.PurchaseOrderVO["orderId"];
const [purchaseOrderVO, setPurchaseOrderVO] =
useState<BusinessAPI.PurchaseOrderVO>();
const [originPrincipal, setOriginPrincipal] = useState("");
console.log("purchaseOrderVO-----", purchaseOrderVO);
const [collapsedSections, setCollapsedSections] = useState<
Record<string, boolean>
>({
supplierInfo: false,
dealerInfo: false,
basicInfo: false,
farmerInfo: false,
purchaseCostInfo: false,
packageInfo: false,
emptyBoxInfo: false,
laborInfo: false,
packagingCost: false,
costSummary: false,
marketPrice: false,
rebateCalc: false,
profitCalc: false,
});
// 暂存和提交审核的Dialog状态
const [saveDialogVisible, setSaveDialogVisible] = useState(false);
const [submitDialogVisible, setSubmitDialogVisible] = useState(false);
const toggleSection = (section: string) => {
setCollapsedSections((prev) => ({
...prev,
[section]: !prev[section],
}));
};
// 暂存操作
const handleSave = () => {
setSaveDialogVisible(true);
};
// 确认暂存
const confirmSave = async () => {
// 关闭对话框
setSaveDialogVisible(false);
// 这里应该调用暂存API
console.log("暂存数据:", purchaseOrderVO);
const { data } = await business.purchaseOrder.approvePurchaseOrder({
...purchaseOrderVO!,
draft: true,
});
if (data.success) {
Toast.show("toast", {
icon: "success",
title: "提示",
content: "暂存成功",
});
// 返回采购单页面
Taro.switchTab({ url: "/pages/main/purchase/index" });
}
};
// 提交老板审核操作
const handleSubmit = () => {
setSubmitDialogVisible(true);
};
// 确认提交老板审核
const confirmSubmit = async () => {
// 关闭对话框
setSubmitDialogVisible(false);
// 表单校验
const errorMsg = validateForm();
if (errorMsg) {
Toast.show("toast", {
icon: "fail",
title: "校验失败",
content: errorMsg,
});
return;
}
// 这里应该调用提交审核API
console.log("提交老板审核:", purchaseOrderVO);
const { data } = await business.purchaseOrder.approvePurchaseOrder({
...purchaseOrderVO!,
draft: false,
});
if (data.success) {
Toast.show("toast", {
icon: "success",
title: "提示",
content: "暂存成功",
});
// 返回采购单页面
Taro.switchTab({ url: "/pages/main/purchase/index" });
}
};
// 表单校验
const validateForm = () => {
// 校验销售方
if (!purchaseOrderVO?.orderCompany?.companyId) {
return "请选择销售方";
}
// 校验经销商和仓库
if (
!purchaseOrderVO?.orderDealer?.dealerId ||
!purchaseOrderVO?.orderDealer?.warehouseId
) {
return "请选择经销商和仓库";
}
// 校验本车次号
if (!purchaseOrderVO?.orderVehicle?.vehicleNo) {
return "请输入本车次号";
}
// 校验运费类型
if (!purchaseOrderVO?.orderVehicle?.priceType) {
return "请选择运费类型";
}
// 校验市场报价的报价方式
if (!purchaseOrderVO?.pricingMethod) {
return "请选择市场报价的报价方式";
}
// 校验市场报价的销售单价
purchaseOrderVO.orderSupplierList.forEach(
(supplier: BusinessAPI.OrderSupplier) => {
if (!supplier.salePrice || supplier.salePrice <= 0) {
return "请填写市场报价的销售单价";
}
},
);
return null;
};
const init = async (orderId: BusinessAPI.PurchaseOrderVO["orderId"]) => {
const { data } = await business.purchaseOrder.showPurchaseOrder({
purchaseOrderShowQry: {
orderId,
},
});
if (data.success) {
setPurchaseOrderVO(data.data);
}
};
useEffect(() => {
if (orderId && !isInitialized) {
init(orderId).then(() => {
setIsInitialized(true);
});
}
}, []);
useDidShow(() => {
if (orderId && isInitialized) {
init(orderId).then();
}
});
if (!purchaseOrderVO) {
return;
}
return (
<View className={"flex flex-col gap-2.5"} id={"purchase-order-audit"}>
{/* 顶部导航 */}
<View className="bg-white p-4">
{/* 展示产地负责人*/}
<View className="mb-3 flex flex-row items-center gap-3">
<View className="flex-shrink-0 text-base font-bold text-gray-900">
</View>
<View
className={`flex h-12 w-full items-center rounded-lg border-2 border-gray-300 bg-gray-50 px-3`}
>
<Input
type="text"
disabled={
role === "boss" ||
(role === "reviewer" &&
purchaseOrderVO.state !== "WAITING_AUDIT")
}
placeholder="请输入产地负责人姓名"
value={originPrincipal || purchaseOrderVO.createdByName}
onChange={(value) => setOriginPrincipal(value)}
onBlur={() => {
// 更新采购订单中的产地负责人
setPurchaseOrderVO((prev) => ({
...prev!,
originPrincipal: originPrincipal,
}));
}}
className="w-full bg-transparent"
/>
</View>
</View>
<View className="mb-2 rounded-lg bg-blue-50 p-3 text-lg font-bold text-gray-800">
{`${purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}车 - ${purchaseOrderVO?.orderVehicle.origin}${purchaseOrderVO?.orderVehicle.destination}`}
</View>
{purchaseOrderVO?.orderDealer && (
<>
<View className="text-neutral-darker mb-1 text-sm font-medium">
: {purchaseOrderVO?.orderDealer?.shortName}
</View>
<View className="text-neutral-darker text-sm font-medium">
: {purchaseOrderVO?.orderDealer?.companyName}
</View>
</>
)}
</View>
{/* 状态栏 */}
<View className="flex flex-row items-center justify-between rounded-lg bg-white p-4 shadow-sm">
<View className="inline-block rounded-md border-1 bg-gradient-to-r from-amber-500 to-orange-500 px-4 py-2 text-sm font-bold text-white shadow">
{
purchaseOrder.stateList.find(
(item) => item.value === purchaseOrderVO?.state,
)?.title
}
</View>
<View className="text-neutral-darker text-right text-sm">
<View>
:{" "}
{dayjs(purchaseOrderVO?.createdAt).format("YYYY-MM-DD HH:mm")}
</View>
<View className="font-medium">
: {purchaseOrderVO?.orderVehicle.plate}
</View>
</View>
</View>
{/* 关键指标 */}
<View className="grid grid-cols-2 gap-3 rounded-lg bg-white p-4 shadow-sm">
<View className="rounded-lg border border-blue-200 bg-gradient-to-br from-blue-50 to-blue-100 p-3 text-center">
<View className="mb-1 text-lg font-bold text-blue-800">
{purchaseOrderVO?.orderSupplierList.length}
</View>
<View className="text-sm font-medium text-blue-600"></View>
</View>
<View className="rounded-lg border border-green-200 bg-gradient-to-br from-green-50 to-green-100 p-3 text-center">
<View className="mb-1 text-lg font-bold text-green-800">
{formatCurrency(getTotalGrossWeight(purchaseOrderVO))}
</View>
<View className="text-sm font-medium text-green-600"></View>
</View>
<View className="rounded-lg border border-purple-200 bg-gradient-to-br from-purple-50 to-purple-100 p-3 text-center">
<View className="mb-1 text-lg font-bold text-purple-800">
{formatCurrency(getTotalNetWeight(purchaseOrderVO))}
</View>
<View className="text-sm font-medium text-purple-600"></View>
</View>
<View className="rounded-lg border border-amber-200 bg-gradient-to-br from-amber-50 to-amber-100 p-3 text-center">
<View className="mb-1 text-lg font-bold text-amber-800">
{formatCurrency(getMelonCost1(purchaseOrderVO))}
</View>
<View className="text-sm font-medium text-amber-600">
西
</View>
</View>
</View>
{/* 循环渲染各部分内容 */}
{Object.keys(sectionList).map((sectionKey) => {
const SectionComponent = sectionList[sectionKey];
const config = sectionConfig[sectionKey];
const isCollapsed = collapsedSections[sectionKey];
return (
<View className="overflow-hidden bg-white" key={sectionKey}>
<View
className={`z-10 flex items-center justify-between p-4 shadow-sm ${config.containerClass}`}
onClick={() => toggleSection(sectionKey)}
>
<View className="text-base font-bold text-gray-800">
{config.title}
</View>
<View className="text-neutral-darker text-lg">
{isCollapsed ? "▲" : "▼"}
</View>
</View>
{!isCollapsed && (
<View className={config.contentClass}>
<SectionComponent
purchaseOrderVO={purchaseOrderVO}
onChange={setPurchaseOrderVO}
readOnly={
role === "boss" ||
(role === "reviewer" &&
purchaseOrderVO.state !== "WAITING_AUDIT")
}
/>
</View>
)}
</View>
);
})}
{/* 按钮操作 */}
<View className={"sticky bottom-0 z-10 bg-white"} id={"bottomBar"}>
<View className="flex justify-between gap-2 border-t border-gray-200 p-2.5">
{role === "reviewer" && purchaseOrderVO.state === "WAITING_AUDIT" && (
<>
<View className={"flex-1"}>
<PurchaseOrderRejectApprove
purchaseOrderVO={purchaseOrderVO}
size={"xlarge"}
onFinish={() => {
// 返回首页
Taro.switchTab({ url: "/pages/main/purchase/index" });
}}
/>
</View>
<View className={"flex-1"}>
<Button
block
type={"default"}
size={"xlarge"}
className="bg-gray-200 text-gray-700"
onClick={handleSave}
>
</Button>
</View>
<View className={"flex-1"}>
<Button
block
type={"primary"}
size={"xlarge"}
className="bg-primary text-white"
onClick={handleSubmit}
>
</Button>
</View>
</>
)}
{role === "boss" &&
purchaseOrderVO.state === "WAITING_BOSS_APPROVE" && (
<>
<View className={"flex-1"}>
<PurchaseOrderRejectFinal
purchaseOrderVO={purchaseOrderVO}
size={"xlarge"}
onFinish={() => {
// 返回首页
Taro.switchTab({ url: "/pages/main/purchase/index" });
}}
/>
</View>
<View className={"flex-1"}>
<PurchaseOrderFinalApprove
purchaseOrderVO={purchaseOrderVO}
size={"xlarge"}
onFinish={() => {
// 返回首页
Taro.switchTab({ url: "/pages/main/purchase/index" });
}}
/>
</View>
</>
)}
</View>
<SafeArea position={"bottom"} />
</View>
{/* 暂存确认对话框 */}
<Dialog
visible={saveDialogVisible}
title="确认暂存"
content="确定要暂存当前采购订单吗?"
onCancel={() => setSaveDialogVisible(false)}
onConfirm={confirmSave}
/>
{/* 提交审核确认对话框 */}
<Dialog
visible={submitDialogVisible}
title="提交审核"
content="确定要提交给老板审核吗?"
onCancel={() => setSubmitDialogVisible(false)}
onConfirm={confirmSubmit}
/>
</View>
);
});