kindergarten_java/docs/design/23-校本课程包功能完善设计.md
En 673214481d feat: 课程包功能完善与代码优化
后端:
- 新增 YesNo 枚举类
- 新增 LessonStepCreateRequest、PackageGrantRequest 等 DTO
- 新增 ResourceItemCreateRequest、ResourceLibraryCreateRequest
- 新增 StatsService 统计服务实现
- 优化 AdminCourseController、AdminResourceController 等控制器
- 完善 TenantService 套餐授权功能

前端:
- 优化套餐详情页和列表页展示
- 更新自动生成的 API 类型定义

文档:
- 更新设计文档和开发日志

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 15:03:02 +08:00

554 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 校本课程包功能完善设计
> 设计日期: 2026-03-11
> 设计者: Claude
> 版本: 1.0
---
## 一、需求概述
### 1.1 核心概念
**校本课程包** = 课程中心标准课程包的副本 + 二次编辑 + 保存到个人/校本中心
```
标准课程包 → 复制 → 编辑 → 保存到个人课程中心 / 校本课程中心
```
### 1.2 使用场景
| 场景 | 描述 | 保存位置 |
|------|------|----------|
| 教师个人使用 | 教师复制标准课程包,根据自己班级情况调整 | 个人课程中心 |
| 学校共享 | 教师创建优质校本课程包,分享给本校其他教师 | 校本课程中心 |
### 1.3 功能目标
1. **完整编辑** - 复用超管端7步编辑流程
2. **灵活保存** - 支持保存到个人或提交到学校
3. **数据隔离** - 校本课程包数据独立存储,不修改原始课程包
---
## 二、数据模型设计
### 2.1 现有模型
```prisma
// 校本课程包V2新增
model SchoolCourse {
id Int @id @default(autoincrement())
tenantId Int @map("tenant_id")
sourceCourseId Int @map("source_course_id") // 源课程包ID
name String
description String?
createdBy Int @map("created_by")
changesSummary String? @map("changes_summary") // 修改说明
usageCount Int @default(0) @map("usage_count")
status String @default("ACTIVE") // ACTIVE, PENDING, REJECTED
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
tenant Tenant @relation(fields: [tenantId], references: [id])
sourceCourse Course @relation(fields: [sourceCourseId], references: [id])
lessons SchoolCourseLesson[]
reservations SchoolCourseReservation[]
@@index([tenantId])
@@map("school_courses")
}
// 校本课程
model SchoolCourseLesson {
id Int @id @default(autoincrement())
schoolCourseId Int @map("school_course_id")
sourceLessonId Int @map("source_lesson_id")
lessonType String @map("lesson_type") // INTRODUCTION, COLLECTIVE, LANGUAGE, etc.
// 可编辑字段(当前已支持)
objectives String?
preparation String?
extension String?
reflection String?
changeNote String? @map("change_note")
// 需要扩展
stepsData String? @map("steps_data") // JSON: 完整的课程配置数据
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
schoolCourse SchoolCourse @relation(fields: [schoolCourseId], references: [id])
@@index([schoolCourseId])
@@map("school_course_lessons")
}
```
### 2.2 数据模型扩展
#### 2.2.1 SchoolCourse 扩展
```prisma
model SchoolCourse {
// ... 现有字段 ...
// ===== 新增:完整课程数据字段 =====
// 基本信息(从 Course 复制)
themeId Int? @map("theme_id")
gradeTags String @default("[]") @map("grade_tags")
domainTags String @default("[]") @map("domain_tags")
duration Int @default(25)
coverImagePath String? @map("cover_image_path")
// 课程介绍8个字段
introSummary String? @map("intro_summary")
introHighlights String? @map("intro_highlights")
introGoals String? @map("intro_goals")
introSchedule String? @map("intro_schedule")
introKeyPoints String? @map("intro_key_points")
introMethods String? @map("intro_methods")
introEvaluation String? @map("intro_evaluation")
introNotes String? @map("intro_notes")
// 排课参考和环创建设
scheduleRefData String? @map("schedule_ref_data")
environmentConstruction String? @map("environment_construction")
// ===== 新增:保存位置和状态 =====
// 保存位置PERSONAL个人 / SCHOOL校本中心
saveLocation String @default("PERSONAL") @map("save_location")
// 审核状态(仅 SCHOOL 需要审核)
reviewStatus String @default("PENDING") @map("review_status") // PENDING, APPROVED, REJECTED
reviewedBy Int? @map("reviewed_by")
reviewedAt DateTime? @map("reviewed_at")
reviewComment String? @map("review_comment")
}
```
#### 2.2.2 SchoolCourseLesson 扩展
```prisma
model SchoolCourseLesson {
// ... 现有字段 ...
// ===== 新增:完整课程配置 =====
name String
description String?
duration Int @default(25)
// 资源
videoPath String? @map("video_path")
videoName String? @map("video_name")
pptPath String? @map("ppt_path")
pptName String? @map("ppt_name")
pdfPath String? @map("pdf_path")
pdfName String? @map("pdf_name")
// stepsData: JSON格式存储完整课程配置
// 结构:
// {
// "resources": { "images": [], "videos": [], "audioList": [], "pptFiles": [], "documents": [] },
// "steps": [
// { "id": 1, "name": "热身", "stepType": "WARMUP", "duration": 5, "objective": "...", "description": "...", "script": "...", "resources": {...} },
// ...
// ]
// }
stepsData String? @map("steps_data")
}
```
---
## 三、功能设计
### 3.1 创建流程
```
1. 选择源课程包
2. 复制源课程包数据7步数据 + 课程配置)
3. 进入编辑页面复用超管端7步组件
4. 编辑课程内容
5. 选择保存位置
- 保存到个人课程中心 → 直接保存
- 提交到校本课程中心 → 进入审核流程
```
### 3.2 编辑页面设计
#### 方案A创建新页面推荐
**文件路径:**
- 教师端:`/views/teacher/school-courses/SchoolCourseEditView.vue`
- 学校端:`/views/school/school-courses/SchoolCourseEditView.vue`
**页面结构:**
```
┌─────────────────────────────────────────────┐
│ [返回] 编辑校本课程包 │
│ [保存草稿] [保存到个人] [提交到校本中心] │
├─────────────────────────────────────────────┤
│ 步骤: [基本信息] → [课程介绍] → ... → [环创建设] │
├─────────────────────────────────────────────┤
│ │
│ 当前步骤内容(复用超管端组件) │
│ │
├─────────────────────────────────────────────┤
│ [上一步] [下一步] [保存] │
└─────────────────────────────────────────────┘
```
#### 方案B扩展现有编辑页面
在超管端 `CourseEditView.vue` 增加模式参数:
```typescript
interface Props {
mode?: 'admin' | 'school'; // 编辑模式
sourceCourseId?: number; // 源课程包IDschool模式
}
```
**评估:**
- 方案A代码独立易于维护但需要复制较多代码
- 方案B代码复用但耦合度高可能影响超管端功能
**推荐方案A** - 独立页面,但通过以下方式复用组件:
1. 将超管端的7个 Step 组件提取到 `src/components/course-edit/`
2. 教师端/学校端编辑页面引用这些共享组件
### 3.3 数据流转
```
┌─────────────────────────────────────────────────────────────┐
│ 数据流转 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 源课程包 (Course + CourseLesson + LessonStep) │
│ ↓ │
│ 复制数据到 formData │
│ ↓ │
│ 7步编辑Step1-7 组件) │
│ ↓ │
│ 保存到 SchoolCourse + SchoolCourseLesson │
│ ↓ │
│ 上课时读取 SchoolCourse 数据 │
│ │
└─────────────────────────────────────────────────────────────┘
```
### 3.4 步骤数据结构
```typescript
interface SchoolCourseFormData {
// 基本信息
basic: {
name: string;
themeId?: number;
grades: string[];
pictureBookName: string;
coreContent: string;
duration: number;
domainTags: string[];
coverImagePath: string;
};
// 课程介绍
intro: {
introSummary: string;
introHighlights: string;
introGoals: string;
introSchedule: string;
introKeyPoints: string;
introMethods: string;
introEvaluation: string;
introNotes: string;
};
// 排课参考
scheduleRefData: string;
// 环创建设
environmentConstruction: string;
// 课程配置Step4-6
lessons: {
// 导入课
introduction?: {
name: string;
objectives: string;
preparation: string;
extension: string;
reflection: string;
resources: LessonResources;
steps: LessonStepData[];
};
// 集体课
collective?: {
name: string;
objectives: string;
preparation: string;
extension: string;
reflection: string;
resources: LessonResources;
steps: LessonStepData[];
};
// 五大领域课
domainLessons: {
[key in 'LANGUAGE' | 'HEALTH' | 'SCIENCE' | 'SOCIAL' | 'ART']?: {
name: string;
objectives: string;
preparation: string;
extension: string;
reflection: string;
resources: LessonResources;
steps: LessonStepData[];
};
};
};
}
```
---
## 四、API设计
### 4.1 后端API
```
# 创建校本课程包
POST /teacher/school-courses/from-source
Body: { sourceCourseId, saveLocation: 'PERSONAL' | 'SCHOOL' }
# 获取校本课程包详情(含完整数据)
GET /teacher/school-courses/:id
Response: { schoolCourse, lessons: { introduction, collective, domainLessons } }
# 更新校本课程包(完整更新)
PUT /teacher/school-courses/:id
Body: SchoolCourseFormData
# 更新单个课程配置
PUT /teacher/school-courses/:schoolCourseId/lessons/:lessonType
Body: { objectives, preparation, extension, reflection, stepsData }
# 学校端API类似增加审核相关
POST /school/school-courses/:id/approve
POST /school/school-courses/:id/reject
```
### 4.2 前端API
```typescript
// src/api/school-course.ts
export interface SchoolCourseFormData {
basic: { ... };
intro: { ... };
scheduleRefData: string;
environmentConstruction: string;
lessons: { ... };
}
// 从源课程包创建
export function createSchoolCourseFromSource(sourceCourseId: number, saveLocation: 'PERSONAL' | 'SCHOOL') {
return http.post('/teacher/school-courses/from-source', { sourceCourseId, saveLocation });
}
// 获取完整详情(含课程配置)
export function getSchoolCourseFullDetail(id: number): Promise<SchoolCourseFormData> {
return http.get(`/teacher/school-courses/${id}/full`);
}
// 更新完整数据
export function updateSchoolCourseFull(id: number, data: SchoolCourseFormData) {
return http.put(`/teacher/school-courses/${id}/full`, data);
}
```
---
## 五、UI设计
### 5.1 创建入口
**教师端 - 课程详情页**
```
┌─────────────────────────────────────────────┐
│ [课程详情内容] │
│ │
│ [开始备课] [选择课程上课] │
│ [创建校本版本] ← 新增按钮 │
└─────────────────────────────────────────────┘
```
点击 "创建校本版本" 后:
1. 弹窗确认:将创建该课程包的校本副本,您可以自由编辑
2. 确认后跳转到编辑页面
### 5.2 编辑页面
**复用超管端7步组件顶部操作按钮调整**
```
┌─────────────────────────────────────────────┐
│ [← 返回] 编辑校本课程包 │
│ │
│ [保存草稿] [保存到个人] [提交到校本中心] │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 步骤: [基本信息] → [课程介绍] → ... → [环创建设] │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ 当前步骤内容(复用超管端组件) │
└─────────────────────────────────────────────┘
```
**按钮说明:**
- **保存草稿** - 保存为草稿状态,可继续编辑
- **保存到个人** - 保存到个人课程中心,立即可用
- **提交到校本中心** - 提交审核,通过后本校教师可见
### 5.3 保存位置选择
首次保存时弹窗:
```
┌─────────────────────────────────────────────┐
│ 选择保存位置 │
├─────────────────────────────────────────────┤
│ │
│ ○ 保存到个人课程中心 │
│ 仅您自己可以看到和使用 │
│ │
│ ○ 提交到校本课程中心 │
│ 本校所有教师都可以看到(需审核) │
│ │
│ [取消] [确认保存] │
└─────────────────────────────────────────────┘
```
### 5.4 个人课程中心
**新增页面:`/views/teacher/my-courses/MyCourseListView.vue`**
```
┌─────────────────────────────────────────────┐
│ 我的课程包 │
│ [校本课程包] [标准课程包收藏] │
├─────────────────────────────────────────────┤
│ 统计: 3个校本课程包 | 本周使用2次 │
├─────────────────────────────────────────────┤
│ ┌────────────┐ ┌────────────┐ │
│ │ 课程包A │ │ 课程包B │ │
│ │ [校本版] │ │ [校本版] │ │
│ │ [编辑][删除]│ │ [编辑][删除]│ │
│ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────┘
```
---
## 六、开发阶段规划
### Phase 6.1: 数据模型扩展0.5天)
- [ ] 扩展 SchoolCourse 表字段
- [ ] 扩展 SchoolCourseLesson 表字段
- [ ] 创建数据库迁移
### Phase 6.2: 后端API开发1-1.5天)
- [ ] 创建校本课程包 APIfrom-source
- [ ] 获取完整详情 API
- [ ] 更新完整数据 API
- [ ] 更新单个课程配置 API
- [ ] 学校端审核 API
### Phase 6.3: 共享组件提取0.5天)
- [ ] 将超管端7个Step组件提取到共享目录
- [ ] 创建共享的类型定义
- [ ] 确保超管端功能不受影响
### Phase 6.4: 编辑页面开发1.5-2天
- [ ] 教师端编辑页面
- [ ] 学校端编辑页面
- [ ] 保存位置选择弹窗
- [ ] 数据加载和保存逻辑
### Phase 6.5: 个人课程中心1天
- [ ] 教师端我的课程包列表页
- [ ] 课程包卡片组件
- [ ] 编辑/删除功能
### Phase 6.6: 上课集成0.5天)
- [ ] 从校本课程包开始上课
- [ ] 数据读取适配
### Phase 6.7: 测试与修复0.5天)
- [ ] 功能测试
- [ ] Bug修复
**总计5.5 - 6.5天**
---
## 七、注意事项
### 7.1 兼容性
1. **现有数据兼容**
- 已创建的 SchoolCourse 需要数据迁移
- 旧数据只有 objectives/preparation 等基础字段
- 新数据包含完整的 stepsData
2. **新旧版本共存**
- 如果 stepsData 为空,使用 sourceCourse 的原始数据
- 如果 stepsData 存在,使用校本版本的配置
### 7.2 性能考虑
1. **数据加载优化**
- 列表页只加载基本信息
- 编辑页按需加载详细配置
2. **数据存储优化**
- stepsData 使用JSON存储避免过度拆分表
### 7.3 权限控制
| 操作 | 教师端 | 学校端 |
|------|--------|--------|
| 创建校本课程包 | ✅ 个人 | ✅ 学校中心 |
| 编辑自己的课程包 | ✅ | ✅ |
| 删除自己的课程包 | ✅ | ✅ |
| 查看校本中心课程 | ✅ | ✅ |
| 审核校本中心课程 | ❌ | ✅ |
| 使用校本课程上课 | ✅ | ✅ |
---
## 八、后续扩展
1. **版本管理** - 支持校本课程包的版本迭代
2. **变更对比** - 可视化展示与源课程包的差异
3. **分享功能** - 支持跨学校分享校本课程包
4. **模板功能** - 将校本课程包保存为模板
5. **使用统计** - 详细的课程使用数据和分析
---
*本文档创建于 2026-03-11*