你可以在这里回顾ReleaseV1.0版本:数字化作业统计平台V1.0-文档 ,但V1.0版本已不再维护!
尊敬的评委:
您好!非常荣幸能够向您展示我们的科创项目。在此,我们衷心感谢您拨冗审阅我们的文档,给予我们这个宝贵的机会来分享我们的努力与成果。
我们的团队怀着对科技创新的热忱与执着,投入了大量的时间和精力,致力于打造一个具有创新性、实用性和前瞻性的项目。这个项目凝聚着我们的智慧与汗水,也承载着我们对未来的憧憬与期望。
在接下来的文档中,您将了解到我们项目的详细内容,包括项目的背景与意义、创新点与核心技术、实施过程与成果展示,以及未来的发展规划等方面。我们相信,这个项目将为相关领域带来新的思路和解决方案,为推动科技创新和社会进步贡献一份力量。
1.灵感
在日常的学习生活中,家庭作业是学生巩固知识、提升能力的重要环节。然而,收作业却常常成为一个令人头疼的问题。每天课代表在收作业时,总有部分同学不交,导致作业收不齐。老师为了检查谁没交作业,不得不花费大量时间与精力,这严重影响了上课进度和办公效率。
为了解决这一问题,我们团队经过深入思考和反复讨论,提出了一个创新的解决方案。我们设想让同学们在书的侧面贴上条形码,并将信息录入系统。当作业收起后,课代表只需把作业本摞成一摞,老师对着侧面的一堆条码进行拍照,上传到系统中。系统便会迅速分析出谁没有交作业,同时记录这些数据,并且在每周还能进行数据汇总。
通过这个智能作业管理系统,我们可以极大地简化检查和统计作业的复杂环节,减少所需时间,从而显著提高效率。这不仅能让老师更专注于教学工作,也能培养学生的自律意识和责任感。
2.使用方法
我们已经将项目部署到我们自己的服务器,在全球内使用互联网均访问,您可通过网址 http://kc.gvicyunjin.cn:44441/ 进行访问。我的域名(gvicyunjin.cn)已通过 ICP 备案(备案号:陕ICP备2024041476号-1),符合国内的法律法规要求,确保了网站的合法性与正规性,不存在钓鱼网站等安全隐患,请您放心使用。
需要说明的是,由于本项目当前处于测试环境,尚未为网页申请 SSL 证书,因此在访问时可能会出现不安全的提示。尽管这在测试阶段是正常情况,但在实际正式使用场景中,出于对用户隐私安全的考虑,建议按照通用数据保护条例(GDPR)等相关规定,及时申请 SSL 证书以实现 HTTPS 访问,从而更好地保护用户数据隐私。
本项目采用 Python 的 Flask 框架进行开发,框架中运用了加密机制对敏感的闪现消息等数据进行加密处理,有效保障了数据在传输和存储过程中的安全性,进一步增强了系统的安全性和可靠性。
使用详情:
班级注册(若首次使用):进入页面后,若未注册班级,会显示班级注册界面。在文本框中每行输入一个学生姓名,输入完成后点击 “注册班级并生成条码” 按钮。系统会自动为每个学生分配学号,并生成对应的条形码。
打印并粘贴条码:注册成功后,会显示班级成员及对应的条码。此时,班主任需截图并打印这些条码,将其粘贴到同学们的作业册子侧面。
作业检查:课代表收齐作业后,将作业摞成一摞交给班主任。班主任对着作业侧面的条码拍照,然后在平台页面中点击 “上传作业条码图片”,选择刚才拍摄的图片,点击 “检查作业” 按钮,系统会快速检测出未提交作业的学生名单,并显示结果。
查看历史记录:在平台首页,您可以查看每个学生的未交作业历史记录,了解学生的作业提交情况。
注销班级与清空记录:若班级有变动,可点击 “注销班级” 按钮删除班级数据;若想清空历史记录,点击 “清空历史记录” 按钮即可。
3.实现过程
3.1文件结构
homework_ckeck/
├── app.py # Flask 主程序,含路由和逻辑
├── requirements.txt # 项目依赖库清单
├── Dockerfile # 构建 Docker 镜像
├── docker-compose.yml # 配置 Docker 多容器应用
├── templates/
│ └── index.html # 主页面 HTML 模板
├── static/
│ └── barcodes/ # 存放生成的条码图片
├── uploads/ # 存储用户上传的条码图片
├── class_data.json # 班级学生信息
└── history.json # 学生未交作业历史记录
3.2.app.py
源码:
from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory, jsonify
from pyzbar.pyzbar import decode
from PIL import Image
import os
import json
import barcode
from barcode.writer import ImageWriter
app = Flask(__name__)
app.secret_key = '123456'
# 数据文件路径
data_file = "class_data.json"
history_file = "history.json"
# 加载班级数据
def load_class_data():
if os.path.exists(data_file):
try:
with open(data_file, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError:
return {}
return {}
# 保存班级数据
def save_class_data(class_data):
with open(data_file, 'w', encoding='utf-8') as f:
json.dump(class_data, f, ensure_ascii=False, indent=4)
# 加载历史记录
def load_history():
if os.path.exists(history_file):
try:
with open(history_file, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError:
return {}
return {}
# 保存历史记录
def save_history(history):
with open(history_file, 'w', encoding='utf-8') as f:
json.dump(history, f, ensure_ascii=False, indent=4)
# 条码生成
def generate_barcode(student_id, student_name):
barcode_dir = "static/barcodes"
if not os.path.exists(barcode_dir):
os.makedirs(barcode_dir)
ean = barcode.get('code128', str(student_id), writer=ImageWriter())
filename = f"{barcode_dir}/{student_name}_{student_id}"
ean.save(filename)
filename += ".png"
return filename
# Flask 路由
@app.route('/')
def index():
class_data = load_class_data()
barcodes = {}
if class_data:
for student_id, student_name in class_data.items():
barcode_path = f"barcodes/{student_name}_{student_id}.png"
barcodes[student_id] = barcode_path
history = load_history()
return render_template('index.html', class_data=class_data, barcodes=barcodes, history=history)
@app.route('/register', methods=['POST'])
def register():
student_names = request.form['student_names'].strip()
if student_names:
students = student_names.splitlines()
class_data = {str(i+1): name for i, name in enumerate(students)}
save_class_data(class_data)
# 自动为所有学生生成条码
for student_id, student_name in class_data.items():
generate_barcode(student_id, student_name)
flash("班级成员注册成功并生成条码!", "success")
else:
flash("请输入班级成员姓名!", "error")
return redirect(url_for('index'))
@app.route('/unregister', methods=['POST'])
def unregister():
if os.path.exists(data_file):
os.remove(data_file)
if os.path.exists(history_file):
os.remove(history_file)
flash("班级数据已删除。", "success")
return redirect(url_for('index'))
@app.route('/check_homework', methods=['POST'])
def check_homework():
file = request.files['barcode_image']
if file:
file_path = os.path.join('uploads', file.filename)
file.save(file_path)
# 解码条码图片
img = Image.open(file_path)
barcodes = decode(img)
if not barcodes:
flash("未检测到任何条码!", "error")
else:
class_data = load_class_data()
submitted_ids = [int(barcode_obj.data.decode('utf-8')) for barcode_obj in barcodes if barcode_obj.type == 'CODE128']
missing_students = [name for student_id, name in class_data.items() if int(student_id) not in submitted_ids]
if missing_students:
missing_list = "\n".join(missing_students)
flash(f"未交作业的学生:\n{missing_list}", "info")
# 更新历史记录
history = load_history()
for student_name in missing_students:
if student_name in history:
history[student_name] += 1
else:
history[student_name] = 1
save_history(history)
else:
flash("所有学生都已提交作业。", "success")
else:
flash("请上传条码图片!", "error")
return redirect(url_for('index'))
@app.route('/clear_history', methods=['POST'])
def clear_history():
if os.path.exists(history_file):
os.remove(history_file)
flash("历史记录已清空。", "success")
return redirect(url_for('index'))
if __name__ == '__main__':
if not os.path.exists('static/barcodes'):
os.makedirs('static/barcodes')
if not os.path.exists('uploads'):
os.makedirs('uploads')
app.run(host='0.0.0.0', port=44441)
解释:
导入必要的库
from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory, jsonify
from pyzbar.pyzbar import decode
from PIL import Image
import os
import json
import barcode
from barcode.writer import ImageWriter
这里导入了多个不同功能的库:
Flask
及其相关模块用于构建 Web 应用,像创建应用实例、处理请求、渲染模板、重定向和消息闪现等操作。pyzbar
库中的decode
函数用于解码条码图片里的条码信息。PIL
库的Image
模块可用于打开和处理图片。os
库用于进行文件和目录操作。json
库用于读写 JSON 文件。barcode
库和ImageWriter
用于生成条码图片。
创建 Flask 应用实例并设置密钥
app = Flask(__name__)
app.secret_key = '123456'
app = Flask(__name__)
:创建一个 Flask 应用实例,__name__
参数能帮助 Flask 确定应用的根目录,进而定位静态文件和模板文件。app.secret_key = '123456'
:设置应用的密钥,此密钥用于会话管理和消息闪现,确保数据在传输和存储过程中的安全性。
定义数据文件路径
data_file = "class_data.json"
history_file = "history.json"
data_file
:指定用于存储班级学生信息的 JSON 文件的路径。history_file
:指定用于存储学生未交作业历史记录的 JSON 文件的路径。
数据加载和保存函数
# 加载班级数据
def load_class_data():
if os.path.exists(data_file):
try:
with open(data_file, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError:
return {}
return {}
# 保存班级数据
def save_class_data(class_data):
with open(data_file, 'w', encoding='utf-8') as f:
json.dump(class_data, f, ensure_ascii=False, indent=4)
# 加载历史记录
def load_history():
if os.path.exists(history_file):
try:
with open(history_file, 'r', encoding='utf-8') as f:
return json.load(f)
except json.JSONDecodeError:
return {}
return {}
# 保存历史记录
def save_history(history):
with open(history_file, 'w', encoding='utf-8') as f:
json.dump(history, f, ensure_ascii=False, indent=4)
load_class_data()
:检查class_data.json
文件是否存在,若存在则读取其内容并解析为 Python 对象;若文件不存在或解析出错,则返回空字典。save_class_data(class_data)
:将班级学生信息以 JSON 格式保存到class_data.json
文件中,ensure_ascii=False
可确保非 ASCII 字符能正确保存,indent=4
使文件内容格式更清晰。load_history()
:检查history.json
文件是否存在,若存在则读取其内容并解析为 Python 对象;若文件不存在或解析出错,则返回空字典。save_history(history)
:将学生未交作业历史记录以 JSON 格式保存到history.json
文件中,同样采用ensure_ascii=False
和indent=4
。
条码生成函数
def generate_barcode(student_id, student_name):
barcode_dir = "static/barcodes"
if not os.path.exists(barcode_dir):
os.makedirs(barcode_dir)
ean = barcode.get('code128', str(student_id), writer=ImageWriter())
filename = f"{barcode_dir}/{student_name}_{student_id}"
ean.save(filename)
filename += ".png"
return filename
该函数接收学生编号和姓名作为参数。
首先检查
static/barcodes
目录是否存在,若不存在则创建该目录。利用
barcode
库生成code128
类型的条码,条码内容为学生编号。将生成的条码保存为 PNG 图片,文件名包含学生姓名和编号。
最后返回生成的条码图片的完整文件名。
Flask 路由
首页路由
@app.route('/')
def index():
class_data = load_class_data()
barcodes = {}
if class_data:
for student_id, student_name in class_data.items():
barcode_path = f"barcodes/{student_name}_{student_id}.png"
barcodes[student_id] = barcode_path
history = load_history()
return render_template('index.html', class_data=class_data, barcodes=barcodes, history=history)
该路由处理根路径
/
的请求。加载班级学生信息和学生未交作业历史记录。
若班级学生信息存在,为每个学生生成对应的条码图片路径。
渲染
index.html
模板,并将班级学生信息、条码图片路径和历史记录传递给模板。
班级注册路由
@app.route('/register', methods=['POST'])
def register():
student_names = request.form['student_names'].strip()
if student_names:
students = student_names.splitlines()
class_data = {str(i+1): name for i, name in enumerate(students)}
save_class_data(class_data)
# 自动为所有学生生成条码
for student_id, student_name in class_data.items():
generate_barcode(student_id, student_name)
flash("班级成员注册成功并生成条码!", "success")
else:
flash("请输入班级成员姓名!", "error")
return redirect(url_for('index'))
该路由处理
POST
请求,用于班级注册。从表单中获取学生姓名列表,去除首尾空格后检查是否为空。
若不为空,将学生姓名转换为字典形式的班级学生信息并保存到
class_data.json
文件中。为每个学生生成条码图片。
使用
flash
函数显示注册成功或失败的消息。最后重定向到首页。
班级注销路由
@app.route('/unregister', methods=['POST'])
def unregister():
if os.path.exists(data_file):
os.remove(data_file)
if os.path.exists(history_file):
os.remove(history_file)
flash("班级数据已删除。", "success")
return redirect(url_for('index'))
该路由处理
POST
请求,用于班级注销。检查
class_data.json
和history.json
文件是否存在,若存在则删除。使用
flash
函数显示注销成功的消息。重定向到首页。
作业检查路由
@app.route('/check_homework', methods=['POST'])
def check_homework():
file = request.files['barcode_image']
if file:
file_path = os.path.join('uploads', file.filename)
file.save(file_path)
# 解码条码图片
img = Image.open(file_path)
barcodes = decode(img)
if not barcodes:
flash("未检测到任何条码!", "error")
else:
class_data = load_class_data()
submitted_ids = [int(barcode_obj.data.decode('utf-8')) for barcode_obj in barcodes if barcode_obj.type == 'CODE128']
missing_students = [name for student_id, name in class_data.items() if int(student_id) not in submitted_ids]
if missing_students:
missing_list = "\n".join(missing_students)
flash(f"未交作业的学生:\n{missing_list}", "info")
# 更新历史记录
history = load_history()
for student_name in missing_students:
if student_name in history:
history[student_name] += 1
else:
history[student_name] = 1
save_history(history)
else:
flash("所有学生都已提交作业。", "success")
else:
flash("请上传条码图片!", "error")
return redirect(url_for('index'))
该路由处理
POST
请求,用于检查作业提交情况。从请求中获取上传的条码图片,若图片存在则保存到
uploads
目录。打开图片并解码其中的条码信息。
若未检测到条码,显示错误消息;若检测到条码,对比班级学生信息,找出未交作业的学生。
若有未交作业的学生,更新历史记录并显示提示信息;若所有学生都已提交作业,显示成功消息。
若未上传图片,显示错误消息。
最后重定向到首页。
清空历史记录路由
@app.route('/clear_history', methods=['POST'])
def clear_history():
if os.path.exists(history_file):
os.remove(history_file)
flash("历史记录已清空。", "success")
return redirect(url_for('index'))
该路由处理
POST
请求,用于清空学生未交作业历史记录。检查
history.json
文件是否存在,若存在则删除。使用
flash
函数显示清空成功的消息。重定向到首页。
启动应用
if __name__ == '__main__':
if not os.path.exists('static/barcodes'):
os.makedirs('static/barcodes')
if not os.path.exists('uploads'):
os.makedirs('uploads')
app.run(host='0.0.0.0', port=44441)
检查
static/barcodes
和uploads
目录是否存在,若不存在则创建。当脚本作为主程序运行时,启动 Flask 应用,监听
0.0.0.0
地址的44441
端口,使得应用可以被外部访问。
3.3.requirements.txt
用于列出python所有依赖库或模块,便于构建docker容器时安装
Flask
Pillow
pyzbar
python-barcode
3.4.dockerfile
# 使用基础镜像
FROM python:3.9-slim
# 安装 zbar 库的依赖
RUN apt-get update && apt-get install -y \
zbar-tools \
libzbar0 \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /app
# 复制当前目录的内容到工作目录
COPY . /app
# 使用阿里云 pypi 镜像源安装依赖
RUN pip install -i https://mirrors.aliyun.com/pypi/simple/ --no-cache-dir Flask Pillow pyzbar python-barcode
# 暴露端口 44441
EXPOSE 44441
# 运行 Flask 应用
CMD ["python", "app.py"]
一、基础镜像选择
FROM python:3.9-slim
使用 Python 3.9 的 slim 版本作为基础镜像,这个镜像通常比较小,包含了基本的 Python 运行环境,可以减少最终构建的 Docker 镜像的大小。
二、安装 zbar 库依赖
RUN apt-get update && apt-get install -y \
zbar-tools \
libzbar0 \
&& rm -rf /var/lib/apt/lists/*
这一步更新了软件包列表并安装了 zbar-tools
和 libzbar0
,这些是用于在容器中处理条形码的依赖库。安装完成后,清理了软件包列表缓存以减小镜像大小。
三、设置工作目录
WORKDIR /app
将容器内的工作目录设置为 /app
,后续的操作都将在这个目录下进行。
四、复制项目文件
COPY. /app
将当前目录(构建上下文)下的所有文件复制到容器的 /app
目录中,确保项目的代码和相关文件在容器内可用。
五、安装项目依赖
RUN pip install -i https://mirrors.aliyun.com/pypi/simple/ --no-cache-dir Flask Pillow pyzbar python-barcode
使用阿里云的 PyPI 镜像源安装项目所需的依赖库,包括 Flask、Pillow、pyzbar 和 python-barcode。--no-cache-dir
参数确保不使用缓存,每次构建都重新安装依赖,以保证依赖的一致性。
六、暴露端口
EXPOSE 44441
声明容器将在运行时监听 44441 端口,以便外部可以访问容器内运行的应用程序。
七、运行应用
CMD ["python", "app.py"]
在容器启动时,执行 python app.py
命令来运行 Flask 应用程序。
3.5./templates/index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字化作业统计平台</title>
<style>
/* 全局样式,设置渐变色背景 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(to bottom, #e0f7fa, #b2ebf2);
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
/* 标题样式 */
h1 {
text-align: center;
color: #333;
margin: 20px 0;
}
/* 小标题样式 */
h2 {
color: #666;
margin: 15px 0;
text-align: center;
}
/* 通用容器样式 */
.container {
background: linear-gradient(to bottom, #ffdab9, #ffb6c1);
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 80%;
max-width: 600px;
margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
/* 文本区域样式 */
textarea {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-bottom: 10px;
}
/* 按钮样式,设置圆角 */
button {
padding: 10px 20px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
transition: background-color 0.3s ease;
margin: 5px;
}
/* 按钮悬停效果 */
button:hover {
background-color: #0056b3;
}
/* 条码展示大容器样式 */
.barcode-container {
background: linear-gradient(to bottom, #d8bfd8, #dda0dd);
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 80%;
max-width: 1200px;
margin-bottom: 20px;
}
/* 网格容器样式 */
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
}
/* 网格项样式,去除背景 */
.grid-item {
padding: 10px;
border-radius: 10px;
text-align: center;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
background: transparent;
}
/* 图片样式 */
img {
max-width: 100%;
height: auto;
border-radius: 4px;
}
/* 表格样式 */
table {
width: 100%;
border-collapse: collapse;
background: linear-gradient(to bottom, #98fb98, #90ee90);
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* 表格表头样式 */
th,
td {
padding: 10px;
text-align: center;
border-bottom: 1px solid #ccc;
}
/* 表格表头样式 */
th {
background-color: rgba(255, 255, 255, 0.3);
}
/* 弹窗样式 */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
/* 弹窗内容样式 */
.modal-content {
background: linear-gradient(to bottom, #add8e6, #87ceeb);
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
/* 弹窗关闭按钮样式 */
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
/* 弹窗关闭按钮悬停效果 */
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
</style>
</head>
<body>
<!-- 弹窗元素 -->
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<p id="modal-message"></p>
</div>
</div>
<h1>数字化作业统计平台</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<script>
// 显示弹窗
var modal = document.getElementById('myModal');
var span = document.getElementsByClassName("close")[0];
var message = "";
{% for category, message in messages %}
message += "<strong>{{ category }}:</strong> {{ message }}<br>";
{% endfor %}
document.getElementById('modal-message').innerHTML = message;
modal.style.display = "block";
// 点击关闭按钮关闭弹窗
span.onclick = function () {
modal.style.display = "none";
}
// 点击窗口外关闭弹窗
window.onclick = function (event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
</script>
{% endif %}
{% endwith %}
{% if not class_data %}
<div class="container">
<h2>注册班级</h2>
<form action="/register" method="post">
<textarea name="student_names" rows="10" placeholder="每行输入一个学生姓名"></textarea><br><br>
<button type="submit">注册班级并生成条码</button>
</form>
</div>
{% endif %}
{% if class_data %}
<div class="barcode-container">
<h2>班级成员及条码</h2>
<div class="grid-container">
{% for student_id, student_name in class_data.items() %}
<div class="grid-item">
<p>{{ student_id }}. {{ student_name }}</p>
<img src="/static/{{ barcodes[student_id] }}" alt="条码">
</div>
{% endfor %}
</div>
</div>
<div class="container">
<h2>注销班级</h2>
<form action="/unregister" method="post">
<button type="submit">注销班级</button>
</form>
</div>
<div class="container">
<h2>上传作业条码图片</h2>
<form action="/check_homework" method="post" enctype="multipart/form-data">
<input type="file" name="barcode_image" accept="image/*"><br><br>
<button type="submit">检查作业</button>
</form>
</div>
<div class="container">
<h2>历史记录</h2>
<table>
<thead>
<tr>
<th>学生姓名</th>
<th>未交作业次数</th>
</tr>
</thead>
<tbody>
{% for student_name, count in history.items() %}
<tr>
<td>{{ student_name }}</td>
<td>{{ count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="container">
<h2>清空历史记录</h2>
<form action="/clear_history" method="post">
<button type="submit">清空历史记录</button>
</form>
</div>
{% endif %}
</body>
</html>
1. HTML 头部部分
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字化作业统计平台</title>
<style>
/* 全局样式,设置渐变色背景 */
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background: linear-gradient(to bottom, #e0f7fa, #b2ebf2);
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
/* 标题样式 */
h1 {
text-align: center;
color: #333;
margin: 20px 0;
}
/* 小标题样式 */
h2 {
color: #666;
margin: 15px 0;
text-align: center;
}
/* 通用容器样式 */
.container {
background: linear-gradient(to bottom, #ffdab9, #ffb6c1);
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 80%;
max-width: 600px;
margin-bottom: 20px;
display: flex;
flex-direction: column;
align-items: center;
}
/* 文本区域样式 */
textarea {
width: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
margin-bottom: 10px;
}
/* 按钮样式,设置圆角 */
button {
padding: 10px 20px;
background-color: #007BFF;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
transition: background-color 0.3s ease;
margin: 5px;
}
/* 按钮悬停效果 */
button:hover {
background-color: #0056b3;
}
/* 条码展示大容器样式 */
.barcode-container {
background: linear-gradient(to bottom, #d8bfd8, #dda0dd);
padding: 20px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 80%;
max-width: 1200px;
margin-bottom: 20px;
}
/* 网格容器样式 */
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
}
/* 网格项样式,去除背景 */
.grid-item {
padding: 10px;
border-radius: 10px;
text-align: center;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
background: transparent;
}
/* 图片样式 */
img {
max-width: 100%;
height: auto;
border-radius: 4px;
}
/* 表格样式 */
table {
width: 100%;
border-collapse: collapse;
background: linear-gradient(to bottom, #98fb98, #90ee90);
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
/* 表格表头样式 */
th,
td {
padding: 10px;
text-align: center;
border-bottom: 1px solid #ccc;
}
/* 表格表头样式 */
th {
background-color: rgba(255, 255, 255, 0.3);
}
/* 弹窗样式 */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
}
/* 弹窗内容样式 */
.modal-content {
background: linear-gradient(to bottom, #add8e6, #87ceeb);
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
max-width: 400px;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
/* 弹窗关闭按钮样式 */
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
/* 弹窗关闭按钮悬停效果 */
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
</style>
</head>
<!DOCTYPE html>
:声明文档类型为 HTML5。<html lang="zh-CN">
:指定页面语言为中文(中国大陆)。<meta>
标签:charset="UTF-8"
:设置字符编码为 UTF - 8,确保页面能正确显示各种字符。name="viewport"
:让页面在不同设备上有良好的显示效果,width=device-width
使页面宽度适应设备屏幕宽度,initial-scale=1.0
设置初始缩放比例为 1。
<title>
标签:设置页面标题为 “数字化作业统计平台”。<style>
标签:定义页面的 CSS 样式,包括:全局样式:为
body
设置渐变色背景,使用 Flexbox 布局让页面内容垂直居中,最小高度为 100vh 以占满整个屏幕高度。标题和小标题样式:设置
h1
和h2
的文本颜色、对齐方式和外边距。容器样式:为
.container
和.barcode-container
设置渐变色背景、内边距、圆角、阴影和宽度等属性。表单元素样式:设置
textarea
的宽度、内边距、边框和圆角等;为button
设置背景颜色、文本颜色、圆角和悬停效果。网格布局样式:使用 CSS Grid 布局为
.grid-container
和.grid-item
设置样式,使班级成员信息和条码图片能以网格形式展示。表格样式:为
table
、th
和td
设置背景颜色、边框和内边距等,让历史记录表格更美观。弹窗样式:定义
.modal
和.modal-content
的样式,实现弹窗的显示和关闭效果。
2. HTML 主体部分
弹窗元素
<body>
<!-- 弹窗元素 -->
<div id="myModal" class="modal">
<div class="modal-content">
<span class="close">×</span>
<p id="modal-message"></p>
</div>
</div>
<h1>数字化作业统计平台</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<script>
// 显示弹窗
var modal = document.getElementById('myModal');
var span = document.getElementsByClassName("close")[0];
var message = "";
{% for category, message in messages %}
message += "<strong>{{ category }}:</strong> {{ message }}<br>";
{% endfor %}
document.getElementById('modal-message').innerHTML = message;
modal.style.display = "block";
// 点击关闭按钮关闭弹窗
span.onclick = function () {
modal.style.display = "none";
}
// 点击窗口外关闭弹窗
window.onclick = function (event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
</script>
{% endif %}
{% endwith %}
弹窗结构:使用
<div>
元素创建一个弹窗,id="myModal"
用于在 JavaScript 中获取该元素,class="modal"
应用弹窗样式。Jinja2 模板代码:
{% with messages = get_flashed_messages(with_categories=true) %}
:获取 Flask 应用中闪现的消息及其类别。{% if messages %}
:判断是否有闪现消息。{% for category, message in messages %}
:遍历所有闪现消息。
JavaScript 代码:
获取弹窗元素和关闭按钮元素。
将闪现消息拼接成 HTML 字符串,并插入到弹窗的消息显示区域。
设置弹窗的
display
属性为block
以显示弹窗。为关闭按钮和窗口点击事件添加监听器,点击关闭按钮或窗口外区域时隐藏弹窗。
班级注册部分
{% if not class_data %}
<div class="container">
<h2>注册班级</h2>
<form action="/register" method="post">
<textarea name="student_names" rows="10" placeholder="每行输入一个学生姓名"></textarea><br><br>
<button type="submit">注册班级并生成条码</button>
</form>
</div>
{% endif %}
条件判断:
{% if not class_data %}
检查是否存在班级数据,如果不存在则显示班级注册表单。表单结构:
<form>
标签:设置表单的提交地址为/register
,提交方法为post
。<textarea>
标签:用于输入学生姓名,每行一个姓名。<button>
标签:点击后提交表单进行班级注册并生成条码。
班级数据存在时的部分
{% if class_data %}
<div class="barcode-container">
<h2>班级成员及条码</h2>
<div class="grid-container">
{% for student_id, student_name in class_data.items() %}
<div class="grid-item">
<p>{{ student_id }}. {{ student_name }}</p>
<img src="/static/{{ barcodes[student_id] }}" alt="条码">
</div>
{% endfor %}
</div>
</div>
<div class="barcode-container">
<h2>班级成员及条码</h2>
<div class="grid-container">
{% for student_id, student_name in class_data.items() %}
<div class="grid-item">
<p>{{ student_id }}. {{ student_name }}</p>
<img src="/static/{{ barcodes[student_id] }}" alt="条码">
</div>
{% endfor %}
</div>
</div>
<div class="container">
<h2>注销班级</h2>
<form action="/unregister" method="post">
<button type="submit">注销班级</button>
</form>
</div>
<div class="container">
<h2>上传作业条码图片</h2>
<form action="/check_homework" method="post" enctype="multipart/form-data">
<input type="file" name="barcode_image" accept="image/*"><br><br>
<button type="submit">检查作业</button>
</form>
</div>
<div class="container">
<h2>历史记录</h2>
<table>
<thead>
<tr>
<th>学生姓名</th>
<th>未交作业次数</th>
</tr>
</thead>
<tbody>
{% for student_name, count in history.items() %}
<tr>
<td>{{ student_name }}</td>
<td>{{ count }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="container">
<h2>清空历史记录</h2>
<form action="/clear_history" method="post">
<button type="submit">清空历史记录</button>
</form>
</div>
{% endif %}
班级成员及条码展示:
容器结构:使用
.barcode-container
作为外层容器,设置了渐变色背景、圆角和阴影等样式。网格布局:
.grid-container
采用 CSS Grid 布局,根据屏幕宽度自动排列.grid-item
。循环展示:通过
{% for student_id, student_name in class_data.items() %}
遍历班级数据,为每个学生生成一个.grid-item
,显示学生编号、姓名和对应的条码图片。
注销班级功能:
表单结构:包含在
.container
容器内,表单的action
属性设置为/unregister
,提交方法为post
。点击 “注销班级” 按钮后,会向该地址发送请求,执行注销班级的操作。
上传作业条码图片功能:
表单结构:同样使用
.container
容器,表单的action
属性为/check_homework
,enctype="multipart/form-data"
表示表单用于上传文件。文件输入:
<input type="file" name="barcode_image" accept="image/*">
允许用户选择一张图片文件,点击 “检查作业” 按钮后,会将图片上传到服务器进行作业检查。
历史记录展示:
表格结构:使用
<table>
元素展示历史记录,<thead>
定义表头,包含 “学生姓名” 和 “未交作业次数” 两列。数据填充:通过
{% for student_name, count in history.items() %}
遍历历史记录数据,为每个学生生成一行表格数据。
清空历史记录功能:
表单结构:在
.container
容器内,表单的action
属性为/clear_history
,提交方法为post
。点击 “清空历史记录” 按钮后,会向该地址发送请求,清空历史记录。
4.私有化部署
基于 Ubuntu 系统的部署(以 Ubuntu 22.04 为例)
安装 Docker:
打开终端,更新软件包索引,执行命令:
sudo apt update
。安装 Docker 的依赖包,执行命令:
sudo apt install apt-transport-https ca-certificates curl software-properties-common
。添加 Docker 的官方 GPG 密钥,执行命令:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
。添加 Docker 的软件源,执行命令:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
。再次更新软件包索引,执行命令:
sudo apt update
。安装 Docker 引擎,执行命令:
sudo apt install docker.io
。将当前用户添加到 docker 用户组(这样可以无需使用 sudo 运行 Docker 命令),执行命令:
sudo usermod -aG docker $USER
,然后重新登录系统使设置生效。
连接服务器与准备文件:使用 finalshell 或其他 ssh 工具连接到服务器。将项目文件夹【homework-ckack】上传到服务器的任意位置,并授予 777 权限(若为 root 用户可忽略此步骤)。
获取权限与构建容器:获取根用户权限,执行命令:
su root
。进入上传文件的根目录,使用ls
命令确认能看到 Dockerfile 文件。在终端输入以下指令构建 docker 容器:docker build -t homework-checker .
,根据网络情况,构建过程大约需要 3 - 10 分钟。启动容器与访问平台:构建完成后,在终端输入以下指令启动 docker 容器:
docker run -d -p 44441:44441 --name homework-checker homework-checker
。在服务器的安全组或防火墙中放行 44441 端口,以保证请求不会被拦截。通过http://{服务器 IP}:44441
访问应用程序。
基于 Windows 系统的部署
安装 Docker:访问 Docker 官方网站(https://www.docker.com/ ),根据系统版本下载并安装适合的 Docker Desktop 应用程序。下载完成后,双击安装包,按照安装向导的提示完成安装。安装完成后,打开 Docker Desktop 确保 Docker 服务已启动。
准备项目文件:将项目文件夹【homework-ckack】复制到电脑的合适位置。
进行项目部署:打开命令提示符(CMD)或 PowerShell,导航到项目文件夹所在目录。依次执行构建 Docker 容器(
docker build -t homework-checker.
)、启动 Docker 容器(docker run -d -p 44441:44441 --name homework-checker homework-checker
)。确保 Windows 防火墙已放行 44441 端口(可在防火墙设置中添加入站规则)。通过http://localhost:44441
或http://{电脑本地 IP}:44441
访问应用程序。
基于 Mac 系统的部署
安装 Docker:
打开浏览器,访问 Docker 官方网站(https://www.docker.com/ ),下载适用于 Mac 的 Docker Desktop 安装包。
下载完成后,双击安装包并按照安装向导提示进行安装,过程中可能需输入 Mac 用户密码授权。
安装完成后启动 Docker Desktop,等待图标显示为运行状态,表明 Docker 环境准备就绪。
准备项目文件:将项目文件夹【homework-ckack】复制到 Mac 电脑合适位置,如 “下载” 或 “文档” 文件夹。
进行项目部署:
打开 “终端” 应用程序(可通过 “聚焦搜索” 找到)。
在终端中,使用
cd
命令导航到项目文件夹【homework-ckack】所在目录。例如,若项目文件夹在 “下载” 文件夹中,输入cd ~/Downloads/homework-ckack
并回车。确认在项目文件夹目录后,输入指令构建 Docker 容器:
docker build -t homework-checker.
,等待 Docker 构建容器,约 3 - 10 分钟。构建完成后,输入指令启动 Docker 容器:
docker run -d -p 44441:44441 --name homework-checker homework-checker
,以后台运行方式启动容器,并将容器 44441 端口映射到 Mac 电脑 44441 端口。若 Mac 系统开启防火墙,需在防火墙设置中允许 Docker 应用访问网络,并确保 44441 端口已放行,可在 “系统偏好设置” -> “安全性与隐私” -> “防火墙” 中设置。
完成上述步骤后,打开浏览器,在地址栏输入
http://localhost:44441
或http://{Mac 电脑本地 IP 地址}:44441
(可在 “系统偏好设置” -> “网络” 中查看本地 IP 地址),访问数字化作业统计平台。
5.未来计划
目前,我们的程序已基本实现通过在同学们的书侧面贴条码,老师拍照条码即可快速确定未交作业的学生,极大地减轻了收作业的工作负担。
对于未来的计划:
1.我们将引入设置多班级功能,可注册多个班级,让更多的师生共同使用我们这套系统。同时,我们还会为每科老师分别设置独立的账号密码,例如,语文老师有专属的语文账号密码,数学老师有自己的数学账号密码。不同老师上传作业情况后,班主任将拥有一个管理平台,在该平台上可以清晰地看到哪个同学具体哪一科未交作业。
2.我们将采用 token 技术与服务器进行验证。这样,老师在一次登录后,在特定时间段内可凭借 token 验证无需再次登录直接打开系统。
3.此外,后续我们还会引入每周、每月、每学期的统计量,并运用图表、折线等数据统计方式,直观地展示学生的作业完成情况,为教学管理提供更有力的支持。
4.为了保证这个数据安全,我们后续还会进行数据容灾方面的优化。在数据冗余方面,我们会对服务器设置 RAID 阵列,将数据存在阵列中,以保证即使硬盘损坏,数据也不会丢失。同时,我们还会进行数据灾备,设置两个异地服务器,让数据在异地服务器之间进行同步。这样,即使有一台服务器由于一些不可控因素如断电、火灾、海啸等问题损坏,我们的数据还能在另外一台服务器上恢复,服务也可以在另外一台服务器上继续进行,从而最大程度地保证用户的数据安全。
6.感谢
在数字化作业统计平台的开发之旅中,我们曾于服务器调试与网页构建的重重困难里徘徊挣扎,无数次想要放弃却又一次次选择坚持。指导老师倾囊相授、悉心指引,网络资源如 CSDN、菜鸟编程等平台以及 zbar 库、flask 库、PILLOW 库的原作者提供的帮助,如同点点星光照亮我们前行的道路。 因为坚持,我们收获成长,未来也将秉持这种探索、积极且坚持不懈的精神,于生活中砥砺前行。我们会努力汲取科学文化知识,钻研辩证思维,立志在科技与网络领域发光发热,为国家贡献自己的力量,不负梦想,成就未来!
再次感谢评委们耐心观看我们的成果展示!
开发团队:
开发者:郭子路
指导老师:薛仙
学校:西安市第六十六中学