refactor(order): 优化订单处理逻辑和界面显示

- 修复经销商信息显示逻辑,正确处理名称和瓜品等级的显示
- 移除PDF自动保存功能,仅保留下载成功提示
- 更新按钮样式为外框样式,改善视觉效果
- 优化箱重计算精度,添加小数位处理
- 移除货币格式化,直接显示数值
- 添加供应商银行名称字段并优化验证逻辑
- 改进供应商信息表单的空值处理
- 优化车次号获取逻辑,支持强制重新获取
- 实现包装信息表格的可编辑功能,支持销售单价修改
- 修复表格渲染逻辑,正确处理合计行显示
- 更新应用版本号至v0.0.61
- 优化PDF模板中经销商和目的地信息的显示逻辑
- 修复订单转换器中的价格计算逻辑
- 调整发货单转换器中的包装分组逻辑
This commit is contained in:
shenyifei 2025-12-29 15:19:18 +08:00
parent 438e17f093
commit 4b9d0002d7
11 changed files with 256 additions and 144 deletions

View File

@ -65,25 +65,11 @@ export default function Step3Success(props: Step3SuccessProps) {
if (downloadRes.tempFilePath) {
setTempFilePath(downloadRes.tempFilePath);
// 保存PDF到手机
if (Taro.saveFile) {
await Taro.saveFile({
tempFilePath: downloadRes.tempFilePath,
});
Taro.showToast({
title: "PDF下载成功",
icon: "success",
duration: 2000,
});
} else {
// 如果不支持saveFile直接提示下载完成
Taro.openDocument({
filePath: downloadRes.tempFilePath,
showMenu: true,
});
}
}
} catch (error) {
console.error("下载PDF失败:", error);
@ -173,8 +159,9 @@ export default function Step3Success(props: Step3SuccessProps) {
{tempFilePath && (
<View className="flex-1">
<Button
icon={<Icon name="eye" size={16} color={"white"} />}
type="default"
icon={<Icon name="eye" size={16} />}
type="primary"
fill={"outline"}
size={"large"}
block
onClick={handleViewPDF}

View File

@ -11,17 +11,17 @@ export default function DealerInfo(props: { module: any }) {
<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}
{config.showDealerName ? config.dealerName : ""}
{config.showDealerName && config.showWatermelonGrade ? "- " : ""}
{config.showWatermelonGrade ? config.watermelonGrade : ""}
</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}
{config.showDestination ? config.destination : ""}
{config.showVehicleNumber ? config.vehicleNumber : ""}
</View>
) : (
<View className="col-span-3"></View>

View File

@ -544,6 +544,7 @@ export default function MadePreview(props: IPurchasePreviewProps) {
sum +
new Decimal(item.boxProductWeight || 0)
.mul(new Decimal(item.boxCount || 0))
.toDecimalPlaces(0)
.toNumber(),
0,
);
@ -594,7 +595,7 @@ export default function MadePreview(props: IPurchasePreviewProps) {
<View className="text-gray-600">
<View className="text-xs"></View>
<View className="font-semibold text-gray-900">
{formatCurrency(brandBoxWeight)}
{brandBoxWeight}
</View>
</View>
</View>
@ -647,6 +648,7 @@ export default function MadePreview(props: IPurchasePreviewProps) {
sum +
new Decimal(item.boxProductWeight || 0)
.mul(new Decimal(item.boxCount || 0))
.toDecimalPlaces(0)
.toNumber(),
0,
);
@ -697,7 +699,7 @@ export default function MadePreview(props: IPurchasePreviewProps) {
<View className="text-gray-600">
<View className="text-xs"></View>
<View className="font-semibold text-gray-900">
{formatCurrency(brandBoxWeight)}
{brandBoxWeight}
</View>
</View>
</View>

View File

@ -471,6 +471,7 @@ export default function MarketPreview(props: IMarketPreviewProps) {
sum +
new Decimal(item.boxProductWeight || 0)
.mul(new Decimal(item.boxCount || 0))
.toDecimalPlaces(0)
.toNumber(),
0,
);
@ -521,7 +522,7 @@ export default function MarketPreview(props: IMarketPreviewProps) {
<View className="text-gray-600">
<View className="text-xs"></View>
<View className="font-semibold text-gray-900">
{formatCurrency(brandBoxWeight)}
{brandBoxWeight}
</View>
</View>
</View>
@ -574,6 +575,7 @@ export default function MarketPreview(props: IMarketPreviewProps) {
sum +
new Decimal(item.boxProductWeight || 0)
.mul(new Decimal(item.boxCount || 0))
.toDecimalPlaces(0)
.toNumber(),
0,
);
@ -624,7 +626,7 @@ export default function MarketPreview(props: IMarketPreviewProps) {
<View className="text-gray-600">
<View className="text-xs"></View>
<View className="font-semibold text-gray-900">
{formatCurrency(brandBoxWeight)}
{brandBoxWeight}
</View>
</View>
</View>

View File

@ -29,6 +29,7 @@ export default forwardRef<SupplierInfoRef, ISupplierInfoProps>(
name: "瓜农1",
payeeName: "",
idCard: "",
bankName: "",
bankCard: "",
phone: "",
selected: true,
@ -201,7 +202,7 @@ export default forwardRef<SupplierInfoRef, ISupplierInfoProps>(
return false;
}
// 银行名称至少2个字符
return bankName.length >= 2;
return bankName?.length >= 2;
};
// 校验手机号函数 (使用项目中已有的规则)
@ -895,7 +896,7 @@ export default forwardRef<SupplierInfoRef, ISupplierInfoProps>(
clearable
type="idcard"
placeholder="请输入身份证号"
value={supplierVO.idCard}
value={supplierVO.idCard || ""}
onChange={(value) => handleIdCardChange(value, supplierVO)}
onBlur={() =>
handleIdCardBlur(
@ -920,7 +921,7 @@ export default forwardRef<SupplierInfoRef, ISupplierInfoProps>(
clearable
type="text"
placeholder="请输入银行名称"
value={supplierVO.bankName}
value={supplierVO.bankName || ""}
onChange={(value) => handleBankNameChange(value, supplierVO)}
onBlur={() =>
handleBankNameBlur(
@ -933,7 +934,7 @@ export default forwardRef<SupplierInfoRef, ISupplierInfoProps>(
</View>
{bankNameError[supplierVO.orderSupplierId] && (
<View className="text-xs text-red-500">
{`银行名称"${supplierVO.bankName}"至少2个字符`}
{`银行名称"${supplierVO.bankName || ""}"至少2个字符`}
</View>
)}
<View className="block text-sm font-normal text-[#000000]">
@ -952,7 +953,7 @@ export default forwardRef<SupplierInfoRef, ISupplierInfoProps>(
clearable
type="digit"
placeholder="请输入银行卡号"
value={supplierVO.bankCard}
value={supplierVO.bankCard || ""}
onChange={(value) => handleBankCardChange(value, supplierVO)}
onBlur={() =>
handleBankCardBlur(

View File

@ -89,7 +89,10 @@ export default function BasicInfoSection(props: {
// 获取上一车次号
const fetchLastVehicleNo = async (
dealerId: BusinessAPI.DealerVO["dealerId"],
// 强制重新获取
force: boolean = false,
) => {
if (!force) {
// 如果已经有车次号,则不需要获取上一车次号
if (orderVehicle?.vehicleNo || !dealerId) {
return;
@ -99,6 +102,11 @@ export default function BasicInfoSection(props: {
if (loadingLastVehicleNo || lastVehicleNo) {
return;
}
} else {
if (loadingLastVehicleNo || !dealerId) {
return;
}
}
setLoadingLastVehicleNo(true);
try {
@ -133,7 +141,7 @@ export default function BasicInfoSection(props: {
// 组件加载时获取上一车次号
useEffect(() => {
fetchLastVehicleNo(orderVO.orderDealer.dealerId);
fetchLastVehicleNo(orderVO.orderDealer.dealerId, true);
}, [orderVO.orderDealer.dealerId]);
// 打开基础信息弹窗

View File

@ -1,8 +1,7 @@
import { formatCurrency } from "@/utils";
import { useEffect, useState } from "react";
import { useMemo, useRef, useState } from "react";
import { Table } from "@nutui/nutui-react-taro";
import { Icon } from "@/components";
import { View } from "@tarojs/components";
import { Input, View } from "@tarojs/components";
import { Decimal } from "decimal.js";
export default function PackageInfoSection(props: {
@ -10,9 +9,85 @@ export default function PackageInfoSection(props: {
onChange?: (orderVO: BusinessAPI.OrderVO) => void;
readOnly?: boolean;
}) {
const { orderVO, readOnly } = props;
const { orderVO, readOnly, onChange } = props;
const defaultColumns = [
const inputRefs = useRef<Record<string, any>>({});
// 管理编辑状态: key 为分组键,值为输入值
const [editingStates, setEditingStates] = useState<
Record<string, { isEditing: boolean; inputValue: string }>
>({});
// 开始编辑
const startEdit = (key: string, currentValue: number) => {
const inputValue = currentValue === 0 ? "" : currentValue.toString();
setEditingStates((prev) => ({
...prev,
[key]: { isEditing: true, inputValue },
}));
// 聚焦输入框
setTimeout(() => {
if (inputRefs.current[key]) {
inputRefs.current[key].focus();
}
}, 0);
};
// 处理输入变化
const handleInputChange = (key: string, e: any) => {
const val = e.detail?.value || e.target?.value || "";
// 只允许数字和小数点
if (/^\d*\.?\d*$/.test(val) || val === "") {
setEditingStates((prev) => ({
...prev,
[key]: { ...prev[key], inputValue: val },
}));
}
};
// 保存编辑
const saveEdit = (key: string) => {
const state = editingStates[key];
if (!state) return;
let newValue = 0;
if (state.inputValue !== "" && !Number.isNaN(Number(state.inputValue))) {
newValue = Number(state.inputValue);
}
// 更新 orderVO 中的数据
const updatedOrderVO = { ...orderVO };
updatedOrderVO.orderSupplierList?.forEach((supplier) => {
supplier.orderPackageList?.forEach((pkg) => {
const pkgKey = `${pkg.boxBrandId}-${pkg.boxProductId}-${pkg.boxSpecId}`;
if (pkgKey === key) {
pkg.boxSalePrice = newValue;
}
});
});
// 触发 onChange 回调
onChange?.(updatedOrderVO);
// 退出编辑状态
setEditingStates((prev) => {
const newState = { ...prev };
delete newState[key];
return newState;
});
};
// 取消编辑
const cancelEdit = (key: string) => {
setEditingStates((prev) => {
const newState = { ...prev };
delete newState[key];
return newState;
});
};
const columns = useMemo(() => {
return [
{
title: "纸箱型号",
key: "boxProductName",
@ -35,20 +110,56 @@ export default function PackageInfoSection(props: {
) => {
// 合计行不显示编辑按钮
if (value.isTotalRow) {
return formatCurrency(value.boxSalePrice as number);
return value.boxSalePrice as number;
}
// 生成分组键
const key = `${value.boxBrandId}-${value.boxProductId}-${value.boxSpecId}`;
const editingState = editingStates[key];
const isEditing = editingState?.isEditing || false;
if (isEditing) {
return (
<View className="flex items-center">
<Input
ref={(el: any) => {
inputRefs.current[key] = el;
}}
type="digit"
value={editingState.inputValue}
onInput={(e) => handleInputChange(key, e)}
onBlur={() => saveEdit(key)}
className="flex-1 border-b border-blue-500 px-1"
style={{ minWidth: "80px" }}
onClick={(e) => e.stopPropagation()}
/>
<View
className="ml-1 cursor-pointer"
onClick={(e) => {
e.stopPropagation();
cancelEdit(key);
}}
>
<Icon name="circle-xmark" size={16} color={"#ef4444"} />
</View>
</View>
);
}
return (
<View
className="flex w-full items-center justify-between"
className="flex items-center justify-between"
onClick={(e) => {
if (!readOnly) {
e.stopPropagation();
startEdit(key, value.boxSalePrice as number);
}
}}
>
<View className={!readOnly ? "cursor-pointer underline" : ""}>
{formatCurrency(value.boxSalePrice as number)}
<View
className={!readOnly ? "cursor-pointer underline" : ""}
>
{value.boxSalePrice as number}
</View>
{!readOnly && (
<View className="-m-2 ml-2 flex items-center justify-center p-2">
@ -77,20 +188,14 @@ export default function PackageInfoSection(props: {
orderPackageId?: string;
isTotalRow?: boolean;
},
) => formatCurrency(value.boxProductWeight),
) => value.boxProductWeight,
},
{
title: "品牌",
key: "boxBrandName",
},
];
const [columns, setColumns] = useState<any[]>(defaultColumns);
useEffect(() => {
if (!readOnly) {
setColumns([...defaultColumns]);
}
}, [readOnly]);
}, [readOnly, editingStates]);
// 将所有包装信息合并并按品牌、型号、规格分组
const groupedPackageData = orderVO.orderSupplierList?.reduce(
@ -99,7 +204,7 @@ export default function PackageInfoSection(props: {
?.filter((pkg) => pkg.boxType === "USED")
?.forEach((pkg) => {
// 生成分组键
const key = `${pkg.boxBrandName}-${pkg.boxProductName}-${pkg.boxSpecId}`;
const key = `${pkg.boxBrandId}-${pkg.boxProductId}-${pkg.boxSpecId}`;
// 转换规格字段
const boxSpecId = pkg.boxSpecId;
@ -208,11 +313,11 @@ export default function PackageInfoSection(props: {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePrice as number)}
{rowData.boxSalePrice as number}
</span>
);
}
return column.render(rowData, rowData);
return column?.render?.(rowData);
},
};
} else if (column.key === "boxSalePayment") {
@ -223,7 +328,7 @@ export default function PackageInfoSection(props: {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePayment)}
{rowData.boxSalePayment}
</span>
);
}
@ -241,11 +346,11 @@ export default function PackageInfoSection(props: {
if (rowData.isTotalRow) {
return (
<span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxProductWeight)}
{rowData.boxProductWeight}
</span>
);
}
return column.render(rowData, rowData);
return column?.render?.(rowData);
},
};
}
@ -257,7 +362,7 @@ export default function PackageInfoSection(props: {
return (
<Table
className={"table-sum"}
columns={columnsWithTotalsRender}
columns={columnsWithTotalsRender as any}
data={dataWithTotals}
striped
/>

View File

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

View File

@ -35,11 +35,9 @@ export class PdfTemplate {
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 || ""
}
${config.showDealerName ? config.dealerName : ""}
${config.showDealerName && config.showWatermelonGrade ? "- " : ""}
${config.showWatermelonGrade ? config.watermelonGrade : ""}
</div>
`;
} else {
@ -49,7 +47,8 @@ export class PdfTemplate {
if (config.showDestination || config.showVehicleNumber) {
htmlString += `
<div class="col-span-3 flex items-end justify-center border-b border-black">
${config.destination || ""} ${config.vehicleNumber || ""}
${config.showDestination ? config.destination : ""}
${config.showVehicleNumber ? config.vehicleNumber : ""}
</div>
`;
} else {

View File

@ -33,7 +33,7 @@ export const convertOrderToOrderShip = (
// 转换供应商列表为发货单项列表,根据 purchasePrice 分组
const suppliersByPrice = groupBy(
orderVO.orderSupplierList || [],
(supplier) => String(supplier.purchasePrice),
(supplier) => String(supplier.salePrice || "0"),
);
const orderShipId = oldOrderShip?.orderShipId || generateShortId();
@ -57,10 +57,15 @@ export const convertOrderToOrderShip = (
);
const totalAmount = DecimalUtils.toDecimalPlaces(
suppliers.reduce(
(sum, supplier) => DecimalUtils.add(sum, supplier.invoiceAmount || 0),
0,
),
suppliers.reduce((sum, supplier) => {
const amount = DecimalUtils.multiply(
orderVO.pricingMethod === "BY_GROSS_WEIGHT"
? supplier.grossWeight
: supplier.netWeight,
supplier.salePrice || 0,
);
return DecimalUtils.add(sum, amount || 0);
}, 0),
);
const totalBoxCount = DecimalUtils.toDecimalPlaces(
@ -89,7 +94,7 @@ export const convertOrderToOrderShip = (
grossWeight: totalGrossWeight,
boxWeight: totalGrossWeight - totalNetWeight,
netWeight: totalNetWeight,
unitPrice: parseFloat(price),
unitPrice: price ? parseFloat(price) : 0,
totalAmount: totalAmount,
watermelonGrade: oldOrderShipItem?.watermelonGrade || "", // 需要手动填写
};

View File

@ -42,7 +42,10 @@ export const convertOrderShipVOToExamplesFormat = (
}
});
const packagesBySpec = groupBy(allPackages, (pkg) => pkg.boxSpecId);
const packagesBySpec = groupBy(
allPackages,
(pkg) => pkg.boxSpecName + pkg.boxProductName,
);
const orderShipPackageList = Object.entries(packagesBySpec).map(
([specId, packages]) => {