7. 【Vue实战--孢子记账--Web 版开发】-- 收支分类设置

server/2025/3/26 1:37:14/

本篇文章我们一起来实现收支分类功能。收支分类和前篇文章的主币种设置界面大体类似。我们将详细介绍如何创建和管理不同的收支分类,以便用户可以更好地组织和跟踪他们的财务状况。

一、功能

先来看一下原型界面,界面很简单,这里就不多讲解页面功能了。我们主要关注如何实现收支分类的核心功能。
在这里插入图片描述

二、实现

表格、分页这两个功能的实现在前篇中已经展示了,在这里就不过多讲解了,我们重点看一下查询、新增、修改以及删除的实现。我们先在Config文件夹下新建IncomeExpenditureType.vue文件,这个文件就是用来实现收支类型功能的。接着在Interface文件夹下新建文件IncomeExpenditureClassification.ts,在这个文件中编写收支分类与服务端交互所使用的类型,代码如下:

import type {PageRequest} from "./request.ts";/*** 收支分类*/
export interface IncomeExpenditureClassification {id: string;name: string;type: number;parentId: string;parentName: string;
}/*** 收支分类提交*/
export interface IncomeExpenditureClassificationRequest{id: string;name: string;type: number;parentClassificationId: string;
}/*** 分页查询*/
export interface IncomeExpenditureClassificationPage extends PageRequest {classificationName: string;type: number;parentClassificationId: string;
}
2.1 查询

搜索功能包含两个下拉选择框和一个输入框,分别用于选择收支分类、父级类型以及输入类型名称进行搜索,还包括“查询”和“新增”两个按钮,分别用于执行搜索操作和添加新类型。

<div class="container"><!--搜索功能区域--><el-row :gutter="20"><el-col :span="3"><el-text>收支分类:</el-text><el-selectv-model="incomeExpenditureClassificationPage.type":default-first-option="true"placeholder="请选择收支分类"@change="incomeExpenditureCategoryChange"style="width: 65%"><el-option label="全部" :value="2"></el-option><el-option label="收入" :value="0"></el-option><el-option label="支出" :value="1"></el-option><el-option label="其他" :value="-1"></el-option></el-select></el-col><el-col :span="3"><el-text>父级类型:</el-text><el-select v-model="incomeExpenditureClassificationPage.parentClassificationId" :default-first-option="true"placeholder="请选择父级类型" style="width: 65%"><el-option label="全部" value="-1"></el-option><el-optionv-for="item in incomeExpenditureClassificationParentOptions":key="item.id":label="item.name":value="item.id"></el-option></el-select></el-col><el-col :span="4"><el-text>类型名:</el-text><el-input placeholder="请输入类型名" v-model="incomeExpenditureClassificationPage.classificationName"style="width: 65%"></el-input></el-col><el-col :span="6"><el-button type="primary" @click="queryIncomeExpenditure">搜索</el-button><el-button type="success" @click="editIncomeExpenditureClassification">新增</el-button></el-col></el-row>
</div>

这段代码是一个使用 Vue 3 和 Element Plus 组件库实现的搜索功能区域,包含三个筛选条件和两个操作按钮。外层容器 <div class="container"> 定义了一个容器,可能用于设置布局或样式。<el-row :gutter="20"> 使用了 Element Plus 的 el-row 组件定义网格布局,gutter="20" 表示列之间的间距为 20px。<el-col :span="3"> 定义了列的宽度,span="3" 表示该列占用 24 栅格系统中的 3 格。第一个下拉框用于选择收支分类,绑定到 incomeExpenditureClassificationPage.type,通过 @change="incomeExpenditureCategoryChange" 监听变化事件,选项包括“全部”“收入”“支出”“其他”。第二个下拉框用于选择父级类型,绑定到 incomeExpenditureClassificationPage.parentClassificationId,通过 v-forincomeExpenditureClassificationParentOptions 中动态生成选项。输入框用于输入类型名称,绑定到 incomeExpenditureClassificationPage.classificationName,用于模糊或精确搜索。第一个按钮设置为 type="primary",点击时触发 queryIncomeExpenditure 方法,执行搜索操作。第二个按钮设置为 type="success",点击时触发 editIncomeExpenditureClassification 方法,执行新增操作。数据绑定通过 data 定义,示例如下:incomeExpenditureClassificationPage 中保存收支分类、父级类型和类型名称,incomeExpenditureClassificationParentOptions 保存父级类型选项。方法包括 incomeExpenditureCategoryChange 处理分类变化,queryIncomeExpenditure 处理查询,editIncomeExpenditureClassification 处理新增。

// 查询收支分类参数IncomeExpenditureClassificationPage
const incomeExpenditureClassificationPage = ref<IncomeExpenditureClassificationPage>({pageSize: 10,pageNumber: 1,type: 2,parentClassificationId: '-1',classificationName: ''
});
// 接收父级类型的数据IncomeExpenditureClassification
const incomeExpenditureClassificationParent = reactive<IncomeExpenditureClassification[]>([]);
// 父级类型操作的数据
const incomeExpenditureClassificationParentOptions = reactive<IncomeExpenditureClassification[]>([]);// 查询父级类型
const queryIncomeExpenditureType = () => {axios.get(import.meta.env.VITE_API_BASE_URL + '/api/IncomeExpenditureClassification/QueryParent', {headers: {Authorization: localStorage.getItem('token')}}).then((res: any) => {const response = res.data;if (response.statusCode === 200) {// 接收返回值incomeExpenditureClassificationParent.splice(0, incomeExpenditureClassificationParent.length);incomeExpenditureClassificationParentOptions.splice(0, incomeExpenditureClassificationParentOptions.length);incomeExpenditureClassificationParent.push(...response.data);incomeExpenditureClassificationParentOptions.push(...response.data);} else {ElMessage.error(response.errorMessage)}}).catch((err: any) => {ElMessage.error(err.message);});
}
// 收支分类下拉选择框
const incomeExpenditureCategoryChange = (value: any) => {// 过滤出来对应分类的类型,不修改元数据,过滤后的数据绑定到父级分类下拉菜单incomeExpenditureClassificationParentOptions.splice(0, incomeExpenditureClassificationParentOptions.length, ...incomeExpenditureClassificationParent);if (value === 2) {return;}const filteredData = incomeExpenditureClassificationParent.filter(item => item.type === Number(value));incomeExpenditureClassificationParentOptions.splice(0, incomeExpenditureClassificationParent.length, ...filteredData);
}

这段代码是使用 Vue 3 和 TypeScript 通过 ref 和 reactive 定义的一个用于收支分类筛选和父级分类管理的功能。首先,定义了查询参数对象 incomeExpenditureClassificationPage,它是一个 ref 响应式对象,类型为 IncomeExpenditureClassificationPage,其中 pageSize 定义每页显示的记录数量为 10,pageNumber 定义当前显示的页码为 1,type 为 2 表示默认选中“全部”,parentClassificationId 为 ‘-1’ 表示默认选择“全部父级分类”,classificationName 为空字符串表示默认不输入具体的分类名称。然后,定义了两个响应式数组 incomeExpenditureClassificationParentincomeExpenditureClassificationParentOptions,类型为 IncomeExpenditureClassification[],其中 incomeExpenditureClassificationParent 用于存储从接口返回的原始父级类型数据,incomeExpenditureClassificationParentOptions 用于存储筛选后的父级类型数据,并绑定到下拉框。queryIncomeExpenditureType 是一个异步查询函数,通过 axios.get 方法向后端接口 /api/IncomeExpenditureClassification/QueryParent 发送 GET 请求,请求头中通过 Authorization 传入本地存储的 token 进行身份认证。成功返回后,使用 splice 方法清空原始数据和筛选数据,随后通过 push 方法将接口返回的数据添加到 incomeExpenditureClassificationParentincomeExpenditureClassificationParentOptions 中。如果接口返回状态码不为 200,则使用 ElMessage.error 显示后端返回的错误信息。如果请求失败,catch 捕获异常并通过 ElMessage.error 显示错误消息。incomeExpenditureCategoryChange 是收支分类下拉框的 change 事件处理方法,value 是当前选择的收支分类值。首先使用 splice 方法清空筛选数据,并将原始数据重新绑定到下拉框选项中。如果 value 为 2(即“全部”),直接返回所有选项,不做筛选。否则使用 filter 方法根据选中的收支分类值筛选出对应的父级类型,并通过 splice 方法将筛选结果更新到下拉框选项中。

2.2 新增/修改/删除

新增和修改操作都需要通过弹窗完成,因此使用 element-plus 提供的 el-dialog 组件来实现。

<el-dialog title="收支类型" v-model="dialogVisible" width="400"><el-form :model="incomeExpenditureClassification" :rules="rules" ref="incomeExpenditureClassificationFormRef"label-width="80px"><el-form-item label="收支类型" prop="name"><el-input v-model="incomeExpenditureClassification.name"></el-input></el-form-item><el-form-item label="收支分类" prop="type"><el-radio-group v-model="incomeExpenditureClassification.type"><el-radio :value="0">收入</el-radio><el-radio :value="1">支出</el-radio><el-radio :value="-1">其他</el-radio></el-radio-group></el-form-item><el-form-item label="父级类型" prop="parentClassificationId"><el-selectclearablev-model="incomeExpenditureClassification.parentClassificationId"placeholder="请选择父级类型"><el-optionv-for="item in incomeExpenditureClassificationParentOptions":key="item.id":label="item.name":value="item.id"></el-option></el-select></el-form-item></el-form><template #footer><el-button @click="dialogVisible = false">取 消</el-button><el-button type="primary" @click="saveIncomeExpenditureClassification(incomeExpenditureClassificationFormRef)">确 定</el-button></template>
</el-dialog>

<el-dialog> 是 Element Plus 提供的弹窗组件,v-model="dialogVisible" 绑定弹窗的显示状态,title="收支类型" 设置弹窗标题为“收支类型”,width="400" 设置弹窗宽度为 400px,保证在不同屏幕尺寸下有合适的显示宽度。当 dialogVisible 变为 true 时弹窗显示,变为 false 时弹窗关闭。

在弹窗内部,<el-form> 是 Element Plus 的表单组件,model="incomeExpenditureClassification" 绑定表单数据对象,rules="rules" 绑定表单验证规则,ref="incomeExpenditureClassificationFormRef" 定义一个 ref,用于在保存时对表单进行校验,label-width="80px" 定义标签的宽度为 80px,保证标签和输入框的对齐。<el-form-item> 用于定义表单中的每个字段项。

第一个 <el-form-item> 定义了“收支类型”字段,prop="name" 绑定表单数据中的 name 字段,并使用 <el-input> 组件定义输入框,v-model="incomeExpenditureClassification.name" 绑定输入框的值到表单对象的 name 字段,输入框中的值会自动同步到数据对象中。

第二个 <el-form-item> 定义了“收支分类”字段,prop="type" 绑定表单数据中的 type 字段,使用 <el-radio-group> 定义单选框组,v-model="incomeExpenditureClassification.type" 绑定当前选中的单选框值到表单对象中的 type 字段。<el-radio> 定义单选项,:value="0" 表示收入,:value="1" 表示支出,:value="-1" 表示其他,用户选择不同选项时,incomeExpenditureClassification.type 的值会自动更新。

第三个 <el-form-item> 定义了“父级类型”字段,prop="parentClassificationId" 绑定到表单数据中的 parentClassificationId 字段,使用 <el-select> 定义下拉框,v-model="incomeExpenditureClassification.parentClassificationId" 绑定下拉框当前选择的值到表单对象,clearable 属性允许用户清空已选择的选项,placeholder="请选择父级类型" 定义占位文本。通过 v-for 遍历 incomeExpenditureClassificationParentOptions 渲染选项,:key="item.id" 定义唯一标识符,:label="item.name" 定义显示文本,:value="item.id" 定义选项值,选择不同选项时 incomeExpenditureClassification.parentClassificationId 的值会自动更新。

<template #footer> 定义弹窗的底部操作按钮,<el-button @click="dialogVisible = false">取 消</el-button> 定义取消按钮,点击时将 dialogVisible 设为 false 以关闭弹窗。<el-button type="primary" @click="saveIncomeExpenditureClassification(incomeExpenditureClassificationFormRef)">确 定</el-button> 定义保存按钮,点击时触发 saveIncomeExpenditureClassification 方法,并传入表单的 ref 进行校验和保存操作。

//删除收支类型
const deleteIncomeExpenditureClassification = (id: string) => {// 弹出确认框ElMessageBox.confirm('此操作将永久删除该收支类型, 是否继续?', '提示', {confirmButtonText: '确定',cancelButtonText: '取消',type: 'warning'}).then(() => {// 确认删除axios.delete(import.meta.env.VITE_API_BASE_URL + '/api/IncomeExpenditureClassification/Delete/' + id, {headers: {Authorization: localStorage.getItem('token')}}).then((res: any) => {const response = res.data;if (response.statusCode === 200) {ElMessage.success('删除成功');queryIncomeExpenditure();queryIncomeExpenditureType();} else {ElMessage.error(response.errorMessage)}}).catch((err: any) => {ElMessage.error(err.message);});}).catch(() => {ElMessage.info('已取消删除');});
}
// 收支分类弹窗事件
const editIncomeExpenditureClassification = (row: any) => {dialogVisible.value = true;if (row) {console.log(row);// 修改incomeExpenditureClassification.id = row.id;incomeExpenditureClassification.name = row.name;incomeExpenditureClassification.type = row.type;incomeExpenditureClassification.parentClassificationId = row.parentId;} else {// 新增incomeExpenditureClassification.id = '';incomeExpenditureClassification.name = '';incomeExpenditureClassification.type = 0;incomeExpenditureClassification.parentClassificationId = '';}
}/*** 保存收支分类*/
const saveIncomeExpenditureClassification = (formEl: FormInstance | undefined) => {if (!formEl) returnformEl.validate().then(() => {if (incomeExpenditureClassification.id) {// 修改axios.put(import.meta.env.VITE_API_BASE_URL + '/api/IncomeExpenditureClassification/Update',incomeExpenditureClassification, {headers: {Authorization: localStorage.getItem('token')}}).then((res: any) => {const response = res.data;if (response.statusCode === 200) {ElMessage.success('修改成功');dialogVisible.value = false;queryIncomeExpenditure();} else {ElMessage.error(response.errorMessage)}}).catch((err: any) => {ElMessage.error(err.message);});} else {// 新增axios.post(import.meta.env.VITE_API_BASE_URL + '/api/IncomeExpenditureClassification/Add',incomeExpenditureClassification, {headers: {Authorization: localStorage.getItem('token')}}).then((res: any) => {const response = res.data;if (response.statusCode === 200) {ElMessage.success('新增成功');dialogVisible.value = false;queryIncomeExpenditure();} else {ElMessage.error(response.errorMessage)}}).catch((err: any) => {ElMessage.error(err.message);});}}).catch(() => {ElMessage.error('请检查输入项');});
}// 收支类型验证
const rules = reactive<FormRules<IncomeExpenditureClassificationRequest>>({name: [{required: true, message: '分类名称不能为空', trigger: 'blur'}],type: [{required: true, message: '收支分类不能为空', trigger: 'blur'},]
})

这段代码定义了删除、编辑和保存收支类型的完整操作逻辑,同时包括了表单验证规则.

deleteIncomeExpenditureClassification 是删除收支类型的函数,参数 id 是待删除的收支类型的唯一标识符。首先调用 ElMessageBox.confirm 弹出确认框,提示用户“此操作将永久删除该收支类型,是否继续?”标题为“提示”,确认按钮文本为“确定”,取消按钮文本为“取消”,对话框类型为“warning”表示警告提示。如果用户点击“确定”,会触发 then 逻辑,调用 axios.delete 发送 DELETE 请求,目标地址为 /api/IncomeExpenditureClassification/Delete/ + id,并在请求头中携带身份认证 token。请求成功后,返回数据通过 res.data 进行解析,若 statusCode === 200 表示删除成功,调用 ElMessage.success 显示“删除成功”,随后调用 queryIncomeExpenditure() 刷新列表,调用 queryIncomeExpenditureType() 刷新父级分类列表。如果 statusCode 不是 200,调用 ElMessage.error 显示后端返回的错误信息。如果请求失败,catch 捕获异常并通过 ElMessage.error 显示错误信息。若用户点击取消或关闭对话框,catch 捕获后调用 ElMessage.info('已取消删除') 显示提示。

editIncomeExpenditureClassification 是编辑和新增收支分类的函数,参数 row 是选中的收支类型对象。当用户点击编辑按钮时,首先将 dialogVisible.value = true 打开弹窗。如果 row 存在,表示是修改操作,通过 row.idrow.namerow.typerow.parentId 分别赋值给 incomeExpenditureClassification 对象中的 idnametypeparentClassificationId,将选中的记录数据绑定到表单中。否则,表示是新增操作,将 id 设为空,name 设为空字符串,type 设为 0(默认选中“收入”),parentClassificationId 设为空字符串,确保表单为空白状态。

saveIncomeExpenditureClassification 是保存收支分类的函数,参数 formEl 是表单的 ref 实例。如果 formEl 不存在则直接返回。调用 formEl.validate() 触发表单验证,验证通过后判断 incomeExpenditureClassification.id 是否存在。如果存在,表示是修改操作,调用 axios.put 发送 PUT 请求到 /api/IncomeExpenditureClassification/Update,请求体传入 incomeExpenditureClassification,请求头中携带 token。如果返回数据的 statusCode === 200,调用 ElMessage.success('修改成功') 显示修改成功提示,将 dialogVisible.value = false 关闭弹窗,调用 queryIncomeExpenditure() 刷新列表。如果 statusCode 不是 200,调用 ElMessage.error 显示错误信息。请求失败则捕获异常并通过 ElMessage.error 显示错误信息。如果 incomeExpenditureClassification.id 不存在,表示是新增操作,调用 axios.post 发送 POST 请求到 /api/IncomeExpenditureClassification/Add,请求体传入 incomeExpenditureClassification,请求头中携带 token。返回数据成功后调用 ElMessage.success('新增成功'),关闭弹窗并刷新列表。请求失败同样捕获异常并显示错误提示。如果表单验证失败,catch 捕获后显示“请检查输入项”。

rules 定义了表单字段的验证规则,使用 reactive 定义为响应式对象,类型为 FormRules<IncomeExpenditureClassificationRequest>name 字段规则要求为必填,未填时触发 blur 事件显示“分类名称不能为空”;type 字段规则要求为必填,未填时触发 blur 事件显示“收支分类不能为空”。

这段代码完整地实现了收支分类的新增、修改、删除和表单验证功能。通过 ElMessageBox.confirm 处理删除确认,通过 axios 调用后端接口完成数据交互,通过 ElMessage 反馈操作结果。通过 ref 绑定表单实例,利用 validate 进行数据校验,确保输入内容合法。整个流程遵循 Vue 3 的响应式和组件化设计思想,保证了交互的流畅性和数据的一致性。

三、小结

本篇文章介绍了如何实现收支分类功能,帮助用户更好地管理和跟踪财务状况。收支分类功能在界面和逻辑上与主币种设置类似,重点在于分类的查询、新增、修改和删除操作的实现。

首先,定义了与收支分类相关的数据结构,包括 IncomeExpenditureClassificationIncomeExpenditureClassificationRequestIncomeExpenditureClassificationPage,用于描述分类的基本信息、提交格式和分页查询参数。查询功能通过两个下拉框和一个输入框实现,分别用于选择收支分类、父级类型以及输入类型名称,配合“查询”和“新增”按钮执行相应的操作。下拉框中的选项数据通过从接口获取的原始数据进行动态生成。通过 axios.get 请求后端接口,接收返回的数据并存储在响应式对象中,供下拉框进行动态展示和筛选。通过 incomeExpenditureCategoryChange 方法,根据所选分类筛选对应的父级类型。

新增和修改操作通过 Element Plus 的 el-dialog 组件实现弹窗。弹窗中包含一个表单,定义了“收支类型”“收支分类”和“父级类型”三个字段,分别使用 el-inputel-radio-groupel-select 进行绑定。用户在弹窗中输入或选择数据后,点击“确定”按钮触发 saveIncomeExpenditureClassification 方法,首先对表单进行校验,校验通过后通过 axios.putaxios.post 请求后端接口,执行保存或修改操作,成功后弹出提示并刷新数据。

yChange` 方法,根据所选分类筛选对应的父级类型。

新增和修改操作通过 Element Plus 的 el-dialog 组件实现弹窗。弹窗中包含一个表单,定义了“收支类型”“收支分类”和“父级类型”三个字段,分别使用 el-inputel-radio-groupel-select 进行绑定。用户在弹窗中输入或选择数据后,点击“确定”按钮触发 saveIncomeExpenditureClassification 方法,首先对表单进行校验,校验通过后通过 axios.putaxios.post 请求后端接口,执行保存或修改操作,成功后弹出提示并刷新数据。

删除操作通过 ElMessageBox.confirm 弹出确认框,用户确认后通过 axios.delete 请求接口,成功后刷新数据。


http://www.ppmy.cn/server/178599.html

相关文章

Cup:检查容器镜像更新的简单方法

简介 什么是 Cup &#xff1f; Cup 是一个开源工具&#xff0c;旨在简化 Docker 容器的更新管理。它提供了一种快速且高效的方法来检查 Docker 镜像的更新。 主要特点 高效快速&#xff1a;Cup 优化了性能&#xff0c;能够快速检查多个镜像的更新&#xff0c;例如在 Raspberr…

《深度学习》——YOLOv3详解

文章目录 YOLOv3简介YOLOv3核心原理YOLOv3改进YOLOv3网络结构 YOLOv3简介 YOLOv3&#xff08;You Only Look Once, version 3&#xff09;是一种先进的实时目标检测算法&#xff0c;由 Joseph Redmon 和 Ali Farhadi 开发。它在目标检测领域表现出色&#xff0c;具有速度快、精…

【AI模型】深度解析:DeepSeek的联网搜索的实现原理与认知误区

一、大模型的“联网魔法”&#xff1a;原来你是这样上网的&#xff01; 在人工智能这个舞台上&#xff0c;大模型们可是妥妥的明星。像DeepSeek、QWen这些大模型&#xff0c;个个都是知识渊博的“学霸”&#xff0c;推理、生成文本那叫一个厉害。不过&#xff0c;要是论起上网…

模型空间、图纸空间、布局(Layout)之间联系——CAD c#二次开发

在 AutoCAD 的二次开发中&#xff0c;**模型空间&#xff08;Model Space&#xff09;**、**图纸空间&#xff08;Paper Space&#xff09;** 和 **布局&#xff08;Layout&#xff09;** 是三个核心概念&#xff0c;它们的关系及开发中的操作逻辑如下&#xff1a; --- 1. 模…

【论文笔记】Transformer

Transformer 2017 年&#xff0c;谷歌团队提出 Transformer 结构&#xff0c;Transformer 首先应用在自然语言处理领域中的机器翻译任务上&#xff0c;Transformer 结构完全构建于注意力机制&#xff0c;完全丢弃递归和卷积的结构&#xff0c;这使得 Transformer 结构效率更高…

家族族谱管理系统基于Spring Boot

目录 引言 一、系统概述 二、系统架构 三、功能模块 四、技术实现 五、系统特色 六、总结 引言 在数字化浪潮席卷全球的今天&#xff0c;家族文化的传承与延续面临着前所未有的挑战与机遇。传统纸质家谱因保存不便、查询困难、更新滞后等问题&#xff0c;已难以满足现代…

Android Shell上执行可执行文件报错:Permission denied

原因分析 文件系统挂载为noexec Android的某些分区&#xff08;如/sdcard、/storage&#xff09;默认挂载为noexec&#xff0c;禁止直接执行其中的文件。使用mount命令可查看挂载选项&#xff0c;若包含noexec&#xff0c;则该分区下的文件无法执行。 SELinux安全策略限制 SEL…

PLY格式文件如何转换成3DTiles格式——使用GISBox软件实现高效转换

一、概述 在三维GIS和数字孪生领域&#xff0c;3DTiles格式已成为主流的数据格式之一。它由Cesium团队提出&#xff0c;专为大规模3D数据可视化设计&#xff0c;能够高效地加载和展示海量模型数据。而PLY格式则是一种常见的三维模型文件格式&#xff0c;主要用于存储点云数据或…