import sys
import pymysql
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QDialog, QTableWidgetItem,
QMessageBox, QHeaderView, QPushButton, QLineEdit, QComboBox, QLabel,
QVBoxLayout, QHBoxLayout, QFormLayout, QGroupBox, QTableWidget
)
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QIntValidator, QRegExpValidator
from PyQt5.QtCore import QRegExp
# 数据库配置,与原系统保持一致
DB_CONFIG = {
'host': '127.0.0.1',
'user': 'root',
'password': '123456',
'database': 'studentscore',
'charset': 'utf8mb4'
}
class LoginDialog(QDialog):
"""登录窗口"""
def __init__(self):
super().__init__()
self.setWindowTitle("专业管理系统登录")
self.setFixedSize(350, 200)
# 设置登录窗口的背景颜色为淡蓝色
self.setStyleSheet("background-color: #e0f7fa;")
# 创建布局
layout = QVBoxLayout()
# 表单组
form_group = QGroupBox("数据库登录信息")
form_layout = QFormLayout()
self.txt_host = QLineEdit()
self.txt_host.setText(DB_CONFIG['host'])
self.txt_host.setPlaceholderText("数据库主机地址")
self.txt_user = QLineEdit()
self.txt_user.setText(DB_CONFIG['user'])
self.txt_user.setPlaceholderText("数据库用户名")
self.txt_pass = QLineEdit()
self.txt_pass.setText(DB_CONFIG['password'])
self.txt_pass.setPlaceholderText("数据库密码")
self.txt_pass.setEchoMode(QLineEdit.Password)
self.txt_db = QLineEdit()
self.txt_db.setText(DB_CONFIG['database'])
self.txt_db.setPlaceholderText("数据库名称")
form_layout.addRow("主机:", self.txt_host)
form_layout.addRow("用户名:", self.txt_user)
form_layout.addRow("密码:", self.txt_pass)
form_layout.addRow("数据库:", self.txt_db)
form_group.setLayout(form_layout)
# 按钮组
btn_layout = QHBoxLayout()
self.btn_login = QPushButton("登录")
self.btn_login.clicked.connect(self.attempt_login)
self.btn_exit = QPushButton("退出")
self.btn_exit.clicked.connect(self.reject)
btn_layout.addWidget(self.btn_login)
btn_layout.addWidget(self.btn_exit)
# 添加到主布局
layout.addWidget(form_group)
layout.addLayout(btn_layout)
self.setLayout(layout)
def attempt_login(self):
"""尝试登录"""
config = {
'host': self.txt_host.text().strip(),
'user': self.txt_user.text().strip(),
'password': self.txt_pass.text().strip(),
'database': self.txt_db.text().strip(),
'charset': 'utf8mb4'
}
if not all(config.values()):
QMessageBox.warning(self, "输入不完整", "请填写所有数据库连接信息")
return
try:
# 测试数据库连接
conn = pymysql.connect(**config)
# 检查bmajor表是否存在
with conn.cursor() as cursor:
cursor.execute("SHOW TABLES LIKE 'bmajor'")
if not cursor.fetchone():
QMessageBox.critical(self, "表不存在", "数据库中未找到bmajor表")
return
conn.close()
# 更新全局配置
global DB_CONFIG
DB_CONFIG = config
self.accept() # 关闭对话框并返回QDialog.Accepted
except pymysql.Error as e:
QMessageBox.critical(self, "连接失败", f"数据库连接失败:\n{str(e)}")
class MajorManager(QMainWindow):
"""专业管理主窗口"""
def __init__(self):
super().__init__()
self.setWindowTitle("专业信息管理系统")
self.setGeometry(100, 100, 1000, 600)
# 创建中央部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 主布局
main_layout = QVBoxLayout(central_widget)
# 搜索功能区
search_group = QGroupBox("专业搜索")
search_layout = QHBoxLayout()
self.lbl_search = QLabel("专业名称:")
self.txt_search = QLineEdit()
self.txt_search.setPlaceholderText("输入专业名称关键字...")
self.btn_search = QPushButton("搜索")
self.btn_search.clicked.connect(self.search_major)
self.btn_refresh = QPushButton("刷新数据")
self.btn_refresh.clicked.connect(self.load_data)
search_layout.addWidget(self.lbl_search)
search_layout.addWidget(self.txt_search)
search_layout.addWidget(self.btn_search)
search_layout.addWidget(self.btn_refresh)
search_group.setLayout(search_layout)
# 数据表格
self.table = self.create_table()
# 操作按钮区
btn_group = QGroupBox("专业操作")
btn_layout = QHBoxLayout()
self.btn_add = QPushButton("添加专业")
self.btn_add.clicked.connect(self.add_major)
self.btn_edit = QPushButton("编辑专业")
self.btn_edit.clicked.connect(self.edit_major)
self.btn_delete = QPushButton("删除专业")
self.btn_delete.clicked.connect(self.delete_major)
btn_layout.addWidget(self.btn_add)
btn_layout.addWidget(self.btn_edit)
btn_layout.addWidget(self.btn_delete)
btn_group.setLayout(btn_layout)
# 添加到主布局
main_layout.addWidget(search_group)
main_layout.addWidget(self.table, 1) # 表格占据更多空间
main_layout.addWidget(btn_group)
# 状态栏
self.statusBar().showMessage("就绪")
# 加载数据
self.load_data()
def create_table(self):
"""创建专业表格"""
table = QTableWidget()
table.setColumnCount(4)
table.setHorizontalHeaderLabels(["专业ID", "专业名称", "院系ID", "院系名称"])
table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
table.verticalHeader().setVisible(False)
table.setSelectionBehavior(QTableWidget.SelectRows)
table.setEditTriggers(QTableWidget.NoEditTriggers)
table.setSortingEnabled(True)
return table
def get_connection(self):
"""获取数据库连接"""
return pymysql.connect(**DB_CONFIG)
def load_data(self):
"""从数据库加载专业数据"""
try:
conn = self.get_connection()
with conn.cursor() as cursor:
# 查询bmajor表的所有数据
cursor.execute("SELECT major_id, major_name, depart_id, depart_name FROM bmajor")
result = cursor.fetchall()
self.table.setRowCount(len(result))
for row_idx, row in enumerate(result):
for col_idx, col in enumerate(row):
item = QTableWidgetItem(str(col) if col is not None else "")
self.table.setItem(row_idx, col_idx, item)
conn.close()
self.statusBar().showMessage(f"成功加载 {len(result)} 条专业记录")
except pymysql.Error as e:
QMessageBox.critical(self, "数据库错误", f"加载数据失败:\n{str(e)}")
self.statusBar().showMessage("数据加载失败")
def add_major(self):
"""添加新专业"""
dialog = MajorDialog(self)
if dialog.exec_() == QDialog.Accepted:
major_data = dialog.get_data()
try:
conn = self.get_connection()
with conn.cursor() as cursor:
sql = """INSERT INTO bmajor
(major_id, major_name, depart_id, depart_name)
VALUES (%s, %s, %s, %s)"""
cursor.execute(sql, major_data)
conn.commit()
self.load_data()
self.statusBar().showMessage(f"成功添加专业: {major_data[1]}")
except pymysql.IntegrityError as e:
if "Duplicate entry" in str(e):
QMessageBox.critical(self, "添加失败", "专业ID或专业名称已存在")
else:
QMessageBox.critical(self, "添加失败", f"添加专业失败:\n{str(e)}")
except pymysql.Error as e:
QMessageBox.critical(self, "添加失败", f"添加专业失败:\n{str(e)}")
def edit_major(self):
"""编辑选中专业"""
selected = self.table.currentRow()
if selected < 0:
QMessageBox.warning(self, "未选择", "请先选择要编辑的专业")
return
major_id = self.table.item(selected, 0).text()
dialog = MajorDialog(self, major_id)
if dialog.exec_() == QDialog.Accepted:
major_data = dialog.get_data()
try:
conn = self.get_connection()
with conn.cursor() as cursor:
sql = """UPDATE bmajor SET
major_name = %s, depart_id = %s, depart_name = %s
WHERE major_id = %s"""
# 注意顺序: name, depart_id, depart_name, id
cursor.execute(sql, (
major_data[1], major_data[2], major_data[3], major_data[0]
))
conn.commit()
self.load_data()
self.statusBar().showMessage(f"成功更新专业: {major_data[1]}")
except pymysql.Error as e:
QMessageBox.critical(self, "更新失败", f"更新专业失败:\n{str(e)}")
def delete_major(self):
"""删除选中专业"""
selected = self.table.currentRow()
if selected < 0:
QMessageBox.warning(self, "未选择", "请先选择要删除的专业")
return
major_id = self.table.item(selected, 0).text()
major_name = self.table.item(selected, 1).text()
reply = QMessageBox.question(self, "确认删除",
f"确定要删除专业 '{major_name}' (ID: {major_id}) 吗?",
QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.Yes:
try:
conn = self.get_connection()
with conn.cursor() as cursor:
cursor.execute("DELETE FROM bmajor WHERE major_id = %s", (major_id,))
conn.commit()
self.load_data()
self.statusBar().showMessage(f"已删除专业: {major_name}")
except pymysql.Error as e:
# 检查是否有外键约束
if "foreign key constraint" in str(e).lower():
QMessageBox.critical(self, "删除失败", "该专业可能被其他表引用,无法删除")
else:
QMessageBox.critical(self, "删除失败", f"删除专业失败:\n{str(e)}")
def search_major(self):
"""搜索专业"""
keyword = self.txt_search.text().strip()
if not keyword:
self.load_data()
return
try:
conn = self.get_connection()
with conn.cursor() as cursor:
cursor.execute("SELECT major_id, major_name, depart_id, depart_name "
"FROM bmajor WHERE major_name LIKE %s",
(f"%{keyword}%",))
result = cursor.fetchall()
self.table.setRowCount(len(result))
for row_idx, row in enumerate(result):
for col_idx, col in enumerate(row):
item = QTableWidgetItem(str(col) if col is not None else "")
self.table.setItem(row_idx, col_idx, item)
conn.close()
self.statusBar().showMessage(f"找到 {len(result)} 条匹配专业")
except pymysql.Error as e:
QMessageBox.critical(self, "搜索失败", f"搜索专业失败:\n{str(e)}")
class MajorDialog(QDialog):
"""专业编辑对话框"""
def __init__(self, parent=None, major_id=None):
super().__init__(parent)
self.setWindowTitle("添加专业" if not major_id else "编辑专业")
self.setFixedSize(400, 350)
# 主布局
layout = QVBoxLayout()
# 表单布局
form_layout = QFormLayout()
self.txt_id = QLineEdit()
self.txt_id.setPlaceholderText("2位专业代码")
# 设置专业ID验证器,必须是2位字母或数字
id_validator = QRegExpValidator(QRegExp(r'^[a-zA-Z0-9]{2}$'))
self.txt_id.setValidator(id_validator)
self.txt_name = QLineEdit()
self.txt_name.setPlaceholderText("输入专业名称")
self.txt_depart_id = QLineEdit()
self.txt_depart_id.setPlaceholderText("2位院系代码")
# 设置院系ID验证器,必须是2位字母或数字
depart_id_validator = QRegExpValidator(QRegExp(r'^[a-zA-Z0-9]{2}$'))
self.txt_depart_id.setValidator(depart_id_validator)
self.txt_depart_name = QLineEdit()
self.txt_depart_name.setPlaceholderText("输入院系名称")
form_layout.addRow("专业ID:", self.txt_id)
form_layout.addRow("专业名称:", self.txt_name)
form_layout.addRow("院系ID:", self.txt_depart_id)
form_layout.addRow("院系名称:", self.txt_depart_name)
# 按钮布局
btn_layout = QHBoxLayout()
self.btn_save = QPushButton("保存")
self.btn_save.clicked.connect(self.validate_and_accept)
self.btn_cancel = QPushButton("取消")
self.btn_cancel.clicked.connect(self.reject)
btn_layout.addStretch()
btn_layout.addWidget(self.btn_save)
btn_layout.addWidget(self.btn_cancel)
# 添加到主布局
layout.addLayout(form_layout)
layout.addLayout(btn_layout)
self.setLayout(layout)
# 如果是编辑模式,加载现有数据
self.major_id = major_id
if major_id:
self.load_major_data()
self.txt_id.setEnabled(False) # 禁止编辑专业ID
def load_major_data(self):
"""加载专业数据到表单"""
try:
conn = pymysql.connect(**DB_CONFIG)
with conn.cursor() as cursor:
cursor.execute("SELECT * FROM bmajor WHERE major_id = %s", (self.major_id,))
result = cursor.fetchone()
if result:
self.txt_id.setText(result[0])
self.txt_name.setText(result[1])
self.txt_depart_id.setText(result[2] if result[2] is not None else "")
self.txt_depart_name.setText(result[3] if result[3] is not None else "")
conn.close()
except pymysql.Error as e:
QMessageBox.critical(self, "加载失败", f"加载专业数据失败:\n{str(e)}")
def get_data(self):
"""从表单获取数据"""
# 处理可能为空的值
def get_value_or_none(text):
return text.strip() if text.strip() else None
return (
self.txt_id.text().strip(),
self.txt_name.text().strip(),
get_value_or_none(self.txt_depart_id.text()),
get_value_or_none(self.txt_depart_name.text())
)
def validate_and_accept(self):
"""验证表单数据并接受"""
# 验证专业ID格式
major_id = self.txt_id.text().strip()
if len(major_id) != 2:
QMessageBox.warning(self, "格式错误", "专业ID必须是2位字符")
return
# 验证必填字段
if not self.txt_name.text().strip():
QMessageBox.warning(self, "输入不完整", "专业名称不能为空")
return
# 验证院系ID
depart_id = self.txt_depart_id.text().strip()
if depart_id and len(depart_id) != 2:
QMessageBox.warning(self, "格式错误", "院系ID必须是2位字符")
return
super().accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
# 显示登录窗口
login = LoginDialog()
if login.exec_() != QDialog.Accepted:
sys.exit()
# 显示主窗口
window = MajorManager()
window.show()
sys.exit(app.exec_())