|
|
# -*- 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() |