You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

534 lines
24 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import os
import re
from datetime import date
from bll.student_bll import StudentBLL
from dal.student_dal_json import StudentDAL
from model.Student import Student
class StudentUI:
def __init__(self):
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
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)
self.bll = StudentBLL(dal)
self.file_path = file_path
def display_menu(self):
"""显示主菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 主菜单 ")
print("*" * 50)
print("1. 添加学生信息")
print("2. 删除学生信息")
print("3. 更新学生信息")
print("4. 查询学生信息")
print("5. 统计分析")
print("6. 数据导入导出")
print("7. 清空所有学生信息")
print("0. 退出系统")
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):
"""显示查询子菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 查询菜单 ")
print("*" * 50)
print("1. 查询所有学生")
print("2. 按身份证号查询")
print("3. 按学号查询")
print("4. 按姓名查询")
print("5. 按班级查询")
print("6. 按专业查询")
print("0. 返回上一级")
print("*" * 50)
def display_stats_menu(self):
"""显示统计分析子菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 统计分析菜单 ")
print("*" * 50)
print("1. 学生总数")
print("2. 平均身高")
print("3. 按身高范围统计")
print("4. 按入学年份统计")
print("5. 按年龄范围统计")
print("6. 统计各专业学生人数")
print("7. 计算平均身高/体重(按班级)")
print("8. 计算平均身高/体重(按专业)")
print("9. 统计性别比例")
print("0. 返回上一级")
print("*" * 50)
def display_import_export_menu(self):
"""显示数据导入导出子菜单"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 学生信息管理系统 - 数据导入导出菜单 ")
print("*" * 50)
print("1. 导出数据到JSON")
print("2. 从JSON导入数据")
print("3. 导出数据到CSV")
print("4. 从CSV导入数据")
print("5. 备份数据")
print("6. 恢复数据")
print("0. 返回上一级")
print("*" * 50)
def get_input(self, prompt: str) -> str:
"""获取用户输入,添加异常处理"""
try:
return input(prompt).strip()
except EOFError:
return "0" # 按Ctrl+D视为退出
def add_student(self):
"""添加学生信息"""
os.system('cls' if os.name == 'nt' else 'clear')
print("*" * 50)
print(" 添加学生信息 ")
print("*" * 50)
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("请输入身份证号: ")
while not re.match(r'^\d{17}[\dXx]$', id_number):
print("身份证号格式不正确请输入18位身份证号。")
id_number = self.get_input("请输入身份证号: ")
while True:
student_id = self.get_input("请输入学号: ")
if re.match(r'^\d{10,15}$', student_id): # 假设学号是10-15位数字
if not self.bll.dal.check_student_exists(student_id):
break
print("学号已存在,请重新输入。")
else:
print("学号格式不正确请输入10到15位数字")
while True:
try:
height = float(self.get_input("请输入身高(cm): "))
if 50 <= height <= 250:
break
print("身高必须在50到250cm之间请重新输入。")
except ValueError:
print("身高必须为有效的数值,请重新输入。")
while True:
try:
weight = float(self.get_input("请输入体重(kg): "))
if 5 <= weight <= 300:
break
print("体重必须在5到300kg之间请重新输入。")
except ValueError:
print("体重必须为有效的数值,请重新输入。")
# 从身份证号中提取出生日期
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:]))
# 电话号码验证
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:
enrollment_date = self.get_input("请输入入学日期(YYYY-MM-DD): ")
try:
enrollment_date_obj = date.fromisoformat(enrollment_date)
if enrollment_date_obj >= birth_date:
break
print("入学日期不能早于出生日期,请重新输入。")
except ValueError:
print("日期格式错误请使用YYYY-MM-DD格式例如2023-09-01")
class_name = self.get_input("请输入班级: ")
major = self.get_input("请输入专业: ")
# 创建Student对象
try:
student = Student(
sid=student_id,
name=name,
gender=gender,
height=height,
weight=weight,
birth_date=birth_date,
enrollment_date=enrollment_date_obj,
class_name=class_name,
id_number=id_number,
phone=phone,
email=email,
major=major
)
if self.bll.add_student(student):
print("学生信息添加成功!")
else:
print("学生信息添加失败。")
except ValueError as 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):
"""处理数据导入导出操作"""
if choice == '1':
file_path = self.get_input("请输入要导出的JSON文件路径: ")
self.bll.dal.export_to_json(file_path)
print("数据导出到JSON成功")
elif choice == '2':
file_path = self.get_input("请输入要导入的JSON文件路径: ")
self.bll.dal.import_from_json(file_path)
print("数据从JSON导入成功")
elif choice == '3':
file_path = self.get_input("请输入要导出的CSV文件路径: ")
self.bll.dal.export_to_csv(file_path)
print("数据导出到CSV成功")
elif choice == '4':
file_path = self.get_input("请输入要导入的CSV文件路径: ")
self.bll.dal.import_from_csv(file_path)
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键继续...")