commit c4473a3eeb2f52f43e8958dc13874ef17c5e034e Author: c1785 <1276933475@qq.com> Date: Wed Jun 18 10:07:49 2025 +0800 123 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/PythonProject.iml b/.idea/PythonProject.iml new file mode 100644 index 0000000..909438d --- /dev/null +++ b/.idea/PythonProject.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9dfa2d1 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5f43230 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bll/__init__.py b/bll/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bll/student_bll.py b/bll/student_bll.py new file mode 100644 index 0000000..5b110c1 --- /dev/null +++ b/bll/student_bll.py @@ -0,0 +1,49 @@ +from datetime import date +from typing import Optional, List +from dal.student_dal_json import StudentDAL +from model import Student + + +class StudentBLL: + def __init__(self, dal: StudentDAL): + self.dal = dal + + def add_student(self, student: Student) -> bool: + if self.dal.check_student_exists(student.sid): + raise ValueError(f"学生 ID {student.sid} 已存在。") + if not student.is_valid: + raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}") + return self.dal.add_student(student) + # 其他方法同理 + + def delete_student(self, sid: str) ->bool: + if not self.dal.check_student_exists(sid): + raise ValueError(f"学生 ID {sid} 不存在。") + return self.dal.delete_student(sid) + + def update_student(self, sid: str, student: Student) -> bool: + if not self.dal.check_student_exists(sid): + raise ValueError(f"学生 ID {sid} 不存在。") + if not student.is_valid: + raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}") + return self.dal.update_student(sid, student) + + def update_student_partial(self, sid: str, name: Optional[str] = None, height: Optional[int] = None, + birth_date: Optional[date] = None, enrollment_date: Optional[date] = None, + class_name: Optional[str] = None) -> bool: + if not self.dal.check_student_exists(sid): + raise ValueError(f"学生 ID {sid} 不存在。") + return self.dal.update_student_partial(sid, name, height, birth_date, enrollment_date, class_name) + + def get_student_by_id(self, sid: str) -> Optional[Student]: + return self.dal.find_student_by_sid(sid) + + def get_students_by_name(self, name: str) -> List[Student]: + return self.dal.find_students_by_name(name) + + def get_students_by_class(self, class_name: str) -> List[Student]: + return self.dal.find_students_by_class_name(class_name) + + def get_all_students(self) -> List[Student]: + return self.dal.get_all_students() + diff --git a/dal/__init__.py b/dal/__init__.py new file mode 100644 index 0000000..e905165 --- /dev/null +++ b/dal/__init__.py @@ -0,0 +1,2 @@ +import json +from model.Student import Student diff --git a/dal/student_dal_json.py b/dal/student_dal_json.py new file mode 100644 index 0000000..77f5377 --- /dev/null +++ b/dal/student_dal_json.py @@ -0,0 +1,155 @@ +import json +import os, sys +from datetime import date +from typing import List, Optional + +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 StudentDAL: + def __init__(self, file_path: str): + self.file_path = file_path + self._ensure_file_exists() + + def _ensure_file_exists(self): + if not os.path.exists(self.file_path): + with open(self.file_path, mode='w', encoding='utf-8') as file: + json.dump([], file) + + def save_students(self, students: List[Student]) -> None: + student_dicts = [student.to_dict() for student in students] + with open(self.file_path, mode='w', encoding='utf-8') as file: + json.dump(student_dicts, file, ensure_ascii=False, indent=4) + + def load_students(self) -> List[Student]: + with open(self.file_path, mode='r', encoding='utf-8') as file: + student_dicts = json.load(file) + return [Student.from_dict(student_dict) for student_dict in student_dicts] + + def add_student(self, student: Student) -> bool: + students = self.load_students() + for existing_student in students: + if existing_student.sid == student.sid: + return False + students.append(student) + self.save_students(students) + return True + + def find_student_by_sid(self, sid: str) -> Optional[Student]: + students = self.load_students() + for student in students: + if student.sid == sid: + return student + return None + + def find_students_by_name(self, name: str) ->List[Student]: + students = self.load_students() + return [student for student in students if name.lower() in student.name.lower()] + + def find_students_by_class_name(self, class_name: str) ->List[Student]: + students = self.load_students() + return [s for s in students if class_name.lower() in s.class_name.lower()] + + def get_all_students(self) -> List[Student]: + return self.load_students() + + def update_student(self, sid: str, updated_student: Student) -> bool: + students = self.load_students() + for i, student in enumerate(students): + if student.sid == sid: + students[i] = updated_student + self.save_students(students) + return True + return False + def update_student_partial(self, sid:str, name:Optional[str]=None, height:Optional[float]=None, birth_date:Optional[date]=None, enrollment_date:Optional[date]=None, class_name:Optional[str]=None) ->bool: + students = self.load_students() + for i, student in enumerate(students): + if student.sid == sid: + if name is not None: + students[i].name = name + if height is not None: + students[i].height = height + if birth_date is not None: + students[i].birth_date = birth_date + if enrollment_date is not None: + students[i].enrollment_date = enrollment_date + if class_name is not None: + students[i].class_name = class_name + self.save_students(students) + return True + return False + + def delete_student(self, sid: str) -> bool: + """根据学生 ID 删除学生""" + students = self.load_students() + initial_length = len(students) + students = [student for student in students if student.sid != sid] + if len(students) < initial_length: + self.save_students(students) + return True + return False + + def clear_all_students(self) -> None: + self.save_students([]) + + def get_student_count(self) -> int: + return len(self.load_students()) + + def check_student_exists(self, sid: str) ->bool: + return self.find_student_by_sid(sid) is not None + + def export_to_json(self, file_path): + students = self.load_students() + student_dicts = [student.to_dict() for student in students] + with open(file_path, mode='w', encoding='utf-8') as file: + json.dump(student_dicts, file, ensure_ascii=False, indent=4) + + def import_from_json(self, file_path): + with open(file_path, mode='r', encoding='utf-8') as file: + student_dicts = json.load(file) + students = [Student.from_dict(student_dict) for student_dict in student_dicts] + self.save_students(students) + + def export_to_csv(self, file_path): + students = self.load_students() + with open(file_path, mode='w', encoding='utf-8', newline='') as file: + fieldnames = ['sid', 'name', 'height', 'birth_date', 'enrollment_date', 'class_name'] + writer = csv.DictWriter(file, fieldnames=fieldnames) + writer.writeheader() + for student in students: + writer.writerow(student.to_dict()) + + def import_from_csv(self, file_path): + students = [] + with open(file_path, mode='r', encoding='utf-8') as file: + reader = csv.DictReader(file) + for row in reader: + row['birth_date'] = date.fromisoformat(row['birth_date']) + row['enrollment_date'] = date.fromisoformat(row['enrollment_date']) + student = Student.from_dict(row) + students.append(student) + self.save_students(students) + + def get_average_height(self): + students = self.load_students() + if not students: + return 0 + total_height = sum(student.height for student in students) + return total_height / len(students) + + def count_students_by_height_range(self, min_height, max_height): + students = self.load_students() + return len([student for student in students if min_height <= student.height <= max_height]) + + def count_students_by_enrollment_year(self, year): + students = self.load_students() + return len([student for student in students if student.enrollment_date.year == year]) + + def count_students_by_age_range(self, min_age, max_age): + today = date.today() + students = self.load_students() + return len([student for student in students if min_age <= today.year - student.birth_date.year <= max_age]) \ No newline at end of file diff --git a/data/student.json b/data/student.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/data/student.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..c6a7721 --- /dev/null +++ b/main.py @@ -0,0 +1,128 @@ +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 + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/model/Student.py b/model/Student.py new file mode 100644 index 0000000..2325357 --- /dev/null +++ b/model/Student.py @@ -0,0 +1,86 @@ +from datetime import date + +class Student: + def __init__(self, sid: str, name: str, height: str, birth_date: date | str, enrollment_date: date | str, class_name: str): + self.sid = sid + self.name = name.strip() + self.height = int(height) # 转换为整数类型 + self.birth_date = birth_date if isinstance(birth_date, date) else date.fromisoformat(birth_date) + self.enrollment_date = enrollment_date if isinstance(enrollment_date, date) else date.fromisoformat(enrollment_date) + self.class_name = class_name + self._validation_errors = [] + self._validate_height() + self._validate_date() + self._validate_name() + + def _validate_height(self) -> None: + if not (50 <= self.height <= 250): + self._validation_errors.append(f"身高{self.height}cm超出合理范围") + + def _validate_name(self) -> None: + if not (2 <= len(self.name) <=20): + self._validation_errors.append("姓名长度需在2-20个字符之间") + if not self.name.isprintable(): + self._validation_errors.append("姓名长度需在2-20个字符之间") + def _validate_date(self) -> None: + today = date.today() + if self.birth_date > today: + self._validation_errors.append('出生日期不能在未来') + if self.enrollment_date > today: + self._validation_errors.append('入学日期不能在未来') + if self.birth_date > self.enrollment_date: + self._validation_errors.append('入学日期不能早于出生日期') + @property + def is_valid(self) -> bool: + return len(self._validation_errors) == 0 + def get_errors(self) -> list[str]: + return self._validation_errors.copy() + def __eq__(self, other) -> bool: + if not isinstance(other, Student): + return NotImplemented + return (self.sid == other.sid and + self.name == other.name and + self.height == other.height and + self.birth_date == other.birth_date and + self.enrollment_date == other.enrollment_date and + self.class_name == other.class_name + ) + + @classmethod + def from_dict(cls, data: dict) -> 'Student': + birth_date = data['birth_date'] if isinstance(data['birth_date'], date) else date.fromisoformat( + data['birth_date']) + enrollment_date = data['enrollment_date'] if isinstance(data['enrollment_date'], date) else date.fromisoformat( + data['enrollment_date']) + return cls( + sid=data['sid'], + name=data['name'].strip(), + height=data['height'], + birth_date=birth_date, + enrollment_date=enrollment_date, + class_name=data['class_name'] + ) + + def to_dict(self): + return { + 'sid': self.sid, + 'name': self.name, + 'height': self.height, + 'birth_date': self.birth_date.isoformat(), + 'enrollment_date': self.enrollment_date.isoformat(), + 'class_name': self.class_name + } + + def __repr__(self) -> str: + return ( + f"{self.__class__.__name__}(" + f"sid='{self.sid}', " + f"name='{self.name}'," + f"height={self.height}," + f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day}), " + f"enrollment_date=date({self.enrollment_date.year},{self.enrollment_date.month},{self.enrollment_date.day}), " + f"class_name='{self.class_name}'" + f")" + ) + + diff --git a/model/__init__.py b/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/model/course.py b/model/course.py new file mode 100644 index 0000000..e69de29 diff --git a/model/score.py b/model/score.py new file mode 100644 index 0000000..e69de29 diff --git a/model/test/__init__.py b/model/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/students_data.json b/students_data.json new file mode 100644 index 0000000..e69de29 diff --git a/ui/__init__.py b/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ui/studentui.py b/ui/studentui.py new file mode 100644 index 0000000..c1c69da --- /dev/null +++ b/ui/studentui.py @@ -0,0 +1,159 @@ +import os +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) + file_path = os.path.join(parent_dir, "data", "student.json") + dal = StudentDAL(file_path) + self.bll = StudentBLL(dal) + self.file_path = file_path + + + def display_menu(self): + """显示主菜单""" + os.system('cls' if os.name == 'nt' else 'clear') + print("\n=" * 1) + print(" 学生信息管理系统 - 主菜单 ") + print("=" * 1) + print("1. 添加学生信息") + print("2. 删除学生信息") + print("3. 更新学生信息") + print("4. 查询学生信息") + print("5. 统计分析") + print("6. 数据导入导出") + print("7. 清空所有学生信息") + print("0. 退出系统") + print("=" * 1) + + def display_query_menu(self): + """显示查询子菜单""" + os.system('cls' if os.name == 'nt' else 'clear') + print("\n=" * 1) + print(" 学生信息管理系统 - 查询菜单 ") + print("=" * 1) + print("1. 查询所有学生") + print("2. 按身份证号查询") + print("3. 按学号查询") + print("4. 按姓名查询") + print("5. 按班级查询") + print("0. 返回上一级") + print("=" * 1) + + def display_stats_menu(self): + """显示统计分析子菜单""" + os.system('cls' if os.name == 'nt' else 'clear') + print("\n=" * 1) + print(" 学生信息管理系统 - 统计分析菜单 ") + print("=" * 1) + print("1. 学生总数") + print("2. 平均身高") + print("3. 按身高范围统计") + print("4. 按入学年份统计") + print("5. 按年龄范围统计") + print("0. 返回上一级") + print("=" * 1) + + def display_import_export_menu(self): + """显示数据导入导出子菜单""" + os.system('cls' if os.name == 'nt' else 'clear') + print("\n=" * 1) + print(" 学生信息管理系统 - 数据导入导出菜单 ") + print("=" * 1) + print("1. 导出数据到JSON") + print("2. 从JSON导入数据") + print("3. 导出数据到CSV") + print("4. 从CSV导入数据") + print("0. 返回上一级") + print("=" * 1) + + 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("\n=" * 1) + print(" 添加学生信息 ") + print("=" * 1) + + name = self.get_input("请输入姓名: ") + id_number = self.get_input("请输入身份证号: ") + student_id = self.get_input("请输入学号: ") + gender = self.get_input("请输入性别(男/女): ") + + while True: + try: + height = float(self.get_input("请输入身高(cm): ")) + if height <= 0 or height > 250: + raise ValueError + break + except ValueError: + print("身高必须为0到250之间的有效数值,请重新输入") + + while True: + try: + weight = float(self.get_input("请输入体重(kg): ")) + if weight <= 0 or weight > 500: + raise ValueError + break + except ValueError: + print("体重必须为0到500之间的有效数值,请重新输入") + + enrollment_date = self.get_input("请输入入学日期(YYYY-MM-DD): ") + class_name = self.get_input("请输入班级: ") + major = self.get_input("请输入专业: ") + + student = Student( + name=name, + id_number=id_number, + student_id=student_id, + gender=gender, + height=height, + weight=weight, + enrollment_date=enrollment_date, + class_name=class_name, + + major=major + ) + student = Student( + sid=student_id, # 注意这里添加了 sid + name=name, + height=height, + birth_date=date.fromisoformat(enrollment_date), # 假设入学日期作为示例,可根据实际情况修改 + enrollment_date=date.fromisoformat(enrollment_date), + class_name=class_name + ) + try: + if self.bll.add_student(student): + print("学生信息添加成功!") + else: + print("学生信息添加失败。") + except ValueError as e: + print(e) + + def handle_import_export(self, choice): + if choice == '1': + file_path = self.get_input("请输入要导出的 JSON 文件路径: ") + self.bll.dal.export_to_json(file_path) + print("数据导出到 JSON 成功!") + elif choice == '2': + file_path = self.get_input("请输入要导入的 JSON 文件路径: ") + self.bll.dal.import_from_json(file_path) + print("数据从 JSON 导入成功!") + elif choice == '3': + file_path = self.get_input("请输入要导出的 CSV 文件路径: ") + self.bll.dal.export_to_csv(file_path) + print("数据导出到 CSV 成功!") + elif choice == '4': + file_path = self.get_input("请输入要导入的 CSV 文件路径: ") + self.bll.dal.import_from_csv(file_path) + print("数据从 CSV 导入成功!") \ No newline at end of file