# -*- coding: utf-8 -*- """ Created on Tue Jun 24 11:10:36 2025 @author: 26745 """ from abc import ABC, abstractmethod from typing import List, Optional, Dict, Union from datetime import date, datetime import csv import json import os import re class IStudentDAL(ABC): """学生信息数据访问层接口""" @abstractmethod def add(self, student: 'Student') -> bool: """添加学生信息""" pass @abstractmethod def delete(self, id_card: str) -> bool: """根据身份证号删除学生信息""" pass @abstractmethod def update(self, student: 'Student') -> bool: """更新学生信息""" pass @abstractmethod def get_by_id(self, id_card: str) -> Optional['Student']: """根据身份证号获取学生信息""" pass @abstractmethod def get_by_stu_id(self, stu_id: str) -> Optional['Student']: """根据学号获取学生信息""" pass @abstractmethod def get_all(self) -> List['Student']: """获取所有学生信息""" pass @abstractmethod def search_by_name(self, name: str) -> List['Student']: """根据姓名模糊查询学生信息""" pass @abstractmethod def search_by_class(self, class_name: str) -> List['Student']: """根据班级模糊查询学生信息""" pass @abstractmethod def search_by_major(self, major: str) -> List['Student']: """根据专业模糊查询学生信息""" pass @abstractmethod def clear_all(self) -> bool: """清空所有学生数据""" pass class StudentCSVDAL(IStudentDAL): """CSV文件存储实现""" def __init__(self, file_path: str = 'students.csv'): 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, 'w', newline='', encoding='utf-8') as f: writer = csv.writer(f) writer.writerow([ 'name', 'id_card', 'stu_id', 'gender', 'height', 'weight', 'enrollment_date', 'class_name', 'major' ]) def _read_all(self) -> List[Dict]: """从CSV文件读取所有学生数据""" try: with open(self.file_path, 'r', newline='', encoding='utf-8') as f: reader = csv.DictReader(f) return list(reader) except FileNotFoundError: self._ensure_file_exists() return [] def _write_all(self, students: List[Dict]): """将所有学生数据写入CSV文件""" with open(self.file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=[ 'name', 'id_card', 'stu_id', 'gender', 'height', 'weight', 'enrollment_date', 'class_name', 'major' ]) writer.writeheader() writer.writerows(students) def add(self, student: 'Student') -> bool: students = self._read_all() # 检查身份证号或学号是否已存在 for s in students: if s['id_card'] == student.id_card: return False if s['stu_id'] == student.stu_id: return False students.append(student.to_dict()) self._write_all(students) return True def delete(self, id_card: str) -> bool: students = self._read_all() original_count = len(students) students = [s for s in students if s['id_card'] != id_card] if len(students) < original_count: self._write_all(students) return True return False def update(self, student: 'Student') -> bool: students = self._read_all() updated = False for i, s in enumerate(students): if s['id_card'] == student.id_card: students[i] = student.to_dict() updated = True break if updated: self._write_all(students) return True return False def get_by_id(self, id_card: str) -> Optional['Student']: students = self._read_all() for s in students: if s['id_card'] == id_card: return Student.from_dict(s) return None def get_by_stu_id(self, stu_id: str) -> Optional['Student']: students = self._read_all() for s in students: if s['stu_id'] == stu_id: return Student.from_dict(s) return None def get_all(self) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students] def search_by_name(self, name: str) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students if name.lower() in s['name'].lower()] def search_by_class(self, class_name: str) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students if s['class_name'] and class_name.lower() in s['class_name'].lower()] def search_by_major(self, major: str) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students if s['major'] and major.lower() in s['major'].lower()] def clear_all(self) -> bool: """清空所有学生数据""" self._ensure_file_exists() return True class StudentJSONDAL(IStudentDAL): """JSON文件存储实现""" def __init__(self, file_path: str = 'students.json'): 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, 'w', encoding='utf-8') as f: json.dump([], f) def _read_all(self) -> List[Dict]: """从JSON文件读取所有学生数据""" try: with open(self.file_path, 'r', encoding='utf-8') as f: return json.load(f) except (FileNotFoundError, json.JSONDecodeError): self._ensure_file_exists() return [] def _write_all(self, students: List[Dict]): """将所有学生数据写入JSON文件""" with open(self.file_path, 'w', encoding='utf-8') as f: json.dump(students, f, ensure_ascii=False, indent=2) def add(self, student: 'Student') -> bool: students = self._read_all() # 检查身份证号或学号是否已存在 for s in students: if s['id_card'] == student.id_card: return False if s['stu_id'] == student.stu_id: return False students.append(student.to_dict()) self._write_all(students) return True def delete(self, id_card: str) -> bool: students = self._read_all() original_count = len(students) students = [s for s in students if s['id_card'] != id_card] if len(students) < original_count: self._write_all(students) return True return False def update(self, student: 'Student') -> bool: students = self._read_all() updated = False for i, s in enumerate(students): if s['id_card'] == student.id_card: students[i] = student.to_dict() updated = True break if updated: self._write_all(students) return True return False def get_by_id(self, id_card: str) -> Optional['Student']: students = self._read_all() for s in students: if s['id_card'] == id_card: return Student.from_dict(s) return None def get_by_stu_id(self, stu_id: str) -> Optional['Student']: students = self._read_all() for s in students: if s['stu_id'] == stu_id: return Student.from_dict(s) return None def get_all(self) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students] def search_by_name(self, name: str) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students if name.lower() in s['name'].lower()] def search_by_class(self, class_name: str) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students if s['class_name'] and class_name.lower() in s['class_name'].lower()] def search_by_major(self, major: str) -> List['Student']: students = self._read_all() return [Student.from_dict(s) for s in students if s['major'] and major.lower() in s['major'].lower()] def clear_all(self) -> bool: """清空所有学生数据""" self._ensure_file_exists() return True class Student: """学生信息实体类""" def __init__( self, name: str, id_card: str, stu_id: str, gender: Optional[bool] = None, height: Optional[int] = None, weight: Optional[float] = None, enrollment_date: Union[date, str, None] = None, class_name: Optional[str] = None, major: Optional[str] = None, ): self.name = name self.id_card = id_card self.stu_id = stu_id self.gender = gender self.height = height self.weight = weight self.class_name = class_name self.major = major # 处理入学日期 if enrollment_date is None: self._enrollment_date = None elif isinstance(enrollment_date, date): self._enrollment_date = enrollment_date else: try: self._enrollment_date = date.fromisoformat(enrollment_date) except (ValueError, TypeError): self._enrollment_date = None @property def name(self) -> str: """获取学生姓名""" return self._name @name.setter def name(self, value: str): """设置学生姓名""" if not isinstance(value, str): raise TypeError("姓名必须是字符串") self._name = value @property def id_card(self) -> str: """获取身份证号""" return self._id_card @id_card.setter def id_card(self, value: str): """设置身份证号""" if not isinstance(value, str): raise TypeError("身份证号必须是字符串") self._id_card = value @property def stu_id(self) -> str: """获取学号""" return self._stu_id @stu_id.setter def stu_id(self, value: str): """设置学号""" if not isinstance(value, str): raise TypeError("学号必须是字符串") self._stu_id = value @property def enrollment_date(self) -> Optional[date]: """获取入学日期""" return self._enrollment_date @enrollment_date.setter def enrollment_date(self, value: Union[date, str, None]): """设置入学日期""" if value is None: self._enrollment_date = None elif isinstance(value, date): self._enrollment_date = value else: try: self._enrollment_date = date.fromisoformat(value) except (ValueError, TypeError): self._enrollment_date = None @property def birthday(self) -> Optional[date]: """从身份证号中提取出生日期""" if not self.id_card or len(self.id_card) != 18: return None try: birth_date_str = self.id_card[6:14] return date.fromisoformat(f"{birth_date_str[:4]}-{birth_date_str[4:6]}-{birth_date_str[6:8]}") except (ValueError, IndexError): return None @property def age(self) -> Optional[int]: """计算年龄""" if not self.birthday: return None today = date.today() age = today.year - self.birthday.year # 如果生日还没到,年龄减1 if (today.month, today.day) < (self.birthday.month, self.birthday.day): age -= 1 return age @property def errors(self) -> Dict[str, str]: """获取所有错误信息""" errors = {} # 验证姓名 if not (2 <= len(self.name) <= 20): errors['name'] = "姓名长度必须在2-20个字符之间" elif not re.match(r'^[\u4e00-\u9fa5A-Za-z]+$', self.name): errors['name'] = "姓名只能包含中文或英文字符" # 验证身份证号 id_card_errors = self._validate_id_card() if id_card_errors: errors['id_card'] = id_card_errors # 验证学号 if not self.stu_id: errors['stu_id'] = "学号不能为空" elif not re.match(r'^\d{8,20}$', self.stu_id): errors['stu_id'] = "学号必须是8-20位数字" # 验证身高 if self.height is not None and not (50 <= self.height <= 250): errors['height'] = "身高必须在50-250厘米之间" # 验证体重 if self.weight is not None: if not (5 <= self.weight <= 300): errors['weight'] = "体重必须在5-300千克之间" elif len(str(self.weight).split('.')[-1]) > 1: errors['weight'] = "体重最多保留一位小数" # 验证入学日期 if self.enrollment_date is not None: if self.enrollment_date > date.today(): errors['enrollment_date'] = "入学日期不能晚于当前日期" if self.birthday and self.enrollment_date < self.birthday: errors['enrollment_date'] = "入学日期不能早于出生日期" return errors @property def is_valid(self) -> bool: """判断学生信息是否有效""" return not bool(self.errors) def _validate_id_card(self) -> Optional[str]: """验证身份证号的有效性""" if not self.id_card: return "身份证号不能为空" # 长度检查 if len(self.id_card) != 18: return "身份证号长度必须为18位" # 前17位必须为数字 if not self.id_card[:17].isdigit(): return "身份证号前17位必须为数字" # 最后一位可以是数字或X last_char = self.id_card[-1].upper() if not (last_char.isdigit() or last_char == 'X'): return "身份证号最后一位必须是数字或X" # 校验码验证 check_code = self._get_check_code() if check_code != last_char: return f"身份证号校验位错误,应为{check_code}" # 出生日期验证 birth_date_str = self.id_card[6:14] if not self._validate_date(birth_date_str): return "身份证号中的出生日期无效" return None @staticmethod def _validate_date(date_str: str) -> bool: """验证日期字符串(YYYYMMDD)的有效性""" if len(date_str) != 8 or not date_str.isdigit(): return False try: year = int(date_str[:4]) month = int(date_str[4:6]) day = int(date_str[6:8]) # 简单的日期有效性检查 if not (1900 <= year <= datetime.now().year): return False if not (1 <= month <= 12): return False if not (1 <= day <= 31): return False # 更精确的日期检查 date.fromisoformat(f"{year}-{month:02d}-{day:02d}") return True except (ValueError, IndexError): return False def _get_check_code(self) -> str: """计算身份证号的校验码""" # 加权因子 weights = [2 ** (17 - i) % 11 for i in range(17)] # 计算加权和 total = sum(int(digit) * weight for digit, weight in zip(self.id_card[:17], weights)) # 计算校验码 check_code_map = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8', 5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'} return check_code_map[total % 11] def to_dict(self) -> Dict: """将学生对象转换为字典""" data = { 'name': self.name, 'id_card': self.id_card, 'stu_id': self.stu_id, 'gender': self.gender, 'height': self.height, 'weight': self.weight, 'enrollment_date': self.enrollment_date.isoformat() if self.enrollment_date else None, 'class_name': self.class_name, 'major': self.major } return data @classmethod def from_dict(cls, data: Dict) -> 'Student': """从字典创建学生对象""" data = data.copy() # 处理入学日期 if 'enrollment_date' in data and isinstance(data['enrollment_date'], str): try: data['enrollment_date'] = date.fromisoformat(data['enrollment_date']) except ValueError: data['enrollment_date'] = None return cls(**data) def __repr__(self) -> str: """返回对象的字符串表示""" return (f"Student(name='{self.name}', id_card='{self.id_card}', stu_id='{self.stu_id}', " f"gender={'男' if self.gender else '女' if self.gender is not None else None}, " f"height={self.height}, weight={self.weight}, " f"enrollment_date={self.enrollment_date.isoformat() if self.enrollment_date else None}, " f"class_name='{self.class_name}', major='{self.major}')") class StudentService: """学生信息业务逻辑层""" def __init__(self, dal: IStudentDAL): self.dal = dal def add_student(self, student_data: Dict) -> Dict: """添加学生信息""" student = Student.from_dict(student_data) if not student.is_valid: return {'success': False, 'message': '学生信息不合法', 'errors': student.errors} # 检查身份证号是否已存在 if self.dal.get_by_id(student.id_card): return {'success': False, 'message': '身份证号已存在'} # 检查学号是否已存在 if self.dal.get_by_stu_id(student.stu_id): return {'success': False, 'message': '学号已存在'} if self.dal.add(student): return {'success': True, 'message': '添加成功'} return {'success': False, 'message': '添加失败'} def delete_student(self, id_card: str) -> Dict: """删除学生信息""" if self.dal.delete(id_card): return {'success': True, 'message': '删除成功'} return {'success': False, 'message': '学生不存在'} from typing import Dict, List, Optional, Union from datetime import date import re def update_student(self, identifier: Dict[str, str], update_data: Dict) -> Dict: """ 通用更新学生信息方法 :param identifier: 识别学生的条件字典,如 {'id_card': '123...'} 或 {'stu_id': '2023001'} :param update_data: 要更新的数据字典 :return: 操作结果字典 """ # 1. 根据条件查找学生 student = self._find_student_by_identifier(identifier) if not student: return {'success': False, 'message': '未找到匹配的学生'} # 2. 合并更新数据 updated_student = self._merge_student_data(student, update_data) # 3. 验证数据 if not updated_student.is_valid: return {'success': False, 'message': '更新数据不合法', 'errors': updated_student.errors} # 4. 检查学号冲突(如果更新了学号) if 'stu_id' in update_data and update_data['stu_id'] != student.stu_id: existing = self.dal.get_by_stu_id(update_data['stu_id']) if existing and existing.id_card != student.id_card: return {'success': False, 'message': '学号已被其他学生使用'} # 5. 执行更新 if self.dal.update(updated_student): return {'success': True, 'message': '更新成功'} return {'success': False, 'message': '更新失败'} def _find_student_by_identifier(self, identifier: Dict[str, str]) -> Optional[Student]: """根据不同的识别条件查找学生""" if 'id_card' in identifier: return self.dal.get_by_id(identifier['id_card']) elif 'stu_id' in identifier: return self.dal.get_by_stu_id(identifier['stu_id']) elif 'name' in identifier: students = self.dal.search_by_name(identifier['name']) if len(students) == 1: return students[0] return None def _merge_student_data(self, original: Student, update_data: Dict) -> Student: """合并原始学生数据和更新数据""" original_data = original.to_dict() merged_data = {**original_data, **update_data} return Student.from_dict(merged_data) def batch_update_students(self, condition: Dict[str, str], update_data: Dict) -> Dict: """ 批量更新学生信息 :param condition: 筛选条件,如 {'class_name': '计算机23-1'} :param update_data: 要更新的数据 :return: 操作结果字典 """ # 1. 根据条件查找学生 students = self._find_students_by_condition(condition) if not students: return {'success': False, 'message': '未找到匹配的学生'} results = { 'total': len(students), 'success': 0, 'failed': 0, 'details': [] } # 2. 逐个更新 for student in students: result = self.update_student( {'id_card': student.id_card}, update_data ) if result['success']: results['success'] += 1 else: results['failed'] += 1 results['details'].append({ 'id_card': student.id_card, 'success': result['success'], 'message': result.get('message', '') }) return { 'success': True, 'message': f'批量更新完成,成功{results["success"]}个,失败{results["failed"]}个', 'results': results } def _find_students_by_condition(self, condition: Dict[str, str]) -> List[Student]: """根据条件查找多个学生""" if 'class_name' in condition: return self.dal.search_by_class(condition['class_name']) elif 'major' in condition: return self.dal.search_by_major(condition['major']) elif 'name' in condition: return self.dal.search_by_name(condition['name']) return [] def get_student_by_id(self, id_card: str) -> Optional[Student]: """根据身份证号获取学生信息""" return self.dal.get_by_id(id_card) def get_student_by_stu_id(self, stu_id: str) -> Optional[Student]: """根据学号获取学生信息""" return self.dal.get_by_stu_id(stu_id) def get_all_students(self) -> List[Student]: """获取所有学生信息""" return self.dal.get_all() def search_students_by_name(self, name: str) -> List[Student]: """根据姓名模糊查询学生信息""" return self.dal.search_by_name(name) def search_students_by_class(self, class_name: str) -> List[Student]: """根据班级模糊查询学生信息""" return self.dal.search_by_class(class_name) def search_students_by_major(self, major: str) -> List[Student]: """根据专业模糊查询学生信息""" return self.dal.search_by_major(major) def get_student_count(self) -> int: """获取学生总数""" return len(self.dal.get_all()) def get_major_statistics(self) -> Dict[str, int]: """统计各专业学生人数""" students = self.dal.get_all() stats = {} for student in students: if student.major: stats[student.major] = stats.get(student.major, 0) + 1 return stats def get_height_statistics(self) -> Dict[str, float]: """计算平均身高""" students = self.dal.get_all() total_height = 0 count = 0 for student in students: if student.height: total_height += student.height count += 1 return {'average_height': total_height / count if count > 0 else 0} def get_weight_statistics(self) -> Dict[str, float]: """计算平均体重""" students = self.dal.get_all() total_weight = 0.0 count = 0 for student in students: if student.weight: total_weight += student.weight count += 1 return {'average_weight': total_weight / count if count > 0 else 0} def export_to_csv(self, file_path: str) -> bool: """导出数据到CSV文件""" try: students = self.dal.get_all() data = [student.to_dict() for student in students] with open(file_path, 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=[ 'name', 'id_card', 'stu_id', 'gender', 'height', 'weight', 'enrollment_date', 'class_name', 'major' ]) writer.writeheader() writer.writerows(data) return True except Exception as e: print(f"导出失败: {e}") return False def import_from_csv(self, file_path: str) -> Dict: """从CSV文件导入数据""" try: with open(file_path, 'r', newline='', encoding='utf-8') as f: reader = csv.DictReader(f) imported = 0 skipped = 0 for row in reader: student = Student.from_dict(row) if not student.is_valid: skipped += 1 continue # 检查是否已存在 if self.dal.get_by_id(student.id_card) or self.dal.get_by_stu_id(student.stu_id): skipped += 1 continue if self.dal.add(student): imported += 1 else: skipped += 1 return { 'success': True, 'message': f'导入完成 (成功: {imported}, 跳过: {skipped})', 'imported': imported, 'skipped': skipped } except Exception as e: return { 'success': False, 'message': f'导入失败: {e}' } def clear_all_data(self) -> bool: """清空所有学生数据""" return self.dal.clear_all() class StudentManagementUI: """学生信息管理系统用户界面""" def __init__(self): # 默认使用JSON存储 self.dal = StudentJSONDAL() self.service = StudentService(self.dal) def display_menu(self): """显示主菜单""" print("\n===== 学生信息管理系统 =====") print("1. 添加学生信息") print("2. 删除学生信息") print("3. 更新学生信息") print("4. 查询学生信息") print("5. 统计分析") print("6. 数据导入导出") print("7. 切换存储方式") print("8. 清空所有学生信息") print("0. 退出系统") print("==========================") def run(self): """运行系统""" while True: self.display_menu() choice = input("请选择操作: ") if choice == '1': self.add_student_ui() elif choice == '2': self.delete_student_ui() elif choice == '3': self.update_student_ui() elif choice == '4': self.query_students_ui() elif choice == '5': self.statistics_ui() elif choice == '6': self.import_export_ui() elif choice == '7': self.switch_storage_ui() elif choice == '8': self.clear_all_data_ui() elif choice == '0': print("感谢使用学生信息管理系统,再见!") sys.exit() else: print("无效的选择,请重新输入!") def add_student_ui(self): """添加学生信息界面""" print("\n===== 添加学生信息 =====") student_data = {} student_data['name'] = input("姓名: ") student_data['id_card'] = input("身份证号: ") student_data['stu_id'] = input("学号: ") gender = input("性别(男/女/跳过): ").strip() if gender == '男': student_data['gender'] = True elif gender == '女': student_data['gender'] = False height = input("身高(cm,跳过请直接回车): ").strip() if height: try: student_data['height'] = int(height) except ValueError: print("身高必须是整数") return weight = input("体重(kg,跳过请直接回车): ").strip() if weight: try: student_data['weight'] = float(weight) except ValueError: print("体重必须是数字") return enrollment_date = input("入学日期(YYYY-MM-DD,跳过请直接回车): ").strip() if enrollment_date: student_data['enrollment_date'] = enrollment_date student_data['class_name'] = input("班级名称(跳过请直接回车): ").strip() or None student_data['major'] = input("专业名称(跳过请直接回车): ").strip() or None result = self.service.add_student(student_data) if result['success']: print("添加学生信息成功!") else: print(f"添加失败: {result['message']}") if 'errors' in result: for field, error in result['errors'].items(): print(f"{field}: {error}") def delete_student_ui(self): """删除学生信息界面""" print("\n===== 删除学生信息 =====") id_card = input("请输入要删除的学生身份证号: ") result = self.service.delete_student(id_card) if result['success']: print("删除学生信息成功!") else: print(f"删除失败: {result['message']}") def update_student_ui(self): """更新学生信息界面""" print("\n===== 更新学生信息 =====") print("请选择查找学生的方式:") print("1. 按身份证号") print("2. 按学号") print("3. 按姓名") print("4. 批量更新(按班级)") print("5. 批量更新(按专业)") print("0. 返回上级菜单") choice = input("请选择操作: ") if choice == '1': self._update_by_id_card() elif choice == '2': self._update_by_stu_id() elif choice == '3': self._update_by_name() elif choice == '4': self._batch_update_by_class() elif choice == '5': self._batch_update_by_major() elif choice == '0': return else: print("无效的选择!") def _update_by_id_card(self): """按身份证号更新""" id_card = input("请输入学生身份证号: ") student = self.service.get_student_by_id(id_card) if not student: print("未找到该学生!") return print("当前学生信息:") self.display_student_details(student) self._perform_update({'id_card': id_card}) def _update_by_stu_id(self): """按学号更新""" stu_id = input("请输入学生学号: ") student = self.service.get_student_by_stu_id(stu_id) if not student: print("未找到该学生!") return print("当前学生信息:") self.display_student_details(student) self._perform_update({'stu_id': stu_id}) def _update_by_name(self): """按姓名更新""" name = input("请输入学生姓名: ") students = self.service.search_students_by_name(name) if not students: print("未找到该学生!") return elif len(students) > 1: print(f"找到{len(students)}个同名学生,请使用身份证号或学号更新") return print("当前学生信息:") self.display_student_details(students[0]) self._perform_update({'id_card': students[0].id_card}) def _batch_update_by_class(self): """按班级批量更新""" class_name = input("请输入要更新的班级名称: ") self._perform_batch_update({'class_name': class_name}) def _batch_update_by_major(self): """按专业批量更新""" major = input("请输入要更新的专业名称: ") self._perform_batch_update({'major': major}) def _perform_update(self, identifier: Dict[str, str]): """执行单个学生更新""" print("\n请输入要更新的信息(直接回车保持原值):") update_data = {} name = input("姓名: ").strip() if name: update_data['name'] = name stu_id = input("学号: ").strip() if stu_id: update_data['stu_id'] = stu_id gender = input("性别(男/女/跳过): ").strip() if gender == '男': update_data['gender'] = True elif gender == '女': update_data['gender'] = False height = input("身高(cm): ").strip() if height: try: update_data['height'] = int(height) except ValueError: print("身高必须是整数") return weight = input("体重(kg): ").strip() if weight: try: update_data['weight'] = float(weight) except ValueError: print("体重必须是数字") return enrollment_date = input("入学日期(YYYY-MM-DD): ").strip() if enrollment_date: update_data['enrollment_date'] = enrollment_date class_name = input("班级名称: ").strip() if class_name: update_data['class_name'] = class_name or None major = input("专业名称: ").strip() if major: update_data['major'] = major or None result = self.service.update_student(identifier, update_data) if result['success']: print("更新成功!") else: print(f"更新失败: {result['message']}") if 'errors' in result: for field, error in result['errors'].items(): print(f"{field}: {error}") def _perform_batch_update(self, condition: Dict[str, str]): """执行批量更新""" print("\n请输入要批量更新的信息:") update_data = {} class_name = input("班级名称(直接回车跳过): ").strip() if class_name: update_data['class_name'] = class_name or None major = input("专业名称(直接回车跳过): ").strip() if major: update_data['major'] = major or None enrollment_date = input("入学日期(YYYY-MM-DD,直接回车跳过): ").strip() if enrollment_date: update_data['enrollment_date'] = enrollment_date if not update_data: print("没有提供任何更新数据!") return confirm = input(f"确定要批量更新{condition}的学生信息吗?(y/n): ").lower() if confirm != 'y': print("操作已取消") return result = self.service.batch_update_students(condition, update_data) print(result['message']) if result['success'] and result['results']['failed'] > 0: print("\n失败详情:") for detail in result['results']['details']: if not detail['success']: print(f"身份证号: {detail['id_card']}, 原因: {detail['message']}") def query_students_ui(self): """查询学生信息界面""" while True: print("\n===== 查询学生信息 =====") print("1. 查询所有学生") print("2. 按身份证号查询") print("3. 按学号查询") print("4. 按姓名查询") print("5. 按班级查询") print("6. 按专业查询") print("7. 返回上一级") print("======================") choice = input("请选择查询方式: ") if choice == '1': self.query_all_students() elif choice == '2': self.query_by_id_card() elif choice == '3': self.query_by_stu_id() elif choice == '4': self.query_by_name() elif choice == '5': self.query_by_class() elif choice == '6': self.query_by_major() elif choice == '7': break else: print("无效的选择,请重新输入!") def query_all_students(self): """查询所有学生""" students = self.service.get_all_students() if not students: print("没有学生信息!") return print("\n所有学生信息:") print("-" * 80) print(f"{'姓名':<10}{'学号':<15}{'身份证号':<20}{'班级':<15}{'专业':<20}") print("-" * 80) for student in students: print(f"{student.name:<10}{student.stu_id:<15}{student.id_card:<20}" f"{student.class_name or '未设置':<15}{student.major or '未设置':<20}") print("-" * 80) print(f"共 {len(students)} 名学生") def query_by_id_card(self): """按身份证号查询""" id_card = input("请输入身份证号: ") student = self.service.get_student_by_id(id_card) if student: self.display_student_details(student) else: print("未找到该学生!") def query_by_stu_id(self): """按学号查询""" stu_id = input("请输入学号: ") student = self.service.get_student_by_stu_id(stu_id) if student: self.display_student_details(student) else: print("未找到该学生!") def query_by_name(self): """按姓名查询""" name = input("请输入姓名(支持模糊查询): ") students = self.service.search_students_by_name(name) if students: print(f"\n找到 {len(students)} 名学生:") for student in students: self.display_student_details(student) print("-" * 40) else: print("未找到匹配的学生!") def query_by_class(self): """按班级查询""" class_name = input("请输入班级名称(支持模糊查询): ") students = self.service.search_students_by_class(class_name) if students: print(f"\n找到 {len(students)} 名学生:") for student in students: self.display_student_details(student) print("-" * 40) else: print("未找到匹配的学生!") def query_by_major(self): """按专业查询""" major = input("请输入专业名称(支持模糊查询): ") students = self.service.search_students_by_major(major) if students: print(f"\n找到 {len(students)} 名学生:") for student in students: self.display_student_details(student) print("-" * 40) else: print("未找到匹配的学生!") def display_student_details(self, student: Student): """显示学生详细信息""" print("\n学生详细信息:") print(f"姓名: {student.name}") print(f"身份证号: {student.id_card}") print(f"学号: {student.stu_id}") print(f"性别: {'男' if student.gender else '女' if student.gender is not None else '未设置'}") print(f"出生日期: {student.birthday.isoformat() if student.birthday else '未知'}") print(f"年龄: {student.age if student.age else '未知'}") print(f"身高: {student.height}cm" if student.height else "身高: 未设置") print(f"体重: {student.weight}kg" if student.weight else "体重: 未设置") print(f"入学日期: {student.enrollment_date.isoformat() if student.enrollment_date else '未设置'}") print(f"班级: {student.class_name or '未设置'}") print(f"专业: {student.major or '未设置'}") def statistics_ui(self): """统计分析界面""" while True: print("\n===== 统计分析 =====") print("1. 学生总数") print("2. 平均身高") print("3. 平均体重") print("4. 专业统计") print("5. 返回上一级") print("===================") choice = input("请选择统计项目: ") if choice == '1': count = self.service.get_student_count() print(f"\n学生总数: {count}") elif choice == '2': stats = self.service.get_height_statistics() print(f"\n平均身高: {stats['average_height']:.1f}cm") elif choice == '3': stats = self.service.get_weight_statistics() print(f"\n平均体重: {stats['average_weight']:.1f}kg") elif choice == '4': stats = self.service.get_major_statistics() print("\n各专业学生人数:") for major, count in stats.items(): print(f"{major}: {count}人") elif choice == '5': break else: print("无效的选择,请重新输入!") def import_export_ui(self): """数据导入导出界面""" while True: print("\n===== 数据导入导出 =====") print("1. 导出数据到CSV") print("2. 从CSV导入数据") print("3. 返回上一级") print("======================") choice = input("请选择操作: ") if choice == '1': file_path = input("请输入导出文件路径(默认: students_export.csv): ").strip() or "students_export.csv" if self.service.export_to_csv(file_path): print(f"数据已成功导出到 {file_path}") else: print("导出失败!") elif choice == '2': file_path = input("请输入要导入的CSV文件路径: ").strip() result = self.service.import_from_csv(file_path) print(result['message']) elif choice == '3': break else: print("无效的选择,请重新输入!") def switch_storage_ui(self): """切换存储方式界面""" print("\n===== 切换存储方式 =====") print(f"当前存储方式: {'JSON' if isinstance(self.dal, StudentJSONDAL) else 'CSV'}") print("1. 切换到JSON存储") print("2. 切换到CSV存储") print("3. 返回上一级") print("======================") choice = input("请选择存储方式: ") if choice == '1': self.dal = StudentJSONDAL() self.service = StudentService(self.dal) print("已切换到JSON存储") elif choice == '2': self.dal = StudentCSVDAL() self.service = StudentService(self.dal) print("已切换到CSV存储") elif choice == '3': return else: print("无效的选择!") def clear_all_data_ui(self): """清空所有数据界面""" confirm = input("确定要清空所有学生数据吗?此操作不可恢复!(y/n): ").strip().lower() if confirm == 'y': if self.service.clear_all_data(): print("所有学生数据已清空!") else: print("清空数据失败!") else: print("操作已取消") if __name__ == '__main__': ui = StudentManagementUI() ui.run()