feat(expenses): 优化费用创建与计提功能
- 为 ExpenseCostCreate 和 ExpenseProvisionCreate 组件添加必填字段标识 - 使用 generateShortId 替代时间戳生成唯一 ID - 移除备注字段的非必要校验逻辑 - 更新客户选择字段的显示文案与交互样式 - 在 ExpenseProvisionCreate 中移除客户名称必填限制 - 引入 Text 组件支持星号标注必填项 - 修复表单提交按钮的禁用条件判断逻辑 - 集成全局 loading 状态管理 - 实现费用记录保存成功后的提示反馈 - 将 DatePicker 替换为 Calendar 组件用于日期选择 - 为 CostPicker 添加 EXPENSE_TYPE 类型筛选参数 - 在 CostPageQry 类型中新增 name 字段定义 - 优化图标组件使用方式,替换原有 class 样式写法
This commit is contained in:
parent
31ece8807a
commit
8562aed7d1
@ -7,10 +7,11 @@ import { business } from "@/services";
|
|||||||
interface ICostPickerProps {
|
interface ICostPickerProps {
|
||||||
onFinish: (costVO: BusinessAPI.CostVO) => void;
|
onFinish: (costVO: BusinessAPI.CostVO) => void;
|
||||||
trigger: React.ReactNode;
|
trigger: React.ReactNode;
|
||||||
|
params?: BusinessAPI.CostPageQry;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CostPicker(props: ICostPickerProps) {
|
export default function CostPicker(props: ICostPickerProps) {
|
||||||
const { onFinish, trigger } = props;
|
const { onFinish, trigger, params } = props;
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [costVO, setCostVO] = useState<BusinessAPI.CostVO>();
|
const [costVO, setCostVO] = useState<BusinessAPI.CostVO>();
|
||||||
@ -28,6 +29,7 @@ export default function CostPicker(props: ICostPickerProps) {
|
|||||||
costListQry: {
|
costListQry: {
|
||||||
name: value || undefined,
|
name: value || undefined,
|
||||||
status: true,
|
status: true,
|
||||||
|
...params,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -25,8 +25,8 @@ export default function ExpenseCostCard(props: IExpenseCostCardProps) {
|
|||||||
>
|
>
|
||||||
<View className="flex-1">
|
<View className="flex-1">
|
||||||
<View className="flex items-center">
|
<View className="flex items-center">
|
||||||
<View className="bg-primary bg-opacity-10 mr-2 flex h-8 w-8 items-center justify-center rounded-full">
|
<View className="bg-primary/10 mr-2 flex h-8 w-8 items-center justify-center rounded-full">
|
||||||
<i className="fas fa-receipt text-primary text-sm"></i>
|
<Icon name="receipt" size={18} />
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
<View className="text-neutral-darkest text-sm font-medium">
|
<View className="text-neutral-darkest text-sm font-medium">
|
||||||
|
|||||||
@ -6,8 +6,9 @@ import {
|
|||||||
TextArea,
|
TextArea,
|
||||||
} from "@nutui/nutui-react-taro";
|
} from "@nutui/nutui-react-taro";
|
||||||
import { CostPicker, Icon } from "@/components";
|
import { CostPicker, Icon } from "@/components";
|
||||||
import { ScrollView, View } from "@tarojs/components";
|
import { ScrollView, Text, View } from "@tarojs/components";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { generateShortId } from "@/utils";
|
||||||
|
|
||||||
interface IExpenseCostCreateProps {
|
interface IExpenseCostCreateProps {
|
||||||
onFinish?: (expressCost: BusinessAPI.ExpenseCost) => void;
|
onFinish?: (expressCost: BusinessAPI.ExpenseCost) => void;
|
||||||
@ -32,7 +33,6 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
|||||||
const [formErrors, setFormErrors] = useState<{
|
const [formErrors, setFormErrors] = useState<{
|
||||||
costId?: boolean;
|
costId?: boolean;
|
||||||
expenseAmount?: boolean;
|
expenseAmount?: boolean;
|
||||||
remark?: boolean;
|
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
// 初始化表单数据
|
// 初始化表单数据
|
||||||
@ -55,7 +55,6 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
|||||||
costId: !expressCost?.costId,
|
costId: !expressCost?.costId,
|
||||||
expenseAmount:
|
expenseAmount:
|
||||||
!expressCost?.expenseAmount || expressCost.expenseAmount <= 0,
|
!expressCost?.expenseAmount || expressCost.expenseAmount <= 0,
|
||||||
remark: false, // 备注是可选的,不设为错误
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setFormErrors(errors);
|
setFormErrors(errors);
|
||||||
@ -75,7 +74,7 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
|||||||
...expressCost,
|
...expressCost,
|
||||||
expenseCostId: editMode
|
expenseCostId: editMode
|
||||||
? initialExpenseCost?.expenseCostId || expressCost.expenseCostId
|
? initialExpenseCost?.expenseCostId || expressCost.expenseCostId
|
||||||
: expressCost.expenseCostId || `EC_${Date.now()}`,
|
: expressCost.expenseCostId || generateShortId(),
|
||||||
};
|
};
|
||||||
|
|
||||||
onFinish?.(finalExpenseCost);
|
onFinish?.(finalExpenseCost);
|
||||||
@ -123,13 +122,16 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
|||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<View className="block text-sm font-normal text-[#000000]">
|
<View className="block text-sm font-normal text-[#000000]">
|
||||||
费用类型
|
费用类型 <Text className="text-red-500">*</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
id={"target2"}
|
id={"target2"}
|
||||||
className={`border-neutral-base flex flex-row items-center rounded-md ${formErrors.costId ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
|
className={`border-neutral-base flex flex-row items-center rounded-md ${formErrors.costId ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
|
||||||
>
|
>
|
||||||
<CostPicker
|
<CostPicker
|
||||||
|
params={{
|
||||||
|
type: "EXPENSE_TYPE",
|
||||||
|
}}
|
||||||
onFinish={(costVO) => {
|
onFinish={(costVO) => {
|
||||||
setExpressCost((prev) => {
|
setExpressCost((prev) => {
|
||||||
return {
|
return {
|
||||||
@ -167,7 +169,7 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<View className={"text-neutral-darkest text-sm font-medium"}>
|
<View className={"text-neutral-darkest text-sm font-medium"}>
|
||||||
金额
|
金额 <Text className="text-red-500">*</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`border-neutral-base flex flex-row items-center rounded-md ${formErrors.expenseAmount ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
|
className={`border-neutral-base flex flex-row items-center rounded-md ${formErrors.expenseAmount ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
|
||||||
|
|||||||
@ -6,8 +6,9 @@ import {
|
|||||||
TextArea,
|
TextArea,
|
||||||
} from "@nutui/nutui-react-taro";
|
} from "@nutui/nutui-react-taro";
|
||||||
import { DealerPicker, Icon } from "@/components";
|
import { DealerPicker, Icon } from "@/components";
|
||||||
import { ScrollView, View } from "@tarojs/components";
|
import { ScrollView, Text, View } from "@tarojs/components";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { generateShortId } from "@/utils";
|
||||||
|
|
||||||
interface IExpenseProvisionCreateProps {
|
interface IExpenseProvisionCreateProps {
|
||||||
onFinish?: (expenseProvision: BusinessAPI.ExpenseProvision) => void;
|
onFinish?: (expenseProvision: BusinessAPI.ExpenseProvision) => void;
|
||||||
@ -33,9 +34,7 @@ export default function ExpenseProvisionCreate(
|
|||||||
|
|
||||||
// 表单错误状态
|
// 表单错误状态
|
||||||
const [formErrors, setFormErrors] = useState<{
|
const [formErrors, setFormErrors] = useState<{
|
||||||
dealerName?: boolean;
|
|
||||||
provisionAmount?: boolean;
|
provisionAmount?: boolean;
|
||||||
remark?: boolean;
|
|
||||||
}>({});
|
}>({});
|
||||||
|
|
||||||
// 初始化表单数据
|
// 初始化表单数据
|
||||||
@ -55,11 +54,9 @@ export default function ExpenseProvisionCreate(
|
|||||||
|
|
||||||
const validateForm = () => {
|
const validateForm = () => {
|
||||||
const errors = {
|
const errors = {
|
||||||
dealerName: !expenseProvision?.dealerName,
|
|
||||||
provisionAmount:
|
provisionAmount:
|
||||||
!expenseProvision?.provisionAmount ||
|
!expenseProvision?.provisionAmount ||
|
||||||
expenseProvision.provisionAmount <= 0,
|
expenseProvision.provisionAmount <= 0,
|
||||||
remark: false, // 备注是可选的,不设为错误
|
|
||||||
};
|
};
|
||||||
|
|
||||||
setFormErrors(errors);
|
setFormErrors(errors);
|
||||||
@ -80,7 +77,7 @@ export default function ExpenseProvisionCreate(
|
|||||||
expenseProvisionId: editMode
|
expenseProvisionId: editMode
|
||||||
? initialExpenseProvision?.expenseProvisionId ||
|
? initialExpenseProvision?.expenseProvisionId ||
|
||||||
expenseProvision.expenseProvisionId
|
expenseProvision.expenseProvisionId
|
||||||
: expenseProvision.expenseProvisionId || `EP_${Date.now()}`,
|
: expenseProvision.expenseProvisionId || generateShortId(),
|
||||||
};
|
};
|
||||||
|
|
||||||
onFinish?.(finalExpenseProvision);
|
onFinish?.(finalExpenseProvision);
|
||||||
@ -117,22 +114,22 @@ export default function ExpenseProvisionCreate(
|
|||||||
{editMode ? (
|
{editMode ? (
|
||||||
<>
|
<>
|
||||||
<View className="block text-sm font-normal text-[#000000]">
|
<View className="block text-sm font-normal text-[#000000]">
|
||||||
客户名称
|
客户选择(可选)
|
||||||
</View>
|
</View>
|
||||||
<View className="rounded-md bg-gray-100 p-3">
|
<View className="rounded-md bg-gray-100 p-3">
|
||||||
<View className="text-base font-medium text-gray-800">
|
<View className="text-base font-medium text-gray-800">
|
||||||
{expenseProvision?.dealerName || "未知客户"}
|
{expenseProvision?.dealerName || "未选客户"}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<View className="block text-sm font-normal text-[#000000]">
|
<View className="block text-sm font-normal text-[#000000]">
|
||||||
客户名称
|
客户名称(可选)
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
id={"target2"}
|
id={"target2"}
|
||||||
className={`border-neutral-base flex flex-row items-center rounded-md ${formErrors.dealerName ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
|
className={`border-neutral-base flex flex-row items-center rounded-md border-4 border-gray-300`}
|
||||||
>
|
>
|
||||||
<DealerPicker
|
<DealerPicker
|
||||||
onFinish={(dealerVO) => {
|
onFinish={(dealerVO) => {
|
||||||
@ -142,11 +139,6 @@ export default function ExpenseProvisionCreate(
|
|||||||
dealerName: dealerVO.shortName,
|
dealerName: dealerVO.shortName,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
setFormErrors((prev) => ({
|
|
||||||
...prev,
|
|
||||||
dealerId: false,
|
|
||||||
}));
|
|
||||||
}}
|
}}
|
||||||
trigger={
|
trigger={
|
||||||
<View
|
<View
|
||||||
@ -162,14 +154,11 @@ export default function ExpenseProvisionCreate(
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
{formErrors.dealerName && (
|
|
||||||
<View className="mt-1 text-xs text-red-500">请选择客户</View>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<View className={"text-neutral-darkest text-sm font-medium"}>
|
<View className={"text-neutral-darkest text-sm font-medium"}>
|
||||||
计提金额
|
计提金额 <Text className="text-red-500">*</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
className={`border-neutral-base flex flex-row items-center rounded-md ${formErrors.provisionAmount ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
|
className={`border-neutral-base flex flex-row items-center rounded-md ${formErrors.provisionAmount ? "border-4 border-red-500" : "border-4 border-gray-300"}`}
|
||||||
@ -261,7 +250,6 @@ export default function ExpenseProvisionCreate(
|
|||||||
block
|
block
|
||||||
type="primary"
|
type="primary"
|
||||||
disabled={
|
disabled={
|
||||||
!expenseProvision?.dealerName ||
|
|
||||||
!expenseProvision?.provisionAmount ||
|
!expenseProvision?.provisionAmount ||
|
||||||
expenseProvision.provisionAmount <= 0 ||
|
expenseProvision.provisionAmount <= 0 ||
|
||||||
Object.values(formErrors).some((item) => item)
|
Object.values(formErrors).some((item) => item)
|
||||||
|
|||||||
@ -2,12 +2,12 @@ import { useShareAppMessage } from "@tarojs/taro";
|
|||||||
import hocAuth from "@/hocs/auth";
|
import hocAuth from "@/hocs/auth";
|
||||||
import { CommonComponent } from "@/types/typings";
|
import { CommonComponent } from "@/types/typings";
|
||||||
import { View } from "@tarojs/components";
|
import { View } from "@tarojs/components";
|
||||||
import { ExpenseProvisionList, Icon } from "@/components";
|
import { ExpenseCostList, ExpenseProvisionList, Icon } from "@/components";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Button, DatePicker, Dialog, SafeArea } from "@nutui/nutui-react-taro";
|
import { Button, Calendar, Dialog, SafeArea, Toast } from "@nutui/nutui-react-taro";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { business } from "@/services";
|
import { business } from "@/services";
|
||||||
import ExpenseCostList from "../../components/expenses/ExpenseCostList";
|
import { globalStore } from "@/store/global-store";
|
||||||
|
|
||||||
export default hocAuth(function Page(props: CommonComponent) {
|
export default hocAuth(function Page(props: CommonComponent) {
|
||||||
const { shareOptions } = props;
|
const { shareOptions } = props;
|
||||||
@ -17,7 +17,7 @@ export default hocAuth(function Page(props: CommonComponent) {
|
|||||||
);
|
);
|
||||||
const [expenseRecord, setExpenseRecord] =
|
const [expenseRecord, setExpenseRecord] =
|
||||||
useState<BusinessAPI.ExpenseRecordVO>();
|
useState<BusinessAPI.ExpenseRecordVO>();
|
||||||
const [loading, setLoading] = useState(true);
|
const { setLoading } = globalStore((state: any) => state);
|
||||||
|
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
@ -98,14 +98,21 @@ export default hocAuth(function Page(props: CommonComponent) {
|
|||||||
try {
|
try {
|
||||||
// 这里添加实际的保存逻辑
|
// 这里添加实际的保存逻辑
|
||||||
console.log("保存数据:", expenseRecord);
|
console.log("保存数据:", expenseRecord);
|
||||||
// await business.expenseRecord.save(expenseRecord);
|
const {
|
||||||
|
data: { success },
|
||||||
|
} = await business.expenseRecord.createExpenseRecord({
|
||||||
|
...expenseRecord,
|
||||||
|
recordDate: currentDate,
|
||||||
|
});
|
||||||
|
|
||||||
// 保存成功后更新原始数据
|
if (success) {
|
||||||
setOriginalData(JSON.parse(JSON.stringify(expenseRecord)));
|
// 保存成功后更新原始数据
|
||||||
setHasUnsavedChanges(false);
|
setOriginalData(JSON.parse(JSON.stringify(expenseRecord)));
|
||||||
lastSaveTime.current = Date.now();
|
setHasUnsavedChanges(false);
|
||||||
|
lastSaveTime.current = Date.now();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return success;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("保存数据失败:", error);
|
console.error("保存数据失败:", error);
|
||||||
return false;
|
return false;
|
||||||
@ -161,14 +168,6 @@ export default hocAuth(function Page(props: CommonComponent) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return (
|
|
||||||
<View className="flex items-center justify-center py-8">
|
|
||||||
<View className="text-gray-500">加载中...</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function onchange(expenseRecord: BusinessAPI.ExpenseRecordVO) {
|
function onchange(expenseRecord: BusinessAPI.ExpenseRecordVO) {
|
||||||
setExpenseRecord((prev) => {
|
setExpenseRecord((prev) => {
|
||||||
const totalProvision =
|
const totalProvision =
|
||||||
@ -291,7 +290,11 @@ export default hocAuth(function Page(props: CommonComponent) {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const saveSuccess = await saveCurrentData();
|
const saveSuccess = await saveCurrentData();
|
||||||
if (saveSuccess) {
|
if (saveSuccess) {
|
||||||
console.log("保存成功");
|
Toast.show("toast", {
|
||||||
|
icon: "success",
|
||||||
|
title: "提示",
|
||||||
|
content: "保存成功",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
disabled={!hasUnsavedChanges}
|
disabled={!hasUnsavedChanges}
|
||||||
@ -303,16 +306,13 @@ export default hocAuth(function Page(props: CommonComponent) {
|
|||||||
<SafeArea position="bottom" />
|
<SafeArea position="bottom" />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<DatePicker
|
<Calendar
|
||||||
title="日期选择"
|
|
||||||
type="date"
|
|
||||||
visible={show}
|
visible={show}
|
||||||
value={new Date(currentDate)}
|
defaultValue={dayjs(currentDate).format("YYYY-MM-DD")}
|
||||||
showChinese
|
startDate={"2025-01-01"}
|
||||||
onClose={() => setShow(false)}
|
onClose={() => setShow(false)}
|
||||||
threeDimensional={false}
|
onConfirm={(data) => {
|
||||||
onConfirm={(_, values) => {
|
const newDate = dayjs(data[3]).format("YYYY-MM-DD");
|
||||||
const newDate = dayjs(values.join("-")).format("YYYY-MM-DD");
|
|
||||||
|
|
||||||
// 如果没有未保存变更,直接切换日期
|
// 如果没有未保存变更,直接切换日期
|
||||||
if (!hasUnsavedChanges) {
|
if (!hasUnsavedChanges) {
|
||||||
@ -325,6 +325,28 @@ export default hocAuth(function Page(props: CommonComponent) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/*<DatePicker*/}
|
||||||
|
{/* title="日期选择"*/}
|
||||||
|
{/* type="date"*/}
|
||||||
|
{/* visible={show}*/}
|
||||||
|
{/* value={new Date(currentDate)}*/}
|
||||||
|
{/* showChinese*/}
|
||||||
|
{/* onClose={() => setShow(false)}*/}
|
||||||
|
{/* threeDimensional={false}*/}
|
||||||
|
{/* onConfirm={(_, values) => {*/}
|
||||||
|
{/* const newDate = dayjs(values.join("-")).format("YYYY-MM-DD");*/}
|
||||||
|
|
||||||
|
{/* // 如果没有未保存变更,直接切换日期*/}
|
||||||
|
{/* if (!hasUnsavedChanges) {*/}
|
||||||
|
{/* setCurrentDate(newDate);*/}
|
||||||
|
{/* } else {*/}
|
||||||
|
{/* // 有未保存变更时,保存待切换日期,显示确认对话框*/}
|
||||||
|
{/* setPendingDate(newDate);*/}
|
||||||
|
{/* setConfirmVisible(true);*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/* }}*/}
|
||||||
|
{/*/>*/}
|
||||||
|
|
||||||
{/* 日期切换确认对话框 */}
|
{/* 日期切换确认对话框 */}
|
||||||
<Dialog
|
<Dialog
|
||||||
visible={confirmVisible}
|
visible={confirmVisible}
|
||||||
|
|||||||
@ -1064,6 +1064,8 @@ declare namespace BusinessAPI {
|
|||||||
| "LOGISTICS_TYPE";
|
| "LOGISTICS_TYPE";
|
||||||
/** 费用归属:0_无归属;1_工头;2_产地;3_司机; */
|
/** 费用归属:0_无归属;1_工头;2_产地;3_司机; */
|
||||||
belong?: "NONE_TYPE" | "WORKER_TYPE" | "PRODUCTION_TYPE" | "DRIVER_TYPE";
|
belong?: "NONE_TYPE" | "WORKER_TYPE" | "PRODUCTION_TYPE" | "DRIVER_TYPE";
|
||||||
|
/** 费用名称 */
|
||||||
|
name?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type CostPageQry = {
|
type CostPageQry = {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user