c1785 1 month ago
parent 36813aaa18
commit 4d4bd8e2e2

@ -14,13 +14,18 @@ class StudentBLL:
if not student.is_valid: if not student.is_valid:
raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}") raise ValueError(f"学生数据校验不通过。错误信息:{student.get_errors()}")
return self.dal.add_student(student) return self.dal.add_student(student)
# 其他方法同理
def delete_student(self, sid: str) ->bool: def delete_student(self, sid: str) -> bool:
if not self.dal.check_student_exists(sid): if not self.dal.check_student_exists(sid):
raise ValueError(f"学生 ID {sid} 不存在。") raise ValueError(f"学生 ID {sid} 不存在。")
return self.dal.delete_student(sid) return self.dal.delete_student(sid)
def delete_student_by_id_number(self, id_number: str) -> bool:
student = self.dal.find_student_by_id_number(id_number)
if not student:
raise ValueError(f"身份证号为 {id_number} 的学生不存在。")
return self.dal.delete_student_by_id_number(id_number)
def update_student(self, sid: str, student: Student) -> bool: def update_student(self, sid: str, student: Student) -> bool:
if not self.dal.check_student_exists(sid): if not self.dal.check_student_exists(sid):
raise ValueError(f"学生 ID {sid} 不存在。") raise ValueError(f"学生 ID {sid} 不存在。")
@ -30,20 +35,39 @@ class StudentBLL:
def update_student_partial(self, sid: str, name: Optional[str] = None, height: Optional[int] = None, 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, birth_date: Optional[date] = None, enrollment_date: Optional[date] = None,
class_name: Optional[str] = None) -> bool: class_name: Optional[str] = None, major: Optional[str] = None,
phone: Optional[str] = None, email: Optional[str] = None) -> bool:
if not self.dal.check_student_exists(sid): if not self.dal.check_student_exists(sid):
raise ValueError(f"学生 ID {sid} 不存在。") raise ValueError(f"学生 ID {sid} 不存在。")
return self.dal.update_student_partial(sid, name, height, birth_date, enrollment_date, class_name) return self.dal.update_student_partial(sid, name, height, birth_date, enrollment_date,
class_name, major, phone, email)
def get_student_by_id(self, sid: str) -> Optional[Student]: def get_student_by_id(self, sid: str) -> Optional[Student]:
return self.dal.find_student_by_sid(sid) return self.dal.find_student_by_sid(sid)
def find_student_by_id_number(self, id_number: str) -> Optional[Student]:
return self.dal.find_student_by_id_number(id_number)
def get_students_by_name(self, name: str) -> List[Student]: def get_students_by_name(self, name: str) -> List[Student]:
return self.dal.find_students_by_name(name) return self.dal.find_students_by_name(name)
def get_students_by_class(self, class_name: str) -> List[Student]: def get_students_by_class(self, class_name: str) -> List[Student]:
return self.dal.find_students_by_class_name(class_name) return self.dal.find_students_by_class_name(class_name)
def get_students_by_major(self, major: str) -> List[Student]:
return self.dal.find_students_by_major(major)
def get_all_students(self) -> List[Student]: def get_all_students(self) -> List[Student]:
return self.dal.get_all_students() return self.dal.get_all_students()
def count_students_by_major(self):
"""统计各专业学生人数"""
return self.dal.count_students_by_major()
def calculate_average_height_weight(self, group_by='major'):
"""计算平均身高/体重(按班级或专业)"""
return self.dal.calculate_average_height_weight(group_by)
def calculate_gender_ratio(self):
"""统计性别比例"""
return self.dal.calculate_gender_ratio()

@ -1,117 +1,10 @@
import json import json
import os, sys,csv import os
import csv
from datetime import date from datetime import date
from typing import List, Optional from typing import List, Optional
from model.Student import Student from model.Student import Student
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 StudentDALJSON:
def __init__(self, file_path="students.json"):
self.file_path = file_path
def get_all_students(self):
try:
with open(self.file_path, 'r', encoding='utf-8') as file:
student_dicts = json.load(file)
return [Student.from_dict(s) for s in student_dicts]
except (FileNotFoundError, json.JSONDecodeError):
return []
def save_students(self, students):
student_dicts = [student.to_dict() for student in students]
with open(self.file_path, 'w', encoding='utf-8') as file:
json.dump(student_dicts, file, ensure_ascii=False, indent=4)
# 新增导出为JSON
def export_to_json(self, file_path=None):
if not file_path:
file_path = self.file_path
students = self.get_all_students()
if not students:
print("没有数据可导出")
return
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump([s.to_dict() for s in students], f, ensure_ascii=False, indent=4)
print(f"数据已导出到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
# 新增导出为CSV
def export_to_csv(self, file_path="students.csv"):
students = self.get_all_students()
if not students:
print("没有数据可导出")
return
try:
with open(file_path, 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(
f,
fieldnames=[
'student_id', 'name', 'gender', 'age', 'class_name',
'major', 'phone', 'email', 'id_number',
'height', 'weight', 'birth_date', 'enrollment_date'
]
)
writer.writeheader()
for student in students:
data = student.to_dict()
# 将日期对象转换为字符串
data['birth_date'] = data['birth_date'].isoformat()
data['enrollment_date'] = data['enrollment_date'].isoformat()
writer.writerow(data)
print(f"数据已导出到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
# 新增从JSON导入
def import_from_json(self, file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
student_dicts = json.load(f)
students = []
for data in student_dicts:
# 转换日期字符串为日期对象
data['birth_date'] = date.fromisoformat(data['birth_date'])
data['enrollment_date'] = date.fromisoformat(data['enrollment_date'])
students.append(Student.from_dict(data))
self.save_students(students)
print(f"{file_path} 导入成功")
except FileNotFoundError:
print(f"文件不存在: {file_path}")
except json.JSONDecodeError:
print(f"JSON格式错误: {file_path}")
except Exception as e:
print(f"导入失败: {e}")
# 新增从CSV导入
def import_from_csv(self, file_path):
try:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
student_dicts = list(reader)
students = []
for data in student_dicts:
# 转换数值类型
data['age'] = int(data['age'])
data['height'] = float(data['height'])
data['weight'] = float(data['weight'])
# 转换日期类型
data['birth_date'] = date.fromisoformat(data['birth_date'])
data['enrollment_date'] = date.fromisoformat(data['enrollment_date'])
students.append(Student.from_dict(data))
self.save_students(students)
print(f"{file_path} 导入成功")
except FileNotFoundError:
print(f"文件不存在: {file_path}")
except Exception as e:
print(f"导入失败: {e}")
class StudentDAL: class StudentDAL:
def __init__(self, file_path: str): def __init__(self, file_path: str):
self.file_path = file_path self.file_path = file_path
@ -148,13 +41,24 @@ class StudentDAL:
return student return student
return None return None
def find_students_by_name(self, name: str) ->List[Student]: def find_student_by_id_number(self, id_number: str) -> Optional[Student]:
students = self.load_students()
for student in students:
if student.id_number == id_number:
return student
return None
def find_students_by_name(self, name: str) -> List[Student]:
students = self.load_students() students = self.load_students()
return [student for student in students if name.lower() in student.name.lower()] 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]: def find_students_by_class_name(self, class_name: str) -> List[Student]:
students = self.load_students() students = self.load_students()
return [s for s in students if class_name.lower() in s.class_name.lower()] return [student for student in students if class_name.lower() in student.class_name.lower()]
def find_students_by_major(self, major: str) -> List[Student]:
students = self.load_students()
return [student for student in students if major.lower() in student.major.lower()]
def get_all_students(self) -> List[Student]: def get_all_students(self) -> List[Student]:
return self.load_students() return self.load_students()
@ -168,7 +72,10 @@ class StudentDAL:
return True return True
return False 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: 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, major: Optional[str] = None,
phone: Optional[str] = None, email: Optional[str] = None) -> bool:
students = self.load_students() students = self.load_students()
for i, student in enumerate(students): for i, student in enumerate(students):
if student.sid == sid: if student.sid == sid:
@ -182,12 +89,17 @@ class StudentDAL:
students[i].enrollment_date = enrollment_date students[i].enrollment_date = enrollment_date
if class_name is not None: if class_name is not None:
students[i].class_name = class_name students[i].class_name = class_name
if major is not None:
students[i].major = major
if phone is not None:
students[i].phone = phone
if email is not None:
students[i].email = email
self.save_students(students) self.save_students(students)
return True return True
return False return False
def delete_student(self, sid: str) -> bool: def delete_student(self, sid: str) -> bool:
"""根据学生 ID 删除学生"""
students = self.load_students() students = self.load_students()
initial_length = len(students) initial_length = len(students)
students = [student for student in students if student.sid != sid] students = [student for student in students if student.sid != sid]
@ -196,13 +108,22 @@ class StudentDAL:
return True return True
return False return False
def delete_student_by_id_number(self, id_number: str) -> bool:
students = self.load_students()
initial_length = len(students)
students = [student for student in students if student.id_number != id_number]
if len(students) < initial_length:
self.save_students(students)
return True
return False
def clear_all_students(self) -> None: def clear_all_students(self) -> None:
self.save_students([]) self.save_students([])
def get_student_count(self) -> int: def get_student_count(self) -> int:
return len(self.load_students()) return len(self.load_students())
def check_student_exists(self, sid: str) ->bool: def check_student_exists(self, sid: str) -> bool:
return self.find_student_by_sid(sid) is not None return self.find_student_by_sid(sid) is not None
def export_to_json(self, file_path): def export_to_json(self, file_path):
@ -226,12 +147,28 @@ class StudentDAL:
def export_to_csv(self, file_path): def export_to_csv(self, file_path):
students = self.load_students() students = self.load_students()
with open(file_path, mode='w', encoding='utf-8', newline='') as file: if not students:
fieldnames = ['sid', 'name', 'height', 'birth_date', 'enrollment_date', 'class_name'] print("没有数据可导出")
writer = csv.DictWriter(file, fieldnames=fieldnames) return
writer.writeheader() try:
for student in students: with open(file_path, 'w', encoding='utf-8-sig', newline='') as f:
writer.writerow(student.to_dict()) writer = csv.DictWriter(
f,
fieldnames=[
'sid', 'name', 'gender', 'height', 'weight', 'birth_date', 'enrollment_date',
'class_name', 'id_number', 'phone', 'email', 'major'
]
)
writer.writeheader()
for student in students:
data = student.to_dict()
# 将日期对象转换为字符串
data['birth_date'] = data['birth_date'].isoformat()
data['enrollment_date'] = data['enrollment_date'].isoformat()
writer.writerow(data)
print(f"数据已导出到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
def import_from_csv(self, file_path): def import_from_csv(self, file_path):
try: try:
@ -253,18 +190,18 @@ class StudentDAL:
students = self.load_students() students = self.load_students()
if not students: if not students:
return 0 return 0
total_height = sum(student.height for student in students) total_height = sum([student.height for student in students])
return total_height / len(students) return total_height / len(students)
def count_students_by_height_range(self, min_height, max_height): def count_students_by_height_range(self, min_height: int, max_height: int):
students = self.load_students() students = self.load_students()
return len([student for student in students if min_height <= student.height <= max_height]) return len([student for student in students if min_height <= student.height <= max_height])
def count_students_by_enrollment_year(self, year): def count_students_by_enrollment_year(self, year: int):
students = self.load_students() students = self.load_students()
return len([student for student in students if student.enrollment_date.year == year]) return len([student for student in students if student.enrollment_date.year == year])
def count_students_by_age_range(self, min_age, max_age): def count_students_by_age_range(self, min_age: int, max_age: int):
today = date.today() today = date.today()
students = self.load_students() students = self.load_students()
return len([student for student in students if min_age <= today.year - student.birth_date.year <= max_age]) return len([student for student in students if min_age <= today.year - student.birth_date.year <= max_age])
@ -287,4 +224,52 @@ class StudentDAL:
except FileNotFoundError: except FileNotFoundError:
print(f"备份文件 {backup_path} 未找到。") print(f"备份文件 {backup_path} 未找到。")
except Exception as e: except Exception as e:
print(f"数据恢复失败: {e}") print(f"数据恢复失败: {e}")
def count_students_by_major(self):
"""统计各专业学生人数"""
students = self.load_students()
major_count = {}
for student in students:
major = student.major
if major in major_count:
major_count[major] += 1
else:
major_count[major] = 1
return major_count
def calculate_average_height_weight(self, group_by='major'):
"""计算平均身高/体重(按班级或专业)"""
students = self.load_students()
group_info = {}
for student in students:
group_key = getattr(student, group_by)
if group_key not in group_info:
group_info[group_key] = {'height_sum': 0, 'weight_sum': 0, 'count': 0}
group_info[group_key]['height_sum'] += student.height
group_info[group_key]['weight_sum'] += student.weight
group_info[group_key]['count'] += 1
result = {}
for group, info in group_info.items():
average_height = info['height_sum'] / info['count']
average_weight = info['weight_sum'] / info['count']
result[group] = {'average_height': average_height, 'average_weight': average_weight}
return result
def calculate_gender_ratio(self):
"""统计性别比例"""
students = self.load_students()
male_count = 0
female_count = 0
for student in students:
if student.gender == '':
male_count += 1
elif student.gender == '':
female_count += 1
total_count = male_count + female_count
if total_count == 0:
return {'male_ratio': 0, 'female_ratio': 0}
male_ratio = male_count / total_count
female_ratio = female_count / total_count
return {'male_ratio': male_ratio, 'female_ratio': female_ratio}

@ -1,10 +1 @@
[ []
{
"sid": "110309230907041",
"name": "cgt",
"height": 180,
"birth_date": "2002-01-25",
"enrollment_date": "2023-09-01",
"class_name": "cs"
}
]

@ -1,129 +1,9 @@
from datetime import date
from ui.studentui import StudentUI from ui.studentui import StudentUI
def main(): def main():
"""主函数"""
ui = StudentUI() ui = StudentUI()
while True: ui.run()
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__': if __name__ == "__main__":
main() main()

@ -1,27 +1,46 @@
from datetime import date from datetime import date
import re
class Student: class Student:
def __init__(self, sid: str, name: str, height: str, birth_date: date | str, enrollment_date: date | str, class_name: str): def __init__(self, sid: str, name: str, gender: str, height: int, weight: float, birth_date: date | str,
enrollment_date: date | str, class_name: str, id_number: str, phone: str, email: str, major: str):
self.sid = sid self.sid = sid
self.name = name.strip() self.name = name.strip()
self.height = int(height) # 转换为整数类型 self.gender = gender
self.height = height
self.weight = weight
self.birth_date = birth_date if isinstance(birth_date, date) else date.fromisoformat(birth_date) 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.enrollment_date = enrollment_date if isinstance(enrollment_date, date) else date.fromisoformat(enrollment_date)
self.class_name = class_name self.class_name = class_name
self.id_number = id_number
self.phone = phone
self.email = email
self.major = major
self._validation_errors = [] self._validation_errors = []
self._validate_height() self._validate_height()
self._validate_weight()
self._validate_date() self._validate_date()
self._validate_name() self._validate_name()
self._validate_id_number()
self._validate_gender()
def _validate_height(self) -> None: def _validate_height(self) -> None:
if not (50 <= self.height <= 250): if not (50 <= self.height <= 250):
self._validation_errors.append(f"身高{self.height}cm超出合理范围") self._validation_errors.append(f"身高{self.height}cm超出合理范围")
def _validate_weight(self) -> None:
if not (5 <= self.weight <= 300):
self._validation_errors.append(f"体重{self.weight}kg超出合理范围")
def _validate_name(self) -> None: def _validate_name(self) -> None:
if not (2 <= len(self.name) <= 20) or not self.name.isprintable(): if not (2 <= len(self.name) <= 20):
self._validation_errors.append("姓名长度需在2-20个字符之间且为可打印字符")
if not self.name.isprintable():
self._validation_errors.append("姓名长度需在2-20个字符之间") self._validation_errors.append("姓名长度需在2-20个字符之间")
if not all('\u4e00' <= char <= '\u9fff' or char.isascii() for char in self.name):
self._validation_errors.append("姓名只能包含中文、英文或数字")
def _validate_id_number(self) -> None:
if not re.match(r'^\d{17}[\dXx]$', self.id_number):
self._validation_errors.append("身份证号格式不正确")
def _validate_date(self) -> None: def _validate_date(self) -> None:
today = date.today() today = date.today()
@ -29,8 +48,12 @@ class Student:
self._validation_errors.append('出生日期不能在未来') self._validation_errors.append('出生日期不能在未来')
if self.enrollment_date > today: if self.enrollment_date > today:
self._validation_errors.append('入学日期不能在未来') self._validation_errors.append('入学日期不能在未来')
if self.birth_date > self.enrollment_date: if (self.enrollment_date.year - self.birth_date.year) < 15:
self._validation_errors.append('入学日期不能早于出生日期') self._validation_errors.append('入学年龄应至少15岁')
def _validate_gender(self) -> None:
if self.gender not in ['', '']:
self._validation_errors.append('性别只能是""""')
@property @property
def is_valid(self) -> bool: def is_valid(self) -> bool:
@ -44,47 +67,66 @@ class Student:
return NotImplemented return NotImplemented
return (self.sid == other.sid and return (self.sid == other.sid and
self.name == other.name and self.name == other.name and
self.gender == other.gender and
self.height == other.height and self.height == other.height and
self.weight == other.weight and
self.birth_date == other.birth_date and self.birth_date == other.birth_date and
self.enrollment_date == other.enrollment_date and self.enrollment_date == other.enrollment_date and
self.class_name == other.class_name self.class_name == other.class_name and
) self.id_number == other.id_number and
self.phone == other.phone and
self.email == other.email and
self.major == other.major)
@classmethod @classmethod
def from_dict(cls, data: dict) -> 'Student': def from_dict(cls, data: dict) -> 'Student':
birth_date = data['birth_date'] if isinstance(data['birth_date'], date) else date.fromisoformat( birth_date = data['birth_date'] if isinstance(data['birth_date'], date) else date.fromisoformat(data['birth_date'])
data['birth_date']) enrollment_date = data['enrollment_date'] if isinstance(data['enrollment_date'], date) else date.fromisoformat(data['enrollment_date'])
enrollment_date = data['enrollment_date'] if isinstance(data['enrollment_date'], date) else date.fromisoformat(
data['enrollment_date'])
return cls( return cls(
sid=data['sid'], sid=data['sid'],
name=data['name'].strip(), name=data['name'],
gender=data['gender'],
height=data['height'], height=data['height'],
weight=data['weight'],
birth_date=birth_date, birth_date=birth_date,
enrollment_date=enrollment_date, enrollment_date=enrollment_date,
class_name=data['class_name'] class_name=data['class_name'],
id_number=data['id_number'],
phone=data['phone'],
email=data['email'],
major=data['major']
) )
def to_dict(self): def to_dict(self) -> dict:
return { return {
'sid': self.sid, 'sid': self.sid,
'name': self.name, 'name': self.name,
'height': self.height, 'gender': self.gender,
'birth_date': self.birth_date.isoformat(), 'height': self.height,
'enrollment_date': self.enrollment_date.isoformat(), 'weight': self.weight,
'class_name': self.class_name 'birth_date': self.birth_date.isoformat(),
} 'enrollment_date': self.enrollment_date.isoformat(),
'class_name': self.class_name,
'id_number': self.id_number,
'phone': self.phone,
'email': self.email,
'major': self.major
}
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
f"{self.__class__.__name__}(" f"{self.__class__.__name__}("
f"sid='{self.sid}', " f"sid='{self.sid}', "
f"name='{self.name}'," f"name='{self.name}', "
f"height={self.height}," f"gender='{self.gender}', "
f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day}), " f"height={self.height}, "
f"enrollment_date=date({self.enrollment_date.year},{self.enrollment_date.month},{self.enrollment_date.day}), " f"weight={self.weight}, "
f"class_name='{self.class_name}'" 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"id_number='{self.id_number}', "
f"phone='{self.phone}', "
f"email='{self.email}', "
f"major='{self.major}'"
f")" f")"
) )

@ -4,19 +4,18 @@ from datetime import date
from bll.student_bll import StudentBLL from bll.student_bll import StudentBLL
from dal.student_dal_json import StudentDAL from dal.student_dal_json import StudentDAL
from model.Student import Student from model.Student import Student
from dal.student_dal_json import StudentDALJSON # 确保导入
class StudentUI: class StudentUI:
def __init__(self): def __init__(self):
current_dir = os.path.dirname(os.path.abspath(__file__)) current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir) parent_dir = os.path.dirname(current_dir)
file_path = os.path.join(parent_dir, "data", "student.json") data_dir = os.path.join(parent_dir, "data")
os.makedirs(data_dir, exist_ok=True)
file_path = os.path.join(data_dir, "students.json")
dal = StudentDAL(file_path) dal = StudentDAL(file_path)
self.bll = StudentBLL(dal) self.bll = StudentBLL(dal)
self.file_path = file_path self.file_path = file_path
def display_menu(self): def display_menu(self):
"""显示主菜单""" """显示主菜单"""
os.system('cls' if os.name == 'nt' else 'clear') os.system('cls' if os.name == 'nt' else 'clear')
@ -33,6 +32,17 @@ class StudentUI:
print("0. 退出系统") print("0. 退出系统")
print("*" * 50) print("*" * 50)
def display_delete_menu(self):
"""显示删除子菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 删除菜单 ")
print("*" * 50)
print("1. 按学号删除学生信息")
print("2. 按身份证号删除学生信息")
print("0. 返回上一级")
print("*" * 50)
def display_query_menu(self): def display_query_menu(self):
"""显示查询子菜单""" """显示查询子菜单"""
os.system('cls' if os.name == 'nt' else 'clear') os.system('cls' if os.name == 'nt' else 'clear')
@ -44,6 +54,7 @@ class StudentUI:
print("3. 按学号查询") print("3. 按学号查询")
print("4. 按姓名查询") print("4. 按姓名查询")
print("5. 按班级查询") print("5. 按班级查询")
print("6. 按专业查询")
print("0. 返回上一级") print("0. 返回上一级")
print("*" * 50) print("*" * 50)
@ -58,6 +69,10 @@ class StudentUI:
print("3. 按身高范围统计") print("3. 按身高范围统计")
print("4. 按入学年份统计") print("4. 按入学年份统计")
print("5. 按年龄范围统计") print("5. 按年龄范围统计")
print("6. 统计各专业学生人数")
print("7. 计算平均身高/体重(按班级)")
print("8. 计算平均身高/体重(按专业)")
print("9. 统计性别比例")
print("0. 返回上一级") print("0. 返回上一级")
print("*" * 50) print("*" * 50)
@ -71,6 +86,8 @@ class StudentUI:
print("2. 从JSON导入数据") print("2. 从JSON导入数据")
print("3. 导出数据到CSV") print("3. 导出数据到CSV")
print("4. 从CSV导入数据") print("4. 从CSV导入数据")
print("5. 备份数据")
print("6. 恢复数据")
print("0. 返回上一级") print("0. 返回上一级")
print("*" * 50) print("*" * 50)
@ -89,62 +106,94 @@ class StudentUI:
print("*" * 50) print("*" * 50)
name = self.get_input("请输入姓名: ") name = self.get_input("请输入姓名: ")
while not (2 <= len(name) <= 20):
print("姓名长度需在2-20个字符之间")
name = self.get_input("请输入姓名: ")
gender = self.get_input("请输入性别(男/女): ")
while gender not in ['', '']:
print("性别只能是''''")
gender = self.get_input("请输入性别(男/女): ")
id_number = self.get_input("请输入身份证号: ") id_number = self.get_input("请输入身份证号: ")
while not re.match(r'^\d{17}[\dXx]$', id_number): while not re.match(r'^\d{17}[\dXx]$', id_number):
print("身份证号格式不正确,请输入 18 位身份证号。") print("身份证号格式不正确,请输入18位身份证号。")
id_number = self.get_input("请输入身份证号: ") id_number = self.get_input("请输入身份证号: ")
while True: while True:
student_id = self.get_input("请输入学号: ") student_id = self.get_input("请输入学号: ")
if re.match(r'^\d{10,15}$', student_id): # 假设学号是10 - 15位数字 if re.match(r'^\d{10,15}$', student_id): # 假设学号是10-15位数字
break if not self.bll.dal.check_student_exists(student_id):
print("学号格式不正确请输入10到15位数字") break
print("学号已存在,请重新输入。")
gender = self.get_input("请输入性别(男/女): ") else:
print("学号格式不正确请输入10到15位数字")
while True: while True:
try: try:
height = float(self.get_input("请输入身高(cm): ")) height = float(self.get_input("请输入身高(cm): "))
if height <= 0 or height > 250: if 50 <= height <= 250:
raise ValueError break
break print("身高必须在50到250cm之间请重新输入。")
except ValueError: except ValueError:
print("身高必须为0到250之间的有效数值,请重新输入") print("身高必须为有效数值,请重新输入")
while True: while True:
try: try:
weight = float(self.get_input("请输入体重(kg): ")) weight = float(self.get_input("请输入体重(kg): "))
if weight <= 0 or weight > 500: if 5 <= weight <= 300:
raise ValueError break
break print("体重必须在5到300kg之间请重新输入。")
except ValueError: except ValueError:
print("体重必须为0到500之间的有效数值,请重新输入") print("体重必须为有效数值,请重新输入")
# 从身份证号中提取出生日期 # 从身份证号中提取出生日期
birth_date_str = id_number[6:14] birth_date_str = id_number[6:14]
birth_date = date(int(birth_date_str[:4]), int(birth_date_str[4:6]), int(birth_date_str[6:])) birth_date = date(int(birth_date_str[:4]), int(birth_date_str[4:6]), int(birth_date_str[6:]))
# 修改后的日期输入验证 # 电话号码验证
while True:
phone = self.get_input("请输入电话号码: ")
if re.match(r'^1[3-9]\d{9}$', phone):
break
print("电话号码格式不正确请输入11位数字")
# 邮箱验证
while True:
email = self.get_input("请输入邮箱地址: ")
if re.match(r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$', email):
break
print("邮箱格式不正确,请重新输入")
# 入学日期验证
while True: while True:
enrollment_date = self.get_input("请输入入学日期(YYYY-MM-DD): ") enrollment_date = self.get_input("请输入入学日期(YYYY-MM-DD): ")
try: try:
enrollment_date_obj = date.fromisoformat(enrollment_date) enrollment_date_obj = date.fromisoformat(enrollment_date)
break if enrollment_date_obj >= birth_date:
break
print("入学日期不能早于出生日期,请重新输入。")
except ValueError: except ValueError:
print("日期格式错误,请使用 YYYY-MM-DD 格式例如2023-09-01") print("日期格式错误,请使用YYYY-MM-DD格式例如2023-09-01")
class_name = self.get_input("请输入班级: ") class_name = self.get_input("请输入班级: ")
major = self.get_input("请输入专业: ") major = self.get_input("请输入专业: ")
# 创建 Student 对象 # 创建Student对象
try: try:
student = Student( student = Student(
sid=student_id, sid=student_id,
name=name, name=name,
gender=gender,
height=height, height=height,
birth_date=birth_date, # 使用从身份证提取的出生日期 weight=weight,
birth_date=birth_date,
enrollment_date=enrollment_date_obj, enrollment_date=enrollment_date_obj,
class_name=class_name class_name=class_name,
id_number=id_number,
phone=phone,
email=email,
major=major
) )
if self.bll.add_student(student): if self.bll.add_student(student):
print("学生信息添加成功!") print("学生信息添加成功!")
@ -153,20 +202,333 @@ class StudentUI:
except ValueError as e: except ValueError as e:
print(f"添加学生失败: {e}") print(f"添加学生失败: {e}")
def handle_delete(self, choice):
"""处理删除操作"""
if choice == '1':
sid = self.get_input("请输入要删除的学生学号: ")
try:
if self.bll.delete_student(sid):
print("学生信息删除成功!")
else:
print("学生信息删除失败。")
except ValueError as e:
print(e)
elif choice == '2':
id_number = self.get_input("请输入要删除的学生身份证号: ")
try:
if self.bll.delete_student_by_id_number(id_number):
print("学生信息删除成功!")
else:
print("学生信息删除失败。")
except ValueError as e:
print(e)
elif choice == '0':
return
else:
print("无效的选择,请重新输入。")
def update_student(self):
"""更新学生信息"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 更新学生信息 ")
print("*" * 50)
sid = self.get_input("请输入要更新的学生学号: ")
student = self.bll.get_student_by_id(sid)
if student:
print(f"当前学生信息: {student}")
# 获取更新信息
name = self.get_input(f"请输入新的姓名(当前:{student.name}),不修改请直接回车: ")
gender = self.get_input(f"请输入新的性别(当前:{student.gender}),不修改请直接回车: ")
height = self.get_input(f"请输入新的身高(当前:{student.height}),不修改请直接回车: ")
weight = self.get_input(f"请输入新的体重(当前:{student.weight}),不修改请直接回车: ")
birth_date = self.get_input(f"请输入新的出生日期(当前:{student.birth_date}),不修改请直接回车: ")
enrollment_date = self.get_input(f"请输入新的入学日期(当前:{student.enrollment_date}),不修改请直接回车: ")
class_name = self.get_input(f"请输入新的班级(当前:{student.class_name}),不修改请直接回车: ")
major = self.get_input(f"请输入新的专业(当前:{student.major}),不修改请直接回车: ")
phone = self.get_input(f"请输入新的电话号码(当前:{student.phone}),不修改请直接回车: ")
email = self.get_input(f"请输入新的邮箱地址(当前:{student.email}),不修改请直接回车: ")
# 更新学生信息
update_fields = {}
if name:
update_fields['name'] = name
if gender:
update_fields['gender'] = gender
if height:
update_fields['height'] = int(height)
if weight:
update_fields['weight'] = float(weight)
if birth_date:
update_fields['birth_date'] = date.fromisoformat(birth_date)
if enrollment_date:
update_fields['enrollment_date'] = date.fromisoformat(enrollment_date)
if class_name:
update_fields['class_name'] = class_name
if major:
update_fields['major'] = major
if phone:
update_fields['phone'] = phone
if email:
update_fields['email'] = email
if update_fields:
try:
if self.bll.update_student_partial(sid, **update_fields):
print("学生信息更新成功!")
else:
print("学生信息更新失败。")
except ValueError as e:
print(e)
else:
print("未输入任何更新信息,操作取消。")
else:
print(f"学生 ID {sid} 不存在。")
def handle_query(self, choice):
"""处理查询操作"""
if choice == '1':
students = self.bll.get_all_students()
if not students:
print("没有找到学生信息。")
return
print("\n所有学生信息:")
print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}")
print("-" * 80)
today = date.today()
for student in students:
age = today.year - student.birth_date.year
if today < date(today.year, student.birth_date.month, student.birth_date.day):
age -= 1
print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}")
elif choice == '2':
id_number = self.get_input("请输入身份证号: ")
student = self.bll.find_student_by_id_number(id_number)
if student:
print("\n找到学生信息:")
print(f"学号: {student.sid}")
print(f"姓名: {student.name}")
print(f"性别: {student.gender}")
print(f"年龄: {date.today().year - student.birth_date.year}")
print(f"班级: {student.class_name}")
print(f"专业: {student.major}")
print(f"身高: {student.height} cm")
print(f"体重: {student.weight} kg")
print(f"身份证号: {student.id_number}")
print(f"电话号码: {student.phone}")
print(f"邮箱地址: {student.email}")
print(f"出生日期: {student.birth_date}")
print(f"入学日期: {student.enrollment_date}")
else:
print(f"未找到身份证号为 {id_number} 的学生。")
elif choice == '3':
sid = self.get_input("请输入学号: ")
student = self.bll.get_student_by_id(sid)
if student:
print("\n找到学生信息:")
print(f"学号: {student.sid}")
print(f"姓名: {student.name}")
print(f"性别: {student.gender}")
print(f"年龄: {date.today().year - student.birth_date.year}")
print(f"班级: {student.class_name}")
print(f"专业: {student.major}")
print(f"身高: {student.height} cm")
print(f"体重: {student.weight} kg")
print(f"身份证号: {student.id_number}")
print(f"电话号码: {student.phone}")
print(f"邮箱地址: {student.email}")
print(f"出生日期: {student.birth_date}")
print(f"入学日期: {student.enrollment_date}")
else:
print(f"未找到学号为 {sid} 的学生。")
elif choice == '4':
name = self.get_input("请输入姓名: ")
students = self.bll.get_students_by_name(name)
if not students:
print(f"未找到姓名包含 '{name}' 的学生。")
return
print(f"\n找到 {len(students)} 名学生:")
print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}")
print("-" * 80)
today = date.today()
for student in students:
age = today.year - student.birth_date.year
if today < date(today.year, student.birth_date.month, student.birth_date.day):
age -= 1
print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}")
elif choice == '5':
class_name = self.get_input("请输入班级: ")
students = self.bll.get_students_by_class(class_name)
if not students:
print(f"未找到班级包含 '{class_name}' 的学生。")
return
print(f"\n找到 {len(students)} 名学生:")
print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}")
print("-" * 80)
today = date.today()
for student in students:
age = today.year - student.birth_date.year
if today < date(today.year, student.birth_date.month, student.birth_date.day):
age -= 1
print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}")
elif choice == '6':
major = self.get_input("请输入专业: ")
students = self.bll.get_students_by_major(major)
if not students:
print(f"未找到专业包含 '{major}' 的学生。")
return
print(f"\n找到 {len(students)} 名学生:")
print(f"{'学号':<15}{'姓名':<10}{'性别':<5}{'年龄':<5}{'班级':<15}{'专业':<15}{'身高(cm)':<10}{'体重(kg)':<10}")
print("-" * 80)
today = date.today()
for student in students:
age = today.year - student.birth_date.year
if today < date(today.year, student.birth_date.month, student.birth_date.day):
age -= 1
print(f"{student.sid:<15}{student.name:<10}{student.gender:<5}{age:<5}{student.class_name:<15}{student.major:<15}{student.height:<10}{student.weight:<10}")
elif choice == '0':
return
else:
print("无效的选择,请重新输入。")
def handle_stats(self, choice):
"""处理统计分析操作"""
if choice == '1':
count = self.bll.dal.get_student_count()
print(f"学生总数为: {count}")
elif choice == '2':
average_height = self.bll.dal.get_average_height()
print(f"学生平均身高为: {average_height:.2f} cm")
elif choice == '3':
min_height = int(self.get_input("请输入最小身高: "))
max_height = int(self.get_input("请输入最大身高: "))
count = self.bll.dal.count_students_by_height_range(min_height, max_height)
print(f"身高在 {min_height} - {max_height} cm 之间的学生数量为: {count}")
elif choice == '4':
year = int(self.get_input("请输入入学年份: "))
count = self.bll.dal.count_students_by_enrollment_year(year)
print(f"{year} 年入学的学生数量为: {count}")
elif choice == '5':
min_age = int(self.get_input("请输入最小年龄: "))
max_age = int(self.get_input("请输入最大年龄: "))
count = self.bll.dal.count_students_by_age_range(min_age, max_age)
print(f"年龄在 {min_age} - {max_age} 岁之间的学生数量为: {count}")
elif choice == '6':
major_counts = self.bll.count_students_by_major()
print("\n各专业学生人数统计:")
print(f"{'专业':<20}{'人数'}")
print("-" * 30)
for major, count in major_counts.items():
print(f"{major:<20}{count}")
elif choice == '7':
print("\n按班级统计平均身高/体重:")
class_stats = self.bll.calculate_average_height_weight('class_name')
print(f"{'班级':<20}{'平均身高(cm)':<15}{'平均体重(kg)'}")
print("-" * 45)
for class_name, stats in class_stats.items():
print(f"{class_name:<20}{stats['average_height']:<15.2f}{stats['average_weight']:.2f}")
elif choice == '8':
print("\n按专业统计平均身高/体重:")
major_stats = self.bll.calculate_average_height_weight('major')
print(f"{'专业':<20}{'平均身高(cm)':<15}{'平均体重(kg)'}")
print("-" * 45)
for major, stats in major_stats.items():
print(f"{major:<20}{stats['average_height']:<15.2f}{stats['average_weight']:.2f}")
elif choice == '9':
gender_ratio = self.bll.calculate_gender_ratio()
print("\n性别比例统计:")
print(f"男生比例: {gender_ratio['male_ratio']:.2%}")
print(f"女生比例: {gender_ratio['female_ratio']:.2%}")
elif choice == '0':
return
else:
print("无效的选择,请重新输入。")
def handle_import_export(self, choice): def handle_import_export(self, choice):
"""处理数据导入导出操作"""
if choice == '1': if choice == '1':
file_path = self.get_input("请输入要导出的 JSON 文件路径: ") file_path = self.get_input("请输入要导出的JSON文件路径: ")
self.bll.dal.export_to_json(file_path) self.bll.dal.export_to_json(file_path)
print("数据导出到 JSON 成功!") print("数据导出到JSON成功")
elif choice == '2': elif choice == '2':
file_path = self.get_input("请输入要导入的 JSON 文件路径: ") file_path = self.get_input("请输入要导入的JSON文件路径: ")
self.bll.dal.import_from_json(file_path) self.bll.dal.import_from_json(file_path)
print("数据从 JSON 导入成功!") print("数据从JSON导入成功")
elif choice == '3': elif choice == '3':
file_path = self.get_input("请输入要导出的 CSV 文件路径: ") file_path = self.get_input("请输入要导出的CSV文件路径: ")
self.bll.dal.export_to_csv(file_path) self.bll.dal.export_to_csv(file_path)
print("数据导出到 CSV 成功!") print("数据导出到CSV成功")
elif choice == '4': elif choice == '4':
file_path = self.get_input("请输入要导入的 CSV 文件路径: ") file_path = self.get_input("请输入要导入的CSV文件路径: ")
self.bll.dal.import_from_csv(file_path) self.bll.dal.import_from_csv(file_path)
print("数据从 CSV 导入成功!") print("数据从CSV导入成功")
elif choice == '5':
backup_path = self.get_input("请输入备份文件路径: ")
self.bll.dal.backup_data(backup_path)
elif choice == '6':
backup_path = self.get_input("请输入恢复文件路径: ")
self.bll.dal.restore_data(backup_path)
elif choice == '0':
return
else:
print("无效的选择,请重新输入。")
def clear_all_students(self):
"""清空所有学生信息"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 警告:清空所有学生信息 ")
print("*" * 50)
confirm = self.get_input("此操作将删除所有学生信息,且无法恢复。确定要继续吗?(y/n): ")
if confirm.lower() == 'y':
try:
self.bll.dal.clear_all_students()
print("所有学生信息已清空!")
except Exception as e:
print(f"清空学生信息失败: {e}")
def run(self):
"""运行主程序"""
while True:
self.display_menu()
choice = self.get_input("请输入你的选择: ")
if choice == '1':
self.add_student()
elif choice == '2':
while True:
self.display_delete_menu()
delete_choice = self.get_input("请输入你的删除选择: ")
if delete_choice == '0':
break
self.handle_delete(delete_choice)
elif choice == '3':
self.update_student()
elif choice == '4':
while True:
self.display_query_menu()
query_choice = self.get_input("请输入你的查询选择: ")
if query_choice == '0':
break
self.handle_query(query_choice)
elif choice == '5':
while True:
self.display_stats_menu()
stats_choice = self.get_input("请输入你的统计选择: ")
if stats_choice == '0':
break
self.handle_stats(stats_choice)
elif choice == '6':
while True:
self.display_import_export_menu()
import_export_choice = self.get_input("请输入你的导入导出选择: ")
if import_export_choice == '0':
break
self.handle_import_export(import_export_choice)
elif choice == '7':
self.clear_all_students()
elif choice == '0':
print("感谢使用学生信息管理系统,再见!")
break
else:
print("无效的选择,请重新输入。")
input("\n按Enter键继续...")
Loading…
Cancel
Save