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.
stu1/student UI.py

1318 lines
44 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.

# -*- coding: utf-8 -*-
"""
Created on Tue Jun 24 11:10:36 2025
@author: 26745
"""
from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Union
from datetime import date, datetime
import csv
import json
import os
import re
class IStudentDAL(ABC):
"""学生信息数据访问层接口"""
@abstractmethod
def add(self, student: 'Student') -> bool:
"""添加学生信息"""
pass
@abstractmethod
def delete(self, id_card: str) -> bool:
"""根据身份证号删除学生信息"""
pass
@abstractmethod
def update(self, student: 'Student') -> bool:
"""更新学生信息"""
pass
@abstractmethod
def get_by_id(self, id_card: str) -> Optional['Student']:
"""根据身份证号获取学生信息"""
pass
@abstractmethod
def get_by_stu_id(self, stu_id: str) -> Optional['Student']:
"""根据学号获取学生信息"""
pass
@abstractmethod
def get_all(self) -> List['Student']:
"""获取所有学生信息"""
pass
@abstractmethod
def search_by_name(self, name: str) -> List['Student']:
"""根据姓名模糊查询学生信息"""
pass
@abstractmethod
def search_by_class(self, class_name: str) -> List['Student']:
"""根据班级模糊查询学生信息"""
pass
@abstractmethod
def search_by_major(self, major: str) -> List['Student']:
"""根据专业模糊查询学生信息"""
pass
@abstractmethod
def clear_all(self) -> bool:
"""清空所有学生数据"""
pass
class StudentCSVDAL(IStudentDAL):
"""CSV文件存储实现"""
def __init__(self, file_path: str = 'students.csv'):
self.file_path = file_path
self._ensure_file_exists()
def _ensure_file_exists(self):
"""确保数据文件存在"""
if not os.path.exists(self.file_path):
with open(self.file_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
writer.writerow([
'name', 'id_card', 'stu_id', 'gender', 'height', 'weight',
'enrollment_date', 'class_name', 'major'
])
def _read_all(self) -> List[Dict]:
"""从CSV文件读取所有学生数据"""
try:
with open(self.file_path, 'r', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
return list(reader)
except FileNotFoundError:
self._ensure_file_exists()
return []
def _write_all(self, students: List[Dict]):
"""将所有学生数据写入CSV文件"""
with open(self.file_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=[
'name', 'id_card', 'stu_id', 'gender', 'height', 'weight',
'enrollment_date', 'class_name', 'major'
])
writer.writeheader()
writer.writerows(students)
def add(self, student: 'Student') -> bool:
students = self._read_all()
# 检查身份证号或学号是否已存在
for s in students:
if s['id_card'] == student.id_card:
return False
if s['stu_id'] == student.stu_id:
return False
students.append(student.to_dict())
self._write_all(students)
return True
def delete(self, id_card: str) -> bool:
students = self._read_all()
original_count = len(students)
students = [s for s in students if s['id_card'] != id_card]
if len(students) < original_count:
self._write_all(students)
return True
return False
def update(self, student: 'Student') -> bool:
students = self._read_all()
updated = False
for i, s in enumerate(students):
if s['id_card'] == student.id_card:
students[i] = student.to_dict()
updated = True
break
if updated:
self._write_all(students)
return True
return False
def get_by_id(self, id_card: str) -> Optional['Student']:
students = self._read_all()
for s in students:
if s['id_card'] == id_card:
return Student.from_dict(s)
return None
def get_by_stu_id(self, stu_id: str) -> Optional['Student']:
students = self._read_all()
for s in students:
if s['stu_id'] == stu_id:
return Student.from_dict(s)
return None
def get_all(self) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students]
def search_by_name(self, name: str) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students if name.lower() in s['name'].lower()]
def search_by_class(self, class_name: str) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students
if s['class_name'] and class_name.lower() in s['class_name'].lower()]
def search_by_major(self, major: str) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students
if s['major'] and major.lower() in s['major'].lower()]
def clear_all(self) -> bool:
"""清空所有学生数据"""
self._ensure_file_exists()
return True
class StudentJSONDAL(IStudentDAL):
"""JSON文件存储实现"""
def __init__(self, file_path: str = 'students.json'):
self.file_path = file_path
self._ensure_file_exists()
def _ensure_file_exists(self):
"""确保数据文件存在"""
if not os.path.exists(self.file_path):
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump([], f)
def _read_all(self) -> List[Dict]:
"""从JSON文件读取所有学生数据"""
try:
with open(self.file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
self._ensure_file_exists()
return []
def _write_all(self, students: List[Dict]):
"""将所有学生数据写入JSON文件"""
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(students, f, ensure_ascii=False, indent=2)
def add(self, student: 'Student') -> bool:
students = self._read_all()
# 检查身份证号或学号是否已存在
for s in students:
if s['id_card'] == student.id_card:
return False
if s['stu_id'] == student.stu_id:
return False
students.append(student.to_dict())
self._write_all(students)
return True
def delete(self, id_card: str) -> bool:
students = self._read_all()
original_count = len(students)
students = [s for s in students if s['id_card'] != id_card]
if len(students) < original_count:
self._write_all(students)
return True
return False
def update(self, student: 'Student') -> bool:
students = self._read_all()
updated = False
for i, s in enumerate(students):
if s['id_card'] == student.id_card:
students[i] = student.to_dict()
updated = True
break
if updated:
self._write_all(students)
return True
return False
def get_by_id(self, id_card: str) -> Optional['Student']:
students = self._read_all()
for s in students:
if s['id_card'] == id_card:
return Student.from_dict(s)
return None
def get_by_stu_id(self, stu_id: str) -> Optional['Student']:
students = self._read_all()
for s in students:
if s['stu_id'] == stu_id:
return Student.from_dict(s)
return None
def get_all(self) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students]
def search_by_name(self, name: str) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students if name.lower() in s['name'].lower()]
def search_by_class(self, class_name: str) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students
if s['class_name'] and class_name.lower() in s['class_name'].lower()]
def search_by_major(self, major: str) -> List['Student']:
students = self._read_all()
return [Student.from_dict(s) for s in students
if s['major'] and major.lower() in s['major'].lower()]
def clear_all(self) -> bool:
"""清空所有学生数据"""
self._ensure_file_exists()
return True
class Student:
"""学生信息实体类"""
def __init__(
self,
name: str,
id_card: str,
stu_id: str,
gender: Optional[bool] = None,
height: Optional[int] = None,
weight: Optional[float] = None,
enrollment_date: Union[date, str, None] = None,
class_name: Optional[str] = None,
major: Optional[str] = None,
):
self.name = name
self.id_card = id_card
self.stu_id = stu_id
self.gender = gender
self.height = height
self.weight = weight
self.class_name = class_name
self.major = major
# 处理入学日期
if enrollment_date is None:
self._enrollment_date = None
elif isinstance(enrollment_date, date):
self._enrollment_date = enrollment_date
else:
try:
self._enrollment_date = date.fromisoformat(enrollment_date)
except (ValueError, TypeError):
self._enrollment_date = None
@property
def name(self) -> str:
"""获取学生姓名"""
return self._name
@name.setter
def name(self, value: str):
"""设置学生姓名"""
if not isinstance(value, str):
raise TypeError("姓名必须是字符串")
self._name = value
@property
def id_card(self) -> str:
"""获取身份证号"""
return self._id_card
@id_card.setter
def id_card(self, value: str):
"""设置身份证号"""
if not isinstance(value, str):
raise TypeError("身份证号必须是字符串")
self._id_card = value
@property
def stu_id(self) -> str:
"""获取学号"""
return self._stu_id
@stu_id.setter
def stu_id(self, value: str):
"""设置学号"""
if not isinstance(value, str):
raise TypeError("学号必须是字符串")
self._stu_id = value
@property
def enrollment_date(self) -> Optional[date]:
"""获取入学日期"""
return self._enrollment_date
@enrollment_date.setter
def enrollment_date(self, value: Union[date, str, None]):
"""设置入学日期"""
if value is None:
self._enrollment_date = None
elif isinstance(value, date):
self._enrollment_date = value
else:
try:
self._enrollment_date = date.fromisoformat(value)
except (ValueError, TypeError):
self._enrollment_date = None
@property
def birthday(self) -> Optional[date]:
"""从身份证号中提取出生日期"""
if not self.id_card or len(self.id_card) != 18:
return None
try:
birth_date_str = self.id_card[6:14]
return date.fromisoformat(f"{birth_date_str[:4]}-{birth_date_str[4:6]}-{birth_date_str[6:8]}")
except (ValueError, IndexError):
return None
@property
def age(self) -> Optional[int]:
"""计算年龄"""
if not self.birthday:
return None
today = date.today()
age = today.year - self.birthday.year
# 如果生日还没到年龄减1
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def errors(self) -> Dict[str, str]:
"""获取所有错误信息"""
errors = {}
# 验证姓名
if not (2 <= len(self.name) <= 20):
errors['name'] = "姓名长度必须在2-20个字符之间"
elif not re.match(r'^[\u4e00-\u9fa5A-Za-z]+$', self.name):
errors['name'] = "姓名只能包含中文或英文字符"
# 验证身份证号
id_card_errors = self._validate_id_card()
if id_card_errors:
errors['id_card'] = id_card_errors
# 验证学号
if not self.stu_id:
errors['stu_id'] = "学号不能为空"
elif not re.match(r'^\d{8,20}$', self.stu_id):
errors['stu_id'] = "学号必须是8-20位数字"
# 验证身高
if self.height is not None and not (50 <= self.height <= 250):
errors['height'] = "身高必须在50-250厘米之间"
# 验证体重
if self.weight is not None:
if not (5 <= self.weight <= 300):
errors['weight'] = "体重必须在5-300千克之间"
elif len(str(self.weight).split('.')[-1]) > 1:
errors['weight'] = "体重最多保留一位小数"
# 验证入学日期
if self.enrollment_date is not None:
if self.enrollment_date > date.today():
errors['enrollment_date'] = "入学日期不能晚于当前日期"
if self.birthday and self.enrollment_date < self.birthday:
errors['enrollment_date'] = "入学日期不能早于出生日期"
return errors
@property
def is_valid(self) -> bool:
"""判断学生信息是否有效"""
return not bool(self.errors)
def _validate_id_card(self) -> Optional[str]:
"""验证身份证号的有效性"""
if not self.id_card:
return "身份证号不能为空"
# 长度检查
if len(self.id_card) != 18:
return "身份证号长度必须为18位"
# 前17位必须为数字
if not self.id_card[:17].isdigit():
return "身份证号前17位必须为数字"
# 最后一位可以是数字或X
last_char = self.id_card[-1].upper()
if not (last_char.isdigit() or last_char == 'X'):
return "身份证号最后一位必须是数字或X"
# 校验码验证
check_code = self._get_check_code()
if check_code != last_char:
return f"身份证号校验位错误,应为{check_code}"
# 出生日期验证
birth_date_str = self.id_card[6:14]
if not self._validate_date(birth_date_str):
return "身份证号中的出生日期无效"
return None
@staticmethod
def _validate_date(date_str: str) -> bool:
"""验证日期字符串(YYYYMMDD)的有效性"""
if len(date_str) != 8 or not date_str.isdigit():
return False
try:
year = int(date_str[:4])
month = int(date_str[4:6])
day = int(date_str[6:8])
# 简单的日期有效性检查
if not (1900 <= year <= datetime.now().year):
return False
if not (1 <= month <= 12):
return False
if not (1 <= day <= 31):
return False
# 更精确的日期检查
date.fromisoformat(f"{year}-{month:02d}-{day:02d}")
return True
except (ValueError, IndexError):
return False
def _get_check_code(self) -> str:
"""计算身份证号的校验码"""
# 加权因子
weights = [2 ** (17 - i) % 11 for i in range(17)]
# 计算加权和
total = sum(int(digit) * weight for digit, weight in zip(self.id_card[:17], weights))
# 计算校验码
check_code_map = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8',
5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}
return check_code_map[total % 11]
def to_dict(self) -> Dict:
"""将学生对象转换为字典"""
data = {
'name': self.name,
'id_card': self.id_card,
'stu_id': self.stu_id,
'gender': self.gender,
'height': self.height,
'weight': self.weight,
'enrollment_date': self.enrollment_date.isoformat() if self.enrollment_date else None,
'class_name': self.class_name,
'major': self.major
}
return data
@classmethod
def from_dict(cls, data: Dict) -> 'Student':
"""从字典创建学生对象"""
data = data.copy()
# 处理入学日期
if 'enrollment_date' in data and isinstance(data['enrollment_date'], str):
try:
data['enrollment_date'] = date.fromisoformat(data['enrollment_date'])
except ValueError:
data['enrollment_date'] = None
return cls(**data)
def __repr__(self) -> str:
"""返回对象的字符串表示"""
return (f"Student(name='{self.name}', id_card='{self.id_card}', stu_id='{self.stu_id}', "
f"gender={'' if self.gender else '' if self.gender is not None else None}, "
f"height={self.height}, weight={self.weight}, "
f"enrollment_date={self.enrollment_date.isoformat() if self.enrollment_date else None}, "
f"class_name='{self.class_name}', major='{self.major}')")
class StudentService:
"""学生信息业务逻辑层"""
def __init__(self, dal: IStudentDAL):
self.dal = dal
def add_student(self, student_data: Dict) -> Dict:
"""添加学生信息"""
student = Student.from_dict(student_data)
if not student.is_valid:
return {'success': False, 'message': '学生信息不合法', 'errors': student.errors}
# 检查身份证号是否已存在
if self.dal.get_by_id(student.id_card):
return {'success': False, 'message': '身份证号已存在'}
# 检查学号是否已存在
if self.dal.get_by_stu_id(student.stu_id):
return {'success': False, 'message': '学号已存在'}
if self.dal.add(student):
return {'success': True, 'message': '添加成功'}
return {'success': False, 'message': '添加失败'}
def delete_student(self, id_card: str) -> Dict:
"""删除学生信息"""
if self.dal.delete(id_card):
return {'success': True, 'message': '删除成功'}
return {'success': False, 'message': '学生不存在'}
from typing import Dict, List, Optional, Union
from datetime import date
import re
def update_student(self, identifier: Dict[str, str], update_data: Dict) -> Dict:
"""
通用更新学生信息方法
:param identifier: 识别学生的条件字典,如 {'id_card': '123...'} 或 {'stu_id': '2023001'}
:param update_data: 要更新的数据字典
:return: 操作结果字典
"""
# 1. 根据条件查找学生
student = self._find_student_by_identifier(identifier)
if not student:
return {'success': False, 'message': '未找到匹配的学生'}
# 2. 合并更新数据
updated_student = self._merge_student_data(student, update_data)
# 3. 验证数据
if not updated_student.is_valid:
return {'success': False, 'message': '更新数据不合法', 'errors': updated_student.errors}
# 4. 检查学号冲突(如果更新了学号)
if 'stu_id' in update_data and update_data['stu_id'] != student.stu_id:
existing = self.dal.get_by_stu_id(update_data['stu_id'])
if existing and existing.id_card != student.id_card:
return {'success': False, 'message': '学号已被其他学生使用'}
# 5. 执行更新
if self.dal.update(updated_student):
return {'success': True, 'message': '更新成功'}
return {'success': False, 'message': '更新失败'}
def _find_student_by_identifier(self, identifier: Dict[str, str]) -> Optional[Student]:
"""根据不同的识别条件查找学生"""
if 'id_card' in identifier:
return self.dal.get_by_id(identifier['id_card'])
elif 'stu_id' in identifier:
return self.dal.get_by_stu_id(identifier['stu_id'])
elif 'name' in identifier:
students = self.dal.search_by_name(identifier['name'])
if len(students) == 1:
return students[0]
return None
def _merge_student_data(self, original: Student, update_data: Dict) -> Student:
"""合并原始学生数据和更新数据"""
original_data = original.to_dict()
merged_data = {**original_data, **update_data}
return Student.from_dict(merged_data)
def batch_update_students(self, condition: Dict[str, str], update_data: Dict) -> Dict:
"""
批量更新学生信息
:param condition: 筛选条件,如 {'class_name': '计算机23-1'}
:param update_data: 要更新的数据
:return: 操作结果字典
"""
# 1. 根据条件查找学生
students = self._find_students_by_condition(condition)
if not students:
return {'success': False, 'message': '未找到匹配的学生'}
results = {
'total': len(students),
'success': 0,
'failed': 0,
'details': []
}
# 2. 逐个更新
for student in students:
result = self.update_student(
{'id_card': student.id_card},
update_data
)
if result['success']:
results['success'] += 1
else:
results['failed'] += 1
results['details'].append({
'id_card': student.id_card,
'success': result['success'],
'message': result.get('message', '')
})
return {
'success': True,
'message': f'批量更新完成,成功{results["success"]}个,失败{results["failed"]}',
'results': results
}
def _find_students_by_condition(self, condition: Dict[str, str]) -> List[Student]:
"""根据条件查找多个学生"""
if 'class_name' in condition:
return self.dal.search_by_class(condition['class_name'])
elif 'major' in condition:
return self.dal.search_by_major(condition['major'])
elif 'name' in condition:
return self.dal.search_by_name(condition['name'])
return []
def get_student_by_id(self, id_card: str) -> Optional[Student]:
"""根据身份证号获取学生信息"""
return self.dal.get_by_id(id_card)
def get_student_by_stu_id(self, stu_id: str) -> Optional[Student]:
"""根据学号获取学生信息"""
return self.dal.get_by_stu_id(stu_id)
def get_all_students(self) -> List[Student]:
"""获取所有学生信息"""
return self.dal.get_all()
def search_students_by_name(self, name: str) -> List[Student]:
"""根据姓名模糊查询学生信息"""
return self.dal.search_by_name(name)
def search_students_by_class(self, class_name: str) -> List[Student]:
"""根据班级模糊查询学生信息"""
return self.dal.search_by_class(class_name)
def search_students_by_major(self, major: str) -> List[Student]:
"""根据专业模糊查询学生信息"""
return self.dal.search_by_major(major)
def get_student_count(self) -> int:
"""获取学生总数"""
return len(self.dal.get_all())
def get_major_statistics(self) -> Dict[str, int]:
"""统计各专业学生人数"""
students = self.dal.get_all()
stats = {}
for student in students:
if student.major:
stats[student.major] = stats.get(student.major, 0) + 1
return stats
def get_height_statistics(self) -> Dict[str, float]:
"""计算平均身高"""
students = self.dal.get_all()
total_height = 0
count = 0
for student in students:
if student.height:
total_height += student.height
count += 1
return {'average_height': total_height / count if count > 0 else 0}
def get_weight_statistics(self) -> Dict[str, float]:
"""计算平均体重"""
students = self.dal.get_all()
total_weight = 0.0
count = 0
for student in students:
if student.weight:
total_weight += student.weight
count += 1
return {'average_weight': total_weight / count if count > 0 else 0}
def export_to_csv(self, file_path: str) -> bool:
"""导出数据到CSV文件"""
try:
students = self.dal.get_all()
data = [student.to_dict() for student in students]
with open(file_path, 'w', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=[
'name', 'id_card', 'stu_id', 'gender', 'height', 'weight',
'enrollment_date', 'class_name', 'major'
])
writer.writeheader()
writer.writerows(data)
return True
except Exception as e:
print(f"导出失败: {e}")
return False
def import_from_csv(self, file_path: str) -> Dict:
"""从CSV文件导入数据"""
try:
with open(file_path, 'r', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
imported = 0
skipped = 0
for row in reader:
student = Student.from_dict(row)
if not student.is_valid:
skipped += 1
continue
# 检查是否已存在
if self.dal.get_by_id(student.id_card) or self.dal.get_by_stu_id(student.stu_id):
skipped += 1
continue
if self.dal.add(student):
imported += 1
else:
skipped += 1
return {
'success': True,
'message': f'导入完成 (成功: {imported}, 跳过: {skipped})',
'imported': imported,
'skipped': skipped
}
except Exception as e:
return {
'success': False,
'message': f'导入失败: {e}'
}
def clear_all_data(self) -> bool:
"""清空所有学生数据"""
return self.dal.clear_all()
class StudentManagementUI:
"""学生信息管理系统用户界面"""
def __init__(self):
# 默认使用JSON存储
self.dal = StudentJSONDAL()
self.service = StudentService(self.dal)
def display_menu(self):
"""显示主菜单"""
print("\n===== 学生信息管理系统 =====")
print("1. 添加学生信息")
print("2. 删除学生信息")
print("3. 更新学生信息")
print("4. 查询学生信息")
print("5. 统计分析")
print("6. 数据导入导出")
print("7. 切换存储方式")
print("8. 清空所有学生信息")
print("0. 退出系统")
print("==========================")
def run(self):
"""运行系统"""
while True:
self.display_menu()
choice = input("请选择操作: ")
if choice == '1':
self.add_student_ui()
elif choice == '2':
self.delete_student_ui()
elif choice == '3':
self.update_student_ui()
elif choice == '4':
self.query_students_ui()
elif choice == '5':
self.statistics_ui()
elif choice == '6':
self.import_export_ui()
elif choice == '7':
self.switch_storage_ui()
elif choice == '8':
self.clear_all_data_ui()
elif choice == '0':
print("感谢使用学生信息管理系统,再见!")
sys.exit()
else:
print("无效的选择,请重新输入!")
def add_student_ui(self):
"""添加学生信息界面"""
print("\n===== 添加学生信息 =====")
student_data = {}
student_data['name'] = input("姓名: ")
student_data['id_card'] = input("身份证号: ")
student_data['stu_id'] = input("学号: ")
gender = input("性别(男/女/跳过): ").strip()
if gender == '':
student_data['gender'] = True
elif gender == '':
student_data['gender'] = False
height = input("身高(cm跳过请直接回车): ").strip()
if height:
try:
student_data['height'] = int(height)
except ValueError:
print("身高必须是整数")
return
weight = input("体重(kg跳过请直接回车): ").strip()
if weight:
try:
student_data['weight'] = float(weight)
except ValueError:
print("体重必须是数字")
return
enrollment_date = input("入学日期(YYYY-MM-DD跳过请直接回车): ").strip()
if enrollment_date:
student_data['enrollment_date'] = enrollment_date
student_data['class_name'] = input("班级名称(跳过请直接回车): ").strip() or None
student_data['major'] = input("专业名称(跳过请直接回车): ").strip() or None
result = self.service.add_student(student_data)
if result['success']:
print("添加学生信息成功!")
else:
print(f"添加失败: {result['message']}")
if 'errors' in result:
for field, error in result['errors'].items():
print(f"{field}: {error}")
def delete_student_ui(self):
"""删除学生信息界面"""
print("\n===== 删除学生信息 =====")
id_card = input("请输入要删除的学生身份证号: ")
result = self.service.delete_student(id_card)
if result['success']:
print("删除学生信息成功!")
else:
print(f"删除失败: {result['message']}")
def update_student_ui(self):
"""更新学生信息界面"""
print("\n===== 更新学生信息 =====")
print("请选择查找学生的方式:")
print("1. 按身份证号")
print("2. 按学号")
print("3. 按姓名")
print("4. 批量更新(按班级)")
print("5. 批量更新(按专业)")
print("0. 返回上级菜单")
choice = input("请选择操作: ")
if choice == '1':
self._update_by_id_card()
elif choice == '2':
self._update_by_stu_id()
elif choice == '3':
self._update_by_name()
elif choice == '4':
self._batch_update_by_class()
elif choice == '5':
self._batch_update_by_major()
elif choice == '0':
return
else:
print("无效的选择!")
def _update_by_id_card(self):
"""按身份证号更新"""
id_card = input("请输入学生身份证号: ")
student = self.service.get_student_by_id(id_card)
if not student:
print("未找到该学生!")
return
print("当前学生信息:")
self.display_student_details(student)
self._perform_update({'id_card': id_card})
def _update_by_stu_id(self):
"""按学号更新"""
stu_id = input("请输入学生学号: ")
student = self.service.get_student_by_stu_id(stu_id)
if not student:
print("未找到该学生!")
return
print("当前学生信息:")
self.display_student_details(student)
self._perform_update({'stu_id': stu_id})
def _update_by_name(self):
"""按姓名更新"""
name = input("请输入学生姓名: ")
students = self.service.search_students_by_name(name)
if not students:
print("未找到该学生!")
return
elif len(students) > 1:
print(f"找到{len(students)}个同名学生,请使用身份证号或学号更新")
return
print("当前学生信息:")
self.display_student_details(students[0])
self._perform_update({'id_card': students[0].id_card})
def _batch_update_by_class(self):
"""按班级批量更新"""
class_name = input("请输入要更新的班级名称: ")
self._perform_batch_update({'class_name': class_name})
def _batch_update_by_major(self):
"""按专业批量更新"""
major = input("请输入要更新的专业名称: ")
self._perform_batch_update({'major': major})
def _perform_update(self, identifier: Dict[str, str]):
"""执行单个学生更新"""
print("\n请输入要更新的信息(直接回车保持原值):")
update_data = {}
name = input("姓名: ").strip()
if name:
update_data['name'] = name
stu_id = input("学号: ").strip()
if stu_id:
update_data['stu_id'] = stu_id
gender = input("性别(男/女/跳过): ").strip()
if gender == '':
update_data['gender'] = True
elif gender == '':
update_data['gender'] = False
height = input("身高(cm): ").strip()
if height:
try:
update_data['height'] = int(height)
except ValueError:
print("身高必须是整数")
return
weight = input("体重(kg): ").strip()
if weight:
try:
update_data['weight'] = float(weight)
except ValueError:
print("体重必须是数字")
return
enrollment_date = input("入学日期(YYYY-MM-DD): ").strip()
if enrollment_date:
update_data['enrollment_date'] = enrollment_date
class_name = input("班级名称: ").strip()
if class_name:
update_data['class_name'] = class_name or None
major = input("专业名称: ").strip()
if major:
update_data['major'] = major or None
result = self.service.update_student(identifier, update_data)
if result['success']:
print("更新成功!")
else:
print(f"更新失败: {result['message']}")
if 'errors' in result:
for field, error in result['errors'].items():
print(f"{field}: {error}")
def _perform_batch_update(self, condition: Dict[str, str]):
"""执行批量更新"""
print("\n请输入要批量更新的信息:")
update_data = {}
class_name = input("班级名称(直接回车跳过): ").strip()
if class_name:
update_data['class_name'] = class_name or None
major = input("专业名称(直接回车跳过): ").strip()
if major:
update_data['major'] = major or None
enrollment_date = input("入学日期(YYYY-MM-DD,直接回车跳过): ").strip()
if enrollment_date:
update_data['enrollment_date'] = enrollment_date
if not update_data:
print("没有提供任何更新数据!")
return
confirm = input(f"确定要批量更新{condition}的学生信息吗?(y/n): ").lower()
if confirm != 'y':
print("操作已取消")
return
result = self.service.batch_update_students(condition, update_data)
print(result['message'])
if result['success'] and result['results']['failed'] > 0:
print("\n失败详情:")
for detail in result['results']['details']:
if not detail['success']:
print(f"身份证号: {detail['id_card']}, 原因: {detail['message']}")
def query_students_ui(self):
"""查询学生信息界面"""
while True:
print("\n===== 查询学生信息 =====")
print("1. 查询所有学生")
print("2. 按身份证号查询")
print("3. 按学号查询")
print("4. 按姓名查询")
print("5. 按班级查询")
print("6. 按专业查询")
print("7. 返回上一级")
print("======================")
choice = input("请选择查询方式: ")
if choice == '1':
self.query_all_students()
elif choice == '2':
self.query_by_id_card()
elif choice == '3':
self.query_by_stu_id()
elif choice == '4':
self.query_by_name()
elif choice == '5':
self.query_by_class()
elif choice == '6':
self.query_by_major()
elif choice == '7':
break
else:
print("无效的选择,请重新输入!")
def query_all_students(self):
"""查询所有学生"""
students = self.service.get_all_students()
if not students:
print("没有学生信息!")
return
print("\n所有学生信息:")
print("-" * 80)
print(f"{'姓名':<10}{'学号':<15}{'身份证号':<20}{'班级':<15}{'专业':<20}")
print("-" * 80)
for student in students:
print(f"{student.name:<10}{student.stu_id:<15}{student.id_card:<20}"
f"{student.class_name or '未设置':<15}{student.major or '未设置':<20}")
print("-" * 80)
print(f"{len(students)} 名学生")
def query_by_id_card(self):
"""按身份证号查询"""
id_card = input("请输入身份证号: ")
student = self.service.get_student_by_id(id_card)
if student:
self.display_student_details(student)
else:
print("未找到该学生!")
def query_by_stu_id(self):
"""按学号查询"""
stu_id = input("请输入学号: ")
student = self.service.get_student_by_stu_id(stu_id)
if student:
self.display_student_details(student)
else:
print("未找到该学生!")
def query_by_name(self):
"""按姓名查询"""
name = input("请输入姓名(支持模糊查询): ")
students = self.service.search_students_by_name(name)
if students:
print(f"\n找到 {len(students)} 名学生:")
for student in students:
self.display_student_details(student)
print("-" * 40)
else:
print("未找到匹配的学生!")
def query_by_class(self):
"""按班级查询"""
class_name = input("请输入班级名称(支持模糊查询): ")
students = self.service.search_students_by_class(class_name)
if students:
print(f"\n找到 {len(students)} 名学生:")
for student in students:
self.display_student_details(student)
print("-" * 40)
else:
print("未找到匹配的学生!")
def query_by_major(self):
"""按专业查询"""
major = input("请输入专业名称(支持模糊查询): ")
students = self.service.search_students_by_major(major)
if students:
print(f"\n找到 {len(students)} 名学生:")
for student in students:
self.display_student_details(student)
print("-" * 40)
else:
print("未找到匹配的学生!")
def display_student_details(self, student: Student):
"""显示学生详细信息"""
print("\n学生详细信息:")
print(f"姓名: {student.name}")
print(f"身份证号: {student.id_card}")
print(f"学号: {student.stu_id}")
print(f"性别: {'' if student.gender else '' if student.gender is not None else '未设置'}")
print(f"出生日期: {student.birthday.isoformat() if student.birthday else '未知'}")
print(f"年龄: {student.age if student.age else '未知'}")
print(f"身高: {student.height}cm" if student.height else "身高: 未设置")
print(f"体重: {student.weight}kg" if student.weight else "体重: 未设置")
print(f"入学日期: {student.enrollment_date.isoformat() if student.enrollment_date else '未设置'}")
print(f"班级: {student.class_name or '未设置'}")
print(f"专业: {student.major or '未设置'}")
def statistics_ui(self):
"""统计分析界面"""
while True:
print("\n===== 统计分析 =====")
print("1. 学生总数")
print("2. 平均身高")
print("3. 平均体重")
print("4. 专业统计")
print("5. 返回上一级")
print("===================")
choice = input("请选择统计项目: ")
if choice == '1':
count = self.service.get_student_count()
print(f"\n学生总数: {count}")
elif choice == '2':
stats = self.service.get_height_statistics()
print(f"\n平均身高: {stats['average_height']:.1f}cm")
elif choice == '3':
stats = self.service.get_weight_statistics()
print(f"\n平均体重: {stats['average_weight']:.1f}kg")
elif choice == '4':
stats = self.service.get_major_statistics()
print("\n各专业学生人数:")
for major, count in stats.items():
print(f"{major}: {count}")
elif choice == '5':
break
else:
print("无效的选择,请重新输入!")
def import_export_ui(self):
"""数据导入导出界面"""
while True:
print("\n===== 数据导入导出 =====")
print("1. 导出数据到CSV")
print("2. 从CSV导入数据")
print("3. 返回上一级")
print("======================")
choice = input("请选择操作: ")
if choice == '1':
file_path = input("请输入导出文件路径(默认: students_export.csv): ").strip() or "students_export.csv"
if self.service.export_to_csv(file_path):
print(f"数据已成功导出到 {file_path}")
else:
print("导出失败!")
elif choice == '2':
file_path = input("请输入要导入的CSV文件路径: ").strip()
result = self.service.import_from_csv(file_path)
print(result['message'])
elif choice == '3':
break
else:
print("无效的选择,请重新输入!")
def switch_storage_ui(self):
"""切换存储方式界面"""
print("\n===== 切换存储方式 =====")
print(f"当前存储方式: {'JSON' if isinstance(self.dal, StudentJSONDAL) else 'CSV'}")
print("1. 切换到JSON存储")
print("2. 切换到CSV存储")
print("3. 返回上一级")
print("======================")
choice = input("请选择存储方式: ")
if choice == '1':
self.dal = StudentJSONDAL()
self.service = StudentService(self.dal)
print("已切换到JSON存储")
elif choice == '2':
self.dal = StudentCSVDAL()
self.service = StudentService(self.dal)
print("已切换到CSV存储")
elif choice == '3':
return
else:
print("无效的选择!")
def clear_all_data_ui(self):
"""清空所有数据界面"""
confirm = input("确定要清空所有学生数据吗?此操作不可恢复!(y/n): ").strip().lower()
if confirm == 'y':
if self.service.clear_all_data():
print("所有学生数据已清空!")
else:
print("清空数据失败!")
else:
print("操作已取消")
if __name__ == '__main__':
ui = StudentManagementUI()
ui.run()