ERPTurbo_Client/packages/app-client/src/pages/ship/document/create.tsx
shenyifei e1d4824ad8 feat(purchase): 添加采购单审核通过页面及发货单生成功能
- 新增采购单审核通过页面,展示审核通过后的采购单信息
- 实现自动关联并展示发货单信息
- 添加快捷生成发货单据、采购底单和成本单的功能
- 根据经销商配置控制可生成的单据类型
- 完善审核通过后页面的UI布局和交互逻辑
- 优化订单审核完成后跳转逻辑,携带订单ID参数
- 在工作台常量中增加发货单菜单项
- 重构认证高阶组件,将overlay状态管理改为loading状态
- 优化纸箱包装模块的类型标签显示和分隔线样式
- 修复部分条件过滤逻辑和按钮状态判断代码格式问题
2025-11-08 15:11:08 +08:00

2243 lines
86 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 { 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";
export default hocAuth(function Page(props: CommonComponent) {
const { router, setLoading } = props;
const shipOrderId = router.params
.shipOrderId as BusinessAPI.ShipOrderVO["shipOrderId"];
// 获取当年的第一天和今天,用于限制日期选择范围
const currentYear = new Date().getFullYear();
const startDate = new Date(currentYear, 0, 1); // 当年第一天
const endDate = new Date(); // 今天
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 [previewUrl, setPreviewUrl] = 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"]) => {
const { data } = await business.shipOrder.showShipOrder({
shipOrderShowQry: {
shipOrderId,
},
});
const shipOrderVO = data.data;
if (shipOrderVO) {
setShipOrderVO(shipOrderVO);
const { data } = await business.dealer.showDealer({
dealerShowQry: {
dealerId: shipOrderVO.dealerId,
},
});
setDeliveryTemplate(data.data?.deliveryTemplate!);
}
};
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: "",
sumAmount:
shipOrderVO.shipOrderItemList?.reduce(
(acc, item) => acc + item?.totalAmount!,
0,
) || "",
},
packingSpec: {
data:
shipOrderVO.shipOrderPackageList?.map((item) => ({
boxCategory: item.boxCategory || "",
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;
}
return (
<View>
{contentSchema.columns.map((column, index) => {
return (
<View key={index} className="mb-2.5">
<View className="mb-1 block text-sm font-normal text-[#000000]">
{column.title}
</View>
{column.dataIndex === "requiredWatermelonGrade" && (
<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>
)}
{column.dataIndex === "requiredShippingFrom" && (
<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>
)}
{column.dataIndex === "requiredEstimatedArrivalTime" && (
<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"}`}
>
<Input
value={
shipOrderVO?.estimatedArrivalDate
? dayjs(shipOrderVO?.estimatedArrivalDate).format(
"YYYY年MM月DD日",
)
: ""
}
placeholder={
column.fieldProps?.placeholder || `${column.title}`
}
type="text"
className="flex-1"
disabled
onClick={() => setShow(true)}
onBlur={() => {
// 失焦时校验
if (!shipOrderVO?.estimatedArrivalDate) {
setFormErrors((prev) => ({
...prev,
estimatedArrivalDate: true,
}));
Toast.show("toast", {
icon: "fail",
title:
column.fieldProps?.placeholder ||
`请选择${column.title}`,
});
}
}}
/>
<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>
)}
{column.dataIndex === "requiredRemarks" && (
<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>
)}
{column.dataIndex === "requiredGrade" && (
<>
{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>
);
})}
</View>
);
};
// 表单校验
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) {
// 显示确认对话框
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-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } .grid-cols-4 { grid-template-columns: repeat(4, 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.boxCategory === "FOUR_GRAIN"
? "4粒装"
: item.boxCategory === "TWO_GRAIN"
? "2粒装"
: "未知"
}
</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(),
},
});
if (data && data.path) {
setPreviewUrl(data.path);
// 保存到系统相册
Taro.downloadFile({
url: data.path,
}).then((downloadRes) => {
if (downloadRes.tempFilePath) {
Taro.saveImageToPhotosAlbum({
filePath: downloadRes.tempFilePath,
complete: (res) => {
console.log("saveImageToPhotosAlbum", res);
},
})
.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);
// 重置表单错误状态
setFormErrors({});
// 重新加载初始数据
if (shipOrderId) {
setLoading(true);
init(shipOrderId).then(() => {
setLoading(false);
});
}
};
// 重新生成单据
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 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();
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.boxCategory === "FOUR_GRAIN"
? "4粒装"
: item.boxCategory === "TWO_GRAIN"
? "2粒装"
: "未知"}
</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 && previewUrl && (
<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">
<View className={"flex-1"}>
<Button
type="default"
size="large"
block
className="max-w-[150px] flex-1"
onClick={handleDownload}
>
</Button>
</View>
<View className={"flex-1"}>
<Button
type="primary"
size="large"
block
className="max-w-[150px] flex-1"
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={regenerateDocument}
>
</Button>
</View>
)}
{step == 3 && (
<View className={"flex-1"}>
<Button
type="primary"
block
size={"large"}
onClick={() => Taro.navigateBack()}
>
</Button>
</View>
)}
</View>
<SafeArea position={"bottom"} />
</View>
</View>
);
});