ARTICLE DETAIL

资讯详情

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

基于Vue3的校园勤工助学系统:从状态机设计到前后端分离实战

基于Vue3的校园勤工助学系统:从状态机设计到前后端分离实战 勤工助学系统算得上计算机毕业设计里“生命周期”最长的题目之一了。它没有电商秒杀那么炫技也没有社交App那么花哨但胜在业务链路完整、角色明确、管理流程有真实痛点特别适合用来展示一个开发者从需求分析到前后端落地再到问题排查的完整能力。基于VUE的校园勤工助学系统就是把这个经典的校园业务场景用Vue前端技术栈重新做了一遍并且配了完整的毕业设计源码和说明文档。这套系统解决的核心问题很直接过去勤工助学岗位从发布、申请、审批到工时上报、工资核算全靠辅导员和资助中心老师在表格和聊天记录里来回折腾信息散、容易漏、对不上账。而把它做成Web系统后岗位信息统一展示、学生在线报名、老师在线审核、工时按月确认、工资按规则自动算整条链路全部线上化每一单都有记录可查。对做毕设的同学来说它又是一个很标准的“Vue前端 Java后端”前后端分离项目能练到的东西非常全面Vue组件化开发、路由权限控制、表格表单交互、文件导出、后端RESTful接口设计、数据库表关系设计以及联调阶段的跨域、Token鉴权、重复提交这些实战问题。总之不管你是拿它当毕业设计题目还是想学一套完整的前端项目实践流程这篇文章都值得看完。以下的内容是我基于“校园勤工助学系统”这类项目的开发经验把从需求拆解到技术落地的完整过程复盘了一遍。文章里没有废话全是能直接抄走的思路和代码级别的细节。1. 为什么需要一套校园勤工助学系统1.1 勤工助学管理的真实痛点我在做这个系统之前先去找了几位在高校做资助工作的老师聊了聊发现勤工助学的“管理难”几乎是全国高校的通病。首先是岗位信息不透明。用工部门要招人过去就是往辅导员群发一个Excel学生能不能看到全凭运气。学生想找个合适的岗位得挨个办公室去问效率极低。其次是审批流程没有留痕。学生申请了哪个岗位、部门老师是否同意、最后谁录用了这些信息常常是口头沟通一旦出了纠纷很难追溯。再有就是工时和工资的计算问题。勤工助学大多按小时计酬但学生的工时上报靠手写或者微信发消息月底资助中心汇总时经常出现几个人报重、漏报、或者工时超出规定上限的情况核起来非常痛苦。所以做这套系统不是“为了做一个管理系统而做”而是真的在解决一个实际存在的协作效率问题。也正因为如此这种题目在答辩时特别容易讲清楚——你可以很自然地说出“原有流程有什么问题、系统怎么解决、带来什么价值”这比硬编一个“图书管理”要有说服力得多。1.2 系统的核心用户与角色划分勤工助学系统最大的特点不是功能多而是角色多。我在设计权限模型时把整个系统分了四种角色学生浏览岗位、在线申请、查看录用结果、上报工时、查看工资明细。用工部门老师发布岗位、审核学生申请、确认学生工时。资助中心管理员审核岗位信息、管理全校岗位、处理工资结算、发布公告。系统管理员维护用户、分配角色、管理系统字典数据比如岗位类别、结算周期。这四个角色之间的数据是有主从关系的学生申请岗位产生一条待审核记录部门老师审核后记录变成通过或拒绝通过之后学生才能上报工时工时被部门确认后资助中心才能发起工资结算。你会发现这个链路天然就是一个状态机非常适合用前端状态管理来配合后端流程控制实现。权限方面我选择的是“后端接口校验为主、前端路由控制为辅”的方案。也就是说前端根据用户角色决定显示哪些菜单和按钮但后端每个接口依然会校验权限。这个习惯一定养成因为答辩时老师大概率会问“如果我绕过前端直接调接口你能拦住吗”答案是能因为后端所有写操作都做了角色断言。2. 技术选型Vue前端方案为什么够用2.1 前端框架选择Vue2还是Vue3我在项目里选择的是Vue3 Vite Element Plus这套组合。说实话如果你用的是Vue2 Element UI功能上也没问题但从毕业设计的评分角度来说Vue3代表着更现代的技术栈答辩时能聊的东西更多组合式API、响应式原理、Teleport传送、Fragment片段、Tree-shaking等等随便展开都算亮点。Vue3的组合式APIComposition API对这个项目特别友好。比如岗位列表页需要同时处理筛选条件、分页、加载状态、收藏标记如果全写在options里逻辑会散在data、methods、computed三个区域改起来到处翻。而用setup语法糖把一组相关的变量和函数放在一起维护阅读成本和维护成本都低很多。这一点在后端联调阶段、定位问题时会深有体会。再强调一下Vite的选择。Vite启动项目的速度比Webpack快一个量级开发时改代码热更新基本是秒级响应这直接决定了写代码的心情。我见过不少同学还在用Vue2 Webpack的老配置每次启动要等半分钟项目一大了改个样式都卡这种体验很磨人。所以新项目一律推荐Vue3 Vite没有例外。2.2 关键依赖与配套工具前端除了Vue框架本身还需要配套一套完整工具链。我项目的package.json核心依赖大致如下{ dependencies: { vue: ^3.4.0, vue-router: ^4.3.0, pinia: ^2.1.0, axios: ^1.6.0, element-plus: ^2.5.0, element-plus/icons-vue: ^2.3.0, sass: ^1.70.0, echarts: ^5.5.0 }, devDependencies: { vite: ^5.0.0, vitejs/plugin-vue: ^5.0.0 } }每一样工具都不是凭空选的。axios负责统一处理HTTP请求和Token注入Pinia承担登录用户信息、角色权限、全局配置的共享状态管理Element Plus提供表格、表单、弹窗、上传、日期选择这些现成组件省掉大量重复样式工作Sass用于样式复用比如全局的主题色变量改一处全校生效ECharts用来做资助中心首页的数据看板比如各部门岗位数量、薪资分布、勤工助学参与趋势这类图表。这些工具组合在一起基本覆盖了一个中小型管理系统的全部前端需求。2.3 后端对接方式毕业设计如果只做前端静态页面那基本拿不到高分。我推荐的前后端对接方式是标准的RESTful API后端选择Spring Boot这也是目前最主流、网上参考资料最多的方案。前端通过axios访问后端的/api路径开发环境下利用Vite代理解决跨域生产环境则通过Nginx将同一个域名下的不同路径转发到不同服务。这里有一个特别值得注意的点接口的返回结构必须统一。我在开发中定义了一个通用的响应格式所有接口都按这个格式返回前端可以少写非常多的判断逻辑。{ code: 200, message: 操作成功, data: { } }{ code: 401, message: 登录状态已过期请重新登录, data: null }前端在axios的响应拦截器里只做一件事判断code。若是200就正常返回data若是401就清空本地登录状态、跳转登录页若是其他错误码就统一弹出ElMessage提示。这样一来每个具体接口的业务代码里就不用反复写错误处理逻辑整洁很多。这个“统一响应格式”的习惯是从学生到初级开发最该养成的好习惯之一。3. 核心功能模块拆解与数据库设计3.1 功能模块总览一个完整的勤工助学系统我把它拆成了六个模块。每个模块之间数据有依赖但代码上尽量解耦这样后期维护和分工都方便。首页看板面向不同角色展示不同内容。学生看最新岗位公告部门老师看自己发布的岗位数据资助中心看全校的统计数据。岗位管理岗位发布、编辑、上下架、审核。这是整个系统的核心数据源头。申请管理学生提交申请后进入此模块部门老师审核后流转到下一环节。工时管理学生按月上报工时部门老师确认或驳回支持批量确认。工资结算资助中心根据确认后的工时和岗位时薪计算工资生成发放清单。系统管理用户管理、角色管理、字典管理、操作日志。功能虽多但主线非常清晰岗位 → 申请 → 用工 → 工时 → 工资。这个主线在答辩时可以画一条流程图讲给老师听前后逻辑是通的不是凑功能。3.2 围绕岗位流转的状态机设计“状态”是这个系统里最容易设计砸的地方也是最容易出亮点的地方。我拿岗位信息举例它的状态转变是这样的草稿(0) → 待审核(1) → 已发布(2) → 已暂停(3) → 已结束(4)岗位由用工部门老师创建后默认是“草稿”状态提交审核后进入“待审核”资助中心管理员审核通过后变成“已发布”学生才可见如果用工中途有问题部门老师可以暂停状态变为“已暂停”岗位到期或者招满变为“已结束”。而学生的申请记录它的状态机是另一条线待审核(0) → 已通过(1) → 已驳回(2) → 已录用(3) → 已结束(4)学生提交申请后是“待审核”部门老师审批后变成“已通过”或“已驳回”录用后变成“已录用”岗位结束则申请记录同步变成“已结束”。之所以要把状态单独设计出来是因为每个状态下的可操作动作都不同。比如“待审核”的申请不能被编辑“已发布”的岗位不能被删除只能下架。前端在做按钮层级控制时判断的依据就是这些状态值。这在Vue里可以封装成自定义指令或者计算属性用起来非常顺手。3.3 数据库表设计与关键字段数据库设计决定了这个系统能走多远。我设计了七张核心表这里给出关键字段和关系说明。用户表sys_userid, username, password, real_name, role_id, phone, email, status, create_time这里角色不直接存字符串而是存role_id关联角色表方便后期加角色权限。岗位表jobid, title, type, description, department, location, headcount, salary_per_hour, working_hours_required, status, create_user_id, create_time, end_timesalary_per_hour是小时薪资working_hours_required是岗位每月最低工时要求这两个字段在工资结算时会参与计算。申请记录表job_applicationid, job_id, student_id, status, apply_time, review_time, review_user_id, reject_reason这里status就用我们在3.2里定义的枚举值后续所有查询和统计都依赖它。工时记录表work_hour_recordid, job_id, student_id, work_date, start_time, end_time, total_hours, status, confirm_time, confirm_user_id注意我在设计时加了一个work_date student_id的联合唯一索引防止同一天重复上报同一岗位的工时。这个字段组合解决了一个很现实的重复数据问题。工资结算表salary_settlementid, student_id, month, total_hours, salary_amount, status, generate_time, pay_time, operator_id工资不是实时算的而是每个月结算一次。所以这里有一个month字段每个月每个学生只有一条汇总记录。岗位类别字典表job_typeid, type_name, code, status岗位类别做成字典表而不是写死在代码里是为了让资助中心管理员能在系统里自己维护下拉选项而不需要找开发改代码。这是管理系统设计里一个很常见也很实用的思路。这几张表的关系简单说一下用户表通过role_id区分角色岗位表通过create_user_id关联部门老师用户申请记录表通过job_id关联岗位、student_id关联学生用户工时记录通过job_id和student_id关联申请链路工资结算表通过student_id和month汇总工时。整体来说数据血缘清晰不会有环状引用。4. 前端Vue实现的核心实操4.1 项目初始化和目录结构创建项目这步很多人会忽略但其实有讲究。我在开发中使用的是npm create vitelatest命令选择Vue3 JavaScript模板如果想练TypeScript也可以选TS但会拉长工作量。项目创建好后我会第一时间配好路径别名否则后面import写相对路径会把人逼疯。在Vite配置文件vite.config.js里添加如下配置import { defineConfig } from vite import vue from vitejs/plugin-vue import path from path export default defineConfig({ plugins: [vue()], resolve: { alias: { : path.resolve(__dirname, src) } }, server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这段配置同时干了两件事配置了指到src目录配置了开发环境的代理把/api请求转发到本地8080端口。第一件事让代码里的import路径变成import xxx from /api/xxx清爽很多第二件事解决了开发阶段跨域问题这是前端联调第一个容易卡住的坑。目录结构我习惯这样组织src/ ├── api/ # 所有接口请求 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── layout/ # 整体布局侧边栏、顶栏、主体 ├── router/ # 路由配置 ├── store/ # Pinia状态管理 ├── utils/ # 工具函数如格式化日期、token操作 └── views/ # 页面视图这种分类方式是中小型Vue项目的标准答案。每个模块负责什么一目了然后期加上路由懒加载按需导入页面文件打包体积也能控制住。4.2 路由设计与权限控制先放下路由配了多少个页面不谈只聊“权限控制”这个关键点。勤工助学系统有四种角色学生、部门老师、资助中心管理员、系统管理员如果只有一个后台且菜单一样那这个系统就失去权限管理的意义了。我用的是前端动态路由方案。用户登录后后端返回一个roles数组数组里记录了该用户当前角色编码。前端根据角色编码在路由守卫里动态添加对应的路由表。import { useUserStore } from /store/user router.beforeEach((to, from, next) { const userStore useUserStore() const token userStore.token if (!token) { if (to.path /login) { next() } else { next(/login?redirect${encodeURIComponent(to.fullPath)}) } return } if (to.path /login) { next(/) return } // 动态注册路由 if (!userStore.menusLoaded) { LoadUserMenus() .then(menus { userStore.setMenus(menus) menus.forEach(route router.addRoute(route)) // 重新进入当前路由让新注册的菜单生效 next({ ...to, replace: true }) }) .catch(() { userStore.logout() next(/login) }) return } // 检查目标路由是否有角色限制 if (to.meta.roles !to.meta.roles.includes(userStore.roleCode)) { next(/403) return } next() })这段逻辑里有一个特别容易踩的坑next({ ...to, replace: true })。动态路由注册发生在导航进行中第一次进入目标路由时如果当前路由尚未被加载直接next()会导致页面空白。正确做法是重新导航一次让新注册的路由表生效。我见过很多同学卡在这个问题上页面一直白屏查了半天代码都没发现问题出在这。还有一种更轻量的方案就是写死全部路由但在每个路由的meta里标记允许访问的角色渲染菜单时根据角色过滤。这种方案简单缺点是路由表暴露了一部分不该暴露的信息。两种方案都能用但如果答辩时想有亮点动态路由方案明显更值得聊。4.3 复用组件岗位卡片、申请流程提示勤工助学系统的页面虽然多但很多区块是重复的。比如学生端浏览岗位时岗位信息以卡片形式展示部门老师端查看自己发布的岗位时也类似。所以我做了两个核心复用组件这里分享一下思路。第一个是岗位卡片组件。这个组件接收一个job对象渲染标题、类别、部门、薪资、申请状态支持插槽扩展操作按钮。学生端放“立即申请”按钮部门老师端放“编辑”和“下架”按钮。得益于Vue的作用域插槽同一个组件在不同页面有完全不同的行为却不用复制粘贴两份HTML代码。template el-card classjob-card shadowhover div classjob-card__header span classjob-card__title{{ job.title }}/span el-tag sizesmall typewarning{{ job.typeName }}/el-tag /div p classjob-card__desc{{ job.description }}/p div classjob-card__meta span部门{{ job.department }}/span span时薪¥{{ job.salaryPerHour }}/span span人数{{ job.headcount }}/span /div div classjob-card__footer slot nameactions :jobjob/slot /div /el-card /template第二个是申请状态步骤条。学生的申请状态有五个每一步的含义都不一样。如果只用表格里的一个Tag显示“已通过”学生根本不知道自己卡在哪个环节。我使用Element Plus的ElSteps组件把状态映射成步骤条一眼就能看清当前进行到哪一步了。template el-steps :activecurrentStep simple el-step title提交申请 description学生提交申请 / el-step title部门审核 description等待部门老师审批 / el-step title录用确认 description已录用等待报到 / el-step title工时确认 description按月确认工时 / el-step title工资发放 description资助中心结算工资 / /el-steps /template组件的抽离原则就是一句话同一个功能在页面中出现超过两次就值得封装成组件。这不仅减少了代码重复也让项目结构看起来有设计感而不是一坨重复HTML堆叠。用Vue的插槽slot做差异化的扩张是前端开发的基本功也是面试官常问的点在这个项目里能练得很透。4.4 与后端接口对接的注意点前端开发过程中联调是最耗时也最容易出错的一环。我从这个项目里总结出几个高频注意点。第一个是Token的注入方式。项目使用JWT作为登录凭证前端在axios封装时统一加请求头这样发请求时不需要每个页面自己拼Authorization。axios.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config })第二个是文件导出功能的处理。工资结算模块里资助中心管理员要能导出Excel清单。如果直接用axios去请求导出接口并接收blob很容易出现“下载下来的文件内容是乱码”的情况。因为响应拦截器可能已经把JSON处理逻辑跑了一遍。正确做法是单独创建一个不受拦截器影响的导出函数。function exportFile(url, params) { axios({ url, method: get, params, responseType: blob, headers: { Authorization: Bearer ${localStorage.getItem(token)} } }).then(res { const blob new Blob([res.data]) const link document.createElement(a) link.href URL.createObjectURL(blob) link.download decodeURIComponent(res.headers[content-disposition].split()[1]) link.click() URL.revokeObjectURL(link.href) }) }第三个是日期参数处理。工时记录按月份筛选时前端传2025-06这种月份字符串后端只要把字符串解析成月份范围即可。但如果后端接口定义的是时间戳或日期对象前端就要先格式化尔后传参。我建议项目中统一所有日期类参数一律传字符串避免前后端日期解析不一致导致查询结果偏移。这一条写在接口文档最前面能省去大量排查时间。5. 联调阶段常见问题与排查实录5.1 跨域问题开发环境下前端跑在localhost:5173后端跑在localhost:8080浏览器的同源策略会阻止请求。解决方式就是我刚才提到的Vite devServer代理配置。但如果配置完仍然报跨域一般有两个原因后端Controller没有加CrossOrigin或者全局CORS配置没写好。前端代理配置的路径跟实际请求路径不一致。排查方法很简单打开浏览器开发者工具看Network面板里被拒绝的请求确认请求路径是否以/api开头。如果路径对上了但依然CORS报错大概率是后端CORS配置没有允许对应的请求头比如Authorization。建议前后端约定好所有接口都以/api开头这样代理配置只需要匹配一个统一前缀避免每个模块都要单独配置。这个习惯在后端用Spring Boot时尤其重要一个WebConfig类搞定所有跨域。5.2 登录状态失效与401跳转Token有效期内用户操作没问题但Token过期后用户如果继续点击页面按钮接口会统一返回401。如果前端不处理会出现双击按钮后页面一直转圈、但没有任何提示的死局。前端在axios响应拦截器中做了统一处理axios.interceptors.response.use( response { return response.data }, error { if (error.response.status 401) { localStorage.clear() window.location.href /login } return Promise.reject(error) } )注意这里在跳转登录页前把本地存储清了否则登录页可能还会拿着旧的用户信息继续渲染出现反复横跳的情况。5.3 工时重复提交学生上报工时的时候如果手一抖双击了提交按钮就会在工时记录表里插入两条一模一样的记录。后端即使做了联合唯一索引前端也应该给用户即时反馈不能等到月底对账才发现数据多了。前端做法很简单在提交按钮上绑定loading状态发送请求期间禁止再次点击。Element Plus的el-button组件自带loading属性设为true时按钮就会处于禁用状态这是一个成本极低但收益极大的防重复提交手段。我还发现一个坑有些同学习惯在submit函数执行完后直接把form表单重置了。这听起来没毛病但如果后端保存时校验失败了表单数据被清掉用户还得重新填一遍。正确做法是先请求后端成功后再重置表单。5.4 表格大数据卡顿优化岗位列表页里资助中心管理员可以查看全部岗位加上历史数据可能上千条。如果一次性全查出来渲染到表格中页面会明显卡顿尤其是搜索和排序时体验极差。我用的方案是后端分页 前端筛选。ElTable组件绑定后端返回的分页数据每次只渲染当前页的20条数据。前端筛选条件通过查询参数传给后端由后端生成SQL查询而不是把全量数据拉到前端内存里用JavaScript过滤。这套方案写起来不复杂但效果立竿见影。如果非要应对几千条数据不做分页的极端情况也可以考虑用ElTable的虚拟滚动但普通管理系统场景分页就足够用了。6. 这份毕业设计/项目如何延展升级6.1 可以扩展的功能点虽然这套系统已经能完成勤工助学管理的主流程但站在项目迭代的角度还有很多功能可以继续扩展让项目显得更完整、更有社会价值。第一个是消息通知模块。现在申请状态变更、工时确认结果都只能在系统内查看学生不可能一直盯页面。可以对接微信服务号通过模板消息把“你的岗位申请已通过”直接推送到学生微信。这在实际校园场景里非常实用。技术方案上可以集成一个消息队列或者后台定时任务把未读消息推送出去。第二个是数据可视化大屏。目前首页看板只展示了简单的统计数据可以升级为一个大屏页面实时展示全校各岗位的在岗人数、工时利用率、月度工资支出趋势。前端用ECharts的折线图、柱状图、饼图组合后端提供对应统计接口。视觉上会非常震撼答辩演示时效果特别好。第三个是贫困生认定辅助。勤工助学本质上是资助体系的一部分可以增加受资助学生的家庭经济情况登记表由资助中心审核后在岗位申请时对经济困难学生优先推荐。这个功能虽然偏业务向但体现了系统的社会温度在项目中写出来是一个很好的加分点。6.2 部署上线建议项目最后必须能够演示给别人看所以部署环节不能马虎。我的建议是使用前后端分离部署方案后端Spring Boot打成jar包放到服务器上运行前端Vue项目构建后生成dist静态资源文件夹交给Nginx托管。Nginx配置要注意一个细节前端页面路由如果使用了history模式刷新页面时会出现404。因为Nginx默认找不到对应的静态文件。解决办法是在Nginx配置中添加一个fallback配置将所有未命中的路径都重定向到index.html。location / { try_files $uri $uri/ /index.html; }这一点不做的话系统上线的第一天就会有人来报“老师我刷新一下页面就白屏了”。我在这上面吃过亏印象特别深刻。部署好之后建议实际测试一下完整业务流程学生注册账号、浏览岗位、提交申请、部门老师审核、学生上报工时、部门确认、管理员结算工资。只要能跑通这个闭环系统基本上就没有大的功能缺陷了。我在实际操作中的体会是这类管理系统的开发难点从来不在某个单一技术上而在于把真实的业务流程抽象成数据和状态流转。这个项目把岗位、申请、工时、工资四个环节串联起来每一步都有清晰的输入输出和状态约束做完之后对“信息管理系统”这个品类会有非常立体认知。如果你正在准备毕业设计或者想练习一个能写进简历的Vue实战项目不妨就从这套基于VUE的校园勤工助学系统开始把文中提到的动态路由、权限控制、组件复用、接口联调逐一实践一遍。等你把这条链路完整走通不仅是Vue水平会上一个台阶对项目从0到1的完整生命周期也会有真正的体感。
返回列表