master
孟天翔 1 month ago
parent cf3adab7eb
commit 88e34462b1

@ -0,0 +1,92 @@
time import date
from typing import List, Optional
from dal.StudentDAL import StudentDAL
from student.Student import Student
class StudentBLL:
def __init__(self, dal: StudentDAL):
self.dal = dal
def add_student(self, student: Student):
if not student.is_valid:
errors = ", ".join(student.get_errors())
raise ValueError(f"学生数据校验失败: {errors}")
if not self.dal.add_student(student):
raise ValueError("添加失败,身份证号或学号可能已存在")
def delete_student(self, stu_id: str):
if not self.dal.delete_student(stu_id):
raise ValueError(f"找不到学号为 {stu_id} 的学生")
def update_student(self, stu_id: str, student: Student):
if not student.is_valid:
errors = ", ".join(student.get_errors())
raise ValueError(f"学生数据校验失败: {errors}")
if not self.dal.update_student(stu_id, student):
raise ValueError(f"找不到学号为 {stu_id} 的学生")
def get_student_by_id_card(self, id_card: str) -> Optional[Student]:
return self.dal.get_by_id_card(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(self, search_type: str, keyword: str) -> List[Student]:
search_type = search_type.lower()
if search_type == 'name':
return self.dal.search_by_name(keyword)
elif search_type == 'class':
return self.dal.search_by_class(keyword)
elif search_type == 'major':
return self.dal.search_by_major(keyword)
else:
return []
def get_student_count(self) -> int:
return self.dal.get_student_count()
def get_major_counts(self) -> dict:
return self.dal.get_major_counts()
def calculate_avg_height(self) -> float:
students = self.dal.get_all()
heights = [s.height for s in students if s.height is not None]
if not heights:
return 0.0
return sum(heights) / len(heights)
def calculate_avg_weight(self) -> float:
students = self.dal.get_all()
weights = [s.weight for s in students if s.weight is not None]
if not weights:
return 0.0
return sum(weights) / len(weights)
def get_students_by_age(self, min_age: int, max_age: int) -> List[Student]:
today = date.today()
students = self.dal.get_all()
result = []
for student in students:
if student.age is None:
continue
if min_age <= student.age <= max_age:
result.append(student)
return result
def export_data(self, file_path: str) -> bool:
return self.dal.export_data(file_path)
def import_data(self, file_path: str) -> bool:
return self.dal.import_data(file_path)
def clear_all(self) -> bool:
return self.dal.clear_all()

@ -1,373 +0,0 @@
from abc import ABC, abstractmethod
import json
import csv
import os
from typing import List, Dict, Optional, Any
from copy import deepcopy
class Student:
def __init__(self, data: Dict[str, Any]):
self.__dict__.update(data)
def to_dict(self) -> Dict[str, Any]:
return self.__dict__.copy()
def validate(self) -> List[str]:
errors = []
if not hasattr(self, 'id_number') or not self.id_number:
errors.append("身份证号不能为空")
if not hasattr(self, 'stu_id') or not self.stu_id:
errors.append("学号不能为空")
if hasattr(self, 'age') and (not isinstance(self.age, int) or self.age < 0):
errors.append("年龄必须为非负整数")
return errors
class IStudentDAL(ABC):
@abstractmethod
def get_by_id(self, id_number: str) -> Optional[Dict[str, Any]]:
pass
@abstractmethod
def get_by_stu_id(self, stu_id: str) -> Optional[Dict[str, Any]]:
pass
@abstractmethod
def get_all(self) -> List[Dict[str, Any]]:
pass
@abstractmethod
def add(self, student: Dict[str, Any]) -> bool:
pass
def delete(self, id_number: str) -> bool:
pass
def delete_by_stu_id(self, stu_id: str) -> bool:
pass
def update(self, student: Dict[str, Any]) -> bool:
pass
class JsonStudentDAL(IStudentDAL):
def __init__(self, file_path: str):
self.file_path = file_path
self.data = self._load()
def _load(self) -> List[Dict[str, Any]]:
if not os.path.exists(self.file_path):
return []
try:
with open(self.file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return []
def _save(self) -> None:
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(self.data, f, ensure_ascii=False, indent=4)
def get_by_id(self, id_number: str) -> Optional[Dict[str, Any]]:
for s in self.data:
if s.get('id_number') == id_number:
return deepcopy(s)
return None
def get_by_stu_id(self, stu_id: str) -> Optional[Dict[str, Any]]:
for s in self.data:
if s.get('stu_id') == stu_id:
return deepcopy(s)
return None
def get_all(self) -> List[Dict[str, Any]]:
return [deepcopy(s) for s in self.data]
def add(self, student: Dict[str, Any]) -> bool:
self.data.append(deepcopy(student))
self._save()
return True
def delete(self, id_number: str) -> bool:
for i, s in enumerate(self.data):
if s.get('id_number') == id_number:
del self.data[i]
self._save()
return True
return False
def delete_by_stu_id(self, stu_id: str) -> bool:
for i, s in enumerate(self.data):
if s.get('stu_id') == stu_id:
del self.data[i]
self._save()
return True
return False
def update(self, student: Dict[str, Any]) -> bool:
for i, s in enumerate(self.data):
if s.get('id_number') == student.get('id_number'):
self.data[i] = deepcopy(student)
self._save()
return True
return False
class CsvStudentDAL(IStudentDAL):
FIELD_TYPES = {
"id_number": str,
"stu_id": str,
"name": str,
"age": int,
"gender": str
}
def __init__(self, file_path: str):
self.file_path = file_path
self.data = self._load()
def _load(self) -> List[Dict[str, Any]]:
if not os.path.exists(self.file_path):
self._ensure_file_exists()
return []
data = []
try:
with open(self.file_path, 'r', newline='', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(self._convert_row(row))
except:
pass
return data
def _ensure_file_exists(self) -> None:
if not os.path.exists(self.file_path):
with open(self.file_path, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
writer.writeheader()
def _convert_row(self, row: Dict[str, str]) -> Dict[str, Any]:
converted = {}
for k, v in row.items():
if k in self.FIELD_TYPES:
try:
converted[k] = self.FIELD_TYPES[k](v)
except:
converted[k] = v
else:
converted[k] = v
return converted
def _save(self) -> None:
with open(self.file_path, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
writer.writeheader()
for s in self.data:
writer.writerow(s)
def get_by_id(self, id_number: str) -> Optional[Dict[str, Any]]:
for s in self.data:
if s.get('id_number') == id_number:
return deepcopy(s)
return None
def get_by_stu_id(self, stu_id: str) -> Optional[Dict[str, Any]]:
for s in self.data:
if s.get('stu_id') == stu_id:
return deepcopy(s)
return None
def get_all(self) -> List[Dict[str, Any]]:
return [deepcopy(s) for s in self.data]
def add(self, student: Dict[str, Any]) -> bool:
self.data.append(deepcopy(student))
self._save()
return True
def delete(self, id_number: str) -> bool:
for i, s in enumerate(self.data):
if s.get('id_number') == id_number:
del self.data[i]
self._save()
return True
return False
def delete_by_stu_id(self, stu_id: str) -> bool:
for i, s in enumerate(self.data):
if s.get('stu_id') == stu_id:
del self.data[i]
self._save()
return True
return False
def update(self, student: Dict[str, Any]) -> bool:
for i, s in enumerate(self.data):
if s.get('id_number') == student.get('id_number'):
self.data[i] = deepcopy(student)
self._save()
return True
return False
class StudentBLL:
def __init__(self, file_path: str):
self.dal = self._get_dal(file_path)
def _get_dal(self, file_path: str) -> IStudentDAL:
ext = os.path.splitext(file_path)[1].lower()
if ext == '.json':
return JsonStudentDAL(file_path)
elif ext == '.csv':
return CsvStudentDAL(file_path)
else:
raise ValueError(f"不支持的文件类型: {ext},请使用.json或.csv")
def _validate_student(self, student: Student) -> None:
errors = student.validate()
if errors:
raise ValueError(f"学生信息校验失败: {', '.join(errors)}")
def _check_uniqueness(self, student: Student) -> None:
all_students = self.dal.get_all()
if any(s.get('id_number') == student.id_number for s in all_students):
raise ValueError(f"身份证号 {student.id_number} 已存在")
if any(s.get('stu_id') == student.stu_id for s in all_students):
raise ValueError(f"学号 {student.stu_id} 已存在")
def add(self, student: Student) -> bool:
self._validate_student(student)
self._check_uniqueness(student)
return self.dal.add(student.to_dict())
def delete(self, id_number: str) -> bool:
if not self.dal.get_by_id(id_number):
raise ValueError(f"学生(身份证号: {id_number})不存在")
return self.dal.delete(id_number)
def delete_by_stu_id(self, stu_id: str) -> bool:
if not self.dal.get_by_stu_id(stu_id):
raise ValueError(f"学生(学号: {stu_id})不存在")
return self.dal.delete_by_stu_id(stu_id)
def update(self, student: Student) -> bool:
self._validate_student(student)
if not self.dal.get_by_id(student.id_number):
raise ValueError(f"学生(身份证号: {student.id_number})不存在")
all_students = self.dal.get_all()
if any(s.get('stu_id') == student.stu_id and s.get('id_number') != student.id_number for s in all_students):
raise ValueError(f"学号 {student.stu_id} 已被其他学生使用")
return self.dal.update(student.to_dict())
def update_partial(self, id_number: str, fields: Dict[str, Any]) -> bool:
student = self.dal.get_by_id(id_number)
if not student:
raise ValueError(f"学生(身份证号: {id_number})不存在")
updated = deepcopy(student)
for k, v in fields.items():
if k == 'id_number':
raise ValueError("身份证号不允许直接修改")
updated[k] = v
if 'stu_id' in fields:
all_students = self.dal.get_all()
if any(s.get('stu_id') == fields['stu_id'] and s.get('id_number') != id_number for s in all_students):
raise ValueError(f"学号 {fields['stu_id']} 已被其他学生使用")
partial_student = Student(updated)
self._validate_student(partial_student)
return self.dal.update(partial_student.to_dict())
def get_by_id(self, id_number: str) -> Optional[Student]:
data = self.dal.get_by_id(id_number)
return Student(data) if data else None
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
data = self.dal.get_by_stu_id(stu_id)
return Student(data) if data else None
def get_all(self) -> List[Student]:
return [Student(deepcopy(s)) for s in self.dal.get_all()]
def get_by_key(self, field: str, value: str, fuzzy: bool = False) -> List[Student]:
all_students = self.dal.get_all()
result = []
for s in all_students:
if field not in s:
continue
s_value = str(s[field])
if fuzzy:
if value in s_value:
result.append(Student(deepcopy(s)))
else:
if s_value == value:
result.append(Student(deepcopy(s)))
return result
def get_by_range(self, field: str, min_val: Any, max_val: Any) -> List[Student]:
all_students = self.dal.get_all()
result = []
for s in all_students:
if field not in s:
continue
try:
s_value = s[field]
if min_val <= s_value <= max_val:
result.append(Student(deepcopy(s)))
except (TypeError, ValueError):
continue
return result
def export_to_json(self, file_path: str) -> bool:
all_data = [s.to_dict() for s in self.get_all()]
try:
dal = JsonStudentDAL(file_path)
return dal.export_to_json(all_data, file_path)
except:
return False
def import_from_json(self, file_path: str) -> List[str]:
errors = []
try:
dal = JsonStudentDAL(file_path)
data_list = dal.import_from_json(file_path)
for data in data_list:
try:
student = Student(data)
self._validate_student(student)
self._check_uniqueness(student)
self.dal.add(student.to_dict())
except Exception as e:
errors.append(f"导入失败:{data.get('id_number', '无ID')} - {str(e)}")
return errors
except Exception as e:
errors.append(f"导入文件错误:{str(e)}")
return errors
def export_to_csv(self, file_path: str) -> bool:
all_data = [s.to_dict() for s in self.get_all()]
try:
dal = CsvStudentDAL(file_path)
return dal.export_to_csv(all_data, file_path)
except:
return False
def import_from_csv(self, file_path: str) -> List[str]:
errors = []
try:
dal = CsvStudentDAL(file_path)
data_list = dal.import_from_csv(file_path)
for data in data_list:
try:
student = Student(data)
self._validate_student(student)
self._check_uniqueness(student)
self.dal.add(student.to_dict())
except Exception as e:
errors.append(f"导入失败:{data.get('id_number', '无ID')} - {str(e)}")
return errors
except Exception as e:
errors.append(f"导入文件错误:{str(e)}")
return errors

@ -0,0 +1,157 @@
import os
import json
from datetime import date
from typing import List, Optional
from student.Student import Student
class StudentDAL:
def __init__(self, file_path: str):
self.file_path = file_path
self._ensure_file_exists()
def _ensure_file_exists(self):
"""确保文件存在"""
if not os.path.exists(self.file_path):
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump([], f)
def _load_data(self) -> List[dict]:
"""加载数据"""
try:
with open(self.file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
return []
def _save_data(self, data: List[dict]):
"""保存数据"""
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
def add_student(self, student: Student) -> bool:
data = self._load_data()
# 检查身份证号是否唯一
if any(s['id_card'] == student.id_card for s in data):
return False
# 检查学号是否唯一
if any(s['stu_id'] == student.stu_id for s in data):
return False
data.append(student.to_dict())
self._save_data(data)
return True
def get_by_id_card(self, id_card: str) -> Optional[Student]:
data = self._load_data()
for student_dict in data:
if student_dict['id_card'] == id_card:
return Student.from_dict(student_dict)
return None
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
data = self._load_data()
for student_dict in data:
if student_dict['stu_id'] == stu_id:
return Student.from_dict(student_dict)
return None
def get_all(self) -> List[Student]:
return [Student.from_dict(d) for d in self._load_data()]
def update_student(self, stu_id: str, student: Student) -> bool:
data = self._load_data()
updated = False
for i, student_dict in enumerate(data):
if student_dict['stu_id'] == stu_id:
# 保留原始身份证号
new_data = student.to_dict()
new_data['id_card'] = student_dict['id_card']
data[i] = new_data
updated = True
break
if updated:
self._save_data(data)
return updated
def delete_student(self, stu_id: str) -> bool:
data = self._load_data()
original_count = len(data)
data = [s for s in data if s['stu_id'] != stu_id]
if len(data) < original_count:
self._save_data(data)
return True
return False
def search_by_name(self, name: str) -> List[Student]:
data = self._load_data()
name_lower = name.lower()
return [
Student.from_dict(d)
for d in data
if name_lower in d['name'].lower()
]
def search_by_class(self, class_name: str) -> List[Student]:
data = self._load_data()
class_lower = class_name.lower()
return [
Student.from_dict(d)
for d in data
if d.get('class_name') and class_lower in d['class_name'].lower()
]
def search_by_major(self, major: str) -> List[Student]:
data = self._load_data()
major_lower = major.lower()
return [
Student.from_dict(d)
for d in data
if d.get('major') and major_lower in d['major'].lower()
]
def get_student_count(self) -> int:
return len(self._load_data())
def get_major_counts(self) -> dict:
data = self._load_data()
counts = {}
for student_dict in data:
major = student_dict.get('major', '未指定')
counts[major] = counts.get(major, 0) + 1
return counts
def clear_all(self) -> bool:
self._save_data([])
return True
def export_data(self, file_path: str) -> bool:
data = self._load_data()
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return True
except Exception:
return False
def import_data(self, file_path: str) -> bool:
try:
with open(file_path, 'r', encoding='utf-8') as f:
new_data = json.load(f)
current_data = self._load_data()
current_ids = {s['id_card'] for s in current_data}
# 只导入不重复的学生
to_import = [s for s in new_data if s['id_card'] not in current_ids]
self._save_data(current_data + to_import)
return True
except Exception:
return False

@ -1,241 +0,0 @@
from abc import ABC, abstractmethod
import json
import csv
class IStudentDAL(ABC):
@abstractmethod
def get_by_id(self, id_number):
pass
@abstractmethod
def get_by_stu_id(self, stu_id):
pass
@abstractmethod
def get_all(self):
pass
@abstractmethod
def add(self, student):
pass
@abstractmethod
def delete(self, id_number):
pass
@abstractmethod
def delete_by_stu_id(self, stu_id):
pass
@abstractmethod
def update(self, student):
pass
@abstractmethod
def is_exist(self, id_number):
pass
@abstractmethod
def is_exist_stu_id(self, stu_id):
pass
@abstractmethod
def import_from_json(self, file_path):
pass
@abstractmethod
def export_to_json(self, data, file_path):
pass
@abstractmethod
def import_from_csv(self, file_path):
pass
@abstractmethod
def export_to_csv(self, data, file_path):
pass
class JsonStudentDAL(IStudentDAL):
def __init__(self, file_path):
self.file_path = file_path
self._ensure_file_exists()
self.data = self._load()
def _ensure_file_exists(self):
try:
with open(self.file_path, 'x'):
pass
except FileExistsError:
pass
def _load(self):
try:
with open(self.file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return []
def _save(self):
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(self.data, f, ensure_ascii=False, indent=4)
def get_by_id(self, id_number):
for student in self.data:
if student['id_number'] == id_number:
return student
return None
def get_by_stu_id(self, stu_id):
for student in self.data:
if student['stu_id'] == stu_id:
return student
return None
def get_all(self):
return self.data
def add(self, student):
if self.is_exist(student['id_number']):
return False
self.data.append(student)
self._save()
return True
def delete(self, id_number):
for index, student in enumerate(self.data):
if student['id_number'] == id_number:
del self.data[index]
self._save()
return True
return False
def delete_by_stu_id(self, stu_id):
for index, student in enumerate(self.data):
if student['stu_id'] == stu_id:
del self.data[index]
self._save()
return True
return False
def update(self, student):
for index, existing_student in enumerate(self.data):
if existing_student['id_number'] == student['id_number']:
self.data[index] = student
self._save()
return True
return False
def is_exist(self, id_number):
for student in self.data:
if student['id_number'] == id_number:
return True
return False
def is_exist_stu_id(self, stu_id):
for student in self.data:
if student['stu_id'] == stu_id:
return True
return False
def import_from_json(self, file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return []
def export_to_json(self, data, file_path):
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
return True
except IOError:
return False
class CsvStudentDAL(IStudentDAL):
FIELD_TYPES = {
"id_number": str,
"stu_id": str,
"name": str,
"age": int,
"gender": str
}
def __init__(self, file_path):
self.file_path = file_path
self._ensure_file_exists()
self.data = self._load()
def _ensure_file_exists(self):
try:
with open(self.file_path, 'x', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
writer.writeheader()
except FileExistsError:
pass
def _load(self):
data = []
try:
with open(self.file_path, 'r', newline='', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(self._convert_row(row))
except FileNotFoundError:
pass
return data
def _save(self):
with open(self.file_path, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
writer.writeheader()
for student in self.data:
writer.writerow(student)
def _convert_row(self, row):
converted_row = {}
for key, value in row.items():
try:
converted_row[key] = self.FIELD_TYPES[key](value)
except ValueError:
converted_row[key] = value
return converted_row
def get_by_id(self, id_number):
for student in self.data:
if student['id_number'] == id_number:
return student
return None
def get_by_stu_id(self, stu_id):
for student in self.data:
if student['stu_id'] == stu_id:
return student
return None
def get_all(self):
return self.data
def add(self, student):
if self.is_exist(student['id_number']):
return False
self.data.append(student)
self._save()
return True
def delete(self, id_number):
for index, student in enumerate(self.data):
if student['id_number'] == id_number:
del self.data[index]
self._save()
return True
return False
def delete_by_stu_id(self, stu_id):
for index, student in enumerate(self.data):
if student['stu_id'] == stu_id:
del self.data[index]
self._save()
return True
return False
def update(self, student):
for index, existing_student in enumerate(self.data):
if existing_student['id_number'] == student['id_number']:
self.data[index] = student
self._save()
return True
return False
def is_exist(self, id_number):
for student in self.data:
if student['id_number'] == id_number:
return True
return False
def is_exist_stu_id(self, stu_id):
for student in self.data:
if student['stu_id'] == stu_id:
return True
return False
def import_from_csv(self, file_path):
data = []
try:
with open(file_path, 'r', newline='', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
for row in reader:
data.append(self._convert_row(row))
except FileNotFoundError:
pass
return data
def export_to_csv(self, data, file_path):
try:
with open(file_path, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
writer.writeheader()
for student in data:
writer.writerow(student)
return True
except IOError:
return False

@ -1,438 +1,22 @@
from datetime import date
import os
from typing import List, Dict, Optional, Any, Union
from dal.StudentDAL import StudentDAL
from bll.StudentBLL import StudentBLL
from tui.StudentTUI import StudentTUI
class StudentUI:
"""学生信息管理系统表示层"""
def main():
# 初始化数据访问层
dal = StudentDAL("data/students.json")
def __init__(self, bll):
"""初始化UI层接收业务逻辑层实例"""
self.bll = bll
# 初始化业务逻辑层
bll = StudentBLL(dal)
def display_menu(self) -> None:
"""显示主菜单"""
print("\n" + "=" * 40)
print("学生信息管理系统 - 主菜单")
print("=" * 40)
print("1. 添加学生信息")
print("2. 删除学生信息")
print("3. 更新学生信息")
print("4. 查询学生信息")
print("5. 统计分析")
print("6. 数据导入导出")
print("7. 清空所有学生信息")
print("8. 退出系统")
print("=" * 40)
# 初始化用户界面
tui = StudentTUI(bll)
def display_query_menu(self) -> None:
"""显示查询子菜单"""
print("\n" + "=" * 40)
print("学生信息查询菜单")
print("=" * 40)
print("1. 查询所有学生")
print("2. 按身份证号查询")
print("3. 按学号查询")
print("4. 按姓名查询")
print("5. 按班级查询")
print("6. 返回上一级菜单")
print("=" * 40)
def display_stats_menu(self) -> None:
"""显示统计分析子菜单"""
print("\n" + "=" * 40)
print("学生信息统计分析菜单")
print("=" * 40)
print("1. 学生总数")
print("2. 平均身高")
print("3. 按身高范围统计")
print("4. 按入学年份统计")
print("5. 按年龄范围统计")
print("6. 返回上一级菜单")
print("=" * 40)
def display_import_export_menu(self) -> None:
print("\n" + "=" * 40)
print("数据导入导出菜单")
print("=" * 40)
print("1. 导出数据到JSON")
print("2. 从JSON导入数据")
print("3. 导出数据到CSV")
print("4. 从CSV导入数据")
print("5. 返回上一级菜单")
print("=" * 40)
def get_input(self, prompt: str, required: bool = True) -> Optional[str]:
while True:
value = input(prompt).strip()
if not value and required:
print("输入不能为空,请重新输入")
continue
return value if value else None
def get_numeric_input(self, prompt: str, min_val: Optional[float] = None, max_val: Optional[float] = None) -> \
Optional[float]:
while True:
value = self.get_input(prompt)
if value is None:
return None
try:
num = float(value)
if min_val is not None and num < min_val:
print(f"输入值必须大于等于{min_val}")
continue
if max_val is not None and num > max_val:
print(f"输入值必须小于等于{max_val}")
continue
return num
except ValueError:
print("请输入有效的数字")
def get_date_input(self, prompt: str) -> Optional[date]:
while True:
value = self.get_input(prompt)
if value is None:
return None
try:
return date.fromisoformat(value)
except ValueError:
print("请输入有效的日期YYYY-MM-DD格式")
def add_student(self) -> None:
print("\n" + "=" * 40)
print("添加学生信息")
print("=" * 40)
sid = self.get_input("请输入学号: ")
name = self.get_input("请输入姓名: ")
height = self.get_numeric_input("请输入身高(cm): ", min_val=50, max_val=250)
birth_date = self.get_date_input("请输入出生日期(YYYY-MM-DD): ")
enrollment_date = self.get_date_input("请输入入学日期(YYYY-MM-DD): ")
class_name = self.get_input("请输入班级: ")
student_data = {
'sid': sid,
'name': name,
'height': int(height),
'birth_date': birth_date,
'enrollment_date': enrollment_date,
'class_name': class_name
}
try:
student = Student.from_dict(student_data)
success = self.bll.add(student)
if success:
print("✅ 学生信息添加成功")
else:
print("❌ 学生信息添加失败")
except Exception as e:
print(f"❌ 添加失败: {str(e)}")
def delete_student(self) -> None:
print("\n" + "=" * 40)
print("删除学生信息")
print("=" * 40)
id_number = self.get_input("请输入要删除的学生身份证号: ")
try:
success = self.bll.delete(id_number)
if success:
print("✅ 学生信息删除成功")
else:
print("❌ 学生信息删除失败")
except Exception as e:
print(f"❌ 删除失败: {str(e)}")
def update_student(self) -> None:
print("\n" + "=" * 40)
print("更新学生信息")
print("=" * 40)
id_number = self.get_input("请输入要更新的学生身份证号: ")
try:
student = self.bll.get_by_id(id_number)
if not student:
print(f"❌ 未找到身份证号为{id_number}的学生")
return
print("\n当前学生信息:")
print(f"学号: {student.sid}")
print(f"姓名: {student.name}")
print(f"身高: {student.height}cm")
print(f"出生日期: {student.birth_date}")
print(f"入学日期: {student.enrollment_date}")
print(f"班级: {student.class_name}")
print("\n请输入新信息(直接回车保持原值):")
sid = self.get_input(f"学号 [{student.sid}]: ", required=False) or student.sid
name = self.get_input(f"姓名 [{student.name}]: ", required=False) or student.name
height = self.get_numeric_input(f"身高 [{student.height}]: ", min_val=50, max_val=250)
height = int(height) if height is not None else student.height
birth_date = self.get_date_input(f"出生日期 [{student.birth_date}]: ") or student.birth_date
enrollment_date = self.get_date_input(f"入学日期 [{student.enrollment_date}]: ") or student.enrollment_date
class_name = self.get_input(f"班级 [{student.class_name}]: ", required=False) or student.class_name
updated_data = {
'id_number': id_number,
'sid': sid,
'name': name,
'height': height,
'birth_date': birth_date,
'enrollment_date': enrollment_date,
'class_name': class_name
}
updated_student = Student.from_dict(updated_data)
success = self.bll.update(updated_student)
if success:
print("✅ 学生信息更新成功")
else:
print("❌ 学生信息更新失败")
except Exception as e:
print(f"❌ 更新失败: {str(e)}")
def query_student(self) -> None:
while True:
self.display_query_menu()
choice = self.get_input("请选择操作(1-6): ")
try:
if choice == "1":
students = self.bll.get_all()
self._display_students(students)
elif choice == "2":
id_number = self.get_input("请输入身份证号: ")
student = self.bll.get_by_id(id_number)
if student:
self._display_students([student])
else:
print(f"❌ 未找到身份证号为{id_number}的学生")
elif choice == "3":
stu_id = self.get_input("请输入学号: ")
student = self.bll.get_by_stu_id(stu_id)
if student:
self._display_students([student])
else:
print(f"❌ 未找到学号为{stu_id}的学生")
elif choice == "4":
name = self.get_input("请输入姓名: ")
students = self.bll.get_by_key("name", name, fuzzy=True)
self._display_students(students)
elif choice == "5":
class_name = self.get_input("请输入班级: ")
students = self.bll.get_by_key("class_name", class_name, fuzzy=True)
self._display_students(students)
elif choice == "6":
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 查询失败: {str(e)}")
def _display_students(self, students: List[Student]) -> None:
if not students:
print("未找到符合条件的学生")
return
print("\n" + "-" * 80)
print(f"{'学号':<12}{'姓名':<12}{'身高(cm)':<10}{'出生日期':<12}{'入学日期':<12}{'班级':<12}")
print("-" * 80)
for student in students:
print(
f"{student.sid:<12}{student.name:<12}{student.height:<10}{student.birth_date:<12}{student.enrollment_date:<12}{student.class_name:<12}")
print("-" * 80)
print(f"共找到 {len(students)} 条记录")
def show_stats(self) -> None:
while True:
self.display_stats_menu()
choice = self.get_input("请选择操作(1-6): ")
try:
if choice == "1":
students = self.bll.get_all()
print(f"学生总数: {len(students)}")
elif choice == "2":
students = self.bll.get_all()
if not students:
print("暂无学生数据")
continue
avg_height = sum(s.height for s in students) / len(students)
print(f"平均身高: {avg_height:.2f}cm")
elif choice == "3":
min_height = self.get_numeric_input("请输入最小身高(cm): ", min_val=50)
max_height = self.get_numeric_input("请输入最大身高(cm): ", min_val=min_height)
students = self.bll.get_by_range("height", min_height, max_height)
print(f"身高在 {min_height}-{max_height}cm 之间的学生有 {len(students)}")
elif choice == "4":
students = self.bll.get_all()
if not students:
print("暂无学生数据")
continue
year_count = {}
for s in students:
year = s.enrollment_date.year
year_count[year] = year_count.get(year, 0) + 1
print("\n按入学年份统计:")
for year, count in sorted(year_count.items()):
print(f"{year}年: {count}")
elif choice == "5":
today = date.today()
min_age = int(self.get_numeric_input("请输入最小年龄: ", min_val=0))
max_age = int(self.get_numeric_input("请输入最大年龄: ", min_val=min_age))
students = self.bll.get_all()
count = 0
for s in students:
age = today.year - s.birth_date.year
if s.birth_date.month > today.month or (
s.birth_date.month == today.month and s.birth_date.day > today.day):
age -= 1
if min_age <= age <= max_age:
count += 1
print(f"年龄在 {min_age}-{max_age} 岁之间的学生有 {count}")
elif choice == "6":
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 统计失败: {str(e)}")
def import_export_data(self) -> None:
while True:
self.display_import_export_menu()
choice = self.get_input("请选择操作(1-5): ")
try:
if choice == "1":
file_path = self.get_input("请输入导出文件名: ", required=False) or "students.json"
if not file_path.endswith('.json'):
file_path += '.json'
success = self.bll.export_to_json(file_path)
if success:
print(f"✅ 数据已成功导出到 {file_path}")
else:
print(f"❌ 数据导出失败")
elif choice == "2":
file_path = self.get_input("请输入导入文件名: ", required=False) or "students.json"
if not file_path.endswith('.json'):
file_path += '.json'
if not os.path.exists(file_path):
print(f"❌ 文件 {file_path} 不存在")
continue
errors = self.bll.import_from_json(file_path)
if not errors:
print(f"✅ 数据已成功导入")
else:
print(f"⚠️ 数据导入完成,但有 {len(errors)} 条记录失败:")
for error in errors:
print(f" - {error}")
elif choice == "3":
file_path = self.get_input("请输入导出文件名: ", required=False) or "students.csv"
if not file_path.endswith('.csv'):
file_path += '.csv'
success = self.bll.export_to_csv(file_path)
if success:
print(f"✅ 数据已成功导出到 {file_path}")
else:
print(f"❌ 数据导出失败")
elif choice == "4":
file_path = self.get_input("请输入导入文件名: ", required=False) or "students.csv"
if not file_path.endswith('.csv'):
file_path += '.csv'
if not os.path.exists(file_path):
print(f"❌ 文件 {file_path} 不存在")
continue
errors = self.bll.import_from_csv(file_path)
if not errors:
print(f"✅ 数据已成功导入")
else:
print(f"⚠️ 数据导入完成,但有 {len(errors)} 条记录失败:")
for error in errors:
print(f" - {error}")
elif choice == "5":
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 操作失败: {str(e)}")
def clear_students(self) -> None:
print("\n" + "=" * 40)
print("清空所有学生信息")
print("=" * 40)
confirm = self.get_input("警告:此操作将删除所有学生信息,且无法恢复。是否继续?(y/n): ")
if confirm.lower() != 'y':
print("❌ 操作已取消")
return
try:
success = self.bll.clear_all()
if success:
print("✅ 所有学生信息已清空")
else:
print("❌ 清空操作失败")
except Exception as e:
print(f"❌ 清空失败: {str(e)}")
def run(self) -> None:
print("\n" + "=" * 40)
print("欢迎使用学生信息管理系统")
print("=" * 40)
while True:
self.display_menu()
choice = self.get_input("请选择操作(1-8): ")
try:
if choice == "1":
self.add_student()
elif choice == "2":
self.delete_student()
elif choice == "3":
self.update_student()
elif choice == "4":
self.query_student()
elif choice == "5":
self.show_stats()
elif choice == "6":
self.import_export_data()
elif choice == "7":
self.clear_students()
elif choice == "8":
confirm = self.get_input("确定要退出系统吗?(y/n): ")
if confirm.lower() == 'y':
print("感谢使用学生信息管理系统,再见!")
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 操作失败: {str(e)}")
finally:
input("\n按回车键继续...")
# 启动系统
tui.run()
if __name__ == "__main__":
main()
data_file = input("请输入数据文件路径默认students.json: ") or "students.json"
bll = StudentBLL(data_file)
ui = StudentUI(bll)
ui.run()

@ -0,0 +1,123 @@
import csv
import json
import os
import sqlite3
from abc import ABC, abstractmethod
from datetime import date
from typing import List, Optional, Union
# 学生类定义
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: Optional[Union[date, str]] = None,
class_name: Optional[str] = None,
major: str = None):
self.name = name
self.id_card = id_card
self.stu_id = stu_id
self.gender = gender
self.height = height
self.weight = weight
if isinstance(enrollment_date, str):
self.enrollment_date = date.fromisoformat(enrollment_date)
else:
self.enrollment_date = enrollment_date
self.class_name = class_name
self.major = major
self.age, self.birthday = self._calculate_age_and_birthday()
self._validation_errors = []
self._validate_all()
def _calculate_age_and_birthday(self):
birth_year = int(self.id_card[6:10])
birth_month = int(self.id_card[10:12])
birth_day = int(self.id_card[12:14])
birthday = date(birth_year, birth_month, birth_day)
today = date.today()
age = today.year - birth_year
if (today.month, today.day) < (birth_month, birth_day):
age -= 1
return age, birthday
def _validate_all(self):
self._validate_id_card()
self._validate_stu_id()
self._validate_name()
self._validate_enrollment_date()
self._validate_height()
self._validate_weight()
def _validate_id_card(self):
if len(self.id_card) != 18:
self._validation_errors.append("身份证号必须为18位")
# 简单的校验位验证,可根据国家标准完善
try:
int(self.id_card[:17])
except ValueError:
self._validation_errors.append("身份证号前17位必须为数字")
def _validate_stu_id(self):
if not self.stu_id:
self._validation_errors.append("学号不能为空")
def _validate_name(self):
if not (2 <= len(self.name) <= 20):
self._validation_errors.append("姓名长度需在2-20个字符之间")
if any(char.isdigit() or not char.isalpha() for char in self.name):
self._validation_errors.append("姓名不能包含数字和特殊符号")
def _validate_enrollment_date(self):
if self.enrollment_date and self.enrollment_date < self.birthday:
self._validation_errors.append("入学日期不能早于出生日期")
def _validate_height(self):
if self.height is not None and not (50 <= self.height <= 250):
self._validation_errors.append("身高需在50-250cm之间")
def _validate_weight(self):
if self.weight is not None and not (5 <= self.weight <= 300):
self._validation_errors.append("体重需在5-300kg之间")
@property
def is_valid(self):
return len(self._validation_errors) == 0
def get_errors(self):
return self._validation_errors.copy()
def to_dict(self):
return {
'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,
'age': self.age,
'birthday': self.birthday.isoformat()
}
@classmethod
def from_dict(cls, data):
return cls(
name=data['name'],
id_card=data['id_card'],
stu_id=data['stu_id'],
gender=data['gender'],
height=data['height'],
weight=data['weight'],
enrollment_date=data['enrollment_date'],
class_name=data['class_name'],
major=data['major']
)

@ -1,112 +0,0 @@
from datetime import date
from typing import Optional, Dict, Any
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: Optional[str] = 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
if enrollment_date:
if isinstance(enrollment_date, str):
self.enrollment_date = date.fromisoformat(enrollment_date)
else:
self.enrollment_date = enrollment_date
else:
self.enrollment_date = None
self.class_name = class_name
self.major = major
@property
def birthday(self) -> Optional[date]:
if not self.id_card or len(self.id_card) != 18:
return None
try:
birth_str = self.id_card[6:14]
return date(int(birth_str[:4]), int(birth_str[4:6]), int(birth_str[6:8]))
except:
return None
@property
def age(self) -> Optional[int]:
if not self.birthday:
return None
today = date.today()
age = today.year - self.birthday.year
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def errors(self) -> Dict[str, str]:
errors = {}
id_card_err = self.__validate_id_card(self.id_card)
if id_card_err:
errors["id_card"] = id_card_err
if len(self.name) < 2 or len(self.name) > 20:
errors["name"] = "姓名长度需在2-20字符之间"
if self.height and not (50 <= self.height <= 250):
errors["height"] = "身高需在50-250cm之间"
if self.weight and not (5.0 <= self.weight <= 300.0):
errors["weight"] = "体重需在5-300kg之间"
if self.enrollment_date and self.enrollment_date > date.today():
errors["enrollment_date"] = "入学日期不能晚于当前日期"
return errors
@property
def is_valid(self) -> bool:
return not bool(self.errors)
def to_dict(self) -> Dict[str, Any]:
data = self.__dict__.copy()
if data.get("enrollment_date"):
data["enrollment_date"] = data["enrollment_date"].isoformat()
return data
@classmethod
def from_dict(cls, data: Dict[str, Any]) -> 'Student':
if not isinstance(data, dict):
raise TypeError("输入必须为字典类型")
enrollment_date = data.get("enrollment_date")
if enrollment_date and isinstance(enrollment_date, str):
data["enrollment_date"] = date.fromisoformat(enrollment_date)
return cls(**data)
def __repr__(self) -> str:
attrs = ", ".join([
f"{k}='{v}'" if isinstance(v, str) else f"{k}={v}"
for k, v in self.__dict__.items()
])
return f"Student({attrs})"
@staticmethod
def get_properties() -> list:
return [k for k, v in vars(Student).items() if isinstance(v, property)]
@staticmethod
def __validate_id_card(id_card: str) -> Optional[str]:
if not id_card or len(id_card) != 18:
return "身份证号长度必须为18位"
if not id_card[:17].isdigit():
return "身份证号前17位必须为数字"
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
check_codes = "10X98765432"
sum_val = sum(int(id_card[i]) * weights[i] for i in range(17))
if id_card[17] != check_codes[sum_val % 11]:
return "身份证号校验码错误"
try:
birth_str = id_card[6:14]
date(int(birth_str[:4]), int(birth_str[4:6]), int(birth_str[6:8]))
except:
return "身份证号中出生日期格式错误"
return None

@ -0,0 +1,292 @@
from datetime import date
from bll.StudentBLL import StudentBLL
from dal.StudentDAL import StudentDAL
from student.Student import Student
class StudentTUI:
def __init__(self, bll: StudentBLL):
self.bll = bll
def display_menu(self):
print("\n===== 学生信息管理系统 =====")
print("1. 添加学生")
print("2. 删除学生")
print("3. 更新学生")
print("4. 查询学生")
print("5. 统计分析")
print("6. 导入导出")
print("7. 清空数据")
print("0. 退出系统")
print("==========================")
def run(self):
while True:
self.display_menu()
choice = input("请选择操作: ").strip()
if choice == "0":
print("再见!")
break
elif 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.show_stats()
elif choice == "6":
self.import_export()
elif choice == "7":
self.clear_data()
else:
print("无效选择,请重试")
def add_student(self):
print("\n--- 添加学生 ---")
name = input("姓名: ").strip()
id_card = input("身份证号: ").strip()
stu_id = input("学号: ").strip()
gender = self.input_gender()
height = self.input_int("身高(cm): ")
weight = self.input_float("体重(kg): ")
enrollment_date = self.input_date("入学日期(YYYY-MM-DD): ")
class_name = input("班级: ").strip()
major = input("专业: ").strip()
try:
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
)
self.bll.add_student(student)
print("添加成功!")
except ValueError as e:
print(f"添加失败: {e}")
def input_gender(self):
"""输入性别"""
while True:
gender = input("性别(1.男 2.女 3.跳过): ").strip()
if gender == '1':
return True
elif gender == '2':
return False
elif gender == '3':
return None
else:
print("无效选择,请重新输入")
def input_int(self, prompt: str):
"""输入整数"""
while True:
value = input(prompt).strip()
try:
return int(value) if value else None
except ValueError:
print("请输入有效的整数")
def input_float(self, prompt: str):
"""输入浮点数"""
while True:
value = input(prompt).strip()
try:
return float(value) if value else None
except ValueError:
print("请输入有效的数字")
def input_date(self, prompt: str):
"""输入日期"""
while True:
try:
date_str = input(prompt).strip()
if not date_str:
return None
return date.fromisoformat(date_str)
except ValueError:
print("日期格式应为YYYY-MM-DD")
def delete_student(self):
print("\n--- 删除学生 ---")
stu_id = input("请输入学号: ").strip()
try:
self.bll.delete_student(stu_id)
print("删除成功!")
except ValueError as e:
print(f"删除失败: {e}")
def update_student(self):
print("\n--- 更新学生 ---")
stu_id = input("请输入学号: ").strip()
student = self.bll.get_student_by_stu_id(stu_id)
if not student:
print("学生不存在")
return
print(f"当前信息: {student}")
name = input(f"新姓名({student.name}): ").strip() or student.name
id_card = input(f"新身份证号({student.id_card}): ").strip() or student.id_card
new_stu_id = input(f"新学号({student.stu_id}): ").strip() or student.stu_id
gender = self.input_gender_update(student.gender)
height = self.input_int(f"新身高({student.height}): ") or student.height
weight = self.input_float(f"新体重({student.weight}): ") or student.weight
enrollment_date = self.input_date_update(student.enrollment_date)
class_name = input(f"新班级({student.class_name}): ").strip() or student.class_name
major = input(f"新专业({student.major}): ").strip() or student.major
try:
updated_student = Student(
name=name,
id_card=id_card,
stu_id=new_stu_id,
gender=gender,
height=height,
weight=weight,
enrollment_date=enrollment_date,
class_name=class_name,
major=major
)
# 如果学号改变了,需要先删除旧记录
if new_stu_id != stu_id:
self.bll.delete_student(stu_id)
self.bll.add_student(updated_student)
print("更新成功!")
except ValueError as e:
print(f"更新失败: {e}")
def input_gender_update(self, current_gender):
current = "" if current_gender is True else "" if current_gender is False else "未指定"
print(f"当前性别: {current}")
return self.input_gender()
def input_date_update(self, current_date):
"""输入日期(更新时)"""
if current_date:
print(f"当前日期: {current_date.isoformat()}")
return self.input_date("新的日期(YYYY-MM-DD): ") or current_date
def query_students(self):
print("\n--- 查询学生 ---")
print("1. 按身份证号查询")
print("2. 按学号查询")
print("3. 按姓名查询")
print("4. 按班级查询")
print("5. 按专业查询")
print("6. 所有学生")
choice = input("请选择查询方式: ").strip()
students = []
if choice == "1":
id_card = input("请输入身份证号: ").strip()
student = self.bll.get_student_by_id_card(id_card)
if student:
students = [student]
elif choice == "2":
stu_id = input("请输入学号: ").strip()
student = self.bll.get_student_by_stu_id(stu_id)
if student:
students = [student]
elif choice == "3":
name = input("请输入姓名: ").strip()
students = self.bll.search_students('name', name)
elif choice == "4":
class_name = input("请输入班级: ").strip()
students = self.bll.search_students('class', class_name)
elif choice == "5":
major = input("请输入专业: ").strip()
students = self.bll.search_students('major', major)
elif choice == "6":
students = self.bll.get_all_students()
else:
print("无效选择")
return
self.print_students(students)
def print_students(self, students):
if not students:
print("没有找到学生")
return
print("\n身份证号\t学号\t姓名\t性别\t身高\t体重\t班级\t专业\t年龄")
print("=" * 100)
for s in students:
gender = "" if s.gender is True else "" if s.gender is False else "未知"
print(f"{s.id_card}\t{s.stu_id}\t{s.name}\t{gender}\t"
f"{s.height or '--'}\t{s.weight or '--'}\t"
f"{s.class_name or '--'}\t{s.major or '--'}\t"
f"{s.age or '--'}")
print(f"共找到 {len(students)} 名学生")
def show_stats(self):
print("\n--- 统计分析 ---")
print("1. 学生总数")
print("2. 专业分布")
print("3. 平均身高")
print("4. 平均体重")
print("5. 按年龄范围查询")
choice = input("请选择统计项目: ").strip()
if choice == "1":
count = self.bll.get_student_count()
print(f"学生总数: {count}")
elif choice == "2":
major_counts = self.bll.get_major_counts()
print("\n专业分布:")
for major, count in major_counts.items():
print(f" {major}: {count}")
elif choice == "3":
avg_height = self.bll.calculate_avg_height()
print(f"平均身高: {avg_height:.1f} cm")
elif choice == "4":
avg_weight = self.bll.calculate_avg_weight()
print(f"平均体重: {avg_weight:.1f} kg")
elif choice == "5":
min_age = int(input("最小年龄: "))
max_age = int(input("最大年龄: "))
students = self.bll.get_students_by_age(min_age, max_age)
self.print_students(students)
else:
print("无效选择")
def import_export(self):
print("\n--- 导入导出 ---")
print("1. 导出数据")
print("2. 导入数据")
choice = input("请选择操作: ").strip()
if choice == "1":
file_path = input("导出文件路径: ").strip()
if self.bll.export_data(file_path):
print("导出成功!")
else:
print("导出失败")
elif choice == "2":
file_path = input("导入文件路径: ").strip()
if self.bll.import_data(file_path):
print("导入成功!")
else:
print("导入失败")
else:
print("无效选择")
def clear_data(self):
confirm = input("确定要清空所有数据吗?(y/n): ").strip().lower()
if confirm == 'y':
if self.bll.clear_all():
print("数据已清空")
else:
print("清空失败")

@ -1,438 +0,0 @@
from datetime import date
import os
from typing import List, Dict, Optional, Any, Union
class StudentUI:
"""学生信息管理系统表示层"""
def __init__(self, bll):
"""初始化UI层接收业务逻辑层实例"""
self.bll = bll
def display_menu(self) -> None:
"""显示主菜单"""
print("\n" + "=" * 40)
print("学生信息管理系统 - 主菜单")
print("=" * 40)
print("1. 添加学生信息")
print("2. 删除学生信息")
print("3. 更新学生信息")
print("4. 查询学生信息")
print("5. 统计分析")
print("6. 数据导入导出")
print("7. 清空所有学生信息")
print("8. 退出系统")
print("=" * 40)
def display_query_menu(self) -> None:
"""显示查询子菜单"""
print("\n" + "=" * 40)
print("学生信息查询菜单")
print("=" * 40)
print("1. 查询所有学生")
print("2. 按身份证号查询")
print("3. 按学号查询")
print("4. 按姓名查询")
print("5. 按班级查询")
print("6. 返回上一级菜单")
print("=" * 40)
def display_stats_menu(self) -> None:
"""显示统计分析子菜单"""
print("\n" + "=" * 40)
print("学生信息统计分析菜单")
print("=" * 40)
print("1. 学生总数")
print("2. 平均身高")
print("3. 按身高范围统计")
print("4. 按入学年份统计")
print("5. 按年龄范围统计")
print("6. 返回上一级菜单")
print("=" * 40)
def display_import_export_menu(self) -> None:
print("\n" + "=" * 40)
print("数据导入导出菜单")
print("=" * 40)
print("1. 导出数据到JSON")
print("2. 从JSON导入数据")
print("3. 导出数据到CSV")
print("4. 从CSV导入数据")
print("5. 返回上一级菜单")
print("=" * 40)
def get_input(self, prompt: str, required: bool = True) -> Optional[str]:
while True:
value = input(prompt).strip()
if not value and required:
print("输入不能为空,请重新输入")
continue
return value if value else None
def get_numeric_input(self, prompt: str, min_val: Optional[float] = None, max_val: Optional[float] = None) -> \
Optional[float]:
while True:
value = self.get_input(prompt)
if value is None:
return None
try:
num = float(value)
if min_val is not None and num < min_val:
print(f"输入值必须大于等于{min_val}")
continue
if max_val is not None and num > max_val:
print(f"输入值必须小于等于{max_val}")
continue
return num
except ValueError:
print("请输入有效的数字")
def get_date_input(self, prompt: str) -> Optional[date]:
while True:
value = self.get_input(prompt)
if value is None:
return None
try:
return date.fromisoformat(value)
except ValueError:
print("请输入有效的日期YYYY-MM-DD格式")
def add_student(self) -> None:
print("\n" + "=" * 40)
print("添加学生信息")
print("=" * 40)
sid = self.get_input("请输入学号: ")
name = self.get_input("请输入姓名: ")
height = self.get_numeric_input("请输入身高(cm): ", min_val=50, max_val=250)
birth_date = self.get_date_input("请输入出生日期(YYYY-MM-DD): ")
enrollment_date = self.get_date_input("请输入入学日期(YYYY-MM-DD): ")
class_name = self.get_input("请输入班级: ")
student_data = {
'sid': sid,
'name': name,
'height': int(height),
'birth_date': birth_date,
'enrollment_date': enrollment_date,
'class_name': class_name
}
try:
student = Student.from_dict(student_data)
success = self.bll.add(student)
if success:
print("✅ 学生信息添加成功")
else:
print("❌ 学生信息添加失败")
except Exception as e:
print(f"❌ 添加失败: {str(e)}")
def delete_student(self) -> None:
print("\n" + "=" * 40)
print("删除学生信息")
print("=" * 40)
id_number = self.get_input("请输入要删除的学生身份证号: ")
try:
success = self.bll.delete(id_number)
if success:
print("✅ 学生信息删除成功")
else:
print("❌ 学生信息删除失败")
except Exception as e:
print(f"❌ 删除失败: {str(e)}")
def update_student(self) -> None:
print("\n" + "=" * 40)
print("更新学生信息")
print("=" * 40)
id_number = self.get_input("请输入要更新的学生身份证号: ")
try:
student = self.bll.get_by_id(id_number)
if not student:
print(f"❌ 未找到身份证号为{id_number}的学生")
return
print("\n当前学生信息:")
print(f"学号: {student.sid}")
print(f"姓名: {student.name}")
print(f"身高: {student.height}cm")
print(f"出生日期: {student.birth_date}")
print(f"入学日期: {student.enrollment_date}")
print(f"班级: {student.class_name}")
print("\n请输入新信息(直接回车保持原值):")
sid = self.get_input(f"学号 [{student.sid}]: ", required=False) or student.sid
name = self.get_input(f"姓名 [{student.name}]: ", required=False) or student.name
height = self.get_numeric_input(f"身高 [{student.height}]: ", min_val=50, max_val=250)
height = int(height) if height is not None else student.height
birth_date = self.get_date_input(f"出生日期 [{student.birth_date}]: ") or student.birth_date
enrollment_date = self.get_date_input(f"入学日期 [{student.enrollment_date}]: ") or student.enrollment_date
class_name = self.get_input(f"班级 [{student.class_name}]: ", required=False) or student.class_name
updated_data = {
'id_number': id_number,
'sid': sid,
'name': name,
'height': height,
'birth_date': birth_date,
'enrollment_date': enrollment_date,
'class_name': class_name
}
updated_student = Student.from_dict(updated_data)
success = self.bll.update(updated_student)
if success:
print("✅ 学生信息更新成功")
else:
print("❌ 学生信息更新失败")
except Exception as e:
print(f"❌ 更新失败: {str(e)}")
def query_student(self) -> None:
while True:
self.display_query_menu()
choice = self.get_input("请选择操作(1-6): ")
try:
if choice == "1":
students = self.bll.get_all()
self._display_students(students)
elif choice == "2":
id_number = self.get_input("请输入身份证号: ")
student = self.bll.get_by_id(id_number)
if student:
self._display_students([student])
else:
print(f"❌ 未找到身份证号为{id_number}的学生")
elif choice == "3":
stu_id = self.get_input("请输入学号: ")
student = self.bll.get_by_stu_id(stu_id)
if student:
self._display_students([student])
else:
print(f"❌ 未找到学号为{stu_id}的学生")
elif choice == "4":
name = self.get_input("请输入姓名: ")
students = self.bll.get_by_key("name", name, fuzzy=True)
self._display_students(students)
elif choice == "5":
class_name = self.get_input("请输入班级: ")
students = self.bll.get_by_key("class_name", class_name, fuzzy=True)
self._display_students(students)
elif choice == "6":
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 查询失败: {str(e)}")
def _display_students(self, students: List[Student]) -> None:
if not students:
print("未找到符合条件的学生")
return
print("\n" + "-" * 80)
print(f"{'学号':<12}{'姓名':<12}{'身高(cm)':<10}{'出生日期':<12}{'入学日期':<12}{'班级':<12}")
print("-" * 80)
for student in students:
print(
f"{student.sid:<12}{student.name:<12}{student.height:<10}{student.birth_date:<12}{student.enrollment_date:<12}{student.class_name:<12}")
print("-" * 80)
print(f"共找到 {len(students)} 条记录")
def show_stats(self) -> None:
while True:
self.display_stats_menu()
choice = self.get_input("请选择操作(1-6): ")
try:
if choice == "1":
students = self.bll.get_all()
print(f"学生总数: {len(students)}")
elif choice == "2":
students = self.bll.get_all()
if not students:
print("暂无学生数据")
continue
avg_height = sum(s.height for s in students) / len(students)
print(f"平均身高: {avg_height:.2f}cm")
elif choice == "3":
min_height = self.get_numeric_input("请输入最小身高(cm): ", min_val=50)
max_height = self.get_numeric_input("请输入最大身高(cm): ", min_val=min_height)
students = self.bll.get_by_range("height", min_height, max_height)
print(f"身高在 {min_height}-{max_height}cm 之间的学生有 {len(students)}")
elif choice == "4":
students = self.bll.get_all()
if not students:
print("暂无学生数据")
continue
year_count = {}
for s in students:
year = s.enrollment_date.year
year_count[year] = year_count.get(year, 0) + 1
print("\n按入学年份统计:")
for year, count in sorted(year_count.items()):
print(f"{year}年: {count}")
elif choice == "5":
today = date.today()
min_age = int(self.get_numeric_input("请输入最小年龄: ", min_val=0))
max_age = int(self.get_numeric_input("请输入最大年龄: ", min_val=min_age))
students = self.bll.get_all()
count = 0
for s in students:
age = today.year - s.birth_date.year
if s.birth_date.month > today.month or (
s.birth_date.month == today.month and s.birth_date.day > today.day):
age -= 1
if min_age <= age <= max_age:
count += 1
print(f"年龄在 {min_age}-{max_age} 岁之间的学生有 {count}")
elif choice == "6":
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 统计失败: {str(e)}")
def import_export_data(self) -> None:
while True:
self.display_import_export_menu()
choice = self.get_input("请选择操作(1-5): ")
try:
if choice == "1":
file_path = self.get_input("请输入导出文件名: ", required=False) or "students.json"
if not file_path.endswith('.json'):
file_path += '.json'
success = self.bll.export_to_json(file_path)
if success:
print(f"✅ 数据已成功导出到 {file_path}")
else:
print(f"❌ 数据导出失败")
elif choice == "2":
file_path = self.get_input("请输入导入文件名: ", required=False) or "students.json"
if not file_path.endswith('.json'):
file_path += '.json'
if not os.path.exists(file_path):
print(f"❌ 文件 {file_path} 不存在")
continue
errors = self.bll.import_from_json(file_path)
if not errors:
print(f"✅ 数据已成功导入")
else:
print(f"⚠️ 数据导入完成,但有 {len(errors)} 条记录失败:")
for error in errors:
print(f" - {error}")
elif choice == "3":
file_path = self.get_input("请输入导出文件名: ", required=False) or "students.csv"
if not file_path.endswith('.csv'):
file_path += '.csv'
success = self.bll.export_to_csv(file_path)
if success:
print(f"✅ 数据已成功导出到 {file_path}")
else:
print(f"❌ 数据导出失败")
elif choice == "4":
file_path = self.get_input("请输入导入文件名: ", required=False) or "students.csv"
if not file_path.endswith('.csv'):
file_path += '.csv'
if not os.path.exists(file_path):
print(f"❌ 文件 {file_path} 不存在")
continue
errors = self.bll.import_from_csv(file_path)
if not errors:
print(f"✅ 数据已成功导入")
else:
print(f"⚠️ 数据导入完成,但有 {len(errors)} 条记录失败:")
for error in errors:
print(f" - {error}")
elif choice == "5":
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 操作失败: {str(e)}")
def clear_students(self) -> None:
print("\n" + "=" * 40)
print("清空所有学生信息")
print("=" * 40)
confirm = self.get_input("警告:此操作将删除所有学生信息,且无法恢复。是否继续?(y/n): ")
if confirm.lower() != 'y':
print("❌ 操作已取消")
return
try:
success = self.bll.clear_all()
if success:
print("✅ 所有学生信息已清空")
else:
print("❌ 清空操作失败")
except Exception as e:
print(f"❌ 清空失败: {str(e)}")
def run(self) -> None:
print("\n" + "=" * 40)
print("欢迎使用学生信息管理系统")
print("=" * 40)
while True:
self.display_menu()
choice = self.get_input("请选择操作(1-8): ")
try:
if choice == "1":
self.add_student()
elif choice == "2":
self.delete_student()
elif choice == "3":
self.update_student()
elif choice == "4":
self.query_student()
elif choice == "5":
self.show_stats()
elif choice == "6":
self.import_export_data()
elif choice == "7":
self.clear_students()
elif choice == "8":
confirm = self.get_input("确定要退出系统吗?(y/n): ")
if confirm.lower() == 'y':
print("感谢使用学生信息管理系统,再见!")
break
else:
print("❌ 无效选择,请重新输入")
except Exception as e:
print(f"❌ 操作失败: {str(e)}")
finally:
input("\n按回车键继续...")
if __name__ == "__main__":
data_file = input("请输入数据文件路径默认students.json: ") or "students.json"
bll = StudentBLL(data_file)
ui = StudentUI(bll)
ui.run()
Loading…
Cancel
Save