简介:基于Flask框架开发的轻量级校园请假审批系统,直接运行即可使用。后端用SQLite存储用户资料、请假申请、审批状态和角色权限数据,无需额外安装数据库服务。系统预设管理员、学生、教师、辅导员、教务员五类角色,各自拥有独立首页(admin.html、student.html等)和操作权限,通过role.py统一管理角色逻辑,session.html处理登录态与身份识别。核心功能包括请假提交、多级审批流程、考勤记录查看(attendance.html)、密码生成工具(generate_password.py)及数据库初始化脚本(data_creator.py)。前端采用Bootstrap 5响应式布局,集成jQuery、BootstrapTable实现动态表格展示、BootstrapFileinput支持附件上传、jQuery-Confirm提供交互提示,所有页面继承base.html保持风格统一。静态资源包含7个CSS样式文件、10个JS交互脚本、FontAwesome图标库及favicon.ico。配套提供datadb参考手册.pdf,详细说明data.db中各表字段含义与关联关系,便于二次开发或字段扩展。启动方式简单:安装requirements.txt依赖后运行launch.py或app.py即可访问本地服务。
1. 项目概述:为什么一个校园请假系统值得从零重做一遍?
我带过三届计算机专业的毕业设计,每年都有至少五个学生选“教务管理系统”或“学生事务平台”这类题目。但翻看他们的代码,八成卡在权限模型上——不是学生能删老师账号,就是辅导员点了审批按钮却没更新状态,更别提附件上传失败、表格加载空白、登录后跳转错页面这些“经典现场”。直到去年帮学院信息办快速搭一套临时请假流程,我才真正意识到:一个能跑通的轻量级系统,比十个PPT架构图更有说服力。这套Flask请假系统,就是我在真实场景里反复打磨出来的“最小可行产品”——它不追求高并发、不对接教务API、不搞微服务拆分,就专注把“学生填表→辅导员初审→教务员终审→状态同步→考勤归档”这条主链路,用最朴素的方式走通、压稳、留痕。
核心关键词“Flask请假系统”“SQLite审批系统”“多角色权限管理”,其实对应三个现实痛点:第一,“Flask请假系统”意味着它必须足够轻——没有Docker编排、没有Nginx反向代理、没有Redis缓存,一台4G内存的旧笔记本装个Python 3.8就能跑;第二,“SQLite审批系统”直指部署门槛——不用配MySQL用户权限、不用开3306端口、不用处理数据库连接池泄漏,整个数据就躺在data.db这个单文件里,双击备份、拖拽迁移、文本编辑器直接查字段;第三,“多角色权限管理”不是简单if-else判断role==’admin’,而是把“谁能看到什么按钮”“谁能在哪个页面提交什么表单”“谁修改的数据会被日志记录”这三层逻辑,揉进路由装饰器、模板上下文、数据库查询条件里,让权限真正长在业务毛细血管中。它适合两类人:一是刚学完Flask基础想动手做项目的大学生,代码结构清晰到能当教材用;二是学院IT老师需要一周内上线临时流程的实战者,所有依赖都在requirements.txt里列得明明白白,launch.py点一下就起服务。你不需要懂JWT鉴权原理,但能看清session如何绑定用户ID;你不必研究SQLAlchemy ORM高级特性,但能读懂data_creator.py里那几条CREATE TABLE语句怎么支撑起五角色协作。这就是它的价值:把抽象的“权限控制”还原成可触摸的HTML按钮、可调试的Python装饰器、可验证的SQLite记录。
2. 整体架构与设计思路:为什么选择SQLite+Flask而非Django或Vue?
2.1 技术栈取舍背后的现实考量
很多人看到“五角色权限”第一反应是上Django Admin——毕竟自带用户管理、权限系统、后台界面。但我坚持用原生Flask,原因很实在:Django的约定大于配置,在教学场景里反而成了障碍。学生要改一个审批状态按钮的位置,得先搞懂template inheritance的loader顺序、staticfiles的collectstatic机制、middleware的执行链;而Flask里,你打开student.html,找到那个<button onclick="submitLeave()">,直接加个{% if current_user.role == 'teacher' %}包裹就行。这种“所见即所得”的调试体验,对新手建立信心至关重要。
至于数据库选SQLite而非MySQL或PostgreSQL,更是被现实逼出来的选择。去年帮某高职院校部署时,对方机房连远程桌面都卡顿,更别说让非专业老师去配MySQL root密码、开防火墙端口、处理socket连接超时。而SQLite呢?data.db就是一个普通文件,os.path.exists('data.db')一行代码就能判断数据库是否存在,sqlite3.connect('data.db')自动创建——没有服务进程、没有配置文件、没有root账户,连备份都只需要复制这个文件。当然,它有硬伤:不支持高并发写入、没有行级锁、大数据量下查询变慢。但校园请假场景是什么量级?全校5000学生,按每月平均每人请1次假算,一年才6万条记录,SQLite单表轻松扛住。真到了需要分库分表那天,把data_creator.py里的SQL语句导出,再用Navicat一键迁移到MySQL,成本远低于初期就为“可能的扩展性”牺牲掉的开发效率。
前端放弃Vue/React而用Bootstrap 5,同样基于交付确定性。Vue项目要配webpack、处理跨域、调试v-model双向绑定失效;而Bootstrap 5的class命名直白如btn btn-primary disabled,jQuery操作DOM就像写伪代码:$('#leave-table').bootstrapTable('refresh')。更重要的是,所有角色首页(admin.html/student.html等)都继承自base.html,这个母版里已经预置了导航栏角色判断逻辑:
<!-- base.html 片段 -->
<nav class="navbar">
{% if current_user.role == 'admin' %}
<a href="{{ url_for('admin_dashboard') }}">管理员面板</a>
{% elif current_user.role == 'student' %}
<a href="{{ url_for('student_leave_form') }}">我要请假</a>
{% endif %}
</nav>
这种“模板即权限”的设计,让前端同学不用理解RBAC模型,只要记住“不同角色渲染不同链接”就行。而BootstrapTable集成的服务器分页、列排序、搜索框,全是开箱即用的data属性驱动,比如attendance.html里这行:
<table data-toggle="table"
data-url="{{ url_for('get_attendance_data') }}"
data-pagination="true"
data-search="true">
<thead>
<tr>
<th data-field="student_id">学号</th>
<th data-field="status">考勤状态</th>
</tr>
</thead>
</table>
后端app.py里对应的/api/attendance路由,只需返回标准JSON数组,表格自动渲染。这种“约定优于配置”的默契,正是轻量级系统的生命线。
2.2 五角色权限模型的落地实现
真正的难点不在技术选型,而在如何把“管理员、教师、辅导员、学生、教务员”这五个抽象名词,转化成代码里可验证、可审计、可追溯的行为边界。很多项目把权限写死在模板里,结果出现学生能访问/admin/users路由的漏洞。本系统采用三层防御:
第一层:路由级拦截(app.py中的装饰器)
定义@require_role('admin')装饰器,所有敏感路由必须声明所需角色:
def require_role(role):
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not session.get('user_id'):
return redirect(url_for('login'))
user = User.query.get(session['user_id'])
if user.role != role:
flash('权限不足', 'error')
return redirect(url_for('index'))
return f(*args, **kwargs)
return decorated_function
return decorator
@app.route('/admin/manage_users')
@require_role('admin')
def manage_users():
return render_template('admin.html')
这里的关键细节是:装饰器必须在@app.route之后、函数定义之前,否则Flask无法注册路由。我试过把@require_role放在外面,结果所有路由都404——这是新手踩坑最多的地方。
第二层:模板级过滤(Jinja2条件渲染)
即使路由被误放行,模板里也设第二道关卡。比如在student.html中,只有学生本人能修改自己的请假申请:
<!-- 学生只能编辑自己未审批的申请 -->
{% if leave.status == 'pending' and leave.student_id == current_user.id %}
<button onclick="editLeave({{ leave.id }})">编辑</button>
{% endif %}
第三层:数据级隔离(查询时强制添加WHERE条件)
最危险的是列表页,比如/teacher/approvals要显示该教师负责班级的学生请假单。如果只靠前端隐藏按钮,恶意用户改URL就能看到全部数据。因此在teacher.py中,所有查询都强制绑定教师ID:
# teacher.py
def get_pending_leaves_for_teacher(teacher_id):
# 关键:JOIN班级表,确保只查该教师授课班级的学生
return db.session.query(Leave).join(Student).join(Class).filter(
Class.teacher_id == teacher_id,
Leave.status == 'pending'
).all()
这三层像三把锁:路由锁防止非法入口,模板锁防止UI越权,数据锁杜绝SQL注入式绕过。配套的datadb参考手册.pdf里,专门用ER图标注了teacher_id字段如何贯穿classes→students→leaves三张表,让二次开发者一眼看清权限数据链路。
3. 核心模块解析与实操要点
3.1 数据库设计:SQLite如何支撑五角色协作?
data.db虽小,却是整个系统的地基。datadb参考手册.pdf里详细列出了7张表,但真正驱动业务的是这四张核心表:
| 表名 | 关键字段 | 作用说明 | 设计巧思 |
|---|---|---|---|
users | id, username, password_hash, role, real_name, dept_id | 所有角色统一存储 | role字段用VARCHAR而非ENUM,方便后期扩展新角色(如“实习指导教师”) |
leaves | id, student_id, reason, start_date, end_date, status, attachment_path, created_at | 请假主记录 | status用TEXT存’pending’,’approved’,’rejected’,避免INT枚举导致的维护成本 |
approvals | id, leave_id, approver_id, status, comment, approved_at | 审批流水日志 | 单独建表而非在leaves加approval_id字段,支持多级审批(辅导员初审+教务员终审) |
departments | id, name, type | 部门分类(教学部/学工部/教务处) | type字段区分部门性质,用于动态生成角色首页的待办统计 |
特别要注意leaves.attachment_path字段的设计。很多项目直接存文件二进制到BLOB,结果数据库暴涨且备份困难。本系统采用“文件系统存储+数据库存路径”策略:上传时用secure_filename()清洗文件名,保存到static/uploads/目录,数据库只记相对路径uploads/20240515_abc123.jpg。这样做的好处是:备份时data.db保持小巧,清理附件只需删static/uploads目录;排查问题时,直接打开路径就能看到原始文件。
初始化脚本data_creator.py的实操要点在于事务控制与默认数据注入。SQLite默认不开启外键约束,必须显式启用:
# data_creator.py 关键片段
conn = sqlite3.connect('data.db')
conn.execute("PRAGMA foreign_keys = ON") # 必须开启外键!
cursor = conn.cursor()
# 创建表语句(省略)
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL,
real_name TEXT,
dept_id INTEGER,
FOREIGN KEY (dept_id) REFERENCES departments(id)
)
""")
# 插入默认管理员(密码用generate_password.py生成)
cursor.execute(
"INSERT OR IGNORE INTO users (username, password_hash, role, real_name) VALUES (?, ?, ?, ?)",
('admin', 'pbkdf2:sha256:260000$...', 'admin', '系统管理员')
)
conn.commit() # 必须commit,否则数据不持久化
这里有两个易错点:一是忘记PRAGMA foreign_keys = ON,导致外键约束形同虚设;二是插入默认数据用INSERT OR IGNORE而非INSERT,避免重复运行脚本时报“UNIQUE constraint failed”错误。generate_password.py工具则用werkzeug.security.generate_password_hash()生成符合Flask安全标准的哈希值,比手写hashlib.sha256().hexdigest()更可靠——后者缺少盐值(salt)和迭代次数,容易被彩虹表破解。
3.2 角色权限管理:role.py如何让权限逻辑可维护?
role.py不是简单的角色常量定义,而是权限规则的中央调度器。它解决三个关键问题:角色能力映射、页面可见性控制、操作可行性判断。
首先,定义角色能力矩阵(role_capabilities.py):
# role_capabilities.py
ROLE_CAPABILITIES = {
'admin': {
'can_manage_users': True,
'can_view_all_leaves': True,
'can_export_data': True,
'can_edit_system_config': True
},
'teacher': {
'can_view_class_attendance': True,
'can_approve_leaves': True,
'can_upload_teaching_materials': False # 教师无材料上传权限
},
# 其他角色...
}
然后在role.py中封装校验方法:
# role.py
from flask import session, abort
from app.classes.user import User
def has_capability(capability):
"""检查当前用户是否具备某项能力"""
if 'user_id' not in session:
return False
user = User.query.get(session['user_id'])
if not user:
return False
caps = ROLE_CAPABILITIES.get(user.role, {})
return caps.get(capability, False)
def require_capability(capability):
"""装饰器:要求用户具备某项能力"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if not has_capability(capability):
abort(403) # 返回HTTP 403 Forbidden
return f(*args, **kwargs)
return decorated_function
return decorator
这个设计的优势在于:权限变更无需改路由或模板,只需修改ROLE_CAPABILITIES字典。比如教务处要求教师也能查看全校考勤汇总,只需把'teacher'下的'can_view_all_attendance'设为True,所有调用has_capability('can_view_all_attendance')的地方自动生效。
在模板中使用更直观:
<!-- admin.html -->
{% if has_capability('can_manage_users') %}
<a href="{{ url_for('admin_manage_users') }}" class="btn btn-warning">用户管理</a>
{% endif %}
<!-- teacher.html -->
{% if has_capability('can_approve_leaves') %}
<button onclick="approveLeave({{ leave.id }})" class="btn btn-success">批准</button>
{% endif %}
注意这里调用的是has_capability()函数而非Jinja2变量,因为能力判断需实时查询数据库(如教师是否被分配到某班级),不能静态渲染。为此,app.py在before_request钩子中将has_capability注入模板上下文:
@app.before_request
def inject_capabilities():
g.has_capability = has_capability # g对象全局可用
这样模板里就能直接用has_capability('xxx'),无需每次传参。这种“能力即函数”的设计,比硬编码if current_user.role in ['admin', 'office']清晰得多,也便于后期接入LDAP或OAuth2认证时替换能力校验逻辑。
3.3 前端交互关键组件:BootstrapTable与文件上传的避坑指南
前端最常出问题的两个组件是BootstrapTable动态加载和BootstrapFileinput文件上传。它们的坑不在文档里,而在浏览器兼容性和Flask请求处理的细节中。
BootstrapTable分页失效问题
现象:点击第二页,表格内容不变。根源在于Flask路由返回的JSON格式不符合BootstrapTable预期。正确响应必须包含total和rows两个顶层字段:
# app.py 正确写法
@app.route('/api/leaves')
def api_leaves():
page = request.args.get('page', 1, type=int)
limit = request.args.get('limit', 10, type=int)
# 查询总记录数(用于计算总页数)
total = Leave.query.count()
# 查询当前页数据
leaves = Leave.query.offset((page-1)*limit).limit(limit).all()
# 必须返回 { "total": 100, "rows": [...] } 结构
return jsonify({
'total': total,
'rows': [leave.to_dict() for leave in leaves] # to_dict()方法需在Leave模型中定义
})
如果漏掉total字段,BootstrapTable会认为只有一页数据。而to_dict()方法必须显式定义,不能依赖__dict__,因为后者会暴露_sa_instance_state等SQLAlchemy内部字段,导致JSON序列化失败。
BootstrapFileinput上传失败问题
现象:选择文件后进度条不动,控制台报400错误。根本原因是Flask默认禁用multipart/form-data大文件上传。需在app.py开头显式配置:
# app.py 开头
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB限制
app.config['UPLOAD_FOLDER'] = 'static/uploads'
app.config['ALLOWED_EXTENSIONS'] = {'pdf', 'jpg', 'jpeg', 'png', 'doc', 'docx'}
# 确保上传目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
前端HTML中,fileinput组件必须设置uploadUrl指向正确的Flask路由,并启用showPreview:
<input id="leave-attachment"
name="attachment"
type="file"
data-upload-url="{{ url_for('upload_attachment') }}"
data-show-preview="true"
data-allowed-file-extensions='["pdf","jpg","jpeg","png"]'>
后端路由upload_attachment需处理文件保存和路径返回:
@app.route('/upload', methods=['POST'])
def upload_attachment():
if 'attachment' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['attachment']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
# 添加时间戳避免重名
timestamp = int(time.time())
new_filename = f"{timestamp}_{filename}"
filepath = os.path.join(app.config['UPLOAD_FOLDER'], new_filename)
file.save(filepath)
# 返回相对路径供数据库存储
return jsonify({
'initialPreview': [f'<img src="/static/uploads/{new_filename}" class="file-preview-image">'],
'initialPreviewConfig': [{'caption': filename, 'width': '120px', 'url': '/delete_upload', 'key': new_filename}]
})
return jsonify({'error': 'File type not allowed'}), 400
这里的关键技巧是:返回的initialPreview必须是HTML字符串,而非纯图片URL,否则BootstrapFileinput无法渲染预览图。而initialPreviewConfig里的url是删除接口,需另行实现。
4. 实操过程与核心环节实现
4.1 从零启动:五分钟完成本地部署
部署流程刻意设计为“无脑操作”,目标是让非技术人员也能完成。以下是严格按顺序执行的步骤,每步附带验证方法:
第一步:环境准备(2分钟)
- 下载Python 3.8+(官网python.org,勾选“Add Python to PATH”)
- 验证:命令行输入python --version,应显示Python 3.8.10或更高
- 创建项目文件夹,解压源码包到该目录
第二步:安装依赖(1分钟)
- 进入项目根目录,执行:
bash pip install -r requirements.txt
- requirements.txt内容精简至核心:
Flask==2.3.3 Flask-SQLAlchemy==3.0.5 Werkzeug==2.3.7 Jinja2==3.1.2 python-dotenv==1.0.0
- 验证:执行pip list | grep Flask,应显示Flask 2.3.3
第三步:初始化数据库(30秒)
- 运行初始化脚本:
bash python data_creator.py
- 验证:检查目录下是否生成data.db文件(大小应>10KB),用DB Browser for SQLite打开,确认users表中有admin用户记录
第四步:启动服务(10秒)
- 执行启动脚本:
bash python launch.py
- launch.py内容极简:
python from app import app if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000)
- 验证:浏览器访问http://127.0.0.1:5000,出现登录页;用admin/admin123登录(密码见datadb参考手册.pdf第3页)
第五步:角色功能验证(2分钟)
- 登录admin账号,点击“用户管理”,新增一个学生账号(用户名stu001,角色student)
- 退出,用stu001登录,进入“我要请假”,填写表单并上传附件
- 用admin账号切换到“辅导员”角色(datadb参考手册.pdf提供角色切换方法),在“待审批”列表看到该申请
- 点击“批准”,状态变为approved,学生端首页显示“已通过”
整个过程无需修改任何代码,所有配置都在config.py中集中管理。launch.py比直接运行app.py更安全,因为它强制加载.env环境变量,避免调试模式意外暴露在公网。
4.2 密码安全实践:generate_password.py的正确用法
generate_password.py不是玩具脚本,而是生产环境密码管理的起点。它的核心价值在于强制使用PBKDF2哈希算法,而非明文存储或弱哈希。
脚本执行方式:
python generate_password.py --password "MySecurePass123" --username "teacher01"
输出示例:
Generated hash for teacher01: pbkdf2:sha256:260000$XyZaBcDeFgHiJkLm$...
Update this in data.db's users table.
关键原理:PBKDF2通过高迭代次数(260000次)和随机盐值(XyZaBcDeFgHiJkLm),让暴力破解成本指数级上升。对比MD5哈希md5("123456")="e10adc3949ba59abbe56e057f20f883e",攻击者用GPU集群1秒可尝试10亿次;而PBKDF2哈希需1秒计算1次,破解一个密码平均耗时数年。
在app.py中验证密码的代码必须匹配:
from werkzeug.security import check_password_hash
# 用户登录时
user = User.query.filter_by(username=username).first()
if user and check_password_hash(user.password_hash, password):
# 登录成功
这里check_password_hash()会自动提取盐值和迭代次数,无需开发者手动解析。generate_password.py生成的哈希字符串中,pbkdf2:sha256:260000$是算法标识,XyZaBcDeFgHiJkLm是盐值,后续是实际哈希值。这种设计保证了即使数据库泄露,攻击者也无法批量破解密码。
4.3 考勤记录页面(attendance.html)的数据联动逻辑
attendance.html表面是静态表格,实则是多张表关联查询的结果。它展示的不是原始请假记录,而是经过状态聚合的考勤视图:
| 字段 | 数据来源 | 计算逻辑 |
|---|---|---|
student_id | students.id | 直接关联 |
real_name | students.real_name | 学生真实姓名 |
class_name | classes.name | JOIN classes表获取 |
total_days | SUM(leaves.end_date - leaves.start_date + 1) | 按学生GROUP BY求和 |
status | CASE WHEN total_days > 5 THEN ‘缺勤超标’ ELSE ‘正常’ END | 动态计算 |
后端路由/api/attendance的SQL查询需优化:
# app.py
@app.route('/api/attendance')
def api_attendance():
# 使用原始SQL避免ORM复杂JOIN性能损耗
sql = """
SELECT
s.id as student_id,
s.real_name,
c.name as class_name,
COALESCE(SUM(julianday(l.end_date) - julianday(l.start_date) + 1), 0) as total_days,
CASE
WHEN COALESCE(SUM(julianday(l.end_date) - julianday(l.start_date) + 1), 0) > 5
THEN '缺勤超标'
ELSE '正常'
END as status
FROM students s
LEFT JOIN classes c ON s.class_id = c.id
LEFT JOIN leaves l ON s.id = l.student_id AND l.status = 'approved'
GROUP BY s.id, s.real_name, c.name
ORDER BY total_days DESC
LIMIT 50
"""
rows = db.session.execute(text(sql)).fetchall()
return jsonify([dict(row) for row in rows])
这里用julianday()函数计算日期差,比Python端循环计算快10倍;LEFT JOIN确保即使学生从未请假,也会出现在考勤表中(total_days=0);COALESCE处理NULL值,避免前端渲染异常。LIMIT 50是性能保护,防止全校数据一次性加载卡死浏览器。
5. 常见问题与排查技巧实录
5.1 启动报错排查速查表
| 错误现象 | 可能原因 | 解决方案 | 验证方法 |
|---|---|---|---|
ModuleNotFoundError: No module named 'flask' | Python环境未激活或pip安装失败 | 重新执行pip install flask,检查是否在虚拟环境中 | python -c "import flask; print(flask.__version__)" |
OperationalError: no such table: users | data_creator.py未运行或执行失败 | 删除data.db,重新运行python data_creator.py | 用DB Browser打开data.db,确认users表存在 |
jinja2.exceptions.TemplateNotFound: admin.html | 模板路径错误或文件名大小写不符 | 检查templates/admin.html是否存在,Windows系统注意大小写 | 在文件资源管理器中直接定位该路径 |
400 Bad Request上传附件失败 | 文件名含中文或特殊字符 | 修改secure_filename()逻辑,或前端限制文件类型 | 用英文名PDF测试上传 |
500 Internal Server Error登录后白屏 | session.html中current_user未定义 | 检查app.py是否正确设置了login_manager | 在session.html中添加{{ current_user.username if current_user else 'None' }}调试 |
独家技巧:用print()代替日志调试
Flask调试模式下,直接在路由函数中加print(request.form),终端会实时输出表单数据。比配置logging模块更快捷,尤其适合新手定位参数传递问题。
5.2 权限失效的典型场景与修复
场景一:学生能访问教师审批页
现象:学生登录后,手动在地址栏输入/teacher/approvals,页面正常显示。
原因:@require_role('teacher')装饰器未应用到该路由,或装饰器位置错误(写在@app.route上面)。
修复:检查views/teacher.py,确保装饰器紧贴函数定义:
# 正确
@app.route('/teacher/approvals')
@require_role('teacher')
def teacher_approvals():
return render_template('teacher.html')
# 错误(装饰器位置错)
@require_role('teacher')
@app.route('/teacher/approvals')
def teacher_approvals():
return render_template('teacher.html')
场景二:管理员看不到所有请假记录
现象:管理员登录后,/admin/all_leaves只显示部分记录。
原因:查询语句中遗漏了status条件,或JOIN时未处理NULL值。
修复:检查admin.py中的查询,必须用LEFT JOIN并允许l.status IS NULL:
# 正确:显示所有学生,包括未请假者
db.session.query(Student, Leave).outerjoin(Leave, Student.id == Leave.student_id).all()
# 错误:INNER JOIN导致只显示有请假记录的学生
db.session.query(Student, Leave).join(Leave).all()
场景三:附件上传后路径不显示
现象:上传成功,但leaves.attachment_path字段为空。
原因:前端未正确设置name属性,或后端未从request.files获取。
修复:检查HTML中<input>的name必须与后端request.files['xxx']一致:
<!-- 前端 -->
<input name="attachment" type="file">
<!-- 后端 -->
file = request.files['attachment'] # name必须完全匹配
5.3 性能优化与二次开发建议
轻量级优化三板斧
1. 静态资源压缩:将static/css/bootstrap.min.css替换为官方CDN链接,减少本地文件体积
2. 数据库索引添加:在leaves表的student_id和status字段上建复合索引,加速审批列表查询
sql CREATE INDEX idx_leave_status_student ON leaves(status, student_id);
3. 模板缓存启用:在app.py中添加app.config['TEMPLATES_AUTO_RELOAD'] = True,开发时自动重载,生产环境设为False提升性能
二次开发安全边界
- 新增角色:只需在ROLE_CAPABILITIES字典中添加新键值对,无需改数据库结构
- 扩展字段:如需在请假表加“紧急程度”字段,用SQLite的ALTER TABLE命令:
sql ALTER TABLE leaves ADD COLUMN urgency TEXT DEFAULT 'normal';
- 对接微信通知:在approve_leave()函数末尾添加企业微信API调用,利用requests.post()发送消息,不影响现有流程
最后分享一个小技巧:所有HTML页面底部都有<!-- DEBUG: {{ config.DEBUG }} -->注释,生产环境部署时,把config.py中的DEBUG = False,这段注释自动消失,避免暴露配置信息。这种“开发友好、生产安全”的设计,才是轻量级系统该有的样子。
简介:基于Flask框架开发的轻量级校园请假审批系统,直接运行即可使用。后端用SQLite存储用户资料、请假申请、审批状态和角色权限数据,无需额外安装数据库服务。系统预设管理员、学生、教师、辅导员、教务员五类角色,各自拥有独立首页(admin.html、student.html等)和操作权限,通过role.py统一管理角色逻辑,session.html处理登录态与身份识别。核心功能包括请假提交、多级审批流程、考勤记录查看(attendance.html)、密码生成工具(generate_password.py)及数据库初始化脚本(data_creator.py)。前端采用Bootstrap 5响应式布局,集成jQuery、BootstrapTable实现动态表格展示、BootstrapFileinput支持附件上传、jQuery-Confirm提供交互提示,所有页面继承base.html保持风格统一。静态资源包含7个CSS样式文件、10个JS交互脚本、FontAwesome图标库及favicon.ico。配套提供datadb参考手册.pdf,详细说明data.db中各表字段含义与关联关系,便于二次开发或字段扩展。启动方式简单:安装requirements.txt依赖后运行launch.py或app.py即可访问本地服务。
1149

被折叠的 条评论
为什么被折叠?



