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 class StudentUI: def __init__(self): current_dir = os.path.dirname(os.path.abspath(__file__)) parent_dir = os.path.dirname(current_dir) data_dir = os.path.join(parent_dir, "data") os.makedirs(data_dir, exist_ok=True) file_path = os.path.join(data_dir, "students.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_delete_menu(self): """显示删除子菜单""" os.system('cls' if os.name == 'nt' else 'clear') print("*" * 50) print(" 学生信息管理系统 - 删除菜单 ") print("*" * 50) print("1. 按学号删除学生信息") print("2. 按身份证号删除学生信息") 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("6. 按专业查询") 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("6. 统计各专业学生人数") print("7. 计算平均身高/体重(按班级)") print("8. 计算平均身高/体重(按专业)") print("9. 统计性别比例") 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("5. 备份数据") print("6. 恢复数据") 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("请输入姓名: ") while not (2 <= len(name) <= 20): print("姓名长度需在2-20个字符之间") name = self.get_input("请输入姓名: ") gender = self.get_input("请输入性别(男/女): ") while gender not in ['男', '女']: print("性别只能是'男'或'女'") gender = 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位数字 if not self.bll.dal.check_student_exists(student_id): break print("学号已存在,请重新输入。") else: print("学号格式不正确,请输入10到15位数字") while True: try: height = float(self.get_input("请输入身高(cm): ")) if 50 <= height <= 250: break print("身高必须在50到250cm之间,请重新输入。") except ValueError: print("身高必须为有效的数值,请重新输入。") while True: try: weight = float(self.get_input("请输入体重(kg): ")) if 5 <= weight <= 300: break print("体重必须在5到300kg之间,请重新输入。") except ValueError: print("体重必须为有效的数值,请重新输入。") # 从身份证号中提取出生日期 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: phone = self.get_input("请输入电话号码: ") if re.match(r'^1[3-9]\d{9}$', phone): break print("电话号码格式不正确,请输入11位数字") # 邮箱验证 while True: email = self.get_input("请输入邮箱地址: ") if re.match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', email): break print("邮箱格式不正确,请重新输入") # 入学日期验证 while True: enrollment_date = self.get_input("请输入入学日期(YYYY-MM-DD): ") try: enrollment_date_obj = date.fromisoformat(enrollment_date) if enrollment_date_obj >= birth_date: break print("入学日期不能早于出生日期,请重新输入。") 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, gender=gender, height=height, weight=weight, birth_date=birth_date, enrollment_date=enrollment_date_obj, class_name=class_name, id_number=id_number, phone=phone, email=email, major=major ) if self.bll.add_student(student): print("学生信息添加成功!") else: print("学生信息添加失败。") except ValueError as e: print(f"添加学生失败: {e}") def handle_delete(self, choice): """处理删除操作""" if choice == '1': sid = self.get_input("请输入要删除的学生学号: ") try: if self.bll.delete_student(sid): print("学生信息删除成功!") else: print("学生信息删除失败。") except ValueError as e: print(e) elif choice == '2': id_number = self.get_input("请输入要删除的学生身份证号: ") try: if self.bll.delete_student_by_id_number(id_number): print("学生信息删除成功!") else: print("学生信息删除失败。") except ValueError as e: print(e) elif choice == '0': return else: print("无效的选择,请重新输入。") def update_student(self): """更新学生信息""" os.system('cls' if os.name == 'nt' else 'clear') print("*" * 50) print(" 更新学生信息 ") print("*" * 50) sid = self.get_input("请输入要更新的学生学号: ") student = self.bll.get_student_by_id(sid) if student: print(f"当前学生信息: {student}") # 获取更新信息 name = self.get_input(f"请输入新的姓名(当前:{student.name}),不修改请直接回车: ") gender = self.get_input(f"请输入新的性别(当前:{student.gender}),不修改请直接回车: ") height = self.get_input(f"请输入新的身高(当前:{student.height}),不修改请直接回车: ") weight = self.get_input(f"请输入新的体重(当前:{student.weight}),不修改请直接回车: ") birth_date = self.get_input(f"请输入新的出生日期(当前:{student.birth_date}),不修改请直接回车: ") enrollment_date = self.get_input(f"请输入新的入学日期(当前:{student.enrollment_date}),不修改请直接回车: ") class_name = self.get_input(f"请输入新的班级(当前:{student.class_name}),不修改请直接回车: ") major = self.get_input(f"请输入新的专业(当前:{student.major}),不修改请直接回车: ") phone = self.get_input(f"请输入新的电话号码(当前:{student.phone}),不修改请直接回车: ") email = self.get_input(f"请输入新的邮箱地址(当前:{student.email}),不修改请直接回车: ") # 更新学生信息 update_fields = {} if name: update_fields['name'] = name if gender: update_fields['gender'] = gender if height: update_fields['height'] = int(height) if weight: update_fields['weight'] = float(weight) if birth_date: update_fields['birth_date'] = date.fromisoformat(birth_date) if enrollment_date: update_fields['enrollment_date'] = date.fromisoformat(enrollment_date) if class_name: update_fields['class_name'] = class_name if major: update_fields['major'] = major if phone: update_fields['phone'] = phone if email: update_fields['email'] = email if update_fields: try: if self.bll.update_student_partial(sid, **update_fields): print("学生信息更新成功!") else: print("学生信息更新失败。") except ValueError as e: print(e) else: print("未输入任何更新信息,操作取消。") else: print(f"学生 ID {sid} 不存在。") def handle_query(self, choice): """处理查询操作""" if choice == '1': students = self.bll.get_all_students() if not students: print("没有找到学生信息。") return print("\n所有学生信息:") print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}") print("-" * 80) today = date.today() for student in students: age = today.year - student.birth_date.year if today < date(today.year, student.birth_date.month, student.birth_date.day): age -= 1 print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}") elif choice == '2': id_number = self.get_input("请输入身份证号: ") student = self.bll.find_student_by_id_number(id_number) if student: print("\n找到学生信息:") print(f"学号: {student.sid}") print(f"姓名: {student.name}") print(f"性别: {student.gender}") print(f"年龄: {date.today().year - student.birth_date.year}") print(f"班级: {student.class_name}") print(f"专业: {student.major}") print(f"身高: {student.height} cm") print(f"体重: {student.weight} kg") print(f"身份证号: {student.id_number}") print(f"电话号码: {student.phone}") print(f"邮箱地址: {student.email}") print(f"出生日期: {student.birth_date}") print(f"入学日期: {student.enrollment_date}") else: print(f"未找到身份证号为 {id_number} 的学生。") elif choice == '3': sid = self.get_input("请输入学号: ") student = self.bll.get_student_by_id(sid) if student: print("\n找到学生信息:") print(f"学号: {student.sid}") print(f"姓名: {student.name}") print(f"性别: {student.gender}") print(f"年龄: {date.today().year - student.birth_date.year}") print(f"班级: {student.class_name}") print(f"专业: {student.major}") print(f"身高: {student.height} cm") print(f"体重: {student.weight} kg") print(f"身份证号: {student.id_number}") print(f"电话号码: {student.phone}") print(f"邮箱地址: {student.email}") print(f"出生日期: {student.birth_date}") print(f"入学日期: {student.enrollment_date}") else: print(f"未找到学号为 {sid} 的学生。") elif choice == '4': name = self.get_input("请输入姓名: ") students = self.bll.get_students_by_name(name) if not students: print(f"未找到姓名包含 '{name}' 的学生。") return print(f"\n找到 {len(students)} 名学生:") print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}") print("-" * 80) today = date.today() for student in students: age = today.year - student.birth_date.year if today < date(today.year, student.birth_date.month, student.birth_date.day): age -= 1 print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}") elif choice == '5': class_name = self.get_input("请输入班级: ") students = self.bll.get_students_by_class(class_name) if not students: print(f"未找到班级包含 '{class_name}' 的学生。") return print(f"\n找到 {len(students)} 名学生:") print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}") print("-" * 80) today = date.today() for student in students: age = today.year - student.birth_date.year if today < date(today.year, student.birth_date.month, student.birth_date.day): age -= 1 print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}") elif choice == '6': major = self.get_input("请输入专业: ") students = self.bll.get_students_by_major(major) if not students: print(f"未找到专业包含 '{major}' 的学生。") return print(f"\n找到 {len(students)} 名学生:") print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}") print("-" * 80) today = date.today() for student in students: age = today.year - student.birth_date.year if today < date(today.year, student.birth_date.month, student.birth_date.day): age -= 1 print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}") elif choice == '0': return else: print("无效的选择,请重新输入。") def handle_stats(self, choice): """处理统计分析操作""" if choice == '1': count = self.bll.dal.get_student_count() print(f"学生总数为: {count}") elif choice == '2': average_height = self.bll.dal.get_average_height() print(f"学生平均身高为: {average_height:.2f} cm") elif choice == '3': min_height = int(self.get_input("请输入最小身高: ")) max_height = int(self.get_input("请输入最大身高: ")) count = self.bll.dal.count_students_by_height_range(min_height, max_height) print(f"身高在 {min_height} - {max_height} cm 之间的学生数量为: {count}") elif choice == '4': year = int(self.get_input("请输入入学年份: ")) count = self.bll.dal.count_students_by_enrollment_year(year) print(f"{year} 年入学的学生数量为: {count}") elif choice == '5': min_age = int(self.get_input("请输入最小年龄: ")) max_age = int(self.get_input("请输入最大年龄: ")) count = self.bll.dal.count_students_by_age_range(min_age, max_age) print(f"年龄在 {min_age} - {max_age} 岁之间的学生数量为: {count}") elif choice == '6': major_counts = self.bll.count_students_by_major() print("\n各专业学生人数统计:") print(f"{'专业':<20}{'人数'}") print("-" * 30) for major, count in major_counts.items(): print(f"{major:<20}{count}") elif choice == '7': print("\n按班级统计平均身高/体重:") class_stats = self.bll.calculate_average_height_weight('class_name') print(f"{'班级':<20}{'平均身高(cm)':<15}{'平均体重(kg)'}") print("-" * 45) for class_name, stats in class_stats.items(): print(f"{class_name:<20}{stats['average_height']:<15.2f}{stats['average_weight']:.2f}") elif choice == '8': print("\n按专业统计平均身高/体重:") major_stats = self.bll.calculate_average_height_weight('major') print(f"{'专业':<20}{'平均身高(cm)':<15}{'平均体重(kg)'}") print("-" * 45) for major, stats in major_stats.items(): print(f"{major:<20}{stats['average_height']:<15.2f}{stats['average_weight']:.2f}") elif choice == '9': gender_ratio = self.bll.calculate_gender_ratio() print("\n性别比例统计:") print(f"男生比例: {gender_ratio['male_ratio']:.2%}") print(f"女生比例: {gender_ratio['female_ratio']:.2%}") elif choice == '0': return else: print("无效的选择,请重新输入。") 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导入成功!") elif choice == '5': backup_path = self.get_input("请输入备份文件路径: ") self.bll.dal.backup_data(backup_path) elif choice == '6': backup_path = self.get_input("请输入恢复文件路径: ") self.bll.dal.restore_data(backup_path) elif choice == '0': return else: print("无效的选择,请重新输入。") def clear_all_students(self): """清空所有学生信息""" os.system('cls' if os.name == 'nt' else 'clear') print("*" * 50) print(" 警告:清空所有学生信息 ") print("*" * 50) confirm = self.get_input("此操作将删除所有学生信息,且无法恢复。确定要继续吗?(y/n): ") if confirm.lower() == 'y': try: self.bll.dal.clear_all_students() print("所有学生信息已清空!") except Exception as e: print(f"清空学生信息失败: {e}") def run(self): """运行主程序""" while True: self.display_menu() choice = self.get_input("请输入你的选择: ") if choice == '1': self.add_student() elif choice == '2': while True: self.display_delete_menu() delete_choice = self.get_input("请输入你的删除选择: ") if delete_choice == '0': break self.handle_delete(delete_choice) elif choice == '3': self.update_student() elif choice == '4': while True: self.display_query_menu() query_choice = self.get_input("请输入你的查询选择: ") if query_choice == '0': break self.handle_query(query_choice) elif choice == '5': while True: self.display_stats_menu() stats_choice = self.get_input("请输入你的统计选择: ") if stats_choice == '0': break self.handle_stats(stats_choice) elif choice == '6': while True: self.display_import_export_menu() import_export_choice = self.get_input("请输入你的导入导出选择: ") if import_export_choice == '0': break self.handle_import_export(import_export_choice) elif choice == '7': self.clear_all_students() elif choice == '0': print("感谢使用学生信息管理系统,再见!") break else: print("无效的选择,请重新输入。") input("\n按Enter键继续...")