文章目录
- 1.学习前提
-
- [1.1 flask框架学习](#1.1 flask框架学习)
- [1.2 vscode配置python环境](#1.2 vscode配置python环境)
- [1.3 flask框架实现orm操作](#1.3 flask框架实现orm操作)
-
- [1.3.1 基础查询get、filter、filter_by、order_by、with_entities](#1.3.1 基础查询get、filter、filter_by、order_by、with_entities)
- [1.3.2 数据记录 first、all、one、limit、offset、count、paginate](#1.3.2 数据记录 first、all、one、limit、offset、count、paginate)
- 2.项目展示
- [3 sqlite3数据库表结构 py-orm展示](#3 sqlite3数据库表结构 py-orm展示)
-
- [3.1 权限管理](#3.1 权限管理)
- [3.2 models](#3.2 models)
- [3.3 数据库设计](#3.3 数据库设计)
-
- [3.3.1. 用户管理模块](#3.3.1. 用户管理模块)
- [3.2.1. 考试管理模块](#3.2.1. 考试管理模块)
- [3.3.3. 评分与答卷模块](#3.3.3. 评分与答卷模块)
- [3.3.4. 权限标识数据](#3.3.4. 权限标识数据)
- 4.后端设计
-
- [4.1. 目录结构](#4.1. 目录结构)
- [4.2. 核心模块说明](#4.2. 核心模块说明)
-
- [4.2.1 应用工厂 (app/init.py)](#4.2.1 应用工厂 (app/init.py))
- [4.2.2 配置管理 (config.py)](#4.2.2 配置管理 (config.py))
- [4.2.3 数据库模型 (app/models.py)](#4.2.3 数据库模型 (app/models.py))
- [4.2.4 API 接口 (app/api/)](#4.2.4 API 接口 (app/api/))
- [4.2.5 权限控制 (app/decorators.py)](#4.2.5 权限控制 (app/decorators.py))
- [4.2.6 工具类 (app/utils.py)](#4.2.6 工具类 (app/utils.py))
- 5.前端设计
-
- [5.1. 技术栈](#5.1. 技术栈)
- [5.2. 目录结构](#5.2. 目录结构)
- [5.3. 核心模块](#5.3. 核心模块)
-
- [5.3.1 认证与权限](#5.3.1 认证与权限)
- [5.3.2 状态管理](#5.3.2 状态管理)
- [5.4.3 网络请求](#5.4.3 网络请求)
- [5.4.4 核心组件](#5.4.4 核心组件)
- [5.4 管理员页面](#5.4 管理员页面)
- [5.5 用户页面](#5.5 用户页面)
- 6.最后
-
- [6.1 参考](#6.1 参考)
- [6.2 总结](#6.2 总结)
1.学习前提
项目情况
- Python3.12.9+
- Vue 3
- Redis
- SqlLite3
1.1 flask框架学习
推荐菜鸟编程:https://www.runoob.com/flask/flask-tutorial.html
基本上用一下午的时间过一遍,就能快速写出一个web小程序了,这里编译器推荐vscode,不推荐pycharm,因为我个人认为vscode更轻量,用i起来可以根据自己的习惯去配置。
1.2 vscode配置python环境
1.创建虚拟环境并激活。

2.vscode选择虚拟环境之后,即自动激活,通过 pip install -r requirements.txt 下载flask框架,并创建web测试程序。


如上,一个简易的flask项目就i已经完成了。
但是再写一个web项目的时候,可能需要涉及到数据库、缓存,但是我也相信,既然你看到这里了,那么肯定是有一定基础了。
所以,不再过多赘述。
1.3 flask框架实现orm操作

如上图,为什么使用User 模型类去调用query呢?

py
from flask import Flask
from flask_bootstrap import Bootstrap5
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_mail import Mail
from flask_cors import CORS
from flask_jwt_extended import JWTManager
from redis import Redis
from config import config
bootstrap = Bootstrap5()
db = SQLAlchemy()
mail = Mail()
jwt = JWTManager()
cors = CORS()
这是因为,我们引入了flask_sqlalchemy其实是可以使用db.session.query(Model)来操作的,但是每一个模型类型又继承了db.Model,所以可以直接使用这些类名去调用相关sql方法。
1.3.1 基础查询get、filter、filter_by、order_by、with_entities
sql
# 1.get按照主键查询
user = User.query.get(1) # 查询 ID 为 1 的用户(存在返回实例,不存在返回 None)
# 2.filter复杂的条件查询
users = User.query.filter(
User.registered_at > datetime(2023, 1, 1),
User.email.like('%@example.com')
).all()
# 3.filter_by等值查询,无法使用区间匹配、模糊搜索等
active_users = User.query.filter_by(is_active=True).all() # 查询所有活跃用户
# 4.order_by排序
# 5.withentitiex 类似 于 SELLETCT
1.3.2 数据记录 first、all、one、limit、offset、count、paginate
学过的Go语言的gORM的其实对以上的一些功能也有同感,可惜没有类似Last、Take的功能,但是first、one这些足够用了。具体功能不做解释了,可以自行搜一下。
2.项目展示

管理员页面

用户页面

3 sqlite3数据库表结构 py-orm展示
3.1 权限管理
py
class Permission:
TAKE_EXAM = 0x01
VIEW_HISTORY = 0x02
CREATE_EXAM = 0x04
GRADE_EXAM = 0x08
ADMINISTER = 0x80
这是一个权限类,我才用16进制的方式,管理每一个用户的对应权限。这样做的好处就是通过一个INTERGER的字段就可以表示这个用户的权限。避免多列表方式,简单高效。
1.比如多权限用户
py
# 赋予用户:参加考试(0x01) 和 查看历史(0x02)
user_permissions = Permission.TAKE_EXAM | Permission.VIEW_HISTORY
# 结果:0x03 (二进制 0000 0011)
0000 0001 (TAKE_EXAM)
0000 0010 (VIEW_HISTORY)
-----------
0000 0011 (结果 0x03)
2.检查用户权限
py
# 1.没有权限
0000 0011 (用户只有 0x01 和 0x02)
0000 1000 (我们要查的是 0x08)
-----------
0000 0000 (结果是 0,在 Python 中代表 False)
# 2.有权限
0000 0011 (用户有 0x01 和 0x02)
0000 0001 (我们要查的是 0x01)
-----------
0000 0001 (结果是 0x01,不为 0,在 Python 中代表 True)
3.移除某个权限
py
# 假设用户目前有:考试(0x01) 和 看历史(0x02),总值是 0x03
user_perm = 0x03
# 现在我们要【删除】考试权限 (0x01)
user_perm = user_perm & ~Permission.TAKE_EXAM
# 结果:user_perm 变成了 0x02 (只剩下看历史了)
0000 0011 (用户原有的: 考试 + 历史)
1111 1110 (除了考试位,其他全都要) # 对原有进制取非,在进行且运算即可
-----------
0000 0010 (结果:只剩下了历史)
3.2 models
py
import json
from flask import current_app
from flask_login import UserMixin
from . import db, login_manager
from .utils import TimeUtil
class Permission:
TAKE_EXAM = 0x01
VIEW_HISTORY = 0x02
CREATE_EXAM = 0x04
GRADE_EXAM = 0x08
ADMINISTER = 0x80
class UserType(db.Model):
__tablename__ = "user_types"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True, index=True)
users = db.relationship(
"User",
backref="user_type",
lazy="dynamic",
primaryjoin="UserType.id==User.user_type_id",
foreign_keys="User.user_type_id",
)
@staticmethod
def insert_default_types():
types = ["默认分组"]
for t in types:
if not UserType.query.filter_by(name=t).first():
db.session.add(UserType(name=t))
db.session.commit()
def to_dict(self):
return {
"id": self.id,
"name": self.name,
}
# 关联表:考试 - 用户类型
exam_user_types = db.Table(
"exam_user_types",
db.Column("exam_id", db.Integer, db.ForeignKey("exams.id"), primary_key=True),
db.Column(
"user_type_id", db.Integer, db.ForeignKey("user_types.id"), primary_key=True
),
)
class Role(db.Model):
__tablename__ = "roles"
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64), unique=True)
default = db.Column(db.Boolean, default=False, index=True)
permissions = db.Column(db.Integer)
users = db.relationship(
"User",
backref="role",
lazy="dynamic",
foreign_keys="User.role_id",
primaryjoin="Role.id==User.role_id",
)
@staticmethod
def insert_roles():
roles = {
"用户": (Permission.TAKE_EXAM | Permission.VIEW_HISTORY, True),
"管理员": (0xFF, False),
}
for r in roles:
role = Role.query.filter_by(name=r).first()
if role is None:
role = Role(name=r)
role.permissions = roles[r][0]
role.default = roles[r][1]
db.session.add(role)
db.session.commit()
def __repr__(self):
return "<Role %r>" % self.name
class User(UserMixin, db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(64), unique=True, index=True)
username = db.Column(db.String(64), index=True)
role_id = db.Column(db.Integer)
user_type_id = db.Column(db.Integer)
status = db.Column(db.String(20), default="pending")
created_at = db.Column(db.DateTime(), default=TimeUtil.now)
submissions = db.relationship(
"Submission",
backref="student",
lazy="dynamic",
cascade="all, delete-orphan",
foreign_keys="Submission.user_id",
primaryjoin="User.id==Submission.user_id",
)
def __init__(self, **kwargs):
super(User, self).__init__(**kwargs)
if self.role is None:
if self.email == current_app.config["FLASKY_ADMIN"]:
self.role = Role.query.filter_by(permissions=0xFF).first()
self.status = "approved"
if self.role is None:
self.role = Role.query.filter_by(default=True).first()
if self.user_type_id is None:
pass
def can(self, permissions):
return (
self.role is not None
and (self.role.permissions & permissions) == permissions
)
def is_administrator(self):
return self.can(Permission.ADMINISTER)
def to_dict(self):
return {
"id": self.id,
"userId": self.id,
"email": self.email,
"userName": self.username,
"role": self.role.name if self.role else None,
"userType": self.user_type.name if self.user_type else None,
"userTypeId": self.user_type_id,
"status": self.status, # pending, approved, rejected
"createTime": TimeUtil.format(self.created_at),
}
def __repr__(self):
return "<User %r>" % self.email
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class QuestionType:
CHOICE = "choice"
ESSAY = "essay"
CODE = "code"
class Question(db.Model):
__tablename__ = "questions"
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text)
question_type = db.Column(db.String(20)) # choice, essay, code
points = db.Column(db.Integer, default=10)
extra_data = db.Column(db.Text) # JSON: options, test_cases
created_at = db.Column(db.DateTime(), default=TimeUtil.now)
exams = db.relationship(
"ExamQuestion",
back_populates="question",
cascade="all, delete-orphan",
primaryjoin="Question.id==ExamQuestion.question_id",
foreign_keys="ExamQuestion.question_id",
)
def get_extra(self):
try:
return json.loads(self.extra_data) if self.extra_data else {}
except:
return {}
def set_extra(self, data):
self.extra_data = json.dumps(data)
def to_dict(self):
return {
"id": self.id,
"content": self.content,
"question_type": self.question_type,
"points": self.points,
"extra_data": self.get_extra(),
"created_at": TimeUtil.format(self.created_at),
}
class Exam(db.Model):
__tablename__ = "exams"
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(128))
description = db.Column(db.Text)
duration_minutes = db.Column(db.Integer, default=60)
start_time = db.Column(db.DateTime)
status = db.Column(db.String(20), default="draft")
created_by = db.Column(db.Integer)
created_at = db.Column(db.DateTime(), default=TimeUtil.now)
settings = db.Column(db.Text, default="{}")
allowed_user_types = db.relationship(
"UserType",
secondary=exam_user_types,
lazy="subquery",
backref=db.backref("exams", lazy=True),
primaryjoin="Exam.id==exam_user_types.c.exam_id",
secondaryjoin="UserType.id==exam_user_types.c.user_type_id",
)
questions = db.relationship(
"ExamQuestion",
back_populates="exam",
cascade="all, delete-orphan",
primaryjoin="Exam.id==ExamQuestion.exam_id",
foreign_keys="ExamQuestion.exam_id",
)
submissions = db.relationship(
"Submission",
backref="exam",
lazy="dynamic",
foreign_keys="Submission.exam_id",
primaryjoin="Exam.id==Submission.exam_id",
)
def get_settings(self):
try:
return json.loads(self.settings) if self.settings else {}
except:
return {}
def to_dict(self):
return {
"id": self.id,
"title": self.title,
"description": self.description,
"duration_minutes": self.duration_minutes,
"start_time": TimeUtil.format(self.start_time),
"status": self.status,
"allowed_user_types": [ut.id for ut in self.allowed_user_types],
"allowed_user_type_names": [ut.name for ut in self.allowed_user_types],
"created_by": self.created_by,
"created_at": TimeUtil.format(self.created_at),
"settings": self.get_settings(),
}
class ExamQuestion(db.Model):
__tablename__ = "exam_questions"
exam_id = db.Column(db.Integer, db.ForeignKey("exams.id"), primary_key=True)
question_id = db.Column(db.Integer, db.ForeignKey("questions.id"), primary_key=True)
order = db.Column(db.Integer, default=0)
score = db.Column(db.Integer)
exam = db.relationship(
"Exam",
back_populates="questions",
primaryjoin="Exam.id==ExamQuestion.exam_id",
foreign_keys="ExamQuestion.exam_id",
)
question = db.relationship(
"Question",
back_populates="exams",
primaryjoin="Question.id==ExamQuestion.question_id",
foreign_keys="ExamQuestion.question_id",
)
class Submission(db.Model):
__tablename__ = "submissions"
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer)
exam_id = db.Column(db.Integer)
start_time = db.Column(db.DateTime, default=TimeUtil.now)
submit_time = db.Column(db.DateTime)
total_score = db.Column(db.Float, default=0.0)
status = db.Column(db.String(20), default="pending")
admin_feedback = db.Column(db.Text)
answers = db.relationship(
"SubmissionAnswer",
backref="submission",
lazy="dynamic",
cascade="all, delete-orphan",
foreign_keys="SubmissionAnswer.submission_id",
primaryjoin="Submission.id==SubmissionAnswer.submission_id",
)
def to_dict(self, include_answers=False):
data = {
"id": self.id,
"user_id": self.user_id,
"exam_id": self.exam_id,
"start_time": TimeUtil.format(self.start_time),
"submit_time": TimeUtil.format(self.submit_time),
"total_score": self.total_score,
"status": self.status,
"admin_feedback": self.admin_feedback,
"student_name": self.student.username if self.student else None,
"exam_title": self.exam.title if self.exam else None,
}
if include_answers:
data["answers"] = [ans.to_dict() for ans in self.answers]
return data
class SubmissionAnswer(db.Model):
__tablename__ = "submission_answers"
id = db.Column(db.Integer, primary_key=True)
submission_id = db.Column(db.Integer)
question_id = db.Column(db.Integer)
content = db.Column(db.Text)
score = db.Column(db.Float, default=0.0)
comment = db.Column(db.Text)
question = db.relationship(
"Question",
foreign_keys="SubmissionAnswer.question_id",
primaryjoin="Question.id==SubmissionAnswer.question_id",
)
def to_dict(self):
return {
"id": self.id,
"submission_id": self.submission_id,
"question_id": self.question_id,
"content": self.content,
"score": self.score,
"comment": self.comment,
"question": self.question.to_dict() if self.question else None,
}
3.3 数据库设计
在线考试系统,主要包含三个核心模块:用户管理(用户、角色、用户组)、考试管理(考试、题目)以及考试执行(答卷、答题详情)。
- 用户 (User) 属于一个 角色 (Role) 和一个 用户组 (UserType)。
- 考试 (Exam) 包含多个 题目 (Question) (通过
ExamQuestion表进行多对多关联,并包含题目顺序和分值信息)。 - 考试 (Exam) 可以授权给多个 用户组 (UserType) 参加(多对多关联)。
- 用户 (User) 参加考试会产生 答卷 (Submission)。
- 答卷 (Submission) 包含多个 答题详情 (SubmissionAnswer)。
3.3.1. 用户管理模块
users (用户表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
id |
Integer | 主键 | 用户的唯一标识符 |
email |
String(64) | 唯一, 索引 | 用户的邮箱地址,用于登录系统 |
username |
String(64) | 索引 | 用户的显示名称 |
role_id |
Integer | 外键 -> roles.id |
关联的角色ID,决定用户的权限(如管理员、普通用户) |
user_type_id |
Integer | 外键 -> user_types.id |
关联的用户组ID,用于考试的分组授权 |
status |
String(20) | 默认: 'pending' | 账号状态(例如:'pending'-待审核, 'approved'-已批准) |
created_at |
DateTime | 账号创建时间 |
roles (角色表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
id |
Integer | 主键 | 角色的唯一标识符 |
name |
String(64) | 唯一 | 角色名称(如 '管理员') |
default |
Boolean | 默认: False, 索引 | 是否为新注册用户的默认角色 |
permissions |
Integer | 权限位掩码,用于控制具体操作权限(如创建考试、阅卷等) |
user_types(用户组/类型表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
id |
Integer | 主键 | 用户组的唯一标识符 |
name |
String(64) | 唯一, 索引 | 用户组名称 |
3.2.1. 考试管理模块
exams (考试表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
id |
Integer | 主键 | 考试的唯一标识符 |
title |
String(128) | 考试标题 | |
description |
Text | 考试说明或指导语 | |
duration_minutes |
Integer | 默认: 60 | 考试时长限制(分钟) |
start_time |
DateTime | 考试开始时间 | |
status |
String(20) | 默认: 'draft' | 考试状态('draft'-草稿, 'published'-已发布, 'ongoing'-进行中) |
created_by |
Integer | 外键 -> users.id |
创建该考试的用户ID |
created_at |
DateTime | 考试创建时间 | |
settings |
Text | 默认: '{}' | JSON 格式的扩展配置 |
questions (题库表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
id |
Integer | 主键 | 题目的唯一标识符 |
content |
Text | 题干内容(支持 HTML/富文本) | |
question_type |
String(20) | 题目类型 | |
points |
Integer | 默认: 10 | 题目的默认分值 |
extra_data |
Text | JSON 格式的额外数据(如选择题的选项、编程题的测试用例) | |
created_at |
DateTime | 题目创建时间 |
exam_questions (试卷-题目关联表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
exam_id |
Integer | 主键 , 外键 -> exams.id |
关联的考试ID |
question_id |
Integer | 主键 , 外键 -> questions.id |
关联的题目ID |
order |
Integer | 默认: 0 | 题目在试卷中的显示顺序 |
score |
Integer | 该题目在本次考试中的具体分值 |
exam_user_types(考试-权限关联表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
exam_id |
Integer | 主键 , 外键 -> exams.id |
关联的考试ID |
user_type_id |
Integer | 主键 , 外键 -> user_types.id |
允许参加考试的用户组ID |
3.3.3. 评分与答卷模块
submissions(答卷表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
id |
Integer | 主键 | 答卷的唯一标识符 |
user_id |
Integer | 外键 -> users.id |
考生的用户ID |
exam_id |
Integer | 外键 -> exams.id |
参加的考试ID |
start_time |
DateTime | 考生开始答题的时间 | |
submit_time |
DateTime | 考生交卷的时间 | |
total_score |
Float | 默认: 0.0 | 答卷的总得分 |
status |
String(20) | 默认: 'pending' | 阅卷状态('pending'-待批改, 'graded'-已批改) |
admin_feedback |
Text | 老师对整张试卷的评语 |
submission_answers (答题详情表)
| 字段名 | 类型 | 约束 | 作用描述 |
|---|---|---|---|
id |
Integer | 主键 | 答题记录的唯一标识符 |
submission_id |
Integer | 外键 -> submissions.id |
关联的答卷ID |
question_id |
Integer | 外键 -> questions.id |
关联的题目ID |
content |
Text | 考生的作答内容(文本、选项值或代码) | |
score |
Float | 默认: 0.0 | 该题的得分 |
comment |
Text | 老师对该题的单题评语 |
3.3.4. 权限标识数据
TAKE_EXAM(0x01): 参加考试VIEW_HISTORY(0x02): 查看考试记录CREATE_EXAM(0x04): 创建考试GRADE_EXAM(0x08): 阅卷ADMINISTER(0x80): 系统管理CHOICE: 选择题ESSAY: 问答题(简答/论述)CODE: 编程题
4.后端设计
Flask-exampy 是一个基于 Flask 框架构建的在线考试系统后端。它提供了一整套 RESTful API,用于处理用户认证、考试管理、题目管理、在线答题及自动阅卷等核心功能。
4.1. 目录结构
backend/
├── app/ # 应用核心代码
│ ├── api/ # API 接口模块(蓝图)
│ │ ├── auth.py # 认证相关接口 (登录, 注册, 令牌刷新)
│ │ ├── exam.py # 考试管理接口 (创建, 发布, 列表, 详情)
│ │ ├── question.py # 题目管理接口 (增删改查)
│ │ ├── system.py # 系统管理接口 (用户管理, 角色管理)
│ │ ├── common.py # 公共接口
│ │ └── ...
│ ├── templates/ # 邮件模板
│ ├── models.py # 数据库模型定义
│ ├── email.py # 邮件发送逻辑
│ ├── utils.py # 通用工具类
│ ├── decorators.py # 自定义装饰器
│ └── __init__.py # 应用工厂函数 create_app
├── configs/ # 配置文件
│ ├── dev.yaml # 开发环境配置
│ └── prod.yaml # 生产环境配置
├── migrations/ # 数据库迁移脚本
├── config.py # 配置加载器
├── manage.py # CLI 入口脚本
├── Dockerfile # 容器构建文件
└── requirements.txt # Python 依赖列表
4.2. 核心模块说明
4.2.1 应用工厂 (app/init.py)
- create_app(config_name): 初始化 Flask 应用的核心函数。
- 负责初始化所有扩展:数据库 (
db)、邮件 (mail)、JWT (jwt)、Redis (redis_client) 等。 - 注册 API 蓝图。
- 启动后台调度线程 (
_start_scheduler),用于定期检查考试状态。
4.2.2 配置管理 (config.py)
- 支持从 YAML 文件 (
configs/dev.yaml,configs/prod.yaml) 加载配置。 - 定义了
DevelopmentConfig和ProductionConfig,通过 YAML 配置进行切换。 - 配置项包括数据库 URI、Redis 地址、邮件服务器设置、密钥等。
4.2.3 数据库模型 (app/models.py)
- 用户体系 :
User,Role,UserType(支持基于角色的权限控制 RBAC 和基于用户组的访问控制)。 - 考试体系 :
Exam,Question,ExamQuestion(关联表,支持题目顺序和分值自定义)。 - 答卷体系 :
Submission,SubmissionAnswer(记录考生的答题情况和得分)。
4.2.4 API 接口 (app/api/)
auth.py: 处理用户注册、登录、获取当前用户信息。使用 JWT 进行身份验证。system.py: 管理员专用接口,用于管理用户列表、修改用户分组、审核账号等。question.py: 题库管理,支持选择题、简答题、编程题的增删改查。exam.py: 考试全生命周期管理(创建草稿 -> 发布 -> 进行中 -> 结束 -> 阅卷)。
4.2.5 权限控制 (app/decorators.py)
@permission_required: 检查用户是否拥有特定权限位。@admin_required: 专门用于保护需要管理员权限的接口。
4.2.6 工具类 (app/utils.py)
RedisWrapper: 封装 Redis 操作。TimeUtil: 统一处理时间时区问题(强制使用上海时间)。
5.前端设计
前端使用框架:https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui
5.1. 技术栈
| 技术 | 说明 | 版本 |
|---|---|---|
| Vue.js | 核心前端框架 | 2.6.12 |
| Element UI | 桌面端组件库 | 2.15.14 |
| Vuex | 状态管理模式 | 3.6.0 |
| Vue Router | 路由管理器 | 3.4.9 |
| Axios | HTTP 客户端 | 0.28.1 |
| Vue CLI | 脚手架工具 | 4.4.6 |
5.2. 目录结构
text
web/
├── bin/ # 构建脚本
├── public/ # 静态资源 (favicon, index.html)
├── src/ # 源代码目录
│ ├── api/ # 后端 API 接口定义
│ ├── assets/ # 静态资源
│ ├── components/ # 全局公共组件
│ ├── directive/ # 自定义指令
│ ├── layout/ # 布局组件 (Sidebar, Navbar, AppMain)
│ ├── plugins/ # 插件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── utils/ # 工具类
│ ├── views/ # 页面组件
│ ├── App.vue # 根组件
│ ├── main.js # 入口文件
│ ├── permission.js # 权限控制
│ └── settings.js # 全局配置
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── babel.config.js # Babel 配置
├── package.json # 项目依赖配置
└── vue.config.js # Vue CLI 配置
5.3. 核心模块
5.3.1 认证与权限
- Token 机制: 系统采用 JWT Token 进行身份验证。
- 存储 : Token 存储在 Cookie 中 (
utils/auth.js)。 - 请求拦截 :
utils/request.js中的 Axios 拦截器会自动在每个请求头中添加Authorization: Bearer <token>。 - 路由守卫 :
permission.js实现了全局路由守卫 (router.beforeEach),负责:- 检查用户是否已登录。
- 获取用户信息和权限角色。
- 根据权限动态生成路由表。
5.3.2 状态管理
Store 位于 src/store,主要模块包括:
- user.js: 管理登录状态、Token、用户信息、角色权限。
- permission.js: 管理动态路由表,根据后端返回的权限生成可访问的菜单。
- app.js: 管理侧边栏收缩状态、设备类型等 UI 状态。
- settings.js: 管理系统布局配置。
5.4.3 网络请求
- 封装 :
utils/request.js对 Axios 进行了二次封装。 - 统一错误处理: 自动处理 401 (未授权)、403 (禁止访问)、500 (服务器错误) 等状态码,并提示错误信息。
- API 管理 : 所有 API 请求按业务模块集中管理在
src/api目录下,例如src/api/exam.js包含所有考试相关的接口。
5.4.4 核心组件
- Layout : 位于
src/layout,包含侧边栏 (Sidebar)、顶部导航 (Navbar) 和主内容区 (AppMain)。 - Editor : 富文本编辑器组件 (
src/components/Editor),基于 Quill 实现。 - Pagination : 封装的 Element UI 分页组件 (
src/components/Pagination),统一了分页样式和逻辑。
5.4 管理员页面

管理员页面功能描述:
- 审核用户申请功能
- 能够发布相关考试题目
- 进行阅卷批改评论
- 查看所有用户答卷
- 待补充...
5.5 用户页面

- 我的考试
- 考试记录
6.最后
6.1 参考
1.https://www.runoob.com/flask/flask-tutorial.html
2.https://gitee.com/y_project/RuoYi-Vue/tree/master/ruoyi-ui
6.2 总结
本系统是一个基于 Flask (后端) 和 Vue.js (前端) 的在线考试管理系统。经过近期的维护和重构,系统目前处于稳定状态。核心的考试管理、题库管理、用户认证等功能均正常运行。
学习并应用Flask框架,做了这么一个web项目,后续继续学习Django然后重新做一个项目,大家就尽情期待吧。