新建项目

master
孟天翔 1 month ago
commit 4c1470a028

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.11 (PyCharmMiscProject)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/PythonProject1.iml" filepath="$PROJECT_DIR$/.idea/PythonProject1.iml" />
</modules>
</component>
</project>

@ -0,0 +1,373 @@
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,241 @@
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

@ -0,0 +1,438 @@
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()

@ -0,0 +1,112 @@
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,438 @@
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