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 {
|
||||
onFinish: (costVO: BusinessAPI.CostVO) => void;
|
||||
trigger: React.ReactNode;
|
||||
params?: BusinessAPI.CostPageQry;
|
||||
}
|
||||
|
||||
export default function CostPicker(props: ICostPickerProps) {
|
||||
const { onFinish, trigger } = props;
|
||||
const { onFinish, trigger, params } = props;
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [costVO, setCostVO] = useState<BusinessAPI.CostVO>();
|
||||
@ -28,6 +29,7 @@ export default function CostPicker(props: ICostPickerProps) {
|
||||
costListQry: {
|
||||
name: value || undefined,
|
||||
status: true,
|
||||
...params,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -25,8 +25,8 @@ export default function ExpenseCostCard(props: IExpenseCostCardProps) {
|
||||
>
|
||||
<View className="flex-1">
|
||||
<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">
|
||||
<i className="fas fa-receipt text-primary text-sm"></i>
|
||||
<View className="bg-primary/10 mr-2 flex h-8 w-8 items-center justify-center rounded-full">
|
||||
<Icon name="receipt" size={18} />
|
||||
</View>
|
||||
<View>
|
||||
<View className="text-neutral-darkest text-sm font-medium">
|
||||
|
||||
@ -6,8 +6,9 @@ import {
|
||||
TextArea,
|
||||
} from "@nutui/nutui-react-taro";
|
||||
import { CostPicker, Icon } from "@/components";
|
||||
import { ScrollView, View } from "@tarojs/components";
|
||||
import { ScrollView, Text, View } from "@tarojs/components";
|
||||
import { useEffect, useState } from "react";
|
||||
import { generateShortId } from "@/utils";
|
||||
|
||||
interface IExpenseCostCreateProps {
|
||||
onFinish?: (expressCost: BusinessAPI.ExpenseCost) => void;
|
||||
@ -32,7 +33,6 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
||||
const [formErrors, setFormErrors] = useState<{
|
||||
costId?: boolean;
|
||||
expenseAmount?: boolean;
|
||||
remark?: boolean;
|
||||
}>({});
|
||||
|
||||
// 初始化表单数据
|
||||
@ -55,7 +55,6 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
||||
costId: !expressCost?.costId,
|
||||
expenseAmount:
|
||||
!expressCost?.expenseAmount || expressCost.expenseAmount <= 0,
|
||||
remark: false, // 备注是可选的,不设为错误
|
||||
};
|
||||
|
||||
setFormErrors(errors);
|
||||
@ -75,7 +74,7 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
||||
...expressCost,
|
||||
expenseCostId: editMode
|
||||
? initialExpenseCost?.expenseCostId || expressCost.expenseCostId
|
||||
: expressCost.expenseCostId || `EC_${Date.now()}`,
|
||||
: expressCost.expenseCostId || generateShortId(),
|
||||
};
|
||||
|
||||
onFinish?.(finalExpenseCost);
|
||||
@ -123,13 +122,16 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
||||
) : (
|
||||
<>
|
||||
<View className="block text-sm font-normal text-[#000000]">
|
||||
费用类型
|
||||
费用类型 <Text className="text-red-500">*</Text>
|
||||
</View>
|
||||
<View
|
||||
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"}`}
|
||||
>
|
||||
<CostPicker
|
||||
params={{
|
||||
type: "EXPENSE_TYPE",
|
||||
}}
|
||||
onFinish={(costVO) => {
|
||||
setExpressCost((prev) => {
|
||||
return {
|
||||
@ -167,7 +169,7 @@ export default function ExpenseCostCreate(props: IExpenseCostCreateProps) {
|
||||
)}
|
||||
|
||||
<View className={"text-neutral-darkest text-sm font-medium"}>
|
||||
金额
|
||||
金额 <Text className="text-red-500">*</Text>
|
||||
</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"}`}
|
||||
|
||||
@ -6,8 +6,9 @@ import {
|
||||
TextArea,
|
||||
} from "@nutui/nutui-react-taro";
|
||||
import { DealerPicker, Icon } from "@/components";
|
||||
import { ScrollView, View } from "@tarojs/components";
|
||||
import { ScrollView, Text, View } from "@tarojs/components";
|
||||
import { useEffect, useState } from "react";
|
||||
import { generateShortId } from "@/utils";
|
||||
|
||||
interface IExpenseProvisionCreateProps {
|
||||
onFinish?: (expenseProvision: BusinessAPI.ExpenseProvision) => void;
|
||||
@ -33,9 +34,7 @@ export default function ExpenseProvisionCreate(
|
||||
|
||||
// 表单错误状态
|
||||
const [formErrors, setFormErrors] = useState<{
|
||||
dealerName?: boolean;
|
||||
provisionAmount?: boolean;
|
||||
remark?: boolean;
|
||||
}>({});
|
||||
|
||||
// 初始化表单数据
|
||||
@ -55,11 +54,9 @@ export default function ExpenseProvisionCreate(
|
||||
|
||||
const validateForm = () => {
|
||||
const errors = {
|
||||
dealerName: !expenseProvision?.dealerName,
|
||||
provisionAmount:
|
||||
!expenseProvision?.provisionAmount ||
|
||||
expenseProvision.provisionAmount <= 0,
|
||||
remark: false, // 备注是可选的,不设为错误
|
||||
};
|
||||
|
||||
setFormErrors(errors);
|
||||
@ -80,7 +77,7 @@ export default function ExpenseProvisionCreate(
|
||||
expenseProvisionId: editMode
|
||||
? initialExpenseProvision?.expenseProvisionId ||
|
||||
expenseProvision.expenseProvisionId
|
||||
: expenseProvision.expenseProvisionId || `EP_${Date.now()}`,
|
||||
: expenseProvision.expenseProvisionId || generateShortId(),
|
||||
};
|
||||
|
||||
onFinish?.(finalExpenseProvision);
|
||||
@ -117,22 +114,22 @@ export default function ExpenseProvisionCreate(
|
||||
{editMode ? (
|
||||
<>
|
||||
<View className="block text-sm font-normal text-[#000000]">
|
||||
客户名称
|
||||
客户选择(可选)
|
||||
</View>
|
||||
<View className="rounded-md bg-gray-100 p-3">
|
||||
<View className="text-base font-medium text-gray-800">
|
||||
{expenseProvision?.dealerName || "未知客户"}
|
||||
{expenseProvision?.dealerName || "未选客户"}
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<View className="block text-sm font-normal text-[#000000]">
|
||||
客户名称
|
||||
客户名称(可选)
|
||||
</View>
|
||||
<View
|
||||
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
|
||||
onFinish={(dealerVO) => {
|
||||
@ -142,11 +139,6 @@ export default function ExpenseProvisionCreate(
|
||||
dealerName: dealerVO.shortName,
|
||||
};
|
||||
});
|
||||
|
||||
setFormErrors((prev) => ({
|
||||
...prev,
|
||||
dealerId: false,
|
||||
}));
|
||||
}}
|
||||
trigger={
|
||||
<View
|
||||
@ -162,14 +154,11 @@ export default function ExpenseProvisionCreate(
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
{formErrors.dealerName && (
|
||||
<View className="mt-1 text-xs text-red-500">请选择客户</View>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
<View className={"text-neutral-darkest text-sm font-medium"}>
|
||||
计提金额
|
||||
计提金额 <Text className="text-red-500">*</Text>
|
||||
</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"}`}
|
||||
@ -261,7 +250,6 @@ export default function ExpenseProvisionCreate(
|
||||
block
|
||||
type="primary"
|
||||
disabled={
|
||||
!expenseProvision?.dealerName ||
|
||||
!expenseProvision?.provisionAmount ||
|
||||
expenseProvision.provisionAmount <= 0 ||
|
||||
Object.values(formErrors).some((item) => item)
|
||||
|
||||
@ -2,12 +2,12 @@ import { useShareAppMessage } from "@tarojs/taro";
|
||||
import hocAuth from "@/hocs/auth";
|
||||
import { CommonComponent } from "@/types/typings";
|
||||
import { View } from "@tarojs/components";
|
||||
import { ExpenseProvisionList, Icon } from "@/components";
|
||||
import { ExpenseCostList, ExpenseProvisionList, Icon } from "@/components";
|
||||
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 { business } from "@/services";
|
||||
import ExpenseCostList from "../../components/expenses/ExpenseCostList";
|
||||
import { globalStore } from "@/store/global-store";
|
||||
|
||||
export default hocAuth(function Page(props: CommonComponent) {
|
||||
const { shareOptions } = props;
|
||||
@ -17,7 +17,7 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
);
|
||||
const [expenseRecord, setExpenseRecord] =
|
||||
useState<BusinessAPI.ExpenseRecordVO>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { setLoading } = globalStore((state: any) => state);
|
||||
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
@ -98,14 +98,21 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
try {
|
||||
// 这里添加实际的保存逻辑
|
||||
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);
|
||||
lastSaveTime.current = Date.now();
|
||||
}
|
||||
|
||||
return true;
|
||||
return success;
|
||||
} catch (error) {
|
||||
console.error("保存数据失败:", error);
|
||||
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) {
|
||||
setExpenseRecord((prev) => {
|
||||
const totalProvision =
|
||||
@ -291,7 +290,11 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
onClick={async () => {
|
||||
const saveSuccess = await saveCurrentData();
|
||||
if (saveSuccess) {
|
||||
console.log("保存成功");
|
||||
Toast.show("toast", {
|
||||
icon: "success",
|
||||
title: "提示",
|
||||
content: "保存成功",
|
||||
});
|
||||
}
|
||||
}}
|
||||
disabled={!hasUnsavedChanges}
|
||||
@ -303,16 +306,13 @@ export default hocAuth(function Page(props: CommonComponent) {
|
||||
<SafeArea position="bottom" />
|
||||
</View>
|
||||
|
||||
<DatePicker
|
||||
title="日期选择"
|
||||
type="date"
|
||||
<Calendar
|
||||
visible={show}
|
||||
value={new Date(currentDate)}
|
||||
showChinese
|
||||
defaultValue={dayjs(currentDate).format("YYYY-MM-DD")}
|
||||
startDate={"2025-01-01"}
|
||||
onClose={() => setShow(false)}
|
||||
threeDimensional={false}
|
||||
onConfirm={(_, values) => {
|
||||
const newDate = dayjs(values.join("-")).format("YYYY-MM-DD");
|
||||
onConfirm={(data) => {
|
||||
const newDate = dayjs(data[3]).format("YYYY-MM-DD");
|
||||
|
||||
// 如果没有未保存变更,直接切换日期
|
||||
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
|
||||
visible={confirmVisible}
|
||||
|
||||
@ -1064,6 +1064,8 @@ declare namespace BusinessAPI {
|
||||
| "LOGISTICS_TYPE";
|
||||
/** 费用归属:0_无归属;1_工头;2_产地;3_司机; */
|
||||
belong?: "NONE_TYPE" | "WORKER_TYPE" | "PRODUCTION_TYPE" | "DRIVER_TYPE";
|
||||
/** 费用名称 */
|
||||
name?: string;
|
||||
};
|
||||
|
||||
type CostPageQry = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user