1. 这类申报管理系统的核心痛点与模块拆分思路
1.1 创新创业项目申报业务到底在管什么
先聊点背景。高校或企业内部的创新创业教育中心,日常最繁重的工作之一就是项目申报管理:申报通知发布、学生在线填报、导师审核、专家评审、立项公示、中期检查、结题验收、经费报销登记。这一条线走下来,涉及的角色有学生、指导老师、院系管理员、中心管理员、评审专家,不同角色看到的数据不一样,操作权限也不一样。很多单位早期用Excel表格收材料,文件名像"最终版3_真不改了.docx"这种,收上来的附件命名混乱,审核进度靠人工催,统计报表靠手搓,一个申报周期下来管理员累得够呛。
所以这套基于SpringBoot+Vue+MyBatis架构+MySQL数据库的申报管理系统,解决的就是把"申报-审核-评审-立项-结题"全流程数字化。源码完整跑通之后,学生端可以维护个人资料、填写申报书、上传附件、查看审核进度;导师端可以审核指导记录、签署意见;专家端可以打分和填评审意见;管理员端可以发布申报批次、分派评审专家、汇总统计、导出报表。一句话概括:让每种角色只看到自己该看的,让每个审核节点都有痕迹,让管理者不用再跟Excel死磕。
这套系统的受众很明确:一是高校创新创业学院、教务处、科技处的信息化建设负责人;二是培训机构或软件公司的开发人员,拿到源码可以直接二次改造交付;三是想系统学习SpringBoot+Vue前后端分离项目怎么落地的开发者。前两类是使用方,第三类是改造方,这篇文章我按改造方的视角来拆解,同时也会点明业务设计上哪些地方是使用方最容易提出定制需求的点。
1.2 技术选型:为什么是SpringBoot+Vue+MyBatis这一套
先说结论:这套组合放到今天依然是中小企业级管理系统里最稳的组合之一,尤其是"申报管理"这种典型的CRUD+状态流转+文件上传业务,SpringBoot+Vue+MyBatis几乎是为它量身定做的。
SpringBoot负责后端服务。它的价值在于"约定大于配置",内嵌Tomcat,打成一个Jar包就能跑,不再需要单独部署WAR到外置容器。对于申报系统这种内部管理系统,部署环境通常只有一台普通服务器,SpringBoot的单Jar部署方式明显比传统SSH那套省心太多。另外SpringBoot的生态太成熟了,Spring Security做权限、Spring Validation做参数校验、Spring Data Redis做缓存,全都是现成的轮子,遇到问题搜一下就有答案。
MyBatis负责数据库访问层。有人可能会问,现在JPA也很流行,为什么选MyBatis?我的经验是,申报管理系统的查询非常"变态"——条件组合多、动态排序多、统计报表多。比如"查询2024年度所有校级立项项目中,预算金额大于5万且指导老师职称是副教授的项目",这种动态SQL用MyBatis的<where>、<if>标签写起来非常灵活,可控性比JPA强。而且MyBatis的SQL是明写的,DBA拿去就能review,对于需要跟学校信息中心数据库打交道的机会少,但SQL可读性要求高的场景,MyBatis更合适。
Vue负责前端界面。申报管理系统交互不算复杂,但也绝对不简单:动态菜单、多级审核表单、附件上传、数据可视化图表,Vue的响应式数据绑定和组件化开发让这些东西实现起来很顺手。Vue 2 + Element UI是经典组合,Vue 3 + Element Plus是当下新项目的默认选择,这套源码采用的是Vue 2 + Element UI,稳定、资料多,跑起来不会有那么多"版本坑"。
MySQL负责数据存储。良心之选,免费、稳定、团队里人人都会,申报系统的数据量和并发量根本到不了MySQL的上限。
总结一下这套技术栈的核心逻辑:SpringBoot把后端服务"标准化",MyBatis把复杂查询"显性化",Vue把管理界面"组件化",MySQL把数据存储"简单化"。四者各司其职,组合出一个易维护、易二次开发、招人也好招的系统底座。
2. 数据库模型设计:申报状态机与审批链落表的关键细节
2.1 核心表结构梳理
数据库是整个申报系统的心脏。我拿到源码后的第一件事不是看Controller,而是先看数据库脚本,表结构设计能直接体现这个系统的业务成熟度。
这套项目的数据库脚本我梳理下来,核心表大致如下:
| 表名 | 用途 | 关键字段 |
|---|---|---|
| sys_user | 用户表 | user_id, username, password, real_name, role_id, dept_id |
| sys_role | 角色表 | role_id, role_name, role_key |
| sys_menu | 菜单权限表 | menu_id, parent_id, menu_name, path, perms |
| biz_project | 项目申报表 | project_id, project_name, applicant_id, category, budget, status |
| biz_project_attachment | 附件表 | attachment_id, project_id, file_name, file_url, upload_time |
| biz_review | 评审记录表 | review_id, project_id, reviewer_id, score, opinion, review_time |
| biz_batch | 申报批次表 | batch_id, batch_name, start_time, end_time, status |
| biz_expert_group | 专家分组表 | group_id, group_name, leader_id |
这里面最有看头的两个设计点:
第一,用户和角色分离。sys_user只存用户基本信息和role_id,角色权限是admin、teacher、student、expert这样几类还是更细粒度,取决于业务方实际的组织架构。更精细的做法是做用户-角色-菜单三层关联,也就是后面会说的RBAC模型。这套系统在这个基础上留了dept_id,也就是部门字段,这就为"同一个角色但不同学院只能看到本院数据"预留了数据权限控制点,这是非常聪明的设计,申报管理这种多学院场景一定需要数据隔离。
第二,申报批次和数据主档分离。biz_batch和biz_project分开,意味着同一套项目申报表可以被多个批次复用。比如上半年是"互联网+创新创业大赛校内选拔赛"批次,下半年是"大学生创新创业训练计划"批次,两个批次共用同一个项目表,只是batch_id不同。这个设计避免了大量冗余字段,也让统计报表可以按批次横向对比。
2.2 状态流转怎么在表里表达
申报系统的核心逻辑之一就是状态流转。一个项目从"草稿"到"已提交"再到"院系审核通过""专家评审中""已立项""中期检查通过""已结题",每个状态都对应不同的操作权限和页面展示。
这套源码里,状态字段是一个status整型或者varchar类型,我建议用tinyint加一个状态枚举类去对应,不建议直接在业务代码里到处写死数字。比如定义ProjectStatusEnum:
public enum ProjectStatusEnum { DRAFT(0, "草稿"), SUBMITTED(1, "已提交待审核"), DEPT_APPROVED(2, "院系审核通过"), EXPERT_REVIEWING(3, "专家评审中"), APPROVED(4, "已立项"), MIDTERM_PASS(5, "中期检查通过"), COMPLETED(6, "已结题"), REJECTED(-1, "已驳回"); private final Integer code; private final String desc; // 构造函数和getter省略 }状态流转的落表方式有几种做法:
- 单表状态字段,就是
biz_project.status,每次更新直接覆盖。优点是简单直观,缺点是没有历史轨迹,用户想查"这个项目什么时候从院系审核变成专家评审的"就没有记录。 - 状态流转日志表,单独一张
biz_status_log记录每个节点的时间、操作人、操作内容。这套系统我看了下是两种结合的——主表存当前状态,日志表存流转过程。
这个设计非常合理。日常查询和前端展示用主表的status字段,不用连表,性能好;审计追踪用日志表,什么时候谁做了什么一查便知,满足"痕迹管理"的要求。后面如果要加"退回修改"功能,直接在状态枚举里加一个BACK_TO_DRAFT,日志表自动记录,主表状态更新,逻辑闭环。
2.3 申报系统的数据权限隔离
申报系统有一个普遍需求:学生只能看到自己的项目,导师只能看到自己指导的项目,院系管理员只能看到本学院的项目,中心管理员才能看到全部。这不是简单的角色权限能解决的,属于"数据权限"范畴。
这套系统的处理思路是在SQL层做数据隔离,核心是利用dept_id和user_id。具体做法是:在MyBatis的Mapper层定义一个DataScope的拦截器或者注解,在SQL解析阶段自动拼接AND dept_id = 当前用户所属部门的条件。比如查询项目列表时:
SELECT p.*, u.real_name AS applicant_name FROM biz_project p LEFT JOIN sys_user u ON p.applicant_id = u.user_id WHERE p.deleted = 0 AND p.dept_id = #{deptId} -- 这个条件由数据权限拦截器自动拼上这里要注意一个问题:超级管理员和普通管理员的拼接条件不一样,所以数据权限表达式最好是动态的。常见的做法是用户表上加一个data_scope字段,值是ALL、DEPT、SELF三个级别,拦截器判断这个字段决定拼接SQL的逻辑。用这种方案,前端代码一行不用改,不同角色天然看到不同范围的数据,安全性和开发效率都能兼顾。
3. SpringBoot后端:鉴权、文件上传与业务接口的实现路径
3.1 基于JWT的登录态设计与角色权限控制
管理系统的第一个技术门槛就是登录认证和权限控制。这套源码采用的是JWT(JSON Web Token)方案,不是传统的Session方案。为什么选JWT?因为前后端分离架构下,前端Vue和后端SpringBoot是分开部署的,Session的跨域会话保持问题很麻烦,要么配CORS带cookie,要么搞Session共享;JWT则把用户身份信息加密放在Token里,前端每次请求在Header里带上Authorization: Bearer <token>,后端解析校验即可,天然适合前后端分离,也适合后面扩展移动端。
登录认证的实现链路:
用户提交用户名密码到/auth/login,后端用AuthenticationManager调UserDetailsService查库,BCryptPasswordEncoder校验密码,成功后生成JWT返回前端。前端把Token存在本地,路由拦截器判断没有Token就跳转到登录页。
权限控制用的是Spring Security + 自定义注解:
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface RequiresPermission { String value(); // 例如 "project:add" }在Controller方法上标注@RequiresPermission("project:review"),然后写一个AOP切面,在方法执行前解析当前用户的权限集合,如果没有对应权限直接抛AccessDeniedException。这样做的好处是权限校验逻辑和业务逻辑完全解耦,加新接口时只需标注注解,不用在方法体里写一堆权限判断。
实际开发中有一个容易踩的坑:JWT的过期时间设置。很多系统上线后被客户投诉"用着用着就掉线",大概率是Token过期时间太短,或者没有做"续期"机制。我建议有效期设置为8小时,同时在前端请求拦截器里做"Token即将过期时自动刷新"的逻辑,而不是等过期了再让用户重新登录。管理系统的用户习惯是"打开网页挂着,想起来的时候再操作一下",上来就让人重新登录,体验很差。
3.2 项目申报模块的接口设计
项目申报是核心业务模块,接口设计得好不好,直接决定前端的开发效率和后端的可维护性。这套源码里的接口风格是RESTful和传统POST混合,比如:
POST /api/project/save新增或更新项目申报书POST /api/project/submit提交申报(状态从草稿变成待审核)GET /api/project/detail/{id}查询项目详情POST /api/review/submit专家提交评审意见GET /api/project/export导出申报项目汇总Excel
我特别想说一下为什么"保存"和"提交"要拆成两个接口。初学者往往只做一个save接口,把数据入库就完事。但申报系统的业务规则是:草稿状态下的项目可以任意修改,一旦提交,关键字段就不能动了(或者必须走"撤回"流程)。如果保存和提交流程混在一起,前端要么传一堆状态参数,要么后端判断逻辑写得特别绕。拆成两个接口后,save接口只管落库,submit接口先做业务校验再更新状态,职责清晰,测试也好写。
文件上传接口这里单独说一下,申报书必然要传文档/PDF/图片。这套源码用的是MultipartFile方式:
@PostMapping("/upload") public R<String> upload(MultipartFile file) { // 校验文件大小、类型 // 生成UUID文件名,防止重名覆盖 // 存储到本地磁盘或OSS // 返回文件访问URL }这里一定要注意:文件存储路径不能写死成绝对路径,要配置到application.yml里,并且使用相对路径或可通过配置项切换的路径。另外文件类型校验不要只在前端做,后端必须做二次校验——别问我怎么知道的,生产环境被传过.exe文件的痛一次就够了。
3.3 MyBatis下复杂查询的XML写法与分页坑
MyBatis的核心是Mapper XML,这套系统的分页查询用了PageHelper插件。PageHelper的用法很简单:
PageHelper.startPage(pageNum, pageSize); List<ProjectVO> list = projectMapper.selectProjectList(query); PageInfo<ProjectVO> pageInfo = new PageInfo<>(list);但PageHelper有个著名的坑:startPage必须在查询语句之前,且只对下一条查询生效。如果你在调用PageHelper.startPage()之后、执行selectProjectList之前又执行了别的SQL操作,分页就会失效或者作用到错误的查询上。更隐蔽的是,如果Mapper方法里有多个SQL语句,比如先查了一个子查询/另一个表的操作,PageHelper分页会作用到第一条SQL上,导致返回结果分页错乱。所以我的习惯是:分页查询的方法里只写一条主查询SQL,子查询用EXISTS或IN嵌套进去,不要拆成多条语句。
另外,MyBatis的XML里动态查询条件建议用<where>标签配合<if>:
<select id="selectProjectList" resultType="com.example.vo.ProjectVO"> SELECT p.*, u.real_name AS applicant_name FROM biz_project p LEFT JOIN sys_user u ON p.applicant_id = u.user_id <where> p.deleted = 0 <if test="batchId != null"> AND p.batch_id = #{batchId} </if> <if test="status != null"> AND p.status = #{status} </if> <if test="keyword != null and keyword != ''"> AND (p.project_name LIKE CONCAT('%', #{keyword}, '%') OR u.real_name LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY p.create_time DESC </select><where>标签会自动处理第一个条件前的AND,非常方便。还有一个比较容易忽略的细节:LIKE查询不要直接写LIKE '%${keyword}%',要使用CONCAT('%', #{keyword}, '%'),避免SQL注入。
4. Vue前端:动态菜单、申报表单与文件预览的实现细节
4.1 前端路由与菜单权限的动态渲染
前端这边第一步要做的是"根据登录用户的角色,动态生成菜单和路由"。不同角色登录后看到的菜单完全不一样:学生看到"项目申报""我的项目""消息通知",管理员看到"用户管理""批次管理""项目审核""汇总统计"。
实现的常规套路是这样的:
- 用户登录成功后,后端返回该用户的路由表/权限标识集合(比如
['project:add', 'project:query', 'user:manage']); - 前端拿到权限集合后,通过
router.addRoutes()动态注册该用户可访问的路由; - 侧边栏菜单不是写死在代码里的,而是根据动态路由表递归渲染出来。
这套源码里的后端对应接口是GET /api/user/routes,返回结构是树形的菜单数据:
[ { "id": 1, "parentId": 0, "name": "项目申报", "path": "/project", "component": "layout/index", "children": [ { "id": 11, "parentId": 1, "name": "新建申报", "path": "/project/create", "component": "project/create" } ] } ]前端用一个asyncRouter方法把返回的菜单数据转换成Vue Router需要的路由对象格式,再动态注册。注意这里有一个细节:刷新页面时,Vuex里的路由信息会被清空,必须在全局前置守卫router.beforeEach里做"刷新后重新拉取路由"的处理,不然用户一按F5就白屏了。这个坑我当年踩了不少次,每次都是"登录进去好好的,一刷新就回到登录页",后来发现是刷新后路由注册被重置导致的。
动态菜单的权限控制还有一个细节:按钮级权限。有的系统里,同一个页面,不同角色看到的操作按钮不一样。比如管理员在项目详情页能看到"审核通过"按钮,学生只能看到"撤回"按钮。这需要前端自定义一个v-permission指令或者写一个hasPermission公共方法,在渲染按钮的时候判断当前用户是否拥有对应权限码。
<el-button v-if="hasPermission('project:review')" type="primary">审核通过</el-button>4.2 申报表单的交互设计和校验
申报表单是学生使用频率最高的页面,交互设计的好坏直接影响使用体验。这套系统的申报表单主要包含:项目名称、项目类别、申报批次、预算金额、项目周期、项目成员、项目简介、指导老师信息、附件上传等几个区块。
表单校验用的Element UI自带的rules机制,这里有两个要点:
一是表单的联动校验。项目类别选择"创新训练项目"时,"预期成果"字段变为必填,选择"创业实践项目"时,"工商注册信息"字段变为必填。实现方式是在字段的change事件里动态更新校验规则:
handleCategoryChange(val) { if (val === 'innovate') { this.rules.expectedOutcome[0].required = true; } else { this.rules.expectedOutcome[0].required = false; } this.$refs.form.validateField('expectedOutcome'); }二是金额字段的处理。预算金额这种字段,数据库存的是DECIMAL,但拿回到前端后很容易出现浮点精度问题。最好的方式是在后端序列化时用BigDecimal转String,前端展示和提交都用字符串,到了后端再转BigDecimal,这样可以避免0.6100000000001这种诡异的问题。我在很多项目里被这种精度问题坑过,这不是申报系统特有的,但凡是涉及金额的模块都要注意。
4.3 文件上传与预览处理的实用做法
申报系统绕不开文件上传,学生要传申报书PDF、项目附件、导师评分表等。Element UI的el-upload组件配合后端的/api/upload接口是标准做法:
<el-upload :action="uploadUrl" :headers="{ Authorization: getToken() }" :on-success="handleUploadSuccess" :before-upload="beforeUpload" :file-list="fileList" > <el-button size="small" type="primary">点击上传</el-button> </el-upload>before-upload里做文件大小和格式的前端校验,on-success拿到后端返回的附件ID和URL后保存到表单数据里。附件删除的交互建议做成"逻辑删除",即在附件表里将deleted字段置为1,而不是物理删文件。原因很简单:一旦误删或者后续审计需要追溯,逻辑删还能恢复,物理删就真没了。文件本身的清理可以挂一个定时任务,每天晚上删除"已逻辑删除且超过30天"的附件记录,兼顾数据安全和存储成本。
文件预览这里要单独讲一个坑。申报系统里PDF预览是最常见的需求,但如果附件是Word文档,前端直接预览就比较麻烦。业界主流的方案是用office在线预览服务,但问题是很多机构的文件包含敏感信息,不适合传到第三方服务。保守做法是后端用OpenOffice或LibreOffice把doc/docx无头转换成PDF,再提供预览。还有一种轻量做法:利用浏览器的PDF预览能力,直接把PDF文件URL放在新窗口打开。
对于视频文件,相关的热搜词里提到了vue播放m3u8,说明有人在这套系统里需要支持上传并在线播放教学视频。m3u8是HLS流媒体协议的分片索引文件,Vue里播放m3u8一般用video.js加videojs-contrib-hls插件,或用plyr加hls.js。不过这类场景通常是教育中心直录播平台的需求,如果只是申报管理,视频上传的需求不强。如果后面要扩展这个模块,建议视频文件单独走对象存储,直传服务器会占用大量带宽,影响系统其他业务的响应速度。
5. 部署上线前必须处理的性能与安全问题
5.1 数据库连接池和索引优化
很多开发者在本地把系统跑通之后,直接部署上线就不管数据库配置了,等用户量一上来就开始崩溃。我在部署这套系统时,会先做两件事:
第一件,检查数据库连接池配置。SpringBoot默认的Tomcat连接池最大连接数是100,具体值要根据你实际的并发量调整。申报管理系统通常不会太高并发,但上线初期经常出现"连接池满了"的报错,原因往往是有慢SQL或者连接没有被正确释放。建议配置:
spring: datasource: hikari: maximum-pool-size: 50 minimum-idle: 10 connection-timeout: 30000 idle-timeout: 600000HikariCP是目前SpringBoot的默认连接池,性能好,监控也方便。
第二件,检查表索引设计。申报系统的查询场景主要有:按用户ID查我的项目、按状态查待审核列表、按批次查项目清单、按项目名称模糊查。对应的索引设计:
ALTER TABLE biz_project ADD INDEX idx_user_id (applicant_id); ALTER TABLE biz_project ADD INDEX idx_status (status); ALTER TABLE biz_project ADD INDEX idx_batch_id (batch_id); ALTER TABLE biz_project ADD INDEX idx_create_time (create_time);注意,索引不是越多越好,每个索引都会拖慢插入和更新的速度。申报系统写操作不频繁,可以把查询索引做得充分一点;但如果系统里有高频写入的场景,索引就要精简。
5.2 文件存储策略与备份
前面上传接口已经提到文件存储,这里展开讲一下生产环境的策略。部署在单机上的时候,上传的文件就放服务器本地目录,Nginx配置一个/uploads/路径的静态映射即可。但是我建议从第一天就做好目录规划:
/data/app-uploads/ ├── project/ # 项目申报书附件 │ ├── 2024/ │ └── 2025/ ├── avatar/ # 用户头像 ├── review/ # 评审意见附件 └── temp/ # 临时文件按业务模块和年份分目录,一方面是查找方便,另一方面是备份策略可以做差异化——项目附件多备份,临时文件少备份。
数据库备份用mysqldump定时任务即可:
mysqldump -u root -p --all-databases --single-transaction --routines --triggers > /data/backup/mysql_$(date +%Y%m%d).sql建议每天凌晨做一次全量备份外加binlog日志备份,保留最近7到30天的备份文件。文件存储备份可以用rsync同步到另一台机器。我见过不少单位服务器磁盘坏了直接丢失所有申报数据的案例,教训就是:数据库要异地备份,文件要异机备份。
5.3 常见部署问题与踩坑记录
最后把部署过程中最容易遇到的问题列出来。这些问题我基本都在实际项目中遇到过,每条都是血泪总结:
提示:以下问题按出现频率从高到低排列,建议部署前逐条对照检查。
问题1:后端启动失败,提示端口被占用。原因通常是本机已经跑了一个Tomcat或者其他服务占用了8080端口。不要急着去杀进程,先netstat -ano | findstr 8080(Windows)或lsof -i:8080(Linux)看一下是被谁占了,如果是无关进程直接换一个端口更省事,SpringBoot改端口就在application.yml的server.port。
问题2:前端页面能打开,但接口全部404。这个现象99%是前后端分离部署时跨域问题或者Nginx代理配置不对。跨域解决方式有两种:后端加CORS配置,或者前端通过Nginx把/api请求代理到后端服务。推荐用Nginx代理,因为生产环境最后还是统一走Nginx入口,顺便可以用同一个域名区分前端静态资源和后端API:
server { listen 80; server_name your-domain.com; location / { root /data/frontend/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /uploads/ { alias /data/app-uploads/; } }注意前端路由是history模式的话,try_files一定要配,否则刷新子路由页面会404。
问题3:上传大文件超时或失败。SpringBoot默认单次请求最大1MB,超过就报错。上传申报书PDF动辄几十MB,一定要确认配置:
spring: servlet: multipart: max-file-size: 100MB max-request-size: 100MBNginx侧的client_max_body_size也要一起调大,否则后端没报错,Nginx先拦了。
问题4:时区问题导致时间显示相差8小时。MySQL连接串里一定要加serverTimezone=Asia/Shanghai,否则默认用UTC时间,所有时间字段都会差8个小时。数据库连接URL是整套系统的细节中最容易被忽略但是后果最明显的配置。
问题5:部署后页面能打开,但白屏或者JS报错。大概率是前端打包时接口地址写死了开发环境的IP。Vue CLI项目的.env.production里面配置VITE_API_BASE_URL(或Vue2的VUE_APP_API_BASE_URL)要用相对路径或正式环境的域名,构建后代码里不要残留localhost。
5.4 系统上线后的扩展思考
项目申报管理系统这类业务系统,上线只是一个开始,后续需求往往会朝这几个方向演进:
第一是流程可配置化。现在的审批流程可能是写死在代码里的,后续业务方可能会提出"国家级、省级、校级项目走不同的审批流"的需求。要应对这个,可以引入工作流引擎(如Flowable、Activiti),或者设计一套简单的流程配置表,把节点和审批人做成数据库记录而不是硬编码枚举。小团队没必要一上来就引入重引擎,先把配置表做出来,后面真复杂了再迁移。
第二是消息通知。申报进度变化、审核被驳回、专家评审结果公布,这些场景都需要通知学生和老师。现在很多系统的做法是待办中心+站内信,进阶的会接企业微信或钉钉机器人。如果后续要加消息模块,建议优先做站内信+邮件,短信和微信通知作为可选项。
第三是数据可视化。申报系统积累了大量的申报数据,学院申报数量对比、专业方向分布、项目经费统计,这些都是管理层喜欢看的。ECharts的图表组件加一个数据看板,把统计接口的数据展示成柱状图、饼图、趋势线,属于性价比很高的加分功能。前端用Vue集成ECharts非常直接,后端写几个聚合查询接口就完事。
我在实际部署这一类系统的过程中最大的心得体会是:这类管理系统的技术难点其实不多,真正的难点在于把业务状态理清楚、把数据结构设计好、把权限模型考虑周全,以及把各种"边缘情况"都照顾到。比如学生填了一半没提交的草稿怎么办、同一项目的多次修改记录怎么留痕、审核被驳回后学生修改再提交的流程怎么走、两个不同批次的同一个项目如何约束唯一性,这些细节才是决定一个系统好不好用的关键。
这套SpringBoot+Vue+MyBatis的项目源码把主干功能都实现了,剩下的就是结合具体业务形态去充实血肉。拿到源码后建议先跑通,再理清业务逻辑,最后才做二次开发,顺序千万别反了。