ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot+Vue前后端分离课堂管理系统开发实战与踩坑总结

Spring Boot+Vue前后端分离课堂管理系统开发实战与踩坑总结

刚带完一个医学电子技术相关的课堂管理系统项目,从Spring Boot后端到Vue前端,到数据库设计,走了不少弯路,也积累了不少可以直接复用的经验。这个过程其实挺有代表性,今天一次性把思路、踩坑点、核心代码和数据表逻辑都理清楚。

这个事情适用于谁?一是正在做类似课程设计或者毕业设计的学生,二是有意向把某门课的管理从Excel和微信群迁移到正轨系统的老师或教务人员,三是想练手前后端分离技术栈的开发者。这套系统不算复杂,但麻雀虽小五脏俱全,用户登录、角色权限、课程管理、作业布置、成绩录入、课表展示、数据统计这些常见的业务模块都有,非常适合作为理解企业级管理系统基础架构的切入点。

1. 项目整体设计与思路拆解

1.1 业务场景分析

医学电子技术属于专业基础课,学的人多、授课班次多、实验环节多,教学过程中的数据也很杂。传统做法是上课点名靠签到表,作业收交靠U盘拷贝或邮箱,成绩汇总靠Excel,课表变动靠群里通知。这套系统的目标就是把这些零零散散的流程收敛到一个平台上,让授课教师、学生、教务管理员各取所需。教师端重点解决课程资源下发与作业批改效率的问题,学生端重点解决查看课表、提交作业、查成绩的便利性问题,管理端解决用户与班级同步、系统参数维护的问题。

从实际访谈的情况看,业务痛点主要集中在三个地方。第一个是作业,医学电子技术课经常要交波形图、仿真截图甚至短实验视频,这些文件用微信传输很容易过期;第二个是实验课的分组与排课,不同班级使用实验室的时间要错开,靠人工协调极易冲突;第三个是成绩构成,该课程往往包含平时分、实验分、期中、期末多部分,各部分权重要动态调整。系统在设计之初,必须把这些场景全部覆盖进去,避免出现做完之后老师仍然要回Excel里二次加工的情况。

1.2 技术选型考量

为什么采用Spring Boot搭配Vue这套组合?简单说,这是目前中小型管理系统领域生态最成熟、招人要求里出现频率最高、学习资料最齐全的一套技术栈。

Spring Boot在Java服务端里优势很明显。内置Tomcat,不用额外配Web容器;Starter机制让第三方依赖集成变得非常简单;配合Spring Security或者JWT就可以完成接口鉴权;MyBatis Plus这类增强框架让写增删改查的效率极高。Vue侧,Vue 3搭配Element Plus,组件丰富,表格、表单、弹窗、树控件开箱即用,对没有专业前端工程师的团队来说,是最快能做出体面界面的方案。

数据库选择MySQL,一是成本低,二是在教学场景里团队普遍对MySQL比较熟。对于这套系统的数据量级——一门课几百名学生、一学期几十个作业任务,MySQL完全没有任何压力。如果非要强调为什么不用MongoDB之类的NoSQL,是因为这里的数据关系非常强,学生、班级、选课、成绩都有关联查询需求,关系型模型是最自然的选择。

1.3 功能模块规划

系统按角色分三大端,整体模块规划如下:

  • 管理员端:用户管理、班级管理、课程管理、学期学期配置、数据看板、系统日志。管理员不参与日常教学内容,但要对基础数据和权限负责。
  • 教师端:课程创建、学生名单导入、课表维护、作业布置与批改、成绩录入与发布、实验分组管理、课程公告。
  • 学生端:课表查看、课程资料下载、作业提交与结果查看、成绩查询、实验分组查看、请假申请。

从业务闭环来看,管理员配好基础数据 → 教师开课并导入学生 → 教师发布课程内容和作业 → 学生学习和提交 → 教师批改并存成绩 → 学生查看结果并反馈。整个过程在系统内流转,所有数据留痕,期末可以直接导出成绩表。

2. 核心模块设计详解

2.1 数据库设计原则

数据库是这个系统最值得花时间设计的部分。初期如果图省事把表设计得过于稀疏,后续做统计查询时会非常痛苦。核心表关系可以用一句话概括:一个用户有一种角色,一个学生属于一个班级,一个班级选择多门课程,一个课程由一位教师授课,一个课程包含多个作业和课件,一个作业对应多份学生提交。

学生和课程之间是多对多关系,这种关系在关系型数据库里必须拆成中间表。选课表(course_student)就是中间表的典型场景,里面除了课程ID和学生ID,还可以冗余一些状态字段,比如是否退选、选课时间,方便做统计。

教师和课程是一对多关系,一个教师可以教多门课,但一门课在同一学期只有一个主讲教师,这个约束在业务层和数据库层都要做控制。

关于外键策略,我个人的习惯是逻辑外键优先。也就是说,在表设计时可以画出物理外键关系,但在实际建表语句中不加物理外键约束,而是通过索引和代码逻辑去维护引用完整性。理由有两个:一是后面做数据清理和迁移时不会有外键约束的牵绊,二是在并发场景下,物理外键会影响插入性能。对教学系统这种量级来说,逻辑外键完全足够。

2.2 核心表结构与字段说明

用户表(sys_user),核心字段包括id、username、password、real_name、role_type、avatar、phone、status、create_time。

学生表(student_info)额外携带学号、所属班级ID、入学年份、性别等。

班级表(class_info)字段相对简单:班级名称、年级、专业方向、辅导员ID。班级和辅导员是弱关联,因为辅导员也可能在系统里是管理员角色。

课程表(course)字段包括课程名称、课程编码、授课教师ID、开课学期、学分、课程简介、课程封面、上课时间(可为空,由课表项填充)、上课地点、选课上限。

作业表(homework)包含所属课程ID、作业标题、作业描述、附件URL、截止时间、作业类型(个人作业/小组作业)、总分。作业一旦发布,不允许随意修改截止时间,至少需要在业务层做一个权限校验,防止学生提交之后老师把时间改短导致作弊纠纷。

提交记录表(homework_submit)核心字段是homework_id、student_id、submit_content、attach_url、submit_time、score、comment、status(已提交/已批阅/已退回)。需要给homework_id和student_id建立联合唯一索引,确保一个学生对一个作业只能有一条正式提交记录。对于允许重复提交的场景,另加版本号字段,每次提交版本号加一,这样既保证了数据整洁,又保留了操作历史。

成绩表(score)包含student_id、course_id、平时成绩、实验成绩、期中成绩、期末成绩、总评成绩。总评成绩不应该由用户手工录入,而是前端展示时根据权重动态计算,后端在导出成绩单时做权威计算,避免出现手工录入的总评与实际构成不一致。

2.3 角色权限控制逻辑

权限部分使用的是JWT配合拦截器的方式。登录成功之后后端返回一个token,前端把token存在localStorage中,之后每次请求在axios拦截器里带上Authorization。后端通过拦截器解析token,取出用户ID和角色类型,再与目标接口要求的权限比对。

权限控制粒度上采用了简单但实用的方案:接口级别权限控制,不做按钮级别细粒度权限。按钮级别的权限控制(比如教师端按钮学生不可见)在前端靠路由和v-if指令根据roleType控制,后端接口也做了对应的角色校验,双保险。

在Spring Boot里,写一个拦截器继承HandlerInterceptor,重写preHandle方法。在preHandle里,从请求头取token,用JWT工具类解析,如果token无效直接返回401。如果有效,把用户信息放入ThreadLocal上下文,方便后续Controller直接获取当前用户。这种方法比Spring Security配置简单,对新手非常友好,同时也够用。

考虑到系统可能部署在多台服务器上(虽然目前没有这个规模),token中不放敏感状态数据,只放userId、roleType、过期时间。如果要强制下线用户,通常需要配合Redis黑名单机制,可以对某个用户token设置不可用状态,这样能够做到比较细粒度的会话控制。

2.4 文件上传与存储策略

教师上传课件、学生提交作业附件,都涉及文件上传。方案上选择本地服务器存储加数据库存路径的方式,不额外引入OSS,因为教学系统一般部署在校内服务器,数据量不大,OSS成本反而高了。

文件上传流程做了一层封装。前端通过Element Plus的Upload组件上传,后端接收到MultipartFile之后,按年/月/日生成目录,重命名文件,避免中文乱码问题。重命名规则建议:日期+随机UUID+原始文件后缀。例如:20250412_3f9d8d1e.jpg。

需要提示的是,文件在存储路径上尽量不和工程代码放一起,要配置一个独立的路径(比如/usr/local/coursefile),并在Spring Boot配置文件中配置一个自定义映射,通过ResourceHandler将URL路径映射到服务器磁盘路径。这样可以避免重启服务时文件丢失,也方便备份。

文件上传中容易忽略的是大小限制问题。Spring Boot里默认上传大小为1MB,Spring Cloud Gateway中则是不同设置,对于课堂系统,课件PPT几十MB很常见。在application.yml里需要把max-file-size调到100MB,同时Nginx层如果加了代理也要同步改client_max_body_size,否则上传会卡在50%。这个问题在部署排查中出现的频率非常高,后面会再提到。

3. 工程搭建与实际业务实现

3.1 后端工程目录重构

Spring Boot的工程结构按照业务模块划分,避免所有类堆在一起。我的习惯是:

com.medical.course ├── controller ├── service │ ├── impl ├── mapper ├── entity ├── dto ├── vo ├── config ├── common │ ├── result │ ├── exception ├── utils

为什么要单独分dto和vo?因为数据库实体类entity应该保持和表结构一一对应,不轻易加额外字段。而前端传入的参数往往不是一个实体能完全承接的,比如登录接口的请求体里面既有用户名又有密码还有一个验证码标识,这种情况下用dto类去接收最为规范。聚合输出的结构(比如学生成绩详情里要附带学生名字和班级名)则定义vo类,保证前端拿到的数据层级清晰。

这种分层在多人协作时尤为关键。之前见过一个项目,请求参数直接复用实体类,结果实体类的字段被业务查询搞得千疮百孔,一个实体字段既要做查询条件,又要当返回结果的冗余字段,可维护性很差。

3.2 关键接口实现:JWT登录与用户信息获取

登录流程拆成四步:前端传入用户名密码和验证码,后端校验验证码,校验用户存在性和密码,密码加密方式使用Bcrypt,登录成功后生成token,返回用户基础信息。

用代码示例说明登录核心逻辑:

public LoginVO login(@RequestBody LoginDTO dto) { // 校验验证码,防止暴力破解 String redisCode = redisTemplate.opsForValue().get("captcha:" + dto.getCaptchaKey()); if (!dto.getCaptcha().equalsIgnoreCase(redisCode)) { throw new BusinessException("验证码错误或已过期"); } // 查询用户 LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SysUser::getUsername, dto.getUsername()); SysUser user = userMapper.selectOne(wrapper); if (user == null || !BCrypt.checkpw(dto.getPassword(), user.getPassword())) { throw new BusinessException("用户不存在或密码错误"); } // 生成token,有效期设置为24小时 String token = JwtUtil.createToken(user.getId(), user.getUsername(), user.getRoleType()); LoginVO vo = new LoginVO(); vo.setToken(token); vo.setRealName(user.getRealName()); vo.setRoleType(user.getRoleType()); vo.setAvatar(user.getAvatar()); return vo; }

在拦截器里通过token解析出的userId来获取当前用户信息,写一个@CurrentUser注解配合HandlerMethodArgumentResolver,可以做到在Controller方法里直接通过参数取到当前登录用户,代码写起来非常清爽,这也是比较常用的一种优化方式。

3.3 教师端作业闭环实现

作业模块是教师使用频率最高的功能,从发布作业到批改成绩,做得好不好直接决定系统口碑。发布作业的接口大概是这样:

public void publishHomework(@RequestBody HomeworkDTO dto, @CurrentUser SysUser user) { // 校验课程是否属于当前教师 Course course = courseService.getById(dto.getCourseId()); if (!course.getTeacherId().equals(user.getId())) { throw new BusinessException("无权操作该课程"); } Homework homework = new Homework(); homework.setCourseId(dto.getCourseId()); homework.setTitle(dto.getTitle()); homework.setDescription(dto.getDescription()); homework.setAttachUrl(dto.getAttachUrl()); homework.setDeadline(dto.getDeadline()); homework.setStatus(1); homeworkMapper.insert(homework); }

学生端提交作业:先查询该学生是否选课(通过中间表),之后判断当前时间是否在截止时间之前。如果过了截止时间,则拒绝提交。有个细节,系统的截止时间要精确到秒,而不是只到日期,否则当天晚上提交会被误判为过期。

批改作业时,讲师可以下载学生提交的附件并打分。接口逻辑为更新homework_submit中的score和comment字段,同时更新status为2(已批阅)。这里要注意,成绩一旦公布,后续再修改要记录修改日志,防止学生成绩被恶意修改。

从实际使用反馈看,教师对批量批改的诉求非常高,因此实现了一个批量打分接口,接收一个包含submitId和score的数组,循环更新。性能上没有压力,前端可以按作业列表逐条录入成绩,提交时一次性发送整个数组。

3.4 课表冲突检测逻辑

医学电子技术课经常有实验课,实验课需要用到实验室。同一个实验室在同一时间段不能同时安排两个班级上课,这个逻辑必须做检测。核心实现思路是在新增或编辑实验课表时,查询同一实验室、同一时间段的已有记录。

这里的时间段不是简单的时间戳,而是用星期几和节次来建一个全天时间轴。实验课表可以设计为字段:weekday(1-7),section_start(起始节次),section_end(结束节次),lab_room(实验室名称),course_id,class_id。

当新增一条课表记录时,检测冲突的伪代码为:

select count(*) from lab_schedule where lab_room = #{labRoom} and weekday = #{weekday} and #{sectionStart} <= section_end and #{sectionEnd} >= section_start

如果count大于0则提示冲突,否则插入成功。这套SQL利用了区间重叠的判断方式:两个区间不重叠的充要条件是有一段完全在另一段的左边或右边,所以反过来,一旦出现前者结束时间不小于后者开始时间,且前者开始时间不大于后者结束时间,就一定存在重合。这个方法可以举一反三应用到会议室预约、面试时间排班等场景。

3.5 前端核心页面实现说明

前端页面采取Vue 3组合式API开发,用Vite作为构建工具。之所以用Vite不用Webpack,是因为开发环境下Vite启动速度快很多,几乎秒开,热更新也非常迅捷,对于需要频繁调试的课堂系统意味着更短的等待时间。

前端路由设计采用按角色区分的动态路由。登录成功之后,根据roleType在前端路由守卫里动态添加对应角色的路由表。实现上,基础的通用路由(比如个人中心、消息通知)是静态注册的,业务路由(教师管理端、学生端、管理员端)采用动态addRoute方式注册。

权限路由这么做可以避免普通学生直接通过修改路由地址进入管理员页面,因为当非法路由被访问时,会因为没有匹配规则而被拦截到404页面。当然,后端接口的权限校验依然是要严格执行的。

在页面开发过程中,遇到最多的坑其实是Element Plus表格的loading状态管理。每张需要在数据加载后刷新的表格都应当在loading变量上做好控制,尤其是分页组件和切换条件查询时,好多新手起手式会忘记在请求结束后把loading置为false,导致页面出现永远转圈的情况。把请求开始前loading置true、请求after中finally块里置false,这个习惯要养成。

3.6 数据看板与统计

系统的数据看板初衷是给教务老师一个直观的课程运行概览。看板包含四块核心内容:课程总数和选课总人次、作业平均分趋势、学生登录活跃度、待批改作业数。

作业平均分趋势用ECharts折线图展示,数据来源是homework_submit和homework表的联合分组查询。SQL逻辑大致为按homework_id分组,求score的平均值,再取homework的title作为横轴标签。前端在页面初始化时调用统计接口拿数据,再做ECharts的option赋值,整个过程非常标准。

统计接口比较适合用异步方式实现,避免影响主流程查询性能。另外在数据量增长之后,可以考虑引入Redis缓存统计结果,设置几小时的过期时间,因为看板数据不需要实时精确到秒,缓存的意义在于减少复杂SQL对数据库的反复压力。

4. 数据库脚本与初始化方案

4.1 基础数据初始化

系统交付时,除了代码必须带上完整的数据库SQL脚本。两份脚本必不可少:一份是表结构脚本,一份是种子数据脚本。种子数据的价值在于,接手项目的人可以不需要手动注册账号就能直接登录系统,体验各角色功能。

种子数据里建议至少包含三种角色的登录账号:管理员账号(admin/某初始密码)、教师账号(teacher01/某初始密码)、学生账号(student01/某初始密码)。同时初始化几个班级、几门课程和选课关系。

特别注意,写在SQL里的密码必须用BCrypt加密后的密文,不能明文。这个细节很容易被忽略,有人图省事直接在种子数据里扔明文密码,运行起来登录又一直报密码错误,排查半天发现是加密方式不匹配,低级错误但非常消耗时间。我习惯在项目启动时提供一个初始化接口,通过命令行参数触发生成admin账号和密码,密码用随机数生成并在控制台打印,这是一种更安全的方案,避免固定弱口令出安全问题。

4.2 数据迁移与备份

课堂系统使用频率虽不算高,但数据备份不能省。建议在服务器上配置crontab,每天凌晨对MySQL做全量备份,保留最近7天的备份文件。备份命令可以参考:

mysqldump -u root -p{password} course_system > /backup/$(date +%Y%m%d).sql

文件数据(上传的作业、课件)可以通过rsync同步到备份磁盘,或打包到另一个目录。在学期教学过程中数据越多,一旦服务器磁盘损坏,没有备份的代价是灾难性的。

4.3 数据的一致性与安全性

数据库层面建议给关键业务表设置软删除标记,比如courses表增加is_deleted字段,代替物理删除。这样可以防止作业和成绩记录因为误删课程而全部丢光。唯一的例外是提交记录表可以根据业务需要做物理清理,但也先做逻辑删除更稳妥。

MySQL的sql_mode设置上,建议开启ONLY_FULL_GROUP_BY,避免统计SQL因为严模式差异出现行为不一致的问题。另外字符集统一使用utf8mb4,能完整支持emoji和特殊字符,避免中文乱码。建库语句建议使用:

CREATE DATABASE course_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

5. 系统部署与线上运行

5.1 环境准备

服务器配置不需要太高,2核4G的云主机跑这个系统完全没有压力。部署方案采用前端打包静态文件交给Nginx托管,后端通过jar包运行,使用systemd管理进程,保证服务异常退出后可以自动拉起。

推荐的环境清单:

  • JDK 17(如果项目基于Spring Boot 3)
  • MySQL 8.0+
  • Maven 3.8+
  • Node.js 16.20+
  • Nginx

5.2 前端构建与部署

前端项目在服务器上不需要安装Node运行时,本地构建后把dist目录上传到服务器即可。

构建命令:

npm run build

Nginx配置关键位置:

server { listen 80; server_name your.domain.com; root /usr/share/nginx/html/course-front; index index.html; location / { 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; } }

这里有两个值得注意的细节。一是try_files结尾必须指向/index.html,这是Vue路由History模式的前提条件,否则刷新页面会出现404。二是API代理通过location前缀实现,前端代码里所有请求都统一以/api开头,Nginx转发时去掉/api前缀再指向后端服务,这样前后端联调与线上部署不需要改代码。

5.3 后端jar包启动

Spring Boot项目打成jar包后,在服务器上使用systemd管理。服务配置文件可以这样写:

[Unit] Description=course-system After=network.target [Service] User=root WorkingDirectory=/usr/local/course-system ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar course-system.jar --spring.profiles.active=prod Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

执行systemctl daemon-reload之后,通过systemctl start course-system启动服务。日志查看用journalctl -u course-system -f。这种管理方式的好处是服务挂掉自动重启,避免学生晚上交作业时发现系统打不开而焦头烂额。

5.4 配置文件多环境隔离

application.yml按环境拆分,开发环境与生产环境的数据库地址、文件存储路径、日志级别都不一样,因此需要Spring Boot多环境配置文件。基础配置写在application.yml中,开发环境使用application-dev.yml,生产环境使用application-prod.yml。启动命令中通过--spring.profiles.active=prod指定使用哪个环境,避免把测试数据库地址带到生产时出事故。

6. 常见问题与排查技巧实录

6.1 前端调接口跨域报错

比较常见的现象是本地开发环境用Vite的proxy配置代理,但上线后Nginx没配置好导致跨域。排查优先级:先确认请求地址是由Nginx转发的还是直接请求了后端地址,如果是直接请求后端,后端要开启CORS的允许跨域配置。如果是走Nginx,要确保proxy_pass配置没有写错。

本地开发时在vite.config.js中可以这样配置:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }

这个代理让前端代码以/api开头的请求在开发环境下自动转发到8080端口,从而绕开跨域。线上则是Nginx完成同样的工作,二者配合起来后,前端代码中就不需要硬编码后端地址了。

6.2 上传文件提示413 Request Entity Too Large

这是典型的Nginx上传大小限制问题。Spring Boot后端就算设置了100MB,Nginx默认只允许1MB请求体,超出就报413。解决方案在Nginx的http块或server块中添加:

client_max_body_size 100m;

还有一点容易被忽略,就是如果flask等后端服务也有请求大小限制,需要一并处理。另外前端的大文件上传要做好进度提示,不然用户以为卡死了。

6.3 用户提交作业后教师端看不到附件

大多数情况是文件存储路径配置不一致。教师端页面通过URL去访问文件,而文件的真实存储路径与URL映射没有正确对应。在配置类中自定义静态资源映射时,要确保addResourceLocations指向的磁盘路径最后有斜杠,并且和保存文件时拼的路径完全一致,多一个少一个斜杠都会404。

比较稳妥的做法是,把文件访问统一抽成一个接口,例如/file/{fileId},后端根据fileId从数据库查找存储路径再返回流。这种方式的优点在于文件不暴露在静态目录中,权限也可以做控制,但代价是每次访问都经过后端,效率上略低于Nginx直接托管静态文件。

6.4 数据库连接出现Public Key Retrieval is not allowed

MySQL 8.0以上版本连接时偶尔会报这个错误。解决方法是在JDBC连接串中附加allowPublicKeyRetrieval=true&useSSL=false。虽然从严格安全角度,允许公钥检索存在中间人攻击风险,但在内网教学系统场景里,这个配置的收益大于风险。

6.5 线上日志排查技巧

生产环境出错不要靠猜。建议在项目里使用logback,按天切分日志,保留30天。日志分级输出到文件,同时把错误日志单独输出到一个error.log。使用grep error error.log + tail -f按关键字查找业务异常,速度比翻滚动日志高很多。

当接口返回数据不对时,我习惯在日志中打印SQL语句和参数。MyBatis Plus配置:

mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

生产环境不建议开启这个日志,因为会大量输出SQL到控制台,影响性能,但排查问题时可以临时打开,定位后关闭。

6.6 时间格式与时区问题

前后端分离项目最容易出现时间差8小时的问题。Spring Boot默认的Jackson序列化时间格式是ISO格式,带有时区,前端解析后如果和东八区不一致就会差8小时。建议后端统一返回时间戳或者标准字符串格式,在application.yml里配置:

spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8

同时,数据库连接串中配置serverTimezone=Asia/Shanghai,并确保服务器操作系统时区正确。三层统一后,时间就非常稳定了。

7. 总结与经验补充

纯从做项目的角度,这个课堂管理系统的难点不在某个技术点上,而在对细节的取舍。很多同学一上来就纠结用Spring Security还是Shiro,实际上对这类系统,JWT加拦截器就已经非常完善了。纠结用RESTful风格还是普通URL风格,实际上团队内定好规则、保持统一就行。系统成败更多的取决于课时、作业、成绩这些核心业务流程是否顺畅,以及部署环境是否稳定。

根据我的实际体验,有两点值得特别提醒。第一,开发时一定先在本地模拟多角色登录场景完整走一遍,特别是学生提交作业、教师批改、学生查成绩这条主线,不能只在Postman里测试接口通过就算完。前端的交互问题往往比后端接口问题更多,多角色验证能早点暴露权限和路由的问题。第二,学期开始前把班级、学生、课程的基础数据导入完整,并反复检查导入模板的正确性。数据源一旦错了,后面所有统计都是错的,返工量极大。

最后分享一个很有用的小技巧。教师端录成绩时,为了让体验更加友好,可以把成绩输入框做成键盘控制,输入数字后回车自动跳到下一个输入框。Element Plus的Table配合input组件加tabIndex属性就能实现,不到二十行代码,但是使用体验提升非常明显。

这个系统的可扩展性也是不错的。下一步如果要做的话,可以加入在线考试模块,前端接入CodeMirror来支持填空、选择、判断等基本题型;再往前一步,可以加入课程知识图谱,把章节、课件、作业、考试的关联关系用图数据库承接。发展空间很充足,从课堂管理系统切入,慢慢向医学电子技术的在线教学全流程平台演进。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进