commit
c4473a3eeb
@ -0,0 +1,3 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.11" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/PythonProject.iml" filepath="$PROJECT_DIR$/.idea/PythonProject.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
@ -0,0 +1,49 @@
|
|||||||
|
from datetime import date
|
||||||
|
from typing import Optional, List
|
||||||
|
from dal.student_dal_json import StudentDAL
|
||||||
|
from model import Student
|
||||||
|
|
||||||
|
|
||||||
|
class StudentBLL:
|
||||||
|
def __init__(self, dal: StudentDAL):
|
||||||
|
self.dal = dal
|
||||||
|
|
||||||
|
def add_student(self, student: Student) -> bool:
|
||||||
|
if self.dal.check_student_exists(student.sid):
|
||||||
|
raise ValueError(f"学生 ID {student.sid} 已存在。")
|
||||||
|
if not student.is_valid:
|
||||||
|
raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}")
|
||||||
|
return self.dal.add_student(student)
|
||||||
|
# 其他方法同理
|
||||||
|
|
||||||
|
def delete_student(self, sid: str) ->bool:
|
||||||
|
if not self.dal.check_student_exists(sid):
|
||||||
|
raise ValueError(f"学生 ID {sid} 不存在。")
|
||||||
|
return self.dal.delete_student(sid)
|
||||||
|
|
||||||
|
def update_student(self, sid: str, student: Student) -> bool:
|
||||||
|
if not self.dal.check_student_exists(sid):
|
||||||
|
raise ValueError(f"学生 ID {sid} 不存在。")
|
||||||
|
if not student.is_valid:
|
||||||
|
raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}")
|
||||||
|
return self.dal.update_student(sid, student)
|
||||||
|
|
||||||
|
def update_student_partial(self, sid: str, name: Optional[str] = None, height: Optional[int] = None,
|
||||||
|
birth_date: Optional[date] = None, enrollment_date: Optional[date] = None,
|
||||||
|
class_name: Optional[str] = None) -> bool:
|
||||||
|
if not self.dal.check_student_exists(sid):
|
||||||
|
raise ValueError(f"学生 ID {sid} 不存在。")
|
||||||
|
return self.dal.update_student_partial(sid, name, height, birth_date, enrollment_date, class_name)
|
||||||
|
|
||||||
|
def get_student_by_id(self, sid: str) -> Optional[Student]:
|
||||||
|
return self.dal.find_student_by_sid(sid)
|
||||||
|
|
||||||
|
def get_students_by_name(self, name: str) -> List[Student]:
|
||||||
|
return self.dal.find_students_by_name(name)
|
||||||
|
|
||||||
|
def get_students_by_class(self, class_name: str) -> List[Student]:
|
||||||
|
return self.dal.find_students_by_class_name(class_name)
|
||||||
|
|
||||||
|
def get_all_students(self) -> List[Student]:
|
||||||
|
return self.dal.get_all_students()
|
||||||
|
|
@ -0,0 +1,2 @@
|
|||||||
|
import json
|
||||||
|
from model.Student import Student
|
@ -0,0 +1,155 @@
|
|||||||
|
import json
|
||||||
|
import os, sys
|
||||||
|
from datetime import date
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import csv
|
||||||
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
parent_dir = os.path.dirname(current_dir)
|
||||||
|
sys.path.append(parent_dir)
|
||||||
|
from model.Student import Student
|
||||||
|
|
||||||
|
|
||||||
|
class StudentDAL:
|
||||||
|
def __init__(self, file_path: str):
|
||||||
|
self.file_path = file_path
|
||||||
|
self._ensure_file_exists()
|
||||||
|
|
||||||
|
def _ensure_file_exists(self):
|
||||||
|
if not os.path.exists(self.file_path):
|
||||||
|
with open(self.file_path, mode='w', encoding='utf-8') as file:
|
||||||
|
json.dump([], file)
|
||||||
|
|
||||||
|
def save_students(self, students: List[Student]) -> None:
|
||||||
|
student_dicts = [student.to_dict() for student in students]
|
||||||
|
with open(self.file_path, mode='w', encoding='utf-8') as file:
|
||||||
|
json.dump(student_dicts, file, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
def load_students(self) -> List[Student]:
|
||||||
|
with open(self.file_path, mode='r', encoding='utf-8') as file:
|
||||||
|
student_dicts = json.load(file)
|
||||||
|
return [Student.from_dict(student_dict) for student_dict in student_dicts]
|
||||||
|
|
||||||
|
def add_student(self, student: Student) -> bool:
|
||||||
|
students = self.load_students()
|
||||||
|
for existing_student in students:
|
||||||
|
if existing_student.sid == student.sid:
|
||||||
|
return False
|
||||||
|
students.append(student)
|
||||||
|
self.save_students(students)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def find_student_by_sid(self, sid: str) -> Optional[Student]:
|
||||||
|
students = self.load_students()
|
||||||
|
for student in students:
|
||||||
|
if student.sid == sid:
|
||||||
|
return student
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_students_by_name(self, name: str) ->List[Student]:
|
||||||
|
students = self.load_students()
|
||||||
|
return [student for student in students if name.lower() in student.name.lower()]
|
||||||
|
|
||||||
|
def find_students_by_class_name(self, class_name: str) ->List[Student]:
|
||||||
|
students = self.load_students()
|
||||||
|
return [s for s in students if class_name.lower() in s.class_name.lower()]
|
||||||
|
|
||||||
|
def get_all_students(self) -> List[Student]:
|
||||||
|
return self.load_students()
|
||||||
|
|
||||||
|
def update_student(self, sid: str, updated_student: Student) -> bool:
|
||||||
|
students = self.load_students()
|
||||||
|
for i, student in enumerate(students):
|
||||||
|
if student.sid == sid:
|
||||||
|
students[i] = updated_student
|
||||||
|
self.save_students(students)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
def update_student_partial(self, sid:str, name:Optional[str]=None, height:Optional[float]=None, birth_date:Optional[date]=None, enrollment_date:Optional[date]=None, class_name:Optional[str]=None) ->bool:
|
||||||
|
students = self.load_students()
|
||||||
|
for i, student in enumerate(students):
|
||||||
|
if student.sid == sid:
|
||||||
|
if name is not None:
|
||||||
|
students[i].name = name
|
||||||
|
if height is not None:
|
||||||
|
students[i].height = height
|
||||||
|
if birth_date is not None:
|
||||||
|
students[i].birth_date = birth_date
|
||||||
|
if enrollment_date is not None:
|
||||||
|
students[i].enrollment_date = enrollment_date
|
||||||
|
if class_name is not None:
|
||||||
|
students[i].class_name = class_name
|
||||||
|
self.save_students(students)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def delete_student(self, sid: str) -> bool:
|
||||||
|
"""根据学生 ID 删除学生"""
|
||||||
|
students = self.load_students()
|
||||||
|
initial_length = len(students)
|
||||||
|
students = [student for student in students if student.sid != sid]
|
||||||
|
if len(students) < initial_length:
|
||||||
|
self.save_students(students)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear_all_students(self) -> None:
|
||||||
|
self.save_students([])
|
||||||
|
|
||||||
|
def get_student_count(self) -> int:
|
||||||
|
return len(self.load_students())
|
||||||
|
|
||||||
|
def check_student_exists(self, sid: str) ->bool:
|
||||||
|
return self.find_student_by_sid(sid) is not None
|
||||||
|
|
||||||
|
def export_to_json(self, file_path):
|
||||||
|
students = self.load_students()
|
||||||
|
student_dicts = [student.to_dict() for student in students]
|
||||||
|
with open(file_path, mode='w', encoding='utf-8') as file:
|
||||||
|
json.dump(student_dicts, file, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
def import_from_json(self, file_path):
|
||||||
|
with open(file_path, mode='r', encoding='utf-8') as file:
|
||||||
|
student_dicts = json.load(file)
|
||||||
|
students = [Student.from_dict(student_dict) for student_dict in student_dicts]
|
||||||
|
self.save_students(students)
|
||||||
|
|
||||||
|
def export_to_csv(self, file_path):
|
||||||
|
students = self.load_students()
|
||||||
|
with open(file_path, mode='w', encoding='utf-8', newline='') as file:
|
||||||
|
fieldnames = ['sid', 'name', 'height', 'birth_date', 'enrollment_date', 'class_name']
|
||||||
|
writer = csv.DictWriter(file, fieldnames=fieldnames)
|
||||||
|
writer.writeheader()
|
||||||
|
for student in students:
|
||||||
|
writer.writerow(student.to_dict())
|
||||||
|
|
||||||
|
def import_from_csv(self, file_path):
|
||||||
|
students = []
|
||||||
|
with open(file_path, mode='r', encoding='utf-8') as file:
|
||||||
|
reader = csv.DictReader(file)
|
||||||
|
for row in reader:
|
||||||
|
row['birth_date'] = date.fromisoformat(row['birth_date'])
|
||||||
|
row['enrollment_date'] = date.fromisoformat(row['enrollment_date'])
|
||||||
|
student = Student.from_dict(row)
|
||||||
|
students.append(student)
|
||||||
|
self.save_students(students)
|
||||||
|
|
||||||
|
def get_average_height(self):
|
||||||
|
students = self.load_students()
|
||||||
|
if not students:
|
||||||
|
return 0
|
||||||
|
total_height = sum(student.height for student in students)
|
||||||
|
return total_height / len(students)
|
||||||
|
|
||||||
|
def count_students_by_height_range(self, min_height, max_height):
|
||||||
|
students = self.load_students()
|
||||||
|
return len([student for student in students if min_height <= student.height <= max_height])
|
||||||
|
|
||||||
|
def count_students_by_enrollment_year(self, year):
|
||||||
|
students = self.load_students()
|
||||||
|
return len([student for student in students if student.enrollment_date.year == year])
|
||||||
|
|
||||||
|
def count_students_by_age_range(self, min_age, max_age):
|
||||||
|
today = date.today()
|
||||||
|
students = self.load_students()
|
||||||
|
return len([student for student in students if min_age <= today.year - student.birth_date.year <= max_age])
|
@ -0,0 +1 @@
|
|||||||
|
[]
|
@ -0,0 +1,128 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from ui.studentui import StudentUI
|
||||||
|
|
||||||
|
def main():
|
||||||
|
ui = StudentUI()
|
||||||
|
while True:
|
||||||
|
ui.display_menu()
|
||||||
|
choice = ui.get_input("请输入你的选择: ")
|
||||||
|
if choice == '1':
|
||||||
|
ui.add_student()
|
||||||
|
elif choice == '2':
|
||||||
|
sid = ui.get_input("请输入要删除的学生学号: ")
|
||||||
|
try:
|
||||||
|
if ui.bll.delete_student(sid):
|
||||||
|
print("学生信息删除成功!")
|
||||||
|
else:
|
||||||
|
print("学生信息删除失败。")
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
elif choice == '3':
|
||||||
|
sid = ui.get_input("请输入要更新的学生学号: ")
|
||||||
|
student = ui.bll.get_student_by_id(sid)
|
||||||
|
if student:
|
||||||
|
# 获取更新信息
|
||||||
|
name = ui.get_input(f"请输入新的姓名(当前:{student.name}),不修改请直接回车: ")
|
||||||
|
height = ui.get_input(f"请输入新的身高(当前:{student.height}),不修改请直接回车: ")
|
||||||
|
birth_date = ui.get_input(f"请输入新的出生日期(当前:{student.birth_date}),不修改请直接回车: ")
|
||||||
|
enrollment_date = ui.get_input(f"请输入新的入学日期(当前:{student.enrollment_date}),不修改请直接回车: ")
|
||||||
|
class_name = ui.get_input(f"请输入新的班级(当前:{student.class_name}),不修改请直接回车: ")
|
||||||
|
|
||||||
|
if name:
|
||||||
|
student.name = name
|
||||||
|
if height:
|
||||||
|
student.height = int(height)
|
||||||
|
if birth_date:
|
||||||
|
student.birth_date = date.fromisoformat(birth_date)
|
||||||
|
if enrollment_date:
|
||||||
|
student.enrollment_date = date.fromisoformat(enrollment_date)
|
||||||
|
if class_name:
|
||||||
|
student.class_name = class_name
|
||||||
|
|
||||||
|
try:
|
||||||
|
if ui.bll.update_student(sid, student):
|
||||||
|
print("学生信息更新成功!")
|
||||||
|
else:
|
||||||
|
print("学生信息更新失败。")
|
||||||
|
except ValueError as e:
|
||||||
|
print(e)
|
||||||
|
else:
|
||||||
|
print(f"学生 ID {sid} 不存在。")
|
||||||
|
elif choice == '4':
|
||||||
|
ui.display_query_menu()
|
||||||
|
query_choice = ui.get_input("请输入你的查询选择: ")
|
||||||
|
if query_choice == '1':
|
||||||
|
students = ui.bll.get_all_students()
|
||||||
|
for student in students:
|
||||||
|
print(student)
|
||||||
|
elif query_choice == '2':
|
||||||
|
sid = ui.get_input("请输入要查询的学生学号: ")
|
||||||
|
student = ui.bll.get_student_by_id(sid)
|
||||||
|
if student:
|
||||||
|
print(student)
|
||||||
|
else:
|
||||||
|
print(f"学生 ID {sid} 不存在。")
|
||||||
|
elif query_choice == '3':
|
||||||
|
# 这里和按学号查询逻辑一样
|
||||||
|
sid = ui.get_input("请输入要查询的学生学号: ")
|
||||||
|
student = ui.bll.get_student_by_id(sid)
|
||||||
|
if student:
|
||||||
|
print(student)
|
||||||
|
else:
|
||||||
|
print(f"学生 ID {sid} 不存在。")
|
||||||
|
elif query_choice == '4':
|
||||||
|
name = ui.get_input("请输入要查询的学生姓名: ")
|
||||||
|
students = ui.bll.get_students_by_name(name)
|
||||||
|
for student in students:
|
||||||
|
print(student)
|
||||||
|
elif query_choice == '5':
|
||||||
|
class_name = ui.get_input("请输入要查询的班级: ")
|
||||||
|
students = ui.bll.get_students_by_class(class_name)
|
||||||
|
for student in students:
|
||||||
|
print(student)
|
||||||
|
elif query_choice == '0':
|
||||||
|
continue
|
||||||
|
elif choice == '5':
|
||||||
|
ui.display_stats_menu()
|
||||||
|
stats_choice = ui.get_input("请输入你的统计选择: ")
|
||||||
|
if stats_choice == '1':
|
||||||
|
count = ui.bll.dal.get_student_count()
|
||||||
|
print(f"学生总数为: {count}")
|
||||||
|
elif stats_choice == '2':
|
||||||
|
average_height = ui.bll.dal.get_average_height()
|
||||||
|
print(f"学生平均身高为: {average_height:.2f} cm")
|
||||||
|
elif stats_choice == '3':
|
||||||
|
min_height = int(ui.get_input("请输入最小身高: "))
|
||||||
|
max_height = int(ui.get_input("请输入最大身高: "))
|
||||||
|
count = ui.bll.dal.count_students_by_height_range(min_height, max_height)
|
||||||
|
print(f"身高在 {min_height} - {max_height} cm 之间的学生数量为: {count}")
|
||||||
|
elif stats_choice == '4':
|
||||||
|
year = int(ui.get_input("请输入入学年份: "))
|
||||||
|
count = ui.bll.dal.count_students_by_enrollment_year(year)
|
||||||
|
print(f"{year} 年入学的学生数量为: {count}")
|
||||||
|
elif stats_choice == '5':
|
||||||
|
min_age = int(ui.get_input("请输入最小年龄: "))
|
||||||
|
max_age = int(ui.get_input("请输入最大年龄: "))
|
||||||
|
count = ui.bll.dal.count_students_by_age_range(min_age, max_age)
|
||||||
|
print(f"年龄在 {min_age} - {max_age} 岁之间的学生数量为: {count}")
|
||||||
|
elif stats_choice == '0':
|
||||||
|
continue
|
||||||
|
elif choice == '6':
|
||||||
|
ui.display_import_export_menu()
|
||||||
|
import_export_choice = ui.get_input("请输入你的导入导出选择: ")
|
||||||
|
if import_export_choice != '0':
|
||||||
|
ui.handle_import_export(import_export_choice)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
elif choice == '7':
|
||||||
|
try:
|
||||||
|
ui.bll.dal.clear_all_students()
|
||||||
|
print("所有学生信息已清空!")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"清空学生信息失败: {e}")
|
||||||
|
elif choice == '0':
|
||||||
|
break
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -0,0 +1,86 @@
|
|||||||
|
from datetime import date
|
||||||
|
|
||||||
|
class Student:
|
||||||
|
def __init__(self, sid: str, name: str, height: str, birth_date: date | str, enrollment_date: date | str, class_name: str):
|
||||||
|
self.sid = sid
|
||||||
|
self.name = name.strip()
|
||||||
|
self.height = int(height) # 转换为整数类型
|
||||||
|
self.birth_date = birth_date if isinstance(birth_date, date) else date.fromisoformat(birth_date)
|
||||||
|
self.enrollment_date = enrollment_date if isinstance(enrollment_date, date) else date.fromisoformat(enrollment_date)
|
||||||
|
self.class_name = class_name
|
||||||
|
self._validation_errors = []
|
||||||
|
self._validate_height()
|
||||||
|
self._validate_date()
|
||||||
|
self._validate_name()
|
||||||
|
|
||||||
|
def _validate_height(self) -> None:
|
||||||
|
if not (50 <= self.height <= 250):
|
||||||
|
self._validation_errors.append(f"身高{self.height}cm超出合理范围")
|
||||||
|
|
||||||
|
def _validate_name(self) -> None:
|
||||||
|
if not (2 <= len(self.name) <=20):
|
||||||
|
self._validation_errors.append("姓名长度需在2-20个字符之间")
|
||||||
|
if not self.name.isprintable():
|
||||||
|
self._validation_errors.append("姓名长度需在2-20个字符之间")
|
||||||
|
def _validate_date(self) -> None:
|
||||||
|
today = date.today()
|
||||||
|
if self.birth_date > today:
|
||||||
|
self._validation_errors.append('出生日期不能在未来')
|
||||||
|
if self.enrollment_date > today:
|
||||||
|
self._validation_errors.append('入学日期不能在未来')
|
||||||
|
if self.birth_date > self.enrollment_date:
|
||||||
|
self._validation_errors.append('入学日期不能早于出生日期')
|
||||||
|
@property
|
||||||
|
def is_valid(self) -> bool:
|
||||||
|
return len(self._validation_errors) == 0
|
||||||
|
def get_errors(self) -> list[str]:
|
||||||
|
return self._validation_errors.copy()
|
||||||
|
def __eq__(self, other) -> bool:
|
||||||
|
if not isinstance(other, Student):
|
||||||
|
return NotImplemented
|
||||||
|
return (self.sid == other.sid and
|
||||||
|
self.name == other.name and
|
||||||
|
self.height == other.height and
|
||||||
|
self.birth_date == other.birth_date and
|
||||||
|
self.enrollment_date == other.enrollment_date and
|
||||||
|
self.class_name == other.class_name
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_dict(cls, data: dict) -> 'Student':
|
||||||
|
birth_date = data['birth_date'] if isinstance(data['birth_date'], date) else date.fromisoformat(
|
||||||
|
data['birth_date'])
|
||||||
|
enrollment_date = data['enrollment_date'] if isinstance(data['enrollment_date'], date) else date.fromisoformat(
|
||||||
|
data['enrollment_date'])
|
||||||
|
return cls(
|
||||||
|
sid=data['sid'],
|
||||||
|
name=data['name'].strip(),
|
||||||
|
height=data['height'],
|
||||||
|
birth_date=birth_date,
|
||||||
|
enrollment_date=enrollment_date,
|
||||||
|
class_name=data['class_name']
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
'sid': self.sid,
|
||||||
|
'name': self.name,
|
||||||
|
'height': self.height,
|
||||||
|
'birth_date': self.birth_date.isoformat(),
|
||||||
|
'enrollment_date': self.enrollment_date.isoformat(),
|
||||||
|
'class_name': self.class_name
|
||||||
|
}
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return (
|
||||||
|
f"{self.__class__.__name__}("
|
||||||
|
f"sid='{self.sid}', "
|
||||||
|
f"name='{self.name}',"
|
||||||
|
f"height={self.height},"
|
||||||
|
f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day}), "
|
||||||
|
f"enrollment_date=date({self.enrollment_date.year},{self.enrollment_date.month},{self.enrollment_date.day}), "
|
||||||
|
f"class_name='{self.class_name}'"
|
||||||
|
f")"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue