又到一年毕设季,Java方向的同学来问我选题的特别多,十个里有八个都在找“难度适中、技术栈主流、参考资料多、答辩还稳”的题目。如果只能给一个答案,我通常会推荐基于SpringBoot+Vue的OA管理系统。办公自动化(OA)领域足够经典,技术路线贴近企业真实项目,无论你是要写程序、凑文档还是讲代码,都有大量成熟经验可以参考。这篇文章就把整个项目从功能规划、后端实现、前端落地、数据库设计、部署运行,一直拆到论文和答辩的“一条龙”完整链路,给选了这道题或者想快速搭一套可用管理系统做课设的同学一个清晰参照。
1. 为什么OA系统是Java毕设里最稳的“安全牌”
1.1 课题价值:查重、资料量和工作量的三重平衡
先说一个很多同学忽略的事实:毕设翻车往往不是题目太难,而是选题“太偏”。比如有人选“基于区块链的电子合同存证系统”,听起来很唬人,实际查资料查一个月都看不明白,最后论文写得像天书,答辩根本讲不出所以然。OA系统恰好相反,它是企业信息化的典型场景,网上能搜到的论文、源码、技术博客数量非常庞大,你不需要从零摸索,重点是“理解之后消化重组”。
从工作量角度看,OA系统的模块边界特别清晰:登录、用户管理、部门管理、考勤、审批、公告、日志,每个功能都能独立成章。这意味着你可以非常灵活地控制工作量——想做简单点,做完登录+用户+公告就能成型;想做得完整,加上考勤、审批流、权限管理,工作量也完全可控。对比那些研究型课题,你要在“流程复杂度”和“创新性”之间找平衡,OA系统天然避开了这个坑。
还有一个很实际的点:查重。OA系统的论文框架、功能描述、技术路线高度成熟,但你只要在具体实现细节上多用自己实际写的代码和真实截图,把需求分析、数据库设计结合自己的表结构去写,重复率很容易降下来。最怕的是整段照搬别人的设计说明,那种一眼就会被看穿。
1.2 技术栈的容错率:为什么它不会让你答辩翻车
SpringBoot+Vue这套组合在高校毕设里已经算“标准配置”了,答辩老师见过的次数比你自己做的次数都多。这其实是好事——老师问的问题基本都能预期,不会出现老师自己都没听过的冷门技术,然后把你问倒的情况。
后端SpringBoot是老牌框架,资料全、坑少、跑起来快;前端Vue配合Element UI或Element Plus,界面不用自己从零写样式组件,拖拖改改就能出一个“企业级后台”效果。MySQL做数据库,Redis可加可不加,权限用JWT或者Sa-Token都行。每个技术点你都能在社区里找到落地方案,哪怕当场报错,网上一定有人踩过同一个坑。
这套技术栈还有一层隐藏优势:贴近就业市场。答辩老师问你“这个项目是干嘛用的”时,你可以理直气壮地说“这是企业里常见的管理系统形态”,面试时也能拿出来当项目经验讲。相比之下,选题如果太“玩具”,比如只做一个单页面的注册登录,答辩和面试都拿不出手。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统功能地图:一张OA系统到底要还原哪些办公场景
2.1 核心功能清单:用户、考勤、审批、公告缺一不可
我梳理了一下,一套比较完整又能撑起论文篇幅的OA系统,至少需要覆盖以下模块:
| 功能模块 | 核心功能 | 参与角色 |
|---|---|---|
| 登录认证 | 账号密码登录、验证码、Token鉴权、退出登录 | 所有用户 |
| 员工与部门管理 | 员工信息维护、部门树管理、角色分配 | 系统管理员 |
| 考勤管理 | 上下班打卡、异常记录、月度考勤汇总 | 普通员工、直属主管 |
| 审批中心 | 请假、报销、加班申请,多级审批流转 | 所有用户 |
| 公告通知 | 公告发布、已读状态、置顶显示 | 管理员发布,全员查看 |
| 系统管理 | 菜单管理、角色权限、操作日志 | 系统管理员 |
这个清单是“平均值”,你可以根据自己的时间裁剪。比如考勤模块如果觉得复杂,可以简化成“每日打卡记录+月度查询”;审批中心如果不想做多种审批类型,做一个请假审批就足够撑起演示了。核心原则是:功能宁精勿杂,做出来的每个模块都要能演示、能截图、能写进论文,凑数量不做完反而扣分。
从我实际帮人审代码的经验看,最理想的规模是“一个大核心+三个小模块”。大核心选审批流,因为它最能体现业务逻辑;小模块选员工管理、公告、考勤。这样论文第5章“系统实现”能写出至少3节的实质内容,不会全是截图充数。
2.2 RBAC权限模型:同一套菜单,不同角色看到不同内容
OA系统做权限,10个里有9个会用RBAC(基于角色的访问控制)模型,这个一定要掌握。它的核心思想很简单:用户不直接关联权限,而是通过“角色”这个中间层间接获得菜单和操作权限。
具体到系统里就是三张核心表和两张关联表:用户表sys_user、角色表sys_role、菜单表sys_menu,加上用户角色关联表sys_user_role、角色菜单关联表sys_role_menu。用户登录之后,后端根据用户ID查出角色,再由角色查出菜单树,返回给前端。前端拿到这棵菜单树之后动态渲染左侧导航,于是不同的人登录进来看到的是不同的菜单。
设计角色的时候建议做成三级:admin(管理员)、manager(部门主管)、employee(普通员工)。管理员管系统配置,主管能审批下属的请假单和处理考勤异常,普通员工就只能看到自己的模块。演示的时候切换三个账号登录,每个人看到的首页和菜单都不一样,这个效果非常直观,答辩时老师都会多看两眼。
要注意的是,菜单权限只解决“看得到看不到”的问题,还要配合操作权限。比如普通员工能进入审批中心,但只能看到“我的申请”以及“发起申请”按钮,不能看到“待我审批”这个Tab。这个可以在后端接口做角色判断,也可以在菜单配置时把按钮权限也做成一个权限标识。毕设阶段用菜单树控制页面入口就够了,按钮级权限点到为止,不用做得太深。
2.3 审批流的业务闭环:从请假单的提交到状态归档
审批流是OA系统的灵魂,也是最值得讲的一块。我们把一条请假单的生命周期完整走一遍,你就知道后端该怎么设计:
- 员工填写请假单,提交后状态变为“审批中”,此时该单出现在直属主管的“待我审批”列表。
- 主管点开详情,选择通过或驳回。通过后状态变为“已通过”;如果驳回,状态回到“待提交”,员工可以重新编辑并再次提交。
- 审批的每一步都要生成一条审批记录,写明操作人、操作时间、审批意见,这些痕迹保存在审批记录表里。
- 员工查看记录的详情页时,能按时间线看到“谁在什么时候做了什么决定”。
这里有一个容易忽略的细节:驳回后的单子不能直接删除,而是要允许员工修改后再重新提交。如果表设计里没有“待提交”这个状态,驳回后单据就卡死了,业务闭环断掉,答辩老师顺着逻辑问两句就会露馅。
状态字段建议用整型枚举而不是字符串,0-待提交,1-审批中,2-已通过,3-已驳回。配合一个current_approver字段存当前待审批人ID,就能支撑“谁能审批”的判断。还可以加一个level字段做多级审批,比如员工请假超过3天需要总监再批,但毕设阶段做到两级审批已经能拿高分了,不必强行拉复杂度。
3. SpringBoot后端的两块硬骨头:登录鉴权与审批状态流转
3.1 技术选型与工程结构:别一上来就堆全家桶
后端推荐用SpringBoot 2.7 + MyBatis-Plus + MySQL 5.7或8.0的组合。Redis可以暂缓,不是必须的,登录状态用JWT管理就够了。如果你为了显得“高级”把Redis加进去,那答辩老师就会顺着问缓存策略、过期时间、缓存一致性,解释不清反而扣分。MyBatis-Plus强烈建议用,它把单表CRUD封装好了,你的精力可以集中在业务逻辑上,代码量少一大半,报错率也低。
工程结构建议按这个分层来组织:
text复制com.example.oa
├── controller // 接口层:接收请求、返回Result
├── service // 业务层:核心业务逻辑
├── mapper // 数据访问层:MyBatis-Plus的Mapper接口
├── entity // 实体类:对应数据库表
├── dto // 数据传输对象:接收前端参数
├── vo // 视图对象:返回给前端的数据
├── config // 配置类:拦截器、跨域、JWT配置
├── common // 公共类:统一Result返回体、异常处理、枚举
└── utils // 工具类:JWT工具、日期工具
分层清楚的好处不只是“好看”,而是写论文的时候第5章可以一层一层讲,代码讲解的时候也能顺着层次推进。A同学当初做这个项目时没分层,所有逻辑堆在Controller里,到了写文档阶段差点崩溃,只能返工拆包。
3.2 JWT登录与拦截器:不要让接口裸奔
登录接口是整套系统的门面,实现方式网上教程很多,但关键点要理解透。用户提交用户名密码后,后端校验账号密码,密码必须加密存储,推荐用BCrypt而不是MD5。登录成功后服务端生成一个JWT令牌返回给前端,令牌里包含用户ID、用户名、角色标识这些非敏感信息,前端把令牌存起来放到请求头里,后续每个接口都带着它来访问。
核心代码如下,逻辑很清晰:
java复制@PostMapping("/login")
public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) {
User user = userService.getByUsername(loginDTO.getUsername());
if (user == null || !passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
return Result.error("用户名或密码错误");
}
if (user.getStatus() != 1) {
return Result.error("账号已被禁用");
}
String token = jwtUtil.generateToken(user);
List<String> roles = roleService.getRoleCodesByUserId(user.getId());
return Result.ok(new LoginVO(token, user.getNickname(), roles));
}
登录之后的接口保护,毕设阶段用拦截器就足够,不必把Spring Security全家桶都搬进来。拦截器做的事情很简单:拿到请求头里的token,如果为空或解析失败就返回401,放行白名单接口(登录、验证码、静态资源)。这里有个细节坑:前端跨域请求会先发一个OPTIONS预检请求,如果你的拦截器没有放行OPTIONS,前端就会莫名奇妙报跨域错误,这个问题能卡一个下午。正确的拦截器开头应该是这样:
java复制@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 放行预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
String token = request.getHeader("token");
if (!StringUtils.hasText(token)) {
response.setStatus(401);
return false;
}
try {
jwtUtil.parseToken(token);
} catch (Exception e) {
response.setStatus(401);
return false;
}
return true;
}
提示:跨域配置别只在一个Controller上加@CrossOrigin,建议写一个全局CorsFilter,让所有接口统一支持跨域。之前有个同学只在登录接口加了注解,结果其他接口全被跨域拦截,排查了半天才找到原因。
3.3 审批状态机:用三个字段维持完整的业务闭环
审批流的后端逻辑是这套系统里最值得讲的“业务亮点”。核心表里真正起作用的字段只有三个:status(状态)、current_approver(当前审批人)、level(当前审批层级)。
提交审批时,状态从0变为1,current_approver设为直属主管的ID。主管审批时,判断是“通过”还是“驳回”:通过就把状态改为2,驳回就把状态改为0并把current_approver清空。如果做了两级审批,通过后先判断当前level是否已到最大层数,没到就level+1并更新current_approver,到了就置为2。
每次操作还要写一条审批记录:
java复制ApprovalRecord record = new ApprovalRecord();
record.setApplicationId(applicationId);
record.setOperatorId(currentUserId);
record.setAction(action); // PASS / REJECT
record.setComment(comment);
record.setCreateTime(LocalDateTime.now());
approvalRecordMapper.insert(record);
这一步很多人会漏。漏掉的结果是:请假单上只有一个最终状态,中间过程完全不可见,答辩时老师问“这个审批流程每一步你们怎么留痕的”,你只能支支吾吾。有了审批记录表,你甚至可以做“审批进度时间线”,前端按创建时间倒序展示,效果非常加分。
关于并发问题,毕设阶段通常不用做太复杂。如果老师追问“两个人同时审批怎么办”,你可以回答数据库层面用行锁或乐观锁版本号来处理,实际代码里可以给申请表加一个version字段做乐观锁。只要你能说出方案,这个追问就变成你的加分项。
3.4 分页与条件查询:MyBatis-Plus的高频封装
OA系统的列表页面几乎全部需要分页+条件筛选,比如请假记录按类型筛选、按状态筛选、按申请人筛选。MyBatis-Plus的Page和LambdaQueryWrapper组合起来非常顺手,示例代码:
java复制@Override
public IPage<LeaveApplication> pageQuery(int pageNum, int pageSize, Long userId,
String type, Integer status) {
LambdaQueryWrapper<LeaveApplication> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(LeaveApplication::getUserId, userId)
.eq(StringUtils.hasText(type), LeaveApplication::getType, type)
.eq(status != null, LeaveApplication::getStatus, status)
.orderByDesc(LeaveApplication::getCreateTime);
return leaveApplicationMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
}
这个方法里有个细节值得学习:eq的第二个参数是条件,只有条件成立时才会把这个等值条件拼进SQL。这样前端不传某个筛选条件时,这个条件就被自动忽略,不用自己写一堆if判断去拼接SQL。我在很多项目里见过因为没这么写,导致“状态筛选失效”“时间筛选报错”的问题,都是因为wrapper条件拼接没处理好。
4. Vue前端落地:页面结构、动态菜单与联调避坑
4.1 工程目录与页面组织:按模块分文件夹,别什么都塞views里
前端我用的是Vue,组件库Element UI或Element Plus都行。如果你是第一次做前后端分离项目,我的建议是跟着成熟后台模板走,比如vue-element-admin这类开源模板,省去配置路由和布局的初始工作量。但要注意,别把模板里用不到的代码原样留着,打包出来几百个文件,答辩演示时加载慢还会被问“这是什么”。
页面目录按业务模块组织,比按路由层级组织更清晰。可以参考这样:
text复制src
├── api // 接口请求封装,按模块拆分
├── assets // 静态资源
├── components // 公共组件
├── layout // 后台布局框架:侧边栏、顶栏
├── router // 路由配置
├── store // 全局状态管理
├── views
│ ├── login // 登录页
│ ├── dashboard // 工作台首页
│ ├── employee // 员工与部门
│ ├── attendance // 考勤
│ ├── approval // 审批中心
│ ├── notice // 公告
│ └── system // 系统管理
└── utils // 工具:axios封装、token存取
每个页面组件要做到“只调api层的方法,不直接写axios”。这样做的直接好处是,后端接口路径一旦调整,只需要改api目录下的一个文件,不用满项目搜索请求地址。A同学当时就是把请求地址写死在页面里,后端改了接口路径,前端每个页面都要跟着改,浪费了一个下午。
4.2 路由守卫与动态菜单渲染
前端有两件必须要做的事:路由守卫和动态菜单。路由守卫负责“没登录就不让进”,动态菜单负责“不同角色看到不同页面”。
路由守卫逻辑很简单,放在router的beforeEach钩子里:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (!token && to.path !== '/login') {
next('/login')
} else {
next()
}
})
动态菜单要注意“刷新页面后不能丢”。因为菜单是根据后端返回的角色权限生成的,如果菜单保存在Vuex内存里,刷新就没了,重新请求又会闪烁。建议的做法是:登录后把菜单树存到本地缓存,刷新时先用本地缓存渲染,同时再向后端拉一次更新。这个细节做好了,演示时会显得非常流畅。
前端调用接口要统一封装axios实例,在请求拦截器里自动带上token,在响应拦截器里统一处理401状态码跳登录页。这样业务代码里不需要每个请求都自己处理token和错误状态,干净很多。Element UI的表单校验组件也要尽量用上,至少登录框的用户名密码必填校验要做,不然空值提交后端还得处理空指针。
4.3 前后端联调最容易卡住的三个问题
联调阶段是毕设最消耗时间的环节,我把高频问题整理成了表格,遇到问题可以先对照排查:
| 现象 | 根因 | 解决办法 |
|---|---|---|
| 前端调用接口报跨域 | 后端没有全局CORS配置,或拦截器拦截了OPTIONS预检 | 写全局CorsFilter;拦截器开头放行OPTIONS |
| 页面刷新后404 | Vue Router用了history模式,服务器没做兜底 | 改用hash模式,或Nginx配置try_files指向index.html |
| 时间字段显示"2024-01-01T08:00:00" | 后端LocalDateTime序列化格式没指定 | 配置文件里加jackson时间格式,前端日期组件绑定value-format |
| 上传文件超过限制报错 | 后端默认最大文件体积只有1MB | 在application.yml里调大multipart限制 |
跨域问题值得多说几句。如果是开发环境,前端配个代理更省事,在vue.config.js里设置proxy指向后端的8080端口,代码里请求路径写/api/xxx,由代理转发。如果是生产环境部署,直接用Nginx反向代理,把/api前缀的请求转发给后端服务,这样前后端就同源了,跨域问题直接消失。
5. 数据库设计:十四张表如何撑起整个OA系统
5.1 表结构总览:权限、业务、流程三块各司其职
数据库设计好不好,直接影响写论文的效率。一套常规的OA系统,表结构大致围绕“权限体系、核心业务、审批流程”三块展开。下表是一个可以直接参考的清单:
| 表名 | 用途 | 归属板块 |
|---|---|---|
| sys_user | 用户表:账号、密码、姓名、部门、状态 | 权限体系 |
| sys_role | 角色表:管理员、主管、员工 | 权限体系 |
| sys_user_role | 用户角色关联表 | 权限体系 |
| sys_menu | 菜单表:菜单树、路由地址、图标 | 权限体系 |
| sys_role_menu | 角色菜单关联表 | 权限体系 |
| sys_dept | 部门表:树形结构 | 权限体系 |
| oa_attendance | 考勤记录表:打卡时间、类型、异常标记 | 核心业务 |
| oa_leave | 请假申请表:类型、起止时间、原因、状态 | 审批流程 |
| oa_approve_record | 审批记录表:审批人、意见、动作、时间 | 审批流程 |
| oa_notice | 公告表:标题、内容、置顶、发布人 | 核心业务 |
| sys_login_log | 登录日志表:登录时间、IP、结果 | 系统管理 |
这些表不一定全都要做,但至少表名和设计思路要在论文的数据库设计章节里体现出来。很多同学只建了五张表就敢写“系统设计”,答辩老师一看E-R图太空,就直接问“你的业务数据都放哪”,场面会很尴尬。表数量在十到十四张左右是比较理想的,既能撑起数据库设计章节,也不至于多到最后维护不过来。
5.2 审批记录表的隐藏价值:让每一项操作都有迹可循
表单设计里最容易被忽视的,恰恰是那套系统的审计能力。审批记录表oa_approve_record就是体现审计能力的关键,它记录每一次审批动作,包括操作人、操作时间、审批意见、动作结果。没有这张表,你的请假单只能显示“最终通过”或“最终驳回”,中间发生了什么完全不可见。
这张表的设计其实很简单,主键ID、申请单ID、操作人ID、动作(PASS/REJECT)、意见、创建时间。查询时按申请单ID把记录取出来,前端按时间线展示,就是一张很专业的审批流水。
我在实际辅导时发现,能把审批记录表做好并讲明白的同学,答辩成绩普遍不差。因为这证明你做过真实的业务系统,不是照着教程敲代码的工具人。反过来,如果连审批记录都没有,老师问“审批痕迹怎么保留”就只能哑火。
5.3 字段与脚本习惯:交付前必须做好的几件事
建表时字段规范要统一。主键我习惯用id BIGINT自增;业务表带上create_time和update_time;逻辑删除用deleted字段,这样删除用户时不会真从前端把数据抹掉,答辩时还能演示“删除后数据库里其实还有一条记录”,在系统安全性上是个可以主动提的点。create_time这类时间字段在Java里用LocalDateTime对应,MyBatis-Plus的自动填充功能可以在插入和更新时自动赋值,不用每个Service里都写一行setCreateTime。
还要记得字段别直接叫数据库保留字。比如desc、level这类词,尽量加个前缀或者用反引号包起来。之前就有同学建了一张表,字段名叫desc,导致Mapper查询SQL报错,排查半天才发现是字段名撞了保留字。
交付前一定要写初始化SQL脚本,把部门数据、管理员账号、角色、菜单权限都写进一个init.sql。我见过太多样例数据都没造的项目,演示时现场注册新用户再慢慢配菜单,十分钟过去了系统还没进入正题。好的演示节奏应该是:启动系统后直接用三个角色账号轮流登录,60秒内把权限差异展示出来,再走一遍完整审批流程,老师对系统的好感度立刻就不一样。
6. 从运行到答辩:部署、论文、演示的一条龙经验
6.1 环境版本与启动顺序:新手最容易卡住的环节
这套系统最稳妥的环境组合是:JDK 1.8(或17,如果你熟悉的话)、Maven 3.6+、MySQL 5.7或8.0、Node 14或16、npm 6+。后端导入项目后,先改application.yml里的数据库用户名密码,再执行init.sql建库脚本,然后运行主启动类。前端在项目根目录执行npm install,安装完成后执行npm run serve,按提示访问开发地址。
启动顺序有个经验:先启动后端,确认8080端口能访问;再启动前端,确认接口能通。很多同学前后端一起启动,报错了分不清是后端问题还是前端代理问题。后端启动后随手用浏览器访问一下登录接口,能返回JSON说明环境基本没问题。
生产环境部署如果想演示给老师看,建议用Nginx。后端打成jar包用java -jar运行,前端npm run build之后把dist目录扔给Nginx托管。Nginx配置里两段关键内容:一是location /指向打包后的前端文件并写try_files兜底路由刷新;二是location /api/把请求转发给后端服务。配置对了之后,整个系统就是一个域名搞定,不再有跨域揪心的问题。
注意:Nginx的proxy_pass地址如果带路径,会把匹配到的/api前缀改掉;如果不带路径,会把完整路径转发过去。要根据你后端Controller的请求映射来决定,用错了接口会404。这是Nginx反向代理最常见的坑。
6.2 从文档到论文:没有现成文档时怎么快速成文
毕设论文一般绕不开这几章:绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试。把项目做完再写论文会轻松很多,但多数同学是边做边写。建议顺序是:先写数据库设计章节,因为表结构定了,你的功能边界就定了;再写需求分析,把用例图画出来,功能需求和非功能需求各写一段;系统设计章节配系统架构图、功能模块图、E-R图、关键业务流程图;系统实现章节按模块贴截图、贴核心代码、配文字说明。
截图一定要从你自己的系统里截,别用别人的图。每张截图下面写三段式:这页是干什么的、操作流程是什么、关键代码涉及哪部分逻辑。论文里贴代码不要整页贴,挑核心方法十几行就够了,重点是讲清逻辑,不是靠代码堆字数。测试章节至少要做功能测试和部分接口测试,做一张测试用例表格,列出用例编号、测试步骤、预期结果、实际结果,比空泛的“系统测试通过”有说服力得多。
6.3 答辩演示与源码讲解的节奏控制
答辩的时候演示系统,我建议走一条固定的“剧本线”:管理员登录,展示部门管理和菜单权限差异;再以员工身份登录,提交一条请假申请;切到主管账号审批,查看审批记录;最后回到管理员后台查看登录日志和操作日志。这条线把系统的核心卖点全部串起来,全程五分钟搞定,节奏非常紧凑。
代码讲解环节很多人会犯一个错误,就是从头到尾念代码。正确的讲法是:先展示数据库表结构,画出“用户-角色-菜单”的关联关系;再讲后端关键接口,比如登录接口和审批接口;最后讲前端如何调用接口和渲染菜单。这样老师听下来会觉得你的思路特别清晰,因为你是从数据模型到业务逻辑到界面一层层往上讲的,这个顺序很重要。
讲到具体代码的时候,准备一两个“老师一定会问”的答案。比如“为什么用JWT不用Session”,你可以答:前后端分离架构下,Token方案天然无状态,不需要在服务端存会话信息,水平扩展友好,移动端也能复用。再比如“分页是怎么实现的”,你说出MyBatis-Plus的Page对象和LambdaQueryWrapper的条件拼接,这个答案就够标准了。回答不要求多有深度,但一定要能自洽。
最后再分享一个实际经验:演示前把所有测试数据在本地完整跑一遍,尤其是审批流程的“通过”“驳回”两条路径和“刷新页面菜单不丢”这个点。我见过不少同学演示时现场造数据,结果不是没配置角色权限,就是审批流程卡在状态更新上,最后只能干瞪眼。OA这类系统重业务,你把一条请假单从提交到归档讲明白,把权限模型讲明白,老师基本上不会为难你。代码写得不是最炫没关系,清晰完整比花里胡哨重要得多。
