ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot3+Vue3+ECharts:家庭理财记账系统全栈开发实战

SpringBoot3+Vue3+ECharts:家庭理财记账系统全栈开发实战 后疫情时代大家越来越重视家庭资产打理但很多家庭的账目依然停留在“微信账单 支付宝账单 手写备忘录”三步走的状态。到了月底想看看钱花在哪却要翻好几个 App还经常对不上。如果能自己动手搭建一套springboot3 vue3 理财记账系统把全家收支统一管理起来再用 ECharts 做可视化统计无论是作为课程设计、毕业设计还是作为学习全栈开发的练手项目都是非常不错的选择。本文将从零开始围绕“家庭理财记账”这个业务场景完成系统设计、数据库建模、后端接口开发、前端页面编写和 ECharts 图表展示的完整闭环。文章会尽可能覆盖你在开发过程中容易踩的坑提供可以直接复制的代码片段和配置希望能帮你少走弯路。1. 背景与系统设计1.1 为什么要做记账系统记账的本质不是“记录”而是“分析”。只有先把每一笔收支记录下来才能回答这几个问题这个月收入多少、支出多少结余是否健康钱主要花在哪些分类上哪些是固定支出哪些是冲动消费最近几个月的趋势如何能否支撑接下来的储蓄计划所以一个合格的理财记账系统需要具备三大核心能力流水管理、分类管理、可视化统计。如果只是做一个简单的 CRUD增删改查 Demo很难体现工作量和技术含量。但加入分类统计、月度趋势、收支对比之后系统既有业务深度又适合在答辩时演示功能亮点这也是很多毕业设计选择“记账系统 数据可视化”组合的原因。1.2 技术选型为什么选 springboot3 vue3Spring Boot 3 是当前 Java 后端非常主流的微服务开发框架它基于 Spring Framework 6最低要求 JDK 17整体性能、生态和可维护性都比 Spring Boot 2 时代更进一步。Vue 3 作为前端渐进式框架配合 Vite 构建工具开发体验比 Vue 2 Webpack 快不少。再加上 ECharts 这样的老牌可视化库可以非常方便地实现饼图、折线图、柱状图等图表。使用这套技术栈有几点实际好处市场需求大Spring Boot 和 Vue 是目前国内中小型项目中使用率很高的组合学会后对就业也有帮助。学习资源丰富遇到问题时容易搜索到解决方案。适合演示和答辩前后端分离结构清晰ECharts 图表效果好整体项目观感不错。1.3 系统功能模块划分先把系统要做什么列清楚后续编码才不会乱。本文实现的“家庭理财记账系统”包含以下模块模块功能说明用户登录基于 JWT 的简单登录认证区分用户数据账单管理新增、修改、删除、分页查询收支流水分类管理内置收入/支出分类例如餐饮、交通、工资、理财收益等统计看板按月统计总收支、结余展示分类占比和趋势图数据可视化使用 ECharts 展示饼图、折线图、柱状图为了降低入门门槛本文不会引入过于复杂的权限框架例如 Spring Security而是用简单的 JWT 拦截器控制接口访问。如果你想在毕业设计中体现安全方面的学习成果后面也可以把它替换成 Spring Security。2. 环境准备与项目初始化2.1 开发环境说明版本需要根据你的项目实际情况调整。本文示例以常见环境为例重点演示配置思路。以下是我本次使用的环境类别版本/工具JDK17Spring Boot 3 必须Maven3.8Spring Boot3.x数据库MySQL 8.x前端Vue 3 ViteNode.js18IDE后端 IDEA前端 VSCode 或 WebStorm需要注意Spring Boot 3 对 JDK 版本有硬性要求如果你的机器上装的是 JDK 8那么需要先升级 JDK。这一步很多人卡住建议先执行java -version确认一下。2.2 后端项目初始化在后端项目中使用 Spring Initializr 创建工程可以选择Spring Web、MySQL Driver、Lombok、MyBatis Framework这几个依赖。如果你用的是 IDEA 商业版可以直接在创建项目时勾选如果使用的是社区版可以到 Spring Initializr 官网生成项目后导入。pom.xml中核心依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version /dependency !-- JWT -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependency /dependencies这里有一个比较容易被新版本“坑”到的点Spring Boot 3 使用jakarta.*包名替代了原来的javax.*因此代码中引入HttpServletRequest、HttpServletResponse时要注意包路径是jakarta.servlet.http.*。2.3 前端项目初始化前端使用 Vite 创建 Vue 3 项目执行以下命令npm create vitelatest family-finance-web -- --template vue创建完成后进入目录并安装依赖。除了 vue-router、pinia、axios 之外还需要安装 EChartscd family-finance-web npm install npm install vue-router4 pinia axios echarts如果网络环境不稳定可以配置 npm 镜像后重试。安装完成后运行npm run dev能看到 Vite 默认页面说明基础环境没问题。3. 数据库设计与后端核心实现3.1 数据库表结构设计对于记账系统来说表结构不需要太复杂但要能满足“流水记录 分类统计”的需求。本文设计三张核心表用户表 sys_userCREATE TABLE sys_user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, nickname varchar(50) DEFAULT NULL, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;账单分类表 bill_categoryCREATE TABLE bill_category ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, type tinyint NOT NULL COMMENT 1-收入 2-支出, icon varchar(200) DEFAULT NULL COMMENT 图标地址, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;账单流水表 bill_recordCREATE TABLE bill_record ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, category_id bigint NOT NULL, amount decimal(10,2) NOT NULL COMMENT 金额, type tinyint NOT NULL COMMENT 1-收入 2-支出, remark varchar(255) DEFAULT NULL, record_date date NOT NULL COMMENT 消费日期, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_date (user_id, record_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;设计说明amount使用decimal(10,2)避免 Float/Double 精度丢失。冗余了type字段因为分类可能被删除或修改但账单的收入/支出属性必须稳定。给user_id和record_date建联合索引统计按月数据时会快很多。3.2 Spring Boot 后端项目结构一个清晰的后端包结构能让答辩和后续维护都轻松很多。本文后端结构如下com.example.familyfinance ├── common // 通用返回结果、异常处理 ├── config // Web 配置、拦截器注册 ├── controller // 接口层 ├── entity // 数据库实体 ├── mapper // MyBatis Mapper 接口 ├── service // 业务层 ├── interceptor // JWT 拦截器 └── utils // JWT 工具类3.3 通用返回结果类为了让前端统一处理接口响应先封装一个Result类。这是很常见但很实用的做法前后端联调时可以减少很多数据格式不一致的问题。package com.example.familyfinance.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }3.4 实体类示例以账单流水BillRecord为例使用 Lombok 简化代码。这里需要留意 MyBatis 中驼峰映射的配置后面会在application.yml中开启。package com.example.familyfinance.entity; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; Data public class BillRecord { private Long id; private Long userId; private Long categoryId; private BigDecimal amount; private Integer type; private String remark; JsonFormat(pattern yyyy-MM-dd) private LocalDate recordDate; JsonFormat(pattern yyyy-MM-dd HH:mm:ss) private LocalDateTime createTime; // 非数据库字段分类查询时冗余展示分类名称 private String categoryName; }3.5 MyBatis 核心 SQLMyBatis 的 Mapper 接口和 XML 中最核心的是账单分页查询和统计查询。分页查询使用 PageHelper 或手动 Limit 都可以这里以手动 Limit 为例减少依赖package com.example.familyfinance.mapper; import com.example.familyfinance.entity.BillRecord; import org.apache.ibatis.annotations.Param; import java.util.List; public interface BillRecordMapper { int insert(BillRecord record); int update(BillRecord record); int deleteById(Param(id) Long id); BillRecord selectById(Param(id) Long id); ListBillRecord selectPage(Param(userId) Long userId, Param(type) Integer type, Param(offset) int offset, Param(limit) int limit); long count(Param(userId) Long userId, Param(type) Integer type); }在对应的BillRecordMapper.xml中分页查询的核心 SQL 如下select idselectPage resultTypecom.example.familyfinance.entity.BillRecord SELECT r.*, c.name AS categoryName FROM bill_record r LEFT JOIN bill_category c ON r.category_id c.id WHERE r.user_id #{userId} if testtype ! null AND r.type #{type} /if ORDER BY r.record_date DESC, r.id DESC LIMIT #{offset}, #{limit} /select这里使用LEFT JOIN关联分类表这样前端列表可以直接展示分类名称不需要二次查询。3.6 统计接口实现统计看板是本文的重点也是 ECharts 图表的数据来源。我们需要提供三个统计维度月度收支趋势返回最近 6 个月的总收入、总支出。支出分类占比返回某个月份各分类的支出金额。收入/支出对比返回指定月份的每日收支情况。对应 SQL 如下select idselectMonthTrend resultTypejava.util.Map SELECT DATE_FORMAT(record_date, %Y-%m) AS month, SUM(CASE WHEN type 1 THEN amount ELSE 0 END) AS income, SUM(CASE WHEN type 2 THEN amount ELSE 0 END) AS expense FROM bill_record WHERE user_id #{userId} AND record_date #{startDate} GROUP BY DATE_FORMAT(record_date, %Y-%m) ORDER BY month /select分类占比统计select idselectCategoryExpense resultTypejava.util.Map SELECT c.name AS name, SUM(r.amount) AS value FROM bill_record r LEFT JOIN bill_category c ON r.category_id c.id WHERE r.user_id #{userId} AND r.type 2 AND DATE_FORMAT(r.record_date, %Y-%m) #{month} GROUP BY c.name /select在 Service 层中需要处理一下数据格式保证前端拿到的是标准的图表数据结构。这里有一个容易忽视的问题如果某个月份没有数据MySQL 的GROUP BY不会返回该月份记录所以最好在 Java 代码中把最近 6 个月的月份数组补齐全 0 填充。public MapString, Object getTrend(Long userId) { LocalDate now LocalDate.now(); ListString months new ArrayList(); for (int i 5; i 0; i--) { YearMonth ym YearMonth.from(now).minusMonths(i); months.add(ym.toString()); } String startDate months.get(0) -01; ListMapString, Object dbList billRecordMapper.selectMonthTrend(userId, startDate); MapString, MapString, Object map new HashMap(); for (MapString, Object m : dbList) { map.put((String) m.get(month), m); } ListBigDecimal incomeList new ArrayList(); ListBigDecimal expenseList new ArrayList(); for (String month : months) { MapString, Object m map.get(month); incomeList.add(m null ? BigDecimal.ZERO : (BigDecimal) m.get(income)); expenseList.add(m null ? BigDecimal.ZERO : (BigDecimal) m.get(expense)); } MapString, Object result new HashMap(); result.put(months, months); result.put(income, incomeList); result.put(expense, expenseList); return result; }3.7 登录认证与 JWT 工具JWT 的实现逻辑并不复杂用户登录成功后后端签发一个 token前端请求需要登录的接口时在 header 中携带Authorization: token后端拦截器校验 token 并对当前用户进行识别。JWT 工具类核心代码package com.example.familyfinance.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.Date; Component public class JwtUtils { Value(${jwt.secret}) private String secret; Value(${jwt.expire}) private Long expire; private SecretKey getKey() { return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); } public String generateToken(Long userId, String username) { Date now new Date(); Date expiration new Date(now.getTime() expire); return Jwts.builder() .setSubject(username) .claim(userId, userId) .setIssuedAt(now) .setExpiration(expiration) .signWith(getKey(), SignatureAlgorithm.HS256) .compact(); } public Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(getKey()) .build() .parseClaimsJws(token) .getBody(); } }注意jwt.secret的长度必须足够长HS256 算法要求密钥至少 256 位。如果你在本地配置的 secret 太短启动时会报错。一个简单的做法是在application.yml中配置一段 32 位以上的随机字符串。4. 前端 Vue3 核心页面与 ECharts 可视化4.1 前端项目结构前端部分本文不会把每个组件都展开而是重点讲解“账单列表页”和“统计看板页”两个核心页面。src ├── api │ ├── request.js // axios 封装 │ ├── bill.js // 账单相关接口 │ └── statistics.js // 统计相关接口 ├── router │ └── index.js ├── views │ ├── Login.vue │ ├── BillList.vue │ └── Dashboard.vue ├── components │ └── BillForm.vue ├── App.vue └── main.js4.2 Axios 请求封装对 axios 进行统一的请求拦截和响应拦截可以在请求头中自动附带 token也可以统一处理 401 登录失效的情况。// src/api/request.js import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: http://localhost:8080/api, timeout: 10000 }) // 请求拦截器 request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization token } return config }) // 响应拦截器 request.interceptors.response.use( response { const res response.data if (res.code 200) { return res } ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) } ) export default request4.3 统计看板页面 Dashboard统计看板是整套系统里最有“直观效果”的页面。我们把 ECharts 的初始化、更新逻辑封装成函数在onMounted中请求后端接口并渲染图表。!-- src/views/Dashboard.vue -- template div classdashboard el-row :gutter20 el-col :span12 el-card template #header 最近6个月收支趋势 /template div reftrendChartRef styleheight: 350px/div /el-card /el-col el-col :span12 el-card template #header 本月支出分类占比 /template div refcategoryChartRef styleheight: 350px/div /el-card /el-col /el-row /div /template script setup import { ref, onMounted, onBeforeUnmount } from vue import * as echarts from echarts import { getTrend, getCategoryExpense } from ../api/statistics const trendChartRef ref(null) const categoryChartRef ref(null) let trendChart null let categoryChart null // 初始化折线图 const renderTrendChart (data) { trendChart echarts.init(trendChartRef.value) trendChart.setOption({ tooltip: { trigger: axis }, legend: { data: [收入, 支出] }, xAxis: { type: category, data: data.months }, yAxis: { type: value }, series: [ { name: 收入, type: line, smooth: true, data: data.income, areaStyle: { opacity: 0.2 } }, { name: 支出, type: line, smooth: true, data: data.expense, areaStyle: { opacity: 0.2 } } ] }) } // 初始化饼图 const renderCategoryChart (data) { categoryChart echarts.init(categoryChartRef.value) categoryChart.setOption({ tooltip: { trigger: item }, series: [ { name: 支出分类, type: pie, radius: [35%, 70%], itemStyle: { borderRadius: 6, borderColor: #fff, borderWidth: 2 }, label: { formatter: {b}: {d}% }, data: data } ] }) } const loadData async () { const trendRes await getTrend() renderTrendChart(trendRes.data) const currentMonth new Date().toISOString().slice(0, 7) const categoryRes await getCategoryExpense(currentMonth) renderCategoryChart(categoryRes.data) } const handleResize () { trendChart trendChart.resize() categoryChart categoryChart.resize() } onMounted(() { loadData() window.addEventListener(resize, handleResize) }) onBeforeUnmount(() { window.removeEventListener(resize, handleResize) trendChart trendChart.dispose() categoryChart categoryChart.dispose() }) /script这段代码有两点值得注意第一ECharts 的init必须绑定到已经渲染好的 DOM 节点上所以不能直接在setup里调用而是要放到onMounted之后。如果在onMounted中请求接口是异步的那么渲染图表前要确保 DOM 已经存在。第二窗口大小变化时要调用chart.resize()否则图表会变形。记得在组件销毁前移除监听器并调用dispose()释放资源。4.4 账单列表页账单列表页的重点是条件查询和分页。本文使用el-table展示数据用el-pagination做分页同时提供新增和编辑弹窗。这里的关键在于前端要向后端传递当前页码和每页条数后端返回分页数据前端再重新渲染表格。!-- src/views/BillList.vue 核心片段 -- script setup import { ref, onMounted } from vue import { getBillPage, deleteBill } from ../api/bill import { ElMessage, ElMessageBox } from element-plus const list ref([]) const total ref(0) const queryParams ref({ pageNum: 1, pageSize: 10, type: null }) const loadData async () { const res await getBillPage(queryParams.value) list.value res.data.records total.value res.data.total } const handleDelete async (row) { await ElMessageBox.confirm(确定删除该账单吗, 提示, { type: warning }) await deleteBill(row.id) ElMessage.success(删除成功) loadData() } onMounted(() { loadData() }) /script为了减少篇幅这里没有完整展示弹窗表单代码但核心思路是表单绑定一个formData对象新增时清空编辑时把当前行数据赋给表单提交时调用新增或更新接口。4.5 前端路由与侧边栏路由配置使用 Vue Router 4在router/index.js中配置登录页和首页的父子路由关系。设置一个简单的全局前置守卫判断用户是否登录避免未登录直接访问首页。import { createRouter, createWebHistory } from vue-router const routes [ { path: /login, component: () import(../views/Login.vue) }, { path: /, component: () import(../layout/Index.vue), redirect: /dashboard, children: [ { path: dashboard, component: () import(../views/Dashboard.vue), meta: { title: 统计看板 } }, { path: bill-list, component: () import(../views/BillList.vue), meta: { title: 账单管理 } } ] } ] const router createRouter({ history: createWebHistory(), routes }) router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } }) export default router5. 运行与联调验证5.1 后端启动在application.yml中配置数据源和 MyBatis 相关设置server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/family_finance?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true jwt: secret: abc123456789012345678901234567890 # 至少32位 expire: 86400000 # 24小时启动后访问http://localhost:8080/api/user/login如果能看到接口返回 JSON说明后端启动成功。5.2 前端启动确认后端接口地址和 axios 的baseURL一致然后在前端项目根目录执行npm run dev浏览器访问http://localhost:5173能看到登录页。使用数据库中初始化好的账号登录后就可以进入统计看板和账单管理页面。5.3 预期效果登录成功后页面跳转到统计看板。统计看板展示最近 6 个月收入支出折线图以及本月支出分类饼图。在账单管理页面新增一笔支出后回到统计看板刷新饼图对应分类金额会变化。修改浏览器窗口大小图表能自适应缩放。6. 常见问题与排查思路在实际开发过程中我整理了一些大家会遇到的高频问题这里以表格形式给出排查思路问题现象常见原因解决思路后端启动失败提示 JDK 版本过低Spring Boot 3 需要 JDK 17安装并切换 JDK 17MyBatis 查询报Invalid bound statementMapper XML 路径配置不对检查mapper-locations是否准确XML 中 namespace 是否与接口全限定名一致前端请求接口跨域前后端端口不同存在跨域问题后端添加全局跨域配置或者前端通过 Vite proxy 代理ECharts 图表空白不显示DOM 未加载完成就初始化把echarts.init放到onMounted或nextTick之后登录后接口返回 401token 过期或 header 名称不一致检查前端 axios 请求拦截器中的 header 名称和后端拦截器是否一致金额小数点后有多余数字使用了 Float/Double 类型使用BigDecimal前端展示时使用toFixed(2)饼图数据为空的月份不展示SQL 没有该月份记录在 Java 层补齐月份空数据补 0修改账单分类但列表不更新没有刷新关联数据删除或修改分类前检查是否有账单引用该分类7. 最佳实践与工程建议7.1 后端规范建议参数校验不能省。新增账单时金额、类型、日期至少要做非空校验。如果是生产级项目建议使用ValidatedNotNull等注解。SQL 不要出现全表更新。尤其是前端传来的条件不可信必须绑定userId再执行更新和删除。本文每个接口都从 token 中解析当前登录用户避免越权操作其他用户数据。合理使用索引。如果账单表数据量大建议在record_date、category_id上建立索引。统计接口只查最近几个月的话日期索引非常关键。统一异常处理。虽然本文没有展开但如果出现运行时异常最好使用RestControllerAdvice统一捕获返回友好的 JSON 提示不要把堆栈信息直接抛给前端。7.2 前端规范建议ECharts 实例不要重复创建。在 Vue 3 中如果setOption之前已经存在实例可以复用同一个实例而不是每次重新echarts.init。组件销毁时释放资源。dispose()方法能避免内存泄漏尤其是页面频繁切换时这一点很关键。图表颜色和样式统一。可以抽取一个配置文件统一维护图表的颜色、字体、背景多个图表保持视觉一致。7.3 毕设/课设答辩建议如果这个项目是你用来做毕设或课设的建议在答辩时突出以下几点业务闭环从账单录入到分类统计再到趋势分析数据是一体化的。技术栈组合Spring Boot 3 Vue 3 MySQL ECharts 是全栈开发的主流组合具备代表性。图表可视化价值ECharts 不只是展示图表而是把记账数据转化为决策依据例如“支出分类占比”能直观反映消费结构。工程化细节JWT 登录、统一返回结果、前后端分离、分页查询这些工程能力是面试官或评委非常看重的点。7.4 进阶扩展方向如果你想把这个记账系统做得更完整可以继续扩展以下功能预算管理给每个支出分类设置月度预算超支时提醒。账单导出使用 EasyExcel 导出月度账单 Excel。定时统计使用 Spring Schedule 每天自动生成昨日收支快照。多角色权限引入 Spring Security 实现管理员和家庭成员不同权限。图表增强ECharts 支持中国地图、自定义主题、数据下钻可以根据业务需要加入区域消费分析或者使用 ECharts 的 visualMap 组件做区间可视化例如收入热力图、消费层级分析等。8. 总结本文围绕 “springboot3 vue3 家庭理财记账系统” 完成了一个全栈实战项目从系统设计、数据库建模到后端接口实现再到前端页面和 ECharts 可视化图表给出一套完整的开发思路和可运行的核心代码。项目虽然精简但涵盖了登录认证、CRUD 操作、分页查询、多表关联统计、异步数据可视化等常见开发场景既适合课程设计和毕业设计的展示也能作为学习 Spring Boot 3 和 Vue 3 全栈开发的项目起点。如果你按照文章中的步骤动手实践建议先跑通后端接口然后再对接前端页面。遇到问题时优先检查配置一致性例如端口、请求路径、header 名称、数据库密码这些最容易忽略但又最影响联调效率的细节。接下来你可以继续完善预算提醒、账单导出、多用户家庭空间等功能把系统打磨成真正可以日常使用的记账工具。如果本文对你有帮助可以收藏备用也欢迎在实际开发中多调试、多记录踩过的坑都会成为你的经验积累。
返回列表