import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import json
import os
import csv
from datetime import datetime
class StudentManagementSystem:
def __init__(self, root):
self.root = root
self.root.title("学生信息管理系统 v1.0")
self.root.geometry("1000x600")
self.root.configure(bg="#f0f2f5")
# 初始化数据
self.students = []
self.current_file = None
self.load_data()
# 创建样式
self.create_styles()
# 创建界面组件
self.create_widgets()
# 加载数据到表格
self.load_table_data()
def create_styles(self):
"""创建自定义样式"""
style = ttk.Style()
# 配置Treeview样式
style.configure("Treeview",
font=("Arial", 11),
rowheight=25,
background="#FFFFFF",
fieldbackground="#FFFFFF")
style.configure("Treeview.Heading",
font=("Arial", 12, "bold"),
background="#4A90E2",
foreground="white")
# 配置按钮样式
style.configure("TButton",
font=("Arial", 11),
padding=6)
style.map("TButton",
background=[("active", "#4A90E2"), ("!active", "#5A9AE6")],
foreground=[("active", "white"), ("!active", "white")])
# 配置标签样式
style.configure("Title.TLabel",
font=("Arial", 16, "bold"),
background="#f0f2f5",
foreground="#333333")
style.configure("Subtitle.TLabel",
font=("Arial", 12),
background="#f0f2f5",
foreground="#666666")
def create_widgets(self):
"""创建界面组件"""
# 创建顶部标题
header_frame = ttk.Frame(self.root)
header_frame.pack(fill="x", padx=20, pady=10)
title_label = ttk.Label(header_frame,
text="学生信息管理系统",
style="Title.TLabel")
title_label.pack(side="left")
subtitle_label = ttk.Label(header_frame,
text="管理学生信息,支持增删改查操作",
style="Subtitle.TLabel")
subtitle_label.pack(side="left", padx=10)
# 创建工具栏
toolbar_frame = ttk.Frame(self.root)
toolbar_frame.pack(fill="x", padx=20, pady=5)
self.create_toolbar_buttons(toolbar_frame)
# 创建搜索区域
search_frame = ttk.Frame(self.root)
search_frame.pack(fill="x", padx=20, pady=10)
self.create_search_area(search_frame)
# 创建学生表格
table_frame = ttk.Frame(self.root)
table_frame.pack(fill="both", expand=True, padx=20, pady=5)
self.create_student_table(table_frame)
# 状态栏
self.status_var = tk.StringVar(value="就绪 | 学生总数: 0")
status_bar = ttk.Label(self.root,
textvariable=self.status_var,
relief="sunken",
anchor="w",
padding=(10, 5))
status_bar.pack(side="bottom", fill="x")
def create_toolbar_buttons(self, parent):
"""创建工具栏按钮"""
button_data = [
("添加学生", self.add_student, "#27AE60"),
("编辑学生", self.edit_student, "#F39C12"),
("删除学生", self.delete_student, "#E74C3C"),
("刷新", self.refresh_data, "#3498DB"),
("保存", self.save_data, "#2ECC71"),
("另存为", self.save_as, "#9B59B6"),
("导入", self.import_data, "#1ABC9C"),
("导出", self.export_data, "#16A085"),
("关于", self.show_about, "#7F8C8D"),
]
for text, command, color in button_data:
btn = ttk.Button(parent,
text=text,
command=command,
style="TButton")
btn.configure(style="TButton")
btn.pack(side="left", padx=5)
def create_search_area(self, parent):
"""创建搜索区域"""
ttk.Label(parent, text="搜索:", font=("Arial", 11)).pack(side="left", padx=(0, 5))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(parent,
textvariable=self.search_var,
width=30,
font=("Arial", 11))
search_entry.pack(side="left", padx=5)
search_entry.bind("<KeyRelease>", self.search_students)
search_btn = ttk.Button(parent,
text="搜索",
command=self.search_students,
style="TButton")
search_btn.pack(side="left", padx=5)
clear_btn = ttk.Button(parent,
text="清除",
command=self.clear_search,
style="TButton")
clear_btn.pack(side="left", padx=5)
# 搜索选项
ttk.Label(parent, text="按:", font=("Arial", 11)).pack(side="left", padx=(20, 5))
self.search_by_var = tk.StringVar(value="姓名")
search_options = ttk.Combobox(parent,
textvariable=self.search_by_var,
values=["学号", "姓名", "班级", "专业"],
state="readonly",
width=8)
search_options.pack(side="left", padx=5)
def create_student_table(self, parent):
"""创建学生表格"""
# 创建滚动条
scroll_y = ttk.Scrollbar(parent, orient="vertical")
scroll_x = ttk.Scrollbar(parent, orient="horizontal")
# 创建表格
columns = ("student_id", "name", "gender", "age", "class", "major", "phone", "email")
self.tree = ttk.Treeview(parent,
columns=columns,
show="headings",
yscrollcommand=scroll_y.set,
xscrollcommand=scroll_x.set,
selectmode="browse")
# 配置列
self.tree.column("student_id", width=100, anchor="center")
self.tree.column("name", width=100, anchor="center")
self.tree.column("gender", width=80, anchor="center")
self.tree.column("age", width=60, anchor="center")
self.tree.column("class", width=120, anchor="center")
self.tree.column("major", width=150, anchor="center")
self.tree.column("phone", width=120, anchor="center")
self.tree.column("email", width=180, anchor="center")
# 设置列标题
self.tree.heading("student_id", text="学号")
self.tree.heading("name", text="姓名")
self.tree.heading("gender", text="性别")
self.tree.heading("age", text="年龄")
self.tree.heading("class", text="班级")
self.tree.heading("major", text="专业")
self.tree.heading("phone", text="电话")
self.tree.heading("email", text="邮箱")
# 配置滚动条
scroll_y.config(command=self.tree.yview)
scroll_x.config(command=self.tree.xview)
# 布局
self.tree.pack(side="left", fill="both", expand=True)
scroll_y.pack(side="right", fill="y")
scroll_x.pack(side="bottom", fill="x")
# 绑定双击事件
self.tree.bind("<Double-1>", self.on_row_double_click)
def load_data(self):
"""尝试加载上次打开的数据"""
try:
if os.path.exists("last_session.json"):
with open("last_session.json", "r", encoding="utf-8") as f:
data = json.load(f)
self.students = data.get("students", [])
self.current_file = data.get("current_file")
except:
self.students = []
self.current_file = None
def save_session(self):
"""保存当前会话信息"""
session_data = {
"students": self.students,
"current_file": self.current_file
}
with open("last_session.json", "w", encoding="utf-8") as f:
json.dump(session_data, f, ensure_ascii=False, indent=2)
def load_table_data(self):
"""加载数据到表格"""
# 清空表格
for row in self.tree.get_children():
self.tree.delete(row)
# 添加数据
for student in self.students:
self.tree.insert("", "end", values=(
student.get("student_id", ""),
student.get("name", ""),
student.get("gender", ""),
student.get("age", ""),
student.get("class", ""),
student.get("major", ""),
student.get("phone", ""),
student.get("email", "")
))
# 更新状态栏
self.status_var.set(f"就绪 | 学生总数: {len(self.students)}")
def add_student(self):
"""打开添加学生对话框"""
dialog = tk.Toplevel(self.root)
dialog.title("添加学生")
dialog.geometry("400x500")
dialog.transient(self.root)
dialog.grab_set()
# 表单字段
fields = [
("学号*", "student_id"),
("姓名*", "name"),
("性别", "gender"),
("年龄", "age"),
("班级", "class"),
("专业", "major"),
("电话", "phone"),
("邮箱", "email")
]
entries = {}
for i, (label_text, field_name) in enumerate(fields):
frame = ttk.Frame(dialog)
frame.pack(fill="x", padx=20, pady=5)
label = ttk.Label(frame, text=label_text, width=10, anchor="e")
label.pack(side="left", padx=5)
entry = ttk.Entry(frame, width=30)
entry.pack(side="left", fill="x", expand=True, padx=5)
entries[field_name] = entry
# 性别选项
gender_frame = ttk.Frame(dialog)
gender_frame.pack(fill="x", padx=20, pady=5)
ttk.Label(gender_frame, text="性别", width=10, anchor="e").pack(side="left", padx=5)
gender_var = tk.StringVar(value="男")
ttk.Radiobutton(gender_frame, text="男", variable=gender_var, value="男").pack(side="left", padx=5)
ttk.Radiobutton(gender_frame, text="女", variable=gender_var, value="女").pack(side="left", padx=5)
entries["gender"] = gender_var
# 按钮区域
button_frame = ttk.Frame(dialog)
button_frame.pack(fill="x", padx=20, pady=20)
def save():
student_data = {}
for field, entry in entries.items():
if isinstance(entry, tk.Entry):
value = entry.get().strip()
else: # StringVar
value = entry.get()
student_data[field] = value
# 验证必填字段
if not student_data.get("student_id") or not student_data.get("name"):
messagebox.showerror("错误", "学号和姓名是必填项!")
return
# 检查学号是否重复
for s in self.students:
if s["student_id"] == student_data["student_id"]:
messagebox.showerror("错误", "该学号已存在!")
return
self.students.append(student_data)
self.load_table_data()
messagebox.showinfo("成功", "学生添加成功!")
dialog.destroy()
ttk.Button(button_frame, text="保存", command=save, style="TButton").pack(side="right", padx=10)
ttk.Button(button_frame, text="取消", command=dialog.destroy, style="TButton").pack(side="right")
def edit_student(self):
"""编辑选中的学生"""
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择一个学生!")
return
item = selected[0]
values = self.tree.item(item, "values")
student_id = values[0]
# 找到学生数据
student = None
for s in self.students:
if s["student_id"] == student_id:
student = s
break
if not student:
messagebox.showerror("错误", "未找到学生信息!")
return
# 打开编辑对话框
dialog = tk.Toplevel(self.root)
dialog.title("编辑学生")
dialog.geometry("400x500")
dialog.transient(self.root)
dialog.grab_set()
# 表单字段
fields = [
("学号", "student_id"),
("姓名", "name"),
("性别", "gender"),
("年龄", "age"),
("班级", "class"),
("专业", "major"),
("电话", "phone"),
("邮箱", "email")
]
entries = {}
for i, (label_text, field_name) in enumerate(fields):
frame = ttk.Frame(dialog)
frame.pack(fill="x", padx=20, pady=5)
label = ttk.Label(frame, text=label_text, width=10, anchor="e")
label.pack(side="left", padx=5)
entry = ttk.Entry(frame, width=30)
entry.insert(0, student.get(field_name, ""))
entry.pack(side="left", fill="x", expand=True, padx=5)
# 学号不可编辑
if field_name == "student_id":
entry.config(state="readonly")
entries[field_name] = entry
# 性别选项
gender_frame = ttk.Frame(dialog)
gender_frame.pack(fill="x", padx=20, pady=5)
ttk.Label(gender_frame, text="性别", width=10, anchor="e").pack(side="left", padx=5)
gender_var = tk.StringVar(value=student.get("gender", "男"))
ttk.Radiobutton(gender_frame, text="男", variable=gender_var, value="男").pack(side="left", padx=5)
ttk.Radiobutton(gender_frame, text="女", variable=gender_var, value="女").pack(side="left", padx=5)
entries["gender"] = gender_var
# 按钮区域
button_frame = ttk.Frame(dialog)
button_frame.pack(fill="x", padx=20, pady=20)
def save():
updated_data = {}
for field, entry in entries.items():
if isinstance(entry, tk.Entry):
value = entry.get().strip()
else: # StringVar
value = entry.get()
updated_data[field] = value
# 验证必填字段
if not updated_data.get("student_id") or not updated_data.get("name"):
messagebox.showerror("错误", "学号和姓名是必填项!")
return
# 更新学生数据
for i, s in enumerate(self.students):
if s["student_id"] == updated_data["student_id"]:
self.students[i] = updated_data
break
self.load_table_data()
messagebox.showinfo("成功", "学生信息更新成功!")
dialog.destroy()
ttk.Button(button_frame, text="保存", command=save, style="TButton").pack(side="right", padx=10)
ttk.Button(button_frame, text="取消", command=dialog.destroy, style="TButton").pack(side="right")
def delete_student(self):
"""删除选中的学生"""
selected = self.tree.selection()
if not selected:
messagebox.showwarning("警告", "请先选择一个学生!")
return
item = selected[0]
values = self.tree.item(item, "values")
student_id = values[0]
name = values[1]
if messagebox.askyesno("确认删除", f"确定要删除学生 {name}(学号:{student_id}) 吗?"):
# 从数据中删除
self.students = [s for s in self.students if s["student_id"] != student_id]
self.load_table_data()
messagebox.showinfo("成功", "学生删除成功!")
def on_row_double_click(self, event):
"""双击行打开编辑对话框"""
self.edit_student()
def search_students(self, event=None):
"""搜索学生"""
keyword = self.search_var.get().lower()
search_by = self.search_by_var.get()
if not keyword:
self.load_table_data()
return
# 清空表格
for row in self.tree.get_children():
self.tree.delete(row)
# 添加匹配的数据
count = 0
for student in self.students:
# 根据搜索类型确定搜索字段
if search_by == "学号":
field_value = student.get("student_id", "").lower()
elif search_by == "姓名":
field_value = student.get("name", "").lower()
elif search_by == "班级":
field_value = student.get("class", "").lower()
elif search_by == "专业":
field_value = student.get("major", "").lower()
else:
field_value = ""
if keyword in field_value:
self.tree.insert("", "end", values=(
student.get("student_id", ""),
student.get("name", ""),
student.get("gender", ""),
student.get("age", ""),
student.get("class", ""),
student.get("major", ""),
student.get("phone", ""),
student.get("email", "")
))
count += 1
# 更新状态栏
self.status_var.set(f"搜索: '{keyword}' | 找到 {count} 个学生")
def clear_search(self):
"""清除搜索"""
self.search_var.set("")
self.load_table_data()
def refresh_data(self):
"""刷新数据"""
self.load_table_data()
messagebox.showinfo("刷新", "数据已刷新!")
def save_data(self):
"""保存数据到当前文件"""
if not self.current_file:
self.save_as()
return
try:
with open(self.current_file, "w", encoding="utf-8") as f:
json.dump(self.students, f, ensure_ascii=False, indent=2)
messagebox.showinfo("成功", f"数据已保存到 {self.current_file}")
self.save_session()
except Exception as e:
messagebox.showerror("错误", f"保存文件时出错: {str(e)}")
def save_as(self):
"""另存为数据文件"""
file_path = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON文件", "*.json"), ("所有文件", "*.*")],
title="保存学生数据"
)
if not file_path:
return
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(self.students, f, ensure_ascii=False, indent=2)
self.current_file = file_path
messagebox.showinfo("成功", f"数据已保存到 {file_path}")
self.save_session()
except Exception as e:
messagebox.showerror("错误", f"保存文件时出错: {str(e)}")
def import_data(self):
"""导入数据"""
file_path = filedialog.askopenfilename(
filetypes=[("JSON文件", "*.json"), ("CSV文件", "*.csv"), ("所有文件", "*.*")],
title="选择要导入的文件"
)
if not file_path:
return
try:
if file_path.endswith(".json"):
with open(file_path, "r", encoding="utf-8") as f:
new_data = json.load(f)
# 合并数据(避免重复学号)
existing_ids = {s["student_id"] for s in self.students}
for student in new_data:
if student.get("student_id") and student["student_id"] not in existing_ids:
self.students.append(student)
existing_ids.add(student["student_id"])
messagebox.showinfo("成功", f"成功导入 {len(new_data)} 条学生记录")
elif file_path.endswith(".csv"):
with open(file_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
new_data = list(reader)
# 合并数据(避免重复学号)
existing_ids = {s["student_id"] for s in self.students}
count = 0
for row in new_data:
if row.get("student_id") and row["student_id"] not in existing_ids:
# 转换数据类型
if "age" in row and row["age"]:
try:
row["age"] = int(row["age"])
except ValueError:
row["age"] = ""
self.students.append(row)
existing_ids.add(row["student_id"])
count += 1
messagebox.showinfo("成功", f"成功导入 {count} 条学生记录")
self.current_file = None
self.load_table_data()
self.save_session()
except Exception as e:
messagebox.showerror("错误", f"导入文件时出错: {str(e)}")
def export_data(self):
"""导出数据"""
if not self.students:
messagebox.showwarning("警告", "没有数据可导出!")
return
file_path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV文件", "*.csv"), ("JSON文件", "*.json"), ("所有文件", "*.*")],
title="导出学生数据"
)
if not file_path:
return
try:
if file_path.endswith(".json"):
with open(file_path, "w", encoding="utf-8") as f:
json.dump(self.students, f, ensure_ascii=False, indent=2)
elif file_path.endswith(".csv"):
fieldnames = ["student_id", "name", "gender", "age", "class", "major", "phone", "email"]
with open(file_path, "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(self.students)
messagebox.showinfo("成功", f"数据已导出到 {file_path}")
except Exception as e:
messagebox.showerror("错误", f"导出文件时出错: {str(e)}")
def show_about(self):
"""显示关于信息"""
about_text = f"""
学生信息管理系统 v1.0
功能:
- 学生信息的增删改查
- 数据导入导出
- 搜索和筛选
- 数据持久化存储
开发人员:Python课设小组
开发日期:2023-11-15
当前数据:{len(self.students)} 名学生
"""
messagebox.showinfo("关于", about_text.strip())
if __name__ == "__main__":
root = tk.Tk()
app = StudentManagementSystem(root)
root.mainloop()
把按钮字体变成黑色,美观一点