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.
wlhdl/ui/add_student.py

543 lines
21 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 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()