|
|
|
@ -0,0 +1,685 @@
|
|
|
|
|
# student/ui/gui_ui.py
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
from tkinter import ttk, messagebox
|
|
|
|
|
from datetime import datetime, date
|
|
|
|
|
import re
|
|
|
|
|
from student.model.student import Student
|
|
|
|
|
from typing import List, Optional
|
|
|
|
|
from ..bll.student_bll import StudentBLL
|
|
|
|
|
from ..model.student import Student
|
|
|
|
|
from ..util.validator import Validator
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class StudentGUI:
|
|
|
|
|
"""学生信息管理系统的图形用户界面"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, root: tk.Tk, bll: StudentBLL):
|
|
|
|
|
self.root = root
|
|
|
|
|
self.root.title("学生信息管理系统")
|
|
|
|
|
self.root.geometry("900x600")
|
|
|
|
|
self.root.minsize(800, 500)
|
|
|
|
|
|
|
|
|
|
self.bll = bll
|
|
|
|
|
|
|
|
|
|
# 设置中文字体
|
|
|
|
|
self.style = ttk.Style()
|
|
|
|
|
self.style.configure("TLabel", font=("SimHei", 10))
|
|
|
|
|
self.style.configure("TButton", font=("SimHei", 10))
|
|
|
|
|
self.style.configure("TTreeview", font=("SimHei", 10))
|
|
|
|
|
|
|
|
|
|
# 创建主框架
|
|
|
|
|
self.main_frame = ttk.Frame(self.root)
|
|
|
|
|
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 创建顶部导航栏
|
|
|
|
|
self.create_navigation_bar()
|
|
|
|
|
|
|
|
|
|
# 创建主内容区域
|
|
|
|
|
self.content_frame = ttk.Frame(self.main_frame)
|
|
|
|
|
self.content_frame.pack(fill=tk.BOTH, expand=True, pady=10)
|
|
|
|
|
|
|
|
|
|
# 默认显示学生列表
|
|
|
|
|
self.show_student_list()
|
|
|
|
|
|
|
|
|
|
def create_navigation_bar(self):
|
|
|
|
|
"""创建顶部导航栏"""
|
|
|
|
|
nav_frame = ttk.Frame(self.main_frame, height=40)
|
|
|
|
|
nav_frame.pack(fill=tk.X, side=tk.TOP)
|
|
|
|
|
|
|
|
|
|
# 添加导航按钮
|
|
|
|
|
buttons = [
|
|
|
|
|
("添加学生", self.show_add_student),
|
|
|
|
|
("修改学生", self.show_update_student),
|
|
|
|
|
("删除学生", self.show_delete_student),
|
|
|
|
|
("查询学生", self.show_search_student),
|
|
|
|
|
("统计信息", self.show_statistics),
|
|
|
|
|
("数据导入导出", self.show_import_export)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for text, command in buttons:
|
|
|
|
|
btn = ttk.Button(nav_frame, text=text, command=command)
|
|
|
|
|
btn.pack(side=tk.LEFT, padx=5, pady=5)
|
|
|
|
|
|
|
|
|
|
def clear_content_frame(self):
|
|
|
|
|
"""清空内容区域"""
|
|
|
|
|
for widget in self.content_frame.winfo_children():
|
|
|
|
|
widget.destroy()
|
|
|
|
|
|
|
|
|
|
def show_student_list(self, students: Optional[List[Student]] = None):
|
|
|
|
|
"""显示学生列表"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
if students is None:
|
|
|
|
|
students = self.bll.get_all_students()
|
|
|
|
|
|
|
|
|
|
# 创建搜索框
|
|
|
|
|
search_frame = ttk.Frame(self.content_frame)
|
|
|
|
|
search_frame.pack(fill=tk.X, pady=5)
|
|
|
|
|
|
|
|
|
|
ttk.Label(search_frame, text="搜索:").pack(side=tk.LEFT, padx=5)
|
|
|
|
|
search_var = tk.StringVar()
|
|
|
|
|
search_entry = ttk.Entry(search_frame, textvariable=search_var, width=30)
|
|
|
|
|
search_entry.pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
|
|
|
|
def search_students():
|
|
|
|
|
keyword = search_var.get()
|
|
|
|
|
if keyword:
|
|
|
|
|
search_results = []
|
|
|
|
|
search_results.extend(self.bll.search_students_by_name(keyword))
|
|
|
|
|
search_results.extend(self.bll.search_students_by_class(keyword))
|
|
|
|
|
search_results.extend(self.bll.search_students_by_major(keyword))
|
|
|
|
|
# 去重
|
|
|
|
|
unique_students = []
|
|
|
|
|
ids = set()
|
|
|
|
|
for s in search_results:
|
|
|
|
|
if s.id_card not in ids:
|
|
|
|
|
unique_students.append(s)
|
|
|
|
|
ids.add(s.id_card)
|
|
|
|
|
self.show_student_list(unique_students)
|
|
|
|
|
else:
|
|
|
|
|
self.show_student_list()
|
|
|
|
|
|
|
|
|
|
ttk.Button(search_frame, text="搜索", command=search_students).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
|
|
|
|
# 创建表格
|
|
|
|
|
columns = ("name", "id_card", "stu_id", "gender", "age", "class_name", "major")
|
|
|
|
|
tree = ttk.Treeview(self.content_frame, columns=columns, show="headings")
|
|
|
|
|
|
|
|
|
|
# 设置列标题
|
|
|
|
|
tree.heading("name", text="姓名")
|
|
|
|
|
tree.heading("id_card", text="身份证号")
|
|
|
|
|
tree.heading("stu_id", text="学号")
|
|
|
|
|
tree.heading("gender", text="性别")
|
|
|
|
|
tree.heading("age", text="年龄")
|
|
|
|
|
tree.heading("class_name", text="班级")
|
|
|
|
|
tree.heading("major", text="专业")
|
|
|
|
|
|
|
|
|
|
# 设置列宽
|
|
|
|
|
tree.column("name", width=80)
|
|
|
|
|
tree.column("id_card", width=150)
|
|
|
|
|
tree.column("stu_id", width=100)
|
|
|
|
|
tree.column("gender", width=50)
|
|
|
|
|
tree.column("age", width=50)
|
|
|
|
|
tree.column("class_name", width=100)
|
|
|
|
|
tree.column("major", width=100)
|
|
|
|
|
|
|
|
|
|
# 添加数据
|
|
|
|
|
for student in students:
|
|
|
|
|
gender_text = "男" if student.gender else "女" if student.gender is not None else ""
|
|
|
|
|
age_text = str(student.age) if student.age is not None else ""
|
|
|
|
|
tree.insert("", tk.END, values=(
|
|
|
|
|
student.name,
|
|
|
|
|
student.id_card,
|
|
|
|
|
student.stu_id,
|
|
|
|
|
gender_text,
|
|
|
|
|
age_text,
|
|
|
|
|
student.class_name or "",
|
|
|
|
|
student.major or ""
|
|
|
|
|
))
|
|
|
|
|
|
|
|
|
|
# 添加滚动条
|
|
|
|
|
scrollbar = ttk.Scrollbar(self.content_frame, orient=tk.VERTICAL, command=tree.yview)
|
|
|
|
|
tree.configure(yscroll=scrollbar.set)
|
|
|
|
|
|
|
|
|
|
# 布局
|
|
|
|
|
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
|
|
|
tree.pack(fill=tk.BOTH, expand=True)
|
|
|
|
|
|
|
|
|
|
# 双击查看详情
|
|
|
|
|
def on_double_click(event):
|
|
|
|
|
item = tree.selection()
|
|
|
|
|
if item:
|
|
|
|
|
id_card = tree.item(item[0])["values"][1] # 身份证号在第二列
|
|
|
|
|
student = self.bll.get_student_by_id(id_card)
|
|
|
|
|
if student:
|
|
|
|
|
self.show_student_details(student)
|
|
|
|
|
|
|
|
|
|
tree.bind("<Double-1>", on_double_click)
|
|
|
|
|
|
|
|
|
|
def show_student_details(self, student: Student):
|
|
|
|
|
"""显示学生详情"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
# 创建详情表单
|
|
|
|
|
detail_frame = ttk.LabelFrame(self.content_frame, text=f"学生详情 - {student.name}")
|
|
|
|
|
detail_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 左侧信息
|
|
|
|
|
left_frame = ttk.Frame(detail_frame)
|
|
|
|
|
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 右侧信息
|
|
|
|
|
right_frame = ttk.Frame(detail_frame)
|
|
|
|
|
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 基本信息
|
|
|
|
|
info_items = [
|
|
|
|
|
("姓名:", student.name),
|
|
|
|
|
("身份证号:", student.id_card),
|
|
|
|
|
("学号:", student.stu_id),
|
|
|
|
|
("性别:", "男" if student.gender else "女" if student.gender is not None else "未设置"),
|
|
|
|
|
("年龄:", str(student.age) if student.age is not None else "未知"),
|
|
|
|
|
("出生日期:", str(student.birthday) if student.birthday else "未知"),
|
|
|
|
|
("身高:", f"{student.height} cm" if student.height else "未设置"),
|
|
|
|
|
("体重:", f"{student.weight} kg" if student.weight else "未设置"),
|
|
|
|
|
("入学日期:", str(student.enrollment_date) if student.enrollment_date else "未设置"),
|
|
|
|
|
("班级:", student.class_name if student.class_name else "未设置"),
|
|
|
|
|
("专业:", student.major if student.major else "未设置"),
|
|
|
|
|
("电子邮箱:", student.email if student.email else "未设置"),
|
|
|
|
|
("联系电话:", student.phone if student.phone else "未设置")
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 显示信息
|
|
|
|
|
for i, (label, value) in enumerate(info_items):
|
|
|
|
|
if i < len(info_items) // 2: # 前半部分放左侧
|
|
|
|
|
ttk.Label(left_frame, text=label).grid(row=i, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
ttk.Label(left_frame, text=value).grid(row=i, column=1, sticky=tk.W, pady=5)
|
|
|
|
|
else: # 后半部分放右侧
|
|
|
|
|
ttk.Label(right_frame, text=label).grid(row=i - len(info_items) // 2, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
ttk.Label(right_frame, text=value).grid(row=i - len(info_items) // 2, column=1, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
# 返回按钮
|
|
|
|
|
ttk.Button(self.content_frame, text="返回", command=self.show_student_list).pack(pady=10)
|
|
|
|
|
|
|
|
|
|
def show_add_student(self):
|
|
|
|
|
"""显示添加学生表单"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
form_frame = ttk.LabelFrame(self.content_frame, text="添加学生")
|
|
|
|
|
form_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 表单字段
|
|
|
|
|
fields = {
|
|
|
|
|
"name": tk.StringVar(),
|
|
|
|
|
"id_card": tk.StringVar(),
|
|
|
|
|
"stu_id": tk.StringVar(),
|
|
|
|
|
"gender": tk.StringVar(value="未设置"),
|
|
|
|
|
"height": tk.StringVar(),
|
|
|
|
|
"weight": tk.StringVar(),
|
|
|
|
|
"enrollment_date": tk.StringVar(),
|
|
|
|
|
"class_name": tk.StringVar(),
|
|
|
|
|
"major": tk.StringVar(),
|
|
|
|
|
"email": tk.StringVar(),
|
|
|
|
|
"phone": tk.StringVar()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 错误提示标签和输入框引用
|
|
|
|
|
error_labels = {}
|
|
|
|
|
entries = {}
|
|
|
|
|
|
|
|
|
|
# 验证函数
|
|
|
|
|
def validate_field(field_name, event=None):
|
|
|
|
|
value = fields[field_name].get()
|
|
|
|
|
error_msg = ""
|
|
|
|
|
# 根据字段类型进行验证
|
|
|
|
|
if field_name == "name":
|
|
|
|
|
if not value:
|
|
|
|
|
error_msg = "姓名不能为空"
|
|
|
|
|
|
|
|
|
|
elif field_name == "id_card":
|
|
|
|
|
if not value:
|
|
|
|
|
error_msg = "身份证号不能为空"
|
|
|
|
|
elif len(value) != 18:
|
|
|
|
|
error_msg = "身份证号必须为18位"
|
|
|
|
|
elif not value[:17].isdigit():
|
|
|
|
|
error_msg = "前17位必须是数字"
|
|
|
|
|
elif not (value[17].isdigit() or value[17].upper() == 'X'):
|
|
|
|
|
error_msg = "最后一位必须是数字或X"
|
|
|
|
|
|
|
|
|
|
elif field_name == "stu_id":
|
|
|
|
|
if not value:
|
|
|
|
|
error_msg = "学号不能为空"
|
|
|
|
|
|
|
|
|
|
elif field_name == "height":
|
|
|
|
|
if value and not value.isdigit():
|
|
|
|
|
error_msg = "请输入数字"
|
|
|
|
|
|
|
|
|
|
elif field_name == "weight":
|
|
|
|
|
if value and not value.replace('.', '', 1).isdigit():
|
|
|
|
|
error_msg = "请输入数字"
|
|
|
|
|
|
|
|
|
|
elif field_name == "enrollment_date":
|
|
|
|
|
if value:
|
|
|
|
|
try:
|
|
|
|
|
datetime.strptime(value, '%Y-%m-%d')
|
|
|
|
|
except ValueError:
|
|
|
|
|
error_msg = "格式应为YYYY-MM-DD"
|
|
|
|
|
|
|
|
|
|
elif field_name == "email":
|
|
|
|
|
if value and not re.match(r"[^@]+@[^@]+\.[^@]+", value):
|
|
|
|
|
error_msg = "邮箱格式不正确"
|
|
|
|
|
|
|
|
|
|
elif field_name == "phone":
|
|
|
|
|
if value and not re.match(r"^1[3-9]\d{9}$", value):
|
|
|
|
|
error_msg = "手机号格式不正确"
|
|
|
|
|
|
|
|
|
|
# 更新错误标签和输入框样式
|
|
|
|
|
if error_labels.get(field_name):
|
|
|
|
|
error_labels[field_name].config(text=error_msg, foreground="red")
|
|
|
|
|
|
|
|
|
|
if entries.get(field_name):
|
|
|
|
|
if error_msg:
|
|
|
|
|
entries[field_name].configure(style="Error.TEntry")
|
|
|
|
|
else:
|
|
|
|
|
entries[field_name].configure(style="TEntry")
|
|
|
|
|
|
|
|
|
|
return error_msg
|
|
|
|
|
|
|
|
|
|
# 创建表单
|
|
|
|
|
row = 0
|
|
|
|
|
for field, var in fields.items():
|
|
|
|
|
# 字段标签
|
|
|
|
|
label_text = {
|
|
|
|
|
"name": "姓名:",
|
|
|
|
|
"id_card": "身份证号:",
|
|
|
|
|
"stu_id": "学号:",
|
|
|
|
|
"height": "身高 (cm):",
|
|
|
|
|
"weight": "体重 (kg):",
|
|
|
|
|
"enrollment_date": "入学日期 (YYYY-MM-DD):",
|
|
|
|
|
"class_name": "班级:",
|
|
|
|
|
"major": "专业:",
|
|
|
|
|
"email": "电子邮箱:",
|
|
|
|
|
"phone": "联系电话:"
|
|
|
|
|
}.get(field, f"{field}:")
|
|
|
|
|
|
|
|
|
|
ttk.Label(form_frame, text=label_text).grid(row=row, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
if field == "gender":
|
|
|
|
|
# 性别选择(单选按钮)
|
|
|
|
|
gender_frame = ttk.Frame(form_frame)
|
|
|
|
|
gender_frame.grid(row=row, column=1, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
for i, option in enumerate(["男", "女", "未设置"]):
|
|
|
|
|
ttk.Radiobutton(gender_frame, text=option, variable=var, value=option).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
|
|
|
|
# 性别不需要错误提示
|
|
|
|
|
error_labels[field] = ttk.Label(form_frame, text="")
|
|
|
|
|
error_labels[field].grid(row=row, column=2, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
# 创建输入框
|
|
|
|
|
entry = ttk.Entry(form_frame, textvariable=var)
|
|
|
|
|
entry.grid(row=row, column=1, sticky=tk.EW, pady=5)
|
|
|
|
|
entries[field] = entry
|
|
|
|
|
|
|
|
|
|
# 绑定验证事件
|
|
|
|
|
var.trace_add("write", lambda name, index, mode, f=field: validate_field(f))
|
|
|
|
|
entry.bind("<FocusOut>", lambda event, f=field: validate_field(f, event))
|
|
|
|
|
|
|
|
|
|
# 添加错误提示标签
|
|
|
|
|
error_labels[field] = ttk.Label(form_frame, text="", foreground="red")
|
|
|
|
|
error_labels[field].grid(row=row, column=2, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
# 添加标准说明
|
|
|
|
|
standard_text = {
|
|
|
|
|
"id_card": "(18位数字,最后一位可为X)",
|
|
|
|
|
"enrollment_date": "(例如: 2023-09-01)",
|
|
|
|
|
"email": "(例如: example@mail.com)",
|
|
|
|
|
"phone": "(例如: 13800138000)"
|
|
|
|
|
}.get(field, "")
|
|
|
|
|
|
|
|
|
|
if standard_text:
|
|
|
|
|
ttk.Label(form_frame, text=standard_text, foreground="gray").grid(row=row, column=3, sticky=tk.W,
|
|
|
|
|
pady=5)
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
# 设置列权重,使输入框可以拉伸
|
|
|
|
|
form_frame.columnconfigure(1, weight=1)
|
|
|
|
|
|
|
|
|
|
# 定义错误样式
|
|
|
|
|
style = ttk.Style()
|
|
|
|
|
style.configure("Error.TEntry", fieldbackground="#ffe6e6", foreground="#000000")
|
|
|
|
|
|
|
|
|
|
# 提交按钮
|
|
|
|
|
def submit_form():
|
|
|
|
|
# 验证所有字段
|
|
|
|
|
errors = {}
|
|
|
|
|
for field in fields:
|
|
|
|
|
if field != "gender": # 性别不需要验证
|
|
|
|
|
error = validate_field(field)
|
|
|
|
|
if error:
|
|
|
|
|
errors[field] = error
|
|
|
|
|
|
|
|
|
|
if errors:
|
|
|
|
|
messagebox.showerror("输入错误", "请修正以下错误:\n\n" + "\n".join(errors.values()))
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 获取表单数据
|
|
|
|
|
name = fields["name"].get()
|
|
|
|
|
id_card = fields["id_card"].get()
|
|
|
|
|
stu_id = fields["stu_id"].get()
|
|
|
|
|
|
|
|
|
|
gender_text = fields["gender"].get()
|
|
|
|
|
gender = None
|
|
|
|
|
if gender_text == "男":
|
|
|
|
|
gender = True
|
|
|
|
|
elif gender_text == "女":
|
|
|
|
|
gender = False
|
|
|
|
|
|
|
|
|
|
height = fields["height"].get()
|
|
|
|
|
height = int(height) if height else None
|
|
|
|
|
|
|
|
|
|
weight = fields["weight"].get()
|
|
|
|
|
weight = float(weight) if weight else None
|
|
|
|
|
|
|
|
|
|
enrollment_date = fields["enrollment_date"].get()
|
|
|
|
|
enrollment_date = datetime.strptime(enrollment_date, '%Y-%m-%d').date() if enrollment_date else None
|
|
|
|
|
|
|
|
|
|
class_name = fields["class_name"].get()
|
|
|
|
|
major = fields["major"].get()
|
|
|
|
|
email = fields["email"].get()
|
|
|
|
|
phone = fields["phone"].get()
|
|
|
|
|
|
|
|
|
|
# 创建学生对象
|
|
|
|
|
student = Student(
|
|
|
|
|
name=name,
|
|
|
|
|
id_card=id_card,
|
|
|
|
|
stu_id=stu_id,
|
|
|
|
|
gender=gender,
|
|
|
|
|
height=height,
|
|
|
|
|
weight=weight,
|
|
|
|
|
enrollment_date=enrollment_date,
|
|
|
|
|
class_name=class_name,
|
|
|
|
|
major=major,
|
|
|
|
|
email=email,
|
|
|
|
|
phone=phone
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 添加学生
|
|
|
|
|
success, message = self.bll.add_student(student)
|
|
|
|
|
if success:
|
|
|
|
|
messagebox.showinfo("成功", "学生添加成功!")
|
|
|
|
|
self.show_student_list()
|
|
|
|
|
else:
|
|
|
|
|
messagebox.showerror("错误", f"添加失败: {message}")
|
|
|
|
|
|
|
|
|
|
# 创建按钮
|
|
|
|
|
button_frame = ttk.Frame(self.content_frame)
|
|
|
|
|
button_frame.pack(fill=tk.X, pady=10)
|
|
|
|
|
|
|
|
|
|
ttk.Button(button_frame, text="提交", command=submit_form).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
ttk.Button(button_frame, text="返回", command=self.show_student_list).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
|
|
|
|
def show_update_student(self):
|
|
|
|
|
"""显示修改学生表单"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
ttk.Label(self.content_frame, text="请输入要修改的学生身份证号:").pack(pady=10)
|
|
|
|
|
|
|
|
|
|
id_card_var = tk.StringVar()
|
|
|
|
|
ttk.Entry(self.content_frame, textvariable=id_card_var).pack(fill=tk.X, padx=20, pady=5)
|
|
|
|
|
|
|
|
|
|
def search_student():
|
|
|
|
|
id_card = id_card_var.get()
|
|
|
|
|
student = self.bll.get_student_by_id(id_card)
|
|
|
|
|
|
|
|
|
|
if student:
|
|
|
|
|
self._show_update_form(student)
|
|
|
|
|
else:
|
|
|
|
|
messagebox.showerror("错误", "未找到该学生!")
|
|
|
|
|
|
|
|
|
|
ttk.Button(self.content_frame, text="查找学生", command=search_student).pack(pady=10)
|
|
|
|
|
ttk.Button(self.content_frame, text="返回", command=self.show_student_list).pack(pady=5)
|
|
|
|
|
|
|
|
|
|
def _show_update_form(self, student: Student):
|
|
|
|
|
"""显示实际的修改表单"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
form_frame = ttk.LabelFrame(self.content_frame, text=f"修改学生信息 - {student.name}")
|
|
|
|
|
form_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 表单字段
|
|
|
|
|
fields = {
|
|
|
|
|
"name": tk.StringVar(value=student.name),
|
|
|
|
|
"id_card": tk.StringVar(value=student.id_card),
|
|
|
|
|
"stu_id": tk.StringVar(value=student.stu_id),
|
|
|
|
|
"gender": tk.StringVar(value="男" if student.gender else "女" if student.gender is not None else "未设置"),
|
|
|
|
|
"height": tk.StringVar(value=str(student.height) if student.height is not None else ""),
|
|
|
|
|
"weight": tk.StringVar(value=str(student.weight) if student.weight is not None else ""),
|
|
|
|
|
"enrollment_date": tk.StringVar(value=str(student.enrollment_date) if student.enrollment_date else ""),
|
|
|
|
|
"class_name": tk.StringVar(value=student.class_name or ""),
|
|
|
|
|
"major": tk.StringVar(value=student.major or ""),
|
|
|
|
|
"email": tk.StringVar(value=student.email or ""),
|
|
|
|
|
"phone": tk.StringVar(value=student.phone or "")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# 性别选项
|
|
|
|
|
gender_options = ["男", "女", "未设置"]
|
|
|
|
|
|
|
|
|
|
# 创建表单
|
|
|
|
|
row = 0
|
|
|
|
|
for field, var in fields.items():
|
|
|
|
|
if field == "id_card": # 身份证号不可修改
|
|
|
|
|
ttk.Label(form_frame, text="身份证号:").grid(row=row, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
ttk.Label(form_frame, text=student.id_card).grid(row=row, column=1, sticky=tk.W, pady=5)
|
|
|
|
|
elif field == "gender":
|
|
|
|
|
ttk.Label(form_frame, text="性别:").grid(row=row, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
gender_frame = ttk.Frame(form_frame)
|
|
|
|
|
gender_frame.grid(row=row, column=1, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
for i, option in enumerate(gender_options):
|
|
|
|
|
ttk.Radiobutton(gender_frame, text=option, variable=var, value=option).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
else:
|
|
|
|
|
label_text = {
|
|
|
|
|
"name": "姓名:",
|
|
|
|
|
"stu_id": "学号:",
|
|
|
|
|
"height": "身高 (cm):",
|
|
|
|
|
"weight": "体重 (kg):",
|
|
|
|
|
"enrollment_date": "入学日期 (YYYY-MM-DD):",
|
|
|
|
|
"class_name": "班级:",
|
|
|
|
|
"major": "专业:",
|
|
|
|
|
"email": "电子邮箱:",
|
|
|
|
|
"phone": "联系电话:"
|
|
|
|
|
}.get(field, f"{field}:")
|
|
|
|
|
|
|
|
|
|
ttk.Label(form_frame, text=label_text).grid(row=row, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
ttk.Entry(form_frame, textvariable=var).grid(row=row, column=1, sticky=tk.EW, pady=5)
|
|
|
|
|
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
# 设置列权重,使输入框可以拉伸
|
|
|
|
|
form_frame.columnconfigure(1, weight=1)
|
|
|
|
|
|
|
|
|
|
# 提交按钮
|
|
|
|
|
def submit_form():
|
|
|
|
|
# 获取表单数据
|
|
|
|
|
name = fields["name"].get()
|
|
|
|
|
stu_id = fields["stu_id"].get()
|
|
|
|
|
|
|
|
|
|
gender_text = fields["gender"].get()
|
|
|
|
|
gender = None
|
|
|
|
|
if gender_text == "男":
|
|
|
|
|
gender = True
|
|
|
|
|
elif gender_text == "女":
|
|
|
|
|
gender = False
|
|
|
|
|
|
|
|
|
|
height = fields["height"].get()
|
|
|
|
|
height = int(height) if height else None
|
|
|
|
|
|
|
|
|
|
weight = fields["weight"].get()
|
|
|
|
|
weight = float(weight) if weight else None
|
|
|
|
|
|
|
|
|
|
enrollment_date = fields["enrollment_date"].get()
|
|
|
|
|
enrollment_date = datetime.strptime(enrollment_date, '%Y-%m-%d').date() if enrollment_date else None
|
|
|
|
|
|
|
|
|
|
class_name = fields["class_name"].get()
|
|
|
|
|
major = fields["major"].get()
|
|
|
|
|
email = fields["email"].get()
|
|
|
|
|
phone = fields["phone"].get()
|
|
|
|
|
|
|
|
|
|
# 更新学生对象
|
|
|
|
|
student.name = name
|
|
|
|
|
student.stu_id = stu_id
|
|
|
|
|
student.gender = gender
|
|
|
|
|
student.height = height
|
|
|
|
|
student.weight = weight
|
|
|
|
|
student.enrollment_date = enrollment_date
|
|
|
|
|
student.class_name = class_name
|
|
|
|
|
student.major = major
|
|
|
|
|
student.email = email
|
|
|
|
|
student.phone = phone
|
|
|
|
|
|
|
|
|
|
# 更新学生
|
|
|
|
|
success, message = self.bll.update_student(student)
|
|
|
|
|
if success:
|
|
|
|
|
messagebox.showinfo("成功", "学生信息更新成功!")
|
|
|
|
|
self.show_student_list()
|
|
|
|
|
else:
|
|
|
|
|
messagebox.showerror("错误", f"更新失败: {message}")
|
|
|
|
|
|
|
|
|
|
button_frame = ttk.Frame(self.content_frame)
|
|
|
|
|
button_frame.pack(fill=tk.X, pady=10)
|
|
|
|
|
|
|
|
|
|
ttk.Button(button_frame, text="提交", command=submit_form).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
ttk.Button(button_frame, text="返回", command=self.show_student_list).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
|
|
|
|
def show_delete_student(self):
|
|
|
|
|
"""显示删除学生界面"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
ttk.Label(self.content_frame, text="请输入要删除的学生身份证号:").pack(pady=10)
|
|
|
|
|
|
|
|
|
|
id_card_var = tk.StringVar()
|
|
|
|
|
ttk.Entry(self.content_frame, textvariable=id_card_var).pack(fill=tk.X, padx=20, pady=5)
|
|
|
|
|
|
|
|
|
|
def delete_student():
|
|
|
|
|
id_card = id_card_var.get()
|
|
|
|
|
student = self.bll.get_student_by_id(id_card)
|
|
|
|
|
|
|
|
|
|
if not student:
|
|
|
|
|
messagebox.showerror("错误", "未找到该学生!")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
confirm = messagebox.askyesno("确认", f"确定要删除学生 {student.name} 吗?")
|
|
|
|
|
if confirm:
|
|
|
|
|
if self.bll.delete_student_by_id(id_card):
|
|
|
|
|
messagebox.showinfo("成功", "学生删除成功!")
|
|
|
|
|
self.show_student_list()
|
|
|
|
|
else:
|
|
|
|
|
messagebox.showerror("错误", "删除失败!")
|
|
|
|
|
|
|
|
|
|
button_frame = ttk.Frame(self.content_frame)
|
|
|
|
|
button_frame.pack(fill=tk.X, pady=10)
|
|
|
|
|
|
|
|
|
|
ttk.Button(button_frame, text="删除", command=delete_student).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
ttk.Button(button_frame, text="返回", command=self.show_student_list).pack(side=tk.LEFT, padx=5)
|
|
|
|
|
|
|
|
|
|
def show_search_student(self):
|
|
|
|
|
"""显示查询学生界面"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
search_frame = ttk.LabelFrame(self.content_frame, text="查询学生")
|
|
|
|
|
search_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 查询方式
|
|
|
|
|
search_type = tk.StringVar(value="name")
|
|
|
|
|
|
|
|
|
|
ttk.Radiobutton(search_frame, text="按姓名", variable=search_type, value="name").grid(row=0, column=0,
|
|
|
|
|
sticky=tk.W, pady=5)
|
|
|
|
|
ttk.Radiobutton(search_frame, text="按班级", variable=search_type, value="class").grid(row=0, column=1,
|
|
|
|
|
sticky=tk.W, pady=5)
|
|
|
|
|
ttk.Radiobutton(search_frame, text="按专业", variable=search_type, value="major").grid(row=0, column=2,
|
|
|
|
|
sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
# 查询关键词
|
|
|
|
|
ttk.Label(search_frame, text="关键词:").grid(row=1, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
keyword_var = tk.StringVar()
|
|
|
|
|
ttk.Entry(search_frame, textvariable=keyword_var).grid(row=1, column=1, sticky=tk.EW, pady=5)
|
|
|
|
|
|
|
|
|
|
search_frame.columnconfigure(1, weight=1)
|
|
|
|
|
|
|
|
|
|
def perform_search():
|
|
|
|
|
keyword = keyword_var.get()
|
|
|
|
|
if not keyword:
|
|
|
|
|
messagebox.showwarning("警告", "请输入关键词!")
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
search_method = search_type.get()
|
|
|
|
|
if search_method == "name":
|
|
|
|
|
students = self.bll.search_students_by_name(keyword)
|
|
|
|
|
elif search_method == "class":
|
|
|
|
|
students = self.bll.search_students_by_class(keyword)
|
|
|
|
|
else: # major
|
|
|
|
|
students = self.bll.search_students_by_major(keyword)
|
|
|
|
|
|
|
|
|
|
if not students:
|
|
|
|
|
messagebox.showinfo("提示", "未找到匹配的学生!")
|
|
|
|
|
self.show_student_list()
|
|
|
|
|
else:
|
|
|
|
|
self.show_student_list(students)
|
|
|
|
|
|
|
|
|
|
ttk.Button(search_frame, text="查询", command=perform_search).grid(row=1, column=2, padx=5, pady=5)
|
|
|
|
|
|
|
|
|
|
ttk.Button(self.content_frame, text="返回", command=self.show_student_list).pack(pady=10)
|
|
|
|
|
|
|
|
|
|
def show_statistics(self):
|
|
|
|
|
"""显示统计信息"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
stats_frame = ttk.LabelFrame(self.content_frame, text="统计信息")
|
|
|
|
|
stats_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
|
|
|
|
|
|
|
|
|
# 学生总数
|
|
|
|
|
total_students = self.bll.get_total_students()
|
|
|
|
|
ttk.Label(stats_frame, text=f"学生总数: {total_students}").grid(row=0, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
# 性别比例
|
|
|
|
|
gender_ratio = self.bll.get_gender_ratio()
|
|
|
|
|
ttk.Label(stats_frame, text=f"男生人数: {gender_ratio['male']} ({gender_ratio['male_ratio']:.2%})").grid(row=1,
|
|
|
|
|
column=0,
|
|
|
|
|
sticky=tk.W,
|
|
|
|
|
pady=5)
|
|
|
|
|
ttk.Label(stats_frame, text=f"女生人数: {gender_ratio['female']} ({gender_ratio['female_ratio']:.2%})").grid(
|
|
|
|
|
row=2, column=0, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
# 各专业人数
|
|
|
|
|
major_frame = ttk.LabelFrame(stats_frame, text="各专业人数")
|
|
|
|
|
major_frame.grid(row=3, column=0, sticky=tk.NSEW, pady=10)
|
|
|
|
|
|
|
|
|
|
major_count = self.bll.get_students_by_major()
|
|
|
|
|
row = 0
|
|
|
|
|
for major, count in major_count.items():
|
|
|
|
|
ttk.Label(major_frame, text=f"{major}: {count} 人").grid(row=row, column=0, sticky=tk.W, pady=2)
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
# 平均身高体重
|
|
|
|
|
avg_height = self.bll.calculate_average_height()
|
|
|
|
|
avg_weight = self.bll.calculate_average_weight()
|
|
|
|
|
|
|
|
|
|
ttk.Label(stats_frame, text=f"平均身高: {avg_height:.2f} cm").grid(row=0, column=1, sticky=tk.W, pady=5)
|
|
|
|
|
ttk.Label(stats_frame, text=f"平均体重: {avg_weight:.2f} kg").grid(row=1, column=1, sticky=tk.W, pady=5)
|
|
|
|
|
|
|
|
|
|
# 设置列权重
|
|
|
|
|
stats_frame.columnconfigure(0, weight=1)
|
|
|
|
|
stats_frame.columnconfigure(1, weight=1)
|
|
|
|
|
stats_frame.rowconfigure(3, weight=1)
|
|
|
|
|
|
|
|
|
|
ttk.Button(self.content_frame, text="返回", command=self.show_student_list).pack(pady=10)
|
|
|
|
|
|
|
|
|
|
def show_import_export(self):
|
|
|
|
|
"""显示数据导入导出界面"""
|
|
|
|
|
self.clear_content_frame()
|
|
|
|
|
|
|
|
|
|
ttk.Label(self.content_frame, text="数据导入导出功能开发中...").pack(pady=20)
|
|
|
|
|
ttk.Button(self.content_frame, text="返回", command=self.show_student_list).pack(pady=10)
|