ERPTurbo_Client/packages/app-client/src/pages/delivery/document/purchase.tsx
shenyifei 9c0c0de0c7 fix(purchase): 修复采购订单相关组件中的空值处理和样式问题
- 统一处理 purchaseOrderVO.orderDealer 可能为空的情况,避免运行时错误
- 修复多个组件中颜色样式结尾多余的分号导致的渲染异常
- 调整 OrderCost 组件中 selected 状态的判断逻辑,确保 count 大于 0 才选中
- 优化 PurchasePreview 组件中空箱使用明细的条件渲染,仅在有数据时显示
- 完善成本校验逻辑,在没有启用的"我方"费用项时标记为有效
- 修正 WorkerAdvanceSection 中费用类型的初始值设置错误
- 更新市场报价计算逻辑,根据是否包含包装费标志位决定计算方式
- 加强 SupplierWeightCalculator 中数值计算的空值防护,防止 NaN问题
- 移除冗余的控制台日志输出,清理调试代码- 调整部分组件结构和类名,提升代码可读性和一致性
2025-11-18 16:48:01 +08:00

2370 lines
88 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 hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import { ReactNode, useEffect, useRef, useState } from "react";
import { Text, View } from "@tarojs/components";
import {
Button,
DatePicker,
Dialog,
Input,
PickerOption,
SafeArea,
Step,
Steps,
Toast,
} from "@nutui/nutui-react-taro";
import shipOrder from "@/constant/shipOrder";
import Taro from "@tarojs/taro";
import classNames from "classnames";
import { business } from "@/services";
import dayjs from "dayjs";
import buildUrl from "@/utils/buildUrl";
import { Icon } from "@/components";
export default hocAuth(function Page(props: CommonComponent) {
const { router, setLoading } = props;
const shipOrderId = router.params
.shipOrderId as BusinessAPI.ShipOrderVO["shipOrderId"];
// 当天和未来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 [scale, setScale] = useState(0.5); // 缩放比例
const [step, setStep] = useState(1);
const [moduleList, setModuleList] = useState<any[]>([]);
const [height, setHeight] = useState<number>();
const [shipOrderVO, setShipOrderVO] = useState<BusinessAPI.ShipOrderVO>();
const [deliveryTemplate, setDeliveryTemplate] = useState<string>();
const [purchaseDocument, setPurchaseDocument] = useState<string>();
const [tempFilePath, setTempFilePath] = useState<string>();
// 表单错误状态
const [formErrors, setFormErrors] = useState<{
watermelonGrade?: boolean;
shippingAddress?: boolean;
estimatedArrivalDate?: boolean;
remark?: boolean;
itemGrades?: { [key: string]: boolean };
}>({});
const init = async (shipOrderId: BusinessAPI.ShipOrderVO["shipOrderId"]) => {
setLoading(true);
const { data } = await business.shipOrder.showShipOrder({
shipOrderShowQry: {
shipOrderId,
},
});
const shipOrderVO = data.data;
if (shipOrderVO) {
setShipOrderVO(shipOrderVO);
if (shipOrderVO.purchaseDocument) {
setStep(3);
setPurchaseDocument(shipOrderVO.purchaseDocument);
}
const { data } = await business.dealer.showDealer({
dealerShowQry: {
dealerId: shipOrderVO.dealerId,
},
});
setDeliveryTemplate(data.data?.deliveryTemplate!);
}
setLoading(false);
};
useEffect(() => {
if (shipOrderId) {
init(shipOrderId).then();
}
}, [shipOrderId]);
useEffect(() => {
if (deliveryTemplate && shipOrderVO) {
const template = JSON.parse(deliveryTemplate);
// 将 shipOrderVO 转换为 examples 的数据格式,然后再替换 moduleList 里面的 config 数据
const convertedData = convertShipOrderVOToExamplesFormat(shipOrderVO);
const updatedTemplate = updateTemplateConfig(template, convertedData);
setModuleList(updatedTemplate);
}
}, [shipOrderVO, deliveryTemplate]);
const [touchInfo, setTouchInfo] = useState({
startDistance: 0,
startScale: 0.5,
}); // 触摸信息
const [position, setPosition] = useState({ x: 0, y: -(29.7 * 37.8 * 0.5) }); // 当前位置
const [startPosition, setStartPosition] = useState({ x: 0, y: 0 }); // 起始位置
// 将 shipOrderVO 转换为 examples 的数据格式
const convertShipOrderVOToExamplesFormat = (
shipOrderVO: BusinessAPI.ShipOrderVO,
) => {
return {
title: {
title: "西瓜发货清单",
},
dealerInfo: {
dealerName: shipOrderVO.dealerName || "",
vehicleNumber: `${shipOrderVO.vehicleNo || ""}`,
destination: shipOrderVO.receivingAddress || "",
watermelonGrade: shipOrderVO.watermelonGrade || "",
},
shippingInfo: {
shippingFrom: shipOrderVO.shippingAddress || "",
date: shipOrderVO.shippingDate || "",
},
weightInfo: {
data: shipOrderVO.shipOrderItemList,
accountCompany: shipOrderVO.companyName || "",
sumAmount:
shipOrderVO.shipOrderItemList?.reduce(
(acc, item) => acc + item?.totalAmount!,
0,
) || "",
},
packingSpec: {
data:
shipOrderVO.shipOrderPackageList?.map((item) => ({
boxSpecId: item.boxSpecId || "",
boxSpecName: item.boxSpecName || "",
boxType: item.boxProduct || "",
quantity: item.quantity?.toString() || "",
unitPrice: item.unitPrice?.toString() || "",
amount: item.itemAmount?.toString() || "",
unitWeight: item.singleWeight?.toString() || "",
weight: item.totalWeight?.toString() || "",
})) || [],
},
vehicleInfo: {
driverPhone: shipOrderVO?.driverPhone || "",
licensePlate: shipOrderVO.licensePlate || "",
estimatedArrivalTime: shipOrderVO.estimatedArrivalDate
? dayjs(shipOrderVO?.estimatedArrivalDate).format("YYYY年MM月DD日")
: "",
freightDebt: shipOrderVO.freightDebt?.toString() || "",
strawMatDebt: shipOrderVO.strawMatDebt?.toString() || "",
remarks: shipOrderVO.remark || "",
},
otherFees: {
trademark: shipOrderVO.trademarkFee?.toString() || "",
labor: shipOrderVO.laborFee?.toString() || "",
paperBox: shipOrderVO.cartonFee?.toString() || "",
fee: shipOrderVO.provisionFee?.toString() || "",
codingFee: shipOrderVO.codingFee?.toString() || "",
},
totalAmount: {
amount: shipOrderVO.totalAmount?.toString() || "",
farmer: shipOrderVO.farmerInfo || "",
},
otherInfo: {
origin: "",
supplier: "",
departureTime: "",
arrivalTime: "",
productName: "",
},
};
};
// 更新模板配置
const updateTemplateConfig = (template: any[], data: any) => {
return template.map((module: any) => {
const newModule = { ...module };
if (data[module.type]) {
newModule.config = { ...module.config, ...data[module.type] };
}
return newModule;
});
};
// 渲染内容配置表单字段
const renderContentFields = (module) => {
const contentSchema = module.schemas.find(
(schema) => 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
value={shipOrderVO?.watermelonGrade || ""}
onChange={(value) => {
// 清除错误状态
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"
/>
</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
value={shipOrderVO?.shippingAddress || ""}
onChange={(value) => {
// 清除错误状态
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"
/>
</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"
}
style={{
color: "var(--nutui-color-title, #1a1a1a)",
}}
onClick={() => setShow(true)}
>
<View className={"text-sm"}>
{shipOrderVO?.estimatedArrivalDate
? dayjs(shipOrderVO?.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,
}));
}
setShipOrderVO((prev) => {
return {
...prev!,
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
value={shipOrderVO?.remark || ""}
onChange={(value) => {
// 清除错误状态
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"
/>
</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>
{shipOrderVO?.shipOrderItemList?.map((shipOrderItem, index) => {
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
value={shipOrderItem.watermelonGrade || ""}
onChange={(value) => {
// 清除该项的错误状态
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"
/>
</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) => 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 (!shipOrderVO?.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 (!shipOrderVO?.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 (!shipOrderVO?.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 (!shipOrderVO?.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
) {
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) {
// 显示确认对话框
Dialog.open("dialog", {
title: "生成发货单据",
content: "即将生成发货单据,请确认发货单据是否正确。",
confirmText: "确认生成",
cancelText: "取消",
onConfirm: async () => {
// 截图预览内容
await capturePreview();
Dialog.close("dialog");
// 进入第三步
setStep(3);
},
onCancel: () => {
Dialog.close("dialog");
},
});
return;
}
if (step < 3) {
setStep(step + 1);
}
};
// 生成发货单据
const generateShippingDocument = async () => {
// 显示确认对话框
Dialog.open("dialog", {
title: "生成发货单据",
content: "即将生成发货单据,请确认发货单据是否正确。",
confirmText: "确认生成",
cancelText: "取消",
onConfirm: async () => {
// 截图预览内容
await capturePreview();
Dialog.close("dialog");
// 进入第三步
setStep(3);
},
onCancel: () => {
Dialog.close("dialog");
},
});
};
// 将预览内容转换为HTML字符串的函数
const generateHtmlString = () => {
let htmlString = `
<style> @page {size: 210mm 297mm;margin: 0;padding: 0;}* {outline: none;box-sizing: border-box;margin: 0;padding: 0;border: 0 solid;}body {background-color: #fff;color: #4d4d4d;font-size: 14px;font-style: normal;box-sizing: border-box;}.page-wrap {width: 210mm;min-height: 297mm;margin: 0 auto;}.page-content {position: relative;box-sizing: border-box;width: 100%;height: 100%;padding: 20mm 10mm 0;display: flex;flex-direction: column;gap: 2mm;}@media print {.print-controls {display: none !important;}body {padding: 0;margin: 0;}}.print-module {margin-bottom: 15px;text-align: center;}.print-controls {position: fixed;top: 10px;right: 10px;z-index: 9999;}.print-button,.close-button {padding: 8px 16px;margin-left: 10px;cursor: pointer;border: 1px solid #d9d9d9;border-radius: 4px;}.print-button {background-color: #1890ff;color: white;}.close-button {background-color: #fff;color: #000;}.preview {width: 19cm;div {height: 0.7cm;}}.table-border {border: 2px solid #000;}.table-border>div {border-bottom: 1px solid #000;}.table-border>div>div {border-right: 1px solid #000;}.table-border>div>div:last-child {border-right: none;}.table-border>div:last-child {border-bottom: none;}.col-span-1 {grid-column: span 1 / span 1;}.col-span-2 {grid-column: span 2 / span 2;}.col-span-3 {grid-column: span 3 / span 3;}.col-span-6 {grid-column: span 6 / span 6;}.col-span-8 {grid-column: span 8 / span 8;}.flex {display: flex;}.items-center {align-items: center;}.grid {display: grid;}.w-full {width: 100%;}.grid-cols-1 {grid-template-columns: repeat(1, minmax(0, 1fr));}.grid-cols-2 {grid-template-columns: repeat(2, minmax(0, 1fr));}.grid-cols-3 {grid-template-columns: repeat(3, minmax(0, 1fr));}.grid-cols-4 {grid-template-columns: repeat(4, minmax(0, 1fr));}.grid-cols-5 {grid-template-columns: repeat(5, minmax(0, 1fr));}.grid-cols-6 {grid-template-columns: repeat(6, minmax(0, 1fr));}.grid-cols-7 {grid-template-columns: repeat(7, minmax(0, 1fr));}.grid-cols-8 {grid-template-columns: repeat(8, minmax(0, 1fr));}.items-end {align-items: flex-end;}.justify-center {justify-content: center;}.border-t-0 {border-top-width: 0px;}.border-b {border-bottom-width: 1px;}.border-black {border-color: #000000;}.bg-white {background-color: #ffffff;}.text-2xl {font-size: 24px;line-height: 1;}.text-base {font-size: 16px;line-height: 1;}.text-lg {font-size: 18px;line-height: 1;}.font-bold {font-weight: bold;}.preview {width: 19cm;div {height: 0.69cm;}}.table-border {border: 2px solid #000;}.table-border>div {border-bottom: 1px solid #000;}.table-border>div>div {border-right: 1px solid #000;}.table-border>div>div:last-child {border-right: none;}.table-border>div:last-child {border-bottom: none;} </style>
<div class="page-wrap">
<div class="page-content">
`;
moduleList.forEach((module) => {
const config = module.config;
if (module.type === "title") {
htmlString += `
<div class="preview grid w-full grid-cols-8 gap-0 text-2xl font-bold">
<div class="col-span-8 flex items-end justify-center">
${config.title || ""}
</div>
</div>
`;
}
if (module.type === "dealerInfo") {
htmlString += `
<div class="preview grid w-full grid-cols-8 gap-0 text-lg font-bold">
<div class="col-span-1"></div>
`;
if (config.showDealerName || config.showWatermelonGrade) {
htmlString += `
<div class="col-span-3 flex items-end justify-center border-b border-black">
${
config.showWatermelonGrade
? `${config.dealerName || ""}-${config.watermelonGrade || ""}`
: config.dealerName || ""
}
</div>
`;
} else {
htmlString += `<div class="col-span-3"></div>`;
}
if (config.showDestination || config.showVehicleNumber) {
htmlString += `
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.destination || ""} ${config.vehicleNumber || ""}
</div>
`;
} else {
htmlString += `<div class="col-span-3"></div>`;
}
htmlString += `
<div class="col-span-1"></div>
</div>
`;
}
if (module.type === "shippingInfo") {
htmlString += `<div class="preview grid w-full grid-cols-8 gap-0 text-base">`;
if (config.showShippingFrom) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">发货地:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.shippingFrom || ""}
</div>
`;
}
if (config.showDate) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">日期:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.date || ""}
</div>
`;
}
htmlString += `</div>`;
}
if (module.type === "weightInfo") {
if (config.data) {
config.data.forEach((item: any) => {
htmlString += `<div class="preview grid w-full grid-cols-2 gap-0 text-base">`;
if (config.showNetWeight) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">净重:</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${item.netWeight || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">
${config.netWeightUnit === "1" ? "斤" : "公斤"}
</div>
</div>
`;
}
if (config.showBoxWeight) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">箱重:</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${item.boxWeight || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">
${config.boxWeightUnit === "1" ? "斤" : "公斤"}
</div>
</div>
`;
}
if (config.showGrossWeight) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">毛重:</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${item.grossWeight || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">
${config.grossWeightUnit === "1" ? "斤" : "公斤"}
</div>
</div>
`;
}
if (config.showUnitPrice) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">单价:</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${item.unitPrice || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">
${config.unitPriceUnit === "1" ? "元/斤" : "元/公斤"}
</div>
</div>
`;
}
if (config.showAmount) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">金额:</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${item.totalAmount || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">元</div>
</div>
`;
}
if (config.showGrade) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">品级:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${item.watermelonGrade || ""}
</div>
</div>
`;
}
htmlString += `</div>`;
});
}
htmlString += `<div class="preview grid w-full grid-cols-2 gap-0 text-base">`;
if (config.showAccountCompany) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">入账公司:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.accountCompany || ""}
</div>
</div>
`;
}
if (config.showSumAmount) {
htmlString += `
<div class="col-span-1 grid grid-cols-4">
<div class="col-span-1 flex items-end justify-center">总计:</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${config.sumAmount || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">元</div>
</div>
`;
}
htmlString += `</div>`;
}
if (module.type === "packingSpec") {
htmlString += `<div>`;
// 计算需要显示的列数
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 += `
<div class="${gridClass}">
<div class="grid-span-1 flex items-end justify-center">规格:</div>
</div>
`;
htmlString += `<div class="${gridClass}">`;
if (config.columns) {
config.columns.forEach((column: any, index: number) => {
if (index === 0) {
htmlString += `<div class="">&nbsp;</div>`;
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 += `
<div class="flex items-end justify-center">${column.title || ""}</div>
`;
}
});
}
htmlString += `</div>`;
htmlString += `<div class="table-border">`;
if (config.data) {
config.data.forEach((item: any, index: number) => {
htmlString += `
<div class="${gridClass} ${index > 0 ? "border-t-0" : ""}">
<div class="flex items-end justify-center">
${item.boxSpecName}
</div>
`;
htmlString += `
<div class="flex items-end justify-center">${item.boxType || ""}</div>
`;
if (config.showQuantity) {
htmlString += `
<div class="flex items-end justify-center">${item.quantity || ""}</div>
`;
}
if (config.showUnitPrice) {
htmlString += `
<div class="flex items-end justify-center">${item.unitPrice || ""}</div>
`;
}
if (config.showAmount) {
htmlString += `
<div class="flex items-end justify-center">${item.amount || ""}</div>
`;
}
if (config.showUnitWeight) {
htmlString += `
<div class="flex items-end justify-center">${item.unitWeight || ""}</div>
`;
}
if (config.showWeight) {
htmlString += `
<div class="flex items-end justify-center">${item.weight || ""}</div>
`;
}
htmlString += `</div>`;
});
}
htmlString += `
<div class="${gridClass}">
<div class="col-span-2 flex items-end justify-center">总件数</div>
`;
if (config.showQuantity) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">
${
config.data?.reduce(
(acc: any, cur: any) => acc + Number(cur.quantity || 0),
0,
) || 0
}
</div>
`;
}
if (config.showUnitPrice) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">
</div>
`;
}
if (config.showAmount) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">
${
config.data?.reduce(
(acc: any, cur: any) => acc + Number(cur.amount || 0),
0,
) || 0
}
</div>
`;
}
if (config.showUnitWeight) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">
</div>
`;
}
if (config.showWeight) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">
${
config.data?.reduce(
(acc: any, cur: any) => acc + Number(cur.weight || 0),
0,
) || 0
}
</div>
`;
}
htmlString += `
</div>
</div>
`;
htmlString += `</div>`;
}
if (module.type === "vehicleInfo") {
htmlString += `<div class="preview grid w-full grid-cols-8 gap-0 text-base">`;
if (config.showDriverPhone) {
htmlString += `
<div class="col-span-2 flex items-end justify-center">司机号码:</div>
<div class="col-span-6 flex items-end justify-center border-b border-black">
${config.driverPhone || ""}
</div>
`;
}
if (config.showLicensePlate) {
htmlString += `
<div class="col-span-2 flex items-end justify-center">车牌:</div>
<div class="col-span-6 flex items-end justify-center border-b border-black">
${config.licensePlate || ""}
</div>
`;
}
if (config.showEstimatedArrivalTime) {
htmlString += `
<div class="col-span-2 flex items-end justify-center">预计到仓时间:</div>
<div class="col-span-6 flex items-end justify-center border-b border-black">
${config.estimatedArrivalTime || ""}
</div>
`;
}
if (config.showRemarks) {
htmlString += `
<div class="col-span-2 flex items-end justify-center">备注:</div>
<div class="col-span-6 flex items-end justify-center border-b border-black">
${config.remarks || ""}
</div>
`;
}
if (config.showFreightDebt) {
htmlString += `
<div class="col-span-2 flex items-end justify-center">
${config.freightDebtTitle || "运费欠"}:
</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${config.freightDebt || ""}
</div>
`;
}
if (config.showStrawMatDebt) {
htmlString += `
<div class="col-span-2 flex items-end justify-center">草帘欠:</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${config.strawMatDebt || ""}
</div>
`;
}
htmlString += `</div>`;
}
if (module.type === "otherFees") {
htmlString += `<div class="preview grid w-full grid-cols-8 gap-0 text-base">`;
if (config.feeItems) {
config.feeItems.forEach((feeType: any) => {
htmlString += `
<div class="col-span-1 flex items-end justify-center">
${(config.feeLabels && config.feeLabels[feeType]) || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">
${config[feeType] || ""}
</div>
`;
});
}
htmlString += `</div>`;
}
if (module.type === "totalAmount") {
htmlString += `<div class="preview grid w-full grid-cols-8 gap-0 text-base">`;
if (config.showTotalAmount) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">
${config.sumTitle || "合计金额"}:
</div>
<div class="col-span-2 flex items-end justify-center border-b border-black">
${config.amount || ""}
</div>
<div class="col-span-1 flex items-end justify-center border-b border-black">元</div>
`;
}
if (config.showFarmer) {
htmlString += `
<div class="col-span-1 flex items-end justify-center">瓜农:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.farmer || ""}
</div>
`;
}
htmlString += `</div>`;
}
if (module.type === "otherInfo") {
htmlString += `
<div class="preview grid w-full grid-cols-8 gap-0 text-base">
<div class="col-span-1 flex items-end justify-center">产地:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.origin || ""}
</div>
<div class="col-span-1 flex items-end justify-center">供应商:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.supplier || ""}
</div>
<div class="col-span-1 flex items-end justify-center">发车时间:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.departureTime || ""}
</div>
<div class="col-span-1 flex items-end justify-center">到达时间:</div>
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.arrivalTime || ""}
</div>
<div class="col-span-1 flex items-end justify-center">产品名称:</div>
<div class="col-span-7 flex items-end justify-center border-b border-black">
${config.productName || ""}
</div>
</div>
`;
}
});
htmlString += `</div></div>`;
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(),
},
});
// 存储 至 shipOrder previewUrl
if (shipOrderVO) {
let formData: BusinessAPI.ShipOrderGenerateDocumentCmd = {
shipOrderId: shipOrderVO?.shipOrderId,
shipDocument: data.path,
};
// 检查各模块中的必填字段是否已填写
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") {
formData.watermelonGrade = shipOrderVO?.watermelonGrade;
}
// 检查发货地字段是否开启且已填写
else if (column.dataIndex === "requiredShippingFrom") {
formData.shippingAddress = shipOrderVO?.shippingAddress;
}
// 检查预计到仓时间字段是否开启且已填写
else if (column.dataIndex === "requiredEstimatedArrivalTime") {
formData.estimatedArrivalDate = shipOrderVO?.estimatedArrivalDate;
}
// 检查备注字段是否开启且已填写
else if (column.dataIndex === "requiredRemarks") {
formData.remark = shipOrderVO?.remark;
}
// 检查品级字段是否开启且已填写
else if (column.dataIndex === "requiredGrade") {
formData.shipOrderItemList = shipOrderVO?.shipOrderItemList;
}
}
}
business.shipOrder.generateDocumentShipOrder(formData).then();
}
if (data && data.path) {
setPurchaseDocument(data.path);
}
};
// 返回上一步
const goToPrevStep = () => {
if (step > 0) {
setStep(step - 1);
}
};
// 重置表单
const resetForm = () => {
setStep(1);
// 重置表单错误状态
setFormErrors({});
// 重新加载初始数据
if (shipOrderId) {
init(shipOrderId).then();
}
};
// 重新生成单据
const regenerateDocument = () => {
setStep(1);
};
// 放大
const zoomIn = () => {
setScale((prev) => Math.min(prev + 0.1, 3)); // 最大放大到3倍
};
// 缩小
const zoomOut = () => {
setScale((prev) => Math.max(prev - 0.1, 0.5)); // 最小缩小到0.5倍
};
// 重置缩放
const resetZoom = () => {
setScale(0.5);
setPosition({ x: 0, y: -(29.7 * 37.8 * 0.5) }); // 重置位置
};
// 计算两点间距离
const getDistance = (touches) => {
const dx = touches[0].clientX - touches[1].clientX;
const dy = touches[0].clientY - touches[1].clientY;
return Math.sqrt(dx * dx + dy * dy);
};
// 处理触摸开始事件
const handleTouchStart = (e) => {
const touches = e.touches;
if (touches.length === 2) {
// 双指触摸,记录初始距离和当前缩放值
const distance = getDistance(touches);
setTouchInfo({
startDistance: distance,
startScale: scale,
});
} else if (touches.length === 1) {
// 单指触摸,记录起始位置
setStartPosition({
x: touches[0].clientX - position.x,
y: touches[0].clientY - position.y,
});
}
};
// 处理触摸移动事件
const handleTouchMove = (e) => {
const touches = e.touches;
e.preventDefault(); // 阻止默认滚动行为
if (touches.length === 2) {
// 双指触摸,计算缩放比例
const currentDistance = getDistance(touches);
const newScale =
touchInfo.startScale * (currentDistance / touchInfo.startDistance);
// 限制缩放范围在0.5到3之间
setScale(Math.min(Math.max(0.5, newScale), 3));
} else if (touches.length === 1 && scale > 0.5) {
// 单指触摸且已放大,允许拖动
setPosition({
x: touches[0].clientX - startPosition.x,
y: touches[0].clientY - startPosition.y,
});
}
};
// 处理触摸结束事件
const handleTouchEnd = (e) => {
// 可以在这里添加触摸结束后的处理逻辑
console.log("Touch ended", e);
};
const [topBarHeight, setTopBarHeight] = useState<number>(0);
const [bottomBarHeight, setBottomBarHeight] = useState<number>(0);
const topBarRef = useRef<any>(null);
const bottomBarRef = useRef<any>(null);
// 查看文档
const handleView = async () => {
if (!tempFilePath) {
Taro.showToast({
title: "请先下载发货单据",
icon: "none",
duration: 2000,
});
return;
}
Taro.openDocument({
filePath: tempFilePath,
showMenu: true,
});
};
// 处理下载功能
const handleDownload = async () => {
Taro.showToast({
title: "正在生成并下载发货单据",
icon: "none",
duration: 2000,
});
// 下载文件
Taro.downloadFile({
url: purchaseDocument!,
}).then((downloadRes) => {
if (downloadRes.tempFilePath) {
setTempFilePath(downloadRes.tempFilePath);
Taro.showToast({
title: "发货单据已下载成功您可以将生成的PDF发送给好友",
icon: "none",
duration: 3000,
});
// Taro.getFileSystemManager().mkdir({
// dirPath: `${Taro.env.USER_DATA_PATH}/${shipOrderVO?.dealerName}/发货单据`,
// recursive: true,
// });
//
// // 保存到文件系统
// Taro.getFileSystemManager().saveFile({
// tempFilePath: downloadRes.tempFilePath,
// filePath: `${Taro.env.USER_DATA_PATH}/${shipOrderVO?.dealerName}/发货单据/${shipOrderVO?.vehicleNo}_${shipOrderVO?.dealerName}_发货单据_${shipOrderVO?.orderSn}.pdf`,
// success: function () {
// Taro.showToast({
// title: "发货单据已保存到本地您可以将生成的PDF发送给好友",
// icon: "none",
// duration: 3000,
// });
// },
// fail: function (err) {
// console.log("保存失败", err);
// Taro.showToast({
// title: "保存失败",
// icon: "none",
// duration: 3000,
// });
// }
// });
}
});
};
// 处理分享功能
const handleShare = async () => {
if (!tempFilePath) {
Taro.showToast({
title: "请先下载发货单据",
icon: "none",
duration: 2000,
});
return;
}
Taro.showToast({
title: "正在分享到微信",
icon: "none",
duration: 2000,
});
// 下载完成后转发
Taro.shareFileMessage({
filePath: tempFilePath,
fileName: `${shipOrderVO?.vehicleNo}车_${shipOrderVO?.dealerName}_发货单据_${shipOrderVO?.orderSn}.pdf`,
complete: (res) => {
console.log("complete", res);
},
});
};
useEffect(() => {
// 获取 topBar 和 bottomBar 的实际高度
const queryTopBar = Taro.createSelectorQuery();
queryTopBar
.select("#topBar")
.boundingClientRect((rect: any) => {
if (rect) {
setTopBarHeight(rect.height);
}
})
.exec();
const queryBottomBar = Taro.createSelectorQuery();
queryBottomBar
.select("#bottomBar")
.boundingClientRect((rect: any) => {
if (rect) {
setBottomBarHeight(rect.height);
}
})
.exec();
}, [step]);
useEffect(() => {
// 计算滚动区域高度:窗口高度 - topBar高度 - bottomBar高度
const windowHeight = Taro.getSystemInfoSync().windowHeight;
setHeight(windowHeight - topBarHeight - bottomBarHeight);
}, [topBarHeight, bottomBarHeight]);
return (
<View
className={
"flex flex-1 flex-col overflow-x-hidden overflow-y-auto bg-[#D1D5DB]"
}
>
<View id={"topBar"} ref={topBarRef}>
<View className={"p-2.5"}>
<View className={"rounded-lg bg-white p-4 shadow-md"}>
<Steps value={step} status="dynamic" layout="single">
{shipOrder.steps.map((item, index) => (
<Step key={index} {...item} />
))}
</Steps>
</View>
</View>
</View>
<View
className={classNames("px-2.5", {
"overflow-hidden": step === 2,
})}
style={{
height: height,
}}
>
{step === 1 && (
<View className="flex flex-col rounded-lg bg-white p-2.5 shadow-md">
{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>
);
})}
</View>
)}
{step === 2 && (
<View className="flex flex-1 flex-col items-center overflow-hidden">
{/* 缩放控制按钮 */}
<View className="mb-2 flex w-full justify-center gap-2 bg-white p-2">
<Button size="small" onClick={zoomOut}>
-
</Button>
<Button size="small" onClick={resetZoom}>
</Button>
<Button size="small" onClick={zoomIn}>
+
</Button>
<Text className="ml-2 flex items-center">
{Math.round(scale * 100)}%
</Text>
</View>
{/* 预览区域 */}
<View
className="flex-1 overflow-auto"
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
overflow: "hidden",
}}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<View
id="preview"
className="rounded-lg bg-white shadow-md"
style={{
width: "21cm",
padding: "2cm 1cm 0 1cm",
margin: "0 auto",
height: "29.7cm", // A4纸高度
transform: `scale(${scale}) translate(${position.x}px, ${position.y}px)`,
transformOrigin: "center center",
transition:
scale === touchInfo.startScale
? "transform 0.2s ease"
: "none",
}}
>
{moduleList.map((module) => {
const config = module.config;
if (module.type === "title") {
return (
<View
key={module.id}
className={
"preview grid w-full grid-cols-8 gap-0 text-2xl font-bold"
}
>
<View className="col-span-8 flex items-end justify-center">
{config.title}
</View>
</View>
);
}
if (module.type === "dealerInfo") {
return (
<View
key={module.id}
className={
"preview grid w-full grid-cols-8 gap-0 text-lg font-bold"
}
>
<View className="col-span-1"></View>
{config.showDealerName || config.showWatermelonGrade ? (
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.showWatermelonGrade
? `${config.dealerName}-${config.watermelonGrade}`
: config.dealerName}
</View>
) : (
<View className="col-span-3"></View>
)}
{config.showDestination || config.showVehicleNumber ? (
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.destination}
{config.vehicleNumber}
</View>
) : (
<View className="col-span-3"></View>
)}
<View className="col-span-1"></View>
</View>
);
}
if (module.type === "shippingInfo") {
return (
<View
key={module.id}
className={
"preview grid w-full grid-cols-8 gap-0 text-base"
}
>
{config.showShippingFrom && (
<>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.shippingFrom}
</View>
</>
)}
{config.showDate && (
<>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.date}
</View>
</>
)}
</View>
);
}
if (module.type === "weightInfo") {
return (
<>
{config.data?.map((item: any, index: number) => {
return (
<View
key={"weightInfo" + index}
className={
"preview grid w-full grid-cols-2 gap-0 text-base"
}
>
{config.showNetWeight && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{item.netWeight}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
{config.netWeightUnit === "1"
? "斤"
: "公斤"}
</View>
</View>
)}
{config.showBoxWeight && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{item.boxWeight}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
{config.boxWeightUnit === "1"
? "斤"
: "公斤"}
</View>
</View>
)}
{config.showGrossWeight && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{item.grossWeight}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
{config.grossWeightUnit === "1"
? "斤"
: "公斤"}
</View>
</View>
)}
{config.showUnitPrice && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{item.unitPrice}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
{config.unitPriceUnit === "1"
? "元/斤"
: "元/公斤"}
</View>
</View>
)}
{config.showAmount && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{item.totalAmount}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
</View>
</View>
)}
{config.showGrade && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{item.watermelonGrade}
</View>
</View>
)}
</View>
);
})}
<View
className={
"preview grid w-full grid-cols-2 gap-0 text-base"
}
>
{config.showAccountCompany && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.accountCompany}
</View>
</View>
)}
{config.showSumAmount && (
<View className={"col-span-1 grid grid-cols-4"}>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{config.sumAmount}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
</View>
</View>
)}
</View>
</>
);
}
if (module.type === "packingSpec") {
// 计算需要显示的列数
const visibleColumnCount =
[
config.showBoxType,
config.showQuantity,
config.showUnitPrice,
config.showAmount,
config.showUnitWeight,
config.showWeight,
].filter(Boolean).length + 1; // +1 是因为"规格:"列总是显示
return (
<>
<View
className={classNames(`grid w-full gap-0 text-base`, {
"grid-cols-1": visibleColumnCount === 1,
"grid-cols-2": visibleColumnCount === 2,
"grid-cols-3": visibleColumnCount === 3,
"grid-cols-4": visibleColumnCount === 4,
"grid-cols-5": visibleColumnCount === 5,
"grid-cols-6": visibleColumnCount === 6,
"grid-cols-7": visibleColumnCount === 7,
"grid-cols-8": visibleColumnCount === 8,
})}
>
<div
className={`grid-span-1 flex items-end justify-center`}
>
</div>
</View>
<View
className={classNames(`grid w-full gap-0 text-base`, {
"grid-cols-1": visibleColumnCount === 1,
"grid-cols-2": visibleColumnCount === 2,
"grid-cols-3": visibleColumnCount === 3,
"grid-cols-4": visibleColumnCount === 4,
"grid-cols-5": visibleColumnCount === 5,
"grid-cols-6": visibleColumnCount === 6,
"grid-cols-7": visibleColumnCount === 7,
"grid-cols-8": visibleColumnCount === 8,
})}
>
{config.columns.map((column: any, index: number) => {
if (index === 0) {
return (
<div key={"title" + index} className="">
&nbsp;
</div>
);
}
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)
) {
return (
<div
key={"title" + index}
className="flex items-end justify-center"
>
{column.title}
</div>
);
}
return <></>;
})}
</View>
<View className={"table-border"}>
{config.data?.map((item: any, index: number) => (
<View
key={index}
className={classNames(
`grid w-full gap-0 text-base`,
{
"grid-cols-1": visibleColumnCount === 1,
"grid-cols-2": visibleColumnCount === 2,
"grid-cols-3": visibleColumnCount === 3,
"grid-cols-4": visibleColumnCount === 4,
"grid-cols-5": visibleColumnCount === 5,
"grid-cols-6": visibleColumnCount === 6,
"grid-cols-7": visibleColumnCount === 7,
"grid-cols-8": visibleColumnCount === 8,
"border-t-0": index > 0,
},
)}
>
<View className={"flex items-end justify-center"}>
{item.boxSpecName}
</View>
<View className={"flex items-end justify-center"}>
{item.boxType}
</View>
{config.showQuantity && (
<View
className={"flex items-end justify-center"}
>
{item.quantity}
</View>
)}
{config.showUnitPrice && (
<View
className={"flex items-end justify-center"}
>
{item.unitPrice}
</View>
)}
{config.showAmount && (
<View
className={"flex items-end justify-center"}
>
{item.amount}
</View>
)}
{config.showUnitWeight && (
<View
className={"flex items-end justify-center"}
>
{item.unitWeight}
</View>
)}
{config.showWeight && (
<View
className={"flex items-end justify-center"}
>
{item.weight}
</View>
)}
</View>
))}
<View
className={classNames(
`grid w-full gap-0 text-base`,
{
"grid-cols-1": visibleColumnCount === 1,
"grid-cols-2": visibleColumnCount === 2,
"grid-cols-3": visibleColumnCount === 3,
"grid-cols-4": visibleColumnCount === 4,
"grid-cols-5": visibleColumnCount === 5,
"grid-cols-6": visibleColumnCount === 6,
"grid-cols-7": visibleColumnCount === 7,
"grid-cols-8": visibleColumnCount === 8,
},
)}
>
<View
className={`col-span-2 flex items-end justify-center`}
>
</View>
{config.showQuantity && (
<View
className={`col-span-1 flex items-end justify-center`}
>
{config.data?.reduce(
(acc: any, cur: any) =>
acc + Number(cur.quantity),
0,
)}
</View>
)}
{config.showUnitPrice && (
<View
className={`col-span-1 flex items-end justify-center`}
></View>
)}
{config.showAmount && (
<View
className={`col-span-1 flex items-end justify-center`}
>
{config.data?.reduce(
(acc: any, cur: any) =>
acc + Number(cur.amount),
0,
)}
</View>
)}
{config.showUnitWeight && (
<View
className={`col-span-1 flex items-end justify-center`}
></View>
)}
{config.showWeight && (
<View
className={`col-span-1 flex items-end justify-center`}
>
{config.data?.reduce(
(acc: any, cur: any) =>
acc + Number(cur.weight),
0,
)}
</View>
)}
</View>
</View>
</>
);
}
if (module.type === "vehicleInfo") {
return (
<View
key={module.id}
className={
"preview grid w-full grid-cols-8 gap-0 text-base"
}
>
{config.showDriverPhone && (
<>
<View className="col-span-2 flex items-end justify-center">
:
</View>
<View className="col-span-6 flex items-end justify-center border-b border-black">
{config.driverPhone}
</View>
</>
)}
{config.showLicensePlate && (
<>
<View className="col-span-2 flex items-end justify-center">
:
</View>
<View className="col-span-6 flex items-end justify-center border-b border-black">
{config.licensePlate}
</View>
</>
)}
{config.showEstimatedArrivalTime && (
<>
<View className="col-span-2 flex items-end justify-center">
:
</View>
<View className="col-span-6 flex items-end justify-center border-b border-black">
{config.estimatedArrivalTime}
</View>
</>
)}
{config.showRemarks && (
<>
<View className="col-span-2 flex items-end justify-center">
:
</View>
<View className="col-span-6 flex items-end justify-center border-b border-black">
{config.remarks}
</View>
</>
)}
{config.showFreightDebt && (
<>
<View className="col-span-2 flex items-end justify-center">
{config.freightDebtTitle || "运费欠"}:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{config.freightDebt}
</View>
</>
)}
{config.showStrawMatDebt && (
<>
<View className="col-span-2 flex items-end justify-center">
:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{config.strawMatDebt}
</View>
</>
)}
</View>
);
}
if (module.type === "otherFees") {
return (
<View
key={module.id}
className={
"preview grid w-full grid-cols-8 gap-0 text-base"
}
>
{config.feeItems?.map((feeType: any) => (
<>
<View className="col-span-1 flex items-end justify-center">
{config.feeLabels[feeType]}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
{config[feeType]}
</View>
</>
))}
</View>
);
}
if (module.type === "totalAmount") {
return (
<View
key={module.id}
className={
"preview grid w-full grid-cols-8 gap-0 text-base"
}
>
{config.showTotalAmount && (
<>
<View className="col-span-1 flex items-end justify-center">
{config.sumTitle || "合计金额"}:
</View>
<View className="col-span-2 flex items-end justify-center border-b border-black">
{config.amount}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
</View>
</>
)}
{config.showFarmer && (
<>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.farmer}
</View>
</>
)}
</View>
);
}
if (module.type === "otherInfo") {
return (
<View
key={module.id}
className={
"preview grid w-full grid-cols-8 gap-0 text-base"
}
>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.origin}
</View>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.supplier}
</View>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.departureTime}
</View>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-3 flex items-end justify-center border-b border-black">
{config.arrivalTime}
</View>
<View className="col-span-1 flex items-end justify-center">
:
</View>
<View className="col-span-7 flex items-end justify-center border-b border-black">
{config.productName}
</View>
</View>
);
}
})}
</View>
</View>
</View>
)}
{step === 3 && purchaseDocument && (
<View className="flex flex-1 flex-col items-center justify-center rounded-lg bg-white p-4 shadow-md">
<View className="mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-green-100">
<Text className="text-2xl text-green-600"></Text>
</View>
<View className="mb-2 text-xl font-bold text-gray-800">
</View>
<View className="mb-8 text-gray-600">
</View>
<View className="mb-6 flex w-full flex-row justify-center gap-4">
{purchaseDocument && !tempFilePath && (
<View className={"flex-1"}>
<Button
type="default"
size="large"
block
onClick={handleDownload}
>
</Button>
</View>
)}
{purchaseDocument && tempFilePath && (
<>
<View className={"flex-1"}>
<Button
type="default"
size="large"
block
onClick={handleView}
>
</Button>
</View>
<View className={"flex-1"}>
<Button
type="primary"
size="large"
block
onClick={handleShare}
>
</Button>
</View>
</>
)}
</View>
<View className="text-sm text-gray-500">
</View>
</View>
)}
</View>
{/* 步骤控制按钮 */}
<View
ref={bottomBarRef}
className={"fixed bottom-0 left-0 z-10 w-full bg-white"}
id={"bottomBar"}
>
<View className={"flex flex-row gap-2.5 p-2.5"}>
{step == 1 && (
<View className={"flex-1"}>
<Button type="default" block size={"large"} onClick={resetForm}>
</Button>
</View>
)}
{step == 1 && (
<View className={"flex-1"}>
<Button
type="primary"
block
size={"large"}
onClick={previewAndConfirm}
>
</Button>
</View>
)}
{step == 2 && (
<View className={"flex-1"}>
<Button
type="default"
block
size={"large"}
onClick={goToPrevStep}
>
</Button>
</View>
)}
{step == 2 && (
<View className={"flex-1"}>
<Button
type="primary"
block
size={"large"}
onClick={generateShippingDocument}
>
</Button>
</View>
)}
{step == 3 && (
<View className={"flex-1"}>
<Button
type="default"
block
size={"large"}
onClick={() =>
Taro.switchTab({
url: buildUrl("/pages/main/index/index"),
})
}
>
</Button>
</View>
)}
{step == 3 && (
<View className={"flex-1"}>
<Button
type="primary"
block
size={"large"}
onClick={regenerateDocument}
>
</Button>
</View>
)}
</View>
<SafeArea position={"bottom"} />
</View>
</View>
);
});