ERPTurbo_Client/packages/app-client/src/components/purchase/section/BasicInfoSection.tsx
shenyifei cbde9caac1 feat(purchase): 优化采购订单费用管理功能
- 移除调试日志输出
- 新增 requireQuantityAndPrice 字段支持
- 调整页面样式间距统一为 p-2.5
- 交换工头垫付与产地垫付显示逻辑
- 完善费用弹窗编辑功能,支持数量单价分别输入
- 增加新手引导 tour 功能
- 优化订单成本列表更新逻辑,避免重复项
- 导出新增的费用 section 组件
- 替换 LaborInfoSection为 WorkerAdvanceSection
- 引入 ProductionAdvanceSection 组件
2025-11-17 10:43:18 +08:00

605 lines
20 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ScrollView, Text, View } from "@tarojs/components";
import { Button, Input, Popup, Radio, SafeArea } from "@nutui/nutui-react-taro";
import dayjs from "dayjs";
import { formatCurrency } from "@/utils/format";
import { useEffect, useState } from "react";
import businessServices from "@/services/business";
export default function BasicInfoSection(props: {
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly } = props;
const { orderVehicle } = purchaseOrderVO;
// 弹窗可见状态
const [visiblePopup, setVisiblePopup] = useState({
basicInfo: false, // 基础信息弹窗
});
// 编辑值的状态(用于弹窗中的临时编辑)
const [editValues, setEditValues] = useState({
vehicleNo: orderVehicle?.vehicleNo || "",
origin: orderVehicle?.origin || "",
destination: orderVehicle?.destination || "",
deliveryTime: orderVehicle?.deliveryTime || "",
plate: orderVehicle?.plate || "",
phone: orderVehicle?.phone || "",
priceType: orderVehicle?.priceType || "MAIN_FREIGHT",
price: orderVehicle?.price || 0,
openStrawCurtain: orderVehicle?.openStrawCurtain || false,
strawCurtainPrice: orderVehicle?.strawCurtainPrice || 0,
});
// 上一车次号
const [lastVehicleNo, setLastVehicleNo] = useState<string | null>(null);
// 是否正在获取上一车次号
const [loadingLastVehicleNo, setLoadingLastVehicleNo] = useState(false);
// 获取上一车次号
const fetchLastVehicleNo = async () => {
// 如果已经有车次号,则不需要获取上一车次号
if (orderVehicle?.vehicleNo) {
return;
}
// 避免重复请求
if (loadingLastVehicleNo || lastVehicleNo) {
return;
}
setLoadingLastVehicleNo(true);
try {
const { data: res } =
await businessServices.purchaseOrder.getLastVehicleNo({
lastVehicleNoQry: {},
});
if (res.success && res.data) {
setLastVehicleNo(res.data);
// 解析车次号中的数字部分并加1
const numberPart = res.data.match(/(\d+)$/);
if (numberPart) {
const nextNumber = parseInt(numberPart[1]) + 1;
const nextVehicleNo = res.data.replace(
/(\d+)$/,
nextNumber.toString(),
);
// 更新车次号
updateVehicleNo(nextVehicleNo);
}
}
} catch (error) {
console.error("获取上一车次号失败:", error);
} finally {
setLoadingLastVehicleNo(false);
}
};
// 组件加载时获取上一车次号
useEffect(() => {
fetchLastVehicleNo();
}, []);
// 打开基础信息弹窗
const openBasicInfoPopup = () => {
if (readOnly) return;
setEditValues({
vehicleNo: orderVehicle?.vehicleNo || "",
origin: orderVehicle?.origin || "",
destination: orderVehicle?.destination || "",
deliveryTime: orderVehicle?.deliveryTime || "",
plate: orderVehicle?.plate || "",
phone: orderVehicle?.phone || "",
priceType: orderVehicle?.priceType || "MAIN_FREIGHT",
price: orderVehicle?.price || 0,
openStrawCurtain: orderVehicle?.openStrawCurtain || false,
strawCurtainPrice: orderVehicle?.strawCurtainPrice || 0,
});
setVisiblePopup((prev) => ({ ...prev, basicInfo: true }));
};
// 保存基础信息
const saveBasicInfo = () => {
if (onChange) {
const updatedOrder = {
...purchaseOrderVO,
orderVehicle: {
...orderVehicle,
...editValues,
deliveryTime: editValues.deliveryTime,
price: Number(editValues.price),
strawCurtainPrice: Number(editValues.strawCurtainPrice),
},
};
onChange(updatedOrder);
}
setVisiblePopup((prev) => ({ ...prev, basicInfo: false }));
};
// 更新车次号
const updateVehicleNo = (value: string) => {
if (onChange) {
const updatedOrder = {
...purchaseOrderVO,
orderVehicle: {
...orderVehicle,
vehicleNo: value,
},
};
onChange(updatedOrder);
}
};
// 更新运费类型
const updatePriceType = (value: BusinessAPI.OrderVehicle["priceType"]) => {
if (onChange) {
const updatedOrder = {
...purchaseOrderVO,
orderVehicle: {
...orderVehicle,
priceType: value,
},
};
onChange(updatedOrder);
}
};
// 显示的参考车次号
const displayReferenceVehicleNo = () => {
if (lastVehicleNo) {
return lastVehicleNo;
}
return "获取中...";
};
return (
<>
{/* 基础信息编辑弹窗 */}
<Popup
duration={150}
style={{
minHeight: "auto",
}}
visible={visiblePopup.basicInfo}
className={"flex flex-col"}
position="bottom"
title={"编辑基础信息"}
onClose={() =>
setVisiblePopup((prev) => ({ ...prev, basicInfo: false }))
}
onOverlayClick={() => {
setVisiblePopup((prev) => ({ ...prev, basicInfo: false }));
}}
lockScroll
>
<ScrollView scrollY className="h-96">
<View className={"flex flex-col gap-3 p-2.5"}>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入本车次号"}
type={"text"}
value={editValues.vehicleNo}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
vehicleNo: value,
}));
}}
/>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入发货地"}
type={"text"}
value={editValues.origin}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
origin: value,
}));
}}
/>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入收货地"}
type={"text"}
value={editValues.destination}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
destination: value,
}));
}}
/>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入发货日期"}
type={"text"}
value={editValues.deliveryTime}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
deliveryTime: value,
}));
}}
/>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入车牌号"}
type={"text"}
value={editValues.plate}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
plate: value,
}));
}}
/>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入联系电话"}
type={"text"}
value={editValues.phone}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
phone: value,
}));
}}
/>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View className="flex flex-row gap-4">
<View
className={`flex flex-1 flex-row items-center justify-center rounded-md py-2 ${
editValues.priceType === "MAIN_FREIGHT"
? "bg-blue-100 text-blue-600"
: "border border-gray-300"
}`}
onClick={() => {
setEditValues((prev) => ({
...prev,
priceType: "MAIN_FREIGHT",
}));
}}
>
</View>
<View
className={`flex flex-1 flex-row items-center justify-center rounded-md py-2 ${
editValues.priceType === "SHORT_TRANSPORT"
? "bg-blue-100 text-blue-600"
: "border border-gray-300"
}`}
onClick={() => {
setEditValues((prev) => ({
...prev,
priceType: "SHORT_TRANSPORT",
}));
}}
>
</View>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入运费"}
type={"digit"}
value={editValues.price.toString()}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
price: parseFloat(value) || 0,
}));
}}
/>
<View className="mr-2"></View>
</View>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View className="flex flex-row gap-4">
<View
className={`flex flex-1 flex-row items-center justify-center rounded-md py-2 ${
editValues.openStrawCurtain
? "bg-blue-100 text-blue-600"
: "border border-gray-300"
}`}
onClick={() => {
setEditValues((prev) => ({
...prev,
openStrawCurtain: true,
}));
}}
>
</View>
<View
className={`flex flex-1 flex-row items-center justify-center rounded-md py-2 ${
!editValues.openStrawCurtain
? "bg-blue-100 text-blue-600"
: "border border-gray-300"
}`}
onClick={() => {
setEditValues((prev) => ({
...prev,
openStrawCurtain: false,
}));
}}
>
</View>
</View>
{editValues.openStrawCurtain && (
<>
<View className={"text-neutral-darkest text-sm font-medium"}>
</View>
<View
className={
"border-neutral-base flex flex-row items-center rounded-md border border-solid"
}
>
<Input
className={"placeholder:text-neutral-dark"}
placeholder={"请输入草帘费金额"}
type={"digit"}
value={editValues.strawCurtainPrice.toString()}
onChange={(value) => {
setEditValues((prev) => ({
...prev,
strawCurtainPrice: parseFloat(value) || 0,
}));
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
</ScrollView>
<View className={"flex w-full flex-col bg-white"}>
<View className={"flex flex-row gap-2 p-3"}>
<View className={"flex-1"}>
<Button
size={"large"}
block
type="default"
onClick={() => {
setVisiblePopup((prev) => ({ ...prev, basicInfo: false }));
}}
>
</Button>
</View>
<View className={"flex-1"}>
<Button
size={"large"}
block
type="primary"
onClick={saveBasicInfo}
>
</Button>
</View>
</View>
<SafeArea position={"bottom"} />
</View>
</Popup>
<View className="rounded-lg border border-solid border-gray-200 bg-white p-3">
{/* 本车次号 */}
<View className="mb-4">
<View className="text-neutral-darkest mb-1 text-sm font-bold">
</View>
{readOnly ? (
<View
className="flex flex-row items-center justify-between rounded-md p-2.5"
style={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}}
>
<View className="text-neutral-darkest text-sm font-medium">
{orderVehicle?.vehicleNo
? "第" + orderVehicle?.vehicleNo + "车"
: "暂未生成车次"}
</View>
</View>
) : (
<View className="flex flex-row items-center justify-between">
<View
className={`flex h-10 flex-1 items-center rounded-md border-4 border-gray-300`}
>
<Input
placeholder="请输入车次号"
type="text"
value={orderVehicle?.vehicleNo || ""}
onChange={(value) => updateVehicleNo(value)}
/>
</View>
<View className="flex-1 text-center text-xs text-gray-500">
{displayReferenceVehicleNo()}
</View>
</View>
)}
</View>
{/* 运费信息 */}
<View className="mb-4 flex flex-col gap-2.5">
<View className="text-neutral-darkest mb-1 text-sm font-bold">
</View>
<View
className="flex flex-row items-center justify-between rounded-md p-2.5"
style={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}}
>
<Text className="pb-2 text-3xl font-bold text-red-500">
{formatCurrency(orderVehicle?.price || 0)}
</Text>
<View className="text-neutral-darkest text-sm font-medium">
{readOnly ? (
<View>
{orderVehicle?.priceType === "MAIN_FREIGHT"
? "主运费"
: orderVehicle?.priceType === "SHORT_TRANSPORT"
? "短驳费"
: "未选择"}
</View>
) : (
<Radio.Group
direction={"horizontal"}
value={orderVehicle?.priceType}
onChange={(value) =>
updatePriceType(
value as BusinessAPI.OrderVehicle["priceType"],
)
}
>
<Radio value="MAIN_FREIGHT"></Radio>
<Radio value="SHORT_TRANSPORT"></Radio>
</Radio.Group>
)}
</View>
</View>
</View>
{/* 草帘 */}
<View className="mb-4 flex flex-col gap-2.5">
<View className="text-neutral-darkest mb-1 text-sm font-bold">
</View>
<View
className="flex flex-row items-center justify-between rounded-md p-2.5"
style={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}}
>
{orderVehicle?.openStrawCurtain ? (
<Text className="pb-2 text-3xl font-bold text-red-500">
{formatCurrency(orderVehicle?.strawCurtainPrice || 0)}
</Text>
) : (
<View className="text-neutral-darkest text-sm font-medium">
</View>
)}
</View>
</View>
{/* 路线和其他信息卡片 */}
<View className="mb-4 rounded-lg bg-gray-50 p-3">
<View className="mb-2 flex flex-row justify-between">
<View className="text-neutral-dark text-xs">线</View>
<View className="text-neutral-darkest text-xs font-medium">
{orderVehicle?.origin || "发货地"} {"->"}{" "}
{orderVehicle?.destination || "收货地"}
</View>
</View>
<View className="flex flex-row justify-between">
<View className="text-neutral-dark text-xs"></View>
<View className="text-neutral-darkest text-xs font-medium">
{orderVehicle?.deliveryTime
? dayjs(orderVehicle.deliveryTime).format("YYYY-MM-DD")
: "发货日期"}{" "}
| {orderVehicle?.plate || "车牌号"} |{" "}
{orderVehicle?.phone || "联系电话"}
</View>
</View>
</View>
{!readOnly && (
<View className="flex justify-center border-t border-gray-200 p-2">
<Button
size="small"
type="primary"
fill="outline"
onClick={openBasicInfoPopup}
>
</Button>
</View>
)}
</View>
</>
);
}