Compare commits

...

3 Commits

Author SHA1 Message Date
shenyifei
74623e6c1b feat(delivery): 新增发货单据预览组件及模板生成功能
- 添加 DealerInfo、WeightInfo 等多个发货单据信息展示组件
- 实现 Template 类用于将模块配置转换为 HTML 字符串
- 更新 OrderVehicle 组件以简化经销商数据设置逻辑
- 移除冗余的单据类型判断逻辑,统一处理发货单据生成流程
- 导出新增的 DeliveryStep 和 PurchaseStep 相关组件
- 在发货列表页集成新的发货单据生成功能并优化界面交互
2025-11-19 08:01:54 +08:00
shenyifei
5bdd8afdd6 feat(app-client): 优化采购订单审核与供应商发票上传功能
- 优化 PageList 组件的选中行样式与全选逻辑
- 重构采购订单相关组件,移除冗余的 dealerVO 属性,统一使用 orderDealer
- 完善成本差异、税金计提、税金补贴等模块的默认值计算逻辑
- 新增供应商发票与合同照片上传弹窗功能
- 改进供应商页面的车次选择与数据统计展示
- 升级 app 版本号至 v0.0.19
- 优化头像图片样式与组件代码结构
- 修复 dealerVO 数据获取逻辑,替换为 dealerRebateCustomer 数据源
- 增加页面加载状态控制与条件渲染逻辑
- 完善 uploader 组件的文件变更处理与提交逻辑
2025-11-18 22:55:57 +08:00
shenyifei
761bc7c8ed feat(supplier): 新增瓜农管理和发票上传功能
- 在 PageList 组件中实现选择行功能,支持复选框操作和状态管理
- 更新 ToolBar 类型定义,允许传递任意类型的选中数据
- 添加新的图标资源:eye、eye-slash 和 phone-flip
- 重构 SupplierPicker 组件以适配最新的 API 接口类型
- 在工作台常量中增加“瓜农管理”和“上传发票”页面路由配置
- 创建供应商采购发票页面及列表展示组件
- 实现瓜农列表页面,支持搜索和详情展示
- 引入并导出订单供应商相关服务接口和类型定义
- 扩展业务模块的类型声明文件,新增订单供应商相关结构体
- 更新字体图标库版本,并添加新图标样式
- 在应用配置中注册新的供应商相关页面路径
- 优化采购订单计算器逻辑,排除特定费用项的重复计算
2025-11-18 18:31:16 +08:00
45 changed files with 3744 additions and 3545 deletions

View File

@ -42,6 +42,11 @@ config = {
root: "pages/delivery",
pages: ["list", "document/delivery", "document/purchase"],
},
// 瓜农
{
root: "pages/supplier",
pages: ["list", "purchase/invoice"],
},
],
permission: {
"scope.userLocation": {

View File

@ -248,7 +248,7 @@ export default <T extends {}, Q extends Query = Query>(
list={data}
itemRender={(item, index) =>
(toolbar?.selectRow && (
<View className={"flex items-center justify-center"}>
<View className={"flex items-center justify-center bg-white"}>
<Checkbox
className={"pl-2"}
checked={selectRows.indexOf(item[rowId]) != -1}
@ -302,7 +302,43 @@ export default <T extends {}, Q extends Query = Query>(
onLoadMore={loadMore}
onRefresh={refresh}
>
{data.map((item, index) => render(item as any, index))}
{data.map(
(item, index) =>
(toolbar?.selectRow && (
<View
className={
"mb-2.5 flex flex-1 items-start gap-2.5 rounded-md bg-white p-2.5 shadow-md"
}
key={index}
>
<Checkbox
checked={selectRows.some(
(item1) => item1[rowId] == item[rowId],
)}
value={item[rowId]}
onClick={() => {
if (
selectRows.some(
(item1) => item1[rowId] === item[rowId],
)
) {
setSelectRows((prev) => {
return prev.filter(
(item1) => item1[rowId] !== item[rowId],
);
});
} else {
setSelectRows((prev) => {
return [...prev, item];
});
}
}}
></Checkbox>
{render(item as any, index)}
</View>
)) ||
render(item as any, index),
)}
</InfiniteLoading>
</View>
)}
@ -315,7 +351,7 @@ export default <T extends {}, Q extends Query = Query>(
{toolbar?.actions && toolbar?.actions.map((item) => item)}
{toolbar?.selectRow?.onClick && (
<View key={"confirm"} className={"flex justify-between gap-2"}>
<View key={"confirm"} className={"flex justify-between gap-2 p-2.5"}>
<View className={"flex flex-1 items-center justify-between gap-1"}>
<View className={"flex items-center gap-2"}>
<Checkbox
@ -329,9 +365,13 @@ export default <T extends {}, Q extends Query = Query>(
} else {
setSelectAll("1");
data?.forEach((item) => {
if (selectRows.indexOf(item[rowId]) == -1) {
if (
!selectRows.some(
(item1) => item1[rowId] === item[rowId],
)
) {
setSelectRows((prev) => {
return [...prev, item[rowId]];
return [...prev, item];
});
}
});
@ -343,12 +383,13 @@ export default <T extends {}, Q extends Query = Query>(
</Text>
</View>
<Text className={"text-sm text-stone-950"}>
{selectRows.length || 0}
{selectRows.length || 0}
</Text>
</View>
<Button
type={"primary"}
size={"large"}
disabled={!selectRows.length}
block={false}
onClick={async () => toolbar?.selectRow?.onClick(selectRows)}
>

View File

@ -23,7 +23,7 @@ export type TabPane = {
export type ToolBar = {
selectRow?: {
onClick: (selectRow: string[]) => void;
onClick: (selectRow: any[]) => void;
};
actions?: React.ReactNode[];
tabs?: {

View File

@ -0,0 +1,574 @@
import { forwardRef, ReactNode, useImperativeHandle, useState } from "react";
import { Text, View } from "@tarojs/components";
import {
DatePicker,
Input,
PickerOption,
Toast,
} from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import { Icon } from "@/components";
interface Step1FormProps {
moduleList: any[];
shipOrderVO: any;
setShipOrderVO: (value: any) => void;
}
export interface Step1FormRef {
validateForm: () => boolean;
}
const Step1Form = forwardRef<Step1FormRef, Step1FormProps>((props, ref) => {
const { moduleList, shipOrderVO, setShipOrderVO } = props;
// 当天和未来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 [formErrors, setFormErrors] = useState<{
watermelonGrade?: boolean;
shippingAddress?: boolean;
estimatedArrivalDate?: boolean;
remark?: boolean;
itemGrades?: { [key: string]: boolean };
}>({});
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
validateForm,
}));
// 渲染内容配置表单字段
const renderContentFields = (module: any) => {
const contentSchema = module.schemas.find(
(schema: any) => 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: any) => {
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: any) => {
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: any) => {
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: any) => {
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: any, index: number) => {
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: any) => {
return {
...prev!,
shipOrderItemList: prev?.shipOrderItemList?.map(
(item: any) => {
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: any) => 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;
};
return (
<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>
);
});
export default Step1Form;

View File

@ -0,0 +1,186 @@
import { useState } from "react";
import { Text, View } from "@tarojs/components";
import { Button } from "@nutui/nutui-react-taro";
import {
DealerInfo,
OtherFees,
OtherInfo,
PackingSpec,
ShippingInfo,
TitleInfo,
TotalAmount,
VehicleInfo,
WeightInfo,
} from "./section";
interface Step2PreviewProps {
moduleList: any[];
}
export default function Step2Preview(props: Step2PreviewProps) {
const { moduleList } = props;
const [scale, setScale] = useState(0.5); // 缩放比例
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 }); // 起始位置
// 放大
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: any) => {
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: any) => {
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: any) => {
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: any) => {
// 可以在这里添加触摸结束后的处理逻辑
console.log("Touch ended", e);
};
return (
<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) => {
if (module.type === "title") {
return <TitleInfo key={module.id} module={module} />;
}
if (module.type === "dealerInfo") {
return <DealerInfo key={"dealerInfo"} module={module} />;
}
if (module.type === "shippingInfo") {
return <ShippingInfo key={module.id} module={module} />;
}
if (module.type === "weightInfo") {
return <WeightInfo key={"weightInfo"} module={module} />;
}
if (module.type === "packingSpec") {
return <PackingSpec key={module.id} module={module} />;
}
if (module.type === "vehicleInfo") {
return <VehicleInfo key={module.id} module={module} />;
}
if (module.type === "otherFees") {
return <OtherFees key={module.id} module={module} />;
}
if (module.type === "totalAmount") {
return <TotalAmount key={module.id} module={module} />;
}
if (module.type === "otherInfo") {
return <OtherInfo key={module.id} module={module} />;
}
})}
</View>
</View>
</View>
);
}

View File

@ -0,0 +1,122 @@
import { useState } from "react";
import { Text, View } from "@tarojs/components";
import { Button } from "@nutui/nutui-react-taro";
import Taro from "@tarojs/taro";
interface Step3SuccessProps {
document: string;
}
export default function Step3Success(props: Step3SuccessProps) {
const { document } = props;
const [tempFilePath, setTempFilePath] = useState<string>();
// 查看文档
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: document!,
}).then((downloadRes) => {
if (downloadRes.tempFilePath) {
setTempFilePath(downloadRes.tempFilePath);
Taro.showToast({
title: "发货单据已下载成功您可以将生成的PDF发送给好友",
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: `发货单据.pdf`,
complete: (res) => {
console.log("complete", res);
},
});
};
return (
<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">
{document && !tempFilePath && (
<View className={"flex-1"}>
<Button type="default" size="large" block onClick={handleDownload}>
</Button>
</View>
)}
{document && 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 File

@ -0,0 +1,4 @@
export { default as DeliveryStep1Form } from "./Step1Form";
export type { Step1FormRef as DeliveryStep1FormRef } from "./Step1Form";
export { default as DeliveryStep2Preview } from "./Step2Preview";
export { default as DeliveryStep3Success } from "./Step3Success";

View File

@ -0,0 +1,32 @@
import { View } from "@tarojs/components";
export default function DealerInfo(props: { module: any }) {
const { module } = props;
const { config } = module;
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>
);
}

View File

@ -0,0 +1,24 @@
import { View } from "@tarojs/components";
export default function OtherFees(props: { module: any }) {
const { module } = props;
const { config } = module;
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.itemId]}
</View>
<View className="col-span-1 flex items-end justify-center border-b border-black">
{feeType.count * feeType.price}
</View>
</>
))}
</View>
);
}

View File

@ -0,0 +1,98 @@
import { View } from "@tarojs/components";
export default function OtherInfo(props: { module: any }) {
const { module } = props;
const { config } = module;
return (
<View key={module.id} className={"table-border"}>
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">{config.vehicleNumber}</View>
</View>
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">{config.destination}</View>
</View>
{config.showShippingFrom && (
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold"></View>
<View className="col-span-2 p-2 text-left">
{config.shippingFrom}
</View>
</View>
)}
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">
{config.accountCompany}
</View>
</View>
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">{config.date}</View>
</View>
{config.showEstimatedArrivalTime && (
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">
{config.estimatedArrivalTime}
</View>
</View>
)}
{config.data?.map((item: any) => {
return (
<>
{config.showGrade && (
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">
{item.watermelonGrade}
</View>
</View>
)}
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">
</View>
</View>
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold"></View>
<View className="col-span-2 p-2 text-left">
{item.unitPrice}
{config.unitPriceUnit === "1" ? "元/斤" : "元/公斤"}
</View>
</View>
</>
);
})}
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold"></View>
<View className="p-2 text-left">
{config.data?.reduce(
(acc: any, cur: any) => acc + cur.grossWeight,
0,
)}
</View>
<View className="p-2 text-left"></View>
</View>
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold"></View>
<View className="p-2 text-left">
{config.data?.reduce((acc: any, cur: any) => acc + cur.boxCount, 0)}
</View>
<View className="p-2 text-left"></View>
</View>
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">{config.licensePlate}</View>
</View>
<View className="grid w-full grid-cols-3 gap-0 text-base">
<View className="p-2 text-left font-bold">:</View>
<View className="col-span-2 p-2 text-left">{config.driverPhone}</View>
</View>
</View>
);
}

View File

@ -0,0 +1,185 @@
import { View } from "@tarojs/components";
import classNames from "classnames";
export default function PackingSpec(props: { module: any }) {
const { module } = props;
const { config } = module;
// 计算需要显示的列数
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>
</>
);
}

View File

@ -0,0 +1,34 @@
import { View } from "@tarojs/components";
export default function ShippingInfo(props: { module: any }) {
const { module } = props;
const { config } = module;
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>
);
}

View File

@ -0,0 +1,17 @@
import { View } from "@tarojs/components";
export default function TitleInfo(props: { module: any }) {
const { module } = props;
const { config } = module;
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>
);
}

View File

@ -0,0 +1,37 @@
import { View } from "@tarojs/components";
export default function TotalAmount(props: { module: any }) {
const { module } = props;
const { config } = module;
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>
);
}

View File

@ -0,0 +1,74 @@
import { View } from "@tarojs/components";
export default function VehicleInfo(props: { module: any }) {
const { module } = props;
const { config } = module;
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>
);
}

View File

@ -0,0 +1,119 @@
import { View } from "@tarojs/components";
export default function WeightInfo(props: { module: any }) {
const { module } = props;
const { config } = module;
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>
</>
);
}

View File

@ -0,0 +1,9 @@
export {default as DealerInfo} from './DealerInfo'
export {default as WeightInfo} from './WeightInfo'
export {default as TitleInfo} from './TitleInfo'
export {default as ShippingInfo} from './ShippingInfo'
export {default as PackingSpec} from './PackingSpec'
export {default as VehicleInfo} from './VehicleInfo'
export {default as OtherFees} from './OtherFees'
export {default as TotalAmount} from './TotalAmount'
export {default as OtherInfo} from './OtherInfo'

View File

@ -3,6 +3,9 @@ import classNames from "classnames";
import React from "react";
export type IconNames =
| "eye"
| "eye-slash"
| "phone-flip"
| "address-book"
| "pen-to-square"
| "location-dot"

View File

@ -5,3 +5,4 @@ export * from "./purchase";
export * from "./dealer";
export * from "./supplier";
export * from "./company";
export * from "./delivery";

View File

@ -36,3 +36,8 @@ export { default as CostDifferenceSection } from "./section/CostDifferenceSectio
export { default as MaterialCostSection } from "./section/MaterialCostSection";
export { default as ProductionAdvanceSection } from "./section/ProductionAdvanceSection";
export { default as WorkerAdvanceSection } from "./section/WorkerAdvanceSection";
export { default as PurchaseStep1Form } from "./document/Step1Form";
export type { Step1FormRef as PurchaseStep1FormRef } from "./document/Step1Form";
export { default as PurchaseStep2Preview } from "./document/Step2Preview";
export { default as PurchaseStep3Success } from "./document/Step3Success";

View File

@ -553,15 +553,7 @@ export default forwardRef<OrderVehicleRef, IOrderVehicleProps>(
if (dealerVO) {
setOrderDealer({
...orderDealer,
dealerId: dealerVO.dealerId,
shortName: dealerVO?.shortName!,
dealerType: dealerVO.dealerType!,
enableShare: dealerVO.enableShare,
shareRatio: dealerVO.shareRatio,
freightCostFlag: dealerVO.freightCostFlag,
strawMatCostFlag: dealerVO.strawMatCostFlag,
includePackingFlag: dealerVO.includePackingFlag,
documentTypes: dealerVO.documentTypes,
...dealerVO,
});
}

View File

@ -5,34 +5,31 @@ import { validatePrice } from "@/utils/format";
import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator";
export default function CostDifferenceSection(props: {
dealerVO: BusinessAPI.DealerVO;
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly } = props;
const orderDealer = purchaseOrderVO.orderDealer;
const calculator = new PurchaseOrderCalculator(purchaseOrderVO);
const [visible, setVisible] = useState(false);
// 主状态,用于页面显示
const [costDifference, setCostDifference] = useState<number>(
purchaseOrderVO.orderDealer?.costDifference || 0,
orderDealer?.costDifference || 0,
);
// 弹窗内的临时状态
const [tempCostDifference, setTempCostDifference] = useState<number>(
purchaseOrderVO.orderDealer?.costDifference || 0,
orderDealer?.costDifference || 0,
);
const profitSharing =
calculator.getDefaultNetProfit() ||
purchaseOrderVO.orderDealer?.profitSharing ||
0;
calculator.getDefaultNetProfit() || orderDealer?.profitSharing || 0;
// 当dealerVO变化时自动计算分成
useEffect(() => {
if (!purchaseOrderVO.orderDealer?.costDifference) {
if (!orderDealer?.costDifference) {
const defaultCostDifference = calculator.getCostDifference();
setCostDifference(defaultCostDifference);
setTempCostDifference(defaultCostDifference);
@ -40,7 +37,7 @@ export default function CostDifferenceSection(props: {
onChange?.({
...purchaseOrderVO,
orderDealer: {
...purchaseOrderVO.orderDealer,
...orderDealer,
costDifference: defaultCostDifference,
profitSharing: profitSharing,
},
@ -56,7 +53,7 @@ export default function CostDifferenceSection(props: {
onChange?.({
...purchaseOrderVO,
orderDealer: {
...purchaseOrderVO.orderDealer,
...orderDealer,
costDifference: tempCostDifference,
profitSharing: profitSharing,
},

View File

@ -33,15 +33,7 @@ export default function (props: {
if (readOnly) return;
const newOrderDealer: BusinessAPI.OrderDealer = {
dealerId: dealerVO.dealerId,
shortName: dealerVO?.shortName!,
dealerType: dealerVO.dealerType!,
enableShare: dealerVO.enableShare,
shareRatio: dealerVO.shareRatio,
freightCostFlag: dealerVO.freightCostFlag,
strawMatCostFlag: dealerVO.strawMatCostFlag,
includePackingFlag: dealerVO.includePackingFlag,
documentTypes: dealerVO.documentTypes,
...dealerVO,
};
setOrderDealer(newOrderDealer);

View File

@ -5,30 +5,30 @@ import { validatePrice } from "@/utils/format";
import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator";
export default function TaxProvisionSection(props: {
dealerVO: BusinessAPI.DealerVO;
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly, dealerVO } = props;
const { purchaseOrderVO, onChange, readOnly } = props;
const orderDealer = purchaseOrderVO.orderDealer;
const calculator = new PurchaseOrderCalculator(purchaseOrderVO);
const [visible, setVisible] = useState(false);
const [taxProvision, setTaxProvision] = useState<number>(
purchaseOrderVO.orderDealer?.taxProvision || 0,
orderDealer?.taxProvision || 0,
);
// 当dealerVO变化时自动计算计提税金
useEffect(() => {
if (!purchaseOrderVO.orderDealer?.taxProvision) {
if (!orderDealer?.taxProvision) {
const defaultValue = calculator.getDefaultTaxProvision();
setTaxProvision(defaultValue);
// 更新父组件的状态
onChange?.({
...purchaseOrderVO,
orderDealer: {
...purchaseOrderVO.orderDealer,
...orderDealer,
taxProvision: defaultValue,
},
});
@ -48,7 +48,7 @@ export default function TaxProvisionSection(props: {
onChange?.({
...purchaseOrderVO,
orderDealer: {
...purchaseOrderVO.orderDealer,
...orderDealer,
taxProvision: taxProvision,
},
});
@ -97,8 +97,8 @@ export default function TaxProvisionSection(props: {
<View className="flex items-center justify-between">
<Text className="text-sm text-gray-500"></Text>
<Text className="text-sm font-medium">
{dealerVO.accrualTaxRatio
? dealerVO.accrualTaxRatio + "%"
{orderDealer.accrualTaxRatio
? orderDealer.accrualTaxRatio + "%"
: "未设置"}
</Text>
</View>
@ -151,7 +151,7 @@ export default function TaxProvisionSection(props: {
</Button>
<View className="ml-2 self-center text-xs text-gray-500">
: ( - ) ×{" "}
{dealerVO.accrualTaxRatio || "未设置"}%
{orderDealer.accrualTaxRatio || "未设置"}%
</View>
</View>
</View>

View File

@ -5,30 +5,29 @@ import { validatePrice } from "@/utils/format";
import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator";
export default function TaxSubsidySection(props: {
dealerVO: BusinessAPI.DealerVO;
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly, dealerVO } = props;
const { purchaseOrderVO, onChange, readOnly } = props;
const orderDealer = purchaseOrderVO.orderDealer;
const calculator = new PurchaseOrderCalculator(purchaseOrderVO);
const [visible, setVisible] = useState(false);
const [taxSubsidy, setTaxSubsidy] = useState<number>(
purchaseOrderVO.orderDealer?.taxSubsidy || 0,
orderDealer?.taxSubsidy || 0,
);
// 当dealerVO变化时自动计算税费补贴
useEffect(() => {
if (!purchaseOrderVO.orderDealer?.taxSubsidy) {
if (!orderDealer?.taxSubsidy) {
const defaultValue = calculator.getDefaultTaxSubsidy();
setTaxSubsidy(defaultValue);
// 更新父组件的状态
onChange?.({
...purchaseOrderVO,
orderDealer: {
...purchaseOrderVO.orderDealer,
...orderDealer,
taxSubsidy: defaultValue,
},
});
@ -46,7 +45,7 @@ export default function TaxSubsidySection(props: {
onChange?.({
...purchaseOrderVO,
orderDealer: {
...purchaseOrderVO.orderDealer,
...orderDealer,
taxSubsidy,
},
});
@ -95,8 +94,8 @@ export default function TaxSubsidySection(props: {
<View className="flex items-center justify-between">
<Text className="text-sm text-gray-500"></Text>
<Text className="text-sm font-medium">
{dealerVO.companyRebateRatio
? dealerVO.companyRebateRatio + "%"
{orderDealer.companyRebateRatio
? orderDealer.companyRebateRatio + "%"
: "未设置"}
</Text>
</View>
@ -148,7 +147,7 @@ export default function TaxSubsidySection(props: {
</Button>
<View className="ml-2 self-center text-xs text-gray-500">
默认值: 市场报价 × {dealerVO.companyRebateRatio || "未设置"}%
默认值: 市场报价 × {orderDealer.companyRebateRatio || "未设置"}%
</View>
</View>
</View>

View File

@ -3,19 +3,18 @@ import { Icon } from "@/components";
import { ScrollView, View } from "@tarojs/components";
import React, { useEffect, useState } from "react";
import { business } from "@/services";
import { SupplierVO } from "@/types/typings";
interface ISupplierPickerProps {
trigger?: React.ReactNode;
onFinish: (supplierVO: SupplierVO) => void;
onFinish: (supplierVO: BusinessAPI.SupplierVO) => void;
}
export default function SupplierPicker(props: ISupplierPickerProps) {
const { onFinish, trigger } = props;
const [supplierVO, setSupplierVO] = useState<SupplierVO>();
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const [visible, setVisible] = useState(false);
const [supplierList, setSupplierList] = useState<SupplierVO[]>();
const [supplierList, setSupplierList] = useState<BusinessAPI.SupplierVO[]>();
const [searchText, setSearchText] = useState("");
useEffect(() => {
@ -28,7 +27,7 @@ export default function SupplierPicker(props: ISupplierPickerProps) {
const { data } = await business.supplier.listSupplier({
supplierListQry: {
name: value || undefined,
status: true
status: true,
},
});
@ -57,7 +56,7 @@ export default function SupplierPicker(props: ISupplierPickerProps) {
position="bottom"
onClose={async () => {
if (visible) {
setSupplierVO(undefined)
setSupplierVO(undefined);
setSearchText("");
setVisible(false);
} else if (supplierVO) {
@ -88,7 +87,7 @@ export default function SupplierPicker(props: ISupplierPickerProps) {
key={item.supplierId}
onClick={async () => {
setSupplierVO(item);
setSearchText("")
setSearchText("");
setVisible(false);
}}
>

View File

@ -1,2 +1,2 @@
// App 相关常量
export const APP_VERSION = "v0.0.12";
export const APP_VERSION = "v0.0.19";

View File

@ -126,6 +126,15 @@ const quickActionMap = {
icon: "file-invoice",
iconColor: "var(--color-orange-600)",
bgColorClass: "bg-orange-100",
path: "/pages/supplier/purchase/invoice",
},
{
id: "supplierManage",
title: "瓜农管理",
icon: "clipboard-list",
iconColor: "var(--color-green-600)",
bgColorClass: "bg-green-100",
path: "/pages/supplier/list",
},
],
"market-buyer": [
@ -145,13 +154,6 @@ const quickActionMap = {
bgColorClass: "bg-green-100",
path: "/pages/purchase/purchaser/history",
},
{
id: "invoiceUpload",
title: "上传发票",
icon: "file-invoice",
iconColor: "var(--color-orange-600)",
bgColorClass: "bg-orange-100",
},
],
reviewer: [
{

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 5042354 */
src: url('//at.alicdn.com/t/c/font_5042354_wqti51yo9xk.woff2?t=1762227792781') format('woff2'),
url('//at.alicdn.com/t/c/font_5042354_wqti51yo9xk.woff?t=1762227792781') format('woff'),
url('//at.alicdn.com/t/c/font_5042354_wqti51yo9xk.ttf?t=1762227792781') format('truetype');
src: url('//at.alicdn.com/t/c/font_5042354_hkkkrqw0kin.woff2?t=1763456511295') format('woff2'),
url('//at.alicdn.com/t/c/font_5042354_hkkkrqw0kin.woff?t=1763456511295') format('woff'),
url('//at.alicdn.com/t/c/font_5042354_hkkkrqw0kin.ttf?t=1763456511295') format('truetype');
}
.iconfont {
@ -13,6 +13,18 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-phone-flip:before {
content: "\e624";
}
.icon-eye:before {
content: "\e62f";
}
.icon-eye-slash:before {
content: "\e62e";
}
.icon-address-book:before {
content: "\e623";
}

File diff suppressed because it is too large Load Diff

View File

@ -23,10 +23,6 @@ export default hocAuth(function Page(props: CommonComponent) {
const [state, setState] = useState<BusinessAPI.ShipOrderPageQry["state"]>();
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const [canGenerateDocuments, setCanGenerateDocuments] = useState({
shipDocument: false,
purchaseDocument: false,
});
const [deliveryTemplate, setDeliveryTemplate] =
useState<BusinessAPI.DealerVO["deliveryTemplate"]>();
@ -37,7 +33,7 @@ export default hocAuth(function Page(props: CommonComponent) {
useState<BusinessAPI.ShipOrderVO | null>(null);
// 生成发货单据
const generateShipDocument = async () => {
const generateDocument = async () => {
if (!currentShipOrder?.shipOrderId) {
Toast.show("toast", {
icon: "fail",
@ -66,36 +62,6 @@ export default hocAuth(function Page(props: CommonComponent) {
}
};
// 生成采购底单单据
const generatePurchaseDocument = () => {
if (!currentShipOrder?.shipOrderId) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "未找到关联的发货单",
});
return;
}
try {
// 跳转到发货单据生成页面
Taro.navigateTo({
url: buildUrl("/pages/delivery/document/purchase", {
shipOrderId: currentShipOrder.shipOrderId,
}),
success: () => {
setPopupVisible(false);
},
});
} catch (error) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "跳转发货单据页面失败",
});
}
};
const actionRef = useRef<ActionType>();
const toolbar: ToolBar = {
search: {
@ -176,37 +142,23 @@ export default hocAuth(function Page(props: CommonComponent) {
</View>
<View className="flex flex-col gap-3">
{canGenerateDocuments.shipDocument && (
<>
{!deliveryTemplate ? (
<View className="border-t border-gray-200 pt-2.5">
<View className="flex flex-col gap-3">
<View className="mt-2.5 text-center text-sm text-red-500">
</View>
</View>
</View>
) : (
<View className="border-t border-gray-200 pt-2.5">
<View className="flex flex-col gap-3">
<View className="text-primary mt-2.5 text-center text-sm">
</View>
</View>
</View>
)}
</>
)}
{!canGenerateDocuments.purchaseDocument &&
!canGenerateDocuments.shipDocument && (
<View className="border-t border-gray-200 pt-2.5">
<View className="flex flex-col gap-3">
<View className="mt-2.5 text-center text-sm text-red-500">
</View>
{!deliveryTemplate ? (
<View className="border-t border-gray-200 pt-2.5">
<View className="flex flex-col gap-3">
<View className="mt-2.5 text-center text-sm text-red-500">
</View>
</View>
)}
</View>
) : (
<View className="border-t border-gray-200 pt-2.5">
<View className="flex flex-col gap-3">
<View className="text-primary mt-2.5 text-center text-sm">
</View>
</View>
</View>
)}
</View>
</View>
)}
@ -214,44 +166,27 @@ export default hocAuth(function Page(props: CommonComponent) {
<View className={"flex w-full flex-col bg-white"}>
<View className={"flex flex-row gap-2 p-3"}>
{canGenerateDocuments.purchaseDocument && currentShipOrder && (
<View className={"flex-1"}>
<Button
type="default"
size={"xlarge"}
block
onClick={generatePurchaseDocument}
>
</Button>
</View>
)}
{canGenerateDocuments.shipDocument && currentShipOrder && (
<View className={"flex-1"}>
<Button
type="primary"
size={"xlarge"}
disabled={!deliveryTemplate}
block
onClick={generateShipDocument}
>
</Button>
</View>
)}
{!canGenerateDocuments.purchaseDocument &&
!canGenerateDocuments.shipDocument && (
<View className={"flex-1"}>
<Button
type="default"
size={"xlarge"}
block
onClick={() => setPopupVisible(false)}
>
</Button>
</View>
)}
<View className={"flex-1"}>
<Button
type="default"
size={"xlarge"}
block
onClick={() => setPopupVisible(false)}
>
</Button>
</View>
<View className={"flex-1"}>
<Button
type="primary"
size={"xlarge"}
disabled={!deliveryTemplate}
block
onClick={generateDocument}
>
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
@ -300,36 +235,6 @@ export default hocAuth(function Page(props: CommonComponent) {
return {};
});
// 生成发货单据
const generateDocument = async (shipOrderVO: BusinessAPI.ShipOrderVO) => {
setCurrentShipOrder(shipOrderVO);
const { data: dealerData } = await business.dealer.showDealer({
dealerShowQry: {
dealerId: shipOrderVO.dealerId,
},
});
if (dealerData.success) {
const deliveryTemplate = dealerData.data?.deliveryTemplate;
setDeliveryTemplate(deliveryTemplate);
const documentTypes = dealerData.data?.documentTypes;
if (documentTypes) {
setCanGenerateDocuments({
shipDocument: documentTypes.includes("delivery"),
purchaseDocument: documentTypes.includes("purchase"),
});
} else {
setCanGenerateDocuments({
shipDocument: false,
purchaseDocument: false,
});
}
}
setPopupVisible(true);
};
return (
<PageList<BusinessAPI.ShipOrderVO, BusinessAPI.ShipOrderPageQry>
rowId={"shipOrderId"}
@ -410,16 +315,47 @@ export default hocAuth(function Page(props: CommonComponent) {
</View>
<View className={"py-2.5"}>
<View className={"flex flex-row justify-end gap-2"}>
<Button
type={"primary"}
size={"small"}
onClick={async (e) => {
await generateDocument(shipOrderVO);
e.stopPropagation();
}}
>
</Button>
{shipOrderVO.document ? (
<Button
type={"primary"}
size={"small"}
onClick={() => {
Taro.navigateTo({
url: buildUrl("/pages/delivery/document/delivery", {
shipOrderId: shipOrderVO.shipOrderId,
}),
});
}}
>
</Button>
) : (
<Button
type={"primary"}
size={"small"}
onClick={async (e) => {
setCurrentShipOrder(shipOrderVO);
const { data: dealerData } =
await business.dealer.showDealer({
dealerShowQry: {
dealerId: shipOrderVO.dealerId,
},
});
if (dealerData.success) {
const deliveryTemplate =
dealerData.data?.deliveryTemplate;
setDeliveryTemplate(deliveryTemplate);
}
setPopupVisible(true);
e.stopPropagation();
}}
>
</Button>
)}
</View>
</View>
</View>

View File

@ -170,7 +170,7 @@ export default hocAuth(function Page(props: CommonComponent) {
<Image
src={employeeVO?.avatar}
mode={"aspectFill"}
className={"rounded-full"}
className={"h-full w-full overflow-hidden rounded-full"}
/>
</View>
) : (

View File

@ -13,10 +13,6 @@ export default hocAuth(function Page(props: CommonComponent) {
const [purchaseOrder, setPurchaseOrder] =
useState<BusinessAPI.PurchaseOrderVO>();
const [canGenerateDocuments, setCanGenerateDocuments] = useState({
shipDocument: false,
purchaseDocument: false,
});
const [shipOrder, setShipOrder] = useState<BusinessAPI.ShipOrderVO>();
const [deliveryTemplate, setDeliveryTemplate] =
useState<BusinessAPI.DealerVO["deliveryTemplate"]>();
@ -55,14 +51,7 @@ export default hocAuth(function Page(props: CommonComponent) {
},
});
if (dealerData.success && dealerData.data?.documentTypes) {
const documentTypes = dealerData.data.documentTypes;
setDeliveryTemplate(dealerData.data.deliveryTemplate);
setCanGenerateDocuments({
shipDocument: documentTypes.includes("delivery"),
purchaseDocument: documentTypes.includes("purchase"),
});
}
setDeliveryTemplate(dealerData.data?.deliveryTemplate);
}
}
} catch (error) {
@ -83,16 +72,7 @@ export default hocAuth(function Page(props: CommonComponent) {
}, [orderId]);
// 生成发货单据
const generateShipDocument = async () => {
if (!canGenerateDocuments.shipDocument) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "当前经销商不支持生成发货单据",
});
return;
}
const generateDocument = async () => {
if (!shipOrder?.shipOrderId) {
Toast.show("toast", {
icon: "fail",
@ -118,42 +98,6 @@ export default hocAuth(function Page(props: CommonComponent) {
}
};
// 生成采购底单单据
const generatePurchaseDocument = () => {
if (!canGenerateDocuments.purchaseDocument) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "当前经销商不支持生成采购底单单据",
});
return;
}
if (!shipOrder?.shipOrderId) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "未找到关联的发货单",
});
return;
}
try {
// 跳转到发货单据生成页面
Taro.navigateTo({
url: buildUrl("/pages/delivery/document/purchase", {
shipOrderId: shipOrder.shipOrderId,
}),
});
} catch (error) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "跳转发货单据页面失败",
});
}
};
return (
<View className="flex flex-1 flex-col gap-2.5">
<View className="flex flex-1 flex-col items-center justify-start bg-gray-100 p-2.5">
@ -215,31 +159,19 @@ export default hocAuth(function Page(props: CommonComponent) {
</View>
<View className="flex flex-col gap-3">
{canGenerateDocuments.shipDocument &&
(deliveryTemplate ? (
<Button
type="primary"
size={"xlarge"}
block
onClick={generateShipDocument}
>
</Button>
) : (
<View className="mt-2.5 text-center text-sm text-red-500">
</View>
))}
{canGenerateDocuments.purchaseDocument && shipOrder && (
{deliveryTemplate ? (
<Button
type="default"
type="primary"
size={"xlarge"}
block
onClick={generatePurchaseDocument}
onClick={generateDocument}
>
</Button>
) : (
<View className="mt-2.5 text-center text-sm text-red-500">
</View>
)}
</View>
</View>

View File

@ -5,7 +5,16 @@ import { business } from "@/services";
import { useEffect, useState } from "react";
import { View } from "@tarojs/components";
import purchaseOrder from "@/constant/purchaseOrder";
import { ActionSheet, Button, Dialog, Input, Popup, SafeArea, TextArea, Toast } from "@nutui/nutui-react-taro";
import {
ActionSheet,
Button,
Dialog,
Input,
Popup,
SafeArea,
TextArea,
Toast,
} from "@nutui/nutui-react-taro";
import {
BasicInfoSection,
CompanyInfoSection,
@ -22,7 +31,7 @@ import {
State,
TaxProvisionSection,
TaxSubsidySection,
WorkerAdvanceSection
WorkerAdvanceSection,
} from "@/components";
import buildUrl from "@/utils/buildUrl";
import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator";
@ -116,7 +125,7 @@ const sections = {
};
export default hocAuth(function Page(props: CommonComponent) {
const { router, isInitialized, setIsInitialized, role } = props;
const { router, isInitialized, setIsInitialized, role, setLoading } = props;
const orderId = router.params
.orderId as BusinessAPI.PurchaseOrderVO["orderId"];
@ -216,17 +225,20 @@ export default hocAuth(function Page(props: CommonComponent) {
// 控制更多操作的ActionSheet显示状态
const [moreActionVisible, setMoreActionVisible] = useState(false);
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const [dealerRebateCustomerVOList, setDealerRebateCustomerVOList] =
useState<BusinessAPI.DealerRebateCustomerVO[]>();
const initDealer = async (dealerId: BusinessAPI.DealerVO["dealerId"]) => {
const { data } = await business.dealer.showDealer({
dealerShowQry: {
const {
data: { data: dealerRebateCustomerVOList },
} = await business.dealerRebateCustomer.listDealerRebateCustomer({
dealerRebateCustomerListQry: {
dealerId: dealerId,
status: true,
},
});
setDealerVO(data.data);
setDealerRebateCustomerVOList(dealerRebateCustomerVOList);
};
// 暂存操作
@ -357,8 +369,10 @@ export default hocAuth(function Page(props: CommonComponent) {
useEffect(() => {
if (orderId && !isInitialized) {
setLoading(true);
init(orderId).then(() => {
setIsInitialized(true);
setLoading(false);
});
}
}, []);
@ -369,7 +383,7 @@ export default hocAuth(function Page(props: CommonComponent) {
}
});
if (!purchaseOrderVO) {
if (!purchaseOrderVO || !dealerRebateCustomerVOList) {
return;
}
@ -436,16 +450,32 @@ export default hocAuth(function Page(props: CommonComponent) {
{/* 循环渲染各部分内容 */}
{Object.keys(sections).map((sectionKey) => {
const section = sections[sectionKey];
const orderDealer = purchaseOrderVO.orderDealer;
if (!dealerVO?.enableCompanyRebate && sectionKey === "taxSubsidy") {
if (
!orderDealer?.enableCompanyRebate &&
sectionKey === "taxSubsidy"
) {
return null;
}
if (!dealerVO?.enableAccrualTax && sectionKey === "taxProvision") {
if (
!orderDealer?.enableAccrualTax &&
sectionKey === "taxProvision"
) {
return null;
}
if (!dealerVO?.enableShare && sectionKey === "costDifference") {
if (!orderDealer?.enableShare && sectionKey === "costDifference") {
return null;
}
// 如果没有返点人这个模块,则不渲染
if (
(!dealerRebateCustomerVOList ||
dealerRebateCustomerVOList.length === 0) &&
sectionKey === "rebateCalc"
) {
return null;
}
@ -456,7 +486,6 @@ export default hocAuth(function Page(props: CommonComponent) {
className={`overflow-x-auto rounded-md rounded-b-lg bg-white p-2.5 shadow-sm`}
>
<section.component
dealerVO={dealerVO}
purchaseOrderVO={purchaseOrderVO}
onChange={setPurchaseOrderVO}
costItemVOList={costItemVOList}

View File

@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: "瓜农管理",
navigationBarBackgroundColor: "#fff",
});

View File

@ -0,0 +1,127 @@
import { ActionType, PageList, ToolBar } from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import { useRef } from "react";
import { business } from "@/services";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import { Label, Text, View } from "@tarojs/components";
import dayjs from "dayjs";
import Phone from "../../components/biz/Phone";
export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props;
const actionRef = useRef<ActionType>();
const toolbar: ToolBar = {
search: {
activeKey: "name",
defaultActiveKey: "name",
items: [
{
key: "name",
name: "瓜农名称",
placeholder: "请输入瓜农名称",
},
],
},
};
useShareAppMessage((res) => {
console.log("useShareAppMessage1", res, shareOptions);
// 如果是按钮触发的转发,使用默认配置
if (res.from === "button") {
return shareOptions;
}
// 页面转发使用设置的配置
return {};
});
return (
<PageList<BusinessAPI.SupplierVO, BusinessAPI.SupplierPageQry>
rowId={"supplierId"}
itemHeight={182}
type={"infinite"}
actionRef={actionRef}
render={(supplierVO: BusinessAPI.SupplierVO, index) => (
<View className={"mb-2.5"} key={index}>
<View
className={
"relative flex flex-col divide-y-2 divide-neutral-100 rounded-lg bg-white px-2.5"
}
>
<View className={"flex flex-col divide-y-2 divide-neutral-100"}>
<View className={"py-2.5"}>
<View className={"flex flex-row items-center"}>
<View className={"flex flex-1 flex-col gap-2"}>
<View className={"flex flex-row gap-1"}>
{/* 复制 */}
<Text
className={"text-neutral-darkest text-xl font-bold"}
>
{supplierVO?.name}
</Text>
</View>
</View>
</View>
</View>
<View className={"py-2.5"}>
<View className={"flex flex-col gap-2"}>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<Text className={"text-neutral-darkest text-sm"}>
{dayjs(supplierVO.createdAt).format("MM-DD HH:mm")}
</Text>
</View>
<View
className={
"flex flex-row items-center justify-between gap-2.5"
}
>
<Label className={"text-neutral-dark text-sm"}>
</Label>
<View
className={
"flex flex-1 flex-row items-center justify-end gap-1"
}
>
<Phone phone={supplierVO.phone} />
</View>
</View>
</View>
</View>
</View>
<View className={"py-2.5"}>
<View className={"flex flex-row justify-end gap-2"}></View>
</View>
</View>
</View>
)}
toolbar={toolbar}
request={async (params) => {
const {
data: { data, success, notEmpty },
} = await business.supplier.pageSupplier({
supplierPageQry: {
...params,
},
});
return {
data,
success,
hasMore: notEmpty,
};
}}
pagination={{
pageSize: 10,
}}
/>
);
});

View File

@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: "上传发票",
navigationBarBackgroundColor: "#fff",
});

View File

@ -0,0 +1,358 @@
import {
ActionType,
Icon,
PageList,
SupplierPicker,
ToolBar,
} from "@/components";
import { useShareAppMessage } from "@tarojs/taro";
import { useRef, useState } from "react";
import { business } from "@/services";
import hocAuth from "@/hocs/auth";
import { CommonComponent } from "@/types/typings";
import { View } from "@tarojs/components";
import dayjs from "dayjs";
import {
Button,
Popup,
SafeArea,
Toast,
Uploader,
UploaderFileItem,
} from "@nutui/nutui-react-taro";
import { uploadFile } from "@/utils/uploader";
export default hocAuth(function Page(props: CommonComponent) {
const { shareOptions } = props;
const [supplierVO, setSupplierVO] = useState<BusinessAPI.SupplierVO>();
const [popupVisible, setPopupVisible] = useState(false);
const [selectedOrders, setSelectedOrders] = useState<
BusinessAPI.OrderSupplierVO[]
>([]);
const [contractFiles, setContractFiles] = useState<any[]>([]);
const [invoiceFiles, setInvoiceFiles] = useState<any[]>([]);
// 发票照片
const [invoiceImgList, setInvoiceImgList] = useState<UploaderFileItem[]>([]);
// 合同照片
const [contractImgList, setContractImgList] = useState<UploaderFileItem[]>(
[],
);
// 发票照片变更处理函数
const handleInvoiceImgChange = (files: UploaderFileItem[]) => {
setInvoiceImgList(files);
// 如果有文件且上传成功保存URL到supplierVO
if (files.length > 0 && files[0].url) {
setSupplierVO((prev) => ({
...prev!,
invoiceImg: [files[0].url!],
invoiceUpload: true,
}));
} else {
// 如果没有文件清空URL
setSupplierVO((prev) => ({
...prev!,
invoiceImg: undefined,
invoiceUpload: false,
}));
}
};
// 合同照片变更处理函数
const handleContractImgChange = (files: UploaderFileItem[]) => {
setContractImgList(files);
// 保存所有文件URL到supplierVO
const urls = files.map((file) => file.url).filter((url) => url) as string[];
setSupplierVO((prev) => ({
...prev!,
contractImg: urls,
contractUpload: urls.length > 0,
}));
};
const actionRef = useRef<ActionType>();
const toolbar: ToolBar = {
selectRow: {
onClick: async (orderSupplierVOList: BusinessAPI.OrderSupplierVO[]) => {
console.log("orderSupplierVOList", orderSupplierVOList);
// 点击弹出popup
setSelectedOrders(orderSupplierVOList);
setPopupVisible(true);
},
},
search: {
activeKey: "vehicleNo",
defaultActiveKey: "vehicleNo",
items: [
{
key: "vehicleNo",
name: "车次号",
placeholder: "请输入车次号",
},
{
key: "plate",
name: "车牌号",
placeholder: "请输入车牌号",
},
],
},
render: () => (
<>
{/* Popup 弹窗 */}
<Popup
visible={popupVisible}
position="bottom"
onClose={() => setPopupVisible(false)}
onOverlayClick={() => setPopupVisible(false)}
lockScroll
round
>
<View className="border-t border-green-100 bg-green-50 p-3">
{/* 统计信息 */}
<View className="mb-2 flex items-center justify-between">
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">
{selectedOrders.length}
</View>{" "}
</View>
<View className="text-sm font-medium text-gray-800">
<View className="text-primary inline">{totalWeight}</View>
</View>
<View className="text-sm font-medium text-gray-800">
{" "}
<View className="text-primary inline">
{totalAmount.toFixed(2)}
</View>
</View>
</View>
{/* 上传发票 */}
<View className="mb-2 flex items-center justify-between">
<View className="flex-1">
<Uploader
className={"w-full"}
value={invoiceImgList}
onChange={handleInvoiceImgChange}
sourceType={["album", "camera"]}
uploadIcon={<Icon name={"camera"} size={36} />}
uploadLabel={
<View className={"flex flex-col items-center"}>
<View className="text-sm"></View>
<View className="mt-1 text-xs text-gray-400">
</View>
</View>
}
maxCount={1}
//@ts-ignore
upload={uploadFile}
multiple
/>
</View>
</View>
{/* 上传合同 */}
<View className="mb-2 flex items-center justify-between">
<View className="flex-1">
<Uploader
className={"w-full"}
value={contractImgList}
onChange={handleContractImgChange}
sourceType={["album", "camera"]}
uploadIcon={<Icon name={"camera"} size={36} />}
uploadLabel={
<View className={"flex flex-col items-center"}>
<View className="text-sm"></View>
<View className="mt-1 text-xs text-gray-400">
</View>
</View>
}
maxCount={9}
//@ts-ignore
upload={uploadFile}
multiple
/>
</View>
</View>
<View className={"flex flex-1 flex-row gap-2.5"}>
<View className={"flex-1"}>
<Button
size={"xlarge"}
block
type="default"
onClick={() => setPopupVisible(false)}
>
</Button>
</View>
{/* 提交按钮 */}
<View className={"flex-1"}>
<Button
size={"xlarge"}
block
type="primary"
disabled={
contractFiles.length === 0 || invoiceFiles.length === 0
}
onClick={handleSubmit}
>
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
</Popup>
<View className={"flex flex-row gap-2.5"}>
<SupplierPicker
onFinish={(supplierVO) => {
setSupplierVO(supplierVO);
actionRef.current?.reload();
}}
trigger={
<View
className={`"border-2 border-primary flex h-6 items-center rounded-md px-2.5`}
>
<View className={"text-primary text-xs"}>
{supplierVO?.name || "瓜农"}
</View>
{supplierVO?.name ? (
<Icon
name={"circle-xmark"}
size={16}
onClick={(event) => {
setSupplierVO(undefined);
actionRef.current?.reload();
event.stopPropagation();
}}
/>
) : (
<Icon name={"chevron-down"} size={16} />
)}
</View>
}
/>
</View>
</>
),
};
useShareAppMessage((res) => {
console.log("useShareAppMessage1", res, shareOptions);
// 如果是按钮触发的转发,使用默认配置
if (res.from === "button") {
return shareOptions;
}
// 页面转发使用设置的配置
return {};
});
// 计算选中车次的总重量和总金额
const calculateTotals = () => {
return selectedOrders.reduce(
(totals, order) => {
totals.totalWeight += order.netWeight || 0;
totals.totalAmount += order.invoiceAmount || 0;
return totals;
},
{ totalWeight: 0, totalAmount: 0 },
);
};
const { totalWeight, totalAmount } = calculateTotals();
// 提交申请
const handleSubmit = async () => {
// 这里添加提交申请的逻辑
Toast.show("提交申请成功", { duration: 1500 });
setPopupVisible(false);
// 重置状态
setContractFiles([]);
setInvoiceFiles([]);
};
return (
<>
<PageList<BusinessAPI.OrderSupplierVO, BusinessAPI.OrderSupplierPageQry>
rowId={"orderSupplierId"}
itemHeight={182}
type={"infinite"}
actionRef={actionRef}
render={(orderSupplierVO: BusinessAPI.OrderSupplierVO, index) => (
<View className={"flex-1"} key={index}>
<View
className={"relative flex flex-col divide-y-2 divide-neutral-100"}
>
<View className="flex-1">
<View className="flex items-start justify-between">
<View>
<View className="text-base font-medium text-gray-800">
{orderSupplierVO.name} (
{orderSupplierVO.orderVehicle?.origin}-
{orderSupplierVO.orderVehicle?.destination})
</View>
</View>
{/*<View className="rounded bg-gray-100 px-2 py-1 text-xs text-gray-800">*/}
{/* 草稿*/}
{/*</View>*/}
</View>
<View className="my-1 flex flex-row flex-wrap gap-1 text-sm text-gray-600">
<View className="text-primary">
{orderSupplierVO.orderVehicle?.vehicleNo
? "第" + orderSupplierVO.orderVehicle?.vehicleNo + "车"
: "暂未生成车次"}
</View>
<View>
{dayjs(orderSupplierVO.orderVehicle?.deliveryTime).format(
"MM-DD",
)}
</View>
<View>|</View>
<View>{orderSupplierVO.netWeight}</View>
<View>|</View>
<View>¥{orderSupplierVO.invoiceAmount}</View>
</View>
<View className="text-xs text-gray-500">
{`${orderSupplierVO.productName} | 品种:`}
</View>
</View>
</View>
</View>
)}
toolbar={toolbar}
request={async (params) => {
const {
data: { data, success, notEmpty },
} = await business.orderSupplier.pageOrderSupplier({
orderSupplierPageQry: {
...params,
...(supplierVO
? {
supplierId: supplierVO.supplierId,
}
: {}),
},
});
return {
data,
success,
hasMore: notEmpty,
};
}}
pagination={{
pageSize: 10,
}}
/>
</>
);
});

View File

@ -9,6 +9,7 @@ import * as setting from "./setting";
import * as purchaseOrder from "./purchaseOrder";
import * as product from "./product";
import * as platform from "./platform";
import * as orderSupplier from "./orderSupplier";
import * as menu from "./menu";
import * as material from "./material";
import * as materialCategory from "./materialCategory";
@ -39,6 +40,7 @@ export default {
purchaseOrder,
product,
platform,
orderSupplier,
menu,
material,
materialCategory,

View File

@ -0,0 +1,59 @@
// @ts-ignore
/* eslint-disable */
import request from "../request";
/** 订单供应商列表 GET /operation/pageOrderSupplier */
export async function pageOrderSupplier(
// 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: BusinessAPI.pageOrderSupplierParams,
options?: { [key: string]: any },
) {
return request<BusinessAPI.PageResponseOrderSupplierVO>(
"/operation/pageOrderSupplier",
{
method: "GET",
params: {
...params,
orderSupplierPageQry: undefined,
...params["orderSupplierPageQry"],
},
...(options || {}),
},
);
}
/** 订单供应商更新 PUT /operation/updateOrderSupplier */
export async function updateOrderSupplier(
body: BusinessAPI.OrderSupplierUpdateCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseOrderSupplierVO>(
"/operation/updateOrderSupplier",
{
method: "PUT",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
},
);
}
/** 订单供应商更新 PATCH /operation/updateOrderSupplier */
export async function updateOrderSupplier1(
body: BusinessAPI.OrderSupplierUpdateCmd,
options?: { [key: string]: any },
) {
return request<BusinessAPI.SingleResponseOrderSupplierVO>(
"/operation/updateOrderSupplier",
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
data: body,
...(options || {}),
},
);
}

View File

@ -1083,8 +1083,6 @@ declare namespace BusinessAPI {
strawMatCostFlag?: boolean;
/** 发货单合计金额是否含包装费 */
includePackingFlag?: boolean;
/** 单据类型delivery-发货单, purchase-采购底单, cost-成本单 */
documentTypes?: string;
/** 应收金额 */
receivable?: number;
/** 备注 */
@ -1401,8 +1399,6 @@ declare namespace BusinessAPI {
strawMatCostFlag?: boolean;
/** 发货单合计金额是否含包装费 */
includePackingFlag?: boolean;
/** 单据类型delivery-发货单, purchase-采购底单, cost-成本单 */
documentTypes?: string;
/** 应收金额 */
receivable?: number;
/** 备注 */
@ -1440,8 +1436,6 @@ declare namespace BusinessAPI {
strawMatCostFlag?: boolean;
/** 发货单合计金额是否含包装费 */
includePackingFlag?: boolean;
/** 单据类型delivery-发货单, purchase-采购底单, cost-成本单 */
documentTypes?: string;
/** 应收金额 */
receivable?: number;
/** 备注 */
@ -2534,8 +2528,6 @@ declare namespace BusinessAPI {
enableCompanyRebate?: boolean;
/** 公司返点比例 */
companyRebateRatio?: number;
/** 单据类型delivery-发货单, purchase-采购底单, cost-成本单 */
documentTypes?: string;
/** 税费补贴 */
taxSubsidy?: number;
/** 计提税金 */
@ -2549,6 +2541,8 @@ declare namespace BusinessAPI {
type OrderPackage = {
/** 记录ID */
orderPackageId?: string;
/** 采购订单记录ID */
orderId?: string;
/** 供应商记录ID */
orderSupplierId?: string;
/** 箱子品牌ID */
@ -2575,7 +2569,7 @@ declare namespace BusinessAPI {
boxCostPrice?: number;
/** 销售单价(元/个) */
boxSalePrice?: number;
/** 箱子类型:1_本次使用2_额外运输3_已使用额外运输4_车上剩余 */
/** 箱子类型:1_本次使用2_额外运输3_已使用额外运输4_车上剩余5_瓜农纸箱6_空箱 */
boxType: "USED" | "EXTRA" | "EXTRA_USED" | "REMAIN" | "OWN" | "EMPTY";
};
@ -2653,6 +2647,106 @@ declare namespace BusinessAPI {
productName?: string;
/** 采购订单包装箱信息 */
orderPackageList?: OrderPackage[];
/** 采购订单车辆信息 */
orderVehicle?: OrderVehicle;
};
type OrderSupplierPageQry = {
pageSize?: number;
pageIndex?: number;
orderBy?: string;
orderDirection?: string;
groupBy?: string;
needTotalCount?: boolean;
/** 自定义字段key */
customFieldKey?: string;
/** 自定义字段value */
customFieldValue?: string;
/** 备注 */
remark?: string;
/** 状态1_启用0_禁用 */
status?: boolean;
/** 订单供应商ID */
orderSupplierId?: string;
/** 订单ID */
orderId?: string;
/** 供应商id */
supplierId?: string;
/** 发货日期 */
deliveryTime?: string[];
offset?: number;
};
type OrderSupplierUpdateCmd = {
/** 订单供应商ID */
orderSupplierId: string;
/** 是否上传票证 */
invoiceUpload?: boolean;
/** 发票照片 */
invoiceImg?: string[];
/** 是否上传合同 */
contractUpload?: boolean;
/** 合同照片 */
contractImg?: string[];
};
type OrderSupplierVO = {
/** 记录ID */
orderSupplierId: string;
/** 订单ID */
orderId: string;
/** 供应商ID */
supplierId?: string;
/** 供应商姓名 */
name?: string;
/** 身份证号 */
idCard?: string;
/** 银行卡号 */
bankCard?: string;
/** 手机号 */
phone?: string;
/** 微信二维码 */
wechatQr?: string;
/** 是否最后一家 */
isLast?: boolean;
/** 空磅是否包含纸箱 */
isPaper?: boolean;
/** 产品id */
productId?: string;
/** 产品名称 */
productName?: string;
/** 空车重量(kg) */
emptyWeight?: number;
/** 总重量(kg) */
totalWeight?: number;
/** 毛重(斤) */
grossWeight?: number;
/** 净重(斤) */
netWeight?: number;
/** 采购单价(元&#x2F;斤) */
purchasePrice?: number;
/** 销售单价(元&#x2F;斤) */
salePrice?: number;
/** 报价方式1_按毛重报价2_按净重报价 */
pricingMethod?: boolean;
/** 发票金额 */
invoiceAmount?: number;
/** 空车照片 */
emptyWeightImg?: string;
/** 满载照片 */
totalWeightImg?: string;
/** 是否上传票证 */
invoiceUpload?: boolean;
/** 发票照片 */
invoiceImg?: string[];
/** 是否上传合同 */
contractUpload?: boolean;
/** 合同照片 */
contractImg?: string[];
/** 创建时间 */
createdAt?: string;
/** 车辆信息 */
orderVehicle?: OrderVehicle;
};
type OrderVehicle = {
@ -2761,6 +2855,10 @@ declare namespace BusinessAPI {
materialPageQry: MaterialPageQry;
};
type pageOrderSupplierParams = {
orderSupplierPageQry: OrderSupplierPageQry;
};
type pagePermissionParams = {
permissionPageQry: PermissionPageQry;
};
@ -2985,6 +3083,19 @@ declare namespace BusinessAPI {
totalPages?: number;
};
type PageResponseOrderSupplierVO = {
success?: boolean;
errCode?: string;
errMessage?: string;
totalCount?: number;
pageSize?: number;
pageIndex?: number;
data?: OrderSupplierVO[];
empty?: boolean;
notEmpty?: boolean;
totalPages?: number;
};
type PageResponsePermissionVO = {
success?: boolean;
errCode?: string;
@ -3323,6 +3434,8 @@ declare namespace BusinessAPI {
orderSupplierList: OrderSupplier[];
/** 采购订单费用信息 */
orderCostList: OrderCost[];
/** 采购订单包装箱信息 */
orderPackageList: OrderPackage[];
/** 是否是暂存 */
draft: boolean;
pricingMethod?: "BY_GROSS_WEIGHT" | "BY_NET_WEIGHT";
@ -3362,7 +3475,7 @@ declare namespace BusinessAPI {
orderSupplierList: OrderSupplier[];
/** 采购订单费用信息 */
orderCostList: OrderCost[];
/** 采购订单空箱费用 */
/** 采购订单包装箱信息 */
orderPackageList: OrderPackage[];
};
@ -3469,7 +3582,7 @@ declare namespace BusinessAPI {
active?: number;
/** 采购订单费用信息 */
orderCostList: OrderCost[];
/** 采购订单空箱费用 */
/** 采购订单包装箱信息 */
orderPackageList: OrderPackage[];
};
@ -3495,6 +3608,8 @@ declare namespace BusinessAPI {
orderSupplierList: OrderSupplier[];
/** 采购订单费用信息 */
orderCostList: OrderCost[];
/** 采购订单包装箱信息 */
orderPackageList: OrderPackage[];
};
type PurchaseOrderVO = {
@ -3556,7 +3671,7 @@ declare namespace BusinessAPI {
orderSupplierList: OrderSupplier[];
/** 采购订单费用信息 */
orderCostList: OrderCost[];
/** 采购订单空箱费用 */
/** 采购订单包装箱信息 */
orderPackageList: OrderPackage[];
};
@ -3846,11 +3961,7 @@ declare namespace BusinessAPI {
/** 发货单明细 */
shipOrderItemList?: ShipOrderItem[];
/** 发货单据 */
shipDocument?: string;
/** 采购底单 */
purchaseDocument?: string;
/** 成本单据 */
costDocument?: string;
document?: string;
};
type ShipOrderItem = {
@ -3972,11 +4083,7 @@ declare namespace BusinessAPI {
/** 发货单明细 */
shipOrderItemList?: ShipOrderItem[];
/** 发货单据 */
shipDocument?: string;
/** 采购底单 */
purchaseDocument?: string;
/** 成本单据 */
costDocument?: string;
document?: string;
};
type ShipOrderVO = {
@ -4035,11 +4142,7 @@ declare namespace BusinessAPI {
/** 瓜农姓名逗号隔开 */
farmerInfo?: string;
/** 发货单据 */
shipDocument?: string;
/** 采购底单 */
purchaseDocument?: string;
/** 成本单据 */
costDocument?: string;
document?: string;
/** 发货单状态1_待回款2_部分回款3_已回款4_拒收完结5_已完结 */
state?:
| "WAIT_PAYMENT"
@ -4304,6 +4407,13 @@ declare namespace BusinessAPI {
data?: MenuVO;
};
type SingleResponseOrderSupplierVO = {
success?: boolean;
errCode?: string;
errMessage?: string;
data?: OrderSupplierVO;
};
type SingleResponseOssTokenVO = {
success?: boolean;
errCode?: string;

View File

@ -30,6 +30,10 @@ export class PurchaseOrderCalculator {
getTotalPackagingCost(): number {
const costItemsCost = this.purchaseOrderVO.orderCostList.reduce(
(sum, cost) => {
// 先过滤一下
if (cost.name === "纸箱费") {
return new Decimal(sum).toNumber();
}
return new Decimal(sum)
.plus(new Decimal(cost.price || 0).mul(cost.count || 0))
.toNumber();
@ -84,6 +88,8 @@ export class PurchaseOrderCalculator {
getTotalPurchaseCost(): number {
const supplierPurchaseCost = this.getSupplierPurchaseCost();
const totalPackagingCost = this.getTotalPackagingCost();
console.log("supplierPurchaseCost", supplierPurchaseCost);
console.log("totalPackagingCost", totalPackagingCost);
return new Decimal(supplierPurchaseCost)
.plus(totalPackagingCost)
.toNumber();
@ -389,9 +395,7 @@ export class PurchaseOrderCalculator {
const includePackingFlag =
this.purchaseOrderVO.orderDealer?.includePackingFlag;
if (includePackingFlag) {
return decimal
.plus(this.getTotalPackagingCost())
.toNumber();
return decimal.plus(this.getTotalPackagingCost()).toNumber();
}
return decimal.toNumber();

View File

@ -0,0 +1,617 @@
class Template {
private moduleList: any[];
constructor(moduleList: any[]) {
this.moduleList = moduleList;
}
// 将预览内容转换为HTML字符串的函数
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;}.p-2 {padding:8px}</style>
<div class="page-wrap">
<div class="page-content">
`;
this.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="table-border">
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">:</div>
<div class="col-span-2 p-2 text-left">
${config.vehicleNumber}
</div>
</div>
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
:
</div>
<div class="col-span-2 p-2 text-left">
${config.destination}
</div>
</div>
`;
if (config.showShippingFrom) {
htmlString += `
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
</div>
<div class="col-span-2 p-2 text-left">
${config.shippingFrom}
</div>
</div>
`;
}
htmlString += `<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
:
</div>
<div class="col-span-2 p-2 text-left">
${config.accountCompany}
</div>
</div>
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
:
</div>
<div class="col-span-2 p-2 text-left">
${config.date}
</div>
</div>
`;
if (config.showEstimatedArrivalTime) {
htmlString += `<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
:
</div>
<div class="col-span-2 p-2 text-left">
${config.estimatedArrivalTime}
</div>
</div>
`;
}
if (config.data) {
config.data?.forEach((item: any) => {
if (config.showGrade) {
htmlString += `
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
:
</div>
<div class="col-span-2 p-2 text-left">
${item.watermelonGrade}
</div>
</div>
`;
}
htmlString += `
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
:
</div>
<div class="col-span-2 p-2 text-left">
</div>
</div>
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
</div>
<div class="col-span-2 p-2 text-left">
${item.unitPrice}
${config.unitPriceUnit === "1" ? "元/斤" : "元/公斤"}
</div>
</div>
`;
});
}
htmlString += `
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
</div>
<div class="p-2 text-left">
${config.data?.reduce(
(acc: any, cur: any) => acc + cur.grossWeight,
0,
)}
</div>
<div class="p-2 text-left"></div>
</div>
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold"></div>
<div class="p-2 text-left">
${config.data?.reduce(
(acc: any, cur: any) => acc + cur.boxCount,
0,
)}
</div>
<div class="p-2 text-left"></div>
</div>
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">:</div>
<div class="col-span-2 p-2 text-left">
${config.licensePlate}
</div>
</div>
<div class="grid w-full grid-cols-3 gap-0 text-base">
<div class="p-2 text-left font-bold">
:
</div>
<div class="col-span-2 p-2 text-left">
${config.driverPhone}
</div>
</div>
</div>
`;
}
});
htmlString += `</div></div>`;
return htmlString;
};
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long