e50a9207b4
- 添加典型方案采纳功能 (POST /api/plans/<id>/adopt) - 添加推荐方案列表 (GET /api/students/<id>/recommended-plans) - PracticePlan 新增 created_by/updated_by/updated_at 审计字段 - 方案编辑/详情页导航优化 (bfcache 处理、pageshow 事件) - 方案列表支持删除功能 - 学员列表'暂无方案/问题'样式统一 - 更新文档:问题文件已废弃(迁移到数据库) - 更新部署脚本和验证清单
10 KiB
10 KiB
数据模型说明
概述
本系统使用 SQLite 数据库,ORM 框架为 Flask-SQLAlchemy。
数据库文件: data/piano_plans.db
数据表
0. Problem (问题定义)
系统预定义的15种常见钢琴学习问题。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键,自增 |
| code | String(50) | 问题编号,如 "05_掌关节支撑差" |
| name | String(100) | 问题名称,如 "掌关节支撑差" |
| category | String(20) | 分类:技术类/认知类/节奏类/习惯类/综合类 |
| created_at | DateTime | 创建时间 |
⚠️ 问题数据已从文件系统迁移到数据库。student_problems 表通过
problem_id外键关联到此表。
1. User (用户)
系统用户,用于登录认证和权限管理。
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键,自增 |
| username | String(50) | 用户名,唯一,必填 |
| password_hash | String(200) | 密码哈希值 |
| role | String(20) | 角色:admin/user,默认 user |
| created_at | DateTime | 创建时间 |
角色说明:
admin: 管理员,拥有所有权限user: 普通用户,受限权限
密码规则: 8位以上,包含大小写字母、数字和特殊字符
2. Student (学员)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键,自增 |
| name | String(100) | 学员姓名,必填 |
| phone | String(20) | 手机号,可选 |
| wechat_nickname | String(100) | 微信昵称,可选 |
| practice_time | String(20) | 每日练习时间,默认"30分钟" |
| notes | Text | 备注信息,可选 |
| class_id | Integer | 外键,关联 Class,可选 |
| created_at | DateTime | 创建时间 |
练习时间选项: 15分钟, 30分钟, 45分钟, 60分钟, 90分钟, 120分钟, 150分钟以上
关系:
problems: 与 StudentProblem 一对多plans: 与 PracticePlan 一对多class: 与 Class 多对一
3. StudentProblem (学员问题记录)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键,自增 |
| student_id | Integer | 外键,关联 Student |
| problem_id | Integer | 外键,关联 Problem.id |
| severity | String(10) | 严重程度:轻微/中等/严重 |
| level | String(20) | 级别:启蒙/入门/进阶/熟练/精通 |
| created_at | DateTime | 创建时间 |
⚠️
problem_id现为数字外键,关联Problem.id。通过student_problem.problem关系获取问题名称。
4. Class (班级)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键,自增 |
| name | String(100) | 班级名称,必填 |
| level | String(20) | 班级级别:启蒙/入门/进阶/熟练/精通 |
| description | Text | 班级描述,可选 |
| created_at | DateTime | 创建时间 |
关系:
students: 与 Student 一对多
5. PracticePlan (练习方案)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键,自增 |
| student_id | Integer | 外键,关联 Student |
| template_id | Integer | 外键,关联 Template(AI提示词模板) |
| is_typical | Boolean | 是否为典型方案 |
| content | Text | 方案内容(JSON格式) |
| created_by | Integer | 外键,关联 User(创建人) |
| created_at | DateTime | 创建时间 |
| updated_by | Integer | 外键,关联 User(更新人,仅编辑时设置) |
| updated_at | DateTime | 更新时间(仅编辑时设置) |
审计字段说明:
created_by:创建时设置updated_by、updated_at:仅在编辑更新时设置,初次创建时为空
content 字段结构:
{
"student_name": "张三",
"practice_time": "30分钟",
"total_daily_minutes": 30,
"problems": [
{
"name": "手小",
"severity": "中等",
"level": "入门",
"focus": {"basic": 15, "tech": 10, "piece": 20}
}
],
"ai_report": "## 个性化练习方案报告\n\n..."
}
ER 关系图
┌─────────────┐ ┌──────────────────┐ ┌───────────────┐
│ User │ │ Student │ │ Class │
├─────────────┤ ├──────────────────┤ ├───────────────┤
│ id │ │ id │◄──────│ id │
│ username │ │ name │ │ name │
│ password │ │ phone │ │ description │
│ role │ │ wechat_nickname │ │ created_at │
│ created_at │ │ practice_time │ └───────────────┘
└─────────────┘ │ notes │
│ class_id │──┐
│ created_at │ │
└──────────────────┘ │
│ │
▼ │
┌──────────────────┐ │
│ StudentProblem │ │
├──────────────────┤ │
│ id │ │
│ student_id ─────┘ │
│ problem_id │◄─┼──► Problem
│ severity │
│ level │
│ created_at │
└──────────────────┘
│
▼
┌──────────────────┐
│ PracticePlan │
├──────────────────┤
│ id │
│ student_id ─────┼──► Student
│ content │
│ created_at │
└──────────────────┘
常用查询
查询用户角色
from app.models import User
user = User.query.filter_by(username="admin").first()
print(user.role) # "admin" or "user"
查询学员及其问题
student = Student.query.get(1)
for sp in student.problems:
print(sp.problem.name, sp.problem.code, sp.severity, sp.level)
查询班级及其学员
cls = Class.query.get(1)
for student in cls.students:
print(student.name)
查询学员及其方案
student = Student.query.get(1)
for plan in student.plans:
print(plan.created_at)
获取最新方案
from app.models import PracticePlan
latest_plan = PracticePlan.query.order_by(
PracticePlan.created_at.desc()
).first()
数据库管理
初始化数据库
首次运行应用时会自动创建所有表:
from app import create_app
from app.models import db
app = create_app()
with app.app_context():
db.create_all()
备份数据库
# 停止服务后复制数据库文件
copy data\piano_plans.db backup\piano_plans_backup.db
查看数据库内容
# 使用 SQLite 命令行
sqlite3 data\piano_plans.db
# 查看表
.schema
# 查询数据
SELECT * FROM users;
数据字典
User.role 取值
| 值 | 说明 |
|---|---|
| admin | 管理员,拥有所有权限 |
| user | 普通用户,受限权限 |
StudentProblem.severity 取值
| 值 | 说明 |
|---|---|
| 轻微 | 问题不明显,日常练习即可改善 |
| 中等 | 需要针对性练习,建议重点关注 |
| 严重 | 需要系统训练,建议额外辅导 |
StudentProblem.level 取值
| 值 | 说明 |
|---|---|
| 启蒙 | 初期入门阶段 |
| 入门 | 基础学习阶段 |
| 进阶 | 技能提升阶段 |
| 熟练 | 技术熟练阶段 |
| 精通 | 高级演奏阶段 |
Student.practice_time 取值
| 值 | 说明 |
|---|---|
| 15分钟 | 初级学员 |
| 30分钟 | 进阶学员 |
| 45分钟 | 中级学员 |
| 60分钟 | 中高级学员 |
| 90分钟 | 高级学员 |
| 120分钟 | 专业学员 |
| 150分钟以上 | 竞技水平 |
目标管理模块
Goal (目标表)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键 |
| name | String(100) | 目标名称 |
| content | Text | 目标内容(Markdown) |
| level | String(20) | 级别:启蒙/入门/进阶/熟练/精通 |
| category | String(20) | 分类:综合/乐理相关/演奏能力/其他 |
| created_at | DateTime | 创建时间 |
| updated_at | DateTime | 更新时间 |
GoalRelation (目标关联表)
自关联多对多关系,用于表示目标之间的父子关系(DAG)。
| 字段 | 类型 | 说明 |
|---|---|---|
| parent_goal_id | Integer | 父目标ID,外键 |
| child_goal_id | Integer | 子目标ID,外键 |
关系类型:自引用多对多(一个目标可以有多个子目标,也可以有多个父目标)
约束:通过应用层循环检测防止形成循环
StudentGoal (学员目标记录表)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键 |
| student_id | Integer | 学员ID,外键 |
| goal_id | Integer | 目标ID,外键 |
| goal_content | Text | 目标内容副本(冗余存储,避免目标被删除后丢失) |
| start_date | DateTime | 开始日期 |
| assessment_date | DateTime | 评估日期 |
| mastery_level | Integer | 掌握程度 1-5(评估时填写) |
| achievement_date | DateTime | 达成日期 |
| comment | Text | 评语 |
| created_at | DateTime | 创建时间 |
状态计算逻辑:
status由start_date和assessment_date自动计算,不存储- 早于
start_date→ 未开始 start_date和assessment_date之间 → 进行中- 晚于
assessment_date→ 已结束
排序规则:按状态(进行中→未开始→已结束),再按评估日期倒序
关系:
- 一个学员可以分配多个目标
- 一个目标可以分配给多个学员
操作入口:
- "调整目标":修改开始/评估日期,移除目标
- "评估目标":填写掌握程度、达成日期、评语