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.

556 lines
22 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, sys
import json
import csv
from datetime import date
from astropy.version import major
from pyasn1_modules.rfc2985 import gender
cur_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(cur_dir)
sys.path.append(project_dir)
from model.student import Student
from dal.student_dal_json import StudentDAL
from bll.studentbll import StudentBLL
class StudentTUI:
def __init__(self, bll: StudentBLL):
self.bll = bll
self.running = True
def display_menu(self):
print("\n ===== 学生信息管理系统 =====")
print("1. 添加学生信息")
print("2. 删除学生信息")
print("3. 更新学生信息")
print("4. 查询学生信息")
print("5. 统计分析")
print("6. 数据导入导出")
print("7. 清空所有学生信息")
print("0. 退出系统")
print("==========================")
def display_query_menu(self):
print("\n ===== 查询学生信息 =====")
print("1. 查询所有学生")
print("2. 按学生ID查询")
print("3. 按姓名查询")
print("4. 按班级查询")
print("5. 按身份证号查询") # 新增按身份证查询选项
print("6. 返回上一级")
print("==========================")
def display_stats_menu(self):
print("\n ===== 统计分析 =====")
print("1. 学生总数")
print("2. 平均身高")
print("3. 按身高范围统计")
print("4. 按入学年份统计")
print("5. 按年龄范围统计")
print("7性别比例")
print("6. 返回上一级")
print("==========================")
def display_import_export_menu(self):
"""更新导入导出菜单添加CSV选项"""
print("\n ===== 数据导入导出 ===== ")
print("1. 导出数据到JSON")
print("2. 从JSON导入数据")
print("3. 导出数据到CSV")
print("4. 从CSV导入数据")
print("5. 返回上一级")
print("==========================")
def handle_main_menu_choice(self, choice):
"""处理主菜单选择"""
if choice == '1':
self.add_student()
elif choice == '2':
self.delete_student()
elif choice == '3':
self.update_student()
elif choice == '4':
self.run_query_menu()
elif choice == '5':
self.run_stats_menu()
elif choice == '6':
self.run_import_export_menu()
elif choice == '7':
self.clear_all_students()
elif choice == '0':
self.running = False
print("感谢使用学生信息管理系统,再见!")
else:
print("无效选择,请重新输入")
def run(self):
"""运行主程序循环"""
while self.running:
self.display_menu()
choice = input("请输入你的选择: ")
self.handle_main_menu_choice(choice)
def run_query_menu(self):
"""运行查询子菜单"""
while True:
self.display_query_menu()
choice = input("请输入你的选择: ")
if choice == '6':
break
self.handle_query_menu_choice(choice)
def handle_query_menu_choice(self, choice):
"""处理查询子菜单选择"""
if choice == '1':
self.query_all_students()
elif choice == '2':
self.query_by_id()
elif choice == '3':
self.query_by_name()
elif choice == '4':
self.query_by_class()
elif choice == '5': # 新增按身份证查询处理
self.query_by_id_card()
else:
print("无效选择,请重新输入")
def run_stats_menu(self):
"""运行统计分析子菜单"""
while True:
self.display_stats_menu()
choice = input("请输入你的选择: ")
if choice == '6':
break
self.handle_stats_menu_choice(choice)
def handle_stats_menu_choice(self, choice):
"""处理统计分析子菜单选择"""
if choice == '1':
self.get_total_students()
elif choice == '2':
self.get_average_height()
elif choice == '3':
self.stat_by_height_range()
elif choice == '4':
self.stat_by_enrollment_date()
elif choice == '5':
self.stat_by_age_range()
elif choice == '7':
self.get_average_gender()
else:
print("无效选择,请重新输入")
def run_import_export_menu(self):
"""运行导入导出子菜单"""
while True:
self.display_import_export_menu()
choice = input("请输入你的选择: ")
if choice == '5':
break
self.handle_import_export_menu_choice(choice)
def handle_import_export_menu_choice(self, choice):
"""处理导入导出菜单选择添加CSV处理"""
if choice == '1':
self.export_to_json()
elif choice == '2':
self.import_from_json()
elif choice == '3':
self.export_to_csv()
elif choice == '4':
self.import_from_csv()
else:
print("无效选择,请重新输入")
def _get_input(self, prompt, min_value=None, max_value=None, type_cast=int):
"""获取用户输入并进行基本验证"""
while True:
try:
value = input(prompt).strip()
if not value and min_value is not None:
raise ValueError("输入不能为空")
value = type_cast(value) if value else None
if min_value is not None and value < min_value:
raise ValueError(f"输入必须大于等于 {min_value}")
if max_value is not None and value > max_value:
raise ValueError(f"输入必须小于等于 {max_value}")
return value
except ValueError as e:
print(f"输入错误: {e}")
except Exception as e:
print(f"格式错误: {e}")
def add_student(self):
"""添加学生信息,包含身份证号输入"""
print("\n===== 添加学生信息 =====")
try:
stu_id = input("请输入学生ID: ").strip()
if not stu_id:
raise ValueError("学生ID不能为空")
name = input("请输入姓名: ").strip()
if not name:
raise ValueError("姓名不能为空")
height = self._get_input("请输入身高(cm): ", min_value=50, max_value=250)
birth_date_str = input("请输入出生日期格式YYYY-MM-DD: ").strip()
if not birth_date_str:
raise ValueError("出生日期不能为空")
try:
birth_date = date.fromisoformat(birth_date_str)
except ValueError:
raise ValueError("出生日期格式错误请使用YYYY-MM-DD格式如2000-01-01")
today = date.today()
if birth_date > today:
raise ValueError("出生日期不能是未来日期")
enrollment_year = self._get_input("请输入入学年份: ", min_value=1900, max_value=date.today().year)
enrollment_date = f"{enrollment_year}-09-01"
class_name = input("请输入班级: ").strip()
if not class_name:
raise ValueError("班级不能为空")
id_card = input("请输入身份证号(可选): ").strip() # 新增身份证号输入
gender = input("请输入性别(可选):").strip()
major = input("请输入专业(可选):").strip()
# 创建Student对象
student = Student(
stu_id=stu_id,
name=name,
height=int(height),
birth_date=birth_date,
enrollment_date=enrollment_date,
class_name=class_name,
id_card=id_card if id_card else None, # 处理空输入
gender=gender if gender else None,
major=major if major else None
)
# 调用业务逻辑层方法
self.bll.add_student(student)
print("学生信息添加成功!")
except ValueError as ve:
print(f"输入错误: {ve}")
except Exception as e:
print(f"添加失败: {str(e)}")
def delete_student(self):
"""删除学生信息"""
print("\n===== 删除学生信息 =====")
stu_id = input("请输入要删除的学生ID: ")
try:
result = self.bll.delete_student(stu_id)
if result:
print("学生信息删除成功!")
else:
print("学生信息删除失败可能ID不存在")
except ValueError as e:
print(f"操作失败: {e}")
def update_student(self):
"""更新学生信息,包含身份证号更新"""
print("\n===== 更新学生信息 =====")
stu_id = input("请输入要更新的学生ID: ")
# 先检查学生是否存在
student = self.bll.get_student_by_id(stu_id)
if not student:
print("学生不存在!")
return
print(f"当前学生信息: {student}")
print("(直接回车保持原值不变)")
name = input(f"请输入新姓名[{student.name}]: ") or student.name
height = self._get_input(f"请输入新身高[{student.height}]: ", min_value=50, max_value=250,
type_cast=lambda x: x or student.height)
class_name = input(f"请输入新班级[{student.class_name}]: ") or student.class_name
enrollment_date = self._get_input(f"请输入新入学年份[{student.enrollment_date.year}]: ",
min_value=1900,
max_value=date.today().year,
type_cast=lambda x: x or student.enrollment_date.year)
id_card = input(f"请输入新身份证号[{student.id_card or '未填写'}]: ") or student.id_card or None # 新增身份证更新
gender = input(f"请输入新性别[{student.gender or '未填写'}]") or student.id_card or None
major = input(f"请输入新专业[{student.major or '未填写'}]") or student.id_card or None
# 创建更新后的Student对象
updated_student = Student(
stu_id=stu_id,
name=name,
height=int(height),
birth_date=student.birth_date,
enrollment_date=f"{enrollment_date}-09-01",
class_name=class_name,
id_card=id_card if id_card else None,
gender=gender if gender else None,
major=major if major else None
)
try:
result = self.bll.update_student(stu_id, updated_student)
if result:
print("学生信息更新成功!")
else:
print("学生信息更新失败!")
except ValueError as e:
print(f"操作失败: {e}")
def query_all_students(self):
"""查询所有学生,显示身份证号"""
print("\n===== 所有学生信息 =====")
students = self.bll.get_all_students()
if not students:
print("暂无学生信息!")
return
for student in students:
age = date.today().year - student.birth_date.year - (
(date.today().month, date.today().day) < (student.birth_date.month, student.birth_date.day))
# 显示身份证号(实际应用中可考虑脱敏处理)
id_card_display = student.id_card or "未填写"
print(f"ID: {student.stu_id}, 姓名: {student.name}, 年龄: {age}, "
f"身高: {student.height}cm, 班级: {student.class_name}, "
f"入学年份: {student.enrollment_date.year}, 身份证: {id_card_display},"
f"性别:{student.gender},专业:{student.major}"
)
def query_by_id(self):
"""按ID查询学生显示身份证号"""
print("\n===== 按ID查询学生 =====")
stu_id = input("请输入学生ID: ")
student = self.bll.get_student_by_id(stu_id)
if student:
age = date.today().year - student.birth_date.year - (
(date.today().month, date.today().day) < (student.birth_date.month, student.birth_date.day))
id_card_display = student.id_card or "未填写"
print(f"ID: {student.stu_id}, 姓名: {student.name}, 年龄: {age}, "
f"身高: {student.height}cm, 班级: {student.class_name}, "
f"入学年份: {student.enrollment_date.year}, 身份证: {id_card_display},"
f"性别:{student.gender},专业:{student.major}"
)
else:
print("未找到该学生!")
def query_by_name(self):
"""按姓名查询学生,显示身份证号"""
print("\n===== 按姓名查询学生 =====")
name = input("请输入姓名: ")
students = self.bll.get_students_by_name(name)
if not students:
print("未找到该学生!")
return
for student in students:
age = date.today().year - student.birth_date.year - (
(date.today().month, date.today().day) < (student.birth_date.month, student.birth_date.day))
id_card_display = student.id_card or "未填写"
print(f"ID: {student.stu_id}, 姓名: {student.name}, 年龄: {age}, "
f"身高: {student.height}cm, 班级: {student.class_name}, "
f"入学年份: {student.enrollment_date.year}, 身份证: {id_card_display},"
f"性别:{student.gender},专业:{student.major}"
)
def query_by_class(self):
"""按班级查询学生,显示身份证号"""
print("\n===== 按班级查询学生 =====")
class_name = input("请输入班级: ")
students = self.bll.get_students_by_class(class_name)
if not students:
print("未找到该班级的学生!")
return
for student in students:
age = date.today().year - student.birth_date.year - (
(date.today().month, date.today().day) < (student.birth_date.month, student.birth_date.day))
id_card_display = student.id_card or "未填写"
print(f"ID: {student.stu_id}, 姓名: {student.name}, 年龄: {age}, "
f"身高: {student.height}cm, 班级: {student.class_name}, "
f"入学年份: {student.enrollment_date.year}, 身份证: {id_card_display},"
f"性别:{student.gender},专业:{student.major}"
)
def query_by_id_card(self):
"""新增:按身份证号查询学生"""
print("\n===== 按身份证号查询学生 =====")
id_card = input("请输入身份证号: ")
student = self.bll.get_student_by_id_card(id_card)
if student:
age = date.today().year - student.birth_date.year - (
(date.today().month, date.today().day) < (student.birth_date.month, student.birth_date.day))
print(f"ID: {student.stu_id}, 姓名: {student.name}, 年龄: {age}, "
f"身高: {student.height}cm, 班级: {student.class_name}, "
f"入学年份: {student.enrollment_date.year},"
f"性别:{student.gender},专业:{student.major}"
)
else:
print("未找到该学生!")
def get_total_students(self):
"""获取学生总数"""
total = self.bll.get_student_count()
print(f"\n学生总数: {total}")
def get_average_height(self):
"""获取平均身高"""
average_height = self.bll.get_avg_height()
if average_height > 0:
print(f"\n平均身高: {average_height:.2f}cm")
else:
print("\n暂无学生数据!")
def get_average_gender(self):
"""统计性别比例"""
average_gender = self.bll.get_avg_gender()
print(f"\n当前性别比例: {average_gender}")
def stat_by_height_range(self):
"""按身高范围统计"""
print("\n===== 按身高范围统计 =====")
min_height = self._get_input("请输入最小身高(cm): ", min_value=50)
max_height = self._get_input("请输入最大身高(cm): ", min_value=min_height, max_value=250)
students = self.bll.get_students_by_height(min_height, max_height)
print(f"身高在 {min_height}-{max_height}cm 之间的学生有 {len(students)}")
def stat_by_enrollment_date(self):
"""按入学年份统计"""
print("\n===== 按入学年份统计 =====")
year = self._get_input("请输入入学年份: ", min_value=1900, max_value=date.today().year)
students = self.bll.get_students_by_enrollment_year(year)
print(f"{year}年入学的学生有 {len(students)}")
def stat_by_age_range(self):
"""按年龄范围统计"""
print("\n===== 按年龄范围统计 =====")
min_age = self._get_input("请输入最小年龄: ", min_value=0)
max_age = self._get_input("请输入最大年龄: ", min_value=min_age, max_value=150)
students = self.bll.get_students_by_age(min_age, max_age)
print(f"年龄在 {min_age}-{max_age} 岁之间的学生有 {len(students)}")
def export_to_csv(self):
"""导出数据到CSV文件包含身份证号"""
print("\n===== 导出数据到CSV =====")
file_path = input("请输入保存文件名(默认: students.csv): ") or "students.csv"
try:
students = self.bll.get_all_students()
if not students:
print("没有学生数据可导出")
return
with open(file_path, 'w', encoding='utf-8', newline='') as f:
writer = csv.DictWriter(f, fieldnames=[
'stu_id', 'name', 'height', 'birth_date', 'enrollment_date', 'class_name', 'id_card'
])
writer.writeheader()
for student in students:
writer.writerow({
'stu_id': student.stu_id,
'name': student.name,
'height': student.height,
'birth_date': student.birth_date.isoformat(),
'enrollment_date': student.enrollment_date.isoformat(),
'class_name': student.class_name,
'id_card': student.id_card or ''
})
print(f"数据已成功导出到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
def import_from_csv(self):
"""从CSV文件导入数据包含身份证号"""
print("\n===== 从CSV导入数据 =====")
file_path = input("请输入文件名(默认: students.csv): ") or "students.csv"
try:
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
student_dicts = list(reader)
imported_count = 0
for student_dict in student_dicts:
try:
# 解析日期字段
birth_date = date.fromisoformat(student_dict.get('birth_date', '2000-01-01'))
enrollment_date = date.fromisoformat(student_dict.get('enrollment_date', '2020-09-01'))
id_card = student_dict.get('id_card', None) or None # 处理空值
student = Student(
stu_id=student_dict['stu_id'],
name=student_dict['name'],
height=int(student_dict['height']),
birth_date=birth_date,
enrollment_date=enrollment_date,
class_name=student_dict['class_name'],
id_card=id_card if id_card else None,
gender=gender,
major=major
)
if self.bll.add_student(student):
imported_count += 1
except Exception as e:
print(f"导入学生 {student_dict.get('stu_id', '未知ID')} 失败: {e}")
print(f"成功从 {file_path} 导入 {imported_count} 条学生数据")
except FileNotFoundError:
print(f"文件不存在: {file_path}")
except Exception as e:
print(f"导入失败: {e}")
def export_to_json(self):
"""导出数据到JSON包含身份证号"""
print("\n===== 导出数据到JSON =====")
file_path = input("请输入保存文件名(默认: students.json): ") or "students.json"
try:
students = self.bll.get_all_students()
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)
print(f"数据已成功导出到 {file_path}")
except Exception as e:
print(f"导出失败: {e}")
def import_from_json(self):
"""从JSON导入数据包含身份证号"""
print("\n===== 从JSON导入数据 =====")
file_path = input("请输入文件名(默认: students.json): ") or "students.json"
try:
with open(file_path, 'r', encoding='utf-8') as f:
student_dicts = json.load(f)
# 导入新数据
imported_count = 0
for student_dict in student_dicts:
try:
student = Student.from_dict(student_dict)
if self.bll.add_student(student):
imported_count += 1
except Exception as e:
print(f"导入学生 {student_dict.get('stu_id', '未知ID')} 失败: {e}")
print(f"成功从 {file_path} 导入 {imported_count} 条学生数据")
except FileNotFoundError:
print(f"文件不存在: {file_path}")
except Exception as e:
print(f"导入失败: {e}")
def clear_all_students(self):
"""清空所有学生信息"""
confirm = input("确定要清空所有学生信息吗?(y/n): ")
if confirm.lower() == 'y':
self.bll.clear_all_students()
print("所有学生信息已清空!")
else:
print("操作已取消")