commit 1018878da40a0234fce20524a85de710336c7370 Author: 员卓豪 <2232041467@qq.com> Date: Wed Jun 25 10:48:35 2025 +0800 提交项目 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..e05e84c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a89ab40 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/script.py b/script.py new file mode 100644 index 0000000..aa31e90 --- /dev/null +++ b/script.py @@ -0,0 +1,259 @@ +import csv +import json +import sqlite3 +from abc import ABC, abstractmethod +from datetime import date +from typing import List, Optional, Dict, Any, 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: Optional[str] = None): + self.name = name + self.id_card = id_card + self.stu_id = stu_id + self.gender = gender + self.height = height + self.weight = weight + self.enrollment_date = date.fromisoformat(enrollment_date) if isinstance(enrollment_date, + str) else 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) -> tuple[int, date]: + birth_year, birth_month, birth_day = map(int, (self.id_card[6:10], self.id_card[10:12], self.id_card[12:14])) + birthday = date(birth_year, birth_month, birth_day) + today = date.today() + age = today.year - birth_year - ((today.month, today.day) < (birth_month, birth_day)) + return age, birthday + + def _validate_all(self) -> List[str]: + errors = [] + if len(self.id_card) != 18 or not self.id_card[:17].isdigit(): + errors.append("身份证号必须为18位数字") + if not self.stu_id: + errors.append("学号不能为空") + if not (2 <= len(self.name) <= 20) or not self.name.isalpha(): + errors.append("姓名需为2-20个字母") + if self.enrollment_date and self.enrollment_date < self.birthday: + errors.append("入学日期不能早于出生日期") + if self.height and not 50 <= self.height <= 250: + errors.append("身高需在50-250cm之间") + if self.weight and not 5 <= self.weight <= 300: + errors.append("体重需在5-300kg之间") + return errors + + @property + def is_valid(self) -> bool: + return not self._validation_errors + + def to_dict(self) -> Dict[str, Any]: + 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: Dict[str, Any]) -> 'Student': + return cls(**data) + + +class IStudentDAL(ABC): + @abstractmethod + def get_by_id(self, id_card: str) -> Optional[Student]: ... + + @abstractmethod + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: ... + + @abstractmethod + def get_all(self) -> List[Student]: ... + + @abstractmethod + def add(self, student: Student) -> bool: ... + + @abstractmethod + def delete(self, id_card: str = None, stu_id: str = None) -> bool: ... + + @abstractmethod + def update(self, student: Student) -> bool: ... + + +class BaseDAL(IStudentDAL): + def __init__(self, file_path: str): + self.file_path = file_path + self._init_storage() + + def _init_storage(self): pass + + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + return next((s for s in self.get_all() if s.stu_id == stu_id), None) + + +class CSVStudentDAL(BaseDAL): + def _init_storage(self): + with open(self.file_path, 'a') as f: pass # Ensure file exists + + def get_all(self) -> List[Student]: + with open(self.file_path, 'r') as f: + return [Student.from_dict(row) for row in csv.DictReader(f)] + + def add(self, student: Student) -> bool: + if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id): + return False + with open(self.file_path, 'a', newline='') as f: + writer = csv.DictWriter(f, fieldnames=student.to_dict().keys()) + if f.tell() == 0: writer.writeheader() + writer.writerow(student.to_dict()) + return True + + def delete(self, id_card: str = None, stu_id: str = None) -> bool: + students = [s for s in self.get_all() + if not ((id_card and s.id_card == id_card) or (stu_id and s.stu_id == stu_id))] + with open(self.file_path, 'w', newline='') as f: + if students: + writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys()) + writer.writeheader() + writer.writerows(s.to_dict() for s in students) + return len(students) < len(self.get_all()) + + +class JSONStudentDAL(BaseDAL): + def _init_storage(self): + with open(self.file_path, 'a') as f: pass + + def get_all(self) -> List[Student]: + with open(self.file_path, 'r') as f: + return [Student.from_dict(row) for row in json.load(f)] + + def add(self, student: Student) -> bool: + data = self.get_all() + if any(s.id_card == student.id_card or s.stu_id == student.stu_id for s in data): + return False + data.append(student) + self._save_all(data) + return True + + def _save_all(self, data: List[Student]): + with open(self.file_path, 'w') as f: + json.dump([s.to_dict() for s in data], f, indent=2) + + +class SQLiteStudentDAL(BaseDAL): + def _init_storage(self): + with sqlite3.connect(self.file_path) as conn: + conn.execute(''' + CREATE TABLE IF NOT EXISTS students ( + id_card TEXT PRIMARY KEY, stu_id TEXT UNIQUE, name TEXT, + gender BOOLEAN, height INTEGER, weight REAL, + enrollment_date TEXT, class_name TEXT, major TEXT, + age INTEGER, birthday TEXT)''') + + def get_all(self) -> List[Student]: + with sqlite3.connect(self.file_path) as conn: + rows = conn.execute("SELECT * FROM students").fetchall() + return [Student.from_dict(dict(zip(('id_card', 'stu_id', 'name', 'gender', 'height', + 'weight', 'enrollment_date', 'class_name', 'major', + 'age', 'birthday'), row))) for row in rows] + + def add(self, student: Student) -> bool: + try: + with sqlite3.connect(self.file_path) as conn: + conn.execute(''' + INSERT INTO students VALUES (?,?,?,?,?,?,?,?,?,?,?)''', + (student.id_card, student.stu_id, student.name, student.gender, + student.height, student.weight, + student.enrollment_date.isoformat() if student.enrollment_date else None, + student.class_name, student.major, student.age, student.birthday.isoformat())) + return True + except sqlite3.IntegrityError: + return False + + +class StudentService: + def __init__(self, dal: IStudentDAL): + self.dal = dal + + def add_student(self, student: Student) -> bool: + if not student.is_valid: + raise ValueError(f"Invalid student: {', '.join(student._validation_errors)}") + return self.dal.add(student) + + def get_all(self) -> List[Student]: + return self.dal.get_all() + + def query(self, **kwargs) -> List[Student]: + return [s for s in self.get_all() + if all(getattr(s, k, None) == v for k, v in kwargs.items() if v)] + + def get_stats(self, field: str) -> Dict[str, int]: + return {v: sum(1 for s in self.get_all() if getattr(s, field) == v) + for v in set(getattr(s, field) for s in self.get_all())} + + +class StudentCLI: + def __init__(self, service: StudentService): + self.service = service + + def run(self): + while True: + print("\n1. Add 2. List 3. Query 4. Stats 0. Exit") + choice = input("Choice: ") + if choice == "1": + self._add() + elif choice == "2": + self._list() + elif choice == "3": + self._query() + elif choice == "4": + self._stats() + elif choice == "0": + break + + def _add(self): + student = Student( + name=input("Name: "), + id_card=input("ID Card: "), + stu_id=input("Student ID: "), + gender=input("Gender (True/False): ").lower() == 'true', + height=int(input("Height: ")) if input("Height (optional): ") else None, + weight=float(input("Weight: ")) if input("Weight (optional): ") else None + ) + try: + if self.service.add_student(student): + print("Added successfully!") + except ValueError as e: + print(f"Error: {e}") + + def _list(self): + for student in self.service.get_all(): + print(student.to_dict()) + + def _query(self): + field = input("Field to query (name/class_name/major): ") + value = input(f"{field} value: ") + for student in self.service.query(**{field: value}): + print(student.to_dict()) + + def _stats(self): + field = input("Field for stats (class_name/major): ") + for k, v in self.service.get_stats(field).items(): + print(f"{k}: {v}") + + +if __name__ == "__main__": + storage = input("Storage (csv/json/sqlite): ").lower() + dal = { + 'csv': CSVStudentDAL('students.csv'), + 'json': JSONStudentDAL('students.json'), + 'sqlite': SQLiteStudentDAL('students.db') + }.get(storage, SQLiteStudentDAL('students.db')) + + service = StudentService(dal) + StudentCLI(service).run() \ No newline at end of file diff --git a/students.csv b/students.csv new file mode 100644 index 0000000..e69de29 diff --git a/students.json b/students.json new file mode 100644 index 0000000..0d7e7bb --- /dev/null +++ b/students.json @@ -0,0 +1,15 @@ +[ + { + "name": "\u5458\u5353\u8c6a", + "id_card": "610502200403240018", + "stu_id": "110309230907009", + "gender": false, + "height": 176, + "weight": 62.0, + "enrollment_date": "2023-09-01", + "class_name": "4", + "major": "\u8bf7\u9ad8\u7ea7", + "age": 21, + "birthday": "2004-03-24" + } +] \ No newline at end of file diff --git a/期末复习.py b/期末复习.py new file mode 100644 index 0000000..1cb22cb --- /dev/null +++ b/期末复习.py @@ -0,0 +1,836 @@ +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'] + ) + + +# 数据访问层接口 +class IStudentDAL(ABC): + @abstractmethod + def get_by_id(self, id_card: str) -> Optional[Student]: + pass + + @abstractmethod + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + pass + + @abstractmethod + def get_all(self) -> List[Student]: + pass + + @abstractmethod + def add(self, student: Student) -> bool: + pass + + @abstractmethod + def delete(self, id_card: str = None, stu_id: str = None) -> bool: + pass + + @abstractmethod + def update(self, student: Student) -> bool: + pass + + +# CSV数据访问层实现 +class CSVStudentDAL(IStudentDAL): + def __init__(self, file_path): + self.file_path = file_path + if not os.path.exists(file_path): + with open(file_path, 'w', newline='') as f: + writer = csv.DictWriter(f, fieldnames=Student.from_dict({}).to_dict().keys()) + writer.writeheader() + + def get_by_id(self, id_card: str) -> Optional[Student]: + with open(self.file_path, 'r', newline='') as f: + reader = csv.DictReader(f) + for row in reader: + if row['id_card'] == id_card: + return Student.from_dict(row) + return None + + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + with open(self.file_path, 'r', newline='') as f: + reader = csv.DictReader(f) + for row in reader: + if row['stu_id'] == stu_id: + return Student.from_dict(row) + return None + + def get_all(self) -> List[Student]: + students = [] + with open(self.file_path, 'r', newline='') as f: + reader = csv.DictReader(f) + for row in reader: + students.append(Student.from_dict(row)) + return students + + def add(self, student: Student) -> bool: + if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id): + return False + with open(self.file_path, 'a', newline='') as f: + writer = csv.DictWriter(f, fieldnames=student.to_dict().keys()) + writer.writerow(student.to_dict()) + return True + + def delete(self, id_card: str = None, stu_id: str = None) -> bool: + students = self.get_all() + initial_length = len(students) + if id_card: + students = [s for s in students if s.id_card != id_card] + elif stu_id: + students = [s for s in students if s.stu_id != stu_id] + if len(students) < initial_length: + with open(self.file_path, 'w', newline='') as f: + writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys() if students else []) + writer.writeheader() + for s in students: + writer.writerow(s.to_dict()) + return True + return False + + def update(self, student: Student) -> bool: + students = self.get_all() + for i, s in enumerate(students): + if s.id_card == student.id_card or s.stu_id == student.stu_id: + students[i] = student + with open(self.file_path, 'w', newline='') as f: + writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys() if students else []) + writer.writeheader() + for s in students: + writer.writerow(s.to_dict()) + return True + return False + + +# JSON数据访问层实现 +class JSONStudentDAL(IStudentDAL): + def __init__(self, file_path): + self.file_path = file_path + if not os.path.exists(file_path): + with open(file_path, 'w') as f: + json.dump([], f) + + def get_by_id(self, id_card: str) -> Optional[Student]: + with open(self.file_path, 'r') as f: + data = json.load(f) + for row in data: + if row['id_card'] == id_card: + return Student.from_dict(row) + return None + + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + with open(self.file_path, 'r') as f: + data = json.load(f) + for row in data: + if row['stu_id'] == stu_id: + return Student.from_dict(row) + return None + + def get_all(self) -> List[Student]: + with open(self.file_path, 'r') as f: + data = json.load(f) + return [Student.from_dict(row) for row in data] + + def add(self, student: Student) -> bool: + if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id): + return False + with open(self.file_path, 'r') as f: + data = json.load(f) + data.append(student.to_dict()) + with open(self.file_path, 'w') as f: + json.dump(data, f, indent=4) + return True + + def delete(self, id_card: str = None, stu_id: str = None) -> bool: + with open(self.file_path, 'r') as f: + data = json.load(f) + initial_length = len(data) + if id_card: + data = [s for s in data if s['id_card'] != id_card] + elif stu_id: + data = [s for s in data if s['stu_id'] != stu_id] + if len(data) < initial_length: + with open(self.file_path, 'w') as f: + json.dump(data, f, indent=4) + return True + return False + + def update(self, student: Student) -> bool: + with open(self.file_path, 'r') as f: + data = json.load(f) + for i, s in enumerate(data): + if s['id_card'] == student.id_card or s['stu_id'] == student.stu_id: + data[i] = student.to_dict() + with open(self.file_path, 'w') as f: + json.dump(data, f, indent=4) + return True + return False + + +# SQLite数据访问层实现 +class SQLiteStudentDAL(IStudentDAL): + def __init__(self, db_path): + self.db_path = db_path + self._create_table() + + def _create_table(self): + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS students ( + id_card TEXT PRIMARY KEY, + stu_id TEXT UNIQUE, + name TEXT, + gender BOOLEAN, + height INTEGER, + weight REAL, + enrollment_date TEXT, + class_name TEXT, + major TEXT, + age INTEGER, + birthday TEXT + ) + ''') + conn.commit() + conn.close() + + def get_by_id(self, id_card: str) -> Optional[Student]: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute("SELECT * FROM students WHERE id_card =?", (id_card,)) + row = cursor.fetchone() + conn.close() + if row: + data = dict(zip([description[0] for description in cursor.description], row)) + return Student.from_dict(data) + return None + + def get_by_stu_id(self, stu_id: str) -> Optional[Student]: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute("SELECT * FROM students WHERE stu_id =?", (stu_id,)) + row = cursor.fetchone() + conn.close() + if row: + data = dict(zip([description[0] for description in cursor.description], row)) + return Student.from_dict(data) + return None + + def get_all(self) -> List[Student]: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + cursor.execute("SELECT * FROM students") + rows = cursor.fetchall() + conn.close() + return [Student.from_dict(dict(zip([description[0] for description in cursor.description], row))) for row in + rows] + + def add(self, student: Student) -> bool: + if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id): + return False + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + try: + cursor.execute(''' + INSERT INTO students (id_card, stu_id, name, gender, height, weight, enrollment_date, class_name, major, age, birthday) + VALUES (?,?,?,?,?,?,?,?,?,?,?) + ''', ( + student.id_card, student.stu_id, student.name, student.gender, student.height, student.weight, + student.enrollment_date.isoformat() if student.enrollment_date else None, student.class_name, + student.major, student.age, student.birthday.isoformat())) + conn.commit() + return True + except sqlite3.IntegrityError: + return False + finally: + conn.close() + + def delete(self, id_card: str = None, stu_id: str = None) -> bool: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + if id_card: + cursor.execute("DELETE FROM students WHERE id_card =?", (id_card,)) + elif stu_id: + cursor.execute("DELETE FROM students WHERE stu_id =?", (stu_id,)) + rows_affected = cursor.rowcount + conn.commit() + conn.close() + return rows_affected > 0 + + def update(self, student: Student) -> bool: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + try: + cursor.execute(''' + UPDATE students + SET stu_id =?, name =?, gender =?, height =?, weight =?, enrollment_date =?, class_name =?, major =?, age =?, birthday =? + WHERE id_card =? + ''', ( + student.stu_id, student.name, student.gender, student.height, student.weight, + student.enrollment_date.isoformat() if student.enrollment_date else None, student.class_name, + student.major, student.age, student.birthday.isoformat(), student.id_card)) + rows_affected = cursor.rowcount + conn.commit() + return rows_affected > 0 + except sqlite3.IntegrityError: + return False + finally: + conn.close() + + +# 业务逻辑层 +class StudentBLL: + def __init__(self, dal: IStudentDAL): + self.dal = dal + + def add_student(self, student: Student) -> bool: + if not student.is_valid: + raise ValueError(f"学生数据无效: {', '.join(student.get_errors())}") + return self.dal.add(student) + + def delete_student(self, id_card: str = None, stu_id: str = None) -> bool: + return self.dal.delete(id_card, stu_id) + + def update_student(self, student: Student) -> bool: + if not student.is_valid: + raise ValueError(f"学生数据无效: {', '.join(student.get_errors())}") + return self.dal.update(student) + + def get_student_by_id(self, id_card: str) -> Optional[Student]: + return self.dal.get_by_id(id_card) + + def get_student_by_stu_id(self, stu_id: str) -> Optional[Student]: + return self.dal.get_by_stu_id(stu_id) + + def get_all_students(self) -> List[Student]: + return self.dal.get_all() + + def query_by_name(self, name: str) -> List[Student]: + return [s for s in self.get_all_students() if name.lower() in s.name.lower()] + + def query_by_class(self, class_name: str) -> List[Student]: + return [s for s in self.get_all_students() if class_name.lower() in s.class_name.lower()] + + def query_by_major(self, major: str) -> List[Student]: + return [s for s in self.get_all_students() if major.lower() in s.major.lower()] + + def get_student_count(self) -> int: + return len(self.get_all_students()) + + def get_major_count(self) -> dict: + major_count = {} + for student in self.get_all_students(): + if student.major in major_count: + major_count[student.major] += 1 + else: + major_count[student.major] = 1 + return major_count + + def get_avg_height_by_class(self, class_name: str) -> float: + students = [s for s in self.get_all_students() if s.class_name == class_name and s.height is not None] + if not students: + return 0 + return sum(s.height for s in students) / len(students) + + def get_avg_weight_by_class(self, class_name: str) -> float: + students = [s for s in self.get_all_students() if s.class_name == class_name and s.weight is not None] + if not students: + return 0 + return sum(s.weight for s in students) / len(students) + + def get_avg_height_by_major(self, major: str) -> float: + students = [s for s in self.get_all_students() if s.major == major and s.height is not None] + if not students: + return 0 + return sum(s.height for s in students) / len(students) + + def get_avg_weight_by_major(self, major: str) -> float: + students = [s for s in self.get_all_students() if s.major == major and s.weight is not None] + if not students: + return 0 + return sum(s.weight for s in students) / len(students) + + def import_from_csv(self, file_path): + with open(file_path, 'r', newline='') as f: + reader = csv.DictReader(f) + for row in reader: + student = Student.from_dict(row) + if student.is_valid: + self.add_student(student) + + def import_from_json(self, file_path): + with open(file_path, 'r') as f: + data = json.load(f) + for row in data: + student = Student.from_dict(row) + if student.is_valid: + self.add_student(student) + + def export_to_csv(self, file_path): + students = self.get_all_students() + with open(file_path, 'w', newline='') as f: + writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys() if students else []) + writer.writeheader() + for s in students: + writer.writerow(s.to_dict()) + + def export_to_json(self, file_path): + students = self.get_all_students() + with open(file_path, 'w') as f: + json.dump([s.to_dict() for s in students], f, indent=4) + + +# 用户界面层 +class StudentUI: + 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("8. 数据导出") + print("0. 退出系统") + print("==========================") + + def display_query_menu(self): + print("\n===== 查询学生信息 =====") + print("1. 按学号查询") + print("2. 按姓名查询") + print("3. 按班级查询") + print("4. 按专业查询") + print("5. 返回上一级") + print("======================") + + def display_stats_menu(self): + print("\n===== 统计信息 =====") + print("1. 学生总数") + print("2. 各专业学生人数") + print("3. 按班级计算平均身高") + print("4. 按班级计算平均体重") + print("5. 按专业计算平均身高") + print("6. 按专业计算平均体重") + print("7. 返回上一级") + print("====================") + + def display_import_menu(self): + print("\n===== 数据导入 =====") + print("1. 从CSV导入") + print("2. 从JSON导入") + print("3. 返回上一级") + print("====================") + + def display_export_menu(self): + print("\n===== 数据导出 =====") + print("1. 导出到CSV") + print("2. 导出到JSON") + print("3. 返回上一级") + print("====================") + + def get_student_input(self, existing_student=None): + """获取学生输入,支持使用现有学生数据作为默认值""" + if existing_student: + print(f"当前值: {existing_student.name}") + name = input("请输入姓名 (直接回车保持不变): ") or (existing_student.name if existing_student else "") + + if existing_student: + print(f"当前值: {existing_student.id_card}") + id_card = input("请输入身份证号 (直接回车保持不变): ") or (existing_student.id_card if existing_student else "") + + if existing_student: + print(f"当前值: {existing_student.stu_id}") + stu_id = input("请输入学号 (直接回车保持不变): ") or (existing_student.stu_id if existing_student else "") + + if existing_student and existing_student.gender is not None: + print(f"当前值: {existing_student.gender}") + gender_str = input("请输入性别 (True/False,直接回车保持不变): ") + gender = None + if gender_str: + gender = gender_str.lower() == 'true' + elif existing_student: + gender = existing_student.gender + + if existing_student and existing_student.height is not None: + print(f"当前值: {existing_student.height}") + height_str = input("请输入身高 (cm,直接回车保持不变): ") + height = None + if height_str: + height = int(height_str) + elif existing_student: + height = existing_student.height + + if existing_student and existing_student.weight is not None: + print(f"当前值: {existing_student.weight}") + weight_str = input("请输入体重 (kg,直接回车保持不变): ") + weight = None + if weight_str: + weight = float(weight_str) + elif existing_student: + weight = existing_student.weight + + if existing_student and existing_student.enrollment_date: + print(f"当前值: {existing_student.enrollment_date.isoformat()}") + enrollment_date = input("请输入入学日期 (YYYY-MM-DD,直接回车保持不变): ") or ( + existing_student.enrollment_date.isoformat() if existing_student and existing_student.enrollment_date else None + ) + + if existing_student: + print(f"当前值: {existing_student.class_name}") + class_name = input("请输入班级名称 (直接回车保持不变): ") or ( + existing_student.class_name if existing_student else "") + + if existing_student: + print(f"当前值: {existing_student.major}") + major = input("请输入专业 (直接回车保持不变): ") or (existing_student.major if existing_student else "") + + return Student(name, id_card, stu_id, gender, height, weight, enrollment_date, class_name, major) + + def run(self): + while True: + self.display_menu() + choice = input("请选择操作: ") + + if choice == "1": + self.add_student() + elif choice == "2": + self.delete_student() + elif choice == "3": + self.update_student() + elif choice == "4": + self.view_student_details() + elif choice == "5": + self.query_student() + elif choice == "6": + self.statistics() + elif choice == "7": + self.import_data() + elif choice == "8": + self.export_data() + elif choice == "0": + print("感谢使用,再见!") + break + else: + print("无效选择,请重试!") + + def add_student(self): + print("\n===== 添加学生信息 =====") + try: + student = self.get_student_input() + if self.bll.add_student(student): + print("添加成功!") + else: + print("添加失败,学号或身份证号可能已存在。") + except ValueError as e: + print(f"添加失败: {e}") + + def delete_student(self): + print("\n===== 删除学生信息 =====") + choice = input("按学号删除请输入1,按身份证号删除请输入2: ") + if choice == "1": + stu_id = input("请输入要删除的学号: ") + if self.bll.delete_student(stu_id=stu_id): + print("删除成功!") + else: + print("删除失败,学号不存在。") + elif choice == "2": + id_card = input("请输入要删除的身份证号: ") + if self.bll.delete_student(id_card=id_card): + print("删除成功!") + else: + print("删除失败,身份证号不存在。") + else: + print("无效选择,请重试!") + + def update_student(self): + print("\n===== 修改学生信息 =====") + stu_id = input("请输入要修改的学生学号: ") + student = self.bll.get_student_by_stu_id(stu_id) + if not student: + print("学号不存在。") + return + + print(f"当前信息: {student.to_dict()}") + print("(直接回车表示不修改)") + + # 使用现有的学生数据作为默认值 + new_student = self.get_student_input(student) + + # 确保不修改ID和学号 + new_student.id_card = student.id_card + new_student.stu_id = student.stu_id + + try: + if self.bll.update_student(new_student): + print("修改成功!") + else: + print("修改失败,可能是数据验证不通过。") + except ValueError as e: + print(f"修改失败: {e}") + + def view_student_details(self): + print("\n===== 查看学生详细信息 =====") + stu_id = input("请输入要查看的学生学号: ") + student = self.bll.get_student_by_stu_id(stu_id) + if student: + print(student.to_dict()) + else: + print("学号不存在。") + + def query_student(self): + while True: + self.display_query_menu() + choice = input("请选择查询方式: ") + if choice == "1": + stu_id = input("请输入学号: ") + student = self.bll.get_student_by_stu_id(stu_id) + if student: + print(student.to_dict()) + else: + print("未找到该学生。") + elif choice == "2": + name = input("请输入姓名: ") + students = self.bll.query_by_name(name) + if students: + for s in students: + print(s.to_dict()) + else: + print("未找到匹配的学生。") + elif choice == "3": + class_name = input("请输入班级名称: ") + students = self.bll.query_by_class(class_name) + if students: + for s in students: + print(s.to_dict()) + else: + print("未找到匹配的学生。") + elif choice == "4": + major = input("请输入专业名称: ") + students = self.bll.query_by_major(major) + if students: + for s in students: + print(s.to_dict()) + else: + print("未找到匹配的学生。") + elif choice == "5": + break + else: + print("无效选择,请重试!") + + def statistics(self): + while True: + self.display_stats_menu() + choice = input("请选择统计方式: ") + if choice == "1": + print(f"学生总数: {self.bll.get_student_count()}") + elif choice == "2": + major_count = self.bll.get_major_count() + for major, count in major_count.items(): + print(f"{major}: {count}人") + elif choice == "3": + class_name = input("请输入班级名称: ") + avg_height = self.bll.get_avg_height_by_class(class_name) + print(f"{class_name} 班级平均身高: {avg_height:.2f} cm") + elif choice == "4": + class_name = input("请输入班级名称: ") + avg_weight = self.bll.get_avg_weight_by_class(class_name) + print(f"{class_name} 班级平均体重: {avg_weight:.2f} kg") + elif choice == "5": + major = input("请输入专业名称: ") + avg_height = self.bll.get_avg_height_by_major(major) + print(f"{major} 专业平均身高: {avg_height:.2f} cm") + elif choice == "6": + major = input("请输入专业名称: ") + avg_weight = self.bll.get_avg_weight_by_major(major) + print(f"{major} 专业平均体重: {avg_weight:.2f} kg") + elif choice == "7": + break + else: + print("无效选择,请重试!") + + def import_data(self): + while True: + self.display_import_menu() + choice = input("请选择导入方式: ") + if choice == "1": + file_path = input("请输入CSV文件路径: ") + try: + self.bll.import_from_csv(file_path) + print("导入成功!") + except Exception as e: + print(f"导入失败: {e}") + elif choice == "2": + file_path = input("请输入JSON文件路径: ") + try: + self.bll.import_from_json(file_path) + print("导入成功!") + except Exception as e: + print(f"导入失败: {e}") + elif choice == "3": + break + else: + print("无效选择,请重试!") + + def export_data(self): + while True: + self.display_export_menu() + choice = input("请选择导出方式: ") + if choice == "1": + file_path = input("请输入导出的CSV文件路径: ") + try: + self.bll.export_to_csv(file_path) + print("导出成功!") + except Exception as e: + print(f"导出失败: {e}") + elif choice == "2": + file_path = input("请输入导出的JSON文件路径: ") + try: + self.bll.export_to_json(file_path) + print("导出成功!") + except Exception as e: + print(f"导出失败: {e}") + elif choice == "3": + break + else: + print("无效选择,请重试!") + + +if __name__ == "__main__": + # 选择数据存储方式 + storage_choice = input("请选择数据存储方式 (1: CSV, 2: JSON, 3: SQLite): ") + if storage_choice == "1": + dal = CSVStudentDAL("students.csv") + elif storage_choice == "2": + dal = JSONStudentDAL("students.json") + elif storage_choice == "3": + dal = SQLiteStudentDAL("students.db") + else: + print("无效选择,默认使用SQLite。") + dal = SQLiteStudentDAL("students.db") + + bll = StudentBLL(dal) + ui = StudentUI(bll) + print("欢迎使用学生信息管理系统!") + ui.run() \ No newline at end of file