# 教师端重构开发规划

> 创建时间：2026-03-11
> 基于需求：17-课程包套餐重构需求.md
> 参考设计：19-教师端设计-重构版.md
> 状态：规划中

---

## 一、重构背景

### 1.1 已完成工作回顾

| 端 | 状态 | 完成内容 |
|----|------|---------|
| 超管端 | ✅ 完成 | 套餐管理、课程包7步流程重构、主题字典、租户授权 |
| 学校端 | ✅ 完成 | 套餐查看/续订、校本课程包管理、课程详情适配 |
| 数据模型 | ✅ 完成 | CoursePackage、CourseLesson、LessonStep等新表 |
| 后端API | ✅ 完成 | 套餐、课程包、课程、环节相关接口 |

### 1.2 教师端现状分析

| 模块 | 当前状态 | 需要适配 |
|-----|---------|---------|
| 课程中心 | 基于旧结构 | ❌ 需适配新课程包结构 |
| 课程详情页 | 基于旧结构 | ❌ 需展示7步内容 |
| 备课模式 | 基于课程维度 | ⚠️ 需适配新课程结构 |
| 上课模式 | 支持整体上课 | ⚠️ 需支持选择课程 |
| 校本课程包 | 已有框架 | ✅ 基本完成 |

### 1.3 核心变更点

```
旧结构：
Course（课程包）
  └─ CourseScript（逐页脚本）
      └─ CourseScriptPage（页面）
  └─ CourseActivity（延伸活动）

新结构：
Course（课程包）
  ├─ 基本信息（主题、年级、绘本、核心内容）
  ├─ 课程介绍（8个富文本字段）
  ├─ 排课计划参考（表格）
  ├─ 环创建设（富文本）
  └─ CourseLesson（课程）
      ├─ INTRODUCTION（导入课）
      ├─ COLLECTIVE（集体课）
      └─ 五大领域课（LANGUAGE/HEALTH/SCIENCE/SOCIAL/ART）
          └─ LessonStep（教学环节）
```

---

## 二、重构目标

### 2.1 功能目标

| 序号 | 目标 | 优先级 |
|-----|------|-------|
| 1 | 课程详情页适配新结构，展示7步内容 | P0 |
| 2 | 备课模式按课程维度切换（导入课→集体课→领域课） | P0 |
| 3 | 上课模式支持选择课程上课 | P0 |
| 4 | 校本课程包功能完善 | P1 |
| 5 | 课程进度追踪 | P1 |

### 2.2 用户体验目标

| 目标 | 说明 |
|-----|------|
| 清晰的信息层次 | 课程包→课程→环节 三级导航清晰 |
| 灵活的教学方式 | 支持整体教学和选择性上课 |
| 本地化能力 | 教师可创建校本课程包 |

---

## 三、详细开发规划

### Phase 1: 基础适配（1-2天）

#### 1.1 API类型定义更新

**文件：** `reading-platform-frontend/src/api/course.ts`

```typescript
// 新增类型定义
export interface CourseLesson {
  id: number;
  courseId: number;
  lessonType: 'INTRODUCTION' | 'COLLECTIVE' | 'LANGUAGE' | 'HEALTH' | 'SCIENCE' | 'SOCIAL' | 'ART';
  name: string;
  description: string | null;
  duration: number;
  videoPath: string | null;
  videoName: string | null;
  pptPath: string | null;
  pptName: string | null;
  pdfPath: string | null;
  pdfName: string | null;
  objectives: string | null;
  preparation: string | null;
  extension: string | null;
  reflection: string | null;
  assessmentData: string | null;
  useTemplate: boolean;
  sortOrder: number;
  steps: LessonStep[];
}

export interface LessonStep {
  id: number;
  lessonId: number;
  name: string;
  content: string;
  duration: number;
  objective: string | null;
  resourceIds: string | null;
  sortOrder: number;
}

// 更新Course接口
export interface Course {
  // ... 保留原有字段 ...

  // 新增字段
  themeId: number | null;
  coreContent: string | null;
  introSummary: string | null;
  introHighlights: string | null;
  introGoals: string | null;
  introSchedule: string | null;
  introKeyPoints: string | null;
  introMethods: string | null;
  introEvaluation: string | null;
  introNotes: string | null;
  scheduleRefData: string | null;
  environmentConstruction: string | null;
  hasCollectiveLesson: boolean;

  // 新增关联
  theme: Theme | null;
  lessons: CourseLesson[];
}

// 新增Theme接口
export interface Theme {
  id: number;
  name: string;
  description: string | null;
}
```

#### 1.2 后端API适配

**文件：** `reading-platform-backend/src/modules/course/course.controller.ts`

```typescript
// 确保课程详情API返回lessons
@Get(':id')
async getCourseDetail(@Param('id') id: string) {
  return this.courseService.findById(+id, {
    include: {
      theme: true,
      lessons: {
        include: {
          steps: {
            orderBy: { sortOrder: 'asc' }
          }
        },
        orderBy: { sortOrder: 'asc' }
      }
    }
  });
}
```

### Phase 2: 课程详情页重构（1-2天）

#### 2.1 课程详情页布局

**文件：** `reading-platform-frontend/src/views/teacher/courses/CourseDetailView.vue`

```
┌──────────────────────────────────────────────────────────────────────┐
│ ◀ 返回课程中心                                   [收藏] [创建校本]   │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│ 【顶部信息卡片】                                                     │
│ ┌────────────────────────────────────────────────────────────────┐   │
│ │ 封面 | 名称 | 主题 | 年级 | 评分 | 使用数 | 包含课程数 | 时长    │   │
│ └────────────────────────────────────────────────────────────────┘   │
│                                                                      │
│ 【Tab导航】                                                          │
│ ┌────────┬────────┬────────┬────────┬────────┐                     │
│ │ 课程介绍│ 课程内容│ 排课参考│ 环创建设│ 用户评价│                     │
│ └────────┴────────┴────────┴────────┴────────┘                     │
│                                                                      │
│ 【内容区域】                                                         │
│ - 课程介绍Tab：展示8个富文本字段                                     │
│ - 课程内容Tab：展示导入课+集体课+领域课列表                          │
│ - 排课参考Tab：展示排课计划表格                                      │
│ - 环创建设Tab：展示环创建设内容                                      │
│ - 用户评价Tab：展示用户评价                                          │
│                                                                      │
│ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
│              [开始备课]              [选择课程上课]                   │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘
```

#### 2.2 组件结构

```
CourseDetailView.vue
├── CourseInfoCard.vue          # 顶部信息卡片
├── CourseDetailTabs.vue        # Tab导航
│   ├── CourseIntroTab.vue      # 课程介绍Tab（8个富文本）
│   ├── CourseContentTab.vue    # 课程内容Tab（课程列表）
│   ├── CourseScheduleTab.vue   # 排课参考Tab
│   ├── CourseEnvironmentTab.vue # 环创建设Tab
│   └── CourseReviewsTab.vue    # 用户评价Tab
└── ActionButtons.vue           # 底部操作按钮
```

#### 2.3 CourseContentTab 详细设计

```vue
<!-- 课程内容Tab - 展示所有课程 -->
<template>
  <div class="course-content-tab">
    <a-alert message="本课程包包含以下课程，建议按顺序教学" type="info" show-icon />

    <div class="lesson-list">
      <!-- 导入课 -->
      <LessonCard
        v-if="introductionLesson"
        :lesson="introductionLesson"
        :course-id="course.id"
        type="导入课"
        @prepare="handlePrepare"
        @start-class="handleStartClass"
      />

      <!-- 集体课 -->
      <LessonCard
        v-if="collectiveLesson"
        :lesson="collectiveLesson"
        :course-id="course.id"
        type="集体课"
        @prepare="handlePrepare"
        @start-class="handleStartClass"
      />

      <!-- 五大领域课 -->
      <LessonCard
        v-for="lesson in domainLessons"
        :key="lesson.id"
        :lesson="lesson"
        :course-id="course.id"
        :type="getLessonTypeName(lesson.lessonType)"
        @prepare="handlePrepare"
        @start-class="handleStartClass"
      />
    </div>

    <div class="action-bar">
      <a-button type="primary" size="large" @click="handlePrepareAll">
        整体备课（全部课程）
      </a-button>
      <a-button size="large" @click="handleSelectLessons">
        选择课程上课
      </a-button>
    </div>
  </div>
</template>

<script setup lang="ts">
const props = defineProps<{
  course: Course;
}>();

// 按类型分组课程
const introductionLesson = computed(() =>
  props.course.lessons?.find(l => l.lessonType === 'INTRODUCTION')
);

const collectiveLesson = computed(() =>
  props.course.lessons?.find(l => l.lessonType === 'COLLECTIVE')
);

const domainLessons = computed(() =>
  props.course.lessons?.filter(l =>
    ['LANGUAGE', 'HEALTH', 'SCIENCE', 'SOCIAL', 'ART'].includes(l.lessonType)
  ) || []
);
</script>
```

#### 2.4 LessonCard 组件

```vue
<!-- 单个课程卡片 -->
<template>
  <div class="lesson-card">
    <div class="lesson-header">
      <span class="lesson-type">{{ type }}</span>
      <span class="lesson-name">{{ lesson.name }}</span>
      <span class="lesson-duration">{{ lesson.duration }}分钟</span>
      <a-button type="link" size="small" @click="$emit('prepare')">
        [备课]
      </a-button>
    </div>
    <div class="lesson-body">
      <div class="lesson-objective">
        <strong>教学目标：</strong>{{ lesson.objectives }}
      </div>
      <div v-if="lesson.steps?.length" class="lesson-steps">
        <strong>教学环节：</strong>
        <span v-for="(step, index) in lesson.steps" :key="step.id">
          {{ step.name }}
          {{ index < lesson.steps.length - 1 ? ' → ' : '' }}
        </span>
      </div>
      <div v-if="type === '集体课' || type.includes('领域')" class="lesson-resources">
        <strong>核心资源：</strong>
        <span v-if="lesson.videoPath">动画视频</span>
        <span v-if="lesson.pptPath">教学课件</span>
        <span v-if="lesson.pdfPath">电子绘本</span>
      </div>
    </div>
    <div class="lesson-footer">
      <a-button type="primary" @click="$emit('start-class', lesson)">
        开始上课
      </a-button>
    </div>
  </div>
</template>
```

### Phase 3: 备课模式重构（2-3天）

#### 3.1 备课模式布局

**文件：** `reading-platform-frontend/src/views/teacher/courses/PrepareModeView.vue`

```
┌──────────────────────────────────────────────────────────────────────┐
│ 📚 备课模式：好饿的毛毛虫                               [退出备课]   │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  ┌─────────────────────────┐  ┌───────────────────────────────────┐ │
│  │ 【左侧：课程导航】       │  │ 【右侧：内容预览】                │ │
│  │                         │  │                                   │ │
│  │ 📋 课程包概览           │  │  当前选中内容的详细展示            │ │
│  │ ├─ 基本信息             │  │                                   │ │
│  │ ├─ 课程介绍             │  │  - 基本信息/课程介绍/排课参考      │ │
│  │ ├─ 排课计划参考         │  │  - 教学目标/教学准备              │ │
│  │ └─ 环创建设             │  │  - 教学环节列表                   │ │
│  │                         │  │  - 核心资源预览                   │ │
│  │ 📖 包含课程             │  │                                   │ │
│  │ ├─ 1.导入课             │  │                                   │ │
│  │ │  ├─ 教学目标          │  │                                   │ │
│  │ │  ├─ 教学准备          │  │                                   │ │
│  │ │  ├─ 教学过程          │  │                                   │ │
│  │ │  └─ 教学反思          │  │                                   │ │
│  │ │                       │  │                                   │ │
│  │ ├─ 2.集体课 ●           │  │                                   │ │
│  │ │  ├─ 核心资源          │  │                                   │ │
│  │ │  ├─ 教学目标          │  │                                   │ │
│  │ │  ├─ 教学准备          │  │                                   │ │
│  │ │  ├─ 教学过程          │  │                                   │ │
│  │ │  │  ├─ 导入环节       │  │                                   │ │
│  │ │  │  ├─ 动画观看       │  │                                   │ │
│  │ │  │  ├─ 绘本跟读       │  │                                   │ │
│  │ │  │  └─ 结束环节       │  │                                   │ │
│  │ │  ├─ 教学延伸          │  │                                   │ │
│  │ │  └─ 教学反思          │  │                                   │ │
│  │ │                       │  │                                   │ │
│  │ └─ 3.五大领域课...      │  │                                   │ │
│  │                         │  │                                   │ │
│  └─────────────────────────┘  └───────────────────────────────────┘ │
│                                                                      │
│  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │
│                                                                      │
│  我的备课笔记：                                     [保存] [清除]     │
│  ┌────────────────────────────────────────────────────────────┐     │
│  │ • 备课笔记内容...                                          │     │
│  └────────────────────────────────────────────────────────────┘     │
│                                                                      │
│  [打印素材清单]              [我已熟悉，开始上课]                     │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘
```

#### 3.2 左侧导航组件

**文件：** `reading-platform-frontend/src/views/teacher/courses/components/PrepareNavigation.vue`

```vue
<template>
  <div class="prepare-navigation">
    <!-- 课程包概览 -->
    <div class="nav-section">
      <div class="nav-title" @click="selectSection('overview')">
        📋 课程包概览
      </div>
      <div v-if="selectedSection === 'overview'" class="nav-items">
        <div class="nav-item" @click="selectItem('basic')">基本信息</div>
        <div class="nav-item" @click="selectItem('intro')">课程介绍</div>
        <div class="nav-item" @click="selectItem('schedule')">排课计划参考</div>
        <div class="nav-item" @click="selectItem('environment')">环创建设</div>
      </div>
    </div>

    <!-- 包含课程 -->
    <div class="nav-section">
      <div class="nav-title">📖 包含课程</div>
      <div class="nav-items">
        <!-- 导入课 -->
        <div
          v-if="introductionLesson"
          class="nav-lesson"
          :class="{ active: selectedLessonId === introductionLesson.id }"
          @click="selectLesson(introductionLesson)"
        >
          <div class="lesson-header">
            <span class="lesson-icon">📖</span>
            <span>1. 导入课</span>
          </div>
          <div v-if="selectedLessonId === introductionLesson.id" class="lesson-items">
            <div class="nav-item" @click.stop="selectLessonItem('objectives')">教学目标</div>
            <div class="nav-item" @click.stop="selectLessonItem('preparation')">教学准备</div>
            <div class="nav-item" @click.stop="selectLessonItem('steps')">教学过程</div>
            <div class="nav-item" @click.stop="selectLessonItem('reflection')">教学反思</div>
          </div>
        </div>

        <!-- 集体课 -->
        <div
          v-if="collectiveLesson"
          class="nav-lesson"
          :class="{ active: selectedLessonId === collectiveLesson.id }"
          @click="selectLesson(collectiveLesson)"
        >
          <div class="lesson-header">
            <span class="lesson-icon">👥</span>
            <span>2. 集体课</span>
          </div>
          <div v-if="selectedLessonId === collectiveLesson.id" class="lesson-items">
            <div class="nav-item" @click.stop="selectLessonItem('resources')">核心资源</div>
            <div class="nav-item" @click.stop="selectLessonItem('objectives')">教学目标</div>
            <div class="nav-item" @click.stop="selectLessonItem('preparation')">教学准备</div>
            <div class="nav-item" @click.stop="selectLessonItem('steps')">教学过程</div>
            <div class="nav-item" @click.stop="selectLessonItem('extension')">教学延伸</div>
            <div class="nav-item" @click.stop="selectLessonItem('reflection')">教学反思</div>
          </div>
        </div>

        <!-- 五大领域课 -->
        <div
          v-for="lesson in domainLessons"
          :key="lesson.id"
          class="nav-lesson"
          :class="{ active: selectedLessonId === lesson.id }"
          @click="selectLesson(lesson)"
        >
          <div class="lesson-header">
            <span class="lesson-icon">{{ getLessonIcon(lesson.lessonType) }}</span>
            <span>{{ getLessonNumber(lesson) }}. {{ getLessonTypeName(lesson.lessonType) }}</span>
          </div>
          <div v-if="selectedLessonId === lesson.id" class="lesson-items">
            <div class="nav-item" @click.stop="selectLessonItem('resources')">核心资源</div>
            <div class="nav-item" @click.stop="selectLessonItem('objectives')">教学目标</div>
            <div class="nav-item" @click.stop="selectLessonItem('preparation')">教学准备</div>
            <div class="nav-item" @click.stop="selectLessonItem('steps')">教学过程</div>
            <div class="nav-item" @click.stop="selectLessonItem('extension')">教学延伸</div>
            <div class="nav-item" @click.stop="selectLessonItem('reflection')">教学反思</div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
```

#### 3.3 右侧预览组件

**文件：** `reading-platform-frontend/src/views/teacher/courses/components/PreparePreview.vue`

```vue
<template>
  <div class="prepare-preview">
    <!-- 课程包概览 -->
    <CourseOverviewContent
      v-if="selectedType === 'overview'"
      :course="course"
      :selected-item="selectedItem"
    />

    <!-- 教学目标 -->
    <LessonObjectivesContent
      v-if="selectedType === 'lesson' && selectedItem === 'objectives'"
      :lesson="selectedLesson"
    />

    <!-- 教学准备 -->
    <LessonPreparationContent
      v-if="selectedType === 'lesson' && selectedItem === 'preparation'"
      :lesson="selectedLesson"
    />

    <!-- 教学过程（环节列表） -->
    <LessonStepsContent
      v-if="selectedType === 'lesson' && selectedItem === 'steps'"
      :lesson="selectedLesson"
      @select-step="handleSelectStep"
    />

    <!-- 单个环节详情 -->
    <LessonStepDetailContent
      v-if="selectedType === 'step'"
      :step="selectedStep"
      :lesson="selectedLesson"
    />

    <!-- 教学延伸 -->
    <LessonExtensionContent
      v-if="selectedType === 'lesson' && selectedItem === 'extension'"
      :lesson="selectedLesson"
    />

    <!-- 教学反思 -->
    <LessonReflectionContent
      v-if="selectedType === 'lesson' && selectedItem === 'reflection'"
      :lesson="selectedLesson"
    />

    <!-- 核心资源 -->
    <LessonResourcesContent
      v-if="selectedType === 'lesson' && selectedItem === 'resources'"
      :lesson="selectedLesson"
    />
  </div>
</template>
```

### Phase 4: 上课模式重构（2-3天）

#### 4.1 课程选择弹窗

**文件：** `reading-platform-frontend/src/views/teacher/lessons/components/SelectLessonsModal.vue`

```vue
<template>
  <a-modal
    v-model:open="visible"
    title="选择上课内容"
    width="700px"
    :footer="null"
  >
    <div class="select-lessons-modal">
      <div class="course-info">
        <strong>课程包：</strong>{{ course.name }}
      </div>
      <div class="class-info">
        <strong>当前班级：</strong>{{ currentClass.name }}
      </div>

      <a-divider />

      <!-- 推荐：整体教学 -->
      <div class="section">
        <h4>【推荐：整体教学】</h4>
        <a-radio-group v-model:value="selectionMode">
          <a-radio value="all">
            <div class="radio-option">
              <div class="option-title">按课程包完整教学</div>
              <div class="option-desc">
                按顺序完成：导入课 → 集体课 → 五大领域课
              </div>
              <div class="option-meta">
                预计总时长：约 {{ totalDuration }} 分钟（可分多次完成）
              </div>
              <div class="option-hint">适合：首次教学、完整学习</div>
            </div>
          </a-radio>
        </a-radio-group>
      </div>

      <!-- 灵活：选择课程 -->
      <div class="section">
        <h4>【灵活：选择课程】</h4>
        <a-radio-group v-model:value="selectionMode">
          <a-radio value="custom">
            <div class="radio-option">
              <div class="option-title">选择单次课程</div>
            </div>
          </a-radio>
        </a-radio-group>

        <div v-if="selectionMode === 'custom'" class="lesson-checkboxes">
          <a-checkbox-group v-model:value="selectedLessonIds">
            <div v-if="introductionLesson" class="lesson-checkbox-item">
              <a-checkbox :value="introductionLesson.id">
                <span class="lesson-name">1. 导入课 - {{ introductionLesson.name }}</span>
                <span class="lesson-duration">({{ introductionLesson.duration }}分钟)</span>
              </a-checkbox>
            </div>
            <div v-if="collectiveLesson" class="lesson-checkbox-item">
              <a-checkbox :value="collectiveLesson.id">
                <span class="lesson-name">2. 集体课 - {{ collectiveLesson.name }}</span>
                <span class="lesson-duration">({{ collectiveLesson.duration }}分钟)</span>
              </a-checkbox>
            </div>
            <div v-for="lesson in domainLessons" :key="lesson.id" class="lesson-checkbox-item">
              <a-checkbox :value="lesson.id">
                <span class="lesson-name">{{ getLessonNumber(lesson) }}. {{ getLessonTypeName(lesson.lessonType) }} - {{ lesson.name }}</span>
                <span class="lesson-duration">({{ lesson.duration }}分钟)</span>
              </a-checkbox>
            </div>
          </a-checkbox-group>

          <div class="selection-summary">
            已选择 {{ selectedLessonIds.length }} 节课，预计时长：{{ selectedDuration }} 分钟
          </div>
        </div>
      </div>

      <a-divider />

      <div class="modal-footer">
        <a-button @click="handleCancel">取消</a-button>
        <a-button type="primary" :disabled="!canStart" @click="handleConfirm">
          开始上课
        </a-button>
      </div>
    </div>
  </a-modal>
</template>

<script setup lang="ts">
const selectionMode = ref<'all' | 'custom'>('all');
const selectedLessonIds = ref<number[]>([]);

const totalDuration = computed(() => {
  return props.course.lessons?.reduce((sum, l) => sum + l.duration, 0) || 0;
});

const selectedDuration = computed(() => {
  return props.course.lessons
    ?.filter(l => selectedLessonIds.value.includes(l.id))
    .reduce((sum, l) => sum + l.duration, 0) || 0;
});

const canStart = computed(() => {
  if (selectionMode.value === 'all') return true;
  return selectedLessonIds.value.length > 0;
});
</script>
```

#### 4.2 上课模式主界面

**文件：** `reading-platform-frontend/src/views/teacher/lessons/LessonView.vue`

```
主要修改：
1. 支持传入课程列表（而非单个课程）
2. 添加课程进度导航
3. 添加课程内环节进度导航
4. 更新资源展示逻辑
```

```vue
<template>
  <div class="lesson-view">
    <!-- 顶部状态栏 -->
    <div class="lesson-header">
      <div class="timer">{{ formattedTime }}</div>
      <a-button @click="handleExit">◀ 退出课堂</a-button>
      <div class="lesson-title">
        {{ currentCourse?.name }} - {{ currentLesson?.name }}
      </div>
      <div class="class-info">{{ currentClass?.name }}</div>
      <a-button type="primary" danger @click="handleFinish">结束</a-button>
    </div>

    <!-- 课程进度 -->
    <div v-if="lessons.length > 1" class="course-progress">
      <a-steps :current="currentCourseIndex" size="small">
        <a-step
          v-for="(lesson, index) in lessons"
          :key="lesson.id"
          :title="getLessonShortName(lesson)"
          :status="getLessonStatus(index)"
        />
      </a-steps>
    </div>

    <!-- 环节进度 -->
    <div class="step-progress">
      <div class="progress-bar">
        <div class="progress-label">
          当前：{{ currentStep?.name }} ({{ currentStepIndex + 1 }}/{{ currentLesson?.steps?.length }})
        </div>
        <a-progress
          :percent="stepProgressPercent"
          :show-info="false"
          stroke-color="#52c41a"
        />
      </div>
      <div class="step-indicators">
        <span
          v-for="(step, index) in currentLesson?.steps"
          :key="step.id"
          class="step-indicator"
          :class="{
            completed: index < currentStepIndex,
            current: index === currentStepIndex
          }"
        >
          {{ step.name }}
        </span>
      </div>
    </div>

    <!-- 资源展示区域 -->
    <div class="resource-display">
      <!-- 动画播放器 -->
      <VideoPlayer
        v-if="currentStepHasVideo"
        :src="currentVideoUrl"
        :poster="currentPosterUrl"
        @ended="handleVideoEnded"
      />

      <!-- 课件查看器 -->
      <SlidesViewer
        v-if="currentStepHasSlides"
        :src="currentSlidesUrl"
      />

      <!-- 电子绘本 -->
      <EbookViewer
        v-if="currentStepHasEbook"
        :src="currentEbookUrl"
      />

      <!-- 默认展示 -->
      <div v-if="!hasResource" class="no-resource">
        <a-empty description="本环节无关联资源" />
      </div>
    </div>

    <!-- 教师指引 -->
    <div class="teacher-guide">
      <div class="guide-header">
        💡 教师指引 - {{ currentStep?.name }} (预计{{ currentStep?.duration }}分钟)
      </div>
      <div class="guide-content">
        <div class="guide-section">
          <strong>教学目的：</strong>
          {{ currentStep?.objective }}
        </div>
        <div class="guide-section">
          <strong>操作提示：</strong>
          <ul>
            <li v-for="tip in getCurrentTips()" :key="tip">{{ tip }}</li>
          </ul>
        </div>
        <div class="guide-section">
          <strong>📢 指导语：</strong>
          <div class="guide-script">{{ getCurrentScript() }}</div>
        </div>
      </div>
    </div>

    <!-- 工具栏 -->
    <div class="toolbar">
      <a-button @click="handlePause">
        <template #icon><PauseOutlined /></template>
        暂停
      </a-button>
      <a-button @click="handleAudio">
        <template #icon><SoundOutlined /></template>
        音频
      </a-button>
      <a-button @click="handleNotes">
        <template #icon><EditOutlined /></template>
        笔记
      </a-button>
      <a-button @click="handleTimer">
        <template #icon><ClockCircleOutlined /></template>
        计时器
      </a-button>
      <a-button @click="handleFullscreen">
        <template #icon><FullscreenOutlined /></template>
        全屏
      </a-button>
    </div>

    <!-- 环节切换 -->
    <div class="step-navigation">
      <a-button
        size="large"
        :disabled="currentStepIndex === 0"
        @click="handlePreviousStep"
      >
        上一环节
      </a-button>
      <a-button
        size="large"
        type="primary"
        @click="handleNextStep"
      >
        完成此环节 →
      </a-button>
    </div>
  </div>
</template>

<script setup lang="ts">
interface Props {
  course: Course;
  lessons: CourseLesson[];
  initialLessonId?: number;
  classId: number;
}

const props = defineProps<Props>();
const currentCourseIndex = ref(0);
const currentLesson = ref<CourseLesson | null>(null);
const currentStepIndex = ref(0);

const currentStep = computed(() => {
  return currentLesson.value?.steps?.[currentStepIndex.value];
});
</script>
```

### Phase 5: 课程进度追踪（1天）

#### 5.1 进度记录逻辑

```typescript
// 课程进度记录结构
interface CourseProgress {
  id: number;
  teacherId: number;
  classId: number;
  courseId: number;
  completedLessonIds: number[]; // 已完成的课程ID列表
  currentLessonId: number | null; // 当前进行到的课程
  currentStepId: number | null; // 当前进行到的环节
  lastSessionAt: Date;
}

// 进度API
// GET /teacher/course-progress/:courseId/:classId
// POST /teacher/course-progress
// PUT /teacher/course-progress/:id
```

#### 5.2 进度恢复逻辑

```typescript
// 进入上课模式时检查进度
async function loadCourseProgress(courseId: number, classId: number) {
  const progress = await api.getCourseProgress(courseId, classId);

  if (progress) {
    // 询问是否继续上次进度
    Modal.confirm({
      title: '继续上次课程？',
      content: `上次进行到：${getLessonName(progress.currentLessonId)}，是否继续？`,
      onOk: () => {
        restoreProgress(progress);
      },
      onCancel: () => {
        startNewSession();
      }
    });
  } else {
    startNewSession();
  }
}
```

### Phase 6: 校本课程包功能完善（1-2天）

#### 6.1 创建校本课程包流程

```
1. 选择平台课程包
2. 选择要调整的内容（教学目标/教学过程/资源等）
3. 保存为本校版本
```

#### 6.2 校本课程包数据结构

```typescript
interface SchoolCourse {
  id: number;
  tenantId: number;
  sourceCourseId: number;
  name: string;
  description: string | null;
  createdBy: number;
  changesSummary: string | null; // 调整摘要
  changesData: string | null; // 调整详情JSON
  usageCount: number;
  status: 'ACTIVE' | 'ARCHIVED';

  sourceCourse: Course;
  lessons: SchoolCourseLesson[];
}

interface SchoolCourseLesson {
  id: number;
  schoolCourseId: number;
  sourceLessonId: number;
  lessonType: string;
  objectives: string | null;
  preparation: string | null;
  extension: string | null;
  reflection: string | null;
  changeNote: string | null;
  stepsData: string | null; // 调整后的环节数据
}
```

---

## 四、开发时间表

| 阶段 | 任务 | 预计时间 | 优先级 |
|-----|------|---------|-------|
| Phase 1 | API类型定义更新、后端API适配 | 1-2天 | P0 |
| Phase 2 | 课程详情页重构 | 1-2天 | P0 |
| Phase 3 | 备课模式重构 | 2-3天 | P0 |
| Phase 4 | 上课模式重构 | 2-3天 | P0 |
| Phase 5 | 课程进度追踪 | 1天 | P1 |
| Phase 6 | 校本课程包功能完善 | 1-2天 | P1 |
| **总计** | | **8-13天** | |

---

## 五、测试计划

### 5.1 功能测试

| 测试项 | 测试内容 | 预期结果 |
|-------|---------|---------|
| 课程详情页 | 展示新结构内容 | 正确展示7步内容 |
| 备课模式 | 按课程维度切换 | 左侧导航正确展开课程 |
| 上课模式 | 选择课程上课 | 可选择单次或整体上课 |
| 进度追踪 | 保存和恢复进度 | 下次进入时可继续上次进度 |

### 5.2 兼容性测试

- 旧课程包数据正确显示
- 无课程数据的课程包不报错
- 各类课程（导入/集体/领域）正确展示

---

## 六、风险与注意事项

### 6.1 技术风险

| 风险 | 应对措施 |
|-----|---------|
| 旧数据兼容 | 保留旧字段，渐进式迁移 |
| 性能问题 | 课程数据量大时使用分页/虚拟滚动 |
| 状态管理复杂 | 使用Pinia管理备课/上课状态 |

### 6.2 用户体验风险

| 风险 | 应对措施 |
|-----|---------|
| 信息过载 | 默认折叠，按需展开 |
| 操作复杂 | 提供引导和默认选项 |
| 兼容性困惑 | 旧版课程标识"经典版"，新版标识"完整版" |

---

## 七、后续优化方向

- [ ] 离线备课/上课支持
- [ ] 课程分享功能
- [ ] 教学数据分析
- [ ] AI辅助备课建议
- [ ] 家长端联动（学生表现反馈）

---

*本文档创建于 2026-03-11*
*基于需求文档：17-课程包套餐重构需求.md*
