feat(courseware,docs): 完善课件传输与岛编辑并统一全仓文档

- 增加课件上传、班级可见范围、设备补收与传输进度
- 整合自定义灵动岛编辑器和相关客户端交互
- 同步三端契约、数据库迁移、部署配置与隔离回归
- 按源码统一文档与官网说明,移除过时任务书和审查记录
- 保持产品版本为 1.4.0
This commit is contained in:
LoveChengke committed 2026-10-18 11:53:37 +08:00
1 parent 536379036a
commit c7f215a09c
127 files changed
+4334 -8482

No files matched your search

@@ -0,0 +1,35 @@
CREATE TABLE "Courseware" (
"id" TEXT NOT NULL PRIMARY KEY, "ownerId" TEXT NOT NULL, "name" TEXT NOT NULL,
"size" INTEGER NOT NULL, "category" TEXT NOT NULL, "extension" TEXT NOT NULL,
"archiveDate" TEXT NOT NULL, "storagePath" TEXT NOT NULL, "sha256" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Courseware_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
CREATE UNIQUE INDEX "Courseware_storagePath_key" ON "Courseware"("storagePath");
CREATE INDEX "Courseware_ownerId_createdAt_idx" ON "Courseware"("ownerId", "createdAt");
CREATE TABLE "CoursewareVisibility" (
"coursewareId" TEXT NOT NULL, "classId" TEXT NOT NULL, PRIMARY KEY ("coursewareId", "classId"),
FOREIGN KEY ("coursewareId") REFERENCES "Courseware"("id") ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ("classId") REFERENCES "Class"("id") ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX "CoursewareVisibility_classId_idx" ON "CoursewareVisibility"("classId");
CREATE TABLE "CoursewarePush" (
"id" TEXT NOT NULL PRIMARY KEY, "coursewareId" TEXT NOT NULL, "classId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY ("coursewareId") REFERENCES "Courseware"("id") ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ("classId") REFERENCES "Class"("id") ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE INDEX "CoursewarePush_classId_createdAt_idx" ON "CoursewarePush"("classId", "createdAt");
CREATE TABLE "CoursewareReceipt" (
"pushId" TEXT NOT NULL, "deviceId" TEXT NOT NULL, "completedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY ("pushId", "deviceId"),
FOREIGN KEY ("pushId") REFERENCES "CoursewarePush"("id") ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY ("deviceId") REFERENCES "ClientDevice"("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- 仅为旧默认组和内置教学岗位补充新能力;自定义组仍需管理员按需授权。
INSERT INTO "PermissionGroupItem" ("id", "groupId", "key")
SELECT 'cw-read-' || "id", "id", 'courseware:read' FROM "PermissionGroup"
WHERE "defaultRole" IN ('ADMIN', 'TEACHER') OR "id" IN ('builtin-head', 'builtin-teacher');
INSERT INTO "PermissionGroupItem" ("id", "groupId", "key")
SELECT 'cw-write-' || "id", "id", 'courseware:write' FROM "PermissionGroup"
WHERE "defaultRole" IN ('ADMIN', 'TEACHER') OR "id" IN ('builtin-head', 'builtin-teacher');
+51
View File
@@ -34,6 +34,7 @@ datasource db {
/// 账号:管理员 / 教师 / ClassHelper 班级端。**学生不在这里**(见 Student)。
model User {
coursewares Courseware[]
id String @id @default(cuid())
/// 登录名:教师即**工号**,班级端即班级码
username String @unique
@@ -201,6 +202,8 @@ model TermWeek {
/// `grade`(高一/高二/高三)表示**当前年级**,学年升级时批量改它即可 ——
/// 升级只动这一个字段,班级记录、学生名单、作业与成绩全部沿用,**不归档**。
model Class {
coursewares CoursewareVisibility[]
coursewarePushes CoursewarePush[]
id String @id @default(cuid())
/// 班级称呼:由 enrollmentYear + classIndex 生成,如「2026级1班」
name String
@@ -501,6 +504,7 @@ model AppSetting {
/// 服务端由此知道**每台机器在哪个班、版本多少、是否在线**,并把更新与远控
/// 指令按「查库 + 心跳拉取」的范式下发,不新增长连接握手。
model ClientDevice {
coursewareReceipts CoursewareReceipt[]
id String @id @default(cuid())
/// 该设备当前登录的班级(未登录时为空)
classId String?
@@ -717,3 +721,50 @@ model PromotionRun {
createdAt DateTime @default(now())
finishedAt DateTime?
}
/// 课件只在完整接收并计算摘要后入库;存储路径不对客户端暴露。
model Courseware {
id String @id @default(cuid())
ownerId String
name String
size Int
category String
extension String
archiveDate String
storagePath String @unique
sha256 String
createdAt DateTime @default(now())
owner User @relation(fields: [ownerId], references: [id], onDelete: Restrict)
visibility CoursewareVisibility[]
pushes CoursewarePush[]
@@index([ownerId, createdAt])
}
model CoursewareVisibility {
coursewareId String
classId String
courseware Courseware @relation(fields: [coursewareId], references: [id], onDelete: Cascade)
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
@@id([coursewareId, classId])
@@index([classId])
}
/// 下发持久化,班级离线时不丢失;每台设备完成下载后单独回执。
model CoursewarePush {
id String @id @default(cuid())
coursewareId String
classId String
createdAt DateTime @default(now())
courseware Courseware @relation(fields: [coursewareId], references: [id], onDelete: Cascade)
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
receipts CoursewareReceipt[]
@@index([classId, createdAt])
}
model CoursewareReceipt {
pushId String
deviceId String
completedAt DateTime @default(now())
push CoursewarePush @relation(fields: [pushId], references: [id], onDelete: Cascade)
device ClientDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)
@@id([pushId, deviceId])
}
+1
View File
@@ -78,6 +78,7 @@ async function resetDatabase(): Promise<void> {
await prisma.clientCommand.deleteMany();
await prisma.clientDevice.deleteMany();
await prisma.clientRelease.deleteMany();
await prisma.courseware.deleteMany();
await prisma.aiAudit.deleteMany();
await prisma.userPermissionGroup.deleteMany();
await prisma.permissionGroupItem.deleteMany();