


在数字化教学管理的当下,一款简洁、高效、美观的学生成绩管理工具,能让教师和管理员告别繁琐的表格统计,轻松完成学生信息管理、成绩分析、数据导出导入等工作。作为Python开发者,我们完全可以用PyQt5图形化界面+SQLite轻量数据库+Matplotlib可视化图表,打造一款功能齐全、界面精致、权限分明的桌面级管理系统——无需复杂配置,本地即可运行,兼顾实用性与高级感,无论是教学实训、班级管理还是个人项目开发,都是绝佳的实战案例!
很多新手会觉得GUI开发、数据库操作、数据可视化是三个独立的难点,但今天这套系统将三者完美融合:界面设计遵循美观大方的原则,不透明质感搭配规整布局,视觉效果超好看;功能上覆盖登录验证、学生/班级/教师全维度管理、Excel批量导入导出、成绩柱状图统计,还实现了管理员与教师的权限区分;代码结构清晰模块化,零基础也能看懂、能修改、能拓展。接下来我就带大家拆解核心代码,手把手带你实现这款全能成绩管理系统,看完就能直接上手用!
任何管理系统都离不开数据存储,我们选用SQLite嵌入式数据库,无需单独安装服务,生成本地.db文件即可存储所有数据,轻量化且适配桌面程序。这个模块的核心作用是自动创建数据表、初始化超级管理员,程序启动时自动执行,无需手动建库,极大降低使用成本。
我们设计了4张核心数据表:
# ===================== 1. 数据库初始化 =====================definit_db():# 连接本地数据库,不存在则自动创建 conn = sqlite3.connect("score_db.db") c = conn.cursor()# 用户表(管理员/教师):存储账号、密码、角色、绑定班级 c.execute('''CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password TEXT, role TEXT, -- admin=管理员/teacher=教师 class_id INTEGER)''')# 班级表:存储班级名称 c.execute('''CREATE TABLE IF NOT EXISTS class (id INTEGER PRIMARY KEY AUTOINCREMENT, class_name TEXT UNIQUE)''')# 学生表:核心成绩数据 c.execute('''CREATE TABLE IF NOT EXISTS student (id INTEGER PRIMARY KEY AUTOINCREMENT, stu_id TEXT UNIQUE, # 学号唯一 name TEXT, gender TEXT, class_id INTEGER, chinese INTEGER, math INTEGER, english INTEGER)''')# 初始化超级管理员:若不存在则创建默认账号 c.execute("SELECT * FROM user WHERE username='admin'")ifnot c.fetchone(): c.execute("INSERT INTO user(username,password,role) VALUES ('admin','123456','admin')")# 提交事务并关闭连接 conn.commit() conn.close()sqlite3.connect("score_db.db"):创建本地数据库文件,所有数据持久化存储CREATE TABLE IF NOT EXISTS:安全创建表,避免重复报错AUTOINCREMENT:自动生成ID,无需手动维护UNIQUE:学号、用户名、班级名不重复,保证数据规范界面是系统的"门面",我们用PyQt5实现高颜值图形化界面,采用标签页布局、分组排版、自适应表格,整体不透明、精致高级;同时开发登录验证模块,实现管理员/教师双权限控制:管理员可操作全部功能,教师仅能查看学生管理、班级管理、成绩统计,无法访问教师管理模块,权限分明更安全。
这个模块包含登录窗口和主界面窗口两大核心:登录窗口验证账号密码,主界面根据角色动态隐藏功能模块,界面自适应拉伸,表格自动适配宽度,操作逻辑极简。
# ===================== 3. 登录窗口 =====================classLoginDialog(QDialog):def__init__(self): super().__init__() self.setWindowTitle("系统登录") self.resize(300, 200) self.username = "" self.password = "" self.role = ""# 表单布局:账号+密码输入框 layout = QFormLayout() self.edit_user = QLineEdit() self.edit_pwd = QLineEdit() self.edit_pwd.setEchoMode(QLineEdit.Password) # 密码隐藏 layout.addRow("账号:", self.edit_user) layout.addRow("密码:", self.edit_pwd) btn_login = QPushButton("登录") btn_login.clicked.connect(self.check_login) main_layout = QVBoxLayout() main_layout.addLayout(layout) main_layout.addWidget(btn_login) self.setLayout(main_layout)# 登录验证:查询数据库匹配账号密码defcheck_login(self): u = self.edit_user.text().strip() p = self.edit_pwd.text().strip() conn = sqlite3.connect("score_db.db") c = conn.cursor() c.execute("SELECT role FROM user WHERE username=? AND password=?", (u, p)) res = c.fetchone() conn.close()if res: self.username = u self.role = res[0] self.accept() # 验证通过,打开主界面else: QMessageBox.warning(self, "提示", "账号或密码错误!")# ===================== 4. 主界面窗口 =====================classMainWin(QMainWindow):def__init__(self, user_role): super().__init__() self.role = user_role self.db_path = "score_db.db" self.initUI()definitUI(self): self.setWindowTitle("PyQt5学生成绩管理系统") self.resize(1200, 800) # 初始窗口大小# 标签页布局:四大功能模块 self.tabs = QTabWidget() self.tab_student = QWidget() self.tab_class = QWidget() self.tab_teacher = QWidget() self.tab_stat = QWidget() self.tabs.addTab(self.tab_student, "学生管理") self.tabs.addTab(self.tab_class, "班级管理") self.tabs.addTab(self.tab_teacher, "教师管理") self.tabs.addTab(self.tab_stat, "成绩统计")# 权限控制:教师隐藏教师管理标签页if self.role != "admin": self.tabs.removeTab(2)# 初始化各标签页功能 self.init_student_tab() self.init_class_tab() self.init_teacher_tab() self.init_stat_tab() self.setCentralWidget(self.tabs)QDialog弹窗实现,密码框隐藏输入,数据库校验账号密码QTabWidget标签页+QVBoxLayout垂直布局,整洁美观QMessageBox弹窗提示登录结果,用户体验更友好这是系统的核心业务模块,包含学生信息的增删查改、Excel批量导入导出、成绩可视化统计,也是最实用的功能集合。我们实现了:
这个模块将PyQt5界面、Pandas数据处理、Matplotlib可视化完美结合,代码复用性强,功能全覆盖,满足日常教学管理所有需求。
# ===================== 2. Matplotlib 图表画布 =====================classMplCanvas(FigureCanvasQTAgg):def__init__(self, parent=None, width=5, height=4, dpi=100): fig = Figure(figsize=(width, height), dpi=dpi) self.axes = fig.add_subplot(111) super(MplCanvas, self).__init__(fig)# -------- 学生管理:搜索、新增、导入、导出 --------definit_student_tab(self): layout = QVBoxLayout()# 搜索栏 h_search = QHBoxLayout() self.edit_search = QLineEdit() self.edit_search.setPlaceholderText("输入姓名/学号搜索") btn_search = QPushButton("搜索") btn_search.clicked.connect(self.load_student_data) h_search.addWidget(self.edit_search) h_search.addWidget(btn_search)# 操作按钮 h_btn = QHBoxLayout() btn_add = QPushButton("新增学生") btn_add.clicked.connect(self.add_student) btn_export = QPushButton("导出Excel") btn_export.clicked.connect(self.export_student) btn_import = QPushButton("导入Excel") btn_import.clicked.connect(self.import_student) h_btn.addWidget(btn_add) h_btn.addWidget(btn_export) h_btn.addWidget(btn_import)# 学生表格:自适应宽度 self.table_stu = QTableWidget() self.table_stu.setColumnCount(8) self.table_stu.setHorizontalHeaderLabels(["ID", "学号", "姓名", "性别", "班级", "语文", "数学", "英语"]) self.table_stu.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) layout.addLayout(h_search) layout.addLayout(h_btn) layout.addWidget(self.table_stu) self.tab_student.setLayout(layout) self.load_student_data()# -------- 成绩统计可视化 --------definit_stat_tab(self): layout = QVBoxLayout() self.canvas = MplCanvas(self) btn_refresh = QPushButton("刷新统计(平均分)") btn_refresh.clicked.connect(self.draw_stat) layout.addWidget(btn_refresh) layout.addWidget(self.canvas) self.tab_stat.setLayout(layout)# 绘制各科平均分柱状图defdraw_stat(self): conn = sqlite3.connect(self.db_path) df = pd.read_sql("SELECT chinese,math,english FROM student", conn) conn.close()if df.empty: QMessageBox.warning(self, "提示", "暂无学生数据")return# 计算平均分 avg = [df["chinese"].mean(), df["math"].mean(), df["english"].mean()] sub = ["语文", "数学", "英语"]# 绘图并渲染到GUI self.canvas.axes.clear() self.canvas.axes.bar(sub, avg, color=["#ff9999", "#66b3ff", "#99ff99"]) self.canvas.axes.set_title("各科平均分统计") self.canvas.draw()MplCanvas类将Matplotlib图表嵌入PyQt5界面,实现GUI内可视化pandas实现一键导入导出,批量处理数据效率拉满将以上所有模块整合,就是完整的学生成绩管理系统,直接复制运行即可使用:
import sysimport osimport sqlite3import pandas as pdfrom PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLineEdit, QLabel, QTableWidget, QTableWidgetItem, QDialog, QFormLayout, QComboBox, QMessageBox, QFileDialog, QTabWidget, QGroupBox, QHeaderView, QSpinBox)from PyQt5.QtCore import Qtfrom matplotlib.backends.backend_qt5agg import FigureCanvasQTAggfrom matplotlib.figure import Figure# ===================== 1. 数据库初始化 =====================definit_db(): conn = sqlite3.connect("score_db.db") c = conn.cursor()# 用户表(管理员/教师) c.execute('''CREATE TABLE IF NOT EXISTS user (id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE, password TEXT, role TEXT, -- admin/teacher class_id INTEGER)''')# 班级表 c.execute('''CREATE TABLE IF NOT EXISTS class (id INTEGER PRIMARY KEY AUTOINCREMENT, class_name TEXT UNIQUE)''')# 学生表 c.execute('''CREATE TABLE IF NOT EXISTS student (id INTEGER PRIMARY KEY AUTOINCREMENT, stu_id TEXT UNIQUE, name TEXT, gender TEXT, class_id INTEGER, chinese INTEGER, math INTEGER, english INTEGER)''')# 初始化超级管理员 c.execute("SELECT * FROM user WHERE username='admin'")ifnot c.fetchone(): c.execute("INSERT INTO user(username,password,role) VALUES ('admin','123456','admin')") conn.commit() conn.close()# ===================== 2. Matplotlib 图表画布 =====================classMplCanvas(FigureCanvasQTAgg):def__init__(self, parent=None, width=5, height=4, dpi=100): fig = Figure(figsize=(width, height), dpi=dpi) self.axes = fig.add_subplot(111) super(MplCanvas, self).__init__(fig)# ===================== 3. 登录窗口 =====================classLoginDialog(QDialog):def__init__(self): super().__init__() self.setWindowTitle("系统登录") self.resize(300, 200) self.username = "" self.password = "" self.role = "" layout = QFormLayout() self.edit_user = QLineEdit() self.edit_pwd = QLineEdit() self.edit_pwd.setEchoMode(QLineEdit.Password) layout.addRow("账号:", self.edit_user) layout.addRow("密码:", self.edit_pwd) btn_login = QPushButton("登录") btn_login.clicked.connect(self.check_login) main_layout = QVBoxLayout() main_layout.addLayout(layout) main_layout.addWidget(btn_login) self.setLayout(main_layout)defcheck_login(self): u = self.edit_user.text().strip() p = self.edit_pwd.text().strip() conn = sqlite3.connect("score_db.db") c = conn.cursor() c.execute("SELECT role FROM user WHERE username=? AND password=?", (u, p)) res = c.fetchone() conn.close()if res: self.username = u self.role = res[0] self.accept()else: QMessageBox.warning(self, "提示", "账号或密码错误!")# ===================== 4. 主界面窗口 =====================classMainWin(QMainWindow):def__init__(self, user_role): super().__init__() self.role = user_role self.db_path = "score_db.db" self.initUI()definitUI(self): self.setWindowTitle("PyQt5学生成绩管理系统") self.resize(1200, 800)# 标签页 self.tabs = QTabWidget() self.tab_student = QWidget() self.tab_class = QWidget() self.tab_teacher = QWidget() self.tab_stat = QWidget() self.tabs.addTab(self.tab_student, "学生管理") self.tabs.addTab(self.tab_class, "班级管理") self.tabs.addTab(self.tab_teacher, "教师管理") self.tabs.addTab(self.tab_stat, "成绩统计")# 权限隐藏:教师看不到教师管理if self.role != "admin": self.tabs.removeTab(2) self.init_student_tab() self.init_class_tab() self.init_teacher_tab() self.init_stat_tab() self.setCentralWidget(self.tabs)# -------- 学生管理 --------definit_student_tab(self): layout = QVBoxLayout()# 搜索栏 h_search = QHBoxLayout() self.edit_search = QLineEdit() self.edit_search.setPlaceholderText("输入姓名/学号搜索") btn_search = QPushButton("搜索") btn_search.clicked.connect(self.load_student_data) h_search.addWidget(self.edit_search) h_search.addWidget(btn_search)# 操作按钮 h_btn = QHBoxLayout() btn_add = QPushButton("新增学生") btn_add.clicked.connect(self.add_student) btn_export = QPushButton("导出Excel") btn_export.clicked.connect(self.export_student) btn_import = QPushButton("导入Excel") btn_import.clicked.connect(self.import_student) h_btn.addWidget(btn_add) h_btn.addWidget(btn_export) h_btn.addWidget(btn_import)# 表格 self.table_stu = QTableWidget() self.table_stu.setColumnCount(8) self.table_stu.setHorizontalHeaderLabels(["ID", "学号", "姓名", "性别", "班级", "语文", "数学", "英语"]) self.table_stu.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) layout.addLayout(h_search) layout.addLayout(h_btn) layout.addWidget(self.table_stu) self.tab_student.setLayout(layout) self.load_student_data()defload_student_data(self): key = self.edit_search.text().strip() conn = sqlite3.connect(self.db_path)if key: sql = '''SELECT s.id,s.stu_id,s.name,s.gender,c.class_name,s.chinese,s.math,s.english FROM student s LEFT JOIN class c ON s.class_id=c.id WHERE s.name LIKE ? OR s.stu_id LIKE ?''' res = pd.read_sql(sql, conn, params=(f"%{key}%", f"%{key}%"))else: sql = '''SELECT s.id,s.stu_id,s.name,s.gender,c.class_name,s.chinese,s.math,s.english FROM student s LEFT JOIN class c ON s.class_id=c.id''' res = pd.read_sql(sql, conn) conn.close() self.table_stu.setRowCount(len(res))for i, row in res.iterrows():for j, val in enumerate(row): self.table_stu.setItem(i, j, QTableWidgetItem(str(val)))defadd_student(self): dlg = QDialog() dlg.setWindowTitle("新增学生") form = QFormLayout() e_stuid = QLineEdit() e_name = QLineEdit() c_gender = QComboBox() c_gender.addItems(["男", "女"]) c_class = QComboBox()# 加载班级 conn = sqlite3.connect(self.db_path) cls = pd.read_sql("SELECT id,class_name FROM class", conn) conn.close()for _, r in cls.iterrows(): c_class.addItem(r["class_name"], r["id"]) e_chs = QSpinBox() e_math = QSpinBox() e_eng = QSpinBox()for w in [e_chs, e_math, e_eng]: w.setRange(0, 100) form.addRow("学号:", e_stuid) form.addRow("姓名:", e_name) form.addRow("性别:", c_gender) form.addRow("班级:", c_class) form.addRow("语文:", e_chs) form.addRow("数学:", e_math) form.addRow("英语:", e_eng)defsave(): stuid = e_stuid.text().strip() name = e_name.text().strip() cid = c_class.currentData()ifnot stuid ornot name: QMessageBox.warning(dlg, "提示", "学号姓名不能为空")return conn = sqlite3.connect(self.db_path)try: conn.execute('''INSERT INTO student(stu_id,name,gender,class_id,chinese,math,english) VALUES (?,?,?,?,?,?,?)''', (stuid, name, c_gender.currentText(), cid, e_chs.value(), e_math.value(), e_eng.value())) conn.commit() QMessageBox.information(dlg, "成功", "添加完成") dlg.accept() self.load_student_data()except: QMessageBox.warning(dlg, "错误", "学号重复!") conn.close() btn_ok = QPushButton("保存") btn_ok.clicked.connect(save) vbox = QVBoxLayout() vbox.addLayout(form) vbox.addWidget(btn_ok) dlg.setLayout(vbox) dlg.exec_()defexport_student(self): path, _ = QFileDialog.getSaveFileName(self, "导出Excel", "学生成绩.xlsx", "Excel(*.xlsx)")ifnot path: return conn = sqlite3.connect(self.db_path) df = pd.read_sql('''SELECT * FROM student''', conn) conn.close() df.to_excel(path, index=False) QMessageBox.information(self, "提示", "导出成功")defimport_student(self): file, _ = QFileDialog.getOpenFileName(self, "选择Excel", "", "Excel(*.xlsx)")ifnot file: returntry: df = pd.read_excel(file) conn = sqlite3.connect(self.db_path) df.to_sql("student", conn, if_exists="append", index=False) conn.commit() conn.close() self.load_student_data() QMessageBox.information(self, "提示", "导入成功")except Exception as e: QMessageBox.warning(self, "错误", f"导入失败:{str(e)}")# -------- 班级管理 --------definit_class_tab(self): layout = QVBoxLayout() h_btn = QHBoxLayout() self.table_cls = QTableWidget() self.table_cls.setColumnCount(2) self.table_cls.setHorizontalHeaderLabels(["ID", "班级名称"]) self.table_cls.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)defadd_cls(): name, ok = QInputDialog.getText(self, "新增班级", "请输入班级名称:")ifnot ok ornot name: return conn = sqlite3.connect(self.db_path)try: conn.execute("INSERT INTO class(class_name) VALUES (?)", (name,)) conn.commit() self.load_class_data() QMessageBox.information(self, "成功", "班级添加成功!")except: QMessageBox.warning(self, "错误", "班级已存在") conn.close() btn_add = QPushButton("新增班级") btn_add.clicked.connect(add_cls) h_btn.addWidget(btn_add) layout.addLayout(h_btn) layout.addWidget(self.table_cls) self.tab_class.setLayout(layout) self.load_class_data()defload_class_data(self): conn = sqlite3.connect(self.db_path) res = pd.read_sql("SELECT * FROM class", conn) conn.close() self.table_cls.setRowCount(len(res))for i, row in res.iterrows(): self.table_cls.setItem(i, 0, QTableWidgetItem(str(row["id"]))) self.table_cls.setItem(i, 1, QTableWidgetItem(row["class_name"]))# -------- 教师管理 --------definit_teacher_tab(self): layout = QVBoxLayout() self.table_tea = QTableWidget() self.table_tea.setColumnCount(4) self.table_tea.setHorizontalHeaderLabels(["账号", "密码", "角色", "绑定班级"]) self.table_tea.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) layout.addWidget(self.table_tea) self.tab_teacher.setLayout(layout) self.load_teacher_data()defload_teacher_data(self): conn = sqlite3.connect(self.db_path) res = pd.read_sql("SELECT username,password,role,class_id FROM user", conn) conn.close() self.table_tea.setRowCount(len(res))for i, row in res.iterrows(): self.table_tea.setItem(i, 0, QTableWidgetItem(row["username"])) self.table_tea.setItem(i, 1, QTableWidgetItem(row["password"])) self.table_tea.setItem(i, 2, QTableWidgetItem(row["role"])) self.table_tea.setItem(i, 3, QTableWidgetItem(str(row["class_id"])))# -------- 成绩统计图表 --------definit_stat_tab(self): layout = QVBoxLayout() self.canvas = MplCanvas(self) btn_refresh = QPushButton("刷新统计(平均分)") btn_refresh.clicked.connect(self.draw_stat) layout.addWidget(btn_refresh) layout.addWidget(self.canvas) self.tab_stat.setLayout(layout)defdraw_stat(self): conn = sqlite3.connect(self.db_path) df = pd.read_sql("SELECT chinese,math,english FROM student", conn) conn.close()if df.empty: QMessageBox.warning(self, "提示", "暂无学生数据")return avg = [df["chinese"].mean(), df["math"].mean(), df["english"].mean()] sub = ["语文", "数学", "英语"] self.canvas.axes.clear() self.canvas.axes.bar(sub, avg, color=["#ff9999", "#66b3ff", "#99ff99"]) self.canvas.axes.set_title("各科平均分统计") self.canvas.draw()# ===================== 程序入口 =====================if __name__ == "__main__": init_db() app = QApplication(sys.argv) login = LoginDialog()if login.exec_(): win = MainWin(login.role) win.show() sys.exit(app.exec_())pip install pyqt5 pandas matplotlib openpyxlscore_db.db数据库文件admin,密码:123456这款系统完美融合了Python三大核心技能,是GUI开发、数据库、数据可视化的绝佳实战项目,无论是学习练手还是实际使用,都能轻松满足需求!