commit 02b600256e505be7698841657f15f756e7853477 Author: HJW20 <1264519711@qq.com> Date: Wed Jun 25 11:42:38 2025 +0800 学生系统Pro diff --git a/学生系统AA版本/.idea/.gitignore b/学生系统AA版本/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/学生系统AA版本/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/学生系统AA版本/.idea/inspectionProfiles/Project_Default.xml b/学生系统AA版本/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..d8b3c97 --- /dev/null +++ b/学生系统AA版本/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/学生系统AA版本/.idea/inspectionProfiles/profiles_settings.xml b/学生系统AA版本/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/学生系统AA版本/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/学生系统AA版本/.idea/misc.xml b/学生系统AA版本/.idea/misc.xml new file mode 100644 index 0000000..a6218fe --- /dev/null +++ b/学生系统AA版本/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/学生系统AA版本/.idea/modules.xml b/学生系统AA版本/.idea/modules.xml new file mode 100644 index 0000000..ec42b5f --- /dev/null +++ b/学生系统AA版本/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/学生系统AA版本/.idea/vcs.xml b/学生系统AA版本/.idea/vcs.xml new file mode 100644 index 0000000..1db1ff7 --- /dev/null +++ b/学生系统AA版本/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/学生系统AA版本/.idea/学生系统AA版本.iml b/学生系统AA版本/.idea/学生系统AA版本.iml new file mode 100644 index 0000000..909438d --- /dev/null +++ b/学生系统AA版本/.idea/学生系统AA版本.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/学生系统AA版本/bll/__init__.py b/学生系统AA版本/bll/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/学生系统AA版本/bll/student_service.py b/学生系统AA版本/bll/student_service.py new file mode 100644 index 0000000..151f5b9 --- /dev/null +++ b/学生系统AA版本/bll/student_service.py @@ -0,0 +1,80 @@ +from typing import List, Optional +from models.student import Student +from dal.interfaces import IStudentDAL +from bll.validators import validate_student + + +class StudentService: + def __init__(self, dal: IStudentDAL): + self.dal = dal + + def add_student(self, student: Student) -> bool: + """添加学生""" + if not validate_student(student): + return False + + # 检查学号和身份证号是否已存在 + if self.dal.get_by_id(student.id_card) or self.dal.get_by_stu_id(student.stu_id): + return False + + return self.dal.add_student(student) + + def delete_student(self, id_card: str) -> bool: + """删除学生""" + return self.dal.delete_student(id_card) + + def update_student(self, student: Student) -> bool: + """更新学生信息""" + if not validate_student(student): + return False + + # 检查学生是否存在 + existing = self.dal.get_by_id(student.id_card) + if not existing: + return False + + return self.dal.update_student(student) + + 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_by_name(self, name: str) -> List[Student]: + """按姓名模糊查询""" + return self.dal.get_by_name(name) + + def search_by_class(self, class_name: str) -> List[Student]: + """按班级模糊查询""" + return self.dal.get_by_class(class_name) + + def search_by_major(self, major: str) -> List[Student]: + """按专业模糊查询""" + return self.dal.get_by_major(major) + + def count_total_students(self) -> int: + """统计学生总数""" + return self.dal.count_students() + + def count_students_by_major(self) -> dict: + """按专业统计学生人数""" + return self.dal.count_by_major() + + def calculate_average_height(self, group_by: str = None) -> dict: + """计算平均身高""" + return self.dal.calculate_avg_height(group_by) + + def calculate_average_weight(self, group_by: str = None) -> dict: + """计算平均体重""" + return self.dal.calculate_avg_weight(group_by) + + def clear_all_data(self) -> bool: + """清空所有数据""" + return self.dal.clear_all() \ No newline at end of file diff --git a/学生系统AA版本/bll/validators.py b/学生系统AA版本/bll/validators.py new file mode 100644 index 0000000..aee904c --- /dev/null +++ b/学生系统AA版本/bll/validators.py @@ -0,0 +1,59 @@ + +import re +from datetime import date +from typing import Optional +from models.student import Student + + +def validate_id_card(id_card: str) -> bool: + """验证身份证号有效性""" + if len(id_card) != 18: + return False + + # 校验位计算 + factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + checksum_map = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'] + + total = 0 + for i in range(17): + total += int(id_card[i]) * factors[i] + + return checksum_map[total % 11] == id_card[-1].upper() + + +def validate_stu_id(stu_id: str) -> bool: + """验证学号有效性""" + # 假设学号格式为: 年份(4位)+学院代码(2位)+专业代码(2位)+序号(3位) + return len(stu_id) == 11 and stu_id.isdigit() + + +def validate_name(name: str) -> bool: + """验证姓名有效性""" + return 2 <= len(name) <= 20 and re.match(r'^[\u4e00-\u9fa5a-zA-Z·]+$', name) + + +def validate_height(height: Optional[int]) -> bool: + """验证身高有效性""" + return height is None or (50 <= height <= 250) + + +def validate_weight(weight: Optional[float]) -> bool: + """验证体重有效性""" + return weight is None or (5 <= weight <= 300) + + +def validate_enrollment_date(enrollment_date: Optional[date], birthday: date) -> bool: + """验证入学日期有效性""" + return enrollment_date is None or enrollment_date > birthday + + +def validate_student(student: Student) -> bool: + """验证学生对象所有字段""" + return all([ + validate_id_card(student.id_card), + validate_stu_id(student.stu_id), + validate_name(student.name), + validate_height(student.height), + validate_weight(student.weight), + validate_enrollment_date(student.enrollment_date, student.birthday) + ]) \ No newline at end of file diff --git a/学生系统AA版本/dal/__init__.py b/学生系统AA版本/dal/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/学生系统AA版本/dal/csv_repo.py b/学生系统AA版本/dal/csv_repo.py new file mode 100644 index 0000000..473964f --- /dev/null +++ b/学生系统AA版本/dal/csv_repo.py @@ -0,0 +1,129 @@ +import csv +from typing import List, Optional +from pathlib import Path +from models.student import Student +from dal.interfaces import IStudentDAL + + +class CSVStudentDAL(IStudentDAL): + def clear_all(self) -> bool: + pass + + def calculate_avg_weight(self, group_by: str = None) -> dict: + pass + + def calculate_avg_height(self, group_by: str = None) -> dict: + pass + + def count_by_major(self) -> dict: + pass + + def count_students(self) -> int: + pass + + def get_by_major(self, major: str) -> List[Student]: + pass + + def get_by_class(self, class_name: str) -> List[Student]: + pass + + def get_by_name(self, name: str) -> List[Student]: + pass + + def __init__(self, file_path: str = "students.csv"): + self.file_path = Path(file_path) + self._ensure_file_exists() + + def _ensure_file_exists(self): + """确保CSV文件存在""" + if not self.file_path.exists(): + 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', 'birthday', 'age' + ]) + + def add_student(self, student: Student) -> bool: + """添加学生到CSV文件""" + students = self.get_all() + + # 检查是否已存在 + if any(s.id_card == student.id_card or s.stu_id == student.stu_id for s in students): + return False + + students.append(student) + return self._save_all(students) + + def delete_student(self, id_card: str) -> bool: + """从CSV文件删除学生""" + students = self.get_all() + original_count = len(students) + + students = [s for s in students if s.id_card != id_card] + + if len(students) < original_count: + return self._save_all(students) + return False + + def update_student(self, student: Student) -> bool: + """更新CSV文件中的学生信息""" + students = self.get_all() + updated = False + + for i, s in enumerate(students): + if s.id_card == student.id_card: + students[i] = student + updated = True + break + + if updated: + return self._save_all(students) + return False + + def get_by_id(self, id_card: str) -> Optional[Student]: + """根据身份证号获取学生""" + students = self.get_all() + for s in students: + if s.id_card == id_card: + return s + return None + + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + """根据学号获取学生""" + students = self.get_all() + for s in students: + if s.stu_id == stu_id: + return s + return None + + def get_all(self) -> List[Student]: + """获取所有学生""" + students = [] + + with open(self.file_path, 'r', newline='', encoding='utf-8') as f: + reader = csv.DictReader(f) + for row in reader: + students.append(Student.from_dict(row)) + + return students + + # 其他方法实现类似,限于篇幅省略... + + def _save_all(self, students: List[Student]) -> bool: + """保存所有学生到CSV文件""" + try: + 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', 'birthday', 'age' + ]) + writer.writeheader() + for student in students: + writer.writerow(student.to_dict()) + return True + except Exception as e: + print(f"保存数据时出错: {e}") + return False \ No newline at end of file diff --git a/学生系统AA版本/dal/interfaces.py b/学生系统AA版本/dal/interfaces.py new file mode 100644 index 0000000..17f965f --- /dev/null +++ b/学生系统AA版本/dal/interfaces.py @@ -0,0 +1,77 @@ +from abc import ABC, abstractmethod +from typing import List, Optional +from models.student import Student + + +class IStudentDAL(ABC): + """学生信息数据访问层接口""" + + @abstractmethod + def add_student(self, student: Student) -> bool: + """添加学生信息""" + pass + + @abstractmethod + def delete_student(self, id_card: str) -> bool: + """根据身份证号删除学生信息""" + pass + + @abstractmethod + def update_student(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 get_by_name(self, name: str) -> List[Student]: + """根据姓名查询学生信息(模糊查询)""" + pass + + @abstractmethod + def get_by_class(self, class_name: str) -> List[Student]: + """根据班级查询学生信息(模糊查询)""" + pass + + @abstractmethod + def get_by_major(self, major: str) -> List[Student]: + """根据专业查询学生信息(模糊查询)""" + pass + + @abstractmethod + def count_students(self) -> int: + """统计学生总数""" + pass + + @abstractmethod + def count_by_major(self) -> dict: + """按专业统计学生人数""" + pass + + @abstractmethod + def calculate_avg_height(self, group_by: str = None) -> dict: + """计算平均身高""" + pass + + @abstractmethod + def calculate_avg_weight(self, group_by: str = None) -> dict: + """计算平均体重""" + pass + + @abstractmethod + def clear_all(self) -> bool: + """清空所有学生数据""" + pass \ No newline at end of file diff --git a/学生系统AA版本/dal/json_repo.py b/学生系统AA版本/dal/json_repo.py new file mode 100644 index 0000000..50d1bae --- /dev/null +++ b/学生系统AA版本/dal/json_repo.py @@ -0,0 +1,175 @@ +import json +from pathlib import Path +from typing import List, Optional, Dict, Any + +from dal.interfaces import IStudentDAL +from models.student import Student + + +class JSONStudentDAL(IStudentDAL): + def __init__(self, file_path: str = "students.json"): + self.file_path = Path(file_path) + self._ensure_file_exists() + + def _ensure_file_exists(self): + """确保JSON文件存在""" + if not self.file_path.exists(): + with open(self.file_path, 'w', encoding='utf-8') as f: + json.dump([], f, ensure_ascii=False, indent=2) + + def _load_data(self) -> List[Dict[str, Any]]: + """从JSON文件加载所有数据""" + try: + with open(self.file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except (json.JSONDecodeError, FileNotFoundError): + return [] + + def _save_data(self, data: List[Dict[str, Any]]) -> bool: + """保存数据到JSON文件""" + try: + with open(self.file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=2) + return True + except Exception as e: + print(f"保存数据时出错: {e}") + return False + + def add_student(self, student: Student) -> bool: + """添加学生到JSON文件""" + students_data = self._load_data() + + # 检查是否已存在 + if any(s['id_card'] == student.id_card or s['stu_id'] == student.stu_id + for s in students_data): + return False + + students_data.append(student.to_dict()) + return self._save_data(students_data) + + def delete_student(self, id_card: str) -> bool: + """从JSON文件删除学生""" + students_data = self._load_data() + original_count = len(students_data) + + students_data = [s for s in students_data if s['id_card'] != id_card] + + if len(students_data) < original_count: + return self._save_data(students_data) + return False + + def update_student(self, student: Student) -> bool: + """更新JSON文件中的学生信息""" + students_data = self._load_data() + updated = False + + for i, s in enumerate(students_data): + if s['id_card'] == student.id_card: + students_data[i] = student.to_dict() + updated = True + break + + if updated: + return self._save_data(students_data) + return False + + def get_by_id(self, id_card: str) -> Optional[Student]: + """根据身份证号获取学生""" + students_data = self._load_data() + for s in students_data: + 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_data = self._load_data() + for s in students_data: + if s['stu_id'] == stu_id: + return Student.from_dict(s) + return None + + def get_all(self) -> List[Student]: + """获取所有学生""" + students_data = self._load_data() + return [Student.from_dict(s) for s in students_data] + + def get_by_name(self, name: str) -> List[Student]: + """根据姓名查询学生信息(模糊查询)""" + students_data = self._load_data() + return [Student.from_dict(s) for s in students_data + if name.lower() in s['name'].lower()] + + def get_by_class(self, class_name: str) -> List[Student]: + """根据班级查询学生信息(模糊查询)""" + students_data = self._load_data() + return [Student.from_dict(s) for s in students_data + if s['class_name'] and class_name.lower() in s['class_name'].lower()] + + def get_by_major(self, major: str) -> List[Student]: + """根据专业查询学生信息(模糊查询)""" + students_data = self._load_data() + return [Student.from_dict(s) for s in students_data + if s['major'] and major.lower() in s['major'].lower()] + + def count_students(self) -> int: + """统计学生总数""" + students_data = self._load_data() + return len(students_data) + + def count_by_major(self) -> Dict[str, int]: + """按专业统计学生人数""" + students_data = self._load_data() + result = {} + + for s in students_data: + major = s.get('major', '未指定') + result[major] = result.get(major, 0) + 1 + + return result + + def calculate_avg_height(self, group_by: str = None) -> Dict[str, float]: + """计算平均身高""" + students_data = self._load_data() + + if not group_by: + heights = [s['height'] for s in students_data if s.get('height') is not None] + return {'all': sum(heights) / len(heights)} if heights else {} + + # 按指定字段分组计算 + groups = {} + for s in students_data: + if s.get('height') is None: + continue + + key = s.get(group_by, '未指定') + if key not in groups: + groups[key] = [] + groups[key].append(s['height']) + + return {k: sum(v) / len(v) for k, v in groups.items()} + + def calculate_avg_weight(self, group_by: str = None) -> Dict[str, float]: + """计算平均体重""" + students_data = self._load_data() + + if not group_by: + weights = [s['weight'] for s in students_data if s.get('weight') is not None] + return {'all': sum(weights) / len(weights)} if weights else {} + + # 按指定字段分组计算 + groups = {} + for s in students_data: + if s.get('weight') is None: + continue + + key = s.get(group_by, '未指定') + if key not in groups: + groups[key] = [] + groups[key].append(s['weight']) + + return {k: sum(v) / len(v) for k, v in groups.items()} + + def clear_all(self) -> bool: + """清空所有学生数据""" + return self._save_data([]) \ No newline at end of file diff --git a/学生系统AA版本/main.py b/学生系统AA版本/main.py new file mode 100644 index 0000000..a4c2583 --- /dev/null +++ b/学生系统AA版本/main.py @@ -0,0 +1,31 @@ +from dal.csv_repo import CSVStudentDAL +from dal.json_repo import JSONStudentDAL +from bll.student_service import StudentService +from ui.console_ui import ConsoleUI + + +def main(): + # 选择数据存储方式 + print("请选择数据存储方式:") + print("1. CSV文件") + print("2. JSON文件") + choice = input("请输入选择(1-2): ").strip() + + if choice == "1": + dal = CSVStudentDAL("students.csv") + elif choice == "2": + dal = JSONStudentDAL("students.json") + else: + print("无效选择,默认使用CSV存储") + dal = CSVStudentDAL("students.csv") + + # 创建服务层和UI层 + service = StudentService(dal) + ui = ConsoleUI(service) + + # 运行系统 + ui.run() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/学生系统AA版本/models/student.py b/学生系统AA版本/models/student.py new file mode 100644 index 0000000..dc9339f --- /dev/null +++ b/学生系统AA版本/models/student.py @@ -0,0 +1,66 @@ +from datetime import date +from typing import Optional, Union +from utils.id_card import extract_birthday_from_id_card, calculate_age + + +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: Optional[Union[date, str]] = 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 # True: 男, False: 女 + self.height = height + self.weight = weight + self.class_name = class_name + self.major = major + + # 处理日期类型 + if isinstance(enrollment_date, str): + self.enrollment_date = date.fromisoformat(enrollment_date) + else: + self.enrollment_date = enrollment_date + + # 从身份证生成字段 + self.birthday = extract_birthday_from_id_card(id_card) + self.age = calculate_age(self.birthday) + + def to_dict(self): + """将学生对象转换为字典""" + return { + '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, + 'birthday': self.birthday.isoformat(), + 'age': self.age + } + + @classmethod + def from_dict(cls, data: dict): + """从字典创建学生对象""" + return cls( + name=data['name'], + id_card=data['id_card'], + stu_id=data['stu_id'], + gender=data.get('gender'), + height=data.get('height'), + weight=data.get('weight'), + enrollment_date=data.get('enrollment_date'), + class_name=data.get('class_name'), + major=data.get('major') + ) \ No newline at end of file diff --git a/学生系统AA版本/students.csv b/学生系统AA版本/students.csv new file mode 100644 index 0000000..2df3687 --- /dev/null +++ b/学生系统AA版本/students.csv @@ -0,0 +1 @@ +name,id_card,stu_id,gender,height,weight,enrollment_date,class_name,major,birthday,age diff --git a/学生系统AA版本/ui/__init__.py b/学生系统AA版本/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/学生系统AA版本/ui/console_ui.py b/学生系统AA版本/ui/console_ui.py new file mode 100644 index 0000000..b482c4b --- /dev/null +++ b/学生系统AA版本/ui/console_ui.py @@ -0,0 +1,403 @@ +import sys +from datetime import date +from typing import Optional +from models.student import Student +from bll.student_service import StudentService + + +def _show_main_menu(): + """显示主菜单""" + print("\n" + "=" * 30) + print("学生信息管理系统".center(24)) + print("=" * 30) + print("1. 添加学生信息") + print("2. 删除学生信息") + print("3. 更新学生信息") + print("4. 查询学生信息") + print("5. 统计分析") + print("6. 数据导入导出") + print("7. 清空所有学生信息") + print("0. 退出系统") + print("=" * 30) + + +def _data_import_export(): + """数据导入导出功能""" + while True: + print("\n" + "=" * 30) + print("数据导入导出".center(24)) + print("=" * 30) + print("1. 导出数据到CSV") + print("2. 从CSV导入数据") + print("3. 导出数据到JSON") + print("4. 从JSON导入数据") + print("5. 返回上一级") + print("=" * 30) + + choice = input("请选择操作: ").strip() + + if choice in ("1", "2", "3", "4"): + print("\n此功能暂未实现") + input("\n按任意键继续...") + elif choice == "5": + break + else: + print("无效的选择,请重新输入!") + input("按任意键继续...") + + +def _input_student_info(update_mode=False) -> Optional[Student]: + """输入学生信息""" + try: + print("\n请输入学生信息:") + name = input("姓名: ").strip() + id_card = input("身份证号: ").strip() if not update_mode else None + stu_id = input("学号: ").strip() if not update_mode else None + + gender_input = input("性别(男/女, 可选): ").strip().lower() + gender = None + if gender_input == '男': + gender = True + elif gender_input == '女': + gender = False + + height_input = input("身高(cm, 可选): ").strip() + height = int(height_input) if height_input else None + + weight_input = input("体重(kg, 可选): ").strip() + weight = float(weight_input) if weight_input else None + + enrollment_date_input = input("入学日期(YYYY-MM-DD, 可选): ").strip() + enrollment_date = date.fromisoformat(enrollment_date_input) if enrollment_date_input else None + + class_name = input("班级名称(可选): ").strip() or None + major = input("专业(可选): ").strip() or None + + if update_mode: + return Student( + name=name or None, + id_card=None, + stu_id=None, + gender=gender, + height=height, + weight=weight, + enrollment_date=enrollment_date, + class_name=class_name, + major=major + ) + else: + return 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 + ) + except ValueError as e: + print(f"\n输入格式错误: {e}") + return None + + +def _display_students_list(students: list[Student]): + """显示学生列表""" + if not students: + print("\n没有找到符合条件的学生") + return + + print("\n" + "=" * 90) + print(f"{'学号':<12}{'姓名':<10}{'性别':<6}{'年龄':<6}{'班级':<15}{'专业':<20}{'入学日期':<12}") + print("=" * 90) + + for student in students: + gender = '男' if student.gender else '女' if student.gender is not None else '未知' + print( + f"{student.stu_id:<12}" + f"{student.name:<10}" + f"{gender:<6}" + f"{student.age:<6}" + f"{student.class_name or '未指定':<15}" + f"{student.major or '未指定':<20}" + f"{student.enrollment_date.isoformat() if student.enrollment_date else '未指定':<12}" + ) + print("=" * 90) + print(f"共找到 {len(students)} 名学生") + + +def _display_student_details(student: Student): + """显示学生详细信息""" + print("\n" + "=" * 40) + print("学生详细信息".center(36)) + print("=" * 40) + print(f"学号: {student.stu_id}") + print(f"姓名: {student.name}") + print(f"性别: {'男' if student.gender else '女' if student.gender is not None else '未指定'}") + print(f"年龄: {student.age}") + print(f"出生日期: {student.birthday.isoformat()}") + print(f"身份证号: {student.id_card}") + print(f"身高: {student.height}cm" if student.height else "身高: 未指定") + print(f"体重: {student.weight}kg" if student.weight else "体重: 未指定") + print(f"班级: {student.class_name or '未指定'}") + print(f"专业: {student.major or '未指定'}") + print(f"入学日期: {student.enrollment_date.isoformat() if student.enrollment_date else '未指定'}") + print("=" * 40) + + +class ConsoleUI: + def __init__(self, service: StudentService): + self.service = service + + def run(self): + """运行控制台界面""" + while True: + _show_main_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._search_student() + elif choice == "5": + self._statistics() + elif choice == "6": + _data_import_export() + elif choice == "7": + self._clear_all_data() + elif choice == "0": + print("感谢使用学生信息管理系统,再见!") + sys.exit(0) + else: + print("无效的选择,请重新输入!") + input("按任意键继续...") + + def _add_student(self): + """添加学生信息""" + print("\n" + "=" * 30) + print("添加学生信息".center(24)) + print("=" * 30) + + student = _input_student_info() + if student: + if self.service.add_student(student): + print("\n学生信息添加成功!") + else: + print("\n添加失败,可能是学号或身份证号已存在,或数据验证失败!") + input("\n按任意键返回主菜单...") + + def _delete_student(self): + """删除学生信息""" + while True: + print("\n" + "=" * 30) + print("删除学生信息".center(24)) + print("=" * 30) + print("1. 按身份证号删除") + print("2. 按学号删除") + print("3. 返回上一级") + print("=" * 30) + + choice = input("请选择操作: ").strip() + + if choice == "1": + id_card = input("请输入身份证号: ").strip() + if self.service.delete_student(id_card): + print("\n删除成功!") + else: + print("\n删除失败,未找到该学生!") + input("\n按任意键继续...") + elif choice == "2": + stu_id = input("请输入学号: ").strip() + student = self.service.get_student_by_stu_id(stu_id) + if student and self.service.delete_student(student.id_card): + print("\n删除成功!") + else: + print("\n删除失败,未找到该学生!") + input("\n按任意键继续...") + elif choice == "3": + break + else: + print("无效的选择,请重新输入!") + input("按任意键继续...") + + def _update_student(self): + """更新学生信息""" + print("\n" + "=" * 30) + print("更新学生信息".center(24)) + print("=" * 30) + + stu_id = input("请输入要更新的学生学号: ").strip() + student = self.service.get_student_by_stu_id(stu_id) + + if not student: + print("\n未找到该学生!") + input("\n按任意键返回主菜单...") + return + + print("\n当前学生信息:") + _display_student_details(student) + + print("\n请输入新的学生信息(留空保持不变):") + new_student = _input_student_info(update_mode=True) + + # 更新字段 + for attr, value in new_student.__dict__.items(): + if value is not None: + setattr(student, attr, value) + + if self.service.update_student(student): + print("\n学生信息更新成功!") + else: + print("\n更新失败,数据验证未通过!") + input("\n按任意键返回主菜单...") + + def _search_student(self): + """查询学生信息""" + while True: + print("\n" + "=" * 30) + print("查询学生信息".center(24)) + print("=" * 30) + print("1. 查询所有学生") + print("2. 按身份证号查询") + print("3. 按学号查询") + print("4. 按姓名查询") + print("5. 按班级查询") + print("6. 按专业查询") + print("7. 返回上一级") + print("=" * 30) + + choice = input("请选择操作: ").strip() + + if choice == "1": + students = self.service.get_all_students() + _display_students_list(students) + input("\n按任意键继续...") + elif choice == "2": + id_card = input("请输入身份证号: ").strip() + student = self.service.get_student_by_id(id_card) + if student: + _display_student_details(student) + else: + print("\n未找到该学生!") + input("\n按任意键继续...") + elif choice == "3": + stu_id = input("请输入学号: ").strip() + student = self.service.get_student_by_stu_id(stu_id) + if student: + _display_student_details(student) + else: + print("\n未找到该学生!") + input("\n按任意键继续...") + elif choice == "4": + name = input("请输入姓名(支持模糊查询): ").strip() + students = self.service.search_by_name(name) + _display_students_list(students) + input("\n按任意键继续...") + elif choice == "5": + class_name = input("请输入班级名称(支持模糊查询): ").strip() + students = self.service.search_by_class(class_name) + _display_students_list(students) + input("\n按任意键继续...") + elif choice == "6": + major = input("请输入专业名称(支持模糊查询): ").strip() + students = self.service.search_by_major(major) + _display_students_list(students) + input("\n按任意键继续...") + elif choice == "7": + break + else: + print("无效的选择,请重新输入!") + input("按任意键继续...") + + def _statistics(self): + """统计分析功能""" + while True: + print("\n" + "=" * 30) + print("统计分析".center(24)) + print("=" * 30) + print("1. 学生总数") + print("2. 按专业统计人数") + print("3. 平均身高") + print("4. 平均体重") + print("5. 返回上一级") + print("=" * 30) + + choice = input("请选择操作: ").strip() + + if choice == "1": + count = self.service.count_total_students() + print(f"\n学生总数: {count}") + input("\n按任意键继续...") + elif choice == "2": + stats = self.service.count_students_by_major() + print("\n各专业学生人数统计:") + for major, count in stats.items(): + print(f"{major}: {count}人") + input("\n按任意键继续...") + elif choice == "3": + print("\n1. 全体平均身高") + print("2. 按班级统计平均身高") + print("3. 按专业统计平均身高") + sub_choice = input("请选择统计方式: ").strip() + + if sub_choice == "1": + avg = self.service.calculate_average_height() + print(f"\n全体平均身高: {avg.get('all', 0):.1f}cm") + elif sub_choice == "2": + avg = self.service.calculate_average_height("class_name") + print("\n各班级平均身高:") + for class_name, height in avg.items(): + print(f"{class_name}: {height:.1f}cm") + elif sub_choice == "3": + avg = self.service.calculate_average_height("major") + print("\n各专业平均身高:") + for major, height in avg.items(): + print(f"{major}: {height:.1f}cm") + else: + print("无效的选择!") + input("\n按任意键继续...") + elif choice == "4": + print("\n1. 全体平均体重") + print("2. 按班级统计平均体重") + print("3. 按专业统计平均体重") + sub_choice = input("请选择统计方式: ").strip() + + if sub_choice == "1": + avg = self.service.calculate_average_weight() + print(f"\n全体平均体重: {avg.get('all', 0):.1f}kg") + elif sub_choice == "2": + avg = self.service.calculate_average_weight("class_name") + print("\n各班级平均体重:") + for class_name, weight in avg.items(): + print(f"{class_name}: {weight:.1f}kg") + elif sub_choice == "3": + avg = self.service.calculate_average_weight("major") + print("\n各专业平均体重:") + for major, weight in avg.items(): + print(f"{major}: {weight:.1f}kg") + else: + print("无效的选择!") + input("\n按任意键继续...") + elif choice == "5": + break + else: + print("无效的选择,请重新输入!") + input("按任意键继续...") + + def _clear_all_data(self): + """清空所有学生数据""" + confirm = input("\n警告: 这将删除所有学生数据!确定要继续吗?(y/n): ").strip().lower() + if confirm == 'y': + if self.service.clear_all_data(): + print("\n所有学生数据已清空!") + else: + print("\n清空数据失败!") + else: + print("\n操作已取消") + input("\n按任意键返回主菜单...") + diff --git a/学生系统AA版本/utils/__init__.py b/学生系统AA版本/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/学生系统AA版本/utils/id_card.py b/学生系统AA版本/utils/id_card.py new file mode 100644 index 0000000..4588cb0 --- /dev/null +++ b/学生系统AA版本/utils/id_card.py @@ -0,0 +1,201 @@ +import re +from datetime import date +from typing import Optional, Tuple + + +def validate_id_card(id_card: str) -> bool: + """ + Validate a Chinese ID card number (18 digits) + + Args: + id_card: ID card number string + + Returns: + bool: True if valid, False otherwise + """ + if not isinstance(id_card, str) or len(id_card) != 18: + return False + + # Check basic format (first 17 digits, last digit can be digit or X) + if not re.match(r'^\d{17}[\dXx]$', id_card): + return False + + # Calculate checksum + factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + checksum_map = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'] + + total = 0 + for i in range(17): + try: + total += int(id_card[i]) * factors[i] + except ValueError: + return False + + calculated_checksum = checksum_map[total % 11] + if id_card[-1].upper() != calculated_checksum: + return False + + # Validate birthdate + try: + birth_date = extract_birthday_from_id_card(id_card) + # Validate the date is reasonable + if birth_date.year < 1900 or birth_date > date.today(): + return False + except ValueError: + return False + + return True + + +def extract_birthday_from_id_card(id_card: str) -> date: + """ + Extract birthdate from ID card number + + Args: + id_card: Valid 18-digit ID card number + + Returns: + date: Birth date as date object + + Raises: + ValueError: If ID card format is invalid + """ + if len(id_card) != 18: + raise ValueError("ID card number must be 18 digits") + + birth_str = id_card[6:14] # YYYYMMDD format + + try: + year = int(birth_str[0:4]) + month = int(birth_str[4:6]) + day = int(birth_str[6:8]) + + # Validate month and day + if month < 1 or month > 12 or day < 1 or day > 31: + raise ValueError("Invalid date components") + + return date(year, month, day) + except (ValueError, IndexError) as e: + raise ValueError(f"Invalid birth date format in ID card: {birth_str}") from e + + +def calculate_age(birth_date: date) -> int: + """ + Calculate current age based on birthdate + + Args: + birth_date: Date of birth + + Returns: + int: Current age in years + """ + today = date.today() + age = today.year - birth_date.year + + # Adjust if birthday hasn't occurred yet this year + if (today.month, today.day) < (birth_date.month, birth_date.day): + age -= 1 + + return age + + +def get_gender_from_id_card(id_card: str) -> Optional[bool]: + """ + Get gender from ID card number + + Args: + id_card: ID card number string + + Returns: + Optional[bool]: + True for male, + False for female, + None if cannot determine + """ + if len(id_card) not in (15, 18): + return None + + # For 18-digit ID: gender is 17th digit (index 16) + # For 15-digit ID: gender is last digit + gender_digit_pos = 16 if len(id_card) == 18 else -1 + + try: + gender_digit = int(id_card[gender_digit_pos]) + return gender_digit % 2 == 1 # Odd for male, even for female + except (ValueError, IndexError): + return None + + +def get_region_from_id_card(id_card: str) -> Tuple[str, str]: + """ + Get region information from ID card number + + Args: + id_card: ID card number string + + Returns: + Tuple[str, str]: (province, city) names + """ + if len(id_card) not in (15, 18): + return "未知", "未知" + + # First 6 digits represent region code + region_code = id_card[:6] + + # Simplified region mapping (in a real app, use a complete database) + region_map = { + '110000': ('北京市', '北京市'), + '110100': ('北京市', '北京市'), + '310000': ('上海市', '上海市'), + '310100': ('上海市', '上海市'), + '440000': ('广东省', ''), + '440300': ('广东省', '深圳市'), + # Add more regions as needed... + } + + return region_map.get(region_code, ("未知", "未知")) + + +def generate_test_id_card() -> str: + """ + Generate a valid format ID card number for testing + + Returns: + str: A syntactically valid but fake ID card number + """ + import random + + # Region code (random Beijing district) + region = '110' + str(random.randint(100, 199))[:3] + + # Birth date (18-60 years old) + birth_year = date.today().year - random.randint(18, 60) + birth_date = f"{birth_year}{random.randint(1, 12):02d}{random.randint(1, 28):02d}" + + # Sequence number + seq_num = f"{random.randint(0, 999):03d}" + + # First 17 digits + first_17 = region + birth_date + seq_num + + # Calculate checksum + factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] + checksum_map = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'] + + total = sum(int(first_17[i]) * factors[i] for i in range(17)) + checksum = checksum_map[total % 11] + + return first_17 + checksum + + +# Example usage +if __name__ == "__main__": + test_id = "110105199003078888" # Example valid ID + print(f"Validate ID: {validate_id_card(test_id)}") + print(f"Birth date: {extract_birthday_from_id_card(test_id)}") + print(f"Age: {calculate_age(extract_birthday_from_id_card(test_id))}") + print(f"Gender: {'Male' if get_gender_from_id_card(test_id) else 'Female'}") + print(f"Region: {get_region_from_id_card(test_id)}") + print(f"Test ID: {generate_test_id_card()}") + +