diff --git a/bll/student_bll.py b/bll/student_bll.py index 5b110c1..23d279b 100644 --- a/bll/student_bll.py +++ b/bll/student_bll.py @@ -14,13 +14,18 @@ class StudentBLL: if not student.is_valid: raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}") return self.dal.add_student(student) - # 其他方法同理 - def delete_student(self, sid: str) ->bool: + 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 delete_student_by_id_number(self, id_number: str) -> bool: + student = self.dal.find_student_by_id_number(id_number) + if not student: + raise ValueError(f"身份证号为 {id_number} 的学生不存在。") + return self.dal.delete_student_by_id_number(id_number) + def update_student(self, sid: str, student: Student) -> bool: if not self.dal.check_student_exists(sid): raise ValueError(f"学生 ID {sid} 不存在。") @@ -30,20 +35,39 @@ class StudentBLL: 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: + class_name: Optional[str] = None, major: Optional[str] = None, + phone: Optional[str] = None, email: 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) + return self.dal.update_student_partial(sid, name, height, birth_date, enrollment_date, + class_name, major, phone, email) def get_student_by_id(self, sid: str) -> Optional[Student]: return self.dal.find_student_by_sid(sid) + def find_student_by_id_number(self, id_number: str) -> Optional[Student]: + return self.dal.find_student_by_id_number(id_number) + 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_students_by_major(self, major: str) -> List[Student]: + return self.dal.find_students_by_major(major) + def get_all_students(self) -> List[Student]: return self.dal.get_all_students() + def count_students_by_major(self): + """统计各专业学生人数""" + return self.dal.count_students_by_major() + + def calculate_average_height_weight(self, group_by='major'): + """计算平均身高/体重(按班级或专业)""" + return self.dal.calculate_average_height_weight(group_by) + + def calculate_gender_ratio(self): + """统计性别比例""" + return self.dal.calculate_gender_ratio() \ No newline at end of file diff --git a/dal/student_dal_json.py b/dal/student_dal_json.py index 7de372d..662e9cf 100644 --- a/dal/student_dal_json.py +++ b/dal/student_dal_json.py @@ -1,117 +1,10 @@ import json -import os, sys,csv +import os +import 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 @@ -148,13 +41,24 @@ class StudentDAL: return student return None - def find_students_by_name(self, name: str) ->List[Student]: + def find_student_by_id_number(self, id_number: str) -> Optional[Student]: + students = self.load_students() + for student in students: + if student.id_number == id_number: + 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]: + 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()] + return [student for student in students if class_name.lower() in student.class_name.lower()] + + def find_students_by_major(self, major: str) -> List[Student]: + students = self.load_students() + return [student for student in students if major.lower() in student.major.lower()] def get_all_students(self) -> List[Student]: return self.load_students() @@ -168,7 +72,10 @@ class StudentDAL: 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: + 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, major: Optional[str] = None, + phone: Optional[str] = None, email: Optional[str] = None) -> bool: students = self.load_students() for i, student in enumerate(students): if student.sid == sid: @@ -182,12 +89,17 @@ class StudentDAL: students[i].enrollment_date = enrollment_date if class_name is not None: students[i].class_name = class_name + if major is not None: + students[i].major = major + if phone is not None: + students[i].phone = phone + if email is not None: + students[i].email = email 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] @@ -196,13 +108,22 @@ class StudentDAL: return True return False + def delete_student_by_id_number(self, id_number: str) -> bool: + students = self.load_students() + initial_length = len(students) + students = [student for student in students if student.id_number != id_number] + 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: + 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): @@ -226,12 +147,28 @@ class StudentDAL: 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()) + if not students: + print("没有数据可导出") + return + try: + with open(file_path, 'w', encoding='utf-8-sig', newline='') as f: + writer = csv.DictWriter( + f, + fieldnames=[ + 'sid', 'name', 'gender', 'height', 'weight', 'birth_date', 'enrollment_date', + 'class_name', 'id_number', 'phone', 'email', 'major' + ] + ) + 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}") def import_from_csv(self, file_path): try: @@ -253,18 +190,18 @@ class StudentDAL: students = self.load_students() if not students: return 0 - total_height = sum(student.height for student in students) + 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): + def count_students_by_height_range(self, min_height: int, max_height: int): 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): + def count_students_by_enrollment_year(self, year: int): 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): + def count_students_by_age_range(self, min_age: int, max_age: int): 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]) @@ -287,4 +224,52 @@ class StudentDAL: except FileNotFoundError: print(f"备份文件 {backup_path} 未找到。") except Exception as e: - print(f"数据恢复失败: {e}") \ No newline at end of file + print(f"数据恢复失败: {e}") + + def count_students_by_major(self): + """统计各专业学生人数""" + students = self.load_students() + major_count = {} + for student in students: + major = student.major + if major in major_count: + major_count[major] += 1 + else: + major_count[major] = 1 + return major_count + + def calculate_average_height_weight(self, group_by='major'): + """计算平均身高/体重(按班级或专业)""" + students = self.load_students() + group_info = {} + for student in students: + group_key = getattr(student, group_by) + if group_key not in group_info: + group_info[group_key] = {'height_sum': 0, 'weight_sum': 0, 'count': 0} + group_info[group_key]['height_sum'] += student.height + group_info[group_key]['weight_sum'] += student.weight + group_info[group_key]['count'] += 1 + + result = {} + for group, info in group_info.items(): + average_height = info['height_sum'] / info['count'] + average_weight = info['weight_sum'] / info['count'] + result[group] = {'average_height': average_height, 'average_weight': average_weight} + return result + + def calculate_gender_ratio(self): + """统计性别比例""" + students = self.load_students() + male_count = 0 + female_count = 0 + for student in students: + if student.gender == '男': + male_count += 1 + elif student.gender == '女': + female_count += 1 + total_count = male_count + female_count + if total_count == 0: + return {'male_ratio': 0, 'female_ratio': 0} + male_ratio = male_count / total_count + female_ratio = female_count / total_count + return {'male_ratio': male_ratio, 'female_ratio': female_ratio} \ No newline at end of file diff --git a/data/student.json b/data/student.json index 583a954..0637a08 100644 --- a/data/student.json +++ b/data/student.json @@ -1,10 +1 @@ -[ - { - "sid": "110309230907041", - "name": "cgt", - "height": 180, - "birth_date": "2002-01-25", - "enrollment_date": "2023-09-01", - "class_name": "cs" - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/main.py b/main.py index c2ee90a..7b8a2bb 100644 --- a/main.py +++ b/main.py @@ -1,129 +1,9 @@ -from datetime import date - from ui.studentui import StudentUI def main(): + """主函数""" ui = StudentUI() - while True: - ui.display_menu() - choice = ui.get_input("请输入你的选择: ") - if choice == '1': - ui.add_student() - elif choice == '2': - sid = ui.get_input("请输入要删除的学生学号: ") - try: - if ui.bll.delete_student(sid): - print("学生信息删除成功!") - else: - print("学生信息删除失败。") - except ValueError as e: - print(e) - elif choice == '3': - sid = ui.get_input("请输入要更新的学生学号: ") - student = ui.bll.get_student_by_id(sid) - if student: - # 获取更新信息 - name = ui.get_input(f"请输入新的姓名(当前:{student.name}),不修改请直接回车: ") - height = ui.get_input(f"请输入新的身高(当前:{student.height}),不修改请直接回车: ") - birth_date = ui.get_input(f"请输入新的出生日期(当前:{student.birth_date}),不修改请直接回车: ") - enrollment_date = ui.get_input(f"请输入新的入学日期(当前:{student.enrollment_date}),不修改请直接回车: ") - class_name = ui.get_input(f"请输入新的班级(当前:{student.class_name}),不修改请直接回车: ") - - if name: - student.name = name - if height: - student.height = int(height) - if birth_date: - student.birth_date = date.fromisoformat(birth_date) - if enrollment_date: - student.enrollment_date = date.fromisoformat(enrollment_date) - if class_name: - student.class_name = class_name - - try: - if ui.bll.update_student(sid, student): - print("学生信息更新成功!") - else: - print("学生信息更新失败。") - except ValueError as e: - print(e) - else: - print(f"学生 ID {sid} 不存在。") - elif choice == '4': - ui.display_query_menu() - query_choice = ui.get_input("请输入你的查询选择: ") - if query_choice == '1': - students = ui.bll.get_all_students() - for student in students: - print(student) - elif query_choice == '2': - sid = ui.get_input("请输入要查询的学生学号: ") - student = ui.bll.get_student_by_id(sid) - if student: - print(student) - else: - print(f"学生 ID {sid} 不存在。") - elif query_choice == '3': - # 这里和按学号查询逻辑一样 - sid = ui.get_input("请输入要查询的学生学号: ") - student = ui.bll.get_student_by_id(sid) - if student: - print(student) - else: - print(f"学生 ID {sid} 不存在。") - elif query_choice == '4': - name = ui.get_input("请输入要查询的学生姓名: ") - students = ui.bll.get_students_by_name(name) - for student in students: - print(student) - elif query_choice == '5': - class_name = ui.get_input("请输入要查询的班级: ") - students = ui.bll.get_students_by_class(class_name) - for student in students: - print(student) - elif query_choice == '0': - continue - elif choice == '5': - ui.display_stats_menu() - stats_choice = ui.get_input("请输入你的统计选择: ") - if stats_choice == '1': - count = ui.bll.dal.get_student_count() - print(f"学生总数为: {count}") - elif stats_choice == '2': - average_height = ui.bll.dal.get_average_height() - print(f"学生平均身高为: {average_height:.2f} cm") - elif stats_choice == '3': - min_height = int(ui.get_input("请输入最小身高: ")) - max_height = int(ui.get_input("请输入最大身高: ")) - count = ui.bll.dal.count_students_by_height_range(min_height, max_height) - print(f"身高在 {min_height} - {max_height} cm 之间的学生数量为: {count}") - elif stats_choice == '4': - year = int(ui.get_input("请输入入学年份: ")) - count = ui.bll.dal.count_students_by_enrollment_year(year) - print(f"{year} 年入学的学生数量为: {count}") - elif stats_choice == '5': - min_age = int(ui.get_input("请输入最小年龄: ")) - max_age = int(ui.get_input("请输入最大年龄: ")) - count = ui.bll.dal.count_students_by_age_range(min_age, max_age) - print(f"年龄在 {min_age} - {max_age} 岁之间的学生数量为: {count}") - elif stats_choice == '0': - continue - elif choice == '6': - ui.display_import_export_menu() - import_export_choice = ui.get_input("请输入你的导入导出选择: ") - if import_export_choice != '0': - ui.handle_import_export(import_export_choice) - else: - continue - - elif choice == '7': - try: - ui.bll.dal.clear_all_students() - print("所有学生信息已清空!") - except Exception as e: - print(f"清空学生信息失败: {e}") - elif choice == '0': - break + ui.run() -if __name__ == '__main__': - main() +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/model/Student.py b/model/Student.py index dafa680..5293745 100644 --- a/model/Student.py +++ b/model/Student.py @@ -1,27 +1,46 @@ from datetime import date +import re class Student: - def __init__(self, sid: str, name: str, height: str, birth_date: date | str, enrollment_date: date | str, class_name: str): + def __init__(self, sid: str, name: str, gender: str, height: int, weight: float, birth_date: date | str, + enrollment_date: date | str, class_name: str, id_number: str, phone: str, email: str, major: str): self.sid = sid self.name = name.strip() - self.height = int(height) # 转换为整数类型 + self.gender = gender + self.height = height + self.weight = weight 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.id_number = id_number + self.phone = phone + self.email = email + self.major = major self._validation_errors = [] self._validate_height() + self._validate_weight() self._validate_date() self._validate_name() + self._validate_id_number() + self._validate_gender() def _validate_height(self) -> None: if not (50 <= self.height <= 250): self._validation_errors.append(f"身高{self.height}cm超出合理范围") + def _validate_weight(self) -> None: + if not (5 <= self.weight <= 300): + self._validation_errors.append(f"体重{self.weight}kg超出合理范围") + 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(): + if not (2 <= len(self.name) <= 20): self._validation_errors.append("姓名长度需在2-20个字符之间") + if not all('\u4e00' <= char <= '\u9fff' or char.isascii() for char in self.name): + self._validation_errors.append("姓名只能包含中文、英文或数字") + + def _validate_id_number(self) -> None: + if not re.match(r'^\d{17}[\dXx]$', self.id_number): + self._validation_errors.append("身份证号格式不正确") def _validate_date(self) -> None: today = date.today() @@ -29,8 +48,12 @@ class Student: self._validation_errors.append('出生日期不能在未来') if self.enrollment_date > today: self._validation_errors.append('入学日期不能在未来') - if self.birth_date > self.enrollment_date: - self._validation_errors.append('入学日期不能早于出生日期') + if (self.enrollment_date.year - self.birth_date.year) < 15: + self._validation_errors.append('入学年龄应至少15岁') + + def _validate_gender(self) -> None: + if self.gender not in ['男', '女']: + self._validation_errors.append('性别只能是"男"或"女"') @property def is_valid(self) -> bool: @@ -44,47 +67,66 @@ class Student: return NotImplemented return (self.sid == other.sid and self.name == other.name and + self.gender == other.gender and self.height == other.height and + self.weight == other.weight and self.birth_date == other.birth_date and self.enrollment_date == other.enrollment_date and - self.class_name == other.class_name - ) + self.class_name == other.class_name and + self.id_number == other.id_number and + self.phone == other.phone and + self.email == other.email and + self.major == other.major) @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']) + 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(), + name=data['name'], + gender=data['gender'], height=data['height'], + weight=data['weight'], birth_date=birth_date, enrollment_date=enrollment_date, - class_name=data['class_name'] + class_name=data['class_name'], + id_number=data['id_number'], + phone=data['phone'], + email=data['email'], + major=data['major'] ) - def to_dict(self): + def to_dict(self) -> dict: 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 - } + 'sid': self.sid, + 'name': self.name, + 'gender': self.gender, + 'height': self.height, + 'weight': self.weight, + 'birth_date': self.birth_date.isoformat(), + 'enrollment_date': self.enrollment_date.isoformat(), + 'class_name': self.class_name, + 'id_number': self.id_number, + 'phone': self.phone, + 'email': self.email, + 'major': self.major + } 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"name='{self.name}', " + f"gender='{self.gender}', " + f"height={self.height}, " + f"weight={self.weight}, " + 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"id_number='{self.id_number}', " + f"phone='{self.phone}', " + f"email='{self.email}', " + f"major='{self.major}'" f")" - ) - - + ) \ No newline at end of file diff --git a/ui/studentui.py b/ui/studentui.py index db79a6c..1e29384 100644 --- a/ui/studentui.py +++ b/ui/studentui.py @@ -4,19 +4,18 @@ 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") + 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') @@ -33,6 +32,17 @@ class StudentUI: 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') @@ -44,6 +54,7 @@ class StudentUI: print("3. 按学号查询") print("4. 按姓名查询") print("5. 按班级查询") + print("6. 按专业查询") print("0. 返回上一级") print("*" * 50) @@ -58,6 +69,10 @@ class StudentUI: print("3. 按身高范围统计") print("4. 按入学年份统计") print("5. 按年龄范围统计") + print("6. 统计各专业学生人数") + print("7. 计算平均身高/体重(按班级)") + print("8. 计算平均身高/体重(按专业)") + print("9. 统计性别比例") print("0. 返回上一级") print("*" * 50) @@ -71,6 +86,8 @@ class StudentUI: print("2. 从JSON导入数据") print("3. 导出数据到CSV") print("4. 从CSV导入数据") + print("5. 备份数据") + print("6. 恢复数据") print("0. 返回上一级") print("*" * 50) @@ -89,62 +106,94 @@ class StudentUI: 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 位身份证号。") + 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("请输入性别(男/女): ") + 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 height <= 0 or height > 250: - raise ValueError - break + if 50 <= height <= 250: + break + print("身高必须在50到250cm之间,请重新输入。") except ValueError: - print("身高必须为0到250之间的有效数值,请重新输入") + print("身高必须为有效的数值,请重新输入。") while True: try: weight = float(self.get_input("请输入体重(kg): ")) - if weight <= 0 or weight > 500: - raise ValueError - break + if 5 <= weight <= 300: + break + print("体重必须在5到300kg之间,请重新输入。") except ValueError: - print("体重必须为0到500之间的有效数值,请重新输入") + 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) - break + if enrollment_date_obj >= birth_date: + break + print("入学日期不能早于出生日期,请重新输入。") except ValueError: - print("日期格式错误,请使用 YYYY-MM-DD 格式(例如:2023-09-01)") + print("日期格式错误,请使用YYYY-MM-DD格式(例如:2023-09-01)") class_name = self.get_input("请输入班级: ") major = self.get_input("请输入专业: ") - # 创建 Student 对象 + # 创建Student对象 try: student = Student( sid=student_id, name=name, + gender=gender, height=height, - birth_date=birth_date, # 使用从身份证提取的出生日期 + weight=weight, + birth_date=birth_date, enrollment_date=enrollment_date_obj, - class_name=class_name + class_name=class_name, + id_number=id_number, + phone=phone, + email=email, + major=major ) if self.bll.add_student(student): print("学生信息添加成功!") @@ -153,20 +202,333 @@ class StudentUI: 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 文件路径: ") + file_path = self.get_input("请输入要导出的JSON文件路径: ") self.bll.dal.export_to_json(file_path) - print("数据导出到 JSON 成功!") + print("数据导出到JSON成功!") elif choice == '2': - file_path = self.get_input("请输入要导入的 JSON 文件路径: ") + file_path = self.get_input("请输入要导入的JSON文件路径: ") self.bll.dal.import_from_json(file_path) - print("数据从 JSON 导入成功!") + print("数据从JSON导入成功!") elif choice == '3': - file_path = self.get_input("请输入要导出的 CSV 文件路径: ") + file_path = self.get_input("请输入要导出的CSV文件路径: ") self.bll.dal.export_to_csv(file_path) - print("数据导出到 CSV 成功!") + print("数据导出到CSV成功!") elif choice == '4': - file_path = self.get_input("请输入要导入的 CSV 文件路径: ") + file_path = self.get_input("请输入要导入的CSV文件路径: ") self.bll.dal.import_from_csv(file_path) - print("数据从 CSV 导入成功!") \ No newline at end of file + 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键继续...") \ No newline at end of file