feat: 初始提交 v1.2.0 - 钢琴练习方案生成系统

This commit is contained in:
hmo
2026-04-21 20:00:33 +08:00
commit fd593bddf4
44 changed files with 10936 additions and 0 deletions
+556
View File
@@ -0,0 +1,556 @@
# API 接口文档
## 基础信息
- **Base URL**: `http://127.0.0.1:5000`
- **Content-Type**: `application/json`
---
## 认证接口
### 检查登录状态
```
GET /api/check-login
```
**响应示例**:
```json
{
"logged_in": true,
"username": "admin",
"role": "admin"
}
```
### 登录
```
POST /api/login
```
**请求体**:
```json
{
"username": "admin",
"password": "Admin@123"
}
```
**响应示例**:
```json
{
"message": "登录成功"
}
```
### 登出
```
POST /api/logout
```
### 初始设置(首次)
```
GET /setup
```
### 初始设置提交
```
POST /api/setup
```
**请求体**:
```json
{
"username": "admin",
"password": "Admin@123",
"confirm_password": "Admin@123"
}
```
### 修改当前用户密码
```
POST /api/users/change-password
```
**请求体**:
```json
{
"old_password": "Old@123",
"new_password": "New@123",
"confirm_password": "New@123"
}
```
---
## 用户管理(仅管理员)
### 获取用户列表
```
GET /api/users
```
**响应示例**:
```json
[
{
"id": 1,
"username": "admin",
"role": "admin",
"created_at": "2026-04-17 10:00"
},
{
"id": 2,
"username": "teacher1",
"role": "user",
"created_at": "2026-04-18 10:00"
}
]
```
### 新增用户
```
POST /api/users
```
**请求体**:
```json
{
"username": "newuser",
"password": "User@123",
"role": "user"
}
```
**角色选项**: `admin`, `user`
### 编辑用户
```
PUT /api/users/<id>
```
**请求体**:
```json
{
"role": "user"
}
```
### 重置用户密码
```
POST /api/users/<id>/reset-password
```
**请求体**:
```json
{
"new_password": "Reset@123"
}
```
### 删除用户
```
DELETE /api/users/<id>
```
---
## 班级管理
### 获取班级列表
```
GET /api/classes
```
**权限**: 登录用户
**响应示例**:
```json
[
{
"id": 1,
"name": "钢琴初级班",
"description": "入门学员",
"student_count": 5,
"created_at": "2026-04-17 10:00"
}
]
```
### 新增班级
```
POST /api/classes
```
**权限**: 管理员
**请求体**:
```json
{
"name": "钢琴中级班",
"description": "进阶学员"
}
```
### 编辑班级
```
PUT /api/classes/<id>
```
**权限**: 管理员
**请求体**:
```json
{
"name": "钢琴中级班(2026",
"description": "进阶学员"
}
```
### 删除班级
```
DELETE /api/classes/<id>
```
**权限**: 管理员
### 获取班级学员列表
```
GET /api/classes/<id>/students
```
**权限**: 登录用户
**响应示例**:
```json
[
{
"id": 1,
"name": "张三",
"phone": "13800138000",
"practice_time": "30分钟"
}
]
```
### 分配学员到班级
```
POST /api/classes/<id>/assign
```
**权限**: 登录用户
**请求体**:
```json
{
"student_ids": [1, 2, 3]
}
```
---
## 学员管理
### 获取学员列表
```
GET /api/students
```
**权限**: 登录用户
**响应示例**:
```json
[
{
"id": 1,
"name": "张三",
"phone": "13800138000",
"wechat_nickname": "小张",
"practice_time": "30分钟",
"class_id": 1,
"class_name": "钢琴初级班",
"notes": "",
"problem_count": 2,
"plan_count": 1,
"created_at": "2026-04-17 10:00"
}
]
```
### 创建学员
```
POST /api/students
```
**权限**: 登录用户
**请求体**:
```json
{
"name": "学员姓名",
"phone": "手机号",
"wechat_nickname": "微信昵称",
"practice_time": "30分钟",
"class_id": 1,
"notes": "备注信息"
}
```
### 获取学员详情
```
GET /api/students/<id>
```
### 更新学员
```
PUT /api/students/<id>
```
**权限**: 登录用户
**请求体**:
```json
{
"name": "新姓名",
"phone": "新手机号",
"wechat_nickname": "新昵称",
"practice_time": "45分钟",
"class_id": 2,
"notes": "新备注"
}
```
### 删除学员
```
DELETE /api/students/<id>
```
---
## 问题记录
### 获取学员的问题列表
```
GET /api/students/<student_id>/problems
```
### 添加问题
```
POST /api/students/<student_id>/problems
```
**请求体**:
```json
{
"problem_id": "01_手小",
"problem_name": "手小",
"severity": "中等",
"level": "入门"
}
```
**严重程度选项**: `轻微`, `中等`, `严重`
**级别选项**: `启蒙`, `入门`, `进阶`, `熟练`, `精通`
### 删除问题
```
DELETE /api/students/<student_id>/problems/<problem_id>
```
---
## 方案生成
### 生成练习方案
```
POST /api/generate-plan
```
**请求体**:
```json
{
"student_id": 1,
"use_ai": true
}
```
**参数说明**:
- `student_id`: 学员ID
- `use_ai`: 是否使用AI生成个性化报告(默认true)
**响应示例**:
```json
{
"plan_id": 1,
"ai_report": "## 个性化练习方案报告\n\n..."
}
```
### 获取方案详情
```
GET /api/plans/<plan_id>
```
**响应示例**:
```json
{
"id": 1,
"student_id": 1,
"student_name": "张三",
"created_at": "2026-04-17 10:30",
"content": {
"student_name": "张三",
"practice_time": "30分钟",
"total_daily_minutes": 30,
"problems": [...],
"daily_schedule": [...],
"ai_report": "..."
}
}
```
### 获取学员的方案列表
```
GET /api/students/<student_id>/plans
```
### 导出PDF
```
GET /api/plans/<plan_id>/pdf
```
**返回**: PDF文件下载
### 微信卡片展示
```
GET /plans/<plan_id>/wechat
```
**返回**: HTML页面,用于微信分享
### 删除方案
```
DELETE /api/plans/<plan_id>
```
---
## 问题配置(仅管理员)
### 获取问题列表
```
GET /api/problems
```
### 获取问题详情
```
GET /api/problems/<problem_id>
```
### 创建问题
```
POST /api/problems
```
### 更新问题
```
PUT /api/problems/<problem_id>
```
### 删除问题
```
DELETE /api/problems/<problem_id>
```
---
## API设置(仅管理员)
### 获取API配置
```
GET /api/config
```
### 更新API配置
```
POST /api/config
```
### 测试API连接
```
POST /api/config/test
```
---
## 权限说明
| 接口 | 管理员 | 普通用户 |
|------|--------|----------|
| 用户管理 | ✅ | ❌ |
| 班级增删改 | ✅ | ❌ |
| 班级查询/分配学员 | ✅ | ✅ |
| 学员管理 | ✅ | ✅ |
| 问题记录 | ✅ | ✅ |
| 方案生成 | ✅ | ✅ |
| 系统设置 | ✅ | ❌ |
| 修改自己密码 | ✅ | ✅ |
---
## 错误响应
所有接口的错误响应格式:
```json
{
"error": "错误信息描述"
}
```
状态码:
- `400` - 请求参数错误
- `401` - 未登录或认证失败
- `403` - 权限不足
- `404` - 资源不存在
- `500` - 服务器内部错误
+250
View File
@@ -0,0 +1,250 @@
# 部署指南
## 目标服务器
- **服务器**: 阿里云 ECS (CentOS 7)
- **IP**: 47.106.65.108
- **连接**: `ssh -i ~/.ssh/id_rsa root@47.106.65.108`
---
## 部署架构
```
┌─────────────────────────────┐
│ 阿里云 ECS (CentOS 7) │
│ │
│ ┌─────────────────────┐ │
│ │ Docker Container │ │
│ │ piano-plan:5000 │ │
│ └─────────────────────┘ │
│ ▲ │ │
│ │ │ │
│ ┌──────┴───┴──────────┐ │
│ │ 数据卷 (宿主机目录) │ │
│ │ /data │ │
│ │ /output │ │
│ │ /config │ │
│ └──────────────────────┘ │
└─────────────────────────────┘
```
---
## 数据分离(关键)
| 目录 | 容器内 | 宿主机 | 说明 |
|------|--------|--------|------|
| 数据库 | /app/data | ./data | SQLite数据库 |
| PDF输出 | /app/output | ./output | 导出的PDF |
| API配置 | /app/config | ./config | LLM配置 |
**优势**:
- 容器删除重建,数据不丢失
- 备份只需备份宿主机目录
---
## 部署步骤
### 1. 上传代码
```bash
# 在本地打包
cd projects/青年钢琴集体课/练习方案系统
tar -czvf piano-plan.tar.gz \
--exclude=venv \
--exclude=__pycache__ \
--exclude=.git \
-f - .
# 上传到服务器
scp -i ~/.ssh/id_rsa piano-plan.tar.gz root@47.106.65.108:/opt/
# SSH登录服务器
ssh -i ~/.ssh/id_rsa root@47.106.65.108
```
### 2. 服务器准备
```bash
# 安装 Docker(如果未安装)
curl -fsSL https://get.docker.com | sh
systemctl enable docker
systemctl start docker
# 安装 docker-composeCentOS 7
curl -L "https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
cd /opt
mkdir -p piano-plan && cd piano-plan
tar -xzf ../piano-plan.tar.gz --strip-components=1
# 创建数据目录
mkdir -p data output config
# 开放端口
firewall-cmd --permanent --add-port=5000/tcp
firewall-cmd --reload
```
### 3. 启动容器
```bash
# 方式一:使用 docker-compose(推荐)
docker-compose up -d --build
# 方式二:手动 docker run
docker build -t piano-plan .
docker run -d \
--name piano-plan \
-p 5000:5000 \
-v $(pwd)/data:/app/data \
-v $(pwd)/output:/app/output \
-v $(pwd)/config:/app/config \
-e FLASK_ENV=production \
piano-plan
```
### 4. 验证部署
```bash
# 检查容器状态
docker ps | grep piano-plan
# 检查日志
docker logs piano-plan
# 访问测试
curl http://localhost:5000/
```
### 5. 配置防火墙
```bash
# 开放5000端口(如果需要)
firewall-cmd --permanent --add-port=5000/tcp
firewall-cmd --reload
```
### 6. 配置 Nginx 反向代理(可选)
```nginx
server {
listen 80;
server_name piano.yourdomain.com; # 替换为实际域名
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
```
---
## 备份与恢复
### 备份
```bash
# 停止服务
docker-compose stop
# 打包数据
tar -czvf piano-plan-backup-$(date +%Y%m%d).tar.gz data/ output/ config/
# 重启服务
docker-compose start
```
### 恢复
```bash
# 停止服务
docker-compose stop
# 解压备份
tar -xzf piano-plan-backup-20260418.tar.gz
# 重启服务
docker-compose start
```
---
## 更新部署
```bash
# 拉取新代码(方式一:git pull)
git pull
# 或 上传新代码(方式二)
# 重复"上传代码"步骤
# 重新构建并启动
docker-compose up -d --build
# 数据不会丢失(挂载卷)
```
---
## 常用命令
```bash
# 查看日志
docker logs -f piano-plan
# 重启服务
docker-compose restart
# 停止服务
docker-compose stop
# 删除容器(数据不丢失)
docker-compose down
# 完全删除(包括镜像)
docker-compose down --rmi local
```
---
## 故障排查
### 容器启动失败
```bash
# 查看日志
docker logs piano-plan
# 检查端口
netstat -tlnp | grep 5000
```
### 数据库问题
```bash
# 进入容器检查
docker exec -it piano-plan sh
# 检查数据目录
ls -la /app/data/
```
---
## 版本信息
| 日期 | 版本 | 说明 |
|------|------|------|
| 2026-04-18 | V1.2 | 初始部署版本,含用户/角色/班级管理 |
---
## 联系
部署完成后访问: `http://47.106.65.108:5000`
+348
View File
@@ -0,0 +1,348 @@
# 钢琴练习方案系统 - 部署 SOP
> 版本:v1.1
> 日期:2026-04-21
> 核心原则:**不删除,只备份后新增/替换**
---
## 一、部署原则(铁律)
| 操作 | 允许? | 说明 |
|------|--------|------|
| 删除容器 | ❌ 禁止 | 停止即可,容器配置是资产 |
| 删除 volume | ❌ 禁止 | 数据资产,不可恢复 |
| 删除 host 文件 | ❌ 禁止 | 先备份到 `/tmp/backup_YYYYMMDD/` |
| 覆盖文件 | ⚠️ 先备份 | 任何覆盖操作前必须先备份 |
| 停止容器 | ✅ 允许 | stop 是安全的 |
| 启动新容器 | ✅ 允许 | 配合正确的挂载配置 |
### 脚本优先原则(铁律)
> **当脚本执行失败时:修复脚本,而非绕过脚本。**
| 错误行为 | 正确行为 |
|---------|---------|
| 脚本报错 → `docker rm` 手动清理 | 脚本报错 → 查看日志 → 修复脚本问题 → 重跑脚本 |
| 挂载丢失 → 手动指定新挂载 | 挂载丢失 → 更新脚本的挂载配置 → 重跑脚本 |
| 镜像加载失败 → `docker rmi` 清理 | 镜像加载失败 → 检查错误 → 重跑脚本 |
| 容器启动失败 → `docker rm` 重来 | 容器启动失败 → 查看 `docker logs` → 修复配置 → 重跑脚本 |
**一旦开始用脚本部署,就要坚持用到底,中途放弃脚本去做手动操作,等于打开了破坏系统的潘多拉魔盒。**
---
## 二、部署前检查清单
```
[ ] 确认本地代码已验证通过
[ ] 确认无未提交的代码
[ ] 确认获得用户的明确同意(用户说"部署吧"或"可以部署"
[ ] 确认需要保留的挂载点列表(见下方)
[ ] 确认 Docker Desktop 已启动(Windows
```
### 必须保留的挂载点
| 类型 | 源 | 容器内 | 说明 |
|------|-----|--------|------|
| Bind Mount | `/opt/piano-plan/个性化方案` | `/app/个性化方案` | 问题文件(15个md |
| Volume | `piano-plan-data` | `/app/data` | SQLite 数据库 |
| Volume | `piano-plan-output` | `/app/output` | PDF 输出 |
| Bind Mount | `/opt/piano-plan/config` | `/app/config` | API 配置 |
---
## 三、部署步骤
### 3.1 本地构建
```powershell
# 1. 启动 Docker DesktopWindows
Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe"
# 2. 等待 Docker 就绪
docker version # 看到 Server: Docker Desktop 即为就绪
# 3. 进入项目目录
cd "D:\F\NewI\opencode\daily-workspace\projects\青年钢琴集体课\练习方案系统"
# 4. 构建 Docker 镜像
docker build -t piano-plan:latest .
# 5. 保存镜像为 tar 文件
docker save piano-plan:latest -o piano-plan.tar
```
### 3.2 上传到服务器
```powershell
# 6. 上传到服务器临时目录
scp -i ~/.ssh/id_rsa piano-plan.tar root@47.106.65.108:/opt/piano-plan/
```
### 3.3 服务器部署(使用脚本!)
```bash
# 7. SSH 到服务器
ssh -i ~/.ssh/id_rsa root@47.106.65.108
# 8. 使用自动化部署脚本(会自动完成所有步骤并验证)
bash /path/to/deploy.sh /opt/piano-plan/piano-plan.tar
# 或者手动部署(不推荐):
# 8a. 确认当前容器挂载配置
docker inspect piano-plan --format '{{json .Mounts}}'
# 9. 创建备份
mkdir -p /opt/piano-plan/backups
docker cp piano-plan:/app/data/piano_plans.db /opt/piano-plan/backups/piano_plans.db.bak
# 10. 停止旧容器
docker stop piano-plan
docker rm piano-plan
# 11. 加载新镜像
docker load -i /opt/piano-plan/piano-plan.tar
# 12. 启动新容器(挂载配置必须完全正确!)
docker run -d \
--name piano-plan \
-p 5001:5001 \
--restart unless-stopped \
-e FLASK_ENV=production \
-v /opt/piano-plan/个性化方案:/app/个性化方案 \
-v piano-plan-data:/app/data \
-v piano-plan-output:/app/output \
-v /opt/piano-plan/config:/app/config \
piano-plan:latest
```
### 3.4 验证
```bash
# 13. 检查容器状态
docker ps --filter name=piano-plan
# 14. 检查日志
docker logs piano-plan --tail 20
# 15. 验证服务
curl -I http://localhost:5001/
# 16. 验证问题文件(应该看到15个md文件)
docker exec piano-plan ls /app/个性化方案/
# 17. 验证数据库(应该看到 templates 表)
docker exec piano-plan python -c "import sqlite3; conn=sqlite3.connect('/app/data/piano_plans.db'); print([r[0] for r in conn.execute('SELECT name FROM sqlite_master WHERE type=\"table\"')])"
# 18. 验证 API 配置
docker exec piano-plan cat /app/config/api_config.json
```
---
## 四、数据保护规范
### 4.1 必须保护的数据(绝对不删除)
| 数据类型 | 存储位置 | 说明 |
|----------|----------|------|
| 用户数据 | piano-plan-data:/app/data | users 表 |
| 学员数据 | piano-plan-data:/app/data | students, student_problems 表 |
| 班级数据 | piano-plan-data:/app/data | classes 表 |
| 练习方案 | piano-plan-data:/app/data | practice_plans 表 |
| 问题文件 | /opt/piano-plan/个性化方案 | 15个md文件 |
### 4.2 新增/更新的数据
| 数据类型 | 说明 |
|----------|------|
| templates 表 | AI提示词模板、报告导出模板 |
| api_config.json | API 配置(provider, model, api_key |
### 4.3 备份操作
```bash
# 备份数据库(volume
docker cp piano-plan:/app/data/piano_plans.db /opt/piano-plan/backups/piano_plans.db.$(date +%Y%m%d)
# 备份 API 配置
cp /opt/piano-plan/config/api_config.json /opt/piano-plan/backups/
# 列出所有备份
ls -la /opt/piano-plan/backups/
```
### 4.4 更新模板数据(部署后必做)
当代码中的模板内容更新时,需要手动更新生产数据库:
```python
# update_templates.py - 在服务器容器内执行
import sqlite3
NEW_AI_PROMPT = '''你是一位资深的钢琴教师。请根据学员的具体问题详情,生成一份个性化练习方案报告。
## 学员基本信息
- **姓名**: {student_name}
- **微信昵称**: {wechat_nickname}
- **每日可练习时间**: {practice_time}
## 学员被诊断的问题
{student_problems}
## 每个问题的详细信息和练习方法(请务必基于这些内容生成方案)
{problems}
## 任务要求
请根据上述学员的问题诊断和详细信息,生成一份针对性的练习方案报告:
1. 先简述该学员当前存在的主要问题
2. 给出一个每日练习安排建议
3. 针对每个问题给出具体的日常练习方法
4. 给出3-5条重点注意事项
请使用Markdown格式,语言专业、简洁、有鼓励性。'''
conn = sqlite3.connect('/app/data/piano_plans.db')
conn.execute("UPDATE templates SET content = ? WHERE type = 'ai_prompt'", (NEW_AI_PROMPT,))
conn.commit()
conn.close()
```
上传脚本到服务器执行:
```bash
scp update_templates.py root@47.106.65.108:/tmp/
ssh -i ~/.ssh/id_rsa root@47.106.65.108 "docker cp /tmp/update_templates.py piano-plan:/tmp/ && docker exec piano-plan python /tmp/update_templates.py"
```
---
## 五、回滚流程
### 5.1 快速回滚(推荐)
```bash
# 停止当前容器
docker stop piano-plan
docker rm piano-plan
# 使用旧镜像重新启动(如果镜像还在)
docker run -d \
--name piano-plan \
-p 5001:5001 \
--restart unless-stopped \
-e FLASK_ENV=production \
-v /opt/piano-plan/个性化方案:/app/个性化方案 \
-v piano-plan-data:/app/data \
-v piano-plan-output:/app/output \
-v /opt/piano-plan/config:/app/config \
piano-plan:latest
```
### 5.2 从备份恢复
```bash
# 恢复数据库
docker stop piano-plan
cp /opt/piano-plan/backups/piano_plans.db.bak /var/lib/docker/volumes/piano-plan-data/_data/piano_plans.db
docker start piano-plan
```
---
## 六、服务信息
| 项目 | 值 |
|------|-----|
| 生产地址 | https://piano.yoin.fun |
| SSH | `ssh -i ~/.ssh/id_rsa root@47.106.65.108` |
| 容器名 | piano-plan |
| 端口 | 5001 |
| 数据库位置 | piano-plan-data volume |
| 问题文件位置 | /opt/piano-plan/个性化方案 |
| API配置位置 | /opt/piano-plan/config |
### Volume 列表
| Volume | 容器内路径 | 说明 |
|--------|------------|------|
| piano-plan-data | /app/data | SQLite 数据库 |
| piano-plan-output | /app/output | PDF 导出 |
### Bind Mount 列表
| 源 | 容器内路径 | 说明 |
|-----|------------|------|
| /opt/piano-plan/个性化方案 | /app/个性化方案 | 问题文件 |
| /opt/piano-plan/config | /app/config | API 配置 |
---
## 七、API 配置说明
### 7.1 支持的 Provider
| Provider | Endpoint | 模型 |
|----------|----------|------|
| minimax | https://api.minimaxi.com/anthropic/v1 | MiniMax-M2.7-highspeed |
| volcengine | https://ark.cn-beijing.volces.com/api/coding/v3 | doubao-seed-2.0-pro |
| deepseek | https://api.deepseek.com | deepseek-chat |
### 7.2 API 配置存储
- 配置存储在 `/opt/piano-plan/config/api_config.json`
- 每个 provider 的 key 存储在 `api_keys` 映射中
- 切换 provider 时自动使用对应的 key
---
## 八、常见问题
### Q: 部署后问题文件看不到?
A: 检查挂载 `/opt/piano-plan/个性化方案:/app/个性化方案` 是否正确
### Q: 数据库是空的?
A: 检查 volume `piano-plan-data` 是否被错误覆盖,尝试从备份恢复
### Q: 容器无法启动?
A: 检查日志 `docker logs piano-plan`,常见原因:端口被占用、volume 权限问题
### Q: API 配置没生效?
A: 检查 `/opt/piano-plan/config/api_config.json` 是否存在且正确
### Q: SSE 不完整,提示词显示不出来或卡在95%?
A: nginx 需要为 SSE (Server-Sent Events) 配置特定的代理设置:
```nginx
location /api/generate-plan {
proxy_buffering off;
proxy_cache off;
tcp_nodelay on;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffers 8 32k;
proxy_buffer_size 32k;
proxy_max_temp_file_size 1024m;
}
```
配置文件位置:`/srv/nginx/conf/conf.d/piano.yoin.fun.conf`
---
## 九、检查清单(部署完成后必填)
```
[ ] 容器状态:running
[ ] 服务响应:HTTP 200/302
[ ] 问题文件数量:15个 md 文件
[ ] 数据库记录:users, students, classes, student_problems, practice_plans 完整
[ ] templates 表存在且包含 AI提示词模板、报告导出模板
[ ] API 配置:provider, model, api_key 正确
[ ] 功能验证:能生成练习方案
```
---
> **最后更新**2026-04-21
> **更新原因**:更新部署流程,添加数据保护规范,明确挂载点配置;添加 SSE 问题排查
+146
View File
@@ -0,0 +1,146 @@
# 钢琴练习方案系统 - 开发规范
## 版本命名规范
### 语义化版本 (SemVer)
采用 `MAJOR.MINOR.PATCH` 格式:
- **MAJOR**: 不兼容的API变更
- **MINOR**: 向后兼容的功能新增
- **PATCH**: 向后兼容的问题修复
**示例**
```
v1.0.0 - 初始版本
v1.1.0 - 新增功能(学员班级管理)
v1.1.1 - 修复bug
v2.0.0 - 重大重构(不兼容)
```
### 禁止的命名方式
❌ 禁止使用以下命名:
- `final`, `last_final`, `final_v2`
- `new`, `new2`, `new_final`
- `backup`, `backup2`
- `test`, `test2`
✅ 正确示例:
- `v1.2.0.tar``piano-plan-v1.2.0.tar`
- `docker镜像``piano-plan:v1.2.0`
---
## 文件组织规范
### 目录结构
```
piano-plan/
├── app/ # Flask应用核心代码
├── config/ # 配置文件
├── data/ # 数据库(不提交git)
├── docs/ # 项目文档
├── releases/ # 部署包(版本化)
├── scripts/ # 临时调试脚本(开发用)
├── output/ # PDF输出(自动生成)
├── app/templates/ # 前端模板
├── app/routes/ # 路由
├── app/services/ # 业务逻辑
├── run.py # 应用入口
├── run.bat # Windows启动
├── deploy.sh # 部署脚本
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
└── README.md
```
### 各类文件处理
| 类型 | 位置 | Git | 说明 |
|------|------|------|------|
| 核心代码 | `app/` | ✅ | 必须 |
| 配置文件 | `config/` | ✅ | API配置等 |
| 数据库 | `data/` | ❌ | 不提交 |
| 文档 | `docs/` | ✅ | 项目文档 |
| 部署包 | `releases/` | ❌ | 版本化后删除本地副本 |
| 临时脚本 | `scripts/` | ❌ | 开发调试用,完成后删除 |
| 输出文件 | `output/` | ❌ | 自动生成 |
| 依赖 | `venv/` | ❌ | 不提交 |
| tar包 | `releases/` | ❌ | 版本化命名 |
---
## 部署包规范
### 发布流程
1. **开发完成** → 本地测试通过
2. **构建镜像**`docker build -t piano-plan:v1.2.0 .`
3. **打包部署文件** → 创建 `releases/v1.2.0/` 目录,放入:
- `piano-plan-v1.2.0.tar.gz` - Docker镜像
- `piano-nginx.conf` - Nginx配置(从服务器获取最新)
- `docker-compose.yml` - 部署编排
4. **上传** → 传到服务器 load 镜像
5. **部署** → docker-compose up -d
6. **清理** → 本地 tar 包可删除(git已管理版本)
### 版本化部署包命名
```
piano-plan-v1.2.0.tar.gz
```
格式:`{项目名}-v{版本}.tar.gz`
---
## 临时脚本规范
### scripts/ 目录
用于存放开发过程中的临时调试脚本:
- `check_*.py` - 数据库/配置检查
- `test_*.py` - API/功能测试
- `debug_*.py` - 调试用
**规则**
1. 开发时放在 `scripts/`
2. 功能稳定后 **删除**
3. 不要提交到 git
---
## Git 提交规范
### Commit Message 格式
```
<type>: <subject>
<body>
```
**Type**:
- `feat`: 新功能
- `fix`: 修复bug
- `docs`: 文档
- `refactor`: 重构
- `deploy`: 部署
**示例**
```
feat: 添加学员管理功能
fix: 修复移动端侧边栏遮挡问题
deploy: v1.2.0 生产环境部署
```
---
*最后更新:2026-04-21*
+239
View File
@@ -0,0 +1,239 @@
# 前端架构规范
> 本文档定义钢琴练习方案系统的模板架构设计,作为前端开发的遵循规范。
## 1. 模板架构
### 1.1 继承模式
所有业务页面必须继承自 `base.html`
```
base.html (基础模板)
├── index.html (学员管理)
├── settings.html (问题配置)
├── classes.html (班级管理)
├── users.html (用户管理)
├── templates.html (模板管理)
└── api_settings.html (API设置)
```
### 1.2 base.html 结构
`base.html` 统一管理以下公共部分:
| 部分 | 说明 |
|------|------|
| CDN 引入 | Bootstrap 5.3, Bootstrap Icons, EasyMDE, Tabulator |
| 基础 CSS | body, sidebar, main-content, card 样式 |
| 移动端响应式 | 汉堡菜单、侧边栏展开/收起 |
| 顶部导航栏 | 移动端显示(固定在顶部) |
| 侧边栏结构 | 桌面端显示 |
| 公共 JS | toggleMobileNav, logout, showChangePasswordModal |
| 修改密码弹窗 | 统一放在 base 中 |
### 1.3 Jinja2 Blocks
| Block 名称 | 位置 | 用途 |
|------------|------|------|
| `title` | `<head><title>` | 页面标题 |
| `extra_css` | `<head>` 末尾 | 额外 CSS(如 CDN 样式链接) |
| `page_css` | `<style>` 末尾 | 页面特定 CSS |
| `sidebar_nav` | 侧边栏 `<nav>` 内 | 导航链接 |
| `content` | 主内容区 | 页面主要内容 HTML |
| `extra_js` | `</body>` 前 | 额外 JS(如 CDN 脚本、页面业务逻辑) |
## 2. 新页面创建规范
### 2.1 最小模板示例
```html
{% extends "base.html" %}
{% block title %}页面标题 - 钢琴练习方案系统{% endblock %}
{% block page_css %}
<style>
/* 仅放置页面特有的 CSS */
</style>
{% endblock %}
{% block sidebar_nav %}
<!-- 复制 base.html 的导航结构,根据当前页面的 active 状态调整 -->
<a class="nav-link" href="/">
<i class="bi bi-people"></i> 学员管理
</a>
<!-- ... 其他链接 ... -->
{% endblock %}
{% block content %}
<!-- 页面主要内容 HTML -->
{% endblock %}
{% block extra_js %}
<script src="https://cdn.jsdelivr.net/npm/xxx/xxx.min.js"></script>
<script>
// 页面业务逻辑 JS
</script>
{% endblock %}
```
### 2.2 侧边栏导航 active 状态
当前页面对应的链接需要添加 `active` class
```html
<a class="nav-link active" href="/settings">
<i class="bi bi-gear"></i> 问题配置
</a>
```
### 2.3 用户信息显示
base.html 在移动端和桌面端都有用户信息占位符,页面 JS 需要在 DOMContentLoaded 中设置:
```javascript
document.addEventListener('DOMContentLoaded', function() {
fetch('/api/check-login').then(r => r.json()).then(data => {
const userDisplay = data.username + ' (' + (data.role === 'admin' ? '管理员' : '普通用户') + ')';
document.getElementById('currentUserDisplay').textContent = userDisplay;
const mobileDisplay = document.getElementById('mobileUserDisplay');
if (mobileDisplay) mobileDisplay.textContent = userDisplay;
});
});
```
## 3. 移动端响应式设计
### 3.1 断点
| 断点 | 屏幕宽度 | 布局 |
|------|----------|------|
| 桌面端 | ≥ 768px | 侧边栏固定在左侧,主内容区在右侧 |
| 移动端 | < 768px | 顶部导航栏 + 隐藏侧边栏(汉堡按钮展开) |
### 3.2 CSS 规范
所有移动端样式统一写在 `base.html``@media (max-width: 767.98px)` 块中,各页面不需要重复定义。
侧边栏移动端样式(base.html):
```css
/* 移动端响应式 */
@media (max-width: 767.98px) {
.sidebar {
position: fixed;
top: 60px; /* 向下偏移,避免被顶部导航栏挡住 */
left: 0;
width: 100%;
min-height: auto;
max-height: calc(100vh - 60px); /* 最大高度为屏幕高度减去导航栏 */
z-index: 1040;
overflow-y: auto;
transform: translateY(-100%); /* 默认隐藏 */
transition: transform 0.3s ease;
}
.sidebar.collapsed {
transform: translateY(-100%); /* 隐藏状态 */
}
.sidebar:not(.collapsed) {
transform: translateY(0); /* 显示状态 */
}
.main-content {
margin-top: 60px;
padding: 10px;
}
.mobile-nav-toggle {
display: flex !important;
}
}
```
### 3.3 移动端导航栏 HTMLbase.html
```html
<nav class="mobile-nav-toggle navbar navbar-dark bg-dark d-flex d-md-none"
style="position:fixed;top:0;left:0;right:0;z-index:1050;padding:10px;">
<div class="container-fluid d-flex justify-content-between align-items-center">
<div>
<span class="navbar-brand mb-0"><i class="bi bi-music-note-beamed"></i> 钢琴方案</span>
<small id="mobileUserDisplay" class="d-block text-white-50" style="font-size:10px;"></small>
</div>
<button class="btn btn-outline-light btn-sm" onclick="toggleMobileNav()">
<i class="bi bi-list"></i>
</button>
</div>
</nav>
```
**注意**
- `mobile-nav-toggle` 类控制显示/隐藏(桌面端隐藏,移动端显示)
- `z-index: 1050` 确保在侧边栏之上(侧边栏是 1040)
## 4. JavaScript 规范
### 4.1 公共函数(base.html 提供)
| 函数 | 用途 |
|------|------|
| `toggleMobileNav()` | 切换侧边栏展开/收起 |
| `logout()` | 退出登录 |
| `showChangePasswordModal()` | 显示修改密码弹窗 |
### 4.2 页面 JS 放置
- 公共函数(如 `showToast`)放在 `extra_js` block 中
- 页面初始化逻辑放在 `document.addEventListener('DOMContentLoaded', ...)`
- 业务函数在 `extra_js` block 中定义
### 4.3 修改密码
base.html 已包含修改密码弹窗 HTML 和 JS。各页面不需要重复定义 `showChangePasswordModal()` 和相关 DOM 绑定。
## 5. 避免重复代码
### 5.1 禁止重复的内容
以下内容**禁止**在各页面模板中重复定义:
- `<head>` 标签内容(meta, title, CDN 链接)
- 基础 CSSbody, sidebar, card 等)
- 移动端响应式 CSS
- 移动端顶部导航栏 HTML
- 侧边栏 HTML 结构
- Bootstrap JS CDN
- `toggleMobileNav()`, `logout()`, `showChangePasswordModal()` 函数
- 修改密码弹窗 HTML 和 JS
### 5.2 正确做法
| 如果需要... | 应该... |
|-------------|---------|
| 页面特定 CSS | 在 `{% block page_css %}` 中添加 |
| 额外 JS 库 | 在 `{% block extra_js %}` 开头引入 |
| 页面业务逻辑 | 在 `{% block extra_js %}` 中定义 |
| 导航 active 状态 | 在 `{% block sidebar_nav %}` 中设置 |
## 6. 文件结构
```
app/templates/
├── base.html # 基础模板(核心)
├── index.html # 学员管理
├── settings.html # 问题配置
├── classes.html # 班级管理
├── users.html # 用户管理
├── templates.html # 模板管理
├── api_settings.html # API设置
├── login.html # 登录页(独立,无侧边栏)
├── setup.html # 初始化页(独立)
└── wechat_card.html # 微信卡片(独立)
```
> 注意:`login.html`、`setup.html`、`wechat_card.html` 是独立页面,不继承 base.html。
## 7. 更新日志
| 日期 | 版本 | 变更内容 |
|------|------|----------|
| 2026-04-21 | v1.0 | 初始文档,定义 base.html 模板继承模式 |
+281
View File
@@ -0,0 +1,281 @@
# 数据模型说明
## 概述
本系统使用 SQLite 数据库,ORM 框架为 Flask-SQLAlchemy。
**数据库文件**: `data/piano_plans.db`
---
## 数据表
### 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 | String(50) | 问题编号,如 "01_手小" |
| problem_name | String(100) | 问题名称,如 "手小" |
| severity | String(10) | 严重程度:轻微/中等/严重 |
| level | String(20) | 级别:启蒙/入门/进阶/熟练/精通 |
| created_at | DateTime | 创建时间 |
---
### 4. Class (班级)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| name | String(100) | 班级名称,必填 |
| description | Text | 班级描述,可选 |
| created_at | DateTime | 创建时间 |
**关系**:
- `students`: 与 Student 一对多
---
### 5. PracticePlan (练习方案)
| 字段 | 类型 | 说明 |
|------|------|------|
| id | Integer | 主键,自增 |
| student_id | Integer | 外键,关联 Student |
| content | Text | 方案内容(JSON格式) |
| created_at | DateTime | 创建时间 |
**content 字段结构**:
```json
{
"student_name": "张三",
"practice_time": "30分钟",
"total_daily_minutes": 30,
"problems": [
{
"name": "手小",
"severity": "中等",
"level": "入门",
"focus": {"basic": 15, "tech": 10, "piece": 20}
}
],
"daily_schedule": [
{
"phase": "热身",
"duration": "3分钟",
"content": "手部放松操 + 呼吸调节",
"purpose": "放松肌肉,进入状态"
}
],
"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_name │
│ 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 problem in student.problems:
print(problem.problem_name, problem.severity, problem.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分钟以上 | 竞技水平 |
+279
View File
@@ -0,0 +1,279 @@
# 项目结构说明
## 目录概览
```
练习方案系统/
├── docs/ # 项目文档
│ ├── API.md # API接口文档
│ ├── MODELS.md # 数据模型说明
│ └── STRUCTURE.md # 本文件 - 项目结构
├── app/ # Flask应用主目录
│ ├── __init__.py # 应用工厂
│ ├── config.py # 配置管理
│ ├── models.py # 数据库模型
│ │
│ ├── routes/ # 路由模块
│ │ ├── __init__.py # 蓝图注册
│ │ ├── auth.py # 登录认证
│ │ ├── students.py # 学员管理API
│ │ ├── problems.py # 问题记录API
│ │ ├── plans.py # 方案生成API
│ │ ├── settings.py # 系统设置API
│ │ └── classes.py # 班级管理API(新增)
│ │
│ ├── services/ # 业务逻辑
│ │ ├── plan_generator.py # 方案生成器
│ │ └── pdf_generator.py # PDF生成器
│ │
│ └── templates/ # 前端模板
│ ├── base.html # 基础模板(所有页面继承)
│ ├── index.html # 学员管理页面(继承base)
│ ├── settings.html # 问题配置页面(继承base)
│ ├── login.html # 登录页面(独立)
│ ├── setup.html # 初始设置页面(独立)
│ ├── users.html # 用户管理页面(继承base)
│ ├── classes.html # 班级管理页面(继承base)
│ ├── templates.html # 模板管理页面(继承base)
│ ├── api_settings.html # API设置页面(继承base
│ └── wechat_card.html # 微信卡片模板(独立)
├── data/ # 数据目录(运行时创建)
│ └── piano_plans.db # SQLite数据库
├── output/ # PDF输出目录(运行时创建)
├── config/ # 配置目录(运行时创建)
│ └── api_config.json # API配置文件
├── 个性化方案/ # 练习方案内容
│ └── 针对性练习(拆分为单独文件)/
│ ├── 01_手小.md
│ ├── 02_识谱慢.md
│ └── ...
├── run.py # 应用入口
├── run.bat # 启动脚本
├── requirements.txt # Python依赖
└── README.md # 项目说明
```
---
## 核心模块
### app/__init__.py
应用工厂,负责:
- 创建Flask应用实例
- 加载配置
- 初始化数据库
- 注册蓝图
- 创建必要目录
```python
def create_app():
app = Flask(__name__)
# 加载配置...
db.init_app(app)
app.register_blueprint(main_bp)
# 创建目录...
return app
```
### app/config.py
配置文件,包含:
- 数据库路径
- 问题文件目录
- PDF输出目录
- API配置加载/保存函数
- 权限配置
### app/models.py
数据库模型定义:
- `User` - 用户(登录认证、权限管理)
- `Student` - 学员
- `StudentProblem` - 问题记录
- `Class` - 班级(新增)
- `PracticePlan` - 练习方案
---
## 路由详解
### routes/auth.py
| 路由 | 方法 | 说明 |
|------|------|------|
| `/login` | GET | 登录页面 |
| `/api/login` | POST | 登录API |
| `/api/logout` | POST | 登出API |
| `/api/check-login` | GET | 检查登录状态 |
| `/setup` | GET | 初始设置页面 |
| `/api/setup` | POST | 初始设置API |
### routes/students.py
| 路由 | 方法 | 说明 |
|------|------|------|
| `/api/students` | GET | 获取学员列表 |
| `/api/students` | POST | 创建学员 |
| `/api/students/<id>` | GET | 获取学员详情 |
| `/api/students/<id>` | PUT | 更新学员 |
| `/api/students/<id>` | DELETE | 删除学员 |
### routes/problems.py
| 路由 | 方法 | 说明 |
|------|------|------|
| `/api/students/<id>/problems` | GET | 获取学员问题 |
| `/api/students/<id>/problems` | POST | 添加问题 |
| `/api/students/<id>/problems/<pid>` | DELETE | 删除问题 |
### routes/plans.py
| 路由 | 方法 | 说明 |
|------|------|------|
| `/api/generate-plan` | POST | 生成练习方案 |
| `/api/plans/<id>` | GET | 获取方案详情 |
| `/api/plans/<id>/pdf` | GET | 导出PDF |
| `/plans/<id>/wechat` | GET | 微信卡片 |
| `/api/plans/<id>` | DELETE | 删除方案 |
### routes/settings.py
| 路由 | 方法 | 说明 | 权限 |
|------|------|------|------|
| `/settings` | GET | 设置页面 | 管理员 |
| `/api/problems` | GET | 获取问题列表 | 管理员 |
| `/api/problems` | POST | 创建问题 | 管理员 |
| `/api/problems/<id>` | GET | 问题详情 | 管理员 |
| `/api/problems/<id>` | PUT | 更新问题 | 管理员 |
| `/api/problems/<id>` | DELETE | 删除问题 | 管理员 |
| `/api/config` | GET | 获取API配置 | 管理员 |
| `/api/config` | POST | 更新API配置 | 管理员 |
| `/api/config/test` | POST | 测试API连接 | 管理员 |
### routes/classes.py(新增)
| 路由 | 方法 | 说明 | 权限 |
|------|------|------|------|
| `/api/classes` | GET | 班级列表 | 登录用户 |
| `/api/classes` | POST | 新增班级 | 管理员 |
| `/api/classes/<id>` | PUT | 编辑班级 | 管理员 |
| `/api/classes/<id>` | DELETE | 删除班级 | 管理员 |
| `/api/classes/<id>/students` | GET | 班级学员 | 登录用户 |
| `/api/classes/<id>/assign` | POST | 分配学员 | 登录用户 |
### routes/users.py(新增)
| 路由 | 方法 | 说明 | 权限 |
|------|------|------|------|
| `/users` | GET | 用户管理页面 | 管理员 |
| `/api/users` | GET | 用户列表 | 管理员 |
| `/api/users` | POST | 新增用户 | 管理员 |
| `/api/users/<id>` | PUT | 编辑用户 | 管理员 |
| `/api/users/<id>` | DELETE | 删除用户 | 管理员 |
| `/api/users/<id>/reset-password` | POST | 重置密码 | 管理员 |
| `/api/users/change-password` | POST | 修改自己密码 | 登录用户 |
---
## 服务层
### services/plan_generator.py
核心业务逻辑:
```python
# 生成基础练习方案
generate_practice_plan(student_name, problems, problems_dir)
# 生成AI报告(调用LLM
generate_ai_report(student_name, problems, practice_time, time_config)
```
### services/pdf_generator.py
使用 reportlab 生成中文PDF
```python
generate_pdf(plan_id, student_name, content, output_dir)
```
---
## 权限系统
### 角色
| 角色 | 说明 |
|------|------|
| admin | 管理员,拥有所有权限 |
| user | 普通用户,受限权限 |
### 权限矩阵
| 模块 | 管理员 | 普通用户 |
|------|--------|----------|
| 用户管理 | 增删改查 | ❌ |
| 班级管理 | 增删改 | 查询+分配 |
| 学员管理 | 增删改查 | 增删改查 |
| 问题记录 | 增删改查 | 增删改查 |
| 方案生成 | ✅ | ✅ |
| 系统设置 | ✅ | ❌ |
| 修改密码 | ✅ | ✅ |
### 权限装饰器
```python
# 后端权限装饰器
@admin_required # 仅管理员
@login_required # 需登录
# 前端根据 role 动态显示菜单和按钮
```
---
## 启动流程
1. 双击 `run.bat`
2. 脚本自动:
- 删除旧venv(如有)
- 创建新venv
- 安装依赖
- 启动Flask服务
3. 首次访问 `/setup` 创建管理员
4. 登录后使用系统
---
## 扩展开发
### 新增功能步骤
1. **数据模型**:在 `app/models.py` 添加新模型
2. **路由**:在 `app/routes/` 添加新路由文件
3. **业务逻辑**:在 `app/services/` 添加服务
4. **前端**:在 `app/templates/` 添加模板
5. **文档**:更新 `docs/` 目录
### 添加新依赖
1. 修改 `requirements.txt`
2. 运行 `pip install -r requirements.txt`
3. 更新 `run.bat` 中的安装命令
---
## 版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| V1.0 | 2026-04-17 | 初始版本:学员管理、问题记录、方案生成 |
| V1.1 | 2026-04-17 | 添加用户登录认证系统 |
| V1.2 | 2026-04-18 | 添加用户管理、角色权限、班级管理 |
File diff suppressed because it is too large Load Diff