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()}")
+
+