教学工作量的统计,这也能算个项目?说实话,我接到这个需求时第一反应也是这句话。当时学院的教务处秘书从文件柜里搬出厚厚一摞Excel,每学期结束都要靠人工核对课程表、教师任课记录、课时津贴表,来回折腾两周以上,还经常被老师质疑“课时算少了”。学院领导拍板:后端用Python,前端用Vue3,做一套学院教学工作量统计系统,目标是能填、能算、能审、能看。我原本以为就是给Excel表加个网页壳子,真正把需求吃透之后才发现,这套系统的核心难点根本不在写界面,而在“工作量”三个字背后的业务规则——课时怎么折算、不同课程怎么加权、平行班重复班怎么处理、跨学期调课怎么追溯。这篇文章把我从需求调研到上线运行全过程的思路、踩坑和优化经验完整记录下来,给准备接高校内部管理系统,尤其是信息填报、审批流转、统计报表类项目的朋友做个参考。
1. 一个教学秘书的Excel噩梦,和系统最初的需求边界
1.1 旧流程的数据到底乱在哪
先把旧流程拆开看。学院每个学期的数据主要来自三块:教务系统导出的课程安排表,老师手工提交的调课记录,以及实验、实习、毕业设计这类实践环节的统计表。这三块数据格式完全不一致,课程安排表里一个老师可能对应好几门课,调课记录是临时产生的,实践环节又没有统一模板,全靠秘书在Excel里手工拼。我调研时印象最深的一幕,是秘书姐姐给我演示她怎么用VLOOKUP跨表匹配教师工号,结果因为一个单元格里有空格,匹配出来全是错误值,她再逐行手工改。这种场景在高校里太常见了,数据本身不大,但散落在各类表格里,又没有统一的编码规范,一学期的数据清理就能耗掉大半精力。
最要命的是“折算规则”藏在人脑里。同一门课,理论课一个学时算1个工作量,实验课可能只算0.8,但如果实验课分了组、每组人数超过阈值,又要乘一个班容量系数。这些规则从来没有一份文档写清楚,全靠历任秘书口口相传。所以系统开发的第一件事,不是建表写代码,而是把所有折算规则通过和教学秘书、系主任的多次访谈逐条确认,落到纸面上给学院签字确认。这一步我强烈建议不要省,业务规则不锁定,后面所有代码都是空中楼阁。
1.2 技术选型的两个“不折腾”理由
为什么选Python + Vue3,而不是其他组合?复盘下来有两个很现实的原因。第一,学院里能长期维护这类系统的通常是信息中心的老师或研究生,Python的学习曲线平缓、生态齐全,处理Excel有 pandas 和 openpyxl,做报表有现成方案,后端写起来比Java那一整套工程化链路轻快得多。第二,前端用Vue3是因为组件化思路清晰,配合Element Plus能快速搭建表单、表格、弹窗这些管理系统的高频组件,而且Vue3的Composition API在处理筛选条件、分页、弹窗状态这类逻辑时比Vue2的Options API顺手很多,代码不容易越写越乱。
这类内部系统不追求极端性能,追求的是“开发快速、长期可维护、换人也能接手”。Python和Vue3恰好都占住了这三点。另外我后端没有选Django而是用了FastAPI,原因是这个项目的接口几乎都是JSON数据交换,FastAPI的异步支持和Pydantic参数校验让代码更简洁,自动生成的OpenAPI文档也方便前端同学对接。如果你更熟悉Django,用Django实现同样的功能也没问题,只是要在模型层多一些配置。
1.3 整体架构与三类角色
系统架构上我采用前后端分离:后端Python FastAPI,前端Vue3 + Vite + Element Plus + ECharts,数据库MySQL。整个系统用户分为三类:普通教师——登录后只能查看和填报自己的工作量;教学秘书——负责审核、导入基础课程数据、维护折算系数;学院领导——只看汇总统计和可视化看板,不参与流程。这个三角色模型是所有功能设计的地基,后面每个模块都在围绕它展开。数据流向也很简单:秘书每学期初导入开课数据,教师在期末核对确认工作量,秘书审核汇总,领导看板完成最终决策支持。整个链路闭合,没有多余的环节。
2. 工作量不是课时的简单加总:业务模型与折算逻辑
2.1 工作量=课时×系数的基本换算
工作量统计最容易踩的坑,就是把它当成“课时求和”。实际上学院的工作量体系是一套乘性模型,核心公式是:
课程工作量 = 计划学时 × 课程类型系数 × 班容量系数 × 平行班系数 × 新开课系数每一层系数都对应一条业务规则。比如理论课的基础系数是1.0,上机课是0.8,实验课是0.9;班容量超过60人后系数上浮到1.1,超过100人上浮到1.2;一个老师带两个平行班,第二个班就要打五折;新开课程因为备课成本高,首学期给1.2的加成,但只加成一次。这些规则看起来不复杂,但它们必须做成可配置的,而不是硬编码在if语句里。因为学院可能每年调整政策,比如某年规定“班容量超过120人才上浮”,如果写在代码里,改一次规则就要发一次版,完全不现实,所以我把系数表单独建了一张配置表,界面上做成系统参数维护页面,秘书自己能改。
2.2 课程类型、班容量与折算系数的数据建模
数据建模上,我用五张核心表承载这套逻辑:teacher表存教师基本信息;course表存课程基本信息;teaching_task表存某个学期某位老师承担的某门课,是工作量计算的最小单位;workload_record表存每个条目的工作量明细和计算过程中的中间值;coefficient_config表存所有类型、容量区间的系数,按学期版本生效。
teaching_task表设计上要特别留意的字段包括:teacher_id、course_id、semester、class_type(理论/实验/上机)、plan_hours(计划学时)、class_count(选课人数)、group_count(实验分组数)、is_repeated(是否平行班)、is_new_course(是否新开课)。这些字段看似冗余,但它们是计算的全部输入。最开始的版本我只存了一个class_type和plan_hours,结果发现实验课是按组算的,没有组数字段根本算不对,后来才补上的。
工作量明细的计算流程我做成一个独立服务,不在接口里直接写公式。因为同一批数据可能因为规则调整需要重算,独立成服务后,只需要重跑一次计算任务,所有记录就会按最新规则刷新,层级清晰,排查问题时也方便,哪条记录算错了,直接把输入参数和每一步中间值拉出来对。
2.3 跨学期与调课场景的边界处理
边界情况才是最考验系统设计的地方。调课记录怎么算,是我和秘书反复确认最多的问题。比如一位老师原本这学期上A课程32学时,因为院里临时安排,期中调走了4个学时给同事代课,那么原老师的32学时就要拆成28+4,代课老师的4学时单独生成一条记录。拆单时还要处理“新开课系数是否保留”的问题,因为代课老师接手时这门课已经开过了,不应该享受新课加成。这类边界如果不提前想清楚,系统上线后就会被业务人员用真实案例问倒。
跨学期延期的课程也要单独处理。有些毕业设计指导是一个学年的事,分两个学期计算,每学期按一半工作量计。我当时加了parent_id字段做关联,把两个学期的指导记录绑在一起,方便做全学年汇总。总结一句话:业务建模阶段多花时间听秘书讲“特殊情况”,写代码阶段就能少改一半需求。
3. Python后端:数据模型、计算引擎与报表实现
3.1 数据模型与关系设计
FastAPI加SQLAlchemy的组合在这个项目里很顺手。教师和课程之间是多对多关系,通过teaching_task中间表关联,workload_record又和teaching_task一对一,存储计算出来的最终工作量。设计时有个细节:workload_record里除了最终值,我额外存了base_hours、final_coef、total_workload三个字段,分别对应原始学时、综合系数、折算结果。这个设计的价值在于,最终汇总表的每一行都能解释清楚“这个数字是怎么来的”,老师有疑问时可以直接看到明细,不用层层解谜。
字段类型上也有教训。教师工号这种字段一开始建成了整型,后来发现有些老师工号是0开头的字符串,整型直接截掉了前缀,导致导入匹配失败。凡是编号类字段,一律用字符串存,这个习惯从那以后我再没改过。学期字段我也用统一的格式,比如2023-2024-1,而不是随便填“2023秋”,否则排序、筛选、按学期汇总都会出问题。
3.2 计算引擎的实现思路
计算引擎我单独抽成了一个函数,核心逻辑大致是:
def calc_task_workload(task: TeachingTask, cfg: CoeffConfig) -> WorkloadRecord: base_coef = cfg.get_class_type_coef(task.class_type) size_coef = cfg.get_size_coef(task.class_count) repeat_coef = 0.5 if task.is_repeated else 1.0 new_course_coef = cfg.new_course_coef if task.is_new_course else 1.0 final_coef = base_coef * size_coef * repeat_coef * new_course_coef total = round(task.plan_hours * final_coef, 2) return WorkloadRecord(...)coefficient_config表的数据结构用“生效学期+类型编码+条件区间下限+条件区间上限+系数值”来表达,比如class_type_coef, theory, 0, 9999, 1.0就是理论课基础系数1.0。这样新增一种课程类型或调整一个区间,只改配置不改代码。计算引擎还需要有幂等性,同一批任务重算多次结果必须一致,这样秘书可以放心在学期末随时点“重算全部”。
批量计算性能上,最初版本是逐条查配置表,数据量到几百条时没什么感觉,但学期末一次性重算几千条记录时明显变慢。后来我把配置表一次性加载成内存字典,计算循环内线程安全地读取,速度瞬间从十几秒降到一秒以内。这个优化很简单,但收益非常直观。
3.3 Excel导入导出:看起来简单实则最磨人
系统的数据入口是Excel导入,因为教务系统导出的开课清单就是Excel格式。导入这块用了pandas + openpyxl,但真正磨人的不是读写本身,而是脏数据清洗。最典型的问题有:单元格里有不可见空格导致工号匹配不上;课程名称中英文括号混用;人数列是文本格式且带“人”字后缀;调课记录表头在不同Excel文件里偶尔不在同一列。
我最终写了一个清洗管线:读入数据后先做列名校准、字段类型强制转换、工号/课程号格式化,再逐行校验必填字段,把校验失败的记录输出到一个错误报告Excel,标记清楚原因,方便秘书下载修改后重新导入。第一次上线时秘书反馈“导入报错都不知道哪一行错了”,加了错误定位报告后,基本没有再来找我抱怨过。导出端则要生成符合学院汇总要求的工作量报表,表格的合并单元格、页脚合计这些细节都要处理,生成后用自动化脚本抽查几组数据对账,确保导出值和一个手工核算结果一致。
4. Vue3前端:填报页、审核流和可视化看板
4.1 为什么用Composition API + Element Plus
前端用Vue3 + Vite + Element Plus是综合考虑后的选择。Vite的冷启动和热更新速度比Webpack时代快了一个量级,开发体验完全不同。Element Plus直接提供了 Table、Form、Dialog、Tabs 等管理系统的标准组件,我几乎没怎么写UI底层,全部精力都放在业务交互上。
Vue3的Composition API在这个项目里优势很明显,最直接的表现是三个功能页面各自独立维护状态。以工作量填报页为例,我用一个useWorkloadStore的组合式函数统一管理筛选条件、分页、选中行、弹窗开关,页面组件代码量少、逻辑集中,后续加需求时不用在data/methods/computed三个区域来回跳。如果换成Vue2的Options API,几十个data字段混在一起,维护起来很容易晕。
4.2 填报与审核的交互细节
填报页面是整个系统使用频率最高的地方,交互设计直接决定系统会不会被老师们嫌弃。每个老师登录后只看到自己的教学任务列表,每条任务显示课程名称、学时、班级人数、折算系数,系统自动算好工作量。老师要做的只是核对,如果信息有误可以发起“修改申请”,填写说明后推给秘书审核。这样设计有一个好处:大部分情况下老师只是查看确认,真正需要手动填的内容不多,界面看起来清爽,使用门槛也低。
审核页面针对秘书的角色做了批量操作设计。秘书打开待审核列表后,可以勾选多条记录进行批量通过或驳回;驳回时必须填写原因,因为后台要记录完整审核日志,否则老师不知道被驳回的理由,沟通成本会变高。审核列表我用Element Plus的Table组件加多选列,配合分页和筛选器,一学期几千条记录的审核可以分课程类型、分教师快速过滤。
前端状态管理我用了Pinia,但只在全局登录状态和用户角色上使用,页面级状态都用组合式函数自己维护。小系统没必要把所有状态都塞进Store,塞多了反而让不同页面互相耦合。数据请求统一封装了一个axios实例,带JWT拦截器,请求失败时根据状态码做统一提示和401跳转,这部分是每个Vue3项目都该有的基础设施。
4.3 可视化看板:让数据会说话
领导不看明细,只看汇总。看板页面我用了ECharts做了四个核心视图:学院整体工作量按系部分布(柱状图)、各课程类型的工作量占比(饼图)、教师个人工作量排名Top10(横向条形图)、近三个学期工作量趋势(折线图)。这些图表全部从后端汇总接口一次取数,前端不做二次计算,因为汇总口径必须后端统一,否则前端算出来的数可能和后端对不上。
图表的数据接口设计上,我把时间范围、系部筛选条件做成可选参数,领导可以在看板页筛选“某一个系部的工作量分布”,也可以查看全院数据。ECharts在Vue3里用起来很简单,关键是setOption的数据结构要提前设计好,避免图表组件里做大量字段转换,容易出错,也不好维护。另外图表容器高度需要显式设置,否则初始渲染时默认高度0,图表出不来,这个细节当时也坑了我十分钟,排查后才发现是容器高度问题。
5. 权限体系设计与审批链路:上线前的最后一道坎
5.1 三类角色、两种数据范围的权限矩阵
权限设计我花了不少心思,因为之前见过太多管理系统“看起来有角色,实际上所有人都能看所有数据”。这套系统最终遵循一个简单的矩阵:教师只能看本人数据和自己的任务;秘书可以看全院数据,但只有审核和导入权限,不能修改老师填写的原始申请;领导只读汇总看板和数据导出,不能改动任何业务数据。
具体落地时,后端每个接口都根据当前用户角色做了数据范围控制,而不是只靠前端隐藏菜单。比如查询工作量列表的接口,教师角色会自动追加teacher_id = 当前用户的过滤条件,秘书和领导则可以带系部或全院条件。这个逻辑必须写在后端,因为前端路由和按钮说到底只是用户体验层面的限制,真正的数据保护在后端的查询边界。
5.2 审批流的状态机设计
工作量填报审批状态我设计了四个:草稿、待审核、已通过、已驳回。每个状态下能执行的操作完全确定,不能乱跳。比如已通过的任务不能再次提交修改,必须先由老师发起“更正申请”并经过秘书审核后才能走更新流程。状态机我用一张状态流转表驱动,前端显示按钮、后端校验合法性都基于这张表。
这个设计的价值在系统运行后体现得很明显。凡是状态机定义清晰的功能,几乎不会有“这个操作不该出现但出现”的问题。反而是那些图省事直接写if判断状态的功能,后期需求一变就容易漏掉某条路径,产生脏数据。如果项目要做得更完整,还可以加“已生效”和“已归档”两个状态做学期封存,我当时因为学院要求简单就没有做,但留给后续扩展的字段已经预留好了。
5.3 JWT接口鉴权与前后端联动
登录认证用的JWT方案,配置了短时效的access_token(两小时)和长时效的refresh_token(七天),前端在请求拦截器里遇到401时自动刷新。FastAPI侧采用OAuth2PasswordBearer做依赖注入,自定义了角色校验依赖,接口上直接声明需要的角色权限即可。
router.get("/api/workloads", dependencies=[Depends(require_role("secretary"))])前端路由用了一个简单的动态路由方案,根据登录用户的角色渲染不同的菜单。这项配置在build阶段就确定,运行时不做模糊判断。部署时要注意的安全点是:前端所有涉及上传的接口要限制文件类型和大小,Excel导入文件大小我限制为5MB,防止异常文件把服务拖垮;上传后服务器做病毒扫描不是内部小系统的必须项,但至少要做文件类型白名单校验,这是成本最低的防护。
6. 实测里最值得记录的坑与性能优化
6.1 多学期数据累积后的查询性能回退
系统上线时数据量不大,查询基本秒开。运行到第二学期末,累积了几万条工作量明细后,汇总接口开始出现明显卡顿,耗时最多到了三秒以上。排查后发现是典型的N+1问题:查工作量记录列表时,每条记录都额外查一次教师和课程信息,几百条记录就是几百次数据库查询,速度自然上不去。
解决方案是改用SQLAlchemy的joinedload一次性预加载关联对象,同时给semester、teacher_id、course_id这些高频筛选字段建联合索引。优化后列表接口响应时间稳定在500毫秒以内。另外汇总统计SQL里,我尽量用一次group by完成所有维度的聚合,而不是在Python里循环计算,尤其在看板接口上,这个改动把数据加载时间从四秒降到了不到一秒,响应速度提升非常明显。
6.2 并发提交导致的数据覆盖问题
还有一次线上问题很值得记录:两个老师同时提交修改申请时,偶尔会出现其中一个提交被静默覆盖,最终表现在数据库里的内容既不是A的也不是B的,而是两人表单字段的混合体。定位后发现是前端在编辑弹窗里直接拿了列表行的对象引用做双向绑定,两个弹窗同时打开时,修改同一个响应式对象,导致相互污染。
修复方案有两个层面:前端方面,打开编辑弹窗时用JSON.parse(JSON.stringify(row))做深拷贝,确保弹窗里的数据是独立副本;后端方面,给workload_record表加version字段做乐观锁,提交时校验版本号,不一致就返回冲突提示,让用户刷新后再操作。两个措施配合后,这类问题再没出现过。
6.3 部署、浏览器兼容与运维细节
部署上我用的是常规的Nginx + Gunicorn + MySQL组合,前端构建产物直接放到Nginx静态目录,接口走反向代理到后端。这里要提醒一个细节:如果系统要部署在子路径下,Vue3的静态资源路径必须配置base为对应子路径,否则CSS和JS全部404。我第一版部署就是因为忘了改这个配置,排查了好久。
浏览器兼容方面,系统本身只要求现代浏览器,但学院里确实有老师还在用IE内核的旧Edge内核模式。我在入口页写了一个简单的环境检测,检测到非现代浏览器就提示升级,同时确保主要页面在Chrome和Edge新内核下表现一致。表单校验、日期选择这些交互在浏览器间的差异主要集中在颜值层面,不影响业务,做到主流程可用即可。运维上我加了每天凌晨的数据库全量备份脚本,保留最近两周备份,虽然学院数据量不算大,但这个习惯让我睡得安稳很多。
最后再分享一个做这类系统的心得,也是我这次项目里最深的体会:内部管理系统的技术占比其实不到一半,更关键的是把业务规则吃透、把数据口径统一、把状态流转闭环。如果你正在准备做类似的工作量统计、课时填报、绩效管理系统,多花时间在需求访谈和规则梳理上,远比纠结用哪个框架更值得。Python加Vue3这套组合干这类活足够高效,剩下的,就是上线后根据老师的真实反馈一点一点把细节打磨顺滑。