master
commit
ef8573eb42
@ -0,0 +1,3 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.12 (python2)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (python2)" project-jdk-type="Python SDK" />
|
||||
</project>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/python2.iml" filepath="$PROJECT_DIR$/.idea/python2.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv1" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.12 (python2)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,92 @@
|
||||
from datetime 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()
|
@ -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
|
@ -0,0 +1,21 @@
|
||||
from dal.StudentDAL import StudentDAL
|
||||
from bll.StudentBLL import StudentBLL
|
||||
from tui.StudentTUI import StudentTUI
|
||||
|
||||
|
||||
def main():
|
||||
# 初始化数据访问层
|
||||
dal = StudentDAL("data/students.json")
|
||||
|
||||
# 初始化业务逻辑层
|
||||
bll = StudentBLL(dal)
|
||||
|
||||
# 初始化用户界面
|
||||
tui = StudentTUI(bll)
|
||||
|
||||
# 启动系统
|
||||
tui.run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
@ -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("清空失败")
|
Loading…
Reference in new issue