先交代一下背景这是我带着两个学弟完成的毕业设计级全栈项目前端用 Vue后端用 Laravel中间还经历了一段从 ThinkPHP 迁移到 Laravel 的折腾过程。做完之后我最大的感受是记账系统听起来只是“增删改查”真正落地时牵扯到分类设计、资金精度、统计报表、权限隔离、跨域联调一堆细节任何一个环节没想清楚后期都在返工。如果你也准备做课程设计、毕业设计或者想给自己攒一个能写进简历的全栈项目这篇可以把我的完整思路、表结构、接口设计、踩坑记录全部扒给你看。1. 项目背景与整体设计思路1.1 大学生记账场景的特殊性市面上现成的记账软件不少随手记账、鲨鱼记账这些都很成熟但真正面向大学生群体时需求其实和上班族有很大差别。大学生的主要特点是收入来源单一基本靠每月固定生活费月初富裕、月底紧张是常态消费金额小但频次极高食堂一顿十几块、打印几块钱、快递代取三五块如果每笔都要手动选十几个字段很容易坚持不下来。再加上校内消费场景和外面不一样澡堂、话费、社团聚餐、宿舍拼单这些分类通用记账软件并不能完全覆盖。所以在设计这套系统时我们定的产品原则是记账操作必须“快”预算提醒必须“真”统计复盘必须“直观”。快速记账意味着分类要有预设模板、常用分类要高亮置顶预算提醒不是花超了才提示而是要在预算使用率达到 70%、90% 时提前预警统计不能只是列一堆数字要用曲线看趋势、用饼图看占比一眼就知道钱到底花在哪里。这也直接决定了技术选型和表结构设计的方向前端需要响应快、交互顺手后端要能撑起灵活的统计查询和权限隔离。1.2 技术栈选型为什么是 Vue Laravel而不是其他组合先说后端。项目标题里写了“Thinkphp-Laravel”这里必须先解释清楚在一个真实项目里ThinkPHP 和 Laravel 不会同时混着用两个框架的请求生命周期、路由注册、ORM、会话机制完全不同混在一起等于给自己挖坑。标题这个写法通常有两种背景一是团队前期用 ThinkPHP 做了原型后期迁移到 Laravel二是做技术选型时在两者之间对比最终落到 Laravel。我们这个项目就是前者。为什么最终放弃 ThinkPHP说句实话ThinkPHP 6 本身不差中文文档全学起来门槛低国内很多教学视频也用它。但真做前后端分离式 API 项目时我对 TP 的路由分组、中间件、以及模型关联的写法总觉得不够顺手。社团立项时原本的 PHP 代码是 ThinkPHP 5.1 写的耦合了一堆模板输出接口返回格式也不统一后期要加小程序端模板渲染这套基本帮不上忙。Laravel 的优势主要体现在几个地方Eloquent 的关联模型和查询构造器很顺手写统计数据比手拼 SQL 舒服Artisan 命令行自动化程度高一条命令能生成模型、迁移、控制器中间件机制让认证、日志、跨域这些横切逻辑非常干净。再加上 Laravel 在 API 开发上的生态成熟Sanctum 做 token 认证、API Resource 做数据格式转换、队列做异步任务都是现成方案。前端选 Vue 而不用 React主要出于团队学习成本考虑。Vue 的模板语法和响应式数据模型非常直白一个学弟之前只会写 jQuery看了一天 Vue 文档就能上手写页面。而且 Vue 生态里的 Element UI 组件库对后台管理类界面非常友好表单、表格、弹窗直接拿来用。对于记账这种表单多、数据联动多的场景Vue 的双向绑定和 computed 计算属性天然比手动操作 DOM 舒服。至于为什么不直接用 Spring Boot 那套很简单团队成员只有 PHP 基础硬上 Java 会死在中途做项目不是炫技是按时交付。版本上我选了 Vue 2 Element UI而不是 Vue 3 Element Plus。原因很实际当时能找到的毕设参考代码、网上教程、组件踩坑帖子绝大多数都是 Vue 2 的遇到问题搜起来快。如果你现在从零开始选 Vue 3 Vite 完全没问题Vite 启动速度比 Webpack 快太多只是参考资料相对少一些需要你抗压能力强一点。1.3 前后端分离的整体架构设计整个系统按模块拆分我建议你在需求阶段就把它列清楚不要边写边想。用户认证模块注册、登录、登录状态保持。账本管理模块每个用户可以创建多个账本比如“日常账本”“旅游账本”“聚餐专用账本”互不干扰。账单流水模块收入、支出、转账支持备注和附件图。分类管理模块系统预设常用分类用户也可以自己增加分类分收入和支出两类。预算管理模块按月份设置预算可以是总预算也可以按分类设置单项预算。统计报表模块月度趋势、分类占比、账户余额变化。数据导出模块账单可以导出 Excel/CSV。前端用 Vue Vue Router Vuex Element UI ECharts Axios后端用 Laravel 8 Sanctum MySQL。开发环境下前端通过 devServer 代理把 /api 请求转发到 Laravel 自带的 PHP 内置服务器避免跨域问题生产环境用 Nginx 部署前端打包出的 dist 目录和 Laravel 的 public 目录做同源访问既省下跨域配置也方便统一维护。画架构图的时候你会习惯用那种方框连线图这里我用文字把数据流描述清楚用户在 Vue 页面发起请求Axios 拦截器自动在请求头加上 token请求先到 NginxNginx 把 /api 前缀的请求转发给 LaravelLaravel 的中间件先校验 token再路由到对应控制器控制器通过 Eloquent 操作 MySQL返回 JSON 数据前端拿到数据后更新 Vuex再通过 computed 或 watch 驱动页面重新渲染。理解了这条链路后面任何报错你都能快速定位是在哪个环节出了问题。2. 数据库设计与核心功能拆解2.1 用户体系与资产账户建模用户表不要只照搬 Laravel 默认的 users 表我给用户加了一个 student_no 字段虽然在非教务系统里拿不到真实的学号认证但可以让学生自己填写方便后续做校内场景的扩展。资产账户是记账系统里很重要的一个概念很多初学者会忽略。用户的钱不是存在一个“总余额”里的它有现金、银行卡、微信零钱、支付宝、校园卡等不同载体。如果把所有支出都只记一个总额后面查“我支付宝里还有多少钱”就无从下手。所以单独建一张 accounts 表。账户余额这里有个容易踩的坑不要试图在表里维护一个账户的当前余额字段。流水的本质是账目变动记录正确的做法是记录每笔账单的金额和类型然后通过“初始余额 收入累计 - 支出累计”去实时计算当前余额。如果你在账户表里直接存一个 balance每次记账都要去更新它很容易出现并发下余额不一致的问题后期对账就是灾难。用户模块的代码上我用 Laravel Sanctum 做认证。安装很简单composer require laravel/sanctum发布配置和迁移文件然后在 User 模型里 use HasApiTokens 这个 trait。登录接口成功后会生成一个 token返回给前端保存前端每次请求带上这个 token 作为身份凭证。选择 token 而不是传统 session 的原因很直接前后端分离部署时前端页面和后端 API 可能不在同一个域名下session 依赖 cookie 的自动携带跨域下处理麻烦token 是无状态的前端用 localStorage 存着request header 手动携带干净利落。2.2 账本、账单与分类的关联设计这是整个系统最核心的三张表关系不难但设计得合理不合理直接决定后面写统计接口是轻松还是痛苦。books 账本表id、user_id创建者、name、cover、sort、created_at。账单和账本是多对一关系一笔账单必须属于某个账本方便用户把餐饮开销和旅行开销分开看。如果想做宿舍共享账本可以加一张 book_user 中间表让一个账本关联多个用户但权限控制会复杂很多建议第一版先不做等核心功能稳定后再扩展。categories 分类表id、user_id、parent_id、name、typeexpense 或 income、icon、sort。一个重要的设计点是分类分为系统预置和用户自定义两类。系统预置分类在迁移文件里初始化user_id 设为 0表示所有用户可见用户自定义分类则记录创建者 id。这样在加载分类列表时只需要“user_id 当前用户 OR user_id 0”就能查出完整分类。bills 账单表id、book_id、category_id、account_id、user_id、amount、type、record_date、remark、image、created_at。这里注意一点账单的类型 expense/income 和分类的 expense/income 要一致分类表里已经有了类型为什么账单还要存一遍因为查询时如果要按“某个时间段所有支出”去统计直接在 bills 上过滤 type 会比关联 categories 表再过滤快得多省一次 join。这是一种常见的反规范化设计用一点冗余换查询性能数据量上来之前完全划算。金额字段务必用整数存储单位是“分”。这不是强迫症是经验之谈。MySQL 的 DECIMAL 类型虽然能表示小数但在 PHP 里浮点数运算 0.1 0.2 的结果不是精确的 0.3记账数据一分钱都不能错否则对不上账用户立刻就会察觉。你可以在 Laravel 的模型访问器里把分转换为元返回给前端展示前端提交时将元转换为分再传给后端全链路都用整数计算精度问题直接消失。索引设计也不能忽略。账单表里最常见的查询条件是“某个账本在某段时间的收支记录”所以建一个组合索引 (book_id, record_date)。统计分类占比时会按 (category_id, record_date) 查询再加一个索引。初期数据量不大时感觉不到差别等到账单积累到几万条没有索引的统计接口会让你怀疑服务器是不是宕机了。2.3 预算与月度统计的业务逻辑预算表 budgetsid、user_id、book_id、category_id可空空表示整本账本的总预算、month、amount、created_at。month 字段用字符串格式保存比如 2024-03不要用时间戳因为时间戳还要做格式化转换字符串直接比较就行。月度预算的逻辑不能只做“每月固定金额”那么死板。我建议在用户设置预算时提供一个“每月自动重置”的勾选开关开启后用户设置一次预算每月 1 日系统自动把它复制到新月度。实现上可以用 Laravel 的任务调度器写一个命令每天检查是否有需要初始化的预算记录也可以更懒一点在查询当前月预算时如果发现当前月没有预算记录就自动从用户设定的模板复制一条。后者少写很多定时任务的代码对小型项目更友好。超额预警的判定也不复杂查询当前月某本账本的支出总额计算占预算的比例。70% 时返回一个 warning 标记90% 时返回 danger 标记前端在预算列表和首页卡片上做进度条提醒。记住不要在用户每次记账时都去全表求和正确的做法是先按月份汇总账单再和预算比较。统计部分的核心 SQL 就两类。一是分类汇总查某本账本某个时间段内每个分类的支出合计二是日月趋势按天或按月把账单金额聚合。用 Laravel 查询构造器写起来非常直观$result Bill::query() -where(book_id, $bookId) -where(type, expense) -whereBetween(record_date, [$startDate, $endDate]) -selectRaw(category_id, SUM(amount) as total) -groupBy(category_id) -get();这种分组聚合在 MySQL 上效率很高因为 SUM 和 GROUP BY 都是数据库擅长的操作。很多时候大家觉得统计接口慢不是数据库不行而是你用了循环查库——比如遍历 30 天每天执行一条 SUM 查询30 次请求当然慢。正确的做法是一次 GROUP BY 查询拿到全部数据然后在 PHP 里把数据映射到日期或分类上。3. 后端 Laravel API 开发实践3.1 从 ThinkPHP 迁移到 Laravel 的关键差异我们项目代码里有一批 ThinkPHP 5.1 的历史接口迁移时最痛苦的不是语法差异而是思维方式的转变。这里列一个对照表给同样在做迁移的人参考。能力点ThinkPHP 5.1/6Laravel 8迁移注意请求入口public/index.php 直接进入应用先经过 Kernel 中间件管道Laravel 的中间件体系更清晰路由定义Route::rule 或配置路由Route::get/post 配合 apiResourceLaravel 的资源路由更省事路由命名规范控制器返回数组 模板赋值return JsonResponse 或 API Resource做纯 API 时 Laravel 更顺手模型think\ModelEloquent Model关联模型写法相近作用域方法名不同数据校验独立验证器类FormRequest 类Laravel 的 FormRequest 更集权命令行工具think 命令artisan 命令make:model -mcr 一条命令生成全套模板引擎think-templateBlade本项目用不上纯 API 不用纠结迁移时最直接的体感是Laravel 的 Eloquent 在关联查询上比 TP 模型灵活很多。TP 的模型 also supports with 关联预加载但 Laravel 的 withCount、whereHas 这些方法在写统计筛选时几乎就是杀器。比如要查“有支出的分类”Laravel 里一句 Category::whereHas(bills, fn($q) $q-where(type, expense))-get() 就搞定了清晰且可读。另一个重点是依赖注入和门面。Laravel 的服务容器让你可以在控制器构造函数里直接注入 Request、Repository 等对象代码的测试性和可维护性高很多。刚开始不习惯容器这套概念没关系先按“控制器里 use 类方法参数里写类型提示”的固定套路写时间长了自然理解。3.2 路由、控制器与 API 资源API 路由建议统一加版本前缀 /api/v1方便以后接口升级了不用直接破坏旧客户端。路由文件按模块拆分auth.php、book.php、bill.php、statistics.php、budget.php然后在 api.php 里用 Route::group 加载它们。路由定义我推荐多用 Laravel 的 apiResource 而不是一个个手动写。它是标准 RESTful 风格一条命令对应 7 个方法。但要注意账单删除、更新这类操作必须做归属校验不能用户传一个 id 就把别人的账单删了。我封装了一个 trait在控制器方法开头统一校验 resource 是否属于当前用户protected function validateOwner($model, $request) { if ($model-user_id ! $request-user()-id) { abort(403, 无权操作); } return $model; }控制器层面尽量薄业务逻辑可以拆到 Service 层或 Repository 层但不要为了设计模式而过度设计。小型项目里控制器直接调用 Eloquent 写简单查询完全可以接受等代码明显膨胀了再抽 Service别一开始就建一堆空壳类。数据返回用 Laravel 的 API Resource它的好处是能把数据库字段和对外字段解耦。比如数据库里的 amount 是分前端要的是元可以在 Resource 里做转换。分页数据直接 return BillResource::collection($paginate)Laravel 会自动包成 data meta 的结构前端解析非常舒服。3.3 认证与 Token 处理登录注册这部分我建议不要自己用 JWT 库折腾直接用 Laravel Sanctum。它底层就是普通的 token 机制使用简单还支持 token 过期时间、按设备撤销 token 等操作。安装完成后在 User 模型里加 HasApiTokens登录接口执行 $user-createToken($request-device)-plainTextToken返回这个 token 给前端。前端拿到 token 后统一存 localStorageAxios 请求拦截器里这样处理service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers.Authorization Bearer ${token} return config })这里有两个坑必须提前说。第一token 不要存到 Vuex 里就完事页面刷新后 store 会被清空一定要同时持久化到 localStorage第二后端路由上加上 auth:sanctum 中间件但也要注意哪些接口是公开的注册、登录、刷新验证码这些不能加中间件否则用户还没登录就被拦住了。某些教程会教你把 Laravel session 用在 API 认证里它适合的是后端渲染的 BBS 或 CMS 系统纯 API 项目不建议因为 session ID 存在 cookie 里跨域请求时浏览器策略会让事情变得异常复杂。在前后端分离的项目里token 模式是绝大多数场景的最优解。3.4 统计报表接口的性能优化统计接口是这类系统最容易出问题的位置因为查询条件里永远有时间段、账本、分类还要按维度分组。首版我写的是循环查库30 天的趋势图就是 30 条 SQL页面接口响应在 200ms 开外数据量上来后会更慢。后来优化成一次聚合查询思路就是前面提到过的数据库先 GROUP BY 分组PHP 再补零。下面这个代码是月度趋势图的核心逻辑处理了日期补零的问题也就是某天空没记录也得补上 0不然前端画出来的折线图会断裂$start Carbon::now()-startOfMonth(); $rows Bill::query() -where(book_id, $bookId) -where(type, expense) -where(record_date, , $start) -selectRaw(DATE_FORMAT(record_date, %Y-%m-%d) as day, SUM(amount) as total) -groupBy(day) -pluck(total, day); $days collect(range(1, Carbon::now()-daysInMonth))-map(function ($day) use ($rows, $start) { $date $start-copy()-day($day)-format(Y-m-d); return [ day $date, total ($rows-get($date, 0)) / 100 ]; });这样无论账单表有多少数据只需要一次 SQL 就能拿到全月每天的支出汇总性能提升非常明显。如果查询范围是一个季度甚至一年在 bills 表上再建一个 (book_id, type, record_date) 的组合索引基本就能满足绝大多数场景。记账总量到达百万级后再考虑 Redis 缓存初期不要为了用缓存而用缓存反而增加数据一致性的维护成本。4. 前端 Vue 实现与核心交互4.1 Vue 环境搭建与工程化要点关于 Vue 环境的搭建网上一搜一大把这里只说几个容易翻车的地方。Node.js 版本不能太老npm 建议换国内镜像源能省下大量等待时间。用 Vue CLI 创建项目时选择手动配置把 Router、Vuex、ESLint 都勾上CSS 预处理器看个人习惯我选了 Sass。安装依赖时的关键点是版本锁定。新手最容易犯的错误就是什么组件都装最新版结果 Element UI 和某个插件不兼容页面控制台疯狂报错。我的建议是 lockfile 文件提交到 Git 里团队所有成员用同一份依赖版本。核心依赖版本大致是这样的依赖包版本用途vue2.6.14核心框架vue-router3.5.4前端路由vuex3.6.2全局状态管理element-ui2.15.14组件库axios0.27.2HTTP 请求echarts5.3.3图表dayjs1.11.5日期格式化Vue Devtools 这个插件强烈建议安装调试响应式数据、查看 Vuex 状态、排查组件 props 传递问题都靠它。Chrome 应用商店直接装就行如果装不上也可以从官方 GitHub 的 release 页下载 crx 文件拖进浏览器。项目开发过程中我至少有一半的“页面为什么没更新”问题都是靠 Devtools 里的组件树和 Vuex 面板定位的。4.2 路由设计与动态权限前端路由设计相对简单分公共页面和认证页面两类。登录页、注册页是公共的首页总览、记账页、流水列表、统计页、预算页、我的页面都要登录。这里不需要完全动态生成路由对记账系统来说业务模块对所有人开放只是操作的是各自的数据而已。所以用 Vue Router 的全局前置守卫做一道登录拦截就够了。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else { next() } })如果你以后要做管理员后台再研究 addRoute 动态路由那套东西。现在很多项目过早引入了动态权限结果就几十个用户权限模型却写得比业务还复杂这是本末倒置。面试时如果有人问动态路由你可以讲原理通过 router.addRoute 在登录后根据用户角色从后端拿到可访问路由表注册再通过全局守卫做菜单和路径的双重控制。但心里要明白这个机制解决的是权限隔离问题不是为炫技而存在的。记账页会用到一个动态路由参数编辑已有账单时需要把账单 id 带过去。我用了 this.$route.params.id 读取跳转时 this.$router.push({ name: BillEdit, params: { id } })。这里特别提醒params 传参时一定要用 name 路由跳转不要用 path不然参数会丢失。4.3 记账页、统计页与自定义指令记账页是整个系统交互最密集的地方。金额输入、分类选择、账户选择、日期选择、备注填写还有收入/支出二段切换。这里我做了两个自己很满意的封装。第一个是自定义 v-model 的金额输入组件。写一个 MoneyInput 组件内部用原生的 HTML input外部通过 value 和 $emit(input) 实现 v-model。组件的逻辑是把金额一分为整数展示时转成元并保留两位小数输入时通过 input 事件把“元”转回“分”传给父组件。这样父组件里拿到的始终是整数分和数据库存储完全对齐。第二个是自定义指令 v-money-format。使用 Vue.directive 注册一个全局指令功能是金额格式化当 input 失去焦点时自动把用户输入的数字变成标准金额格式。指令内部用 binding.value 接受参数比如要保留几位小数、是否显示千分位分隔符。用指令的好处是可以应用于任意表单不需要每个组件都写一遍格式化方法。分类联动也值得说说。用户选择一级分类后子分类需要联动加载。我用 watch 监听当前选中的一级分类 id变化时动态请求该分类下的子分类列表同时清空之前选中的子分类。这种联动逻辑千万不要写在按钮点击事件里因为选择分类后数据变化了但页面不知道要去触发更新用 watch 最合适。统计页是 ECharts 的舞台。这里第一个坑是图表初始化时机如果图表放在 v-if 条件渲染的组件里DOM 还没渲染完成就调用 echarts.init会拿不到容器节点。解决方法是 this.$nextTick 里初始化如果用了 v-show 则确保容器始终渲染。第二个坑是组件销毁时一定要调用 chart.dispose()否则页面切来切去浏览器内存唰唰涨时间长了整个页面越来越卡。统计页的数据结构设计也很有讲究。后端返回的不应该是“已经生成好的图表配置”而应该是原始数据比如日期数组、金额数组、分类名称数组。前端负责把原始数据映射成 ECharts 需要的 series 结构。这样后端接口保持稳定未来你想把折线图换成柱状图只需要改前端代码不用动后端。4.4 与后端联调跨域、拦截器与错误处理开发模式下前端 devServer 和后端 API 端口不同必然触发跨域。最省事的方案是配置 Vue CLI 的 proxy让浏览器完全感知不到跨域的存在module.exports { devServer: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true, pathRewrite: { ^/api: /api } } } } }这样做之后前端所有请求都是相对路径 /api/v1/xxx浏览器访问的是 devServer 地址devServer 再转发给 Laravel。生产环境则用 Nginx 做同样的事前端打包后的 dist 里接口路径也是 /api 开头由 Nginx 配置把 /api 请求反向代理到 Laravel。前后端同源部署CORS 的问题就完全不存在了。如果你硬要走 CDN 域名完全不同源的方案再去后端配置 config/cors.php 把允许的来源、方法、头部都放开。Axios 拦截器是联调阶段的重要环节。我在请求拦截器里统一注入 token在响应拦截器里做了三层处理正常返回 data、业务错误弹 Message 提示、HTTP 401 自动跳转登录。注意 401 跳转要想清楚场景如果同时发出多个请求可以用一个标志位防止重复跳转。错误提示不能太笼统。前后端要约定一个统一的返回格式Laravel 返回 { code: 0, message: ok, data: {...} }出错时 code 非 0message 是给人看的具体原因。前端响应拦截器发现 code 非 0 就直接 ElMessage.error(message)这样后端抛出的“预算金额不能小于 0”这类业务提示能原样展示给用户而不是让用户看到一个大大的 undefined。5. 常见问题与排查技巧实录5.1 前端开发中的高频问题第一个高频问题登录成功后刷新页面又被守卫拦回登录页。原因是 Vuex 里的 token 是在内存里的刷新就丢了但 localStorage 里还有。解决方法是路由守卫里优先判断 localStorage 是否有 tokenVuex 只在页面启动的初始化动作里从 localStorage 读取。第二个高频问题配置了 proxy 但还是报跨域。这种情况九成是 devServer 配置没生效。检查你是不是改完 vue.config.js 以后没有重启 npm run serve这个文件不是热更新的必须重启。还有一个隐蔽问题pathRewrite 写错导致请求转发后还带上了多余的 /api 前缀。第三个高频问题金额显示不对比如输入 10.1 元保存后变成 10.09 元。这是本系统最典型的浮点精度问题。解决方案就是用分存储前端展示和输入统一除以 100。这里切记不要在展示时用元去乘以汇率之类操作所有计算都完成后最后一步再除 100。第四个高频问题列表滚动卡顿。当账单流水达到几千条时一次性渲染全部 DOM 会让页面卡死。我当时的方案是后端分页前端用 el-table 的 load 事件做滚动加载。现在市面上有更好的虚拟滚动方案但核心思路是一样的不要把几千条 DOM 一次性塞给浏览器。5.2 后端开发中的高频问题Laravel 里最气人的一个错误是路由明明定义了却访问 404。原因通常是 Nginx 的伪静态没配好所有非真实文件请求没有被转发到 index.php。PHP 内置服务器没有这个问题但部署到真实环境一定会有。解决方法是 Nginx 站点配置里加上这一句location / { try_files $uri $uri/ /index.php?$query_string; }第二个问题是迁移文件报错“Class not found”。Laravel 的迁移文件里如果用到了自定义的枚举类或模型类需要确认它们已经被 Composer 自动加载。执行 php artisan clear-compiled 和 composer dump-autoload 基本能解决。另外一个很常见的坑是你修改了迁移文件但数据库里表已经创建再次 migrate 会报表已存在这时需要用 php artisan migrate:rollback 回滚上一次迁移或者直接手动删表重建。开发阶段无所谓线上千万别这么干。第三个问题是修改字段类型时迁移报错提示需要安装 doctrine/dbal。Laravel 8 中修改列必须依赖这个扩展包装一下就好composer require doctrine/dbal然后就能正常使用 $table-decimal(amount, 10, 2)-change() 这类修改操作。第四个问题是日期查询查不出当天数据。我项目里的 record_date 是 date 类型但有人会粗心把它当 datetime 来存导致 whereDate 查不出来。保持字段类型的纯净不要过度设计。date 就存 Y-m-ddatetime 才存时间戳查询时对应地去用 whereDate 和 whereDateTime。5.3 部署与上线部署版本用的是 Nginx PHP-FPM前端打包后和后端放在同一个服务器。目录结构大致是 /www/wwwroot/finance-api 放 Laravel 代码/www/wwwroot/finance-web/dist 放前端打包产物。Nginx 配置两个 location一个匹配后端 API一个匹配前端静态文件。部署完成后第一件事检查 storage 目录的可写权限。Laravel 日志写不进去页面永远白屏但什么都不报这是新手最容易懵掉的情况。用 chmod -R 775 storage bootstrap/cache 解决同时检查 PHP-FPM 的运行用户和目录属主是否一致。第二件事是 .env 的环境变量。APP_KEY 必须生成过php artisan key:generate。数据库连接、redis 地址都要改成生产环境的真实值。别忘了 laravel 配置缓存php artisan config:cache。第三件事是前端打包。build 之前把 vue.config.js 里的 publicPath 设为 ./不然子路由刷新页面时静态资源路径解析不对。打包完成后 dist 目录里的 index.html 需要能被 Nginx 的 try_files 指向防止前端路由在非根路径下刷新 404。5.4 常见问题速查表现象可能原因解决方案登录后刷新又跳回登录页Vuex 没有持久化 tokenlocalStorage 存取 token路由守卫读 localStoragedevServer 代理不生效配置文件改后未重启重启 npm run serve后端路由 404Nginx 未配置伪静态try_files 转发到 index.php金额显示错乱浮点存储精度全部用分存储整数运算修改迁移字段报错缺少 doctrine/dbalcomposer require doctrine/dbal上传图片 413Nginx client_max_body_size 默认 1M修改为 10M统计接口响应慢循环查库改成 group by 聚合查询页面白屏控制台报 chunk 错误前端资源路径不对publicPath 设为 ./6. 项目扩展与高频面试题准备6.1 后续可以做哪些扩展功能记账系统的扩展空间其实很大。共享账本这个方向最适合大学生场景宿舍拼单、社团活动、班级出游时几个人共同记录一笔消费AA 结算自动生成每人应付款项。实现上建一张 book_user 关联表再在账单上增加 payer_id 和受益人列表复杂度可控交互上能吹的点很多。账单导入导出也值得做。支付宝和微信的账单可以导出 CSV系统解析后按时间、金额、分类批量导入省去手动记账的麻烦。这个功能的难点在 CSV 编码和字段映射不同渠道的模板不一样需要写解析器。做出来后实用性极强因为大学生最烦的就是一条条手输。如果想玩得更深入可以给社团经费管理做一个审批流。社团活动的每一笔支出申请都要经过负责人审批才能计入账本。这就用到 Laravel 里的状态机设计把审批状态、审批人、审批时间记录下来前端展示流程进度。这个功能适合在答辩时展示你对复杂业务逻辑的处理能力也能和前面 Laravel 中间件、事件系统的知识串起来讲。6.2 面试时怎么讲这个项目如果你把这个项目写进简历几乎一定会被问到 Vue 和 Laravel 的高频知识点。提前准备好这些问题的答案面试效果会好很多。Vue 生命周期是必问题。你可以在项目里的具体场景回答created 里调用接口拉取账单列表mounted 里初始化 ECharts 图表beforeDestroy 里销毁图表实例释放内存。这样回答比背诵生命周期名称生动得多。Vue 路由传参和动态路由也是高频。传参区分 query 和 params 的适用场景动态路由要讲清 router.addRoute 的实现原理。你还可以主动提到前后端分离下权限控制在路由层怎么处理用本地路由守卫拦截登录态和动态注册路由两条链路来说明。Vue 自定义指令和自定义 v-model直接用你项目里封装过的 v-money-format 和 MoneyInput 组件举例。面试官听到你有实际封装经验会认为你不是只会调用现成组件而是理解 Vue 底层机制。Vue 和 React 的区别是前端面试绕不开的题。你不需要背长篇大论抓住核心差异讲清楚即可Vue 基于响应式依赖追踪更新粒度细React 基于虚拟 DOM 和不可变数据需要用 Fiber 架构把渲染拆成可中断的小任务。Fiber 本质是一棵可以暂停、继续、优先级的链表结构解决的是长任务阻塞主线程的问题。你把这条讲明白面试官基本不会再往下死磕。Laravel 相关的问题Sanctum 认证、中间件执行顺序、Eloquent 关联预加载、观察者模式都是常用知识点。再准备一个你优化统计接口的真实案例把“循环查库优化成 group by 聚合”这个过程讲清楚比背八股文有说服力得多。6.3 面试中的项目亮点怎么提炼面试官拿着你的简历问项目重点听的不是功能列表而是你有没有主动思考和解决问题的能力。所以讲项目时不要说“我实现了记账功能”要说“我为什么要这样设计”。金额用分存储这个点你可以说是因为浮点精度问题为了账目准确所以做全面改造。预算预警功能你可以说你参考了支付宝账单的提醒策略设计了 70% 和 90% 两个阈值并说明这个阈值怎么通过配置项让用户自己调整。统计接口优化你可以给出优化前后的数据对比。跨域问题你可以讲清楚开发环境代理、生产环境同源部署的区别以及为什么这么设计。这些内容讲完后面试官会意识到你做过真实的思考而不是照着教程敲了一遍代码。项目本身技术栈只是 Vue Laravel不算特别吸引眼球但你能把细节讲透就是加分项。最后给你一个实用建议这类记账系统做完以后不要停在“功能能用”就收工。找三五个人真正用一个月你会发现真实使用场景和你预想的有大量出入——比如你以为大家都在记账前选择分类实际上用户会抱怨找不到某个自定义分类你以为预算预警会让大家省钱实际上大家只是把它当成一个数字弹窗。关注真实反馈优化交互和业务逻辑这个过程才是项目最大的价值。
