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

View File

@ -11,17 +11,17 @@ export default function DealerInfo(props: { module: any }) {
<View className="col-span-1"></View> <View className="col-span-1"></View>
{config.showDealerName || config.showWatermelonGrade ? ( {config.showDealerName || config.showWatermelonGrade ? (
<View className="col-span-3 flex items-end justify-center border-b border-black"> <View className="col-span-3 flex items-end justify-center border-b border-black">
{config.showWatermelonGrade {config.showDealerName ? config.dealerName : ""}
? `${config.dealerName}-${config.watermelonGrade}` {config.showDealerName && config.showWatermelonGrade ? "- " : ""}
: config.dealerName} {config.showWatermelonGrade ? config.watermelonGrade : ""}
</View> </View>
) : ( ) : (
<View className="col-span-3"></View> <View className="col-span-3"></View>
)} )}
{config.showDestination || config.showVehicleNumber ? ( {config.showDestination || config.showVehicleNumber ? (
<View className="col-span-3 flex items-end justify-center border-b border-black"> <View className="col-span-3 flex items-end justify-center border-b border-black">
{config.destination} {config.showDestination ? config.destination : ""}
{config.vehicleNumber} {config.showVehicleNumber ? config.vehicleNumber : ""}
</View> </View>
) : ( ) : (
<View className="col-span-3"></View> <View className="col-span-3"></View>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
import { formatCurrency } from "@/utils"; import { useMemo, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { Table } from "@nutui/nutui-react-taro"; import { Table } from "@nutui/nutui-react-taro";
import { Icon } from "@/components"; import { Icon } from "@/components";
import { View } from "@tarojs/components"; import { Input, View } from "@tarojs/components";
import { Decimal } from "decimal.js"; import { Decimal } from "decimal.js";
export default function PackageInfoSection(props: { export default function PackageInfoSection(props: {
@ -10,9 +9,85 @@ export default function PackageInfoSection(props: {
onChange?: (orderVO: BusinessAPI.OrderVO) => void; onChange?: (orderVO: BusinessAPI.OrderVO) => void;
readOnly?: boolean; 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: "纸箱型号", title: "纸箱型号",
key: "boxProductName", key: "boxProductName",
@ -35,20 +110,56 @@ export default function PackageInfoSection(props: {
) => { ) => {
// 合计行不显示编辑按钮 // 合计行不显示编辑按钮
if (value.isTotalRow) { 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 ( return (
<View <View
className="flex w-full items-center justify-between" className="flex items-center justify-between"
onClick={(e) => { onClick={(e) => {
if (!readOnly) { if (!readOnly) {
e.stopPropagation(); e.stopPropagation();
startEdit(key, value.boxSalePrice as number);
} }
}} }}
> >
<View className={!readOnly ? "cursor-pointer underline" : ""}> <View
{formatCurrency(value.boxSalePrice as number)} className={!readOnly ? "cursor-pointer underline" : ""}
>
{value.boxSalePrice as number}
</View> </View>
{!readOnly && ( {!readOnly && (
<View className="-m-2 ml-2 flex items-center justify-center p-2"> <View className="-m-2 ml-2 flex items-center justify-center p-2">
@ -77,20 +188,14 @@ export default function PackageInfoSection(props: {
orderPackageId?: string; orderPackageId?: string;
isTotalRow?: boolean; isTotalRow?: boolean;
}, },
) => formatCurrency(value.boxProductWeight), ) => value.boxProductWeight,
}, },
{ {
title: "品牌", title: "品牌",
key: "boxBrandName", key: "boxBrandName",
}, },
]; ];
const [columns, setColumns] = useState<any[]>(defaultColumns); }, [readOnly, editingStates]);
useEffect(() => {
if (!readOnly) {
setColumns([...defaultColumns]);
}
}, [readOnly]);
// 将所有包装信息合并并按品牌、型号、规格分组 // 将所有包装信息合并并按品牌、型号、规格分组
const groupedPackageData = orderVO.orderSupplierList?.reduce( const groupedPackageData = orderVO.orderSupplierList?.reduce(
@ -99,7 +204,7 @@ export default function PackageInfoSection(props: {
?.filter((pkg) => pkg.boxType === "USED") ?.filter((pkg) => pkg.boxType === "USED")
?.forEach((pkg) => { ?.forEach((pkg) => {
// 生成分组键 // 生成分组键
const key = `${pkg.boxBrandName}-${pkg.boxProductName}-${pkg.boxSpecId}`; const key = `${pkg.boxBrandId}-${pkg.boxProductId}-${pkg.boxSpecId}`;
// 转换规格字段 // 转换规格字段
const boxSpecId = pkg.boxSpecId; const boxSpecId = pkg.boxSpecId;
@ -208,11 +313,11 @@ export default function PackageInfoSection(props: {
if (rowData.isTotalRow) { if (rowData.isTotalRow) {
return ( return (
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePrice as number)} {rowData.boxSalePrice as number}
</span> </span>
); );
} }
return column.render(rowData, rowData); return column?.render?.(rowData);
}, },
}; };
} else if (column.key === "boxSalePayment") { } else if (column.key === "boxSalePayment") {
@ -223,7 +328,7 @@ export default function PackageInfoSection(props: {
if (rowData.isTotalRow) { if (rowData.isTotalRow) {
return ( return (
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxSalePayment)} {rowData.boxSalePayment}
</span> </span>
); );
} }
@ -241,11 +346,11 @@ export default function PackageInfoSection(props: {
if (rowData.isTotalRow) { if (rowData.isTotalRow) {
return ( return (
<span style={{ fontWeight: "bold" }}> <span style={{ fontWeight: "bold" }}>
{formatCurrency(rowData.boxProductWeight)} {rowData.boxProductWeight}
</span> </span>
); );
} }
return column.render(rowData, rowData); return column?.render?.(rowData);
}, },
}; };
} }
@ -257,7 +362,7 @@ export default function PackageInfoSection(props: {
return ( return (
<Table <Table
className={"table-sum"} className={"table-sum"}
columns={columnsWithTotalsRender} columns={columnsWithTotalsRender as any}
data={dataWithTotals} data={dataWithTotals}
striped striped
/> />

View File

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

View File

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