|
|
import json
|
|
|
import csv
|
|
|
from datetime import date
|
|
|
from typing import List, Tuple
|
|
|
from BLL.add_student import StudentBLL
|
|
|
from model.student import Student
|
|
|
from DAL.clear_all_student import CsvStudentDAL
|
|
|
from DAL.clear_all_student import JsonStudentDAL
|
|
|
|
|
|
|
|
|
def display_menu():
|
|
|
print("\n=== 学生信息管理系统 ====")
|
|
|
print("1. 添加学生信息")
|
|
|
print("2. 删除学生信息")
|
|
|
print("3. 更新学生信息")
|
|
|
print("4. 查询学生信息")
|
|
|
print("5. 统计分析")
|
|
|
print("6. 数据导入导出")
|
|
|
print("7. 清空所有学生信息")
|
|
|
print("8. 退出系统")
|
|
|
print("---")
|
|
|
|
|
|
|
|
|
def display_query_menu():
|
|
|
print("\n==== 查询学生信息 ====")
|
|
|
print("1. 查询所有学生")
|
|
|
print("2. 按身份证号查询")
|
|
|
print("3. 按学号查询")
|
|
|
print("4. 按姓名查询")
|
|
|
print("5. 按班级查询")
|
|
|
print("6. 按专业查询")
|
|
|
print("7. 返回上一级")
|
|
|
print("=======================")
|
|
|
|
|
|
|
|
|
def display_stats_menu():
|
|
|
print("\n==== 统计分析 ====")
|
|
|
print("1. 学生总数")
|
|
|
print("2. 平均身高")
|
|
|
print("3. 按身高范围统计")
|
|
|
print("4. 按入学年份统计")
|
|
|
print("5. 按年龄范围统计")
|
|
|
print("6. 按专业分组统计")
|
|
|
print("7. 性别比例")
|
|
|
print("8. 返回上一级")
|
|
|
print("=======================")
|
|
|
|
|
|
|
|
|
def display_import_export_menu():
|
|
|
print("\n==== 数据导入导出 ====")
|
|
|
print("1. 导出数据到JSON")
|
|
|
print("2. 从JSON导入数据")
|
|
|
print("3. 导出数据到CSV")
|
|
|
print("4. 从CSV导入数据")
|
|
|
print("5. 返回上一级")
|
|
|
print("=======================")
|
|
|
|
|
|
|
|
|
def display_students(students: List[Student]):
|
|
|
if not students:
|
|
|
print("没有找到学生信息")
|
|
|
return
|
|
|
|
|
|
print("\n学生信息列表:")
|
|
|
print("-" * 120)
|
|
|
print(
|
|
|
f"{'姓名':<10}{'学号':<15}{'身份证号':<20}{'性别':<6}{'年龄':<6}{'身高':<6}{'体重':<6}{'班级':<10}{'专业':<15}{'入学日期':<12}")
|
|
|
print("-" * 120)
|
|
|
|
|
|
for student in students:
|
|
|
gender = '男' if student.gender else '女' if student.gender is not None else '未知'
|
|
|
print(
|
|
|
f"{student.name:<10}{student.stu_id:<15}{student.id_card:<20}"
|
|
|
f"{gender:<6}{student.age or '-':<6}{student.height or '-':<6}"
|
|
|
f"{student.weight or '-':<6}{student.class_name or '-':<10}"
|
|
|
f"{student.major or '-':<15}{student.enrollment_date or '-'}"
|
|
|
)
|
|
|
|
|
|
print("-" * 120)
|
|
|
print(f"共找到 {len(students)} 名学生")
|
|
|
|
|
|
|
|
|
class StudentTUI:
|
|
|
def __init__(self, bll: StudentBLL):
|
|
|
self.bll = bll
|
|
|
# 省份代码映射表
|
|
|
self.province_codes = {
|
|
|
'11': '北京市', '12': '天津市', '13': '河北省', '14': '山西省', '15': '内蒙古自治区',
|
|
|
'21': '辽宁省', '22': '吉林省', '23': '黑龙江省', '31': '上海市', '32': '江苏省',
|
|
|
'33': '浙江省', '34': '安徽省', '35': '福建省', '36': '江西省', '37': '山东省',
|
|
|
'41': '河南省', '42': '湖北省', '43': '湖南省', '44': '广东省', '45': '广西壮族自治区',
|
|
|
'46': '海南省', '50': '重庆市', '51': '四川省', '52': '贵州省', '53': '云南省',
|
|
|
'54': '西藏自治区', '61': '陕西省', '62': '甘肃省', '63': '青海省', '64': '宁夏回族自治区',
|
|
|
'65': '新疆维吾尔自治区'
|
|
|
}
|
|
|
|
|
|
def validate_id_card(self, id_card: str) -> bool:
|
|
|
"""验证身份证号是否合法"""
|
|
|
if len(id_card) != 18:
|
|
|
return False
|
|
|
|
|
|
# 校验前17位是否为数字
|
|
|
if not id_card[:-1].isdigit():
|
|
|
return False
|
|
|
|
|
|
# 校验最后一位是否为数字或X
|
|
|
if not (id_card[-1].isdigit() or id_card[-1].upper() == 'X'):
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
|
|
|
def get_gender_from_id_card(self, id_card: str) -> bool:
|
|
|
"""从身份证号获取性别(男True/女False)"""
|
|
|
if len(id_card) != 18:
|
|
|
raise ValueError("身份证号长度不正确")
|
|
|
|
|
|
gender_code = int(id_card[16])
|
|
|
return gender_code % 2 == 1 # 奇数表示男性,偶数表示女性
|
|
|
|
|
|
def get_hometown_from_id_card(self, id_card: str) -> str:
|
|
|
"""从身份证号获取籍贯"""
|
|
|
if len(id_card) != 18:
|
|
|
raise ValueError("身份证号长度不正确")
|
|
|
|
|
|
province_code = id_card[:2]
|
|
|
return self.province_codes.get(province_code, "未知地区")
|
|
|
|
|
|
def run(self):
|
|
|
while True:
|
|
|
display_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.stats_students()
|
|
|
elif choice == '6':
|
|
|
self.import_export_data()
|
|
|
elif choice == '7':
|
|
|
self.clear_all_students()
|
|
|
elif choice == '8':
|
|
|
print("感谢使用学生信息管理系统,再见!")
|
|
|
break
|
|
|
else:
|
|
|
print("无效的选择,请重新输入!")
|
|
|
|
|
|
def add_student(self):
|
|
|
print("\n==== 添加学生信息 ====")
|
|
|
try:
|
|
|
name = input("姓名: ").strip()
|
|
|
id_card = input("身份证号: ").strip()
|
|
|
|
|
|
# 验证身份证号
|
|
|
if not self.validate_id_card(id_card):
|
|
|
print("身份证号不合法!")
|
|
|
return
|
|
|
|
|
|
# 自动获取性别
|
|
|
gender = self.get_gender_from_id_card(id_card)
|
|
|
print(f"自动识别性别: {'男' if gender else '女'}")
|
|
|
|
|
|
# 自动获取籍贯
|
|
|
hometown = self.get_hometown_from_id_card(id_card)
|
|
|
print(f"籍贯: {hometown}")
|
|
|
|
|
|
stu_id = input("学号: ").strip()
|
|
|
height = int(input("身高(cm): ").strip())
|
|
|
weight = float(input("体重(kg): ").strip())
|
|
|
enrollment_date = input("入学日期(YYYY-MM-DD): ").strip()
|
|
|
class_name = input("班级名称: ").strip()
|
|
|
major = input("专业: ").strip()
|
|
|
email = input("邮箱(可选): ").strip() or None
|
|
|
phone = input("手机号(可选): ").strip() or None
|
|
|
|
|
|
student = 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,
|
|
|
email=email,
|
|
|
phone=phone
|
|
|
)
|
|
|
|
|
|
if self.bll.add_student(student):
|
|
|
print("学生信息添加成功!")
|
|
|
else:
|
|
|
print("添加失败,可能学号或身份证号已存在")
|
|
|
except ValueError as e:
|
|
|
print(f"输入错误: {e}")
|
|
|
except Exception as e:
|
|
|
print(f"添加学生信息时出错: {e}")
|
|
|
|
|
|
def delete_student(self):
|
|
|
print("\n==== 删除学生信息 ====")
|
|
|
print("1. 按身份证号删除")
|
|
|
print("2. 按学号删除")
|
|
|
choice = input("请选择删除方式: ").strip()
|
|
|
|
|
|
if choice == '1':
|
|
|
id_card = input("请输入身份证号: ").strip()
|
|
|
try:
|
|
|
if self.bll.delete_student_by_id_card(id_card):
|
|
|
print("学生信息删除成功!")
|
|
|
else:
|
|
|
print("删除失败,未找到匹配的学生")
|
|
|
except ValueError as e:
|
|
|
print(f"错误: {e}")
|
|
|
elif choice == '2':
|
|
|
stu_id = input("请输入学号: ").strip()
|
|
|
try:
|
|
|
if self.bll.delete_student_by_stu_id(stu_id):
|
|
|
print("学生信息删除成功!")
|
|
|
else:
|
|
|
print("删除失败,未找到匹配的学生")
|
|
|
except ValueError as e:
|
|
|
print(f"错误: {e}")
|
|
|
else:
|
|
|
print("无效的选择")
|
|
|
|
|
|
def update_student(self):
|
|
|
print("\n==== 更新学生信息 ====")
|
|
|
id_card = input("请输入要更新的学生身份证号: ").strip()
|
|
|
|
|
|
try:
|
|
|
student = self.bll.get_student_by_id_card(id_card)
|
|
|
if not student:
|
|
|
print("未找到该学生")
|
|
|
return
|
|
|
|
|
|
print("当前学生信息:")
|
|
|
print(student)
|
|
|
|
|
|
print("\n请输入要更新的字段 (留空表示不更新):")
|
|
|
name = input(f"姓名({student.name}): ").strip() or student.name
|
|
|
stu_id = input(f"学号({student.stu_id}): ").strip() or student.stu_id
|
|
|
height = input(f"身高({student.height}): ").strip()
|
|
|
height = int(height) if height else student.height
|
|
|
weight = input(f"体重({student.weight}): ").strip()
|
|
|
weight = float(weight) if weight else student.weight
|
|
|
enrollment_date = input(f"入学日期({student.enrollment_date}): ").strip()
|
|
|
enrollment_date = enrollment_date or student.enrollment_date
|
|
|
class_name = input(f"班级名称({student.class_name}): ").strip() or student.class_name
|
|
|
major = input(f"专业({student.major}): ").strip() or student.major
|
|
|
email = input(f"邮箱({student.email}): ").strip() or student.email
|
|
|
phone = input(f"手机号({student.phone}): ").strip() or student.phone
|
|
|
|
|
|
updated_student = Student(
|
|
|
name=name,
|
|
|
id_card=id_card, # 身份证号不可更改
|
|
|
stu_id=stu_id,
|
|
|
gender=student.gender, # 性别从身份证号自动获取,不可更改
|
|
|
height=height,
|
|
|
weight=weight,
|
|
|
enrollment_date=enrollment_date,
|
|
|
class_name=class_name,
|
|
|
major=major,
|
|
|
email=email,
|
|
|
phone=phone
|
|
|
)
|
|
|
|
|
|
if self.bll.update_student(id_card, updated_student):
|
|
|
print("学生信息更新成功!")
|
|
|
else:
|
|
|
print("更新失败")
|
|
|
except ValueError as e:
|
|
|
print(f"输入错误: {e}")
|
|
|
except Exception as e:
|
|
|
print(f"更新学生信息时出错: {e}")
|
|
|
|
|
|
def query_students(self):
|
|
|
while True:
|
|
|
display_query_menu()
|
|
|
choice = input("请选择查询方式: ").strip()
|
|
|
|
|
|
if choice == '1': # 查询所有学生
|
|
|
students = self.bll.get_all_students()
|
|
|
display_students(students)
|
|
|
elif choice == '2': # 按身份证号查询
|
|
|
id_card = input("请输入身份证号: ").strip()
|
|
|
student = self.bll.get_student_by_id_card(id_card)
|
|
|
if student:
|
|
|
# 显示籍贯信息
|
|
|
hometown = self.get_hometown_from_id_card(student.id_card)
|
|
|
print(f"\n籍贯: {hometown}")
|
|
|
display_students([student])
|
|
|
else:
|
|
|
print("未找到匹配的学生")
|
|
|
elif choice == '3': # 按学号查询
|
|
|
stu_id = input("请输入学号: ").strip()
|
|
|
student = self.bll.get_student_by_stu_id(stu_id)
|
|
|
if student:
|
|
|
display_students([student])
|
|
|
else:
|
|
|
print("未找到匹配的学生")
|
|
|
elif choice == '4': # 按姓名查询
|
|
|
name = input("请输入姓名: ").strip()
|
|
|
students = self.bll.get_students_by_name(name)
|
|
|
display_students(students)
|
|
|
elif choice == '5': # 按班级查询
|
|
|
class_name = input("请输入班级名称: ").strip()
|
|
|
students = self.bll.get_students_by_class_name(class_name)
|
|
|
display_students(students)
|
|
|
elif choice == '6': # 按专业查询
|
|
|
major = input("请输入专业名称: ").strip()
|
|
|
students = self.bll.get_students_by_major(major)
|
|
|
display_students(students)
|
|
|
elif choice == '7': # 返回上一级
|
|
|
break
|
|
|
else:
|
|
|
print("无效的选择,请重新输入!")
|
|
|
|
|
|
def stats_students(self):
|
|
|
while True:
|
|
|
display_stats_menu()
|
|
|
choice = input("请选择统计方式: ").strip()
|
|
|
|
|
|
if choice == '1': # 学生总数
|
|
|
count = self.bll.get_student_count()
|
|
|
print(f"\n学生总数: {count}")
|
|
|
elif choice == '2': # 平均身高
|
|
|
avg_height = self.bll.get_average_height()
|
|
|
print(f"\n平均身高: {avg_height:.2f} cm")
|
|
|
elif choice == '3': # 按身高范围统计
|
|
|
min_height = int(input("最小身高(cm): ").strip())
|
|
|
max_height = int(input("最大身高(cm): ").strip())
|
|
|
students = self.bll.get_students_by_height_range(min_height, max_height)
|
|
|
print(f"\n身高在 {min_height}-{max_height} cm 的学生 ({len(students)} 人):")
|
|
|
display_students(students)
|
|
|
elif choice == '4': # 按入学年份统计
|
|
|
year = int(input("请输入入学年份: ").strip())
|
|
|
students = self.bll.get_students_by_enrollment_year(year)
|
|
|
print(f"\n{year}年入学的学生 ({len(students)} 人):")
|
|
|
display_students(students)
|
|
|
elif choice == '5': # 按年龄范围统计
|
|
|
min_age = int(input("最小年龄: ").strip())
|
|
|
max_age = int(input("最大年龄: ").strip())
|
|
|
students = self.bll.get_students_by_age_range(min_age, max_age)
|
|
|
print(f"\n年龄在 {min_age}-{max_age} 岁的学生 ({len(students)} 人):")
|
|
|
display_students(students)
|
|
|
elif choice == '6': # 按专业分组统计
|
|
|
groups = self.bll.get_students_by_major_group()
|
|
|
for major, students in groups.items():
|
|
|
print(f"\n专业: {major} ({len(students)} 人)")
|
|
|
display_students(students)
|
|
|
elif choice == '7': # 性别比例
|
|
|
ratio = self.bll.get_gender_ratio()
|
|
|
print(f"\n性别比例:")
|
|
|
print(f"男生: {ratio.get(True, 0.0):.2f}%")
|
|
|
print(f"女生: {ratio.get(False, 0.0):.2f}%")
|
|
|
elif choice == '8': # 返回上一级
|
|
|
break
|
|
|
else:
|
|
|
print("无效的选择,请重新输入!")
|
|
|
|
|
|
def import_export_data(self):
|
|
|
while True:
|
|
|
display_import_export_menu()
|
|
|
choice = input("请选择操作: ").strip()
|
|
|
|
|
|
if choice == '1': # 导出到JSON
|
|
|
file_path = input("请输入导出文件路径: ").strip()
|
|
|
if self.export_to_json(file_path):
|
|
|
print("导出成功!")
|
|
|
else:
|
|
|
print("导出失败")
|
|
|
elif choice == '2': # 从JSON导入
|
|
|
file_path = input("请输入导入文件路径: ").strip()
|
|
|
success, error = self.import_from_json(file_path)
|
|
|
print(f"导入完成: 成功 {success} 条, 失败 {error} 条")
|
|
|
elif choice == '3': # 导出到CSV
|
|
|
file_path = input("请输入导出文件路径: ").strip()
|
|
|
if self.export_to_csv(file_path):
|
|
|
print("导出成功!")
|
|
|
else:
|
|
|
print("导出失败")
|
|
|
elif choice == '4': # 从CSV导入
|
|
|
file_path = input("请输入导入文件路径: ").strip()
|
|
|
success, error = self.import_from_csv(file_path)
|
|
|
print(f"导入完成: 成功 {success} 条, 失败 {error} 条")
|
|
|
elif choice == '5': # 返回上一级
|
|
|
break
|
|
|
else:
|
|
|
print("无效的选择,请重新输入!")
|
|
|
|
|
|
def clear_all_students(self):
|
|
|
confirm = input("确定要清空所有学生信息吗? (y/n): ").strip().lower()
|
|
|
if confirm == 'y':
|
|
|
self.bll.clear_all_students()
|
|
|
print("所有学生信息已清空!")
|
|
|
else:
|
|
|
print("操作已取消")
|
|
|
|
|
|
def export_to_json(self, file_path: str) -> bool:
|
|
|
"""导出学生信息到JSON文件"""
|
|
|
students = self.bll.get_all_students()
|
|
|
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)
|
|
|
return True
|
|
|
except Exception as e:
|
|
|
print(f"导出学生数据到JSON时出错: {e}")
|
|
|
return False
|
|
|
|
|
|
def import_from_json(self, file_path: str) -> Tuple[int, int]:
|
|
|
"""从JSON文件导入学生信息"""
|
|
|
success_count = 0
|
|
|
error_count = 0
|
|
|
|
|
|
try:
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
student_dicts = json.load(f)
|
|
|
|
|
|
for s_dict in student_dicts:
|
|
|
try:
|
|
|
student = Student.from_dict(s_dict)
|
|
|
if self.bll.add_student(student):
|
|
|
success_count += 1
|
|
|
except Exception as e:
|
|
|
error_count += 1
|
|
|
print(f"导入学生数据时出错: {e}")
|
|
|
|
|
|
return success_count, error_count
|
|
|
except Exception as e:
|
|
|
print(f"从JSON导入学生数据时出错: {e}")
|
|
|
return 0, 0
|
|
|
|
|
|
def export_to_csv(self, file_path: str) -> bool:
|
|
|
"""导出学生信息到CSV文件"""
|
|
|
students = self.bll.get_all_students()
|
|
|
if not students:
|
|
|
return False
|
|
|
|
|
|
try:
|
|
|
with open(file_path, 'w', encoding='utf-8', newline='') as f:
|
|
|
# 获取第一个学生的字段作为CSV表头
|
|
|
fieldnames = list(students[0].to_dict().keys())
|
|
|
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
|
writer.writeheader()
|
|
|
for student in students:
|
|
|
writer.writerow(student.to_dict())
|
|
|
return True
|
|
|
except Exception as e:
|
|
|
print(f"导出学生数据到CSV时出错: {e}")
|
|
|
return False
|
|
|
|
|
|
def import_from_csv(self, file_path: str) -> Tuple[int, int]:
|
|
|
"""从CSV文件导入学生信息"""
|
|
|
success_count = 0
|
|
|
error_count = 0
|
|
|
|
|
|
try:
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
|
reader = csv.DictReader(f)
|
|
|
for row in reader:
|
|
|
try:
|
|
|
# 转换数据类型
|
|
|
converted_row = {}
|
|
|
for key, value in row.items():
|
|
|
if key in ['birth_date', 'enrollment_date'] and value:
|
|
|
converted_row[key] = date.fromisoformat(value)
|
|
|
elif key in ['height', 'weight'] and value:
|
|
|
converted_row[key] = float(value) if value else 0.0
|
|
|
else:
|
|
|
converted_row[key] = value
|
|
|
|
|
|
student = Student.from_dict(converted_row)
|
|
|
if self.bll.add_student(student):
|
|
|
success_count += 1
|
|
|
except Exception as e:
|
|
|
error_count += 1
|
|
|
print(f"导入学生数据时出错: {e}")
|
|
|
|
|
|
return success_count, error_count
|
|
|
except Exception as e:
|
|
|
print(f"从CSV导入学生数据时出错: {e}")
|
|
|
return 0, 0
|
|
|
|
|
|
|
|
|
def main():
|
|
|
# 选择存储方式
|
|
|
print("请选择数据存储方式:")
|
|
|
print("1. JSON")
|
|
|
print("2. CSV")
|
|
|
storage_choice = input("请输入选择 (1/2): ").strip()
|
|
|
|
|
|
file_path = "students.json" if storage_choice == '1' else "students.csv"
|
|
|
|
|
|
# 创建数据访问层
|
|
|
if storage_choice == '1':
|
|
|
dal = JsonStudentDAL(file_path)
|
|
|
elif storage_choice == '2':
|
|
|
dal = CsvStudentDAL(file_path)
|
|
|
else:
|
|
|
print("无效的选择,默认使用JSON存储")
|
|
|
dal = JsonStudentDAL("students.json")
|
|
|
|
|
|
# 创建业务逻辑层
|
|
|
bll = StudentBLL(dal)
|
|
|
|
|
|
# 创建用户界面
|
|
|
tui = StudentTUI(bll)
|
|
|
tui.run()
|
|
|
def fun(self):
|
|
|
"""运行UI"""
|
|
|
while True:
|
|
|
self.display_menu()
|
|
|
choice = input("请选择操作: ").strip()
|
|
|
if choice == '1':
|
|
|
self.add_student()
|
|
|
elif choice == '2':
|
|
|
self.delete_student()
|
|
|
|
|
|
elif choice == '3':
|
|
|
self.update_student()
|
|
|
|
|
|
elif choice == '5':
|
|
|
self.show_stats()
|
|
|
elif choice == '6':
|
|
|
self.import_export_data()
|
|
|
elif choice == '7':
|
|
|
self.clear_students()
|
|
|
elif choice == '8':
|
|
|
self.query_student()
|
|
|
elif choice == '4':
|
|
|
print("感谢使用学生信息管理系统!")
|
|
|
break
|
|
|
else:
|
|
|
print("无效的选择,请重新输入!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
main()
|