Compare commits

...

No commits in common. 'main' and 'master' have entirely different histories.
main ... master

3
.idea/.gitignore vendored

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.11" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/PythonProject.iml" filepath="$PROJECT_DIR$/.idea/PythonProject.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -1,2 +0,0 @@
# adss

@ -0,0 +1,38 @@
{
"20250001": {
"basic_info": {
"id": "20250001",
"id_card": "522427200508146476",
"name": "董忠志",
"gender": "男",
"birth_date": "2005-08-14",
"class": "5",
"admission_year": "2023"
},
"score": {
"math": 85.0,
"python": 80.0,
"english": 90.0,
"physics": 95.0,
"sum": 350.0
}
},
"20250002": {
"basic_info": {
"id": "20250002",
"id_card": "52242720040209978X",
"name": "余腾飞",
"gender": "男",
"birth_date": "2004-02-09",
"class": "5",
"admission_year": "2023"
},
"score": {
"math": 100.0,
"python": 100.0,
"english": 100.0,
"physics": 100.0,
"sum": 400.0
}
}
}

@ -0,0 +1,516 @@
import json
import os
import tkinter as tk
from tkinter import ttk, messagebox
class StudentManagementSystemGUI:
def __init__(self, root):
self.root = root
self.root.title("学生信息管理系统")
self.root.geometry("1000x700")
# 初始化数据
self.students_info = {}
self.file_path = os.path.join(os.getcwd(), 'student_data.json')
self.subject_name = {
'math': '数学',
'python': 'Python程序设计',
'english': '英语',
'physics': '物理',
'sum': '总分'
}
# 加载数据
self.load_data()
# 创建UI
self.create_widgets()
# 默认显示所有学生
self.refresh_student_list()
def load_data(self):
"""从文件加载学生数据"""
try:
if os.path.exists(self.file_path):
with open(self.file_path, 'r', encoding='utf-8') as f:
self.students_info = json.load(f)
messagebox.showinfo("成功", "学生信息已成功加载!")
else:
messagebox.showinfo("提示", "未找到数据文件,已初始化空数据库。")
except Exception as e:
messagebox.showerror("错误", f"加载数据时出错: {e}")
self.students_info = {}
def save_data(self):
"""保存学生数据到文件"""
try:
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(self.students_info, f, ensure_ascii=False, indent=4)
return True
except Exception as e:
messagebox.showerror("错误", f"保存数据时出错: {e}")
return False
def create_widgets(self):
"""创建所有UI组件"""
# 顶部按钮区域
button_frame = ttk.Frame(self.root, padding="10")
button_frame.pack(fill=tk.X)
ttk.Button(button_frame, text="添加学生", command=self.show_add_dialog).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="修改学生", command=self.show_edit_dialog).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="删除学生", command=self.delete_student).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="刷新列表", command=self.refresh_student_list).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="统计信息", command=self.show_statistics).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="按总分排序", command=self.sort_by_total_score).pack(side=tk.LEFT, padx=5)
# 搜索区域
search_frame = ttk.Frame(self.root, padding="10")
search_frame.pack(fill=tk.X)
ttk.Label(search_frame, text="搜索:").pack(side=tk.LEFT)
self.search_var = tk.StringVar()
search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=30)
search_entry.pack(side=tk.LEFT, padx=5)
search_entry.bind("<KeyRelease>", self.on_search_changed)
# 学生列表
self.tree = ttk.Treeview(self.root, columns=('id', 'name', 'gender', 'class', 'total'), show='headings')
self.tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# 设置列
self.tree.heading('id', text='学号', anchor=tk.W)
self.tree.heading('name', text='姓名', anchor=tk.W)
self.tree.heading('gender', text='性别', anchor=tk.W)
self.tree.heading('class', text='班级', anchor=tk.W)
self.tree.heading('total', text='总分', anchor=tk.W)
# 设置列宽
self.tree.column('id', width=100)
self.tree.column('name', width=100)
self.tree.column('gender', width=60)
self.tree.column('class', width=100)
self.tree.column('total', width=80)
# 详细信息区域
detail_frame = ttk.Frame(self.root, padding="10")
detail_frame.pack(fill=tk.X)
self.detail_text = tk.Text(detail_frame, height=10, state=tk.DISABLED)
self.detail_text.pack(fill=tk.BOTH, expand=True)
# 绑定选中事件
self.tree.bind("<<TreeviewSelect>>", self.on_student_selected)
def refresh_student_list(self, students=None):
"""刷新学生列表"""
self.tree.delete(*self.tree.get_children())
students_to_show = students if students else self.students_info.values()
for student in students_to_show:
self.tree.insert('', tk.END, values=(
student['basic_info']['id'],
student['basic_info']['name'],
student['basic_info']['gender'],
student['basic_info']['class'],
student['score']['sum']
))
def on_student_selected(self):
"""当选中学生时显示详细信息"""
selected_item = self.tree.selection()
if not selected_item:
return
student_id = self.tree.item(selected_item)['values'][0]
student = self.students_info[student_id]
self.detail_text.config(state=tk.NORMAL)
self.detail_text.delete(1.0, tk.END)
# 显示基本信息
self.detail_text.insert(tk.END, "=== 基本信息 ===\n")
for key, value in student['basic_info'].items():
self.detail_text.insert(tk.END, f"{key}: {value}\n")
# 显示成绩
self.detail_text.insert(tk.END, "\n=== 成绩信息 ===\n")
for subject, score in student['score'].items():
if subject in self.subject_name:
self.detail_text.insert(tk.END, f"{self.subject_name[subject]}: {score}\n")
self.detail_text.config(state=tk.DISABLED)
def on_search_changed(self, event):
"""搜索框内容变化时过滤列表"""
search_term = self.search_var.get().lower()
if not search_term:
self.refresh_student_list()
return
filtered_students = [
student for student in self.students_info.values()
if (search_term in student['basic_info']['id'].lower() or
search_term in student['basic_info']['name'].lower() or
search_term in student['basic_info']['class'].lower())
]
self.refresh_student_list(filtered_students)
def show_add_dialog(self):
"""显示添加学生对话框"""
dialog = tk.Toplevel(self.root)
dialog.title("添加学生")
dialog.geometry("400x500")
dialog.resizable(False, False)
# 学号
ttk.Label(dialog, text="学号(8位数字):").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
id_entry = ttk.Entry(dialog)
id_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
# 身份证号
ttk.Label(dialog, text="身份证号(18位):").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
id_card_entry = ttk.Entry(dialog)
id_card_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
# 姓名
ttk.Label(dialog, text="姓名:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
name_entry = ttk.Entry(dialog)
name_entry.grid(row=2, column=1, padx=5, pady=5, sticky=tk.EW)
# 性别
ttk.Label(dialog, text="性别:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
gender_var = tk.StringVar(value="")
ttk.Radiobutton(dialog, text="", variable=gender_var, value="").grid(row=3, column=1, padx=5, pady=5,
sticky=tk.W)
ttk.Radiobutton(dialog, text="", variable=gender_var, value="").grid(row=3, column=1, padx=5, pady=5,
sticky=tk.E)
# 出生日期
ttk.Label(dialog, text="出生日期(YYYY-MM-DD):").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W)
birth_entry = ttk.Entry(dialog)
birth_entry.grid(row=4, column=1, padx=5, pady=5, sticky=tk.EW)
# 班级
ttk.Label(dialog, text="班级:").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W)
class_entry = ttk.Entry(dialog)
class_entry.grid(row=5, column=1, padx=5, pady=5, sticky=tk.EW)
# 入学年份
ttk.Label(dialog, text="入学年份:").grid(row=6, column=0, padx=5, pady=5, sticky=tk.W)
year_entry = ttk.Entry(dialog)
year_entry.grid(row=6, column=1, padx=5, pady=5, sticky=tk.EW)
# 成绩
ttk.Label(dialog, text="=== 成绩信息 ===").grid(row=7, column=0, columnspan=2, pady=10)
ttk.Label(dialog, text="数学:").grid(row=8, column=0, padx=5, pady=5, sticky=tk.W)
math_entry = ttk.Entry(dialog)
math_entry.grid(row=8, column=1, padx=5, pady=5, sticky=tk.EW)
ttk.Label(dialog, text="Python:").grid(row=9, column=0, padx=5, pady=5, sticky=tk.W)
python_entry = ttk.Entry(dialog)
python_entry.grid(row=9, column=1, padx=5, pady=5, sticky=tk.EW)
ttk.Label(dialog, text="英语:").grid(row=10, column=0, padx=5, pady=5, sticky=tk.W)
english_entry = ttk.Entry(dialog)
english_entry.grid(row=10, column=1, padx=5, pady=5, sticky=tk.EW)
ttk.Label(dialog, text="物理:").grid(row=11, column=0, padx=5, pady=5, sticky=tk.W)
physics_entry = ttk.Entry(dialog)
physics_entry.grid(row=11, column=1, padx=5, pady=5, sticky=tk.EW)
# 按钮
button_frame = ttk.Frame(dialog)
button_frame.grid(row=12, column=0, columnspan=2, pady=10)
def add_student():
"""添加学生"""
# 验证学号
student_id = id_entry.get().strip()
if not student_id.isdigit() or len(student_id) != 8:
messagebox.showerror("错误", "学号必须为8位数字")
return
if student_id in self.students_info:
messagebox.showerror("错误", "该学号已存在!")
return
# 验证身份证号
id_card = id_card_entry.get().strip().upper()
if len(id_card) != 18:
messagebox.showerror("错误", "身份证号必须为18位")
return
if not (id_card[:-1].isdigit() and (id_card[-1].isdigit() or id_card[-1] == 'X')):
messagebox.showerror("错误", "无效的身份证号格式前17位必须是数字最后一位可以是数字或X")
return
if any(student['basic_info'].get('id_card') == id_card for student in self.students_info.values()):
messagebox.showerror("错误", "该身份证号已存在!")
return
# 验证成绩
try:
math_score = float(math_entry.get())
python_score = float(python_entry.get())
english_score = float(english_entry.get())
physics_score = float(physics_entry.get())
except ValueError:
messagebox.showerror("错误", "成绩必须为数字!")
return
# 创建学生信息
student = {
'basic_info': {
'id': student_id,
'id_card': id_card,
'name': name_entry.get(),
'gender': gender_var.get(),
'birth_date': birth_entry.get(),
'class': class_entry.get(),
'admission_year': year_entry.get()
},
'score': {
'math': math_score,
'python': python_score,
'english': english_score,
'physics': physics_score,
'sum': math_score + python_score + english_score + physics_score
}
}
self.students_info[student_id] = student
if self.save_data():
messagebox.showinfo("成功", "学生信息添加成功!")
self.refresh_student_list()
dialog.destroy()
else:
messagebox.showerror("错误", "学生信息添加失败!")
ttk.Button(button_frame, text="确定", command=add_student).pack(side=tk.LEFT, padx=10)
ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=10)
def show_edit_dialog(self):
"""显示编辑学生对话框"""
selected_item = self.tree.selection()
if not selected_item:
messagebox.showwarning("提示", "请先选择一个学生!")
return
student_id = self.tree.item(selected_item)['values'][0]
student = self.students_info[student_id]
dialog = tk.Toplevel(self.root)
dialog.title("编辑学生信息")
dialog.geometry("400x500")
dialog.resizable(False, False)
# 学号(不可编辑)
ttk.Label(dialog, text="学号:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
ttk.Label(dialog, text=student['basic_info']['id']).grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
# 身份证号
ttk.Label(dialog, text="身份证号:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
id_card_entry = ttk.Entry(dialog)
id_card_entry.insert(0, student['basic_info'].get('id_card', ''))
id_card_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
# 姓名
ttk.Label(dialog, text="姓名:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
name_entry = ttk.Entry(dialog)
name_entry.insert(0, student['basic_info']['name'])
name_entry.grid(row=2, column=1, padx=5, pady=5, sticky=tk.EW)
# 性别
ttk.Label(dialog, text="性别:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W)
gender_var = tk.StringVar(value=student['basic_info']['gender'])
ttk.Radiobutton(dialog, text="", variable=gender_var, value="").grid(row=3, column=1, padx=5, pady=5,
sticky=tk.W)
ttk.Radiobutton(dialog, text="", variable=gender_var, value="").grid(row=3, column=1, padx=5, pady=5,
sticky=tk.E)
# 出生日期
ttk.Label(dialog, text="出生日期:").grid(row=4, column=0, padx=5, pady=5, sticky=tk.W)
birth_entry = ttk.Entry(dialog)
birth_entry.insert(0, student['basic_info']['birth_date'])
birth_entry.grid(row=4, column=1, padx=5, pady=5, sticky=tk.EW)
# 班级
ttk.Label(dialog, text="班级:").grid(row=5, column=0, padx=5, pady=5, sticky=tk.W)
class_entry = ttk.Entry(dialog)
class_entry.insert(0, student['basic_info']['class'])
class_entry.grid(row=5, column=1, padx=5, pady=5, sticky=tk.EW)
# 入学年份
ttk.Label(dialog, text="入学年份:").grid(row=6, column=0, padx=5, pady=5, sticky=tk.W)
year_entry = ttk.Entry(dialog)
year_entry.insert(0, student['basic_info']['admission_year'])
year_entry.grid(row=6, column=1, padx=5, pady=5, sticky=tk.EW)
# 成绩
ttk.Label(dialog, text="=== 成绩信息 ===").grid(row=7, column=0, columnspan=2, pady=10)
ttk.Label(dialog, text="数学:").grid(row=8, column=0, padx=5, pady=5, sticky=tk.W)
math_entry = ttk.Entry(dialog)
math_entry.insert(0, student['score']['math'])
math_entry.grid(row=8, column=1, padx=5, pady=5, sticky=tk.EW)
ttk.Label(dialog, text="Python:").grid(row=9, column=0, padx=5, pady=5, sticky=tk.W)
python_entry = ttk.Entry(dialog)
python_entry.insert(0, student['score']['python'])
python_entry.grid(row=9, column=1, padx=5, pady=5, sticky=tk.EW)
ttk.Label(dialog, text="英语:").grid(row=10, column=0, padx=5, pady=5, sticky=tk.W)
english_entry = ttk.Entry(dialog)
english_entry.insert(0, student['score']['english'])
english_entry.grid(row=10, column=1, padx=5, pady=5, sticky=tk.EW)
ttk.Label(dialog, text="物理:").grid(row=11, column=0, padx=5, pady=5, sticky=tk.W)
physics_entry = ttk.Entry(dialog)
physics_entry.insert(0, student['score']['physics'])
physics_entry.grid(row=11, column=1, padx=5, pady=5, sticky=tk.EW)
# 按钮
button_frame = ttk.Frame(dialog)
button_frame.grid(row=12, column=0, columnspan=2, pady=10)
def update_student(student=None):
"""更新学生信息"""
# 验证身份证号
id_card = id_card_entry.get().strip().upper()
if len(id_card) != 18:
messagebox.showerror("错误", "身份证号必须为18位")
return
if not (id_card[:-1].isdigit() and (id_card[-1].isdigit() or id_card[-1] == 'X')):
messagebox.showerror("错误", "无效的身份证号格式前17位必须是数字最后一位可以是数字或X")
return
if any(sid != student_id and student['basic_info'].get('id_card') == id_card
for sid, student in self.students_info.items()):
messagebox.showerror("错误", "该身份证号已被其他学生使用!")
return
# 验证成绩
try:
math_score = float(math_entry.get())
python_score = float(python_entry.get())
english_score = float(english_entry.get())
physics_score = float(physics_entry.get())
except ValueError:
messagebox.showerror("错误", "成绩必须为数字!")
return
# 更新学生信息
student['basic_info'].update({
'id_card': id_card,
'name': name_entry.get(),
'gender': gender_var.get(),
'birth_date': birth_entry.get(),
'class': class_entry.get(),
'admission_year': year_entry.get()
})
student['score'].update({
'math': math_score,
'python': python_score,
'english': english_score,
'physics': physics_score,
'sum': math_score + python_score + english_score + physics_score
})
if self.save_data():
messagebox.showinfo("成功", "学生信息修改成功!")
self.refresh_student_list()
dialog.destroy()
else:
messagebox.showerror("错误", "学生信息修改失败!")
ttk.Button(button_frame, text="确定", command=update_student).pack(side=tk.LEFT, padx=10)
ttk.Button(button_frame, text="取消", command=dialog.destroy).pack(side=tk.LEFT, padx=10)
def delete_student(self):
"""删除学生"""
selected_item = self.tree.selection()
if not selected_item:
messagebox.showwarning("提示", "请先选择一个学生!")
return
student_id = self.tree.item(selected_item)['values'][0]
if messagebox.askyesno("确认", f"确定要删除学号为 {student_id} 的学生吗?"):
del self.students_info[student_id]
if self.save_data():
messagebox.showinfo("成功", "学生信息删除成功!")
self.refresh_student_list()
else:
messagebox.showerror("错误", "学生信息删除失败!")
def show_statistics(self):
"""显示统计信息"""
if not self.students_info:
messagebox.showwarning("提示", "当前没有学生信息!")
return
total = len(self.students_info)
math_avg = sum(s['score']['math'] for s in self.students_info.values()) / total
python_avg = sum(s['score']['python'] for s in self.students_info.values()) / total
english_avg = sum(s['score']['english'] for s in self.students_info.values()) / total
physics_avg = sum(s['score']['physics'] for s in self.students_info.values()) / total
total_avg = sum(s['score']['sum'] for s in self.students_info.values()) / total
stats = f"""=== 统计信息 ===
学生总数: {total}
数学平均分: {math_avg:.2f}
Python平均分: {python_avg:.2f}
英语平均分: {english_avg:.2f}
物理平均分: {physics_avg:.2f}
总分平均分: {total_avg:.2f}"""
messagebox.showinfo("统计信息", stats)
def sort_by_total_score(self):
"""按总分排序显示"""
if not self.students_info:
messagebox.showwarning("提示", "当前没有学生信息!")
return
sorted_students = sorted(
self.students_info.values(),
key=lambda x: x['score']['sum'],
reverse=True
)
# 创建一个新窗口显示排序结果
sort_window = tk.Toplevel(self.root)
sort_window.title("按总分排序")
sort_window.geometry("600x400")
# 创建Treeview显示排序结果
tree = ttk.Treeview(sort_window, columns=('rank', 'id', 'name', 'total'), show='headings')
tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
tree.heading('rank', text='排名')
tree.heading('id', text='学号')
tree.heading('name', text='姓名')
tree.heading('total', text='总分')
tree.column('rank', width=50, anchor=tk.CENTER)
tree.column('id', width=100, anchor=tk.CENTER)
tree.column('name', width=100, anchor=tk.CENTER)
tree.column('total', width=80, anchor=tk.CENTER)
for rank, student in enumerate(sorted_students, 1):
tree.insert('', tk.END, values=(
rank,
student['basic_info']['id'],
student['basic_info']['name'],
student['score']['sum']
))
if __name__ == "__main__":
root = tk.Tk()
app = StudentManagementSystemGUI(root)
root.mainloop()

@ -0,0 +1,2 @@
from .studentBLL import StudentBLL
__all__ = ['StudentBLL']

@ -0,0 +1,47 @@
from datetime import date
from typing import Optional, List
from dal.student_dal_json import StudentDAL
from model import Student
class StudentBLL:
def __init__(self, dal: StudentDAL):
self.dal = dal
def add_student(self, student: Student) -> bool:
if self.dal.check_student_exists(student.sid):
raise ValueError(f"学生 ID {student.sid} 已存在。")
if not student.is_valid:
raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}")
return self.dal.add_student(student)
# 其他方法同理
def delete_student(self, sid: str) ->bool:
if not self.dal.check_student_exists(sid):
raise ValueError(f"学生 ID {sid} 不存在。")
return self.dal.delete_student(sid)
def update_student(self, sid: str, student: Student) -> bool:
if not self.dal.check_student_exists(sid):
raise ValueError(f"学生 ID {sid} 不存在。")
if not student.is_valid:
raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}")
return self.dal.update_student(sid, student)
def update_student_partial(self, sid: str, name: Optional[str] = None, height: Optional[int] = None,
birth_date: Optional[date] = None, enrollment_date: Optional[date] = None,
class_name: Optional[str] = None) -> bool:
if not self.dal.check_student_exists(sid):
raise ValueError(f"学生 ID {sid} 不存在。")
return self.dal.update_student_partial(sid, name, height, birth_date, enrollment_date, class_name)
def get_student_by_id(self, sid: str) -> Optional[Student]:
return self.dal.find_student_by_sid(sid)
def get_students_by_name(self, name: str) -> List[Student]:
return self.dal.find_students_by_name(name)
def get_students_by_class(self, class_name: str) -> List[Student]:
return self.dal.find_students_by_class_name(class_name)
def get_all_students(self) -> List[Student]:
return self.dal.get_all_students()

@ -0,0 +1,2 @@
from .studentDAL import IStudentDAL, CsvStudentDAL, JsonStudentDAL
__all__ = ['IStudentDAL', 'CsvStudentDAL', 'JsonStudentDAL']

@ -0,0 +1,290 @@
import json
import os, sys,csv
from datetime import date
from typing import List, Optional
from model.Student import Student
import csv
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)
from model.Student import Student
class StudentDALJSON:
def __init__(self, file_path="students.json"):
self.file_path = file_path
def get_all_students(self):
try:
with open(self.file_path, 'r', encoding='utf-8') as file:
student_dicts = json.load(file)
return [Student.from_dict(s) for s in student_dicts]
except (FileNotFoundError, json.JSONDecodeError):
return []
def save_students(self, students):
student_dicts = [student.to_dict() for student in students]
with open(self.file_path, 'w', encoding='utf-8') as file:
json.dump(student_dicts, file, ensure_ascii=False, indent=4)
# 新增导出为JSON
def export_to_json(self, file_path=None):
if not file_path:
file_path = self.file_path
students = self.get_all_students()
if not students:
print("没有数据可导出")
return
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump([s.to_dict() for s in students], f, ensure_ascii=False, indent=4)
print(f"数据已导出到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
# 新增导出为CSV
def export_to_csv(self, file_path="students.csv"):
students = self.get_all_students()
if not students:
print("没有数据可导出")
return
try:
with open(file_path, 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(
f,
fieldnames=[
'student_id', 'name', 'gender', 'age', 'class_name',
'major', 'phone', 'email', 'id_number',
'height', 'weight', 'birth_date', 'enrollment_date'
]
)
writer.writeheader()
for student in students:
data = student.to_dict()
# 将日期对象转换为字符串
data['birth_date'] = data['birth_date'].isoformat()
data['enrollment_date'] = data['enrollment_date'].isoformat()
writer.writerow(data)
print(f"数据已导出到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
# 新增从JSON导入
def import_from_json(self, file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
student_dicts = json.load(f)
students = []
for data in student_dicts:
# 转换日期字符串为日期对象
data['birth_date'] = date.fromisoformat(data['birth_date'])
data['enrollment_date'] = date.fromisoformat(data['enrollment_date'])
students.append(Student.from_dict(data))
self.save_students(students)
print(f"{file_path} 导入成功")
except FileNotFoundError:
print(f"文件不存在: {file_path}")
except json.JSONDecodeError:
print(f"JSON格式错误: {file_path}")
except Exception as e:
print(f"导入失败: {e}")
# 新增从CSV导入
def import_from_csv(self, file_path):
try:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
student_dicts = list(reader)
students = []
for data in student_dicts:
# 转换数值类型
data['age'] = int(data['age'])
data['height'] = float(data['height'])
data['weight'] = float(data['weight'])
# 转换日期类型
data['birth_date'] = date.fromisoformat(data['birth_date'])
data['enrollment_date'] = date.fromisoformat(data['enrollment_date'])
students.append(Student.from_dict(data))
self.save_students(students)
print(f"{file_path} 导入成功")
except FileNotFoundError:
print(f"文件不存在: {file_path}")
except Exception as e:
print(f"导入失败: {e}")
class StudentDAL:
def __init__(self, file_path: str):
self.file_path = file_path
self._ensure_file_exists()
def _ensure_file_exists(self):
if not os.path.exists(self.file_path):
with open(self.file_path, mode='w', encoding='utf-8') as file:
json.dump([], file)
def save_students(self, students: List[Student]) -> None:
student_dicts = [student.to_dict() for student in students]
with open(self.file_path, mode='w', encoding='utf-8') as file:
json.dump(student_dicts, file, ensure_ascii=False, indent=4)
def load_students(self) -> List[Student]:
with open(self.file_path, mode='r', encoding='utf-8') as file:
student_dicts = json.load(file)
return [Student.from_dict(student_dict) for student_dict in student_dicts]
def add_student(self, student: Student) -> bool:
students = self.load_students()
for existing_student in students:
if existing_student.sid == student.sid:
return False
students.append(student)
self.save_students(students)
return True
def find_student_by_sid(self, sid: str) -> Optional[Student]:
students = self.load_students()
for student in students:
if student.sid == sid:
return student
return None
def find_students_by_name(self, name: str) ->List[Student]:
students = self.load_students()
return [student for student in students if name.lower() in student.name.lower()]
def find_students_by_class_name(self, class_name: str) ->List[Student]:
students = self.load_students()
return [s for s in students if class_name.lower() in s.class_name.lower()]
def get_all_students(self) -> List[Student]:
return self.load_students()
def update_student(self, sid: str, updated_student: Student) -> bool:
students = self.load_students()
for i, student in enumerate(students):
if student.sid == sid:
students[i] = updated_student
self.save_students(students)
return True
return False
def update_student_partial(self, sid:str, name:Optional[str]=None, height:Optional[float]=None, birth_date:Optional[date]=None, enrollment_date:Optional[date]=None, class_name:Optional[str]=None) ->bool:
students = self.load_students()
for i, student in enumerate(students):
if student.sid == sid:
if name is not None:
students[i].name = name
if height is not None:
students[i].height = height
if birth_date is not None:
students[i].birth_date = birth_date
if enrollment_date is not None:
students[i].enrollment_date = enrollment_date
if class_name is not None:
students[i].class_name = class_name
self.save_students(students)
return True
return False
def delete_student(self, sid: str) -> bool:
"""根据学生 ID 删除学生"""
students = self.load_students()
initial_length = len(students)
students = [student for student in students if student.sid != sid]
if len(students) < initial_length:
self.save_students(students)
return True
return False
def clear_all_students(self) -> None:
self.save_students([])
def get_student_count(self) -> int:
return len(self.load_students())
def check_student_exists(self, sid: str) ->bool:
return self.find_student_by_sid(sid) is not None
def export_to_json(self, file_path):
students = self.load_students()
student_dicts = [student.to_dict() for student in students]
with open(file_path, mode='w', encoding='utf-8') as file:
json.dump(student_dicts, file, ensure_ascii=False, indent=4)
def import_from_json(self, file_path):
try:
with open(file_path, mode='r', encoding='utf-8') as file:
student_dicts = json.load(file)
students = [Student.from_dict(student_dict) for student_dict in student_dicts]
self.save_students(students)
except FileNotFoundError:
print(f"文件 {file_path} 未找到。")
except json.JSONDecodeError:
print(f"文件 {file_path} 不是有效的 JSON 文件。")
except ValueError as e:
print(f"数据转换错误: {e}")
def export_to_csv(self, file_path):
students = self.load_students()
with open(file_path, mode='w', encoding='utf-8', newline='') as file:
fieldnames = ['sid', 'name', 'height', 'birth_date', 'enrollment_date', 'class_name']
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
for student in students:
writer.writerow(student.to_dict())
def import_from_csv(self, file_path):
try:
students = []
with open(file_path, mode='r', encoding='utf-8') as file:
reader = csv.DictReader(file)
for row in reader:
row['birth_date'] = date.fromisoformat(row['birth_date'])
row['enrollment_date'] = date.fromisoformat(row['enrollment_date'])
student = Student.from_dict(row)
students.append(student)
self.save_students(students)
except FileNotFoundError:
print(f"文件 {file_path} 未找到。")
except ValueError as e:
print(f"日期格式错误: {e}")
def get_average_height(self):
students = self.load_students()
if not students:
return 0
total_height = sum(student.height for student in students)
return total_height / len(students)
def count_students_by_height_range(self, min_height, max_height):
students = self.load_students()
return len([student for student in students if min_height <= student.height <= max_height])
def count_students_by_enrollment_year(self, year):
students = self.load_students()
return len([student for student in students if student.enrollment_date.year == year])
def count_students_by_age_range(self, min_age, max_age):
today = date.today()
students = self.load_students()
return len([student for student in students if min_age <= today.year - student.birth_date.year <= max_age])
def backup_data(self, backup_path):
try:
with open(self.file_path, mode='r', encoding='utf-8') as src_file, \
open(backup_path, mode='w', encoding='utf-8') as dst_file:
dst_file.write(src_file.read())
print(f"数据备份到 {backup_path} 成功。")
except Exception as e:
print(f"数据备份失败: {e}")
def restore_data(self, backup_path):
try:
with open(backup_path, mode='r', encoding='utf-8') as src_file, \
open(self.file_path, mode='w', encoding='utf-8') as dst_file:
dst_file.write(src_file.read())
print(f"数据从 {backup_path} 恢复成功。")
except FileNotFoundError:
print(f"备份文件 {backup_path} 未找到。")
except Exception as e:
print(f"数据恢复失败: {e}")

@ -0,0 +1,7 @@
[{
"sid": "110309230907041",
"name": "cgt",
"height": 180,
"birth_date": "2023-09-01",
"enrollment_date": "2023-09-01",
"class_name": "cls2" }]

@ -0,0 +1,2 @@
from .student import Student
__all__ = ['Student']

@ -0,0 +1,90 @@
from datetime import date
class Student:
def __init__(self, sid: str, name: str, height: str, birth_date: date | str, enrollment_date: date | str, class_name: str):
self.sid = sid
self.name = name.strip()
self.height = int(height) # 转换为整数类型
self.birth_date = birth_date if isinstance(birth_date, date) else date.fromisoformat(birth_date)
self.enrollment_date = enrollment_date if isinstance(enrollment_date, date) else date.fromisoformat(enrollment_date)
self.class_name = class_name
self._validation_errors = []
self._validate_height()
self._validate_date()
self._validate_name()
def _validate_height(self) -> None:
if not (50 <= self.height <= 250):
self._validation_errors.append(f"身高{self.height}cm超出合理范围")
def _validate_name(self) -> None:
if not (2 <= len(self.name) <= 20) or not self.name.isprintable():
self._validation_errors.append("姓名长度需在2-20个字符之间且为可打印字符")
if not self.name.isprintable():
self._validation_errors.append("姓名长度需在2-20个字符之间")
def _validate_date(self) -> None:
today = date.today()
if self.birth_date > today:
self._validation_errors.append('出生日期不能在未来')
if self.enrollment_date > today:
self._validation_errors.append('入学日期不能在未来')
if self.birth_date > self.enrollment_date:
self._validation_errors.append('入学日期不能早于出生日期')
@property
def is_valid(self) -> bool:
return len(self._validation_errors) == 0
def get_errors(self) -> list[str]:
return self._validation_errors.copy()
def __eq__(self, other) -> bool:
if not isinstance(other, Student):
return NotImplemented
return (self.sid == other.sid and
self.name == other.name and
self.height == other.height and
self.birth_date == other.birth_date and
self.enrollment_date == other.enrollment_date and
self.class_name == other.class_name
)
@classmethod
def from_dict(cls, data: dict) -> 'Student':
birth_date = data['birth_date'] if isinstance(data['birth_date'], date) else date.fromisoformat(
data['birth_date'])
enrollment_date = data['enrollment_date'] if isinstance(data['enrollment_date'], date) else date.fromisoformat(
data['enrollment_date'])
return cls(
sid=data['sid'],
name=data['name'].strip(),
height=data['height'],
birth_date=birth_date,
enrollment_date=enrollment_date,
class_name=data['class_name']
)
def to_dict(self):
return {
'sid': self.sid,
'name': self.name,
'height': self.height,
'birth_date': self.birth_date.isoformat(),
'enrollment_date': self.enrollment_date.isoformat(),
'class_name': self.class_name
}
def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"sid='{self.sid}', "
f"name='{self.name}',"
f"height={self.height},"
f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day}), "
f"enrollment_date=date({self.enrollment_date.year},{self.enrollment_date.month},{self.enrollment_date.day}), "
f"class_name='{self.class_name}'"
f")"
)

@ -0,0 +1,2 @@
from .studentUI import StudentUI
__all__ = ['StudentUI']

@ -0,0 +1,172 @@
import os
import re
from datetime import date
from bll.student_bll import StudentBLL
from dal.student_dal_json import StudentDAL
from model.Student import Student
from dal.student_dal_json import StudentDALJSON # 确保导入
class StudentUI:
def __init__(self):
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
file_path = os.path.join(parent_dir, "data", "student.json")
dal = StudentDAL(file_path)
self.bll = StudentBLL(dal)
self.file_path = file_path
def display_menu(self):
"""显示主菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 主菜单 ")
print("*" * 50)
print("1. 添加学生信息")
print("2. 删除学生信息")
print("3. 更新学生信息")
print("4. 查询学生信息")
print("5. 统计分析")
print("6. 数据导入导出")
print("7. 清空所有学生信息")
print("0. 退出系统")
print("*" * 50)
def display_query_menu(self):
"""显示查询子菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 查询菜单 ")
print("*" * 50)
print("1. 查询所有学生")
print("2. 按身份证号查询")
print("3. 按学号查询")
print("4. 按姓名查询")
print("5. 按班级查询")
print("0. 返回上一级")
print("*" * 50)
def display_stats_menu(self):
"""显示统计分析子菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 统计分析菜单 ")
print("*" * 50)
print("1. 学生总数")
print("2. 平均身高")
print("3. 按身高范围统计")
print("4. 按入学年份统计")
print("5. 按年龄范围统计")
print("0. 返回上一级")
print("*" * 50)
def display_import_export_menu(self):
"""显示数据导入导出子菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 数据导入导出菜单 ")
print("*" * 50)
print("1. 导出数据到JSON")
print("2. 从JSON导入数据")
print("3. 导出数据到CSV")
print("4. 从CSV导入数据")
print("0. 返回上一级")
print("*" * 50)
def get_input(self, prompt: str) -> str:
"""获取用户输入,添加异常处理"""
try:
return input(prompt).strip()
except EOFError:
return "0" # 按Ctrl+D视为退出
def add_student(self):
"""添加学生信息"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 添加学生信息 ")
print("*" * 50)
name = self.get_input("请输入姓名: ")
id_number = self.get_input("请输入身份证号: ")
while not re.match(r'^\d{17}[\dXx]$', id_number):
print("身份证号格式不正确,请输入 18 位身份证号。")
id_number = self.get_input("请输入身份证号: ")
while True:
student_id = self.get_input("请输入学号: ")
if re.match(r'^\d{10,15}$', student_id): # 假设学号是10 - 15位数字
break
print("学号格式不正确请输入10到15位数字")
gender = self.get_input("请输入性别(男/女): ")
while True:
try:
height = float(self.get_input("请输入身高(cm): "))
if height <= 0 or height > 250:
raise ValueError
break
except ValueError:
print("身高必须为0到250之间的有效数值请重新输入")
while True:
try:
weight = float(self.get_input("请输入体重(kg): "))
if weight <= 0 or weight > 500:
raise ValueError
break
except ValueError:
print("体重必须为0到500之间的有效数值请重新输入")
# 从身份证号中提取出生日期
birth_date_str = id_number[6:14]
birth_date = date(int(birth_date_str[:4]), int(birth_date_str[4:6]), int(birth_date_str[6:]))
# 修改后的日期输入验证
while True:
enrollment_date = self.get_input("请输入入学日期(YYYY-MM-DD): ")
try:
enrollment_date_obj = date.fromisoformat(enrollment_date)
break
except ValueError:
print("日期格式错误,请使用 YYYY-MM-DD 格式例如2023-09-01")
class_name = self.get_input("请输入班级: ")
major = self.get_input("请输入专业: ")
# 创建 Student 对象
try:
student = Student(
sid=student_id,
name=name,
height=height,
birth_date=birth_date, # 使用从身份证提取的出生日期
enrollment_date=enrollment_date_obj,
class_name=class_name
)
if self.bll.add_student(student):
print("学生信息添加成功!")
else:
print("学生信息添加失败。")
except ValueError as e:
print(f"添加学生失败: {e}")
def handle_import_export(self, choice):
if choice == '1':
file_path = self.get_input("请输入要导出的 JSON 文件路径: ")
self.bll.dal.export_to_json(file_path)
print("数据导出到 JSON 成功!")
elif choice == '2':
file_path = self.get_input("请输入要导入的 JSON 文件路径: ")
self.bll.dal.import_from_json(file_path)
print("数据从 JSON 导入成功!")
elif choice == '3':
file_path = self.get_input("请输入要导出的 CSV 文件路径: ")
self.bll.dal.export_to_csv(file_path)
print("数据导出到 CSV 成功!")
elif choice == '4':
file_path = self.get_input("请输入要导入的 CSV 文件路径: ")
self.bll.dal.import_from_csv(file_path)
print("数据从 CSV 导入成功!")
Loading…
Cancel
Save