feat: 初始提交 v1.2.0 - 钢琴练习方案生成系统
This commit is contained in:
+236
@@ -0,0 +1,236 @@
|
||||
# 数据库模型定义
|
||||
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from datetime import datetime
|
||||
import re
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
class User(db.Model):
|
||||
"""管理员用户表"""
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(50), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(200), nullable=False)
|
||||
role = db.Column(db.String(20), default="user") # admin / user
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
|
||||
def set_password(self, password):
|
||||
"""设置密码(验证复杂度后hash)"""
|
||||
if not self.validate_password(password):
|
||||
raise ValueError("密码不符合要求")
|
||||
import hashlib
|
||||
|
||||
self.password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
def check_password(self, password):
|
||||
"""验证密码"""
|
||||
import hashlib
|
||||
|
||||
return self.password_hash == hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def validate_password(password):
|
||||
"""验证密码复杂度:大写+小写+数字+特殊字符,8位以上"""
|
||||
if len(password) < 8:
|
||||
return False
|
||||
if not re.search(r"[A-Z]", password):
|
||||
return False
|
||||
if not re.search(r"[a-z]", password):
|
||||
return False
|
||||
if not re.search(r"[0-9]", password):
|
||||
return False
|
||||
if not re.search(r"['!@#$%^&*(),.?\":{}|<>\-_]", password):
|
||||
return False
|
||||
return True
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"username": self.username,
|
||||
"role": self.role,
|
||||
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
if self.created_at
|
||||
else None,
|
||||
}
|
||||
|
||||
|
||||
class Class(db.Model):
|
||||
"""班级表"""
|
||||
|
||||
__tablename__ = "classes"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
description = db.Column(db.Text)
|
||||
active = db.Column(db.Boolean, default=True) # 进行中
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
|
||||
# 关联 - 在Student模型中定义backref
|
||||
def to_dict(self):
|
||||
# 直接查询学员数量,避免relationship问题
|
||||
from app.models import Student
|
||||
|
||||
student_count = Student.query.filter_by(class_id=self.id).count()
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"description": self.description,
|
||||
"active": self.active if self.active is not None else True,
|
||||
"student_count": student_count,
|
||||
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
if self.created_at
|
||||
else None,
|
||||
}
|
||||
|
||||
|
||||
class Student(db.Model):
|
||||
"""学员表"""
|
||||
|
||||
__tablename__ = "students"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
phone = db.Column(db.String(20))
|
||||
wechat_nickname = db.Column(db.String(100)) # 微信昵称
|
||||
practice_time = db.Column(db.String(20), default="30分钟") # 每日练习时间
|
||||
notes = db.Column(db.Text) # 备注
|
||||
class_id = db.Column(db.Integer, db.ForeignKey("classes.id")) # 班级
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
|
||||
# 关联
|
||||
problems = db.relationship(
|
||||
"StudentProblem",
|
||||
backref="student",
|
||||
lazy="dynamic",
|
||||
cascade="all, delete-orphan",
|
||||
)
|
||||
plans = db.relationship(
|
||||
"PracticePlan", backref="student", lazy="dynamic", cascade="all, delete-orphan"
|
||||
)
|
||||
class_obj = db.relationship("Class", backref="students")
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"phone": self.phone,
|
||||
"wechat_nickname": self.wechat_nickname,
|
||||
"practice_time": self.practice_time,
|
||||
"notes": self.notes,
|
||||
"class_id": self.class_id,
|
||||
"class_name": self.class_obj.name if self.class_obj else None,
|
||||
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
if self.created_at
|
||||
else None,
|
||||
"problem_count": self.problems.count(),
|
||||
"plan_count": self.plans.count(),
|
||||
}
|
||||
|
||||
|
||||
class StudentProblem(db.Model):
|
||||
"""学员问题记录表"""
|
||||
|
||||
__tablename__ = "student_problems"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
student_id = db.Column(db.Integer, db.ForeignKey("students.id"), nullable=False)
|
||||
problem_id = db.Column(db.String(50), nullable=False) # 如 "01_手小"
|
||||
problem_name = db.Column(db.String(100), nullable=False) # 如 "手小"
|
||||
severity = db.Column(db.String(10), nullable=False) # 轻微/中等/严重
|
||||
level = db.Column(db.String(20)) # 启蒙/入门/进阶/熟练/精通
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"problem_id": self.problem_id,
|
||||
"problem_name": self.problem_name,
|
||||
"severity": self.severity,
|
||||
"level": self.level,
|
||||
}
|
||||
|
||||
|
||||
class PracticePlan(db.Model):
|
||||
"""练习方案表"""
|
||||
|
||||
__tablename__ = "practice_plans"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
student_id = db.Column(db.Integer, db.ForeignKey("students.id"), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False) # JSON格式存储方案内容
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"student_id": self.student_id,
|
||||
"student_name": self.student.name if self.student else "",
|
||||
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M")
|
||||
if self.created_at
|
||||
else None,
|
||||
"content": self.content,
|
||||
}
|
||||
|
||||
|
||||
class Template(db.Model):
|
||||
"""模板配置表"""
|
||||
__tablename__ = "templates"
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(50), unique=True, nullable=False) # 模板名称
|
||||
type = db.Column(db.String(20), nullable=False) # ai_prompt / report
|
||||
content = db.Column(db.Text, nullable=False) # 模板内容
|
||||
description = db.Column(db.String(200)) # 模板描述
|
||||
sort_order = db.Column(db.Integer, default=0) # 排序,数字越小越靠前
|
||||
created_at = db.Column(db.DateTime, default=datetime.now)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"type": self.type,
|
||||
"content": self.content,
|
||||
"description": self.description,
|
||||
"sort_order": self.sort_order,
|
||||
"created_at": self.created_at.strftime("%Y-%m-%d %H:%M") if self.created_at else None,
|
||||
"updated_at": self.updated_at.strftime("%Y-%m-%d %H:%M") if self.updated_at else None,
|
||||
}
|
||||
|
||||
|
||||
# 问题配置(从文件读取)
|
||||
PROBLEM_LIST = [
|
||||
{"id": "01_手小", "name": "手小", "category": "技术类(生理限制)"},
|
||||
{"id": "02_识谱慢", "name": "识谱慢", "category": "识谱类"},
|
||||
{"id": "03_节奏感差", "name": "节奏感差", "category": "综合类"},
|
||||
{"id": "04_压手腕", "name": "压手腕", "category": "技术类(手型)"},
|
||||
{"id": "05_掌关节支撑差", "name": "掌关节支撑差", "category": "技术类(手型)"},
|
||||
{"id": "06_第一关节支撑差", "name": "第一关节支撑差", "category": "技术类(手型)"},
|
||||
{"id": "07_对键盘不熟悉", "name": "对键盘不熟悉", "category": "识谱类"},
|
||||
{"id": "08_手指僵硬_紧张", "name": "手指僵硬、紧张", "category": "技术类(手型)"},
|
||||
{"id": "09_手指不会跑动", "name": "手指不会跑动", "category": "技术类"},
|
||||
{"id": "10_力度不会把握", "name": "力度不会把握", "category": "技术类"},
|
||||
{"id": "11_左右手不协调", "name": "左右手不协调", "category": "综合类"},
|
||||
{"id": "12_不会用节拍器", "name": "不会用节拍器", "category": "识谱类"},
|
||||
{"id": "13_不会编配指法", "name": "不会编配指法", "category": "综合类"},
|
||||
{"id": "14_基本功练习", "name": "基本功练习", "category": "综合类"},
|
||||
{"id": "15_练习缺乏监督", "name": "练习缺乏监督", "category": "综合类"},
|
||||
]
|
||||
|
||||
SEVERITY_LEVELS = ["轻微", "中等", "严重"]
|
||||
LEVEL_OPTIONS = ["启蒙", "入门", "进阶", "熟练", "精通"]
|
||||
PRACTICE_TIME_OPTIONS = [
|
||||
"15分钟",
|
||||
"30分钟",
|
||||
"45分钟",
|
||||
"60分钟",
|
||||
"90分钟",
|
||||
"120分钟",
|
||||
"150分钟以上",
|
||||
]
|
||||
|
||||
ROLE_OPTIONS = ["admin", "user"]
|
||||
Reference in New Issue
Block a user