ERPTurbo_Client/packages/app-client/src/pages/delivery/document.tsx
shenyifei fffb0c7269 feat(preview): 添加PC端预览页面和发货单预览功能
- 添加了PC端预览页面,支持多种设备模拟预览
- 在采购审核页面集成了发货单预览按钮
- 重构了发货表单组件,移除ref暴露机制
- 更新订单转换器以支持发货单数据转换
- 在发票上传页面添加供应商选择器组件
- 添加初始车次号相关字段到API类型定义
- 将发货表单中的Input组件替换为TextArea组件
- 修复了订单项ID匹配逻辑错误
2025-12-27 13:47:37 +08:00

441 lines
12 KiB
TypeScript

import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import { useEffect, useRef, useState } from "react";
import { View } from "@tarojs/components";
import { Button, Dialog, SafeArea, Step, Steps } from "@nutui/nutui-react-taro";
import orderShip from "@/constant/orderShip";
import Taro from "@tarojs/taro";
import classNames from "classnames";
import { business, poster } from "@/services";
import {
buildUrl,
convertOrderShipVOToExamplesFormat,
PdfTemplate,
} from "@/utils";
import {
DeliveryStep1Form,
DeliveryStep1FormRef,
DeliveryStep2Preview,
DeliveryStep3Success,
} from "@/components";
import dayjs from "dayjs";
// 特殊处理:其他费用要实时获取费用项目
const updateOtherFeesModule = async (
module: any,
orderVO: BusinessAPI.OrderVO,
) => {
const {
data: { data },
} = await business.costItem.listCostItem({
costItemListQry: {
status: true,
},
});
const costItems =
data?.filter((item) => item.type !== "ARTIFICIAL_TYPE") || [];
console.log("module", module);
return {
...module,
config: {
...module.config,
feeItems: orderVO.orderCostList?.filter(
(item) => item.type !== "ARTIFICIAL_TYPE",
),
feeLabels: costItems.reduce((acc: any, item: any) => {
acc[item.itemId] = item.name;
return acc;
}, {}),
},
schemas: [
{
title: "显示配置",
valueType: "group",
columns: [
{
dataIndex: "feeItems",
title: "显示费用项目",
valueType: "checkbox",
valueEnum: costItems.reduce((acc: any, item: any) => {
acc[item.itemId] = item.name;
return acc;
}, {}),
},
],
},
],
};
};
export default hocAuth(function Page(props: CommonComponent) {
const { router, setLoading } = props;
const orderShipId = router.params
.orderShipId as BusinessAPI.OrderShipVO["orderShipId"];
const orderId = router.params.orderId as BusinessAPI.OrderShipVO["orderId"];
const [step, setStep] = useState(1);
const [moduleList, setModuleList] = useState<any[]>([]);
const [orderVO, setOrderVO] = useState<BusinessAPI.OrderVO>();
const [orderShipVO, setOrderShipVO] = useState<BusinessAPI.OrderShip>();
const [deliveryTemplate, setDeliveryTemplate] = useState<string>();
const [pdfUrl, setPdfUrl] = useState<string>();
const [picUrl, setPicUrl] = useState<string>();
const step1FormRef = useRef<DeliveryStep1FormRef>(null);
const init = async (
orderId: BusinessAPI.OrderShipVO["orderId"],
orderShipId: BusinessAPI.OrderShipVO["orderShipId"],
) => {
setLoading(true);
const { data } = await business.order.showOrder({
orderShowQry: {
orderId,
orderShipId,
},
});
const orderVO = data.data;
if (orderVO) {
setOrderVO(orderVO);
const orderShip = orderVO.orderShipList[0];
setOrderShipVO(orderShip);
if (orderShip.pdfUrl || orderShip.picUrl) {
setStep(3);
setPdfUrl(orderShip.pdfUrl);
setPicUrl(orderShip.picUrl);
}
const { data } = await business.dealer.showDealer({
dealerShowQry: {
dealerId: orderShip?.dealerId,
},
});
const deliveryTemplate = data.data?.deliveryTemplate!;
setDeliveryTemplate(deliveryTemplate);
}
setLoading(false);
};
useEffect(() => {
if (orderId && orderShipId) {
init(orderId, orderShipId).then();
}
}, [orderId, orderShipId]);
const refresh = async (
deliveryTemplate: string,
orderVO: BusinessAPI.OrderVO,
) => {
const template = JSON.parse(deliveryTemplate);
// 将 orderShipVO 转换为 examples 的数据格式,然后再替换 moduleList 里面的 config 数据
const convertedData = convertOrderShipVOToExamplesFormat(orderVO);
const updatedTemplate = await updateTemplateConfig(
template,
convertedData,
orderVO,
);
setModuleList(updatedTemplate);
};
useEffect(() => {
if (deliveryTemplate && orderVO) {
refresh(deliveryTemplate, orderVO).then();
}
}, [orderVO, deliveryTemplate]);
// 更新模板配置
const updateTemplateConfig = async (
template: any[],
data: any,
orderVO: BusinessAPI.OrderVO,
) => {
let templateList: any[] = [];
template.map(async (module: any) => {
let newModule = { ...module };
if (data[module.type]) {
newModule.config = { ...module.config, ...data[module.type] };
}
// 特殊处理: otherFees 要更新为最新的数据
if (module.type === "otherFees") {
newModule = await updateOtherFeesModule(module, orderVO);
}
templateList.push(newModule);
});
return templateList;
};
// 预览确认
const previewAndConfirm = () => {
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 === 1) {
// 调用 Step1Form 的表单校验方法
if (step1FormRef.current && step1FormRef.current.validateForm()) {
setStep(2);
}
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");
},
});
};
// 截图预览内容
const capturePreview = async () => {
const template = new PdfTemplate(moduleList);
const {
data: { data: pdfData },
} = await poster.pdf.postApiV1Pdf({
html: template.generateHtmlString(),
});
const {
data: { data: picData },
} = await poster.poster.postApiV1Poster({
html: template.generateHtmlString(),
//@ts-ignore
format: "a4",
});
setPdfUrl(pdfData?.path);
setPicUrl(picData?.path);
const orderShip = orderVO?.orderShipList[0];
// 存储 至 orderShip previewUrl
if (orderShip) {
let formData: BusinessAPI.OrderShipGenerateDocumentCmd = {
orderShipId: orderShip?.orderShipId,
pdfUrl: pdfData?.path as string,
picUrl: picData?.path as string,
};
// 检查各模块中的必填字段是否已填写
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 = orderShip?.watermelonGrade;
}
// 检查发货地字段是否开启且已填写
else if (column.dataIndex === "requiredShippingFrom") {
formData.shippingAddress = orderShip?.shippingAddress;
}
// 检查预计到仓时间字段是否开启且已填写
else if (column.dataIndex === "requiredEstimatedArrivalTime") {
formData.estimatedArrivalDate = dayjs(
orderShip?.estimatedArrivalDate,
).format("YYYY-MM-DD");
}
// 检查备注字段是否开启且已填写
else if (column.dataIndex === "requiredRemarks") {
formData.remark = orderShip?.remark;
}
// 检查品级字段是否开启且已填写
else if (column.dataIndex === "requiredGrade") {
formData.orderShipItemList = orderShip?.orderShipItemList;
}
}
}
business.orderShip.generateDocumentOrderShip(formData).then();
}
};
// 返回上一步
const goToPrevStep = () => {
if (step > 0) {
setStep(step - 1);
}
};
// 重置表单
const resetForm = () => {
setStep(1);
// 重新加载初始数据
if (orderId && orderShipId) {
init(orderId, orderShipId).then();
}
};
// 重新生成单据
const regenerateDocument = () => {
setStep(1);
};
return (
<>
<View
className={
"flex flex-1 flex-col overflow-x-hidden overflow-y-auto bg-[#D1D5DB]"
}
>
<View className={"p-2.5"}>
<View className={"rounded-lg bg-white p-4 shadow-md"}>
<Steps value={step} status="dynamic" layout="single">
{orderShip.steps.map((item, index) => (
<Step key={index} {...item} />
))}
</Steps>
</View>
</View>
<View
className={classNames("px-2.5", {
"overflow-hidden": step === 2,
})}
>
{step === 1 && (
<View className="flex flex-col rounded-lg bg-white p-2.5 shadow-md">
<DeliveryStep1Form
ref={step1FormRef}
moduleList={moduleList}
orderShip={orderShipVO}
setOrderShip={setOrderShipVO}
/>
</View>
)}
{step === 2 && <DeliveryStep2Preview moduleList={moduleList} />}
{step === 3 && pdfUrl && (
<DeliveryStep3Success pdfUrl={pdfUrl} picUrl={picUrl} />
)}
</View>
</View>
<View className={"sticky bottom-0 left-0 z-10 w-full bg-white"}>
<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>
</>
);
});