import json import csv from datetime import date from typing import List, Tuple from BLL.add_student import StudentBLL from model.student import Student from DAL.clear_all_student import CsvStudentDAL from DAL.clear_all_student import JsonStudentDAL def display_menu(): print("\n=== 学生信息管理系统 ====") print("1. 添加学生信息") print("2. 删除学生信息") print("3. 更新学生信息") print("4. 查询学生信息") print("5. 统计分析") print("6. 数据导入导出") print("7. 清空所有学生信息") print("8. 退出系统") print("---") def display_query_menu(): print("\n==== 查询学生信息 ====") print("1. 查询所有学生") print("2. 按身份证号查询") print("3. 按学号查询") print("4. 按姓名查询") print("5. 按班级查询") print("6. 按专业查询") print("7. 返回上一级") print("=======================") def display_stats_menu(): print("\n==== 统计分析 ====") print("1. 学生总数") print("2. 平均身高") print("3. 按身高范围统计") print("4. 按入学年份统计") print("5. 按年龄范围统计") print("6. 按专业分组统计") print("7. 性别比例") print("8. 返回上一级") print("=======================") def display_import_export_menu(): print("\n==== 数据导入导出 ====") print("1. 导出数据到JSON") print("2. 从JSON导入数据") print("3. 导出数据到CSV") print("4. 从CSV导入数据") print("5. 返回上一级") print("=======================") def display_students(students: List[Student]): if not students: print("没有找到学生信息") return print("\n学生信息列表:") print("-" * 120) print( f"{'姓名':<10}{'学号':<15}{'身份证号':<20}{'性别':<6}{'年龄':<6}{'身高':<6}{'体重':<6}{'班级':<10}{'专业':<15}{'入学日期':<12}") print("-" * 120) for student in students: gender = '男' if student.gender else '女' if student.gender is not None else '未知' print( f"{student.name:<10}{student.stu_id:<15}{student.id_card:<20}" f"{gender:<6}{student.age or '-':<6}{student.height or '-':<6}" f"{student.weight or '-':<6}{student.class_name or '-':<10}" f"{student.major or '-':<15}{student.enrollment_date or '-'}" ) print("-" * 120) print(f"共找到 {len(students)} 名学生") class StudentTUI: def __init__(self, bll: StudentBLL): self.bll = bll # 省份代码映射表 self.province_codes = { '11': '北京市', '12': '天津市', '13': '河北省', '14': '山西省', '15': '内蒙古自治区', '21': '辽宁省', '22': '吉林省', '23': '黑龙江省', '31': '上海市', '32': '江苏省', '33': '浙江省', '34': '安徽省', '35': '福建省', '36': '江西省', '37': '山东省', '41': '河南省', '42': '湖北省', '43': '湖南省', '44': '广东省', '45': '广西壮族自治区', '46': '海南省', '50': '重庆市', '51': '四川省', '52': '贵州省', '53': '云南省', '54': '西藏自治区', '61': '陕西省', '62': '甘肃省', '63': '青海省', '64': '宁夏回族自治区', '65': '新疆维吾尔自治区' } def validate_id_card(self, id_card: str) -> bool: """验证身份证号是否合法""" if len(id_card) != 18: return False # 校验前17位是否为数字 if not id_card[:-1].isdigit(): return False # 校验最后一位是否为数字或X if not (id_card[-1].isdigit() or id_card[-1].upper() == 'X'): return False return True def get_gender_from_id_card(self, id_card: str) -> bool: """从身份证号获取性别(男True/女False)""" if len(id_card) != 18: raise ValueError("身份证号长度不正确") gender_code = int(id_card[16]) return gender_code % 2 == 1 # 奇数表示男性,偶数表示女性 def get_hometown_from_id_card(self, id_card: str) -> str: """从身份证号获取籍贯""" if len(id_card) != 18: raise ValueError("身份证号长度不正确") province_code = id_card[:2] return self.province_codes.get(province_code, "未知地区") def run(self): while True: display_menu() choice = input("请选择操作: ").strip() if choice == '1': self.add_student() elif choice == '2': self.delete_student() elif choice == '3': self.update_student() elif choice == '4': self.query_students() elif choice == '5': self.stats_students() elif choice == '6': self.import_export_data() elif choice == '7': self.clear_all_students() elif choice == '8': print("感谢使用学生信息管理系统,再见!") break else: print("无效的选择,请重新输入!") def add_student(self): print("\n==== 添加学生信息 ====") try: name = input("姓名: ").strip() id_card = input("身份证号: ").strip() # 验证身份证号 if not self.validate_id_card(id_card): print("身份证号不合法!") return # 自动获取性别 gender = self.get_gender_from_id_card(id_card) print(f"自动识别性别: {'男' if gender else '女'}") # 自动获取籍贯 hometown = self.get_hometown_from_id_card(id_card) print(f"籍贯: {hometown}") stu_id = input("学号: ").strip() height = int(input("身高(cm): ").strip()) weight = float(input("体重(kg): ").strip()) enrollment_date = input("入学日期(YYYY-MM-DD): ").strip() class_name = input("班级名称: ").strip() major = input("专业: ").strip() email = input("邮箱(可选): ").strip() or None phone = input("手机号(可选): ").strip() or None 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 ) if self.bll.add_student(student): print("学生信息添加成功!") else: print("添加失败,可能学号或身份证号已存在") except ValueError as e: print(f"输入错误: {e}") except Exception as e: print(f"添加学生信息时出错: {e}") def delete_student(self): print("\n==== 删除学生信息 ====") print("1. 按身份证号删除") print("2. 按学号删除") choice = input("请选择删除方式: ").strip() if choice == '1': id_card = input("请输入身份证号: ").strip() try: if self.bll.delete_student_by_id_card(id_card): print("学生信息删除成功!") else: print("删除失败,未找到匹配的学生") except ValueError as e: print(f"错误: {e}") elif choice == '2': stu_id = input("请输入学号: ").strip() try: if self.bll.delete_student_by_stu_id(stu_id): print("学生信息删除成功!") else: print("删除失败,未找到匹配的学生") except ValueError as e: print(f"错误: {e}") else: print("无效的选择") def update_student(self): print("\n==== 更新学生信息 ====") id_card = input("请输入要更新的学生身份证号: ").strip() try: student = self.bll.get_student_by_id_card(id_card) if not student: print("未找到该学生") return print("当前学生信息:") print(student) print("\n请输入要更新的字段 (留空表示不更新):") name = input(f"姓名({student.name}): ").strip() or student.name stu_id = input(f"学号({student.stu_id}): ").strip() or student.stu_id height = input(f"身高({student.height}): ").strip() height = int(height) if height else student.height weight = input(f"体重({student.weight}): ").strip() weight = float(weight) if weight else student.weight enrollment_date = input(f"入学日期({student.enrollment_date}): ").strip() enrollment_date = enrollment_date or student.enrollment_date class_name = input(f"班级名称({student.class_name}): ").strip() or student.class_name major = input(f"专业({student.major}): ").strip() or student.major email = input(f"邮箱({student.email}): ").strip() or student.email phone = input(f"手机号({student.phone}): ").strip() or student.phone updated_student = Student( name=name, id_card=id_card, # 身份证号不可更改 stu_id=stu_id, gender=student.gender, # 性别从身份证号自动获取,不可更改 height=height, weight=weight, enrollment_date=enrollment_date, class_name=class_name, major=major, email=email, phone=phone ) if self.bll.update_student(id_card, updated_student): print("学生信息更新成功!") else: print("更新失败") except ValueError as e: print(f"输入错误: {e}") except Exception as e: print(f"更新学生信息时出错: {e}") def query_students(self): while True: display_query_menu() choice = input("请选择查询方式: ").strip() if choice == '1': # 查询所有学生 students = self.bll.get_all_students() display_students(students) elif choice == '2': # 按身份证号查询 id_card = input("请输入身份证号: ").strip() student = self.bll.get_student_by_id_card(id_card) if student: # 显示籍贯信息 hometown = self.get_hometown_from_id_card(student.id_card) print(f"\n籍贯: {hometown}") display_students([student]) else: print("未找到匹配的学生") elif choice == '3': # 按学号查询 stu_id = input("请输入学号: ").strip() student = self.bll.get_student_by_stu_id(stu_id) if student: display_students([student]) else: print("未找到匹配的学生") elif choice == '4': # 按姓名查询 name = input("请输入姓名: ").strip() students = self.bll.get_students_by_name(name) display_students(students) elif choice == '5': # 按班级查询 class_name = input("请输入班级名称: ").strip() students = self.bll.get_students_by_class_name(class_name) display_students(students) elif choice == '6': # 按专业查询 major = input("请输入专业名称: ").strip() students = self.bll.get_students_by_major(major) display_students(students) elif choice == '7': # 返回上一级 break else: print("无效的选择,请重新输入!") def stats_students(self): while True: display_stats_menu() choice = input("请选择统计方式: ").strip() if choice == '1': # 学生总数 count = self.bll.get_student_count() print(f"\n学生总数: {count}") elif choice == '2': # 平均身高 avg_height = self.bll.get_average_height() print(f"\n平均身高: {avg_height:.2f} cm") elif choice == '3': # 按身高范围统计 min_height = int(input("最小身高(cm): ").strip()) max_height = int(input("最大身高(cm): ").strip()) students = self.bll.get_students_by_height_range(min_height, max_height) print(f"\n身高在 {min_height}-{max_height} cm 的学生 ({len(students)} 人):") display_students(students) elif choice == '4': # 按入学年份统计 year = int(input("请输入入学年份: ").strip()) students = self.bll.get_students_by_enrollment_year(year) print(f"\n{year}年入学的学生 ({len(students)} 人):") display_students(students) elif choice == '5': # 按年龄范围统计 min_age = int(input("最小年龄: ").strip()) max_age = int(input("最大年龄: ").strip()) students = self.bll.get_students_by_age_range(min_age, max_age) print(f"\n年龄在 {min_age}-{max_age} 岁的学生 ({len(students)} 人):") display_students(students) elif choice == '6': # 按专业分组统计 groups = self.bll.get_students_by_major_group() for major, students in groups.items(): print(f"\n专业: {major} ({len(students)} 人)") display_students(students) elif choice == '7': # 性别比例 ratio = self.bll.get_gender_ratio() print(f"\n性别比例:") print(f"男生: {ratio.get(True, 0.0):.2f}%") print(f"女生: {ratio.get(False, 0.0):.2f}%") elif choice == '8': # 返回上一级 break else: print("无效的选择,请重新输入!") def import_export_data(self): while True: display_import_export_menu() choice = input("请选择操作: ").strip() if choice == '1': # 导出到JSON file_path = input("请输入导出文件路径: ").strip() if self.export_to_json(file_path): print("导出成功!") else: print("导出失败") elif choice == '2': # 从JSON导入 file_path = input("请输入导入文件路径: ").strip() success, error = self.import_from_json(file_path) print(f"导入完成: 成功 {success} 条, 失败 {error} 条") elif choice == '3': # 导出到CSV file_path = input("请输入导出文件路径: ").strip() if self.export_to_csv(file_path): print("导出成功!") else: print("导出失败") elif choice == '4': # 从CSV导入 file_path = input("请输入导入文件路径: ").strip() success, error = self.import_from_csv(file_path) print(f"导入完成: 成功 {success} 条, 失败 {error} 条") elif choice == '5': # 返回上一级 break else: print("无效的选择,请重新输入!") def clear_all_students(self): confirm = input("确定要清空所有学生信息吗? (y/n): ").strip().lower() if confirm == 'y': self.bll.clear_all_students() print("所有学生信息已清空!") else: print("操作已取消") def export_to_json(self, file_path: str) -> bool: """导出学生信息到JSON文件""" students = self.bll.get_all_students() 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) return True except Exception as e: print(f"导出学生数据到JSON时出错: {e}") return False def import_from_json(self, file_path: str) -> Tuple[int, int]: """从JSON文件导入学生信息""" success_count = 0 error_count = 0 try: with open(file_path, 'r', encoding='utf-8') as f: student_dicts = json.load(f) for s_dict in student_dicts: try: student = Student.from_dict(s_dict) if self.bll.add_student(student): success_count += 1 except Exception as e: error_count += 1 print(f"导入学生数据时出错: {e}") return success_count, error_count except Exception as e: print(f"从JSON导入学生数据时出错: {e}") return 0, 0 def export_to_csv(self, file_path: str) -> bool: """导出学生信息到CSV文件""" students = self.bll.get_all_students() if not students: return False try: with open(file_path, 'w', encoding='utf-8', newline='') as f: # 获取第一个学生的字段作为CSV表头 fieldnames = list(students[0].to_dict().keys()) writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() for student in students: writer.writerow(student.to_dict()) return True except Exception as e: print(f"导出学生数据到CSV时出错: {e}") return False def import_from_csv(self, file_path: str) -> Tuple[int, int]: """从CSV文件导入学生信息""" success_count = 0 error_count = 0 try: with open(file_path, 'r', encoding='utf-8') as f: reader = csv.DictReader(f) for row in reader: try: # 转换数据类型 converted_row = {} for key, value in row.items(): if key in ['birth_date', 'enrollment_date'] and value: converted_row[key] = date.fromisoformat(value) elif key in ['height', 'weight'] and value: converted_row[key] = float(value) if value else 0.0 else: converted_row[key] = value student = Student.from_dict(converted_row) if self.bll.add_student(student): success_count += 1 except Exception as e: error_count += 1 print(f"导入学生数据时出错: {e}") return success_count, error_count except Exception as e: print(f"从CSV导入学生数据时出错: {e}") return 0, 0 def main(): # 选择存储方式 print("请选择数据存储方式:") print("1. JSON") print("2. CSV") storage_choice = input("请输入选择 (1/2): ").strip() file_path = "students.json" if storage_choice == '1' else "students.csv" # 创建数据访问层 if storage_choice == '1': dal = JsonStudentDAL(file_path) elif storage_choice == '2': dal = CsvStudentDAL(file_path) else: print("无效的选择,默认使用JSON存储") dal = JsonStudentDAL("students.json") # 创建业务逻辑层 bll = StudentBLL(dal) # 创建用户界面 tui = StudentTUI(bll) tui.run() if __name__ == "__main__": main()