e50a9207b4
- 添加典型方案采纳功能 (POST /api/plans/<id>/adopt) - 添加推荐方案列表 (GET /api/students/<id>/recommended-plans) - PracticePlan 新增 created_by/updated_by/updated_at 审计字段 - 方案编辑/详情页导航优化 (bfcache 处理、pageshow 事件) - 方案列表支持删除功能 - 学员列表'暂无方案/问题'样式统一 - 更新文档:问题文件已废弃(迁移到数据库) - 更新部署脚本和验证清单
359 lines
10 KiB
Markdown
359 lines
10 KiB
Markdown
# 数据模型说明
|
||
|
||
## 概述
|
||
|
||
本系统使用 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 字段结构**:
|
||
```json
|
||
{
|
||
"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 │
|
||
└──────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 常用查询
|
||
|
||
### 查询用户角色
|
||
|
||
```python
|
||
from app.models import User
|
||
user = User.query.filter_by(username="admin").first()
|
||
print(user.role) # "admin" or "user"
|
||
```
|
||
|
||
### 查询学员及其问题
|
||
|
||
```python
|
||
student = Student.query.get(1)
|
||
for sp in student.problems:
|
||
print(sp.problem.name, sp.problem.code, sp.severity, sp.level)
|
||
```
|
||
|
||
### 查询班级及其学员
|
||
|
||
```python
|
||
cls = Class.query.get(1)
|
||
for student in cls.students:
|
||
print(student.name)
|
||
```
|
||
|
||
### 查询学员及其方案
|
||
|
||
```python
|
||
student = Student.query.get(1)
|
||
for plan in student.plans:
|
||
print(plan.created_at)
|
||
```
|
||
|
||
### 获取最新方案
|
||
|
||
```python
|
||
from app.models import PracticePlan
|
||
latest_plan = PracticePlan.query.order_by(
|
||
PracticePlan.created_at.desc()
|
||
).first()
|
||
```
|
||
|
||
---
|
||
|
||
## 数据库管理
|
||
|
||
### 初始化数据库
|
||
|
||
首次运行应用时会自动创建所有表:
|
||
|
||
```python
|
||
from app import create_app
|
||
from app.models import db
|
||
|
||
app = create_app()
|
||
with app.app_context():
|
||
db.create_all()
|
||
```
|
||
|
||
### 备份数据库
|
||
|
||
```bash
|
||
# 停止服务后复制数据库文件
|
||
copy data\piano_plans.db backup\piano_plans_backup.db
|
||
```
|
||
|
||
### 查看数据库内容
|
||
|
||
```bash
|
||
# 使用 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` → 已结束
|
||
|
||
**排序规则**:按状态(进行中→未开始→已结束),再按评估日期倒序
|
||
|
||
**关系**:
|
||
- 一个学员可以分配多个目标
|
||
- 一个目标可以分配给多个学员
|
||
|
||
**操作入口**:
|
||
- "调整目标":修改开始/评估日期,移除目标
|
||
- "评估目标":填写掌握程度、达成日期、评语 |