Appearance
💻 开发者指南
约束
以下路径是硬编码的,目录结构不能动:
qx-dashboard-ts/vite.config.ts→outDir: ../../qc-pocket-space/frontend/dashboardbackend/main.py→_DATA_ROOT: ../../data/qx-launcher/app/config.py→ 向上找兄弟qc-pocket-space/tests/seed_dev_data.py→PROJECT_ROOT向上 3 级
数据组织概述
所有数据都在 data/ 目录下,结构如下:
data/
├── qingcheng.db ← SQLite 数据库(用户账号 + 回收站)
├── users/ ← 用户文件数据
│ ├── admins/{id}/ ← 管理员
│ ├── teachers/{id}/ ← 教师
│ └── students/{id}/ ← 学生
├── courses/ ← 课件素材
├── trash/ ← 回收站
└── share/ ← 共享文件核心设计原则:数据库只管理登录和角色,所有业务数据走文件系统。
qingcheng.db — 数据库
位置:data/qingcheng.db(SQLite)
users 表 — 用户账号
python
class UserModel(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True) # 用户 ID
username = Column(String, unique=True) # 登录名(全局唯一)
password = Column(String) # 密码(明文)
role = Column(String) # "admin" / "teacher" / "student"
avatar_path = Column(String) # 头像路径
qc_descriptors = Column(Text) # JSON 字符串:自定义描述符| 字段 | 示例 | 说明 |
|---|---|---|
id | 1 | 自增主键,对应文件目录名users/teachers/1/ |
username | Arin | 登录账号,全局唯一 |
password | 111111 | 明文存储,当前设计 |
role | teacher | 角色权限 |
avatar_path | avatar_01.jpg | 头像文件名 |
qc_descriptors | {} 或 {"teacher":"Arin"} | JSON 字符串,info.json 中的 descriptors 来源 |
trash 表 — 回收站
python
class TrashModel(Base):
__tablename__ = "trash"
id = Column(Integer, primary_key=True)
username = Column(String) # 被删用户名
role = Column(String) # 被删用户角色
original_path = Column(String) # 原文件路径
trash_folder = Column(String) # 回收站文件夹名
db_snapshot = Column(Text) # JSON:用户数据快照(用于恢复)
trashed_at = Column(String) # 删除时间数据库 vs 文件系统
| 维度 | users 表(qingcheng.db) | 文件系统(data/) |
|---|---|---|
| 用户标识 | id = 5 | students/5/ ← 目录名 = id |
| 登录凭证 | username | info.json 里的 username 是冗余 |
| 密码 | password | 不存文件系统 |
| 角色 | role | info.json 的 role(冗余存储) |
| 描述符 | qc_descriptors | info.json 的 descriptors(冗余存储) |
| 业务数据 | 无 | classes.json, sessions/, notes/, messages/ 等 |
关键理解: DB 和文件系统通过 id(目录名)关联。DB 管登录校验,文件系统管业务数据。两者是引用关系,不是同步关系。
关键理解: DB 和文件系统通过 id(目录名)关联。DB 管登录校验,文件系统管业务数据。两者是引用关系,不是同步关系。
管理员 (admin)
admins/{id}/
└── info.json管理员只有基本账号信息,没有班级、课堂记录等业务数据。
数据库中: users 表,role = "admin"
相关 API:
POST /api/register— 注册教师账号(仅 admin 可操作)DELETE /api/users/{username}— 删除用户(移至回收站)GET /api/trash— 查看回收站POST /api/trash/restore/{id}— 恢复被删用户DELETE /api/trash— 清空回收站
教师 (teacher)
teachers/{id}/
├── info.json ← 基本信息
├── classes.json ← 班级花名册(核心)
└── sessions/ ← 课堂记录
├── 初级班/
│ ├── 2026-07.json
│ └── 2026-06.json
└── 高级班/
└── 2026-07.jsoninfo.json
json
{
"username": "Arin",
"role": "teacher",
"avatar_path": "avatar_01.jpg",
"descriptors": {},
"subfolders": []
}descriptors— 自定义描述符(JSON 对象),存储在 DBqc_descriptors字段subfolders— 教师为[](学生有["sb3_projects", "notes"])
classes.json — 班级花名册(核心)
json
[
{
"name": "初级班",
"schedule": ["2026-07-13"],
"students": [
{ "user_id": 5, "name": "张三" },
{ "user_id": 8, "name": "李四" }
]
},
{
"name": "高级班",
"schedule": ["2026-07-14"],
"students": [
{ "user_id": 12, "name": "王五" }
]
}
]| 字段 | 类型 | 说明 |
|---|---|---|
name | string | 班级名称,同教师下唯一 |
schedule | string[] | 上课日期列表,UI 未使用此字段 |
students | object[] | 班级学生列表 |
students[].user_id | int | 对应 DBusers 表的 id |
students[].name | string | 学生显示名 |
关键设计:
classes.json是师生归属关系的唯一凭证- 删除学生 = 从
students移除该user_id(历史记录不受影响) - 同一学生可在不同老师的
classes.json中出现(跨老师转班) - 同名不同人需手动加后缀(如张三(小明)、张三(小红))
API:
| 方法 | 路由 | 作用 |
|---|---|---|
| GET | /api/classes | 获取当前教师的所有班级 |
| POST | /api/classes | 创建班级 |
| PUT | /api/classes/{name} | 修改班级名称(同步重命名 sessions 目录) |
| DELETE | /api/classes/{name} | 删除班级(移到回收站,sessions 历史保留) |
| POST | /api/classes/{name}/students | 添加学生 |
| DELETE | /api/classes/{name}/students/{id} | 从班级移除学生 |
sessions — 课堂记录
sessions/{班级}/{YYYY-MM}.jsonjson
[
{
"date": "2026-07-13",
"students": [
{ "name": "张三", "remark": "", "status": "present",
"content": "循环结构掌握良好", "next_plan": "函数入门" },
{ "name": "试听生", "remark": "临时",
"status": "present", "content": "正常推进", "next_plan": "正常推进" }
]
}
]| 字段 | 类型 | 说明 |
|---|---|---|
date | string | 上课日期YYYY-MM-DD,同班级下唯一,覆盖保存 |
students[].name | string | 学生姓名 |
students[].remark | string | "临时" = 临时学生(不在 classes.json 中) |
students[].status | string | "present" 出勤 / "absent" 缺勤 |
students[].content | string | 本节课内容 |
students[].next_plan | string | 下节课计划 |
关键设计:
- 删除班级不删除 sessions 目录,历史记录永远保留
- 班级改名时 sessions 目录同步重命名
- 临时学生只存在此处,不影响 classes.json
API:
| 方法 | 路由 | 作用 |
|---|---|---|
| GET | /api/sessions/{class} | 获取某班级某月的课堂记录 |
| POST | /api/sessions/{class} | 保存某日期的课堂记录(覆盖) |
| DELETE | /api/sessions/{class}/{date} | 删除某日期的课堂记录 |
| GET | /api/sessions/student/{name} | 全局扫描所有教师 sessions,查学生全部历史 |
学生 (student)
students/{id}/
├── info.json ← 基本信息
├── sb3_projects/ ← Scratch 作品
├── notes/ ← 笔记本 + .md 笔记
│ ├── Scratch/
│ │ ├── 入门.md
│ │ └── 循环结构.md
│ └── Python/
│ └── 变量.md
└── messages/ ← 站内信
└── 收件箱/
└── 20260713143052_Arin.jsoninfo.json
json
{
"username": "张三",
"role": "student",
"avatar_path": "avatar_37.jpg",
"descriptors": { "teacher": "Arin" },
"subfolders": ["sb3_projects", "notes"]
}descriptors.teacher— 创建该学生的教师(仅标签,不参与权限校验)
站内信
json
{
"id": "20260713143052_Arin",
"sender": "Arin",
"subject": "课堂笔记",
"body": "支持 **Markdown** 语法",
"created_at": "2026-07-13 14:30:52",
"is_read": false
}学生与教师的关联逻辑
关联通过 classes.json 的 user_id 引用实现,不是 DB 外键。
DB: users 表 → 只管登录、角色权限
FS: classes.json(教师的文件) → 管师生归属
↓ 通过 user_id 引用 DB
↓ 通过全局扫描 sessions 找学生历史跨教师查询: /api/sessions/student/{name} 扫描所有教师的 sessions/ 目录,所以学生的课堂记录不受换班、换老师影响。
文件关系:
teacher → classes.json(花名册)
├── 班级 A → students: [张三, 李四]
│ └── sessions/A/ → 张三的记录
└── 班级 B → students: [王五, 张三]
└── sessions/B/ → 张三的记录
↓ 全局扫描
张三的全部历史