去年帮本地一所高校开发教学工作量统计系统时,教务主任拿着厚达3厘米的Excel表格跟我说:"每学期末,5个教务员要加班两周才能完成全院300多位教师的工作量核算。"这正是我们开发这套系统的初衷——用Python+Django+Vue3技术栈打造一个全自动化的教学工作量管理平台。系统上线后,原本需要14天的人工核算工作,现在2小时就能自动生成全院报表,准确率从原来的85%提升到99.9%。
这个系统采用经典的前后端分离架构,后端使用Python的Django框架搭建RESTful API服务,前端基于Vue3+Element Plus构建响应式管理界面。特别设计了双重核算机制:既支持按院系标准公式自动计算,也允许教师提交个性化修正申请,最后由教研室主任线上审批。这种设计既保证了效率,又兼顾了教学工作的特殊性。
选择Django而非Flask作为后端框架,主要基于三个考量:
python复制# 使用Django ORM的annotate和Avg函数
from django.db.models import Avg
workload_avg = Teacher.objects.filter(id=teacher_id).annotate(
avg_workload=Avg('teachingrecord__workload',
filter=Q(teachingrecord__year__gte=current_year-3))
).first().avg_workload
数据库选用MySQL 8.0,主要利用其窗口函数特性处理排名统计。例如计算教师工作量院系排名:
sql复制SELECT
teacher_id,
SUM(workload) AS total,
RANK() OVER (PARTITION BY department_id ORDER BY SUM(workload) DESC) AS rank
FROM teaching_records
GROUP BY teacher_id, department_id
Vue3的组合式API特别适合处理动态表单逻辑。比如工作量申报页面需要根据课程类型显示不同字段:
javascript复制// 使用Vue3的setup语法
const formState = reactive({
courseType: '理论课',
fields: computed(() => {
return courseTypeFields[formState.courseType] // 动态字段配置
})
})
采用的技术栈版本控制:
系统最核心的算法是工作量计算公式解析器。我们设计了一个支持动态规则的DSL(领域特定语言),教务管理员可以在后台配置不同课程类型的计算公式:
python复制# 公式示例:理论课工作量=课时数×1.2 + 学生人数×0.01
def calculate_theory(hours, students):
return hours * 1.2 + students * 0.01
# 实验课工作量=基础课时×1.5 + 分组数×0.5
def calculate_lab(hours, groups):
return hours * 1.5 + groups * 0.5
实际实现时使用了AST(抽象语法树)解析技术:
python复制import ast
class FormulaCalculator:
def __init__(self, formula_str):
self.tree = ast.parse(formula_str, mode='eval')
def calculate(self, **variables):
# 安全验证:禁止导入模块和执行函数
for node in ast.walk(self.tree):
if isinstance(node, (ast.Import, ast.Call)):
raise ValueError("Unsafe formula")
code = compile(self.tree, '<string>', 'eval')
return eval(code, {'__builtins__': None}, variables)
处理Excel批量导入时,发现直接使用Pandas会导致内存暴涨。最终采用分块处理方案:
python复制def import_workload(file_path):
chunk_size = 1000
for chunk in pd.read_excel(file_path, chunksize=chunk_size):
# 预处理数据
chunk = preprocess(chunk)
# 使用bulk_create提升性能
TeachingRecord.objects.bulk_create([
TeachingRecord(**row) for row in chunk.to_dict('records')
])
实测对比:
系统定义了四级权限体系:
| 角色 | 数据范围 | 操作权限 |
|---|---|---|
| 普通教师 | 本人数据 | 查看/申报/修改未审核记录 |
| 教研室主任 | 本教研室所有教师数据 | 审核/统计/导出本教研室数据 |
| 院系管理员 | 本院系所有数据 | 全院统计/报表生成/规则配置 |
| 超级管理员 | 系统所有数据 | 系统配置/用户管理/数据备份 |
前端在axios拦截器中处理token刷新逻辑:
javascript复制// 请求拦截器
axios.interceptors.request.use(config => {
if (token && !isPublicAPI(config.url)) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器 - 处理token过期
axios.interceptors.response.use(response => {
return response
}, async error => {
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
const newToken = await refreshToken()
store.commit('updateToken', newToken)
return axios(originalRequest)
}
return Promise.reject(error)
})
工作量分布图采用异步数据加载方案:
javascript复制async function initChart() {
const chart = echarts.init(dom)
const option = {
tooltip: { trigger: 'item' },
series: [{
type: 'pie',
data: await loadWorkloadData() // 异步加载数据
}]
}
chart.setOption(option)
// 响应窗口大小变化
window.addEventListener('resize', () => chart.resize())
}
后端提供报表模板引擎:
python复制from jinja2 import Environment, FileSystemLoader
def generate_pdf_report(data):
env = Environment(loader=FileSystemLoader('templates'))
template = env.get_template('workload_report.html')
html = template.render(data=data)
# 使用weasyprint转为PDF
from weasyprint import HTML
return HTML(string=html).write_pdf()
Nginx关键配置示例:
nginx复制location /api {
proxy_pass http://backend:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 文件上传大小限制
client_max_body_size 20M;
}
location / {
root /frontend/dist;
try_files $uri $uri/ /index.html;
}
使用Redis缓存热门数据:
python复制from django.core.cache import cache
def get_department_stats(department_id):
cache_key = f"dept_stats_{department_id}"
if data := cache.get(cache_key):
return data
data = heavy_query(department_id)
cache.set(cache_key, data, timeout=3600) # 缓存1小时
return data
Excel日期处理:发现教务导出的Excel中日期可能是数字或文本格式,最终统一处理方案:
python复制def parse_excel_date(value):
if isinstance(value, (int, float)):
return xlrd.xldate_as_datetime(value, 0)
return pd.to_datetime(value)
Vue表格性能:当渲染超过1000行数据时,改用虚拟滚动方案:
vue复制<el-table-v2
:columns="columns"
:data="data"
:width="800"
:height="600"
:row-height="50"
/>
并发申报冲突:采用乐观锁解决多人同时申报冲突:
python复制try:
with transaction.atomic():
record = TeachingRecord.objects.select_for_update().get(pk=id)
# 业务处理...
except DatabaseError:
return JsonResponse({'error': '数据已被修改,请刷新后重试'})
这个项目让我深刻体会到,教育信息化不是简单地把纸质流程电子化,而是要通过技术手段重构业务流程。比如我们新增的"工作量预测"功能,教师输入下学期计划授课信息,系统就能预估工作量,帮助教师合理安排教学科研时间。这种增值功能才是数字化系统真正的价值所在。