feat(purchase): 优化采购订单费用管理功能

- 移除调试日志输出
- 新增 requireQuantityAndPrice 字段支持
- 调整页面样式间距统一为 p-2.5
- 交换工头垫付与产地垫付显示逻辑
- 完善费用弹窗编辑功能,支持数量单价分别输入
- 增加新手引导 tour 功能
- 优化订单成本列表更新逻辑,避免重复项
- 导出新增的费用 section 组件
- 替换 LaborInfoSection为 WorkerAdvanceSection
- 引入 ProductionAdvanceSection 组件
This commit is contained in:
shenyifei 2025-11-17 10:43:18 +08:00
parent 3f8c6d962a
commit cbde9caac1
11 changed files with 2459 additions and 685 deletions

View File

@ -34,3 +34,5 @@ export { default as TaxSubsidySection } from "./section/TaxSubsidySection";
export { default as TaxProvisionSection } from "./section/TaxProvisionSection"; export { default as TaxProvisionSection } from "./section/TaxProvisionSection";
export { default as CostDifferenceSection } from "./section/CostDifferenceSection"; export { default as CostDifferenceSection } from "./section/CostDifferenceSection";
export { default as MaterialCostSection } from "./section/MaterialCostSection"; export { default as MaterialCostSection } from "./section/MaterialCostSection";
export { default as ProductionAdvanceSection } from "./section/ProductionAdvanceSection";
export { default as WorkerAdvanceSection } from "./section/WorkerAdvanceSection";

View File

@ -53,8 +53,6 @@ export default forwardRef<OrderCostRef, IOrderCostProps>(
(costItem) => costItem.itemId === item.itemId, (costItem) => costItem.itemId === item.itemId,
); );
console.log("existingItem", existingItem, value)
return { return {
orderCostId: existingItem orderCostId: existingItem
? existingItem.orderCostId ? existingItem.orderCostId
@ -68,6 +66,9 @@ export default forwardRef<OrderCostRef, IOrderCostProps>(
payerType: existingItem ? existingItem.payerType : undefined, payerType: existingItem ? existingItem.payerType : undefined,
principal: existingItem ? existingItem.principal : "", principal: existingItem ? existingItem.principal : "",
costType: item.costType, costType: item.costType,
requireQuantityAndPrice: existingItem
? existingItem.requireQuantityAndPrice
: false,
}; };
}) || []; }) || [];
@ -91,6 +92,7 @@ export default forwardRef<OrderCostRef, IOrderCostProps>(
payerType: item.payerType, payerType: item.payerType,
principal: item.principal, principal: item.principal,
costType: item.costType, costType: item.costType,
requireQuantityAndPrice: item.requireQuantityAndPrice,
}; };
}) || []), }) || []),
]); ]);
@ -421,7 +423,7 @@ export default forwardRef<OrderCostRef, IOrderCostProps>(
}; };
return ( return (
<View className="flex flex-1 flex-col bg-[#D1D5DB] p-2.5 pt-2.5 gap-2.5"> <View className="flex flex-1 flex-col gap-2.5 bg-[#D1D5DB] p-2.5 pt-2.5">
<View className={"flex flex-1 flex-col gap-2.5"}> <View className={"flex flex-1 flex-col gap-2.5"}>
<View className="text-sm font-bold"></View> <View className="text-sm font-bold"></View>
<View className="flex items-center rounded-lg border border-blue-200 bg-blue-50 p-2.5"> <View className="flex items-center rounded-lg border border-blue-200 bg-blue-50 p-2.5">

View File

@ -537,7 +537,10 @@ export default forwardRef<OrderVehicleRef, IOrderVehicleProps>(
className="min-h-20 w-full !border !border-none p-2.5 !text-lg" className="min-h-20 w-full !border !border-none p-2.5 !text-lg"
placeholder="「粘贴识别」获输入文本,智能拆分车牌号、司机姓名、联系电话、出发地、目的地和运费" placeholder="「粘贴识别」获输入文本,智能拆分车牌号、司机姓名、联系电话、出发地、目的地和运费"
/> />
<View className="absolute right-0 bottom-0 z-9 flex gap-2"> <View
className="absolute right-0 bottom-0 z-9 flex gap-2"
id={"target1"}
>
{text ? ( {text ? (
<Button <Button
size={"large"} size={"large"}
@ -667,6 +670,7 @@ export default forwardRef<OrderVehicleRef, IOrderVehicleProps>(
<View className={"flex flex-row gap-2.5"}> <View className={"flex flex-row gap-2.5"}>
<View> <View>
<View <View
id={"target2"}
className={`relative flex h-10 w-full items-center rounded-md ${dealerNameError ? "border-4 border-red-500" : "border-4 border-gray-300"}`} className={`relative flex h-10 w-full items-center rounded-md ${dealerNameError ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
> >
<DealerPicker <DealerPicker

View File

@ -471,7 +471,7 @@ export default function BasicInfoSection(props: {
</View> </View>
{readOnly ? ( {readOnly ? (
<View <View
className="flex flex-row items-center justify-between rounded-md p-4" className="flex flex-row items-center justify-between rounded-md p-2.5"
style={{ style={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)", background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}} }}
@ -507,7 +507,7 @@ export default function BasicInfoSection(props: {
</View> </View>
<View <View
className="flex flex-row items-center justify-between rounded-md p-4" className="flex flex-row items-center justify-between rounded-md p-2.5"
style={{ style={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)", background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}} }}
@ -549,7 +549,7 @@ export default function BasicInfoSection(props: {
</View> </View>
<View <View
className="flex flex-row items-center justify-between rounded-md p-4" className="flex flex-row items-center justify-between rounded-md p-2.5"
style={{ style={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)", background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}} }}

View File

@ -211,10 +211,11 @@ export default function LaborInfoSection(props: {
})} })}
</View> </View>
</View> </View>
<View className="grid grid-cols-1 gap-2.5">
<View className={"text-sm font-bold"}></View> <View className="flex flex-col gap-2.5">
{/* 产地垫付 */} <View className={"text-sm font-bold"}></View>
{productionAdvanceCosts.map((item, index) => { {/* 工头垫付 */}
{workerAdvanceCosts.map((item, index) => {
// 初始化编辑值,包括费用承担方和工头姓名 // 初始化编辑值,包括费用承担方和工头姓名
initEditValues( initEditValues(
item.orderCostId, item.orderCostId,
@ -261,10 +262,11 @@ export default function LaborInfoSection(props: {
); );
})} })}
</View> </View>
<View className="grid grid-cols-1 gap-2.5">
<View className={"text-sm font-bold"}></View> <View className="flex flex-col gap-2.5">
{/* 工头垫付 */} <View className={"text-sm font-bold"}></View>
{workerAdvanceCosts.map((item, index) => { {/* 产地垫付 */}
{productionAdvanceCosts.map((item, index) => {
// 初始化编辑值,包括费用承担方和工头姓名 // 初始化编辑值,包括费用承担方和工头姓名
initEditValues( initEditValues(
item.orderCostId, item.orderCostId,
@ -390,7 +392,7 @@ export default function LaborInfoSection(props: {
costItems costItems
.filter((item) => item.costType === newCostData.costType) .filter((item) => item.costType === newCostData.costType)
.filter((item) => { .filter((item) => {
if (newCostData.costType === 'PRODUCTION_ADVANCE') { if (newCostData.costType === "PRODUCTION_ADVANCE") {
// 检查该项目是否已经被选择 // 检查该项目是否已经被选择
return !productionAdvanceCosts.some( return !productionAdvanceCosts.some(
(hc) => hc.itemId === item.itemId, (hc) => hc.itemId === item.itemId,
@ -604,6 +606,8 @@ export default function LaborInfoSection(props: {
payerType: newCostData.payerType || "US", payerType: newCostData.payerType || "US",
costType: newCostData.costType!, costType: newCostData.costType!,
principal: "", principal: "",
requireQuantityAndPrice:
newCostData.requireQuantityAndPrice,
}; };
// 更新purchaseOrderVO将新费用添加到orderCostList中 // 更新purchaseOrderVO将新费用添加到orderCostList中
@ -648,395 +652,423 @@ export default function LaborInfoSection(props: {
{productionAdvanceCosts.map((item) => { {productionAdvanceCosts.map((item) => {
// 获取费用项目的 requireQuantityAndPrice 属性 // 获取费用项目的 requireQuantityAndPrice 属性
const costItem = costItems.find((cost) => cost.itemId === item.itemId); const costItem = costItems.find((cost) => cost.itemId === item.itemId);
const requireQuantityAndPrice = costItem?.requireQuantityAndPrice ?? false; const requireQuantityAndPrice =
costItem?.requireQuantityAndPrice ?? false;
return ( return (
<Popup <Popup
key={item.orderCostId} key={item.orderCostId}
visible={visiblePopup[item.orderCostId]} visible={visiblePopup[item.orderCostId]}
position="bottom" position="bottom"
title={`编辑${item.name}`} title={`编辑${item.name}`}
onClose={() => onClose={() =>
setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) setVisiblePopup((prev) => ({
} ...prev,
onOverlayClick={() => [item.orderCostId]: false,
setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) }))
} }
lockScroll onOverlayClick={() =>
> setVisiblePopup((prev) => ({
<View className="flex flex-col gap-3 p-2.5"> ...prev,
{requireQuantityAndPrice ? ( [item.orderCostId]: false,
<> }))
<View className="text-neutral-darkest text-sm font-medium"> }
lockScroll
</View> >
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid"> <View className="flex flex-col gap-3 p-2.5">
<Input {requireQuantityAndPrice ? (
className="placeholder:text-neutral-dark" <>
placeholder="请输入人数" <View className="text-neutral-darkest text-sm font-medium">
type="digit"
value={ </View>
tempEditValues[item.orderCostId]?.count?.toString() || "" <View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
} <Input
onChange={(value) => { className="placeholder:text-neutral-dark"
const numValue = validatePrice(value); placeholder="请输入人数"
if (numValue !== undefined) { type="digit"
setTempEditValues((prev) => ({ value={
...prev, tempEditValues[item.orderCostId]?.count?.toString() ||
[item.orderCostId]: { ""
...prev[item.orderCostId],
count: numValue as number,
},
}));
} }
}} onChange={(value) => {
/> const numValue = validatePrice(value);
<View className="mr-2"></View> if (numValue !== undefined) {
</View> setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
count: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
<View className="text-neutral-darkest text-sm font-medium"> <View className="text-neutral-darkest text-sm font-medium">
</View> </View>
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid"> <View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
<Input <Input
className="placeholder:text-neutral-dark" className="placeholder:text-neutral-dark"
placeholder="请输入单价" placeholder="请输入单价"
type="digit" type="digit"
value={ value={
tempEditValues[item.orderCostId]?.price?.toString() || "" tempEditValues[item.orderCostId]?.price?.toString() ||
} ""
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
},
}));
} }
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></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={(
tempEditValues[item.orderCostId]?.price *
tempEditValues[item.orderCostId]?.count ||
item.price * item.count
).toString()}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
count: 1,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
<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,
[item.orderCostId]: false,
}));
}} }}
/> >
<View className="mr-2"></View>
</Button>
</View> </View>
</> <View className="flex-1">
) : ( <Button
<> size="large"
<View className="text-neutral-darkest text-sm font-medium"> block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...tempEditValues[item.orderCostId],
// 如果费用承担方是瓜农,清空工头姓名
principal:
tempEditValues[item.orderCostId]?.payerType ===
"OTHER"
? ""
: tempEditValues[item.orderCostId]?.principal,
},
}));
// 这里应该调用更新接口或更新状态
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View> </View>
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid"> <View className="flex-1">
<Input <Button
className="placeholder:text-neutral-dark" size="large"
placeholder="请输入金额" block
type="digit" type="danger"
value={ onClick={() => {
(tempEditValues[item.orderCostId]?.price * tempEditValues[item.orderCostId]?.count || item.price * item.count).toString() // 删除费用项
} setEditValues((prev) => {
onChange={(value) => { const newEditValues = { ...prev };
const numValue = validatePrice(value); delete newEditValues[item.orderCostId];
if (numValue !== undefined) { return newEditValues;
setTempEditValues((prev) => ({ });
...prev,
[item.orderCostId]: { // 更新purchaseOrderVO移除被删除的费用项
...prev[item.orderCostId], if (onChange) {
price: numValue as number, const newOrderCostList = (
count: 1, purchaseOrderVO.orderCostList || []
}, ).filter(
})); (cost) => cost.orderCostId !== item.orderCostId,
);
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: newOrderCostList,
};
onChange(newPurchaseOrderVO);
} }
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}} }}
/> >
<View className="mr-2"></View>
</Button>
</View> </View>
</>
)}
</View>
<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,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...tempEditValues[item.orderCostId],
// 如果费用承担方是瓜农,清空工头姓名
principal:
tempEditValues[item.orderCostId]?.payerType ===
"OTHER"
? ""
: tempEditValues[item.orderCostId]?.principal,
},
}));
// 这里应该调用更新接口或更新状态
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="danger"
onClick={() => {
// 删除费用项
setEditValues((prev) => {
const newEditValues = { ...prev };
delete newEditValues[item.orderCostId];
return newEditValues;
});
// 更新purchaseOrderVO移除被删除的费用项
if (onChange) {
const newOrderCostList = (
purchaseOrderVO.orderCostList || []
).filter((cost) => cost.orderCostId !== item.orderCostId);
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: newOrderCostList,
};
onChange(newPurchaseOrderVO);
}
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View> </View>
</View> </View>
</View> <SafeArea position="bottom" />
<SafeArea position="bottom" /> </Popup>
</Popup> );
)})} })}
{/* 工头垫付 */} {/* 工头垫付 */}
{workerAdvanceCosts.map((item) => { {workerAdvanceCosts.map((item) => {
// 获取费用项目的 requireQuantityAndPrice 属性 // 获取费用项目的 requireQuantityAndPrice 属性
const costItem = costItems.find((cost) => cost.itemId === item.itemId); const costItem = costItems.find((cost) => cost.itemId === item.itemId);
const requireQuantityAndPrice = costItem?.requireQuantityAndPrice ?? false; const requireQuantityAndPrice =
costItem?.requireQuantityAndPrice ?? false;
return ( return (
<Popup <Popup
key={item.orderCostId} key={item.orderCostId}
visible={visiblePopup[item.orderCostId]} visible={visiblePopup[item.orderCostId]}
position="bottom" position="bottom"
title={`编辑${item.name}`} title={`编辑${item.name}`}
onClose={() => onClose={() =>
setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) setVisiblePopup((prev) => ({
} ...prev,
onOverlayClick={() => [item.orderCostId]: false,
setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false })) }))
} }
lockScroll onOverlayClick={() =>
> setVisiblePopup((prev) => ({
<View className="flex flex-col gap-3 p-2.5"> ...prev,
{requireQuantityAndPrice ? ( [item.orderCostId]: false,
<> }))
<View className="text-neutral-darkest text-sm font-medium"> }
lockScroll
</View> >
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid"> <View className="flex flex-col gap-3 p-2.5">
<Input {requireQuantityAndPrice ? (
className="placeholder:text-neutral-dark" <>
placeholder="请输入人数" <View className="text-neutral-darkest text-sm font-medium">
type="digit"
value={ </View>
tempEditValues[item.orderCostId]?.count?.toString() || "" <View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
} <Input
onChange={(value) => { className="placeholder:text-neutral-dark"
const numValue = validatePrice(value); placeholder="请输入人数"
if (numValue !== undefined) { type="digit"
setTempEditValues((prev) => ({ value={
...prev, tempEditValues[item.orderCostId]?.count?.toString() ||
[item.orderCostId]: { ""
...prev[item.orderCostId],
count: numValue as number,
},
}));
} }
}} onChange={(value) => {
/> const numValue = validatePrice(value);
<View className="mr-2"></View> if (numValue !== undefined) {
</View> setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
count: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
<View className="text-neutral-darkest text-sm font-medium"> <View className="text-neutral-darkest text-sm font-medium">
</View> </View>
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid"> <View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
<Input <Input
className="placeholder:text-neutral-dark" className="placeholder:text-neutral-dark"
placeholder="请输入单价" placeholder="请输入单价"
type="digit" type="digit"
value={ value={
tempEditValues[item.orderCostId]?.price?.toString() || "" tempEditValues[item.orderCostId]?.price?.toString() ||
} ""
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
},
}));
} }
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></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={(
tempEditValues[item.orderCostId]?.price *
tempEditValues[item.orderCostId]?.count ||
item.price * item.count
).toString()}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
count: 1,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
<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,
[item.orderCostId]: false,
}));
}} }}
/> >
<View className="mr-2"></View>
</Button>
</View> </View>
</> <View className="flex-1">
) : ( <Button
<> size="large"
<View className="text-neutral-darkest text-sm font-medium"> block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...tempEditValues[item.orderCostId],
// 如果费用承担方是瓜农,清空工头姓名
principal:
tempEditValues[item.orderCostId]?.payerType ===
"OTHER"
? ""
: tempEditValues[item.orderCostId]?.principal,
},
}));
// 这里应该调用更新接口或更新状态
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View> </View>
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid"> <View className="flex-1">
<Input <Button
className="placeholder:text-neutral-dark" size="large"
placeholder="请输入金额" block
type="digit" type="danger"
value={ onClick={() => {
(tempEditValues[item.orderCostId]?.price * tempEditValues[item.orderCostId]?.count || item.price * item.count).toString() // 删除费用项
} setEditValues((prev) => {
onChange={(value) => { const newEditValues = { ...prev };
const numValue = validatePrice(value); delete newEditValues[item.orderCostId];
if (numValue !== undefined) { return newEditValues;
setTempEditValues((prev) => ({ });
...prev,
[item.orderCostId]: { // 更新purchaseOrderVO移除被删除的费用项
...prev[item.orderCostId], if (onChange) {
price: numValue as number, const newOrderCostList = (
count: 1, purchaseOrderVO.orderCostList || []
}, ).filter(
})); (cost) => cost.orderCostId !== item.orderCostId,
);
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: newOrderCostList,
};
onChange(newPurchaseOrderVO);
} }
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}} }}
/> >
<View className="mr-2"></View>
</Button>
</View> </View>
</>
)}
</View>
<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,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...tempEditValues[item.orderCostId],
// 如果费用承担方是瓜农,清空工头姓名
principal:
tempEditValues[item.orderCostId]?.payerType ===
"OTHER"
? ""
: tempEditValues[item.orderCostId]?.principal,
},
}));
// 这里应该调用更新接口或更新状态
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="danger"
onClick={() => {
// 删除费用项
setEditValues((prev) => {
const newEditValues = { ...prev };
delete newEditValues[item.orderCostId];
return newEditValues;
});
// 更新purchaseOrderVO移除被删除的费用项
if (onChange) {
const newOrderCostList = (
purchaseOrderVO.orderCostList || []
).filter((cost) => cost.orderCostId !== item.orderCostId);
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: newOrderCostList,
};
onChange(newPurchaseOrderVO);
}
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View> </View>
</View> </View>
</View> <SafeArea position="bottom" />
<SafeArea position="bottom" /> </Popup>
</Popup> );
)})} })}
</> </>
); );
} }

View File

@ -606,123 +606,207 @@ export default function MaterialCostSection(props: {
</Popup> </Popup>
{/* 辅料费编辑弹窗 */} {/* 辅料费编辑弹窗 */}
{packagingMaterials.map((item) => ( {packagingMaterials.map((item) => {
<Popup // 获取费用项目的 requireQuantityAndPrice 属性
key={item.orderCostId} const costItem = costItems.find((cost) => cost.itemId === item.itemId);
visible={visiblePopup[item.orderCostId]} const requireQuantityAndPrice =
position="bottom" costItem?.requireQuantityAndPrice ?? false;
title={`编辑${item.name}`}
onClose={() =>
setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false }))
}
onOverlayClick={() =>
setVisiblePopup((prev) => ({ ...prev, [item.orderCostId]: false }))
}
lockScroll
>
<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="digit"
value={
tempEditValues[item.orderCostId]?.count?.toString() ||
editValues[item.orderCostId]?.count?.toString() ||
""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
count: numValue as number,
},
}));
}
}}
/>
<View className="mr-2">{item.unit}</View>
</View>
<View className="text-neutral-darkest text-sm font-medium"> return (
<Popup
</View> key={item.orderCostId}
<View className="border-neutral-base flex flex-row items-center rounded-md border border-solid"> visible={visiblePopup[item.orderCostId]}
<Input position="bottom"
className="placeholder:text-neutral-dark" title={`编辑${item.name}`}
placeholder="请输入单价" onClose={() =>
type="digit" setVisiblePopup((prev) => ({
value={ ...prev,
tempEditValues[item.orderCostId]?.price?.toString() || [item.orderCostId]: false,
editValues[item.orderCostId]?.price?.toString() || }))
"" }
} onOverlayClick={() =>
onChange={(value) => { setVisiblePopup((prev) => ({
const numValue = validatePrice(value); ...prev,
if (numValue !== undefined) { [item.orderCostId]: false,
setTempEditValues((prev) => ({ }))
...prev, }
[item.orderCostId]: { lockScroll
...prev[item.orderCostId], >
price: numValue as number, <View className="flex flex-col gap-3 p-2.5">
}, {requireQuantityAndPrice ? (
})); <>
} <View className="text-neutral-darkest text-sm font-medium">
}}
/> </View>
<View className="mr-2"></View> <View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
</View> <Input
</View> className="placeholder:text-neutral-dark"
<View className="flex w-full flex-col bg-white"> placeholder="请输入数量"
<View className="flex flex-row gap-2 p-3"> type="digit"
<View className="flex-1"> value={
<Button tempEditValues[item.orderCostId]?.count?.toString() ||
size="large" ""
block }
type="default" onChange={(value) => {
onClick={() => const numValue = validatePrice(value);
setVisiblePopup((prev) => ({ if (numValue !== undefined) {
...prev, setTempEditValues((prev) => ({
[item.orderCostId]: false, ...prev,
})) [item.orderCostId]: {
} ...prev[item.orderCostId],
> count: numValue as number,
},
</Button> }));
</View> }
<View className="flex-1"> }}
<Button />
size="large" <View className="mr-2"></View>
block </View>
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: tempEditValues[item.orderCostId],
}));
// 关闭弹窗 <View className="text-neutral-darkest text-sm font-medium">
setVisiblePopup((prev) => ({
...prev, </View>
[item.orderCostId]: false, <View className="border-neutral-base flex flex-row items-center rounded-md border border-solid">
})); <Input
}} className="placeholder:text-neutral-dark"
> placeholder="请输入单价"
type="digit"
</Button> value={
tempEditValues[item.orderCostId]?.price?.toString() ||
""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></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={(
tempEditValues[item.orderCostId]?.price *
tempEditValues[item.orderCostId]?.count ||
item.price * item.count
).toString()}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
count: 1,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
<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,
[item.orderCostId]: false,
}))
}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: tempEditValues[item.orderCostId],
}));
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="danger"
onClick={() => {
// 删除费用项
setEditValues((prev) => {
const newEditValues = { ...prev };
delete newEditValues[item.orderCostId];
return newEditValues;
});
// 更新purchaseOrderVO移除被删除的费用项
if (onChange) {
const newOrderCostList = (
purchaseOrderVO.orderCostList || []
).filter(
(cost) => cost.orderCostId !== item.orderCostId,
);
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: newOrderCostList,
};
onChange(newPurchaseOrderVO);
}
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
</View> </View>
</View> </View>
</View> <SafeArea position="bottom" />
<SafeArea position="bottom" /> </Popup>
</Popup> );
))} })}
</> </>
); );
} }

View File

@ -0,0 +1,741 @@
import { ScrollView, View } from "@tarojs/components";
import {
Button,
Input,
Picker,
Popup,
SafeArea,
} from "@nutui/nutui-react-taro";
import { useEffect, useState } from "react";
import { Icon } from "@/components";
import { business } from "@/services";
import { formatCurrency, validatePrice } from "@/utils/format";
import { generateShortId } from "@/utils/generateShortId";
export default function ProductionAdvanceSection(props: {
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly } = props;
// 弹窗相关状态
const [visiblePopup, setVisiblePopup] = useState<{ [key: string]: boolean }>(
{},
);
// 新增人工费弹窗状态
const [showAddCostPopup, setShowAddCostPopup] = useState(false);
// 新增人工费表单数据
const [newCostData, setNewCostData] = useState<any>({
costType: "PRODUCTION_ADVANCE", // 费用类型
itemId: "", // 费用项目ID
name: "", // 费用名称
quantity: 0, // 数量(人数)
unit: "", // 单位
unitPrice: "", // 单价
amount: "", // 金额
payerType: "US", // 费用承担方,默认我方
principal: "", // 工头姓名
requireQuantityAndPrice: false,
});
console.log("newCostData", newCostData);
// Picker可见状态
const [pickerVisible, setPickerVisible] = useState({
costType: false, // 费用类型Picker
costItem: false, // 费用项目Picker
});
// 费用项目列表
const [costItems, setCostItems] = useState<BusinessAPI.CostItemVO[]>([]);
// 编辑值的状态
const [editValues, setEditValues] = useState<{
[key: string]: {
count: number;
price: number;
name?: string;
principal?: string;
payerType?: "US" | "OTHER";
};
}>({});
// 临时编辑值的状态(用于在保存前暂存编辑的值)
const [tempEditValues, setTempEditValues] = useState<{
[key: string]: {
count: number;
price: number;
name?: string;
principal?: string;
payerType?: "US" | "OTHER";
};
}>({});
// 初始化编辑值
const initEditValues = (
itemId: string,
count: number,
price: number,
name?: string,
principal?: string,
payerType?: "US" | "OTHER",
) => {
if (!editValues[itemId]) {
setEditValues((prev) => ({
...prev,
[itemId]: { count, price, name, principal, payerType },
}));
}
// 同时初始化临时编辑值
if (!tempEditValues[itemId]) {
setTempEditValues((prev) => ({
...prev,
[itemId]: { count, price, name, principal, payerType },
}));
}
};
// 获取费用项目列表
useEffect(() => {
const fetchCostItems = async () => {
try {
const { data } = await business.costItem.listCostItem({
costItemListQry: {
status: true,
showInEntry: true,
},
});
setCostItems(data.data || []);
} catch (error) {
console.error("获取费用项目列表失败:", error);
}
};
fetchCostItems();
}, []);
// 产地垫付
const productionAdvanceCosts =
purchaseOrderVO.orderCostList?.filter(
(item) => item.costType === "PRODUCTION_ADVANCE",
) || [];
// 当editValues发生变化时更新purchaseOrderVO
useEffect(() => {
// 只有当onChange存在时才更新
if (onChange) {
// 获取现有的orderCostList
const existingOrderCostList = purchaseOrderVO.orderCostList || [];
// 更新已有的成本项
const updatedOrderCostList = existingOrderCostList.map((item) => {
if (editValues[item.orderCostId]) {
return {
...item,
price:
editValues[item.orderCostId].price !== undefined
? editValues[item.orderCostId].price
: item.price,
count:
editValues[item.orderCostId].count !== undefined
? editValues[item.orderCostId].count
: item.count,
name:
editValues[item.orderCostId].name !== undefined
? editValues[item.orderCostId].name
: item.name,
principal:
editValues[item.orderCostId].principal !== undefined
? editValues[item.orderCostId].principal
: item.principal,
payerType:
editValues[item.orderCostId].payerType !== undefined
? editValues[item.orderCostId].payerType
: item.payerType,
};
}
// 未修改的成本项保持原样
return item;
});
// 创建新的purchaseOrderVO对象
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: updatedOrderCostList,
};
// 调用onChange回调
onChange(newPurchaseOrderVO as any);
}
}, [editValues]);
return (
<>
<View className={"flex flex-col gap-2.5 divide-y divide-[#eaeaea]"}>
<View className="flex flex-col gap-2.5">
<View className={"text-sm font-bold"}>
{purchaseOrderVO.originPrincipal}
</View>
{/* 产地垫付 */}
{productionAdvanceCosts.map((item, index) => {
// 初始化编辑值,包括费用承担方和工头姓名
initEditValues(
item.orderCostId,
item.count,
item.price,
item.name,
item.principal,
item.payerType,
);
return (
<View
className="flex items-center justify-between gap-2.5"
key={item.orderCostId}
onClick={() =>
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: true,
}))
}
>
<View className="text-neutral-dark flex-shrink-0 text-sm">
{index + 1}-{item.name}
{item.principal && `(${item.principal})`}
</View>
<View className="text-neutral-darkest flex flex-row items-center justify-between gap-2.5 text-sm font-medium">
{formatCurrency(
Number(
(editValues[item.orderCostId]?.price || item.price) *
(editValues[item.orderCostId]?.count || item.count),
),
)}
{!readOnly && (
<View className="ml-1 text-gray-500">
<Icon
name={"pen-to-square"}
size={16}
color={"#1a73e8"}
/>
</View>
)}
</View>
</View>
);
})}
</View>
{!readOnly && (
<Button
type={"primary"}
block
size={"large"}
onClick={() => setShowAddCostPopup(true)}
>
</Button>
)}
</View>
{/* 新增其他产地垫付费用 */}
<Popup
visible={showAddCostPopup}
position="bottom"
title="新增其他产地垫付费用"
onClose={() => setShowAddCostPopup(false)}
onOverlayClick={() => setShowAddCostPopup(false)}
lockScroll
>
<ScrollView scrollY className="max-h-150">
<View className="flex flex-col gap-3 p-2.5">
{/* 垫付项目 */}
{newCostData.costType && (
<>
<View className="text-neutral-darkest text-sm font-medium">
</View>
<View
className="border-neutral-base relative flex h-10 w-full items-center rounded-md border border-solid"
onClick={() =>
setPickerVisible((prev) => ({ ...prev, costItem: true }))
}
>
<Input
type="text"
placeholder="请选择垫付项目"
value={newCostData.name || ""}
disabled
/>
<Icon
name="chevron-down"
className="absolute -right-1 mr-4"
/>
</View>
<Picker
title="请选择垫付项目"
visible={pickerVisible.costItem}
options={[
costItems
.filter((item) => item.costType === newCostData.costType)
.filter((item) => {
// 检查该项目是否已经被选择
return !productionAdvanceCosts.some(
(hc) => hc.itemId === item.itemId,
);
})
.map((item) => ({
label: item.name,
value: item.itemId,
})),
]}
onConfirm={(_, values) => {
const selectedValue = values[0] as string;
const selectedItem = costItems.find(
(item) => item.itemId === selectedValue,
);
if (selectedItem) {
setNewCostData((prev) => ({
...prev,
itemId: selectedValue,
name: selectedItem.name,
payerType: "US",
quantity: selectedItem.requireQuantityAndPrice ? 0 : 1, // 数量(人数)
unit: selectedItem.requireQuantityAndPrice
? selectedItem.unit
: "元", // 单位
unitPrice: "", // 单价
amount: "", // 金额
principal: "", // 工头姓名
requireQuantityAndPrice:
selectedItem.requireQuantityAndPrice,
}));
}
setPickerVisible((prev) => ({ ...prev, costItem: false }));
}}
onCancel={() =>
setPickerVisible((prev) => ({ ...prev, costItem: false }))
}
onClose={() =>
setPickerVisible((prev) => ({ ...prev, costItem: false }))
}
/>
</>
)}
{/* 数量输入 */}
{newCostData.itemId &&
newCostData.costType &&
newCostData.requireQuantityAndPrice && (
<>
<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 flex-1"
placeholder="请输入数量"
type="digit"
value={newCostData.quantity?.toString() || ""}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setNewCostData((prev) => ({
...prev,
quantity: numValue as any,
}));
}
}}
/>
<View className="mr-2">{newCostData.unit}</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 flex-1"
placeholder="请输入单价"
type="digit"
value={newCostData.unitPrice?.toString() || ""}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setNewCostData((prev) => ({
...prev,
unitPrice: numValue as any,
}));
}
}}
/>
<View className="mr-2"></View>
</View>
{/* 金额展示 */}
<View className="flex items-center justify-between rounded-md bg-gray-100 p-3">
<View className="text-neutral-darkest text-sm font-medium">
</View>
<View className="text-neutral-darkest text-sm font-medium">
{formatCurrency(
Number(newCostData.quantity || 0) *
Number(newCostData.unitPrice || 0),
)}{" "}
</View>
</View>
</>
)}
{newCostData.itemId &&
newCostData.costType &&
!newCostData.requireQuantityAndPrice && (
<>
<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 flex-1"
placeholder="请输入金额"
type="digit"
value={newCostData.amount?.toString() || ""}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setNewCostData((prev) => ({
...prev,
unitPrice: numValue as any,
amount: numValue as any,
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
<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={() => {
setShowAddCostPopup(false);
// 重置表单
setNewCostData({
costType: "PRODUCTION_ADVANCE", // 费用类型
itemId: "",
name: "", // 费用名称
quantity: 0, // 数量(人数)
unit: "", // 单位
unitPrice: "", // 单价
amount: "", // 金额
payerType: "", // 费用承担方,默认我方
principal: "", // 工头姓名
requireQuantityAndPrice: false,
});
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 检查必填项
if (!newCostData.itemId) {
console.log("请选择辅料项目");
return;
}
// 检查数量和单价或金额
if (
newCostData.requireQuantityAndPrice &&
(!newCostData.quantity || !newCostData.unitPrice)
) {
console.log("请填写数量和单价");
return;
}
if (
!newCostData.requireQuantityAndPrice &&
!newCostData.amount
) {
console.log("请填写金额");
return;
}
// 创建新的费用项
const newOrderCost: BusinessAPI.OrderCost = {
orderCostId: generateShortId(),
itemId: newCostData.itemId || "",
name: newCostData.name,
price: Number(newCostData.unitPrice || 0),
unit: newCostData.unit,
count: Number(newCostData.quantity || 1),
//@ts-ignore
payerType: newCostData.payerType || "US",
costType: newCostData.costType!,
principal: "",
requireQuantityAndPrice: false,
};
// 更新purchaseOrderVO将新费用添加到orderCostList中
if (onChange) {
const updatedOrderCostList = [
...(purchaseOrderVO.orderCostList || []),
newOrderCost,
];
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: updatedOrderCostList,
};
onChange(newPurchaseOrderVO);
}
setShowAddCostPopup(false);
// 重置表单
setNewCostData({
costType: "PRODUCTION_ADVANCE", // 费用类型
itemId: "",
name: "", // 费用名称
quantity: 0, // 数量(人数)
unit: "", // 单位
unitPrice: "", // 单价
amount: "", // 金额
payerType: "", // 费用承担方,默认我方
principal: "", // 工头姓名
requireQuantityAndPrice: false,
});
}}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</ScrollView>
</Popup>
{/* 产地垫付 */}
{productionAdvanceCosts.map((item) => {
return (
<Popup
key={item.orderCostId}
visible={visiblePopup[item.orderCostId]}
position="bottom"
title={`编辑${item.name}`}
onClose={() =>
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}))
}
onOverlayClick={() =>
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}))
}
lockScroll
>
<View className="flex flex-col gap-3 p-2.5">
{item?.requireQuantityAndPrice ? (
<>
<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={
tempEditValues[item.orderCostId]?.count?.toString() ||
""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
count: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></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={
tempEditValues[item.orderCostId]?.price?.toString() ||
""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></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={(
tempEditValues[item.orderCostId]?.price *
tempEditValues[item.orderCostId]?.count ||
item.price * item.count
).toString()}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
count: 1,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
<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,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...tempEditValues[item.orderCostId],
// 如果费用承担方是瓜农,清空工头姓名
principal:
tempEditValues[item.orderCostId]?.payerType ===
"OTHER"
? ""
: tempEditValues[item.orderCostId]?.principal,
},
}));
// 这里应该调用更新接口或更新状态
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="danger"
onClick={() => {
// 删除费用项
setEditValues((prev) => {
const newEditValues = { ...prev };
delete newEditValues[item.orderCostId];
return newEditValues;
});
// 更新purchaseOrderVO移除被删除的费用项
if (onChange) {
const newOrderCostList = (
purchaseOrderVO.orderCostList || []
).filter(
(cost) => cost.orderCostId !== item.orderCostId,
);
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: newOrderCostList,
};
onChange(newPurchaseOrderVO);
}
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</Popup>
);
})}
</>
);
}

View File

@ -0,0 +1,762 @@
import { ScrollView, View } from "@tarojs/components";
import {
Button,
Input,
Picker,
Popup,
SafeArea,
} from "@nutui/nutui-react-taro";
import { useEffect, useState } from "react";
import { Icon } from "@/components";
import { business } from "@/services";
import { formatCurrency, validatePrice } from "@/utils/format";
import { generateShortId } from "@/utils/generateShortId";
export default function WorkerAdvanceSection(props: {
purchaseOrderVO: BusinessAPI.PurchaseOrderVO;
onChange?: (purchaseOrderVO: BusinessAPI.PurchaseOrderVO) => void;
readOnly?: boolean;
}) {
const { purchaseOrderVO, onChange, readOnly } = props;
// 弹窗相关状态
const [visiblePopup, setVisiblePopup] = useState<{ [key: string]: boolean }>(
{},
);
// 新增人工费弹窗状态
const [showAddCostPopup, setShowAddCostPopup] = useState(false);
// 新增人工费表单数据
const [newCostData, setNewCostData] = useState<any>({
costType: "WORKER_ADVANCE", // 费用类型
itemId: "", // 费用项目ID
name: "", // 费用名称
quantity: 0, // 数量(人数)
unit: "", // 单位
unitPrice: "", // 单价
amount: "", // 金额
payerType: "US", // 费用承担方,默认我方
principal: "", // 工头姓名
requireQuantityAndPrice: false,
});
console.log("newCostData", newCostData);
// Picker可见状态
const [pickerVisible, setPickerVisible] = useState({
costType: false, // 费用类型Picker
costItem: false, // 费用项目Picker
});
// 费用项目列表
const [costItems, setCostItems] = useState<BusinessAPI.CostItemVO[]>([]);
// 编辑值的状态
const [editValues, setEditValues] = useState<{
[key: string]: {
count: number;
price: number;
name?: string;
principal?: string;
payerType?: "US" | "OTHER";
};
}>({});
// 临时编辑值的状态(用于在保存前暂存编辑的值)
const [tempEditValues, setTempEditValues] = useState<{
[key: string]: {
count: number;
price: number;
name?: string;
principal?: string;
payerType?: "US" | "OTHER";
};
}>({});
// 初始化编辑值
const initEditValues = (
itemId: string,
count: number,
price: number,
name?: string,
principal?: string,
payerType?: "US" | "OTHER",
) => {
if (!editValues[itemId]) {
setEditValues((prev) => ({
...prev,
[itemId]: { count, price, name, principal, payerType },
}));
}
// 同时初始化临时编辑值
if (!tempEditValues[itemId]) {
setTempEditValues((prev) => ({
...prev,
[itemId]: { count, price, name, principal, payerType },
}));
}
};
// 获取费用项目列表
useEffect(() => {
const fetchCostItems = async () => {
try {
const { data } = await business.costItem.listCostItem({
costItemListQry: {
status: true,
showInEntry: true,
},
});
setCostItems(data.data || []);
} catch (error) {
console.error("获取费用项目列表失败:", error);
}
};
fetchCostItems();
}, []);
// 人工费
const humanCosts =
purchaseOrderVO.orderCostList?.filter(
(item) =>
item.costType === "HUMAN_COST" &&
item.payerType === "US" &&
item.count > 0,
) || [];
// 工头垫付
const workerAdvanceCosts =
purchaseOrderVO.orderCostList?.filter(
(item) => item.costType === "WORKER_ADVANCE",
) || [];
// 当editValues发生变化时更新purchaseOrderVO
useEffect(() => {
// 只有当onChange存在时才更新
if (onChange) {
// 获取现有的orderCostList
const existingOrderCostList = purchaseOrderVO.orderCostList || [];
// 更新已有的成本项
const updatedOrderCostList = existingOrderCostList.map((item) => {
if (editValues[item.orderCostId]) {
return {
...item,
price:
editValues[item.orderCostId].price !== undefined
? editValues[item.orderCostId].price
: item.price,
count:
editValues[item.orderCostId].count !== undefined
? editValues[item.orderCostId].count
: item.count,
name:
editValues[item.orderCostId].name !== undefined
? editValues[item.orderCostId].name
: item.name,
principal:
editValues[item.orderCostId].principal !== undefined
? editValues[item.orderCostId].principal
: item.principal,
payerType:
editValues[item.orderCostId].payerType !== undefined
? editValues[item.orderCostId].payerType
: item.payerType,
};
}
// 未修改的成本项保持原样
return item;
});
// 创建新的purchaseOrderVO对象
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: updatedOrderCostList,
};
// 调用onChange回调
onChange(newPurchaseOrderVO as any);
}
}, [editValues]);
return (
<>
<View className={"flex flex-col gap-2.5 divide-y divide-[#eaeaea]"}>
<View className="flex flex-col gap-2.5">
<View className={"flex flex-col gap-2.5"}>
<View className={"text-sm font-bold"}>
{humanCosts?.[0].principal}
</View>
<View
className={
"text-neutral-dark flex flex-row flex-wrap gap-2.5 text-xs font-medium"
}
>
{humanCosts.map((item, index) => {
return (
<View key={index}>{`${item.name} ${item.count}`}</View>
);
})}
</View>
</View>
{/* 工头垫付 */}
{workerAdvanceCosts.map((item, index) => {
// 初始化编辑值,包括费用承担方和工头姓名
initEditValues(
item.orderCostId,
item.count,
item.price,
item.name,
item.principal,
item.payerType,
);
return (
<View
className="flex items-center justify-between gap-2.5"
key={item.orderCostId}
onClick={() =>
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: true,
}))
}
>
<View className="text-neutral-dark flex-shrink-0 text-sm">
{index + 1}-{item.name}
{item.principal && `(${item.principal})`}
</View>
<View className="text-neutral-darkest flex flex-row items-center justify-between gap-2.5 text-sm font-medium">
{formatCurrency(
Number(
(editValues[item.orderCostId]?.price || item.price) *
(editValues[item.orderCostId]?.count || item.count),
),
)}
{!readOnly && (
<View className="ml-1 text-gray-500">
<Icon
name={"pen-to-square"}
size={16}
color={"#1a73e8"}
/>
</View>
)}
</View>
</View>
);
})}
</View>
{!readOnly && (
<Button
type={"primary"}
block
size={"large"}
onClick={() => setShowAddCostPopup(true)}
>
</Button>
)}
</View>
{/* 新增其他工头垫付费用 */}
<Popup
visible={showAddCostPopup}
position="bottom"
title="新增其他工头垫付费用"
onClose={() => setShowAddCostPopup(false)}
onOverlayClick={() => setShowAddCostPopup(false)}
lockScroll
>
<ScrollView scrollY className="max-h-150">
<View className="flex flex-col gap-3 p-2.5">
{/* 垫付项目 */}
{newCostData.costType && (
<>
<View className="text-neutral-darkest text-sm font-medium">
</View>
<View
className="border-neutral-base relative flex h-10 w-full items-center rounded-md border border-solid"
onClick={() =>
setPickerVisible((prev) => ({ ...prev, costItem: true }))
}
>
<Input
type="text"
placeholder="请选择垫付项目"
value={newCostData.name || ""}
disabled
/>
<Icon
name="chevron-down"
className="absolute -right-1 mr-4"
/>
</View>
<Picker
title="请选择垫付项目"
visible={pickerVisible.costItem}
options={[
costItems
.filter((item) => item.costType === newCostData.costType)
.filter((item) => {
return !workerAdvanceCosts.some(
(hc) => hc.itemId === item.itemId,
);
})
.map((item) => ({
label: item.name,
value: item.itemId,
})),
]}
onConfirm={(_, values) => {
const selectedValue = values[0] as string;
const selectedItem = costItems.find(
(item) => item.itemId === selectedValue,
);
if (selectedItem) {
setNewCostData((prev) => ({
...prev,
itemId: selectedValue,
name: selectedItem.name,
payerType: "US",
quantity: selectedItem.requireQuantityAndPrice ? 0 : 1, // 数量(人数)
unit: selectedItem.requireQuantityAndPrice
? selectedItem.unit
: "元", // 单位
unitPrice: "", // 单价
amount: "", // 金额
principal: "", // 工头姓名
requireQuantityAndPrice:
selectedItem.requireQuantityAndPrice,
}));
}
setPickerVisible((prev) => ({ ...prev, costItem: false }));
}}
onCancel={() =>
setPickerVisible((prev) => ({ ...prev, costItem: false }))
}
onClose={() =>
setPickerVisible((prev) => ({ ...prev, costItem: false }))
}
/>
</>
)}
{/* 数量输入 */}
{newCostData.itemId &&
newCostData.costType &&
newCostData.requireQuantityAndPrice && (
<>
<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 flex-1"
placeholder="请输入数量"
type="digit"
value={newCostData.quantity?.toString() || ""}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setNewCostData((prev) => ({
...prev,
quantity: numValue as any,
}));
}
}}
/>
<View className="mr-2">{newCostData.unit}</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 flex-1"
placeholder="请输入单价"
type="digit"
value={newCostData.unitPrice?.toString() || ""}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setNewCostData((prev) => ({
...prev,
unitPrice: numValue as any,
}));
}
}}
/>
<View className="mr-2"></View>
</View>
{/* 金额展示 */}
<View className="flex items-center justify-between rounded-md bg-gray-100 p-3">
<View className="text-neutral-darkest text-sm font-medium">
</View>
<View className="text-neutral-darkest text-sm font-medium">
{formatCurrency(
Number(newCostData.quantity || 0) *
Number(newCostData.unitPrice || 0),
)}{" "}
</View>
</View>
</>
)}
{newCostData.itemId &&
newCostData.costType &&
!newCostData.requireQuantityAndPrice && (
<>
<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 flex-1"
placeholder="请输入金额"
type="digit"
value={newCostData.amount?.toString() || ""}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setNewCostData((prev) => ({
...prev,
unitPrice: numValue as any,
amount: numValue as any,
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
<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={() => {
setShowAddCostPopup(false);
// 重置表单
setNewCostData({
costType: "WORKER_ADVANCE", // 费用类型
itemId: "",
name: "", // 费用名称
quantity: 0, // 数量(人数)
unit: "", // 单位
unitPrice: "", // 单价
amount: "", // 金额
payerType: "", // 费用承担方,默认我方
principal: "", // 工头姓名
requireQuantityAndPrice: false,
});
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 检查必填项
if (!newCostData.itemId) {
console.log("请选择辅料项目");
return;
}
// 检查数量和单价或金额
if (
newCostData.requireQuantityAndPrice &&
(!newCostData.quantity || !newCostData.unitPrice)
) {
console.log("请填写数量和单价");
return;
}
if (
!newCostData.requireQuantityAndPrice &&
!newCostData.amount
) {
console.log("请填写金额");
return;
}
// 创建新的费用项
const newOrderCost: BusinessAPI.OrderCost = {
orderCostId: generateShortId(),
itemId: newCostData.itemId || "",
name: newCostData.name,
price: Number(newCostData.unitPrice || 0),
unit: newCostData.unit,
count: Number(newCostData.quantity || 1),
//@ts-ignore
payerType: newCostData.payerType || "US",
costType: newCostData.costType!,
principal: "",
requireQuantityAndPrice: false,
};
// 更新purchaseOrderVO将新费用添加到orderCostList中
if (onChange) {
const updatedOrderCostList = [
...(purchaseOrderVO.orderCostList || []),
newOrderCost,
];
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: updatedOrderCostList,
};
onChange(newPurchaseOrderVO);
}
setShowAddCostPopup(false);
// 重置表单
setNewCostData({
costType: "PRODUCTION_ADVANCE", // 费用类型
itemId: "",
name: "", // 费用名称
quantity: 0, // 数量(人数)
unit: "", // 单位
unitPrice: "", // 单价
amount: "", // 金额
payerType: "", // 费用承担方,默认我方
principal: "", // 工头姓名
requireQuantityAndPrice: false,
});
}}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</ScrollView>
</Popup>
{/* 工头垫付 */}
{workerAdvanceCosts.map((item) => {
return (
<Popup
key={item.orderCostId}
visible={visiblePopup[item.orderCostId]}
position="bottom"
title={`编辑${item.name}`}
onClose={() =>
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}))
}
onOverlayClick={() =>
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}))
}
lockScroll
>
<View className="flex flex-col gap-3 p-2.5">
{item?.requireQuantityAndPrice ? (
<>
<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={
tempEditValues[item.orderCostId]?.count?.toString() ||
""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
count: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></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={
tempEditValues[item.orderCostId]?.price?.toString() ||
""
}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
},
}));
}
}}
/>
<View className="mr-2"></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={(
tempEditValues[item.orderCostId]?.price *
tempEditValues[item.orderCostId]?.count ||
item.price * item.count
).toString()}
onChange={(value) => {
const numValue = validatePrice(value);
if (numValue !== undefined) {
setTempEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...prev[item.orderCostId],
price: numValue as number,
count: 1,
},
}));
}
}}
/>
<View className="mr-2"></View>
</View>
</>
)}
</View>
<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,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="primary"
onClick={() => {
// 保存时才更新editValues状态
setEditValues((prev) => ({
...prev,
[item.orderCostId]: {
...tempEditValues[item.orderCostId],
// 如果费用承担方是瓜农,清空工头姓名
principal:
tempEditValues[item.orderCostId]?.payerType ===
"OTHER"
? ""
: tempEditValues[item.orderCostId]?.principal,
},
}));
// 这里应该调用更新接口或更新状态
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size="large"
block
type="danger"
onClick={() => {
// 删除费用项
setEditValues((prev) => {
const newEditValues = { ...prev };
delete newEditValues[item.orderCostId];
return newEditValues;
});
// 更新purchaseOrderVO移除被删除的费用项
if (onChange) {
const newOrderCostList = (
purchaseOrderVO.orderCostList || []
).filter(
(cost) => cost.orderCostId !== item.orderCostId,
);
const newPurchaseOrderVO = {
...purchaseOrderVO,
orderCostList: newOrderCostList,
};
onChange(newPurchaseOrderVO);
}
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</Popup>
);
})}
</>
);
}

View File

@ -1,7 +1,14 @@
import hocAuth from "@/hocs/auth"; import hocAuth from "@/hocs/auth";
import { CommonComponent, CostItem, SupplierVO } from "@/types/typings"; import { CommonComponent, CostItem, SupplierVO } from "@/types/typings";
import { View } from "@tarojs/components"; import { View } from "@tarojs/components";
import { Button, Dialog, SafeArea, Toast } from "@nutui/nutui-react-taro"; import {
Button,
Dialog,
SafeArea,
Toast,
Tour,
TourList,
} from "@nutui/nutui-react-taro";
import { purchase } from "@/constant"; import { purchase } from "@/constant";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { import {
@ -61,6 +68,26 @@ export default hocAuth(function Page(props: CommonComponent) {
const defaultStep = router.params.step as number; const defaultStep = router.params.step as number;
const defaultSupplierId = router.params.supplierId as string; const defaultSupplierId = router.params.supplierId as string;
const [showTour, setShowTour] = useState(true);
const closeTour = () => {
setShowTour(false);
};
const steps: TourList[] = [
{
content: "粘贴识别",
target: "target1",
popoverOffset: [0, 12],
arrowOffset: 0,
},
{
content: "选择经销商",
target: "target2",
location: "bottom-left",
},
];
const vehicleRef = useRef<OrderVehicleRef>(null); const vehicleRef = useRef<OrderVehicleRef>(null);
// 创建MelonFarmer组件的ref数组 // 创建MelonFarmer组件的ref数组
const melonFarmerRefs = useRef<MelonFarmerRef[]>([]); const melonFarmerRefs = useRef<MelonFarmerRef[]>([]);
@ -622,29 +649,54 @@ export default hocAuth(function Page(props: CommonComponent) {
}), }),
]); ]);
setPurchaseOrder((prev) => { setPurchaseOrder((prev) => {
const costItemVOList = productVO.costItemVOList;
// 将 orderCostList 中 不存在与 costItemVOList 的项 删除,剩余项保留
const orderCostList = prev?.orderCostList.filter((item) => {
return (
(item.costType === "WORKER_ADVANCE" ||
item.costType === "PRODUCTION_ADVANCE" ||
item.costType === "PACKAGING_MATERIALS") &&
costItemVOList?.find(
(costItemVO) => costItemVO.itemId === item.itemId,
)
);
});
// 添加 costItemVOList 中 不存在与 orderCostList 的项
costItemVOList?.forEach((item) => {
if (
!orderCostList?.find(
(costItemVO) => costItemVO.itemId === item.itemId,
)
) {
orderCostList?.push({
orderCostId: generateShortId(),
orderId: purchaseOrder?.orderId,
itemId: item.itemId,
name: item.name,
price: item.price,
unit: item.unit,
count: 0,
payerType: "US" as any,
principal: "",
costType: item.costType,
requireQuantityAndPrice: item.requireQuantityAndPrice,
});
}
});
return { return {
...prev!, ...prev!,
orderCostList: [ orderCostList: [
...(prev?.orderCostList?.filter((item) => { ...(prev?.orderCostList?.filter((item) => {
return ( return (
item.costType !== "WORKER_ADVANCE" && item.costType !== "WORKER_ADVANCE" &&
item.costType !== "PRODUCTION_ADVANCE" item.costType !== "PRODUCTION_ADVANCE" &&
item.costType !== "PACKAGING_MATERIALS"
); );
}) || []), }) || []),
...(productVO.costItemVOList?.map((item) => { ...(orderCostList || []),
return {
orderCostId: generateShortId(),
orderId: purchaseOrder?.orderId,
itemId: item.itemId,
name: item.name,
price: item.price,
unit: item.unit,
count: 0,
payerType: "US" as any,
principal: "",
costType: item.costType,
};
}) || []),
], ],
}; };
}); });
@ -866,6 +918,15 @@ export default hocAuth(function Page(props: CommonComponent) {
</View> </View>
<SafeArea position={"bottom"} /> <SafeArea position={"bottom"} />
</View> </View>
<Tour
className="nut-custom-tour nut-customword-tour hidden"
visible={showTour}
onClose={closeTour}
list={steps}
type="step"
location="bottom-right"
/>
</> </>
); );
}); });

View File

@ -5,13 +5,7 @@ import { business } from "@/services";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { View } from "@tarojs/components"; import { View } from "@tarojs/components";
import purchaseOrder from "@/constant/purchaseOrder"; import purchaseOrder from "@/constant/purchaseOrder";
import { import { ActionSheet, Button, Dialog, Input, Popup, SafeArea, TextArea, Toast } from "@nutui/nutui-react-taro";
Button,
Dialog,
Input,
SafeArea,
Toast,
} from "@nutui/nutui-react-taro";
import { import {
BasicInfoSection, BasicInfoSection,
CompanyInfoSection, CompanyInfoSection,
@ -19,16 +13,16 @@ import {
CostSummarySection, CostSummarySection,
DealerInfoSection, DealerInfoSection,
EmptyBoxInfoSection, EmptyBoxInfoSection,
LaborInfoSection,
MarketPriceSection, MarketPriceSection,
MaterialCostSection, MaterialCostSection,
PackageInfoSection, PackageInfoSection,
PackagingCostSection, PackagingCostSection,
PurchaseOrderRejectApprove, ProductionAdvanceSection,
RebateCalcSection, RebateCalcSection,
State, State,
TaxProvisionSection, TaxProvisionSection,
TaxSubsidySection, TaxSubsidySection,
WorkerAdvanceSection
} from "@/components"; } from "@/components";
import buildUrl from "@/utils/buildUrl"; import buildUrl from "@/utils/buildUrl";
import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator"; import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator";
@ -38,113 +32,86 @@ const sections = {
marketPrice: { marketPrice: {
component: MarketPriceSection, component: MarketPriceSection,
title: "市场报价", title: "市场报价",
containerClass: "border-l-8 border-l-lime-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 我方入账公司 // 我方入账公司
supplierInfo: { supplierInfo: {
component: CompanyInfoSection, component: CompanyInfoSection,
title: "我方入账公司", title: "我方入账公司",
containerClass: "border-l-8 border-l-blue-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 下游经销商信息 // 下游经销商信息
dealerInfo: { dealerInfo: {
component: DealerInfoSection, component: DealerInfoSection,
title: "下游经销商", title: "下游经销商",
containerClass: "border-l-8 border-l-green-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 基础信息 // 基础信息
basicInfo: { basicInfo: {
component: BasicInfoSection, component: BasicInfoSection,
title: "基础信息", title: "基础信息",
containerClass: "border-l-8 border-l-yellow-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// // 瓜农信息 // // 瓜农信息
// farmerInfo: { // farmerInfo: {
// component: SupplierInfoSection, // component: SupplierInfoSection,
// title: "瓜农信息", // title: "瓜农信息",
// containerClass: "border-l-8 border-l-purple-500",
// contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
// }, // },
// // 采购成本 // // 采购成本
// purchaseCostInfo: { // purchaseCostInfo: {
// component: PurchaseCostInfoSection, // component: PurchaseCostInfoSection,
// title: "采购成本", // title: "采购成本",
// containerClass: "border-l-8 border-l-red-500",
// contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
// }, // },
// 包装纸箱费 // 包装纸箱费
packageInfo: { packageInfo: {
component: PackageInfoSection, component: PackageInfoSection,
title: "包装纸箱费复核", title: "包装纸箱费复核",
containerClass: "border-l-8 border-l-indigo-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 空箱费用 // 空箱费用
emptyBoxInfo: { emptyBoxInfo: {
component: EmptyBoxInfoSection, component: EmptyBoxInfoSection,
title: "空箱费用复核", title: "空箱费用复核",
containerClass: "border-l-8 border-l-pink-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 用工信息 // 工头垫付费用复核
laborInfo: { foremanAdvance: {
component: LaborInfoSection, component: WorkerAdvanceSection,
title: "人工费复核", title: "工头垫付费用复核",
containerClass: "border-l-8 border-l-teal-500", },
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto", // 产地垫付费用复核
originAdvance: {
component: ProductionAdvanceSection,
title: "产地垫付费用复核",
}, },
// 辅料费用复核 // 辅料费用复核
materialCost: { materialCost: {
component: MaterialCostSection, component: MaterialCostSection,
title: "辅料费复核", title: "辅料费复核",
containerClass: "border-l-8 border-l-orange-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 其他费用复核 // 其他费用复核
otherCost: { otherCost: {
component: PackagingCostSection, component: PackagingCostSection,
title: "其他费用复核", title: "其他费用复核",
containerClass: "border-l-8 border-l-orange-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 成本合计 // 成本合计
costSummary: { costSummary: {
component: CostSummarySection, component: CostSummarySection,
title: "成本合计", title: "成本合计",
containerClass: "border-l-8 border-l-cyan-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 个人返点复核 // 个人返点复核
rebateCalc: { rebateCalc: {
component: RebateCalcSection, component: RebateCalcSection,
title: "个人返点复核", title: "个人返点复核",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 公司返点复核 // 公司返点复核
taxSubsidy: { taxSubsidy: {
component: TaxSubsidySection, component: TaxSubsidySection,
title: "公司返点复核", title: "公司返点复核",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 计提税金复核 // 计提税金复核
taxProvision: { taxProvision: {
component: TaxProvisionSection, component: TaxProvisionSection,
title: "计提税金复核", title: "计提税金复核",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
// 调诚信志远分红 // 调诚信志远分红
costDifference: { costDifference: {
component: CostDifferenceSection, component: CostDifferenceSection,
title: "调诚信志远分红", title: "调诚信志远分红",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
}, },
}; };
@ -157,16 +124,75 @@ export default hocAuth(function Page(props: CommonComponent) {
const [purchaseOrderVO, setPurchaseOrderVO] = const [purchaseOrderVO, setPurchaseOrderVO] =
useState<BusinessAPI.PurchaseOrderVO>(); useState<BusinessAPI.PurchaseOrderVO>();
const [originPrincipal, setOriginPrincipal] = useState(""); // 驳回原因弹窗相关状态
const [rejectVisible, setRejectVisible] = useState(false);
const [rejectReason, setRejectReason] = useState("");
const [collapsedSections, setCollapsedSections] = useState< // 驳回操作
Record<string, boolean> const handleReject = () => {
>(Object.keys(sections).reduce((acc, key) => ({ ...acc, [key]: false }), {})); setRejectVisible(true);
};
// 确认驳回
const confirmReject = async () => {
if (!purchaseOrderVO) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "请稍后再试",
});
return;
}
if (!rejectReason.trim()) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: "请输入驳回原因",
});
return;
}
try {
// 调用驳回API
await business.purchaseOrder.rejectApprovePurchaseOrder({
orderId: purchaseOrderVO?.orderId,
rejectReason: rejectReason,
});
Toast.show("toast", {
icon: "success",
title: "提示",
content: "驳回成功,正在跳转我的审核...",
});
// 关闭弹窗
setRejectVisible(false);
setRejectReason("");
setTimeout(() => {
Taro.redirectTo({ url: buildUrl("/pages/purchase/reviewer/list") });
}, 1000);
// 返回采购单页面
} catch (error) {
Toast.show("toast", {
icon: "fail",
title: "提示",
content: error.message || "驳回失败",
});
}
};
const [originPrincipal, setOriginPrincipal] = useState("");
// 暂存和提交审核的Dialog状态 // 暂存和提交审核的Dialog状态
const [saveDialogVisible, setSaveDialogVisible] = useState(false); const [saveDialogVisible, setSaveDialogVisible] = useState(false);
const [submitDialogVisible, setSubmitDialogVisible] = useState(false); const [submitDialogVisible, setSubmitDialogVisible] = useState(false);
// 控制更多操作的ActionSheet显示状态
const [moreActionVisible, setMoreActionVisible] = useState(false);
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>(); const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const initDealer = async (dealerId: BusinessAPI.DealerVO["dealerId"]) => { const initDealer = async (dealerId: BusinessAPI.DealerVO["dealerId"]) => {
@ -180,13 +206,6 @@ export default hocAuth(function Page(props: CommonComponent) {
setDealerVO(data.data); setDealerVO(data.data);
}; };
const toggleSection = (section: string) => {
setCollapsedSections((prev) => ({
...prev,
[section]: !prev[section],
}));
};
// 暂存操作 // 暂存操作
const handleSave = () => { const handleSave = () => {
setSaveDialogVisible(true); setSaveDialogVisible(true);
@ -335,124 +354,119 @@ export default hocAuth(function Page(props: CommonComponent) {
const personalProfit = calculator.getPersonalProfit(); const personalProfit = calculator.getPersonalProfit();
return ( return (
<View className={"flex flex-col gap-2.5"} id={"purchase-order-audit"}> <>
{/* 顶部导航 */} <View
<View className="flex flex-col gap-2.5 bg-white p-2.5"> className={"overflow-x-hidden overflow-y-auto bg-[#D1D5DB]"}
<View className={"flex flex-row justify-between gap-2.5"}> id={"purchase-order-audit"}
<View className="text-lg font-bold"> >
{purchaseOrderVO?.orderDealer?.shortName || "-"} <View className={"flex flex-col gap-2.5 p-2.5"}>
{purchaseOrderVO?.orderVehicle?.vehicleNo || "-"} {/* 顶部导航 */}
</View> <View className="flex flex-col gap-2.5 rounded-md bg-white p-2.5 shadow-sm">
<View className={"flex flex-row justify-between gap-2.5"}>
<State <View className="text-lg font-bold">
state={purchaseOrderVO.state} {purchaseOrderVO?.orderDealer?.shortName || "-"}
stateMap={purchaseOrder.stateMap} {purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}
/>
</View>
<View className="text-neutral-darker text-sm font-medium">
{purchaseOrderVO?.orderVehicle.origin || "-"} {" "}
{purchaseOrderVO?.orderVehicle.destination || "-"}
</View>
{/* 展示产地负责人*/}
<View className="flex flex-row items-center gap-2.5">
<View className="flex-shrink-0 text-base font-bold text-gray-900">
</View>
<View
className={`flex h-12 w-full items-center rounded-lg border-2 border-gray-300 bg-gray-50 px-3`}
>
<Input
type="text"
disabled={
role === "boss" ||
(role === "reviewer" &&
purchaseOrderVO.state !== "WAITING_AUDIT")
}
placeholder="请输入产地负责人姓名"
value={originPrincipal || purchaseOrderVO.createdByName}
onChange={(value) => setOriginPrincipal(value)}
onBlur={() => {
// 更新采购订单中的产地负责人
setPurchaseOrderVO((prev) => ({
...prev!,
originPrincipal: originPrincipal,
}));
}}
className="w-full bg-transparent"
/>
</View>
</View>
</View>
{/* 循环渲染各部分内容 */}
{Object.keys(sections).map((sectionKey) => {
const section = sections[sectionKey];
const isCollapsed = collapsedSections[sectionKey];
if (!dealerVO?.enableCompanyRebate && sectionKey === "taxSubsidy") {
return null;
}
if (!dealerVO?.enableAccrualTax && sectionKey === "taxProvision") {
return null;
}
if (!dealerVO?.enableShare && sectionKey === "costDifference") {
return null;
}
return (
<View className="overflow-hidden bg-white" key={sectionKey}>
<View
className={`z-10 flex items-center justify-between p-4 shadow-sm ${section.containerClass}`}
onClick={() => toggleSection(sectionKey)}
>
<View className="text-base font-bold text-gray-800">
{section.title}
</View> </View>
<View className="text-neutral-darker text-lg">
{isCollapsed ? "▲" : "▼"} <State
state={purchaseOrderVO.state}
stateMap={purchaseOrder.stateMap}
/>
</View>
<View className="text-neutral-darker text-sm font-medium">
{purchaseOrderVO?.orderVehicle.origin || "-"} {" "}
{purchaseOrderVO?.orderVehicle.destination || "-"}
</View>
{/* 展示产地负责人*/}
<View className="flex flex-row items-center gap-2.5">
<View className="flex-shrink-0 text-base font-bold text-gray-900">
</View>
<View
className={`flex h-10 flex-1 items-center rounded-md border-4 border-gray-300`}
>
<Input
type="text"
disabled={
role === "boss" ||
(role === "reviewer" &&
purchaseOrderVO.state !== "WAITING_AUDIT")
}
placeholder="请输入产地负责人姓名"
value={originPrincipal || purchaseOrderVO.createdByName}
onChange={(value) => setOriginPrincipal(value)}
onBlur={() => {
// 更新采购订单中的产地负责人
setPurchaseOrderVO((prev) => ({
...prev!,
originPrincipal: originPrincipal,
}));
}}
className="w-full bg-transparent"
/>
</View> </View>
</View> </View>
{!isCollapsed && ( </View>
<View className={section.contentClass}>
<section.component {/* 循环渲染各部分内容 */}
dealerVO={dealerVO} {Object.keys(sections).map((sectionKey) => {
purchaseOrderVO={purchaseOrderVO} const section = sections[sectionKey];
onChange={setPurchaseOrderVO}
// readOnly={ if (!dealerVO?.enableCompanyRebate && sectionKey === "taxSubsidy") {
// role === "boss" || return null;
// (role === "reviewer" && }
// purchaseOrderVO.state !== "WAITING_AUDIT")
// } if (!dealerVO?.enableAccrualTax && sectionKey === "taxProvision") {
/> return null;
}
if (!dealerVO?.enableShare && sectionKey === "costDifference") {
return null;
}
return (
<>
<View className="text-sm font-bold">{section.title}</View>
<View
className={`overflow-x-auto rounded-md rounded-b-lg bg-white p-2.5 shadow-sm`}
>
<section.component
dealerVO={dealerVO}
purchaseOrderVO={purchaseOrderVO}
onChange={setPurchaseOrderVO}
/>
</View>
</>
);
})}
{/* 个人利润 */}
<View
className={
"flex flex-row items-center rounded-md border border-[#FFBB96] p-4"
}
style={{
background: "linear-gradient(100deg, #FFF2E8 25%, #FFE7D9 75%)",
}}
>
<View className={"flex-1 text-center"}></View>
{personalProfit > 0 ? (
<View
className={"text-primary flex-1 text-left text-2xl font-bold"}
>
{personalProfit || "-"}
</View>
) : (
<View
className={"flex-1 text-left text-2xl font-bold text-red-500"}
>
{personalProfit || "-"}
</View> </View>
)} )}
</View> </View>
); </View>
})}
{/* 个人利润 */}
<View
className={
"m-2.5 flex flex-row items-center rounded-md border border-[#FFBB96] p-4"
}
style={{
background: "linear-gradient(100deg, #FFF2E8 25%, #FFE7D9 75%)",
}}
>
<View className={"flex-1 text-center"}></View>
{personalProfit > 0 ? (
<View className={"text-primary flex-1 text-left text-2xl font-bold"}>
{personalProfit || "-"}
</View>
) : (
<View className={"flex-1 text-left text-2xl font-bold text-red-500"}>
{personalProfit || "-"}
</View>
)}
</View> </View>
{/* 按钮操作 */} {/* 按钮操作 */}
@ -460,25 +474,15 @@ export default hocAuth(function Page(props: CommonComponent) {
<View className="flex justify-between gap-2 border-t border-gray-200 p-2.5"> <View className="flex justify-between gap-2 border-t border-gray-200 p-2.5">
{purchaseOrderVO.state === "WAITING_AUDIT" && ( {purchaseOrderVO.state === "WAITING_AUDIT" && (
<> <>
<View className={"flex-1"}>
<PurchaseOrderRejectApprove
purchaseOrderVO={purchaseOrderVO}
size={"xlarge"}
onFinish={() => {
// 返回首页
Taro.redirectTo({ url: "/pages/purchase/reviewer/list" });
}}
/>
</View>
<View className={"flex-1"}> <View className={"flex-1"}>
<Button <Button
block block
type={"default"} type={"default"}
size={"xlarge"} size={"xlarge"}
className="bg-gray-200 text-gray-700" className="bg-gray-200 text-gray-700"
onClick={handleSave} onClick={() => setMoreActionVisible(true)}
> >
</Button> </Button>
</View> </View>
<View className={"flex-1"}> <View className={"flex-1"}>
@ -489,7 +493,7 @@ export default hocAuth(function Page(props: CommonComponent) {
className="bg-primary text-white" className="bg-primary text-white"
onClick={handleSubmit} onClick={handleSubmit}
> >
</Button> </Button>
</View> </View>
</> </>
@ -531,6 +535,86 @@ export default hocAuth(function Page(props: CommonComponent) {
onCancel={() => setSubmitDialogVisible(false)} onCancel={() => setSubmitDialogVisible(false)}
onConfirm={confirmSubmit} onConfirm={confirmSubmit}
/> />
</View>
{/* 更多操作ActionSheet */}
<ActionSheet
title="更多操作"
visible={moreActionVisible}
options={[
{
name: "暂存",
},
{
name: "驳回",
description: "此采购单将退回到产地录入员",
danger: true,
},
]}
onCancel={() => setMoreActionVisible(false)}
onSelect={(item) => {
setMoreActionVisible(false);
if (item.name === "暂存") {
// 暂存操作
handleSave();
} else if (item.name === "驳回") {
// 驳回操作
handleReject();
}
}}
cancelText="取消"
/>
{/* 驳回原因弹窗 */}
<Popup
visible={rejectVisible}
position="bottom"
onClose={() => {
setRejectVisible(false);
setRejectReason("");
}}
closeable
destroyOnClose
title={"驳回"}
description={"此采购单将退回到产地录入员"}
>
<View className="p-4">
<View className="mb-4 text-lg font-bold"></View>
<TextArea
value={rejectReason}
onChange={(value) => setRejectReason(value)}
placeholder="请输入驳回原因"
rows={4}
maxLength={200}
showCount
/>
<View className="mt-4 flex justify-between gap-2">
<View className="flex-1">
<Button
size={"large"}
block
type="default"
onClick={() => {
setRejectVisible(false);
setRejectReason("");
}}
>
</Button>
</View>
<View className="flex-1">
<Button
size={"large"}
block
type="primary"
onClick={confirmReject}
>
</Button>
</View>
</View>
</View>
<SafeArea position="bottom" />
</Popup>
</>
); );
}); });

View File

@ -2392,6 +2392,8 @@ declare namespace BusinessAPI {
| "FIXED_COST" | "FIXED_COST"
| "WORKER_ADVANCE" | "WORKER_ADVANCE"
| "PRODUCTION_ADVANCE"; | "PRODUCTION_ADVANCE";
/** 是否需要填写数量和单价 */
requireQuantityAndPrice: boolean;
}; };
type OrderDealer = { type OrderDealer = {