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 CostDifferenceSection } from "./section/CostDifferenceSection";
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,
);
console.log("existingItem", existingItem, value)
return {
orderCostId: existingItem
? existingItem.orderCostId
@ -68,6 +66,9 @@ export default forwardRef<OrderCostRef, IOrderCostProps>(
payerType: existingItem ? existingItem.payerType : undefined,
principal: existingItem ? existingItem.principal : "",
costType: item.costType,
requireQuantityAndPrice: existingItem
? existingItem.requireQuantityAndPrice
: false,
};
}) || [];
@ -91,6 +92,7 @@ export default forwardRef<OrderCostRef, IOrderCostProps>(
payerType: item.payerType,
principal: item.principal,
costType: item.costType,
requireQuantityAndPrice: item.requireQuantityAndPrice,
};
}) || []),
]);
@ -421,7 +423,7 @@ export default forwardRef<OrderCostRef, IOrderCostProps>(
};
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="text-sm font-bold"></View>
<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"
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 ? (
<Button
size={"large"}
@ -667,6 +670,7 @@ export default forwardRef<OrderVehicleRef, IOrderVehicleProps>(
<View className={"flex flex-row gap-2.5"}>
<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"}`}
>
<DealerPicker

View File

@ -471,7 +471,7 @@ export default function BasicInfoSection(props: {
</View>
{readOnly ? (
<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={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}}
@ -507,7 +507,7 @@ export default function BasicInfoSection(props: {
</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={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}}
@ -549,7 +549,7 @@ export default function BasicInfoSection(props: {
</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={{
background: "linear-gradient(101deg, #F8FAFF 25%, #F0F5FF 74%)",
}}

View File

@ -211,10 +211,11 @@ export default function LaborInfoSection(props: {
})}
</View>
</View>
<View className="grid grid-cols-1 gap-2.5">
<View className={"text-sm font-bold"}></View>
{/* 产地垫付 */}
{productionAdvanceCosts.map((item, index) => {
<View className="flex flex-col gap-2.5">
<View className={"text-sm font-bold"}></View>
{/* 工头垫付 */}
{workerAdvanceCosts.map((item, index) => {
// 初始化编辑值,包括费用承担方和工头姓名
initEditValues(
item.orderCostId,
@ -261,10 +262,11 @@ export default function LaborInfoSection(props: {
);
})}
</View>
<View className="grid grid-cols-1 gap-2.5">
<View className={"text-sm font-bold"}></View>
{/* 工头垫付 */}
{workerAdvanceCosts.map((item, index) => {
<View className="flex flex-col gap-2.5">
<View className={"text-sm font-bold"}></View>
{/* 产地垫付 */}
{productionAdvanceCosts.map((item, index) => {
// 初始化编辑值,包括费用承担方和工头姓名
initEditValues(
item.orderCostId,
@ -390,7 +392,7 @@ export default function LaborInfoSection(props: {
costItems
.filter((item) => item.costType === newCostData.costType)
.filter((item) => {
if (newCostData.costType === 'PRODUCTION_ADVANCE') {
if (newCostData.costType === "PRODUCTION_ADVANCE") {
// 检查该项目是否已经被选择
return !productionAdvanceCosts.some(
(hc) => hc.itemId === item.itemId,
@ -604,6 +606,8 @@ export default function LaborInfoSection(props: {
payerType: newCostData.payerType || "US",
costType: newCostData.costType!,
principal: "",
requireQuantityAndPrice:
newCostData.requireQuantityAndPrice,
};
// 更新purchaseOrderVO将新费用添加到orderCostList中
@ -648,395 +652,423 @@ export default function LaborInfoSection(props: {
{productionAdvanceCosts.map((item) => {
// 获取费用项目的 requireQuantityAndPrice 属性
const costItem = costItems.find((cost) => cost.itemId === item.itemId);
const requireQuantityAndPrice = costItem?.requireQuantityAndPrice ?? false;
const requireQuantityAndPrice =
costItem?.requireQuantityAndPrice ?? false;
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">
{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,
},
}));
<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">
{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() ||
""
}
}}
/>
<View className="mr-2"></View>
</View>
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="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,
}));
}}
/>
<View className="mr-2"></View>
>
</Button>
</View>
</>
) : (
<>
<View className="text-neutral-darkest text-sm font-medium">
<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="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="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,
}));
}}
/>
<View className="mr-2"></View>
>
</Button>
</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>
)})}
<SafeArea position="bottom" />
</Popup>
);
})}
{/* 工头垫付 */}
{workerAdvanceCosts.map((item) => {
// 获取费用项目的 requireQuantityAndPrice 属性
const costItem = costItems.find((cost) => cost.itemId === item.itemId);
const requireQuantityAndPrice = costItem?.requireQuantityAndPrice ?? false;
const requireQuantityAndPrice =
costItem?.requireQuantityAndPrice ?? false;
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">
{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,
},
}));
<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">
{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() ||
""
}
}}
/>
<View className="mr-2"></View>
</View>
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="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,
}));
}}
/>
<View className="mr-2"></View>
>
</Button>
</View>
</>
) : (
<>
<View className="text-neutral-darkest text-sm font-medium">
<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="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="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,
}));
}}
/>
<View className="mr-2"></View>
>
</Button>
</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>
)})}
<SafeArea position="bottom" />
</Popup>
);
})}
</>
);
}

View File

@ -606,123 +606,207 @@ export default function MaterialCostSection(props: {
</Popup>
{/* 辅料费编辑弹窗 */}
{packagingMaterials.map((item) => (
<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">
<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>
{packagingMaterials.map((item) => {
// 获取费用项目的 requireQuantityAndPrice 属性
const costItem = costItems.find((cost) => cost.itemId === item.itemId);
const requireQuantityAndPrice =
costItem?.requireQuantityAndPrice ?? false;
<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() ||
editValues[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>
<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],
}));
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">
{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>
// 关闭弹窗
setVisiblePopup((prev) => ({
...prev,
[item.orderCostId]: false,
}));
}}
>
</Button>
<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],
}));
// 关闭弹窗
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>
<SafeArea position="bottom" />
</Popup>
))}
<SafeArea position="bottom" />
</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 { CommonComponent, CostItem, SupplierVO } from "@/types/typings";
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 { useEffect, useRef, useState } from "react";
import {
@ -61,6 +68,26 @@ export default hocAuth(function Page(props: CommonComponent) {
const defaultStep = router.params.step as number;
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);
// 创建MelonFarmer组件的ref数组
const melonFarmerRefs = useRef<MelonFarmerRef[]>([]);
@ -622,29 +649,54 @@ export default hocAuth(function Page(props: CommonComponent) {
}),
]);
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 {
...prev!,
orderCostList: [
...(prev?.orderCostList?.filter((item) => {
return (
item.costType !== "WORKER_ADVANCE" &&
item.costType !== "PRODUCTION_ADVANCE"
item.costType !== "PRODUCTION_ADVANCE" &&
item.costType !== "PACKAGING_MATERIALS"
);
}) || []),
...(productVO.costItemVOList?.map((item) => {
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,
};
}) || []),
...(orderCostList || []),
],
};
});
@ -866,6 +918,15 @@ export default hocAuth(function Page(props: CommonComponent) {
</View>
<SafeArea position={"bottom"} />
</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 { View } from "@tarojs/components";
import purchaseOrder from "@/constant/purchaseOrder";
import {
Button,
Dialog,
Input,
SafeArea,
Toast,
} from "@nutui/nutui-react-taro";
import { ActionSheet, Button, Dialog, Input, Popup, SafeArea, TextArea, Toast } from "@nutui/nutui-react-taro";
import {
BasicInfoSection,
CompanyInfoSection,
@ -19,16 +13,16 @@ import {
CostSummarySection,
DealerInfoSection,
EmptyBoxInfoSection,
LaborInfoSection,
MarketPriceSection,
MaterialCostSection,
PackageInfoSection,
PackagingCostSection,
PurchaseOrderRejectApprove,
ProductionAdvanceSection,
RebateCalcSection,
State,
TaxProvisionSection,
TaxSubsidySection,
WorkerAdvanceSection
} from "@/components";
import buildUrl from "@/utils/buildUrl";
import { PurchaseOrderCalculator } from "@/utils/PurchaseOrderCalculator";
@ -38,113 +32,86 @@ const sections = {
marketPrice: {
component: MarketPriceSection,
title: "市场报价",
containerClass: "border-l-8 border-l-lime-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 我方入账公司
supplierInfo: {
component: CompanyInfoSection,
title: "我方入账公司",
containerClass: "border-l-8 border-l-blue-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 下游经销商信息
dealerInfo: {
component: DealerInfoSection,
title: "下游经销商",
containerClass: "border-l-8 border-l-green-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 基础信息
basicInfo: {
component: BasicInfoSection,
title: "基础信息",
containerClass: "border-l-8 border-l-yellow-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// // 瓜农信息
// farmerInfo: {
// component: SupplierInfoSection,
// title: "瓜农信息",
// containerClass: "border-l-8 border-l-purple-500",
// contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
// },
// // 采购成本
// purchaseCostInfo: {
// component: PurchaseCostInfoSection,
// title: "采购成本",
// containerClass: "border-l-8 border-l-red-500",
// contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
// },
// 包装纸箱费
packageInfo: {
component: PackageInfoSection,
title: "包装纸箱费复核",
containerClass: "border-l-8 border-l-indigo-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 空箱费用
emptyBoxInfo: {
component: EmptyBoxInfoSection,
title: "空箱费用复核",
containerClass: "border-l-8 border-l-pink-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 用工信息
laborInfo: {
component: LaborInfoSection,
title: "人工费复核",
containerClass: "border-l-8 border-l-teal-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
// 工头垫付费用复核
foremanAdvance: {
component: WorkerAdvanceSection,
title: "工头垫付费用复核",
},
// 产地垫付费用复核
originAdvance: {
component: ProductionAdvanceSection,
title: "产地垫付费用复核",
},
// 辅料费用复核
materialCost: {
component: MaterialCostSection,
title: "辅料费复核",
containerClass: "border-l-8 border-l-orange-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 其他费用复核
otherCost: {
component: PackagingCostSection,
title: "其他费用复核",
containerClass: "border-l-8 border-l-orange-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 成本合计
costSummary: {
component: CostSummarySection,
title: "成本合计",
containerClass: "border-l-8 border-l-cyan-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 个人返点复核
rebateCalc: {
component: RebateCalcSection,
title: "个人返点复核",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 公司返点复核
taxSubsidy: {
component: TaxSubsidySection,
title: "公司返点复核",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 计提税金复核
taxProvision: {
component: TaxProvisionSection,
title: "计提税金复核",
containerClass: "border-l-8 border-l-amber-500",
contentClass: "p-4 bg-white rounded-b-lg shadow-sm overflow-x-auto",
},
// 调诚信志远分红
costDifference: {
component: CostDifferenceSection,
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] =
useState<BusinessAPI.PurchaseOrderVO>();
const [originPrincipal, setOriginPrincipal] = useState("");
// 驳回原因弹窗相关状态
const [rejectVisible, setRejectVisible] = useState(false);
const [rejectReason, setRejectReason] = useState("");
const [collapsedSections, setCollapsedSections] = useState<
Record<string, boolean>
>(Object.keys(sections).reduce((acc, key) => ({ ...acc, [key]: false }), {}));
// 驳回操作
const handleReject = () => {
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状态
const [saveDialogVisible, setSaveDialogVisible] = useState(false);
const [submitDialogVisible, setSubmitDialogVisible] = useState(false);
// 控制更多操作的ActionSheet显示状态
const [moreActionVisible, setMoreActionVisible] = useState(false);
const [dealerVO, setDealerVO] = useState<BusinessAPI.DealerVO>();
const initDealer = async (dealerId: BusinessAPI.DealerVO["dealerId"]) => {
@ -180,13 +206,6 @@ export default hocAuth(function Page(props: CommonComponent) {
setDealerVO(data.data);
};
const toggleSection = (section: string) => {
setCollapsedSections((prev) => ({
...prev,
[section]: !prev[section],
}));
};
// 暂存操作
const handleSave = () => {
setSaveDialogVisible(true);
@ -335,124 +354,119 @@ export default hocAuth(function Page(props: CommonComponent) {
const personalProfit = calculator.getPersonalProfit();
return (
<View className={"flex flex-col gap-2.5"} id={"purchase-order-audit"}>
{/* 顶部导航 */}
<View className="flex flex-col gap-2.5 bg-white p-2.5">
<View className={"flex flex-row justify-between gap-2.5"}>
<View className="text-lg font-bold">
{purchaseOrderVO?.orderDealer?.shortName || "-"}
{purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}
</View>
<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-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
className={"overflow-x-hidden overflow-y-auto bg-[#D1D5DB]"}
id={"purchase-order-audit"}
>
<View className={"flex flex-col gap-2.5 p-2.5"}>
{/* 顶部导航 */}
<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"}>
<View className="text-lg font-bold">
{purchaseOrderVO?.orderDealer?.shortName || "-"}
{purchaseOrderVO?.orderVehicle?.vehicleNo || "-"}
</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>
{!isCollapsed && (
<View className={section.contentClass}>
<section.component
dealerVO={dealerVO}
purchaseOrderVO={purchaseOrderVO}
onChange={setPurchaseOrderVO}
// readOnly={
// role === "boss" ||
// (role === "reviewer" &&
// purchaseOrderVO.state !== "WAITING_AUDIT")
// }
/>
</View>
{/* 循环渲染各部分内容 */}
{Object.keys(sections).map((sectionKey) => {
const section = sections[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="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
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">
{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"}>
<Button
block
type={"default"}
size={"xlarge"}
className="bg-gray-200 text-gray-700"
onClick={handleSave}
onClick={() => setMoreActionVisible(true)}
>
</Button>
</View>
<View className={"flex-1"}>
@ -489,7 +493,7 @@ export default hocAuth(function Page(props: CommonComponent) {
className="bg-primary text-white"
onClick={handleSubmit}
>
</Button>
</View>
</>
@ -531,6 +535,86 @@ export default hocAuth(function Page(props: CommonComponent) {
onCancel={() => setSubmitDialogVisible(false)}
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"
| "WORKER_ADVANCE"
| "PRODUCTION_ADVANCE";
/** 是否需要填写数量和单价 */
requireQuantityAndPrice: boolean;
};
type OrderDealer = {