commit
ba6ebe37a2
@ -0,0 +1,11 @@
|
|||||||
|
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)
|
@ -0,0 +1,8 @@
|
|||||||
|
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
|
@ -0,0 +1,2 @@
|
|||||||
|
def check_students_exists(self,sid:str)-> bool:
|
||||||
|
return self.find_studemts_by_sid(sid) is not None
|
@ -0,0 +1,6 @@
|
|||||||
|
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
|
@ -0,0 +1,7 @@
|
|||||||
|
def delete_student(self,sid:str)-> bool:
|
||||||
|
students= self.load_students()
|
||||||
|
initial_length=len(students)
|
||||||
|
students=[student for student in students if student.sid!= sid]
|
||||||
|
if len(students)<initial_length:
|
||||||
|
return True
|
||||||
|
return False
|
@ -0,0 +1,3 @@
|
|||||||
|
def find_student_by_class_name(self,class_name:str)-> Lise[Student]:
|
||||||
|
students = self.load_students()
|
||||||
|
return[s for s in students if class_name.lower()in s.class_name.lower()]
|
@ -0,0 +1,3 @@
|
|||||||
|
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()]
|
@ -0,0 +1,6 @@
|
|||||||
|
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
|
@ -0,0 +1,2 @@
|
|||||||
|
def get_all_students(self)-> List[Student]:
|
||||||
|
return self.load_students()
|
@ -0,0 +1,2 @@
|
|||||||
|
def get_student_count(self)-> int:
|
||||||
|
return len(self.load_students())
|
@ -0,0 +1,67 @@
|
|||||||
|
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 clear_all(self) -> bool:
|
||||||
|
"""清空所有学生数据"""
|
||||||
|
pass
|
@ -0,0 +1,117 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import List, Optional
|
||||||
|
from models.student import Student
|
||||||
|
from dal.idal import IStudentDAL
|
||||||
|
|
||||||
|
|
||||||
|
class JsonStudentDAL(IStudentDAL):
|
||||||
|
"""JSON存储实现"""
|
||||||
|
|
||||||
|
def __init__(self, file_path: str = 'data/students.json'):
|
||||||
|
self.file_path = file_path
|
||||||
|
self._ensure_file_exists()
|
||||||
|
|
||||||
|
def _ensure_file_exists(self):
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
|
||||||
|
if not os.path.exists(self.file_path):
|
||||||
|
with open(self.file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump([], f)
|
||||||
|
|
||||||
|
def _load_data(self) -> List[dict]:
|
||||||
|
"""加载JSON数据"""
|
||||||
|
with open(self.file_path, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def _save_data(self, data: List[dict]):
|
||||||
|
"""保存数据到JSON文件"""
|
||||||
|
with open(self.file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
def add_student(self, student: Student) -> bool:
|
||||||
|
data = self._load_data()
|
||||||
|
|
||||||
|
# 检查学号和身份证号是否已存在
|
||||||
|
for item in data:
|
||||||
|
if item['id_card'] == student.id_card or item['stu_id'] == student.stu_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
data.append(student.to_dict())
|
||||||
|
self._save_data(data)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def delete_student(self, id_card: str) -> bool:
|
||||||
|
data = self._load_data()
|
||||||
|
original_length = len(data)
|
||||||
|
|
||||||
|
data = [item for item in data if item['id_card'] != id_card]
|
||||||
|
|
||||||
|
if len(data) != original_length:
|
||||||
|
self._save_data(data)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update_student(self, student: Student) -> bool:
|
||||||
|
data = self._load_data()
|
||||||
|
updated = False
|
||||||
|
|
||||||
|
for i, item in enumerate(data):
|
||||||
|
if item['id_card'] == student.id_card:
|
||||||
|
data[i] = student.to_dict()
|
||||||
|
updated = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if updated:
|
||||||
|
self._save_data(data)
|
||||||
|
return updated
|
||||||
|
|
||||||
|
def get_by_id(self, id_card: str) -> Optional[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
for item in data:
|
||||||
|
if item['id_card'] == id_card:
|
||||||
|
return Student.from_dict(item)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
for item in data:
|
||||||
|
if item['stu_id'] == stu_id:
|
||||||
|
return Student.from_dict(item)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_all(self) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data]
|
||||||
|
|
||||||
|
def get_by_name(self, name: str) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data if name.lower() in item['name'].lower()]
|
||||||
|
|
||||||
|
def get_by_class(self, class_name: str) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data
|
||||||
|
if item['class_name'] and class_name.lower() in item['class_name'].lower()]
|
||||||
|
|
||||||
|
def get_by_major(self, major: str) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data
|
||||||
|
if item['major'] and major.lower() in item['major'].lower()]
|
||||||
|
|
||||||
|
def count_students(self) -> int:
|
||||||
|
data = self._load_data()
|
||||||
|
return len(data)
|
||||||
|
|
||||||
|
def count_by_major(self) -> dict:
|
||||||
|
data = self._load_data()
|
||||||
|
majors = {}
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
major = item.get('major', '未指定')
|
||||||
|
majors[major] = majors.get(major, 0) + 1
|
||||||
|
|
||||||
|
return majors
|
||||||
|
|
||||||
|
def clear_all(self) -> bool:
|
||||||
|
self._save_data([])
|
||||||
|
return True
|
@ -0,0 +1,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]
|
@ -0,0 +1,4 @@
|
|||||||
|
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)
|
@ -0,0 +1,24 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from typing_extensions import Optional
|
||||||
|
from xlwings.pro.reports.filters import height
|
||||||
|
|
||||||
|
from 面向对象学生系统.model.test.student import Student
|
||||||
|
class StudentDAL:
|
||||||
|
def __init__(self,file_path:str):
|
||||||
|
self.file_path=file_path
|
||||||
|
self._ensure_file_exists()
|
||||||
|
def _ensure_file_exist(self):
|
||||||
|
if not os.path.exists(self.file_path):
|
||||||
|
with open(self,file_path,mode='w',encoding='utf-8') as file:
|
||||||
|
json.dump([],file)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
|||||||
|
def update_student(self,sid:str,updated_student:Student)-> bool:
|
||||||
|
students =self.load_students()
|
||||||
|
for i, student in enumerate(students):
|
||||||
|
if student.sid== sid:
|
||||||
|
student[i]=upadted_student
|
||||||
|
self.save_students(students)
|
||||||
|
return True
|
||||||
|
return False
|
@ -0,0 +1,18 @@
|
|||||||
|
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 students.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 enrollemnt_date is not None:
|
||||||
|
students[i].enrollemnt_date = enrollemnt_date
|
||||||
|
if class_name is not None:
|
||||||
|
students[i].class_name = class_name
|
||||||
|
self.save_students(students)
|
||||||
|
return True
|
||||||
|
return False
|
@ -0,0 +1,2 @@
|
|||||||
|
def clear_all_students(self) -> None:
|
||||||
|
self.dal.clear_all_students()
|
@ -0,0 +1,5 @@
|
|||||||
|
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)
|
||||||
|
|
@ -0,0 +1,9 @@
|
|||||||
|
def export_students(self,file_path:str)-> bool:
|
||||||
|
student =self.dal.get_all_students()
|
||||||
|
try:
|
||||||
|
with open(file_path,'w',encoding='utf-8')as f:
|
||||||
|
json.dump([student.to_dict() for student in students],f,ensure_ascii=False,indent=4)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print(f"导出学生数据到JOSN 时出错:{e}")
|
||||||
|
return False
|
@ -0,0 +1,6 @@
|
|||||||
|
def get_avg_height(self)-> float:
|
||||||
|
students =self.dal.get_all_students()
|
||||||
|
if not students:
|
||||||
|
return 0.0
|
||||||
|
total_height = sum(student.height for student in students)
|
||||||
|
return total_height/len(students)
|
@ -0,0 +1,2 @@
|
|||||||
|
def get_student_by_count(self)-> int:
|
||||||
|
return self.dal.get_student_by_count()
|
@ -0,0 +1,3 @@
|
|||||||
|
def get_student_by_height(self,min_height:int,max_height:int)-> List[Student]:
|
||||||
|
students=self.dal.get_all_students()
|
||||||
|
return [s for s in students if min_height <= s.height <= max_height]
|
@ -0,0 +1,8 @@
|
|||||||
|
def get_student_by_id(self,sid:str)-> Optional[Student]:
|
||||||
|
return self.dal.find_student_by_sid(sid)
|
||||||
|
def get_student_by_name(self,name:str)-> List[Student]:
|
||||||
|
return self.dal.find_student_by_name(name)
|
||||||
|
def get_student_by_class(self,class_name:str)-> List[Student]:
|
||||||
|
return self.dal.find_student_by_class_name(name)
|
||||||
|
def get_all_students(self)-> List[Student]:
|
||||||
|
return self.dal.get_all_students()
|
@ -0,0 +1,9 @@
|
|||||||
|
def get_students_by_age(self,min_age:int,max_age:int)->List[Student]:
|
||||||
|
today =date.today()
|
||||||
|
students = self.dal.get_all_students()
|
||||||
|
age_filtered_students=[]
|
||||||
|
for student in students:
|
||||||
|
age =today.year-student.birth_date.year-((today.month,today,day)<(student.birth_date.month,student.birth_date.day))
|
||||||
|
if min_age <=age<=max_age:
|
||||||
|
age_filtered_students.append(student)
|
||||||
|
return age_filtered_students
|
@ -0,0 +1,3 @@
|
|||||||
|
def get_students_by_enrollment_year(self,year:int)-> List[Student]:
|
||||||
|
students =self.dal.get_all_students()
|
||||||
|
return [student for student in students if student.enrollment_date.year ==year]
|
@ -0,0 +1,7 @@
|
|||||||
|
class StudentBll:
|
||||||
|
def __init__(self,dal:StudentDal):
|
||||||
|
self.dal=dal
|
||||||
|
def delete_student(self,sid:str)-> bool:
|
||||||
|
if not self.dal.check_student_exists(sid):
|
||||||
|
raise ValueError(f"学生 ID {sid}不存在。")
|
||||||
|
return self.dal.delete_students(sid)
|
@ -0,0 +1,117 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
from models.student import Student
|
||||||
|
from dal.idal import IStudentDAL
|
||||||
|
from utils.validator import validate_student_data
|
||||||
|
from utils.id_utils import is_valid_id_card
|
||||||
|
|
||||||
|
|
||||||
|
class StudentService:
|
||||||
|
"""学生信息业务逻辑层"""
|
||||||
|
|
||||||
|
def __init__(self, dal: IStudentDAL):
|
||||||
|
self.dal = dal
|
||||||
|
|
||||||
|
def add_student(self, student: Student) -> tuple[bool, str]:
|
||||||
|
"""添加学生"""
|
||||||
|
# 数据验证
|
||||||
|
is_valid, msg = validate_student_data(student)
|
||||||
|
if not is_valid:
|
||||||
|
return False, msg
|
||||||
|
|
||||||
|
# 检查学号是否已存在
|
||||||
|
if self.dal.get_by_stu_id(student.stu_id):
|
||||||
|
return False, "学号已存在"
|
||||||
|
|
||||||
|
# 检查身份证号是否已存在
|
||||||
|
if self.dal.get_by_id(student.id_card):
|
||||||
|
return False, "身份证号已存在"
|
||||||
|
|
||||||
|
# 添加学生
|
||||||
|
success = self.dal.add_student(student)
|
||||||
|
return success, "添加成功" if success else "添加失败"
|
||||||
|
|
||||||
|
def delete_student(self, id_card: str) -> tuple[bool, str]:
|
||||||
|
"""删除学生"""
|
||||||
|
if not is_valid_id_card(id_card):
|
||||||
|
return False, "无效的身份证号"
|
||||||
|
|
||||||
|
success = self.dal.delete_student(id_card)
|
||||||
|
return success, "删除成功" if success else "删除失败"
|
||||||
|
|
||||||
|
def update_student(self, student: Student) -> tuple[bool, str]:
|
||||||
|
"""更新学生信息"""
|
||||||
|
# 数据验证
|
||||||
|
is_valid, msg = validate_student_data(student)
|
||||||
|
if not is_valid:
|
||||||
|
return False, msg
|
||||||
|
|
||||||
|
# 检查学生是否存在
|
||||||
|
existing = self.dal.get_by_id(student.id_card)
|
||||||
|
if not existing:
|
||||||
|
return False, "学生不存在"
|
||||||
|
|
||||||
|
# 检查学号是否被其他学生使用
|
||||||
|
stu_with_same_id = self.dal.get_by_stu_id(student.stu_id)
|
||||||
|
if stu_with_same_id and stu_with_same_id.id_card != student.id_card:
|
||||||
|
return False, "学号已被其他学生使用"
|
||||||
|
|
||||||
|
success = self.dal.update_student(student)
|
||||||
|
return success, "更新成功" if success else "更新失败"
|
||||||
|
|
||||||
|
def get_student_by_id(self, id_card: str) -> Optional[Student]:
|
||||||
|
"""根据身份证号获取学生"""
|
||||||
|
if not is_valid_id_card(id_card):
|
||||||
|
return None
|
||||||
|
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_by_major(self) -> dict:
|
||||||
|
"""按专业统计人数"""
|
||||||
|
return self.dal.count_by_major()
|
||||||
|
|
||||||
|
def calculate_avg_height(self, group_by: str = None) -> dict:
|
||||||
|
"""计算平均身高"""
|
||||||
|
students = self.dal.get_all()
|
||||||
|
if not group_by:
|
||||||
|
heights = [s.height for s in students if s.height is not None]
|
||||||
|
return {'all': sum(heights) / len(heights)} if heights else {'all': 0}
|
||||||
|
|
||||||
|
groups = {}
|
||||||
|
for student in students:
|
||||||
|
if student.height is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = getattr(student, group_by)
|
||||||
|
if key not in groups:
|
||||||
|
groups[key] = []
|
||||||
|
groups[key].append(student.height)
|
||||||
|
|
||||||
|
return {k: sum(v) / len(v) for k, v in groups.items()}
|
||||||
|
|
||||||
|
def clear_all_data(self) -> tuple[bool, str]:
|
||||||
|
"""清空所有学生数据"""
|
||||||
|
success = self.dal.clear_all()
|
||||||
|
return success, "数据已清空" if success else "清空失败"
|
@ -0,0 +1,6 @@
|
|||||||
|
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)
|
@ -0,0 +1,4 @@
|
|||||||
|
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)
|
@ -0,0 +1,3 @@
|
|||||||
|
class StudentTUI:
|
||||||
|
def __init__(self,bll:StudentBLL):
|
||||||
|
self.bll=bll
|
@ -0,0 +1 @@
|
|||||||
|
def add_student()
|
@ -0,0 +1,88 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from typing_extensions import Optional
|
||||||
|
from xlwings.pro.reports.filters import height
|
||||||
|
|
||||||
|
from 面向对象学生系统.model.test.student import Student
|
||||||
|
class StudentDAL:
|
||||||
|
def __init__(self,file_path:str):
|
||||||
|
self.file_path=file_path
|
||||||
|
self._ensure_file_exists()
|
||||||
|
def _ensure_file_exist(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_student_by_class_name(slef,class_nmae:str)-> Lise[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,upadted_student:Student)-> bool:
|
||||||
|
students =self.load_students()
|
||||||
|
for i, student in enumerate(students):
|
||||||
|
if student.sid== sid:
|
||||||
|
student[i]=upadted_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 students.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 enrollemnt_date is not None:
|
||||||
|
students[i].enrollemnt_date=enrollemnt_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:
|
||||||
|
students= self.load_students()
|
||||||
|
initial_length=len(students)
|
||||||
|
students=[student for student in students if student.sid!= sid]
|
||||||
|
if len(students)<initial_length:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
|||||||
|
def display_menu(self):
|
||||||
|
print("\n===== 学生信息管理系统 ====")
|
||||||
|
print("1.添加学生信息")
|
||||||
|
print("2.删除学生信息")
|
||||||
|
print("3.更新学生信息")
|
||||||
|
print("4.查询学生信息")
|
||||||
|
print("5.统计分析")
|
||||||
|
print("6.数据导入导出")
|
||||||
|
print("7.清空所有学生信息")
|
||||||
|
print("8.退出系统")
|
||||||
|
print("==========================")
|
||||||
|
def display_query_menu(self):
|
||||||
|
print("\n===== 查询学生信息 ====")
|
||||||
|
print("1.查询所有学生")
|
||||||
|
print("2.按学生ID查询")
|
||||||
|
print("3.按姓名查询")
|
||||||
|
print("4.按班级查询")
|
||||||
|
print("5.返回上一级")
|
||||||
|
print("==========================")
|
||||||
|
def display_states_menu(self):
|
||||||
|
print("\n===== 统计分析 ====")
|
||||||
|
print("1.学生总数")
|
||||||
|
print("2.平均身高")
|
||||||
|
print("3.按身高范围统计")
|
||||||
|
print("4.按入学年份统计")
|
||||||
|
print("5.按年龄范围统计")
|
||||||
|
print("6.返回上一级")
|
||||||
|
print("==========================")
|
||||||
|
def display_import_export_menu(self):
|
||||||
|
print("\n===== 数据导入导出 ====")
|
||||||
|
print("1.导出数据到JSON")
|
||||||
|
print("2.从JSON导入数据")
|
||||||
|
print("3.返回上一级")
|
||||||
|
print("==========================")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
|||||||
|
class StudentBll:
|
||||||
|
def __init__(self,dal:StudentDal):
|
||||||
|
self.dal=dal
|
||||||
|
def delete_student(self,sid:str)-> bool:
|
||||||
|
if not self.dal.check_student_exists(sid):
|
||||||
|
raise ValueError(f"学生 ID {sid}不存在。")
|
||||||
|
return self.dal.delete_students(sid)
|
@ -0,0 +1,67 @@
|
|||||||
|
from datetime import date
|
||||||
|
from typing import Optional, Union
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Student:
|
||||||
|
name: str
|
||||||
|
id_card: str
|
||||||
|
stu_id: str
|
||||||
|
gender: Optional[bool] = None # True:男, False:女
|
||||||
|
height: Optional[int] = None # cm
|
||||||
|
weight: Optional[float] = None # kg
|
||||||
|
enrollment_date: Optional[Union[date, str]] = None
|
||||||
|
class_name: Optional[str] = None
|
||||||
|
major: Optional[str] = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def age(self) -> int:
|
||||||
|
"""从身份证号计算年龄(周岁)"""
|
||||||
|
from utils.id_utils import calculate_age
|
||||||
|
return calculate_age(self.id_card)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def birthday(self) -> date:
|
||||||
|
"""从身份证号提取出生日期"""
|
||||||
|
from utils.id_utils import extract_birthday
|
||||||
|
return extract_birthday(self.id_card)
|
||||||
|
|
||||||
|
def to_dict(self) -> dict:
|
||||||
|
"""将学生对象转换为字典"""
|
||||||
|
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 isinstance(self.enrollment_date, date) else self.enrollment_date,
|
||||||
|
'class_name': self.class_name,
|
||||||
|
'major': self.major
|
||||||
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> 'Student':
|
||||||
|
"""从字典创建学生对象"""
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
enrollment_date = data.get('enrollment_date')
|
||||||
|
if enrollment_date and isinstance(enrollment_date, str):
|
||||||
|
try:
|
||||||
|
enrollment_date = datetime.strptime(enrollment_date, '%Y-%m-%d').date()
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
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=enrollment_date,
|
||||||
|
class_name=data.get('class_name'),
|
||||||
|
major=data.get('major')
|
||||||
|
)
|
@ -0,0 +1,197 @@
|
|||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
from typing import Optional
|
||||||
|
from models.student import Student
|
||||||
|
from bll.student_service import StudentService
|
||||||
|
|
||||||
|
|
||||||
|
class ConsoleUI:
|
||||||
|
"""控制台用户界面"""
|
||||||
|
|
||||||
|
def __init__(self, service: StudentService):
|
||||||
|
self.service = service
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""运行主界面"""
|
||||||
|
while True:
|
||||||
|
self._display_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._query_students()
|
||||||
|
elif choice == '5':
|
||||||
|
self._statistics()
|
||||||
|
elif choice == '6':
|
||||||
|
self._import_export()
|
||||||
|
elif choice == '7':
|
||||||
|
self._clear_all_data()
|
||||||
|
elif choice == '0':
|
||||||
|
print("感谢使用学生信息管理系统,再见!")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("无效选择,请重新输入!")
|
||||||
|
|
||||||
|
def _display_main_menu(self):
|
||||||
|
"""显示主菜单"""
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print(" 学生信息管理系统 ")
|
||||||
|
print("=" * 30)
|
||||||
|
print("1. 添加学生信息")
|
||||||
|
print("2. 删除学生信息")
|
||||||
|
print("3. 更新学生信息")
|
||||||
|
print("4. 查询学生信息")
|
||||||
|
print("5. 统计分析")
|
||||||
|
print("6. 数据导入导出")
|
||||||
|
print("7. 清空所有学生信息")
|
||||||
|
print("0. 退出系统")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
def _add_student(self):
|
||||||
|
"""添加学生"""
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print(" 添加学生信息 ")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
student = self._input_student_info()
|
||||||
|
if student is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
success, msg = self.service.add_student(student)
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
def _delete_student(self):
|
||||||
|
"""删除学生"""
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print(" 删除学生信息 ")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
id_card = input("请输入要删除学生的身份证号: ").strip()
|
||||||
|
success, msg = self.service.delete_student(id_card)
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
def _update_student(self):
|
||||||
|
"""更新学生信息"""
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print(" 更新学生信息 ")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
id_card = input("请输入要更新学生的身份证号: ").strip()
|
||||||
|
existing = self.service.get_student_by_id(id_card)
|
||||||
|
if not existing:
|
||||||
|
print("学生不存在")
|
||||||
|
return
|
||||||
|
|
||||||
|
print("\n当前学生信息:")
|
||||||
|
self._display_student_details(existing)
|
||||||
|
|
||||||
|
print("\n请输入新的学生信息(留空保持原值):")
|
||||||
|
student = self._input_student_info(existing)
|
||||||
|
if student is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
success, msg = self.service.update_student(student)
|
||||||
|
print(msg)
|
||||||
|
|
||||||
|
def _query_students(self):
|
||||||
|
"""查询学生信息"""
|
||||||
|
while True:
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print(" 查询学生信息 ")
|
||||||
|
print("=" * 30)
|
||||||
|
print("1. 查询所有学生")
|
||||||
|
print("2. 按身份证号查询")
|
||||||
|
print("3. 按学号查询")
|
||||||
|
print("4. 按姓名查询")
|
||||||
|
print("5. 按班级查询")
|
||||||
|
print("6. 按专业查询")
|
||||||
|
print("7. 返回上一级")
|
||||||
|
print("=" * 30)
|
||||||
|
|
||||||
|
choice = input("请选择操作: ").strip()
|
||||||
|
|
||||||
|
if choice == '1':
|
||||||
|
self._list_all_students()
|
||||||
|
elif choice == '2':
|
||||||
|
self._query_by_id_card()
|
||||||
|
elif choice == '3':
|
||||||
|
self._query_by_stu_id()
|
||||||
|
elif choice == '4':
|
||||||
|
self._query_by_name()
|
||||||
|
elif choice == '5':
|
||||||
|
self._query_by_class()
|
||||||
|
elif choice == '6':
|
||||||
|
self._query_by_major()
|
||||||
|
elif choice == '7':
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("无效选择,请重新输入!")
|
||||||
|
|
||||||
|
def _statistics(self):
|
||||||
|
"""统计分析"""
|
||||||
|
while True:
|
||||||
|
print("\n" + "=" * 30)
|
||||||
|
print(" 统计分析 ")
|
||||||
|
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}")
|
||||||
|
elif choice == '2':
|
||||||
|
stats = self.service.count_by_major()
|
||||||
|
print("\n各专业人数统计:")
|
||||||
|
for major, count in stats.items():
|
||||||
|
print(f"{major}: {count}人")
|
||||||
|
elif choice == '3':
|
||||||
|
avg_height = self.service.calculate_avg_height()
|
||||||
|
print(f"\n全体学生平均身高: {avg_height['all']:.1f}cm")
|
||||||
|
elif choice == '4':
|
||||||
|
# 类似实现平均体重
|
||||||
|
pass
|
||||||
|
elif choice == '5':
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
print("无效选择,请重新输入!")
|
||||||
|
|
||||||
|
def _input_student_info(self, existing: Student = None) -> Optional[Student]:
|
||||||
|
"""输入学生信息"""
|
||||||
|
try:
|
||||||
|
name = self._get_input("姓名", existing.name if existing else None, required=True)
|
||||||
|
id_card = self._get_input("身份证号", existing.id_card if existing else None, required=True)
|
||||||
|
stu_id = self._get_input("学号", existing.stu_id if existing else None, required=True)
|
||||||
|
gender = self._get_gender_input(existing.gender if existing else None)
|
||||||
|
height = self._get_int_input("身高(cm)", existing.height if existing else None, 50, 250)
|
||||||
|
weight = self._get_float_input("体重(kg)", existing.weight if existing else None, 5, 300)
|
||||||
|
enrollment_date = self._get_date_input("入学日期", existing.enrollment_date if existing else None)
|
||||||
|
class_name = self._get_input("班级", existing.class_name if existing else None)
|
||||||
|
major = self._get_input("专业", existing.major if existing else None)
|
||||||
|
|
||||||
|
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"输入错误: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 其他辅助方法...
|
@ -0,0 +1,117 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import List, Optional
|
||||||
|
from models.student import Student
|
||||||
|
from dal.idal import IStudentDAL
|
||||||
|
|
||||||
|
|
||||||
|
class JsonStudentDAL(IStudentDAL):
|
||||||
|
"""JSON存储实现"""
|
||||||
|
|
||||||
|
def __init__(self, file_path: str = 'data/students.json'):
|
||||||
|
self.file_path = file_path
|
||||||
|
self._ensure_file_exists()
|
||||||
|
|
||||||
|
def _ensure_file_exists(self):
|
||||||
|
"""确保JSON文件存在"""
|
||||||
|
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
|
||||||
|
if not os.path.exists(self.file_path):
|
||||||
|
with open(self.file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump([], f)
|
||||||
|
|
||||||
|
def _load_data(self) -> List[dict]:
|
||||||
|
"""加载JSON数据"""
|
||||||
|
with open(self.file_path, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
def _save_data(self, data: List[dict]):
|
||||||
|
"""保存数据到JSON文件"""
|
||||||
|
with open(self.file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
|
||||||
|
def add_student(self, student: Student) -> bool:
|
||||||
|
data = self._load_data()
|
||||||
|
|
||||||
|
# 检查学号和身份证号是否已存在
|
||||||
|
for item in data:
|
||||||
|
if item['id_card'] == student.id_card or item['stu_id'] == student.stu_id:
|
||||||
|
return False
|
||||||
|
|
||||||
|
data.append(student.to_dict())
|
||||||
|
self._save_data(data)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def delete_student(self, id_card: str) -> bool:
|
||||||
|
data = self._load_data()
|
||||||
|
original_length = len(data)
|
||||||
|
|
||||||
|
data = [item for item in data if item['id_card'] != id_card]
|
||||||
|
|
||||||
|
if len(data) != original_length:
|
||||||
|
self._save_data(data)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def update_student(self, student: Student) -> bool:
|
||||||
|
data = self._load_data()
|
||||||
|
updated = False
|
||||||
|
|
||||||
|
for i, item in enumerate(data):
|
||||||
|
if item['id_card'] == student.id_card:
|
||||||
|
data[i] = student.to_dict()
|
||||||
|
updated = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if updated:
|
||||||
|
self._save_data(data)
|
||||||
|
return updated
|
||||||
|
|
||||||
|
def get_by_id(self, id_card: str) -> Optional[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
for item in data:
|
||||||
|
if item['id_card'] == id_card:
|
||||||
|
return Student.from_dict(item)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
for item in data:
|
||||||
|
if item['stu_id'] == stu_id:
|
||||||
|
return Student.from_dict(item)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_all(self) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data]
|
||||||
|
|
||||||
|
def get_by_name(self, name: str) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data if name.lower() in item['name'].lower()]
|
||||||
|
|
||||||
|
def get_by_class(self, class_name: str) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data
|
||||||
|
if item['class_name'] and class_name.lower() in item['class_name'].lower()]
|
||||||
|
|
||||||
|
def get_by_major(self, major: str) -> List[Student]:
|
||||||
|
data = self._load_data()
|
||||||
|
return [Student.from_dict(item) for item in data
|
||||||
|
if item['major'] and major.lower() in item['major'].lower()]
|
||||||
|
|
||||||
|
def count_students(self) -> int:
|
||||||
|
data = self._load_data()
|
||||||
|
return len(data)
|
||||||
|
|
||||||
|
def count_by_major(self) -> dict:
|
||||||
|
data = self._load_data()
|
||||||
|
majors = {}
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
major = item.get('major', '未指定')
|
||||||
|
majors[major] = majors.get(major, 0) + 1
|
||||||
|
|
||||||
|
return majors
|
||||||
|
|
||||||
|
def clear_all(self) -> bool:
|
||||||
|
self._save_data([])
|
||||||
|
return True
|
Loading…
Reference in new issue