ARTICLE DETAIL

资讯详情

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

Spring Boot+Vue学生综合素质测评系统实战:从表结构到前后端分离

Spring Boot+Vue学生综合素质测评系统实战:从表结构到前后端分离 简介这套基于Spring Boot与Vue框架的学生综合素质测评系统源码面向Java课程设计、毕业设计及前后端分离技术学习者。系统围绕学生多维度素质评价场景涵盖后端业务逻辑、RESTful API接口、前端页面交互与数据库脚本能够帮助学习者完整体验从需求分析到系统实现的全过程。压缩包共565个文件大小22.74MB除33个Java源码与对应class字节码外还包含222个JavaScript脚本、72个CSS样式、30个HTML页面、98个XML配置及SQL脚本等各类型文件按职责划分清晰便于按模块拆解学习。目前已有410人学习下载。借助这套资源读者可掌握Spring Boot快速搭建、Vue组件化开发、前后端数据交互、权限控制等关键技能项目目录结构完整前端资源与后端逻辑分离是拓展毕业设计功能或深入研究前后端整合实践的优质参考。1. 学生综合素质测评系统到底在测什么每到学期末教务处老师最头疼的不是考试而是把一堆纸质测评表录入Excel再按德智体美劳各项权重做加权汇总。一个年级几百号人自评、互评、师评三套分数光校验总分是不是对得上就要加班好几天。学生综合素质测评系统就是把这套线下流程搬到线上管理员配置指标和权重学生登录后完成自评与互评班主任录入师评系统自动汇总排名并导出报表。做这个项目的核心难点不在功能多而在三块指标权重怎么建模不写死、三种评分角色怎么控制权限、成绩汇总逻辑怎样保证和手工算出来的分毫不差。这篇文章面向的是要用 Spring Boot Vue 把这套系统从零做出来并交付的开发者会给到可以直接照抄的表结构、接口设计和前端页面实现也会把最容易翻车的地方提前标出来。2. 为什么是Spring BootVue前后端分离的选型逻辑与骨架搭建2.1 选型理由Spring Boot解决什么Vue解决什么做学生综合素质测评这种典型的管理信息系统市面上主流方案就是 Spring Boot 做后端接口、Vue 做前端页面。Spring Boot 的价值在于自动配置和起步依赖一个内嵌 Tomcat 的 jar 包直接跑起来不需要单独装 Web 服务器。测评系统的需求边界很清晰用户管理、指标配置、评分录入、成绩汇总没有复杂的分布式诉求单体应用加关系型数据库就是最合理的形态没必要引入微服务那套东西。Vue 这边组件化和响应式数据绑定对测评场景特别合适。一个打分页面里指标列表、评分滑块、已提交状态、剩余时间倒计时全部是状态驱动的界面更新。Vue 的插槽机制还能让指标项的内容区灵活替换比如某个指标需要上传佐证材料另一个指标只要打分不用把整张页面拆得七零八落。相比 jQuery 时代手动操作 DOMVue 把页面状态和 DOM 的同步问题直接抹掉了前端开发效率高出一大截。也有团队直接用若依框架这类现成的 Spring Boot 后台管理系统脚手架省掉用户管理和权限模块的开发。但我个人不建议在测评系统这种业务逻辑占比高的项目里用原因有两个一是若依生成了大量代码后改业务要顺着它的生成规则走后期维护跟拆黑匣子一样费劲二是测评系统的权限模型比通用后台要复杂同一个学生既是自评者又是互评者这种角色交叉在通用 RBAC 里要绕好几个弯。自己搭骨架每一层代码都清楚出问题能顺着调用链一路查到 SQL。2.2 前后端分离的接口约定与数据格式前后端分离的第一步是把接口约定定下来否则两边各写各的联调时全是摩擦。我一般会在项目启动时就固定三件事接口路径前缀、统一返回结构、认证方式。接口路径前缀统一用/api开头后面跟资源名比如/api/student、/api/assessment、/api/weight。返回结构统一是{ code: 0, message: success, data: {...} }code 为 0 表示成功非 0 表示业务错误。前端拿到响应先判断 code再取 data不会出现有的接口直接返回数组、有的返回对象的情况。认证这块测评系统建议用 JWT。登录接口校验用户名密码后签发 token前端把 token 存到 localStorage每次请求在 Authorization 头带上。后端用一个拦截器统一解析 token把当前用户信息塞进 ThreadLocal后续 Controller 里直接取不需要每个接口都去查一遍 Redis。这里要注意 token 过期时间不能设太短学生互评可能对着屏幕磨蹭半小时才提交token 过期了再提交就会报 401体验很差一般设 2 到 4 小时比较合适。2.3 项目骨架搭建后端初始化与前端工程创建后端工程我习惯用 Spring Initializr 生成Java 版本选 8 或 11Spring Boot 版本选 2.7.x 这一代。2.3.x 和 2.6.x 也能用但 2.7.x 的依赖兼容性更好MyBatis 和 Druid 的整合资料也最全。Spring Boot 3.0 以后要求 Java 17学校机房和老服务器不一定支持没必要冒这个险。生成后改pom.xml加上 MyBatis Plus、MySQL 驱动、Druid 连接池、Lombok 这几个关键依赖。MyBatis Plus 比原生 MyBatis 省事的地方在于单表 CRUD 不用写 XML测评记录、指标列表这种简单查询直接调用BaseMapper的方法就行只有成绩汇总这种多表关联才需要手写 SQL。前端用 Vue CLI 创建工程记得选 Vue Router 和 PiniaVue 3 推荐用 Pinia 替代 Vuex。整个工程结构我习惯这样组织backend ├── src/main/java/com/school/assessment │ ├── controller # 接口层 │ ├── service # 业务逻辑 │ ├── mapper # MyBatis Plus Mapper │ ├── entity # 数据库实体 │ ├── config # 拦截器、跨域、JWT配置 │ └── common # 统一返回、异常处理 └── src/main/resources ├── mapper # 手写SQL的XML文件 └── application.yml frontend ├── src │ ├── api # axios请求封装 │ ├── router # 路由配置 │ ├── stores # Pinia状态 │ ├── views # 页面组件 │ └── components # 公共组件 └── vite.config.jsapplication.yml里最需要注意的是数据库连接和 MyBatis 配置。数据源用 Druid 的话连接池参数一定要给足学校机房并发不高但一个班几十个人同时提交测评连接池默认的 8 个连接很容易被打满。max-active设 50initial-size设 5min-idle设 5max-wait设 10000 毫秒。MyBatis 这边要打开驼峰映射数据库字段student_name自动映射到实体属性studentName否则查出来的对象一堆 null。server: port: 8080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/assessment_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: your_password druid: initial-size: 5 min-idle: 5 max-active: 50 max-wait: 10000 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case打开后SQL 查出来的assessment_type会自动赋给assessmentType字段少写一堆resultMap。log-impl设为 StdOutImpl 会在控制台打印 SQL开发阶段排查问题很有用上线前记得去掉。3. 把测评流程落成表结构五张核心表与后端接口实现3.1 从测评流程到字段建模指标、权重与评分角色学生综合素质测评的业务流程拆开看就四步管理员配置测评方案学生自评和互评教师师评系统汇总成绩。这里最容易设计错的地方是把“测评方案”和“测评记录”混在一张表里。我做过一版方案指标就写在测评记录表里结果学期中要调整某条指标的权重历史数据全乱了。正确的做法是把指标体系和测评实例分开指标体系是模板定义有哪些指标、每项占多少分、权重是多少测评实例是某一次测评活动的快照某年某学期用了哪套模板、参与范围是哪个班级、自评互评师评的时间窗口是什么时候。权重这块建议在指标表里设计一个weight字段一级指标之间做权重分配比如德育占 30%、智育占 40%、体育占 15%、美育占 10%、劳育占 5%。二级指标在一级指标内再分配几分比如智育下面的“课堂表现”占 20 分、“期末成绩”占 80 分。汇总时按权重逐级算最终满分是 100 分。权重不要写死在代码里管理员在页面上调权重后端重新计算汇总这就避免了每次改权重都要动代码发布的尴尬。3.2 核心数据表设计建表 SQL 与字段说明测评系统最少需要五张表student学生表、assessment_template测评模板表、assessment_indicator测评指标表、assessment_record测评记录表、user账号表。user表单独拆出来不要把账号字段塞进student表。因为教师和管理员也要登录系统用户表要存角色字段role取值admin、teacher、student。学生表和用户表通过user_id关联这样用户表管登录认证学生表管学籍信息互不干扰。CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, role varchar(20) NOT NULL COMMENT admin/teacher/student, status tinyint DEFAULT 1 COMMENT 1启用 0禁用, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户账号表; CREATE TABLE student ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 关联user表, student_no varchar(20) NOT NULL COMMENT 学号, name varchar(50) NOT NULL COMMENT 姓名, class_name varchar(50) NOT NULL COMMENT 班级, grade varchar(20) NOT NULL COMMENT 年级, PRIMARY KEY (id), UNIQUE KEY uk_student_no (student_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT学生信息表; CREATE TABLE assessment_template ( id bigint NOT NULL AUTO_INCREMENT, name varchar(100) NOT NULL COMMENT 模板名称如2024-2025第一学期, semester varchar(30) NOT NULL COMMENT 学期标识, status tinyint DEFAULT 0 COMMENT 0草稿 1启用 2归档, self_start_time datetime DEFAULT NULL COMMENT 自评开始时间, self_end_time datetime DEFAULT NULL COMMENT 自评结束时间, peer_start_time datetime DEFAULT NULL COMMENT 互评开始时间, peer_end_time datetime DEFAULT NULL COMMENT 互评结束时间, teacher_start_time datetime DEFAULT NULL COMMENT 师评开始时间, teacher_end_time datetime DEFAULT NULL COMMENT 师评结束时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT测评模板表; CREATE TABLE assessment_indicator ( id bigint NOT NULL AUTO_INCREMENT, template_id bigint NOT NULL COMMENT 关联模板, parent_id bigint DEFAULT 0 COMMENT 父指标id一级指标为0, name varchar(50) NOT NULL COMMENT 指标名称, score decimal(5,2) NOT NULL COMMENT 分值, weight decimal(5,2) DEFAULT NULL COMMENT 权重百分比, sort_order int DEFAULT 0 COMMENT 排序号, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT测评指标表; CREATE TABLE assessment_record ( id bigint NOT NULL AUTO_INCREMENT, template_id bigint NOT NULL COMMENT 关联模板, indicator_id bigint NOT NULL COMMENT 关联指标, student_id bigint NOT NULL COMMENT 被测评学生, assessor_id bigint NOT NULL COMMENT 评价人学生或教师, assess_type varchar(10) NOT NULL COMMENT self/peer/teacher, score decimal(5,2) NOT NULL COMMENT 评分, comment varchar(500) DEFAULT NULL COMMENT 评语, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_record (template_id, indicator_id, student_id, assessor_id, assess_type) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT测评记录表;assessment_record表里的唯一索引是关键它保证了同一个人在同一个模板下、对同一个指标、评同一个学生只能有一条记录。前端重复提交、用户刷新页面后狂点提交按钮数据库层面直接挡住不需要额外写判重代码。3.3 后端接口实现成绩汇总的业务逻辑后端接口按资源划分核心接口有这几个模板管理接口、指标管理接口、评分提交接口、成绩汇总接口。评分提交接口要注意事务控制一次自评要插入十几条记录中途任何一条失败都要回滚否则学生评分只提交了一半成绩汇总时少算了一项分数。我习惯用Transactional注解加在 Service 方法上同时捕获异常抛出业务异常让全局异常处理器统一返回错误信息。Service public class AssessmentServiceImpl implements AssessmentService { Autowired private AssessmentRecordMapper recordMapper; Autowired private AssessmentIndicatorMapper indicatorMapper; Transactional(rollbackFor Exception.class) public void submitAssessment(AssessmentSubmitDTO dto) { // 1. 校验时间窗口 AssessmentTemplate template templateMapper.selectById(dto.getTemplateId()); Date now new Date(); if (self.equals(dto.getAssessType())) { if (now.before(template.getSelfStartTime()) || now.after(template.getSelfEndTime())) { throw new BusinessException(当前不在自评时间窗口内); } } // 2. 校验指标列表是否完整 ListLong indicatorIds dto.getScores().stream() .map(AssessmentItem::getIndicatorId).collect(Collectors.toList()); Long count indicatorMapper.selectCount( new LambdaQueryWrapperAssessmentIndicator() .eq(AssessmentIndicator::getTemplateId, dto.getTemplateId()) .in(AssessmentIndicator::getId, indicatorIds)); if (count ! indicatorIds.size()) { throw new BusinessException(提交的指标与当前模板不匹配); } // 3. 批量插入评分记录 for (AssessmentItem item : dto.getScores()) { AssessmentRecord record new AssessmentRecord(); record.setTemplateId(dto.getTemplateId()); record.setIndicatorId(item.getIndicatorId()); record.setStudentId(dto.getStudentId()); record.setAssessorId(dto.getAssessorId()); record.setAssessType(dto.getAssessType()); record.setScore(item.getScore()); recordMapper.insert(record); } } }部分字段我做了简化但核心逻辑就是这样。第一步校验时间窗口学校的测评流程有严格时间安排自评、互评、师评各开放一周过期就不能提交。第二步校验指标完整性防止前端只提交了一半指标最典型的情况是网络中断后前端只把部分评分发出去了后端如果直接入库成绩汇总就是缺项的。成绩汇总接口是测评系统里最容易被问“这个分数怎么算出来的”的地方。汇总逻辑按三级走先按被评学生、评分类型聚合出每个指标的均分再乘权重累加出一级指标得分最后把三类评分按比例合成总分。自评占 20%、互评占 30%、师评占 50% 是常见的默认比例这个比例应该在模板表里加字段self_ratio、peer_ratio、teacher_ratio存起来而不是写死在代码里。SELECT student_id, indicator_id, assess_type, AVG(score) AS avg_score FROM assessment_record WHERE template_id #{templateId} GROUP BY student_id, indicator_id, assess_type这个 SQL 拿到的就是每个被评学生各指标三类评分的均分。后面在 Java 里做权重累加因为指标层级和权重都在数据库里查出来以后用 Map 按 parentId 分组逐层往上算。4. Vue前端从登录到测评页路由守卫、状态管理与打分组件4.1 前端路由设计与动态菜单Vue 前端的路由设计直接影响使用体验。测评系统的角色有三种每种角色看到的菜单不一样管理员看到模板配置和成绩管理教师看到师评录入和成绩查看学生看到自评和互评入口。路由分静态路由和动态路由两部分。静态路由是登录页、404 页这些所有角色都能访问的页面。动态路由根据登录用户的角色动态生成登录成功后从后端接口拿菜单权限前端再router.addRoute()把对应页面注册进去。// 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, name: Dashboard, component: () import(/views/Dashboard.vue) } ] }, { path: /:pathMatch(.*)*, component: () import(/views/NotFound.vue) } ] // 动态路由表按角色区分 export const dynamicRoutes { admin: [ { path: template, component: () import(/views/admin/TemplateList.vue), meta: { title: 测评模板 } }, { path: student-manage, component: () import(/views/admin/StudentManage.vue), meta: { title: 学生管理 } } ], teacher: [ { path: teacher-assess, component: () import(/views/teacher/TeacherAssess.vue), meta: { title: 师评录入 } } ], student: [ { path: self-assess, component: () import(/views/student/SelfAssess.vue), meta: { title: 自评 } }, { path: peer-assess, component: () import(/views/student/PeerAssess.vue), meta: { title: 互评 } } ] }路由守卫是这套设计的关键。beforeEach里检查有没有 token没有就跳登录页有 token 再查用户角色把属于这个角色的动态路由加进去。要注意addRoute之后需要用next({ ...to, replace: true })重新导航一次否则首次访问动态路由会匹配不到而落到 404 页。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) return } if (token !router.hasRoute(to.name)) { const userInfo JSON.parse(localStorage.getItem(userInfo)) const role userInfo.role const roleRoutes dynamicRoutes[role] || [] roleRoutes.forEach(r { router.addRoute(Index, r) }) next({ ...to, replace: true }) return } next() })如果角色很多、菜单层级很深用静态路由表按角色写死的方式就不够灵活了后端返回菜单树是更常见的做法。但测评系统角色就三类菜单层级最多两级用静态配置的方式反而更直观改菜单不用改后端接口前端发一版就行。4.2 状态管理Pinia存用户信息与测评状态测评系统里跨页面共享的状态主要就两类登录用户信息和未提交的评分草稿。用户信息用 Pinia 存一份登录成功后写入localStorage里再备份一份刷新页面后从本地恢复避免刷新就掉登录态。评分草稿这个需求容易被忽略。学生做互评时一个班级三四十个人评到一半可能被叫走或者电脑断电。如果没有草稿功能再回来全部重新评很容易引发投诉。我的做法是在 Pinia 里维护一个draftMapkey 是被评学生 IDvalue 是评分对象每次打分组件触发updateScore时同步写入localStorage的assessment_draft字段。提交成功后把对应学生的草稿从draftMap和localStorage里移除。// stores/assessment.js import { defineStore } from pinia export const useAssessmentStore defineStore(assessment, { state: () ({ draftMap: JSON.parse(localStorage.getItem(assessment_draft) || {}) }), actions: { saveDraft(studentId, scores) { this.draftMap[studentId] scores localStorage.setItem(assessment_draft, JSON.stringify(this.draftMap)) }, clearDraft(studentId) { delete this.draftMap[studentId] localStorage.setItem(assessment_draft, JSON.stringify(this.draftMap)) } } })草稿数据存localStorage而不是 sessionStorage是因为学生可能关掉浏览器再回来。存大的评分对象也没问题一个学生 15 个指标每个指标一个分数一个评语JSON 序列化后也就几 KB。到了时间窗口关闭后端提交接口会拒绝前端再从草稿里清掉该学生的记录不影响新的一轮。4.3 测评打分页组件指标渲染与提交拦截打分页是学生和教师使用频率最高的页面交互设计要简单直接。左侧是待评学生列表点击某个学生后右侧加载他的指标列表。每个指标一行左边是指标名称和分值说明右边是分数输入框或打分滑块下面留一个评语输入框。指标列表渲染用组件递归一级指标作为分组标题二级指标渲染为可打分行。这里 Vue 插槽就有用了有的指标需要上传附件有的指标只要打分我给指标行组件预留一个#extra插槽业务方按需填充内容。template div classindicator-list div v-forgroup in indicators :keygroup.id classgroup h4{{ group.name }}{{ group.score }}分/h4 div v-foritem in group.children :keyitem.id classindicator-row div classindicator-info span{{ item.name }}/span span classscore-limit0~{{ item.score }}分/span /div el-slider v-modelscores[item.id] :maxitem.score :step0.5 show-input changehandleScoreChange(item.id, $event) / el-input v-modelcomments[item.id] typetextarea placeholder评价意见选填 maxlength200 show-word-limit / slot nameextra :indicatoritem/slot /div /div /div /template script setup import { ref, watch } from vue const props defineProps({ indicators: { type: Array, required: true }, studentId: { type: Number, required: true } }) const emit defineEmits([save-draft]) const scores ref({}) const comments ref({}) function handleScoreChange(indicatorId, value) { emit(save-draft, { studentId: props.studentId, scores: scores.value, comments: comments.value }) } /scriptel-slider的step设成 0.5 是考虑到有些测评表允许打半分如果学校规定必须整数分改step为 1 就行。max绑定的是该指标的满分值从后端接口返回的指标列表里取这样即使某学期某个指标分值调了前端页面自动跟着变不需要改代码。提交拦截的逻辑在前端和后端都要做。前端在提交前检查是否所有必填指标都已打分漏打的用ElMessage.warning提示并滚动到第一个空项。后端再次校验时间窗口和数据完整性双保险防止绕过前端直接调接口的情况。5. 测评系统开发避坑跨域、时间窗口与数据精度的5个教训5.1 跨域请求被拦截前端连不上接口现象前端npm run dev启动后访问http://localhost:5173页面能打开但一调接口就报CORS error后台日志里能看到请求进来但响应被浏览器拦截。原因Vite 开发服务器跑在 5173 端口后端 Spring Boot 跑在 8080 端口两边不同源浏览器的同源策略直接把响应拦了。这不是后端接口挂了而是浏览器安全机制在起作用。解决开发环境用 Vite 的代理转发把/api开头的请求代理到http://localhost:8080这样浏览器看到的请求是同源的不会触发 CORS。生产环境用 Nginx 反向代理同样处理。// vite.config.js export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })5.2 学生互评时自己看到自己的名字现象互评页面加载待评学生列表某个学生登录后居然能在列表里看到自己还能给自己打分。原因互评接口查询学生列表时没有排除当前登录人。学生既是被评对象又是评价人前端列表渲染时后端没过滤直接把全班学生全返回了。解决后端接口根据assessorId当前登录人过滤掉studentId assessorId的记录。这个判断一定要放在后端做前端过滤的话绕过接口直接调用就失效了。SQL 里加一行WHERE s.id ! #{assessorId}就行。5.3 评分精度丢失汇总总分差了零点几分现象学生明明所有指标都是整数分汇总出来的总分却是89.999999导出报表时变成了 90 分和手工计算不一致。原因用的float或double类型存分数浮点数在二进制表示下不精确累加运算后误差累积。成绩这玩意儿小数点后两位就够精确了浮点数算钱都会出问题算分也一样。解决数据库字段用decimal(5,2)Java 实体字段用BigDecimal所有算术运算都用BigDecimal的add和multiply方法。汇总后再做一次setScale(2, RoundingMode.HALF_UP)四舍五入保证总分最多两位小数。5.4 前端路由刷新后出现404现象系统部署到服务器的 Nginx 上首页能打开但进入测评页后按 F5 刷新页面变成 404。原因前端路由用的 history 模式URL 里没有#比如/assessment/self。Nginx 默认按路径去找对应的静态文件/assessment/self不是真实文件自然就 404 了。解决Nginx 的location /配置里加一句try_files $uri $uri/ /index.html;所有路径都回退到index.html由前端路由接管后续跳转逻辑。5.5 前端隐藏提交按钮不等于后端不做校验现象自评时间窗口过了前端把提交按钮禁用了但有学生直接拿 Postman 调后端接口照样提交成功。原因前端禁用按钮只是交互层提示没有做后端的主体验证。后端接口不知道当前时间在不在窗口内只觉得来了请求就入库。解决后端的submitAssessment方法里第一步就校验时间窗口窗口外直接抛业务异常返回给前端。这种校验本质上是后端接口的守门逻辑跟页面展示无关。6. 从“能跑”到“能用”权限模型、成绩导出与全流程验证测评系统交付时功能演示一遍跑通只是及格线真正决定能不能被学校持续用的是权限的细致程度和报表导出好不好用。权限模型有个很容易被忽视的细节教师只能给本班学生打师评不能跨班。前端菜单只是隐藏了入口后端接口必须校验教师与班级的关联关系否则换个请求参数就能给别人班打分。实现上在teacher表加一个class_name字段师评提交接口先查教师的班级再校验被评学生是否属于该班级。成绩导出这块前端调后端接口拿汇总数据用 EasyExcel 生成 Excel 文件返回给浏览器下载。导出内容分两个 Sheet一个按班级汇总排名另一个是指标明细。汇总排名会直接用于评优评先格式要按学校的模板来表头第一行是“XX学年XX学期学生综合素质测评汇总表”第二行才是字段名。EasyExcel 的注解式写法把字段映射写在实体类上维护起来比 POI 手写 Workbook 清爽得多。ExcelProperty(value 学号, index 0) private String studentNo; ExcelProperty(value 姓名, index 1) private String name; ExcelProperty(value 德育, index 2) private BigDecimal moralScore; ExcelProperty(value 智育, index 3) private BigDecimal intellectualScore; ExcelProperty(value 总分, index 7) private BigDecimal totalScore;我自己的习惯是正式交付前把三个角色各建一个测试账号完整跑一遍测评流程管理员建模板配指标、学生自评、学生互评、教师师评、汇总导出。重点看三件事互评列表里过滤掉自己了吗、提交后刷新页面会不会重复提交、导出的 Excel 用公式校验总分是不是和系统显示一致。这套流程走通系统才算真正立住了。遇到过最折腾的坑就是汇总和 Excel 里差一分钱的感觉后来统一用BigDecimal并在入库前做setScale(2)再没出过误差。最后想跟打算复刻这套系统的朋友说一句源码能跑通功能只是起点把细节打磨到学校老师愿意用、能代替手工 Excel 的程度才是这套系统的价值所在。希望这篇实战笔记能帮你少走几步弯路。本文还有配套的精品资源点击获取
返回列表