上一篇 学生成绩管理系统 是tkinter+sqlite3数据库版,为了满足更多人,PyQt5版的他来了,为了对新手友好一点,这篇文章我将会上完整代码,并且分开阐述。
老规矩:简介的口水话就不过多介绍了,代码有点多,如果想要源码的直接扫我,或QQ 1690361973
下面是程序的主要代码:
一.导入库,没有自行下载 pip install 库名
# 复制粘贴库
import shutil
# 界面库
import tkinter as tk
from tkinter import ttk, messagebox, filedialog
# 数据库
import sqlite3
# 处理文件
import os
# 处理导入的数据
import pandas as pd
# 处理背景图片
from PIL import Image, ImageTk
二.创建主页面类 MainApp,之后的内容都将在这个页面中显示
class MainApp(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("学生成绩管理系统")
# 设置窗口大小
# 获取当前屏幕的可用几何区域
screen = QApplication.primaryScreen()
available_rect = screen.availableGeometry()
available_width = available_rect.width()
available_height = available_rect.height()
# 设置窗口大小为可用屏幕尺寸减去30像素,以确保窗口不会完全覆盖屏幕
self.resize(available_width - 30, available_height - 30)
# 获取屏幕尺寸并计算窗口居中的位置
x = (available_width - self.width()) // 2
y = (available_height - self.height()) // 2
# 将窗口移动到屏幕中央
self.move(5, -5)
# 创建一个QStackedWidget对象,用于管理多个页面
self.stack = QStackedWidget()
# 将QStackedWidget设置为主窗口的中央控件
self.setCentralWidget(self.stack)
# 创建登录页面,并将切换到功能页面的方法传递给它
self.login_app = LoginApp(self.switch_to_app)
# 将登录页面添加到QStackedWidget中
self.stack.addWidget(self.login_app)
# 创建主页面
self.main_page = App()
# 将主页面添加到QStackedWidget中
self.stack.addWidget(self.main_page)
# 定义一个方法,用于切换到功能页面
def switch_to_app(self):
# 将当前显示的页面切换为功能页面
self.stack.setCurrentWidget(self.main_page)
这里来讲一下如何实现的,首先主页面会按照给定的大小生成一个页面,并把登陆注册的内容加载进去,登录成功后,会调用切换页面的函数 switch_to_app 将当前显示的页面切换成功能页面
下面是这个类的展示页面
三.功能页面代码App类,这里代码有点多,这个页面的代码有点多,我分开来说
3.1创建类,初始化参数
class App(QMainWindow):
def __init__(self):
super().__init__()
self.students = {}
self.subjects = ['序号', '学号', "姓名", '课程名', '专业', '学分', '平时成绩', '期末成绩', '总评成绩', '绩点']
self.entries = {}
self.initUI()
3.2功能的主页面
# 主页面
def initUI(self):
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.layout = QHBoxLayout(self.central_widget) # 使用水平布局
# 创建一个新的垂直布局,并将其放在左边
self.button_layout = QVBoxLayout()
self.button_layout.setSpacing(10) # 设置按钮之间的间距
self.button_layout.setAlignment(Qt.AlignTop) # 设置垂直布局顶部对齐
self.layout.addLayout(self.button_layout)
# 添加顶部间距(固定高度)
top_spacer = QSpacerItem(20, 50, QSizePolicy.Minimum, QSizePolicy.Fixed) # 调整高度
self.button_layout.addItem(top_spacer)
# 添加按钮到垂直布局,并设置样式
self.button_add = QPushButton('添加学生成绩')
self.button_add.setStyleSheet(
"QPushButton { font-size: 16px; font-family: Arial; font-weight: bold; color: black; min-height: 20px; }")
self.button_add.clicked.connect(lambda: self.show_page('add'))
self.button_layout.addWidget(self.button_add)
self.button_query = QPushButton('查询学生成绩')
self.button_query.setStyleSheet(
"QPushButton { font-size: 16px; font-family: Arial; font-weight: bold; color: black; min-height: 20px; }")
self.button_query.clicked.connect(lambda: self.show_page('query'))
self.button_layout.addWidget(self.button_query)
self.button_list = QPushButton('所有学生成绩')
self.button_list.setStyleSheet(
"QPushButton { font-size: 16px; font-family: Arial; font-weight: bold; color: black; min-height: 20px; }")
self.button_list.clicked.connect(lambda: self.show_page('list'))
self.button_layout.addWidget(self.button_list)
# 将 QTabWidget 放在右边
self.tab_widget = QTabWidget()
self.tab_widget.setTabsClosable(True) # 设置标签页可关闭
self.tab_widget.tabCloseRequested.connect(self.close_page) # 连接关闭信号到槽函数
self.layout.addWidget(self.tab_widget)
self.pages = {}
self.current_page = 1
self.page_size = 24 # 每页显示的行数
3.3打开标签页
# 打开标签页
def show_page(self, page):
if page not in self.pages:
if page == 'add':
self.pages[page] = self.create_add_page()
self.tab_widget.addTab(self.pages[page], '添加学生成绩')
elif page == 'query':
self.pages[page] = self.create_query_page()
self.tab_widget.addTab(self.pages[page], '查询学生成绩')
elif page == 'list':
self.pages[page] = self.create_list_page()
self.tab_widget.addTab(self.pages[page], '所有学生成绩')
else:
index = self.tab_widget.indexOf(self.pages[page])
if index == -1:
if page == 'add':
self.pages[page] = self.create_add_page()
self.tab_widget.addTab(self.pages[page], '添加学生成绩')
elif page == 'query':
self.pages[page] = self.create_query_page()
self.tab_widget.addTab(self.pages[page], '查询学生成绩')
elif page == 'list':
self.pages[page] = self.create_list_page()
self.tab_widget.addTab(self.pages[page], '所有学生成绩')
self.tab_widget.setCurrentWidget(self.pages[page])
3.4关闭标签页窗口
# 关闭窗口
def close_page(self, index):
widget = self.tab_widget.widget(index)
for key, value in self.pages.items():
if value == widget:
del self.pages[key]
break
self.tab_widget.removeTab(index)
3.5创建 添加数据 功能的页面
# 添加数据标签页
def create_add_page(self):
# 创建一个新的 QWidget 作为页面的框架
frame = QWidget()
# 创建一个垂直布局,并将其添加到框架中
layout = QVBoxLayout(frame)
# 创建一个水平布局,用于放置输入框和标签
input_layout = QHBoxLayout()
# 将水平布局添加到垂直布局中
layout.addLayout(input_layout)
# 创建一个字典,用于存储输入框
self.entries = {}
# 定义每个输入框的宽度,顺序与self.subjects[1:]对应
widths = [200, 100, 200, 200, 50, 50, 50, 50, 50]
# 设置字体,按钮背景颜色
font = QFont('宋体', 13, QFont.Bold)
button_stylesheet = "background-color: skyblue;"
# 遍历科目列表(不包括第一项 "序号"),并设置输入框宽度
for i, subject in enumerate(self.subjects[1:]):
# 创建一个标签,并将其文本设置为当前科目
label = QLabel(f'{subject}:')
label.setFont(font)
# 将标签添加到水平布局中
input_layout.addWidget(label)
# 创建一个输入框
entry = QLineEdit()
entry.setFont(font)
# 设置输入框宽度
entry.setFixedWidth(widths[i])
# 将输入框添加到水平布局中
input_layout.addWidget(entry)
# 将输入框添加到字典中,键为科目名称
self.entries[subject] = entry
# 创建一个提交按钮
submit_button = QPushButton('提交')
# 设置字体,按钮背景颜色
submit_button.setFont(font)
submit_button.setStyleSheet(button_stylesheet)
# 将提交按钮的点击事件连接到 save_student 函数
submit_button.clicked.connect(self.save_student)
# 将提交按钮添加到水平布局中
input_layout.addWidget(submit_button)
# 创建一个水平布局,用于放置导入和导入模板下载按钮
button_layout = QHBoxLayout()
# 将水平布局添加到垂直布局中
layout.addLayout(button_layout)
# 将按钮布局左对齐
button_layout.setAlignment(Qt.AlignLeft)
# 创建一个导入按钮
import_button = QPushButton('导入')
# # 设置字体,按钮背景颜色
import_button.setFont(font)
import_button.setStyleSheet(button_stylesheet)
# 将导入按钮的点击事件连接到 import_students 函数
import_button.clicked.connect(self.import_students)
# 将导入按钮添加到水平布局
button_layout.addWidget(import_button)
# 创建一个导入模板下载按钮
template_button = QPushButton('导入模板下载')
# # 设置字体,按钮背景颜色
template_button.setFont(font)
template_button.setStyleSheet(button_stylesheet)
# 将导入模板下载按钮的点击事件连接到 template_download 函数
template_button.clicked.connect(self.template_download)
# 将导入模板下载按钮添加到水平布局
button_layout.addWidget(template_button)
# 创建一个表格,用于显示学生成绩
self.tree = QTableWidget()
# 设置表格的列数为科目列表的长度
self.tree.setColumnCount(len(self.subjects))
# 设置表格的水平表头标签为科目列表
self.tree.setHorizontalHeaderLabels(self.subjects)
# 隐藏垂直表头(行号)
self.tree.verticalHeader().setVisible(False)
# 设置表格的字体
table_font = QFont('宋体', 12, QFont.Bold)
self.tree.setFont(table_font)
# 设置表格线条样式
# 设置表格每个单元格的字体样式
self.tree.setStyleSheet("""
QTableWidget::item {
border: 1px solid #D3D3D3; /* 浅灰色边框 */
padding: 0px;
margin: 0px;
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QHeaderView::section {
border: 1px solid #D3D3D3; /* 浅灰色边框 */
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QTableWidget {
gridline-color: #D3D3D3; /* 浅灰色边框 */
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
""")
# 将表格添加到垂直布局中
layout.addWidget(self.tree)
# 设置初始列宽比例
widths = [90, 300, 150, 360, 360, 85, 90, 90, 90, 85]
for i, width in enumerate(widths):
# 设置每一列根据内容调整宽度
self.tree.setColumnWidth(i, width)
# 设置表头视图的调整模式
header = self.tree.horizontalHeader()
for i in range(self.tree.columnCount()):
if i in [0, 1, 2, 3, 4, 5, 6, 7, 8]: # 固定宽度的列
header.setSectionResizeMode(i, QHeaderView.Fixed)
else: # 自适应宽度的列
header.setSectionResizeMode(i, QHeaderView.Stretch)
# 使窗口大小变化时,调整列宽
self.tree.horizontalHeader().setStretchLastSection(True)
# 返回创建的框架
return frame
3.6创建 查询数据 的功能页面
# 查询单条数据标签页
def create_query_page(self):
# 创建一个新的 QWidget 作为页面的框架
frame = QWidget()
# 创建一个垂直布局,并将其添加到框架中
layout = QVBoxLayout(frame)
# 创建一个水平布局,用于放置搜索框和按钮
search_layout = QHBoxLayout()
# 将水平布局添加到垂直布局中
layout.addLayout(search_layout)
# 创建一个小部件容器,用于包含下拉框、输入框和按钮
container = QWidget()
container_layout = QHBoxLayout(container)
# 创建一个下拉框,用于选择搜索选项(科目)
self.search_option = QComboBox()
# 添加科目列表(不包括第一项 "序号")到下拉框
self.search_option.addItems(self.subjects[1:])
# 设置下拉框的字体和大小
combo_font = QFont('宋体', 16)
self.search_option.setFont(combo_font)
# 设置下拉框的大小
self.search_option.setMinimumWidth(150)
# 将下拉框添加到容器布局中
container_layout.addWidget(self.search_option)
# 创建一个输入框,用于输入搜索关键字
self.entry_name_query = QLineEdit()
# 设置输入框的字体和大小
input_font = QFont('宋体', 16)
self.entry_name_query.setFont(input_font)
# 设置输入框的大小
self.entry_name_query.setMinimumWidth(200)
# 将输入框添加到容器布局中
container_layout.addWidget(self.entry_name_query)
# 设置字体,按钮背景颜色
font = QFont('宋体', 13, QFont.Bold)
button_stylesheet = "background-color: skyblue;"
# 创建一个查询按钮
query_button = QPushButton('查询')
# 设置字体,按钮背景颜色
query_button.setFont(font)
query_button.setStyleSheet(button_stylesheet)
# 将查询按钮的点击事件连接到 query_student 函数
query_button.clicked.connect(self.query_student)
# 将查询按钮添加到容器布局中
container_layout.addWidget(query_button)
# 将容器小部件添加到搜索布局中,并将其左对齐
search_layout.addWidget(container)
search_layout.setAlignment(container, Qt.AlignLeft)
# 创建一个表格,用于显示查询结果
self.query_tree = QTableWidget()
# 设置表格的列数为科目列表的长度
self.query_tree.setColumnCount(len(self.subjects))
# 设置表格的水平表头标签为科目列表
self.query_tree.setHorizontalHeaderLabels(self.subjects)
# 隐藏垂直表头(行号)
self.query_tree.verticalHeader().setVisible(False)
# 设置表格的字体
table_font = QFont('宋体', 12, QFont.Bold)
self.query_tree.setFont(table_font)
# 设置整行选择
self.query_tree.setSelectionBehavior(QTableWidget.SelectRows)
# 设置表格线条样式
# 设置表格每个单元格的字体样式
self.query_tree.setStyleSheet("""
QTableWidget::item {
border: 1px solid #D3D3D3; /* 浅灰色边框 */
padding: 0px;
margin: 0px;
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QHeaderView::section {
border: 1px solid #D3D3D3; /* 浅灰色边框 */
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QTableWidget {
gridline-color: #D3D3D3; /* 浅灰色边框 */
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QTableWidget::item:selected {
background-color: #87CEEB; /* 选中行的背景颜色,浅蓝色 */
}
""")
# 将表格添加到垂直布局中
layout.addWidget(self.query_tree)
# 获取表格的水平表头
# header = self.query_tree.horizontalHeader()
# 设置表格每一列的宽度
for i, width in enumerate([90, 300, 150, 360, 360, 85, 90, 90, 90, 85]):
# 设置每一列根据内容调整宽度
self.query_tree.setColumnWidth(i, width)
# 设置表头视图的调整模式
header = self.query_tree.horizontalHeader()
for i in range(self.query_tree.columnCount()):
if i in [0, 1, 2, 3, 4, 5, 6, 7, 8]: # 固定宽度的列
header.setSectionResizeMode(i, QHeaderView.Fixed)
else: # 自适应宽度的列
header.setSectionResizeMode(i, QHeaderView.Stretch)
# 使窗口大小变化时,调整列宽
self.query_tree.horizontalHeader().setStretchLastSection(True)
# 创建一个修改按钮
modify_button = QPushButton('修改')
# 设置字体,按钮背景颜色
modify_button.setFont(font)
modify_button.setStyleSheet(button_stylesheet)
# 将修改按钮添加到垂直布局中
container_layout.addWidget(modify_button)
modify_button.clicked.connect(self.update_student)
# 创建一个删除按钮
delete_button = QPushButton('删除')
# 设置字体,按钮背景颜色
delete_button.setFont(font)
delete_button.setStyleSheet(button_stylesheet)
# 将删除按钮添加到垂直布局中
container_layout.addWidget(delete_button)
delete_button.clicked.connect(self.delete_student)
# 返回创建的框架
return frame
3.7创建 查询所有数据 的功能页
# 查询所有数据标签页
def create_list_page(self):
# 创建一个新的 QWidget 作为页面的框架
frame = QWidget()
# 创建一个垂直布局,并将其添加到框架中
layout = QVBoxLayout(frame)
font = QFont('宋体', 16, QFont.Bold)
button_stylesheet = "background-color: skyblue;"
# 创建一个按钮,用于列出所有学生成绩
list_button = QPushButton('列出所有学生成绩')
# 设置字体,按钮背景颜色
list_button.setFont(font)
list_button.setStyleSheet(button_stylesheet)
# 设置按钮的固定宽度
list_button.setFixedWidth(200)
# 将按钮的点击事件连接到 sh_page 函数
list_button.clicked.connect(self.sh_page)
# 创建一个导出按钮,用于导出表格数据为 xlsx
export_button = QPushButton('导出数据')
export_button.setFont(font)
export_button.setStyleSheet(button_stylesheet)
export_button.setFixedWidth(200)
export_button.clicked.connect(self.export_data)
# 创建一个水平布局来包含按钮
h_layout = QHBoxLayout()
h_layout.addStretch() # 添加一个伸缩项,以便按钮居中
h_layout.addWidget(list_button)
h_layout.addStretch() # 添加另一个伸缩项,以便按钮居中
h_layout.addWidget(export_button)
h_layout.addStretch() # 添加另一个伸缩项,以便按钮居中
# 将水平布局添加到垂直布局中
layout.addLayout(h_layout)
# 创建一个表格,用于显示学生成绩列表
self.list_result = QTableWidget()
# 设置表格的列数为科目列表的长度
self.list_result.setColumnCount(len(self.subjects))
# 设置表格的水平表头标签为科目列表
self.list_result.setHorizontalHeaderLabels(self.subjects)
# 隐藏垂直表头(行号)
self.list_result.verticalHeader().setVisible(False)
# 设置表格的字体
table_font = QFont('宋体', 12, QFont.Bold)
self.list_result.setFont(table_font)
# 设置整行选择
self.list_result.setSelectionBehavior(QTableWidget.SelectRows)
# 设置表格线条样式
# 设置表格每个单元格的字体样式
self.list_result.setStyleSheet("""
QTableWidget::item {
border: 1px solid #D3D3D3; /* 浅灰色边框 */
padding: 0px;
margin: 0px;
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QHeaderView::section {
border: 1px solid #D3D3D3; /* 浅灰色边框 */
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QTableWidget {
gridline-color: #D3D3D3; /* 浅灰色边框 */
font-family: 宋体;
font-size: 12pt;
font-weight: bold;
}
QTableWidget::item:selected {
background-color: #87CEEB; /* 选中行的背景颜色,浅蓝色 */
}
""")
# 将表格添加到垂直布局中
layout.addWidget(self.list_result)
# 设置表格每一列的宽度
for i, width in enumerate([90, 300, 150, 360, 360, 85, 90, 90, 90, 85]):
# 设置每一列根据内容调整宽度
self.list_result.setColumnWidth(i, width)
# 设置表头视图的调整模式
header = self.list_result.horizontalHeader()
for i in range(self.list_result.columnCount()):
if i in [0, 1, 2, 3, 4, 5, 6, 7, 8]: # 固定宽度的列
header.setSectionResizeMode(i, QHeaderView.Fixed)
else: # 自适应宽度的列
header.setSectionResizeMode(i, QHeaderView.Stretch)
# 使窗口大小变化时,调整列宽
self.list_result.horizontalHeader().setStretchLastSection(True)
# 创建一个水平布局,用于放置分页控件
pagination_layout = QHBoxLayout()
# 将分页布局添加到垂直布局中
layout.addLayout(pagination_layout)
# 创建一个标签,用于显示当前页码
self.page_label = QLabel('第 1 页')
font = QFont('宋体', 14)
self.page_label.setFont(font)
# 将标签添加到分页布局中
pagination_layout.addWidget(self.page_label)
font = QFont('宋体', 16, QFont.Bold)
button_stylesheet = "background-color: skyblue;"
# 创建一个按钮,用于跳转到上一页
prev_button = QPushButton('上一页')
# 设置字体,按钮背景颜色
prev_button.setFont(font)
prev_button.setStyleSheet(button_stylesheet)
# 将按钮的点击事件连接到 prev_page 函数
prev_button.clicked.connect(self.prev_page)
# 将按钮添加到分页布局中
pagination_layout.addWidget(prev_button)
# 创建一个按钮,用于跳转到下一页
next_button = QPushButton('下一页')
next_button.setFont(font)
next_button.setStyleSheet(button_stylesheet)
# 将按钮的点击事件连接到 next_page 函数
next_button.clicked.connect(self.next_page)
# 将按钮添加到分页布局中
pagination_layout.addWidget(next_button)
# 返回创建的框架
return frame
3.8保存单条数据
# 保存单条数据
def save_student(self):
student_data = {subject: entry.text() for subject, entry in self.entries.items()}
student_name = student_data['姓名']
if student_name:
self.students[student_name] = student_data
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
cursor.execute('''
INSERT INTO achievement (Student_ID, Name, Course_Name, Major, Credit, Regular_Score, Final_Score, Overall_Score, GPA)
VALUES (:学号, :姓名, :课程名, :专业, :学分, :平时成绩, :期末成绩, :总评成绩, :绩点)
''', student_data)
conn.commit()
cursor.close()
conn.close()
QMessageBox.information(self, '成功', '学生成绩已保存。')
# 将数据添加到表格中
new_index = self.tree.rowCount()
self.tree.insertRow(new_index)
# 在序号列填入自增序号
self.tree.setItem(new_index, 0, QTableWidgetItem(str(new_index + 1)))
# 从第一列开始填入其他数据
for col, subject in enumerate(self.subjects):
if subject in student_data:
self.tree.setItem(new_index, col, QTableWidgetItem(student_data[subject]))
# 清空输入框
for entry in self.entries.values():
entry.clear()
3.9表格导入保存数据
# 表格导入保存
def import_students(self):
file_dialog = QFileDialog()
file_path, _ = file_dialog.getOpenFileName(self, '打开文件', '', 'Excel files (*.xlsx *.xls);;CSV files (*.csv)')
if file_path:
try:
if file_path.endswith('.xlsx') or file_path.endswith('.xls'):
data = pd.read_excel(file_path)
elif file_path.endswith('.csv'):
data = pd.read_csv(file_path)
else:
QMessageBox.critical(self, '错误', '不支持的文件类型')
return
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
for index, row in data.iterrows():
student_data = {
"学号": row["学号"],
"姓名": row["姓名"],
"课程名": row["课程名"],
"专业": row["专业"],
"学分": row["学分"],
"平时成绩": row["平时成绩"],
"期末成绩": row["期末成绩"],
"总评成绩": row["总评成绩"],
"绩点": row["绩点"]
}
cursor.execute('''
INSERT INTO achievement (Student_ID, Name, Course_Name, Major, Credit, Regular_Score, Final_Score, Overall_Score, GPA)
VALUES (:学号, :姓名, :课程名, :专业, :学分, :平时成绩, :期末成绩, :总评成绩, :绩点)
''', student_data)
new_index = self.tree.rowCount()
self.tree.insertRow(new_index)
for col, value in enumerate([new_index + 1] + list(student_data.values())):
self.tree.setItem(new_index, col, QTableWidgetItem(str(value)))
conn.commit()
cursor.close()
conn.close()
QMessageBox.information(self, '成功', '学生成绩已导入。')
except Exception as e:
QMessageBox.critical(self, '错误', f'导入数据时发生错误: {e}')
3.10导入模板下载
# 导入模板下载
def template_download(self):
try:
filespath = os.path.abspath(os.path.dirname(__file__))
source_path = fr'{filespath}/成绩导入模板.xlsx' # 确保你有一个名为 成绩导入模板.xlsx 的模板文件
default_filename = '成绩导入模板.xlsx'
target_path, _ = QFileDialog.getSaveFileName(self, '保存文件', default_filename, 'Excel files (*.xlsx)')
if target_path:
shutil.copyfile(source_path, target_path)
QMessageBox.information(self, '成功', '模板文件已下载。')
except Exception as e:
QMessageBox.critical(self, '错误', f'下载模板文件时发生错误: {e}')
3.11数据库查询单条数据,并显示到查询数据的页面上
# 查询数据
def query_student(self):
self.query_tree.setRowCount(0) # 清空表格
# 查询字段映射
field_mapping = {
'学号': 'Student_ID',
'姓名': 'Name',
'课程名': 'Course_Name',
'专业': 'Major',
'学分': 'Credit',
'平时成绩': 'Regular_Score',
'期末成绩': 'Final_Score',
'总评成绩': 'Overall_Score',
'绩点': 'GPA'
}
# 获取查询条件和内容
search_option = self.search_option.currentText()
search_value = self.entry_name_query.text()
# print(search_option,search_value)
# 获取数据库字段名称
db_field = field_mapping.get(search_option)
print(search_option, search_value, db_field)
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
cursor.row_factory = sqlite3.Row
# 根据选择的条件生成SQL查询语句
# 使用等于操作符
query = f"SELECT * FROM achievement WHERE {db_field} = ?"
params = (search_value,) # 这里不需要使用 LIKE 通配符
cursor.execute(query, params)
results = cursor.fetchall()
# print(results)
cursor.close()
conn.close()
# 遍历查询结果,并将数据添加到表格中
for row_index, row_data in enumerate(results, start=1):
# print(row_index, row_data)
new_index = self.query_tree.rowCount()
print('new_index', new_index)
self.query_tree.insertRow(new_index)
# 设置自增序号
self.query_tree.setItem(new_index, 0, QTableWidgetItem(str(row_index)))
# 添加其他数据列
for col, value in enumerate(row_data):
if col == 0: # 跳过 ID 列
continue
print(col, value)
self.query_tree.setItem(new_index, col, QTableWidgetItem(str(value)))
3.12数据库查询所有数据的函数,这部分功能需要根据窗口大小计算显示的条数,获取总条数,计算分多少页显示,实现翻页的效果
# 窗口大小改变时重新计算分页
def resizeEvent(self, event):
if event.oldSize().height() != -1: # 检查是否为初始大小变化事件
self.sh_page()
super().resizeEvent(event)
# 查询所有数据
def sh_page(self):
window_height = self.list_result.height()
row_height = 31
self.page_size = window_height // row_height
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM achievement")
rows = cursor.fetchall()
cursor.close()
conn.close()
self.list_result.setRowCount(0) # 清空表格
start_index = (self.current_page - 1) * self.page_size
end_index = start_index + self.page_size
page_rows = rows[start_index:end_index]
for idx, row in enumerate(page_rows, start=start_index + 1):
row_count = self.list_result.rowCount()
self.list_result.insertRow(row_count)
self.list_result.setItem(row_count, 0, QTableWidgetItem(str(idx)))
for col, item in enumerate(row[1:], start=1):
self.list_result.setItem(row_count, col, QTableWidgetItem(str(item)))
self.update_pagination_label()
# 上一页
def prev_page(self):
if self.current_page > 1:
self.current_page -= 1
self.sh_page()
# 下一页
def next_page(self):
self.current_page += 1
self.sh_page()
# 获取总页数
def update_pagination_label(self):
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM achievement")
total_records = cursor.fetchone()[0]
conn.close()
self.total_pages = (total_records + self.page_size - 1) // self.page_size
self.page_label.setText(f'第 {self.current_page} 页 共 {self.total_pages} 页')
3.13修改数据的函数
# 修改信息
def update_student(self):
selected_items = self.query_tree.selectedItems()
if selected_items:
selected_item = selected_items[0]
selected_row = selected_item.row()
values = [self.query_tree.item(selected_row, col).text() for col in range(self.query_tree.columnCount())]
update_window = QDialog(self)
update_window.setWindowTitle("修改学生信息")
update_window.setModal(True)
update_window.setFixedSize(400, 300) # 设置窗口大小为 400x300
update_entries = {}
labels = ['学号', '姓名', '课程名', '专业', '学分', '平时成绩', '期末成绩', '总评成绩', '绩点']
read_only_labels = ['学号', '姓名', '课程名']
layout = QGridLayout(update_window)
for i, label in enumerate(labels):
layout.addWidget(QLabel(label, update_window), i, 0)
if label in read_only_labels:
readonly_label = QLabel(values[i + 1]) # 修正索引
layout.addWidget(readonly_label, i, 1)
else:
entry = QLineEdit(values[i + 1]) # 修正索引
layout.addWidget(entry, i, 1)
update_entries[label] = entry
def save_changes():
updated_values = []
for i, label in enumerate(labels):
if label in read_only_labels:
updated_values.append(values[i + 1]) # 修正索引
else:
updated_values.append(update_entries[label].text())
for col, value in enumerate(updated_values):
self.query_tree.setItem(selected_row, col + 1, QTableWidgetItem(value)) # 修正索引
# 更新数据库
student_id = values[1]
student_name = values[2]
course_name = values[3]
update_data = {
'专业': update_entries['专业'].text(),
'学分': update_entries['学分'].text(),
'平时成绩': update_entries['平时成绩'].text(),
'期末成绩': update_entries['期末成绩'].text(),
'总评成绩': update_entries['总评成绩'].text(),
'绩点': update_entries['绩点'].text()
}
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
cursor.execute('''
UPDATE achievement
SET Major = :专业, Credit = :学分, Regular_Score = :平时成绩, Final_Score = :期末成绩, Overall_Score = :总评成绩, GPA = :绩点
WHERE Student_ID = ? AND Name = ? AND Course_Name = ?
''', (*update_data.values(), student_id, student_name, course_name))
conn.commit()
cursor.close()
conn.close()
QMessageBox.information(self, "成功", "学生信息已更新。")
update_window.accept()
save_button = QPushButton("保存", update_window)
save_button.clicked.connect(save_changes)
layout.addWidget(save_button, len(labels), 1)
update_window.exec_()
3.14删除数据
# 删除数据
def delete_student(self):
selected_items = self.query_tree.selectedItems()
if selected_items:
selected_item = selected_items[0]
selected_row = selected_item.row()
# 弹出确认对话框
confirm = QMessageBox.question(self, "确认删除", "确定要删除选中的学生信息吗?", QMessageBox.Ok | QMessageBox.Cancel)
if confirm == QMessageBox.Ok:
# 获取选中行的数据
item_data = [self.query_tree.item(selected_row, col).text() for col in
range(self.query_tree.columnCount())]
print(item_data)
# 从数据库删除记录
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
field_mapping = {
'学号': 'Student_ID',
'姓名': 'Name',
'课程名': 'Course_Name',
'专业': 'Major',
'学分': 'Credit',
'平时成绩': 'Regular_Score',
'期末成绩': 'Final_Score',
'总评成绩': 'Overall_Score',
'绩点': 'GPA'
}
placeholders = " AND ".join([f"{field_mapping[key]}=?" for key in field_mapping.keys()])
query = f"DELETE FROM achievement WHERE {placeholders}"
cursor.execute(query, item_data[1:]) # 跳过序号列
conn.commit()
cursor.close()
conn.close()
# 从表格中删除选中的行
self.query_tree.removeRow(selected_row)
QMessageBox.information(self, "成功", "学生信息已删除。")
3.15导出所有数据
# 导出所有数据
def export_data(self):
# 检查当前显示的表格中是否有数据
if self.list_result.rowCount() == 0:
QMessageBox.warning(self, "导出失败", "表格中没有数据,无法导出。")
return
# 查询数据库中的所有数据
filespath = os.path.abspath(os.path.dirname(__file__))
conn = sqlite3.connect(fr'{filespath}/StudentAchievements.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM achievement")
rows = cursor.fetchall()
cursor.close()
conn.close()
if not rows:
QMessageBox.warning(self, "导出失败", "数据库中没有数据,无法导出。")
return
# 获取表头,去掉序号列
headers = [self.list_result.horizontalHeaderItem(i).text() for i in range(1, self.list_result.columnCount())]
# 获取所有数据,去掉ID列
data = []
for row in rows:
data.append(row[1:]) # 跳过第一个元素(ID列)
# 转换为 DataFrame 并导出
df = pd.DataFrame(data, columns=headers)
options = QFileDialog.Options()
default_file_name = "学生成绩表.xlsx"
file_path, _ = QFileDialog.getSaveFileName(self, "保存文件", default_file_name,
"Excel Files (*.xlsx);;All Files (*)", options=options)
if file_path:
df.to_excel(file_path, index=False)
QMessageBox.information(self, "导出成功", f"数据已成功导出到 {file_path}")
3.16最后是run的代码
if __name__ == "__main__":
app = QApplication([])
main_app = MainApp()
main_app.show()
app.exec_()
四.各页面的展示
4.1添加学生成绩
4.2查询成绩
4.3修改
4.4删除
4.5列出所有成绩
至此,结束。
不足之处请见谅,维护开源和谐环境 ,转载请注明出处。