Browse Source

AI生成课程取消退出重连逻辑,只保留一次性生成完毕
生成和取消都加入了二次确认

liyanbo 1 month ago
parent
commit
14cd840423
2 changed files with 161 additions and 273 deletions
  1. 161 272
      src/components/study/SelfDirectedLearning.vue
  2. 0 1
      src/views/AIPage/AIGeneralCourse.vue

+ 161 - 272
src/components/study/SelfDirectedLearning.vue

@@ -29,8 +29,7 @@
                 <el-icon><component :is="courseDropdown.icon" /></el-icon>
               </template>
               <el-option
-                  v-for="item in courseDropdown.options"
-                  v-show="item.show"
+                  v-for="item in availableThemeOptions"
                   :key="item.value"
                   :label="item.label"
                   :value="item.value"
@@ -129,7 +128,7 @@
             </div>
           </div>
           <div class="cancel-box">
-            <button class="cancel-button" @click="cancelGenerate">
+            <button class="cancel-button" @click="handleCancel">
               <span>取消</span>
             </button>
           </div>
@@ -147,6 +146,7 @@ import {ref, onMounted, onUnmounted, computed, reactive, defineEmits, watch} fro
 // 定义事件
 const emit = defineEmits(['refreshData']);
 import { Top, Search, User, Memo } from '@element-plus/icons-vue'
+import { ElMessageBox } from 'element-plus'
 import {
   aIGenerateCourse,
   AiImageStatusEnum,
@@ -186,13 +186,7 @@ const inProgressImageMap = ref({}) // 监听的图片映射
 const inProgressTimer = ref(null) // 生成中的图片定时器
 const checkImagesCompleteTimer = ref(null) // 图片完成检查定时器
 
-// 任务恢复相关 - 使用统一的缓存键并绑定用户ID
-const getTaskStateKey = () => {
-  const userId = localStorage.getItem('userId') || 'anonymous'
-  return `ai_course_task_state_${userId}`
-}
-
-// 单个脚本数据的缓存键(保持原有逻辑,用于单独存储脚本数据)
+// 单个脚本数据的缓存键(用于存储最终生成的脚本数据)
 const SCRIPT_DATA_KEY = 'scriptData'
 
 // 小节列表(包含详细讲解要求,适用于所有课程类型)
@@ -379,138 +373,25 @@ const hasPendingTask = computed(() => {
   }
 })
 
-// 当前正在生成的小节索引(用于恢复时从正确位置继续)
-const currentLessonIndex = ref(0)
-
-// 保存任务状态到缓存(使用统一的键并绑定用户ID)
-const saveTaskState = (currentStep) => {
-  const taskState = {
-    isRestoring: true,
-    currentStep: currentStep,
-    currentLessonIndex: currentLessonIndex.value,
-    progressSteps: progressSteps.value,
-    progressCounters: progressCounters.value,
-    prompt: prompt.value,
-    courseDropdown: courseDropdown.value,
-    teacherDropdown: teacherDropdown.value,
-    assistantDropdown: assistantDropdown.value,
-    inProgressImageMap: inProgressImageMap.value
-  }
-
-  localStorage.setItem(getTaskStateKey(), JSON.stringify(taskState))
-  // 脚本数据单独存储(保持原有逻辑)
-  localStorage.setItem(SCRIPT_DATA_KEY, JSON.stringify(scriptData))
-}
-
-// 清除任务状态缓存
-const clearTaskState = () => {
-  localStorage.removeItem(getTaskStateKey())
-  localStorage.removeItem(SCRIPT_DATA_KEY)
-}
-
-// 标记任务完成
-const markTaskCompleted = () => {
-  const taskState = localStorage.getItem(getTaskStateKey())
-  if (taskState) {
-    try {
-      const parsedState = JSON.parse(taskState)
-      parsedState.completionTime = String(Date.now())
-      parsedState.isRestoring = false
-      localStorage.setItem(getTaskStateKey(), JSON.stringify(parsedState))
-    } catch (error) {
-      console.error('标记任务完成失败:', error)
-    }
-  }
-  clearTaskState()
-}
-
-// 从缓存恢复任务状态
-const restoreTaskState = () => {
-  try {
-    const taskState = localStorage.getItem(getTaskStateKey())
-    const savedScriptData = localStorage.getItem(SCRIPT_DATA_KEY)
-
-    if (!taskState) {
-      console.warn('未找到任务状态缓存')
-      return
-    }
-
-    const parsedState = JSON.parse(taskState)
-
-    // 恢复进度步骤
-    if (parsedState.progressSteps) {
-      progressSteps.value = parsedState.progressSteps
-    }
-
-    // 恢复进度计数器
-    if (parsedState.progressCounters) {
-      progressCounters.value = parsedState.progressCounters
-    }
-
-    // 恢复脚本数据
-    if (savedScriptData) {
-      const parsedData = JSON.parse(savedScriptData)
-      Object.assign(scriptData, parsedData)
-    }
-
-    // 恢复当前小节索引
-    if (parsedState.currentLessonIndex !== undefined) {
-      currentLessonIndex.value = parsedState.currentLessonIndex
-    }
-
-    // 恢复用户输入
-    if (parsedState.prompt) {
-      prompt.value = parsedState.prompt
-    }
-
-    // 恢复下拉框选择
-    if (parsedState.courseDropdown) {
-      Object.assign(courseDropdown.value, parsedState.courseDropdown)
-    }
-    if (parsedState.teacherDropdown) {
-      Object.assign(teacherDropdown.value, parsedState.teacherDropdown)
-      selectedMainTeacher.value = teacherDropdown.value.value
-    }
-    if (parsedState.assistantDropdown) {
-      Object.assign(assistantDropdown.value, parsedState.assistantDropdown)
-      selectedAssistants.value = [assistantDropdown.value.value]
-    }
-
-    // 恢复图片映射(只保留还未完成的图片)
-    if (parsedState.inProgressImageMap) {
-      const savedImageMap = parsedState.inProgressImageMap
-      const validImageMap = {}
-      
-      // 过滤掉已经完成的图片(有url的图片不需要再轮询)
-      Object.keys(savedImageMap).forEach(key => {
-        const info = savedImageMap[key]
-        if (info.type === 'cover') {
-          if (!scriptData.coverImage || !scriptData.coverImage.url) {
-            validImageMap[key] = info
-          }
-        } else {
-          const lesson = scriptData.lessons[info.lessonIndex]
-          if (lesson && lesson.sections && lesson.sections[info.sectionIndex]) {
-            const section = lesson.sections[info.sectionIndex]
-            if (!section.backgroundImage || !section.backgroundImage.url) {
-              validImageMap[key] = info
-            }
-          }
-        }
-      })
-      
-      inProgressImageMap.value = validImageMap
-      
-      // 恢复后立即检查一次图片状态,确保进度准确
-      if (Object.keys(inProgressImageMap.value).length > 0) {
-        refreshWatchImages()
-      }
+/**
+ * 取消生成确认(带确认提示)
+ */
+const handleCancel = async () => {
+  const confirmResult = await ElMessageBox.confirm(
+    '确定要取消当前的课程生成吗?取消后所有已生成的内容将丢失,需要重新开始。',
+    '取消确认',
+    {
+      confirmButtonText: '确定取消',
+      cancelButtonText: '继续生成',
+      type: 'warning',
+      showClose: false
     }
+  ).catch(() => {
+    return 'cancel'
+  })
 
-    showNewContainer.value = true
-  } catch (error) {
-    console.error('恢复任务状态失败:', error)
-    clearTaskState()
+  if (confirmResult === 'confirm') {
+    cancelGenerate(true)
   }
 }
 
@@ -535,8 +416,8 @@ const cancelGenerate = (showMessage = true) => {
   isGeneratingImages.value = false
   isGeneratingVoiceovers.value = false
 
-  // 清除任务状态缓存
-  clearTaskState()
+  // 清除脚本数据缓存
+  localStorage.removeItem(SCRIPT_DATA_KEY)
 
   // 重置进度步骤
   progressSteps.value.forEach(step => {
@@ -568,9 +449,8 @@ const isUnmounted = ref(false)
 
 /**
  * 生成脚本主函数
- * @param {boolean} isResume - 是否为恢复任务(从缓存中恢复)
  */
-const generateScript = async (isResume = false) => {
+const generateScript = async () => {
   try {
     // 设置生成状态为进行中
     isGenerating.value = true
@@ -578,30 +458,18 @@ const generateScript = async (isResume = false) => {
 
     currentStep.value = 0
 
-    // 如果是恢复任务,从缓存中获取当前步骤
-    if (isResume) {
-      const taskState = localStorage.getItem(getTaskStateKey())
-      if (taskState) {
-        try {
-          const parsedState = JSON.parse(taskState)
-          currentStep.value = parsedState.currentStep || 0
-        } catch (error) {
-          console.error('获取当前步骤失败:', error)
-        }
-      }
-    }
-
     // ========== 步骤1:生成课程基本信息 ==========
     if (!progressSteps.value[0].completed) {
       // 检查是否已取消
       if (isCancelled.value) return
 
       currentStep.value = 0
-      saveTaskState(currentStep.value)
 
       try {
-        // 创建会话
-        await createAiRoleIdConversation(255)
+        // 创建会话 - 使用第一个隐藏的主题选项
+        const hiddenTheme = themeOptions.value.find(item => !item.show)
+        const themeId = hiddenTheme ? hiddenTheme.value : 255
+        await createAiRoleIdConversation(themeId)
 
         // 检查会话是否创建成功
         if (!activeConversationId.value || activeConversationId.value === null) {
@@ -635,7 +503,6 @@ const generateScript = async (isResume = false) => {
       if (isCancelled.value) return
 
       currentStep.value = 1
-      saveTaskState(currentStep.value)
 
       try {
         // 设置进度状态
@@ -665,17 +532,16 @@ const generateScript = async (isResume = false) => {
         // 构建基础提示词(不含课程列表)
         const baseContent = `${prompt.value}(主讲人:${role.name},主讲人角色定位:${role.description};助讲有:${assistants})`
 
-        // 如果是恢复任务,从已保存的脚本数据中获取已完成的课程
-        let courseLessons = isResume && scriptData.lessons.length > 0 ? [...scriptData.lessons] : [];
+        // 初始化课程列表(不再支持恢复,每次都重新生成)
+        const courseLessons = []
         scriptData.lessons = []
 
-        // 循环生成每节课程内容(从保存的索引位置开始)
-        for (let i = currentLessonIndex.value; i < lessonList.value.length; i++) {
-          // 更新当前小节索引
-          currentLessonIndex.value = i
-
-          // 检查是否已取消
-          if (isCancelled.value) return
+        // 循环生成每节课程内容(从第0节开始)
+        for (let i = 0; i < lessonList.value.length; i++) {
+          // 检查是否已取消或组件已卸载
+          if (isCancelled.value || isUnmounted.value) {
+            throw new Error('生成已取消')
+          }
 
           let lesson = lessonList.value[i]
           progressSteps.value[1].text = "生成课程小节【" + lesson.name + "】..."
@@ -685,22 +551,31 @@ const generateScript = async (isResume = false) => {
 
           // 创建独立的输出对象,避免数据污染
           let lessonInfo = {courseName: lesson.name, courseLabel: lesson.id};
-          await doSendMessageStream(activeConversationId.value, content, lessonInfo)
+          
+          try {
+            await doSendMessageStream(activeConversationId.value, content, lessonInfo)
+          } catch (error) {
+            // 如果是取消或卸载导致的错误,直接抛出,不保存数据
+            if (error.message.includes('已取消') || error.message.includes('已卸载')) {
+              throw error
+            }
+            throw new Error(`生成第${i+1}节课程【${lesson.name}】失败:${error.message}`)
+          }
+
+          // 检查是否已取消或卸载(流式请求可能在后台完成)
+          if (isCancelled.value || isUnmounted.value) {
+            throw new Error('生成已取消')
+          }
 
           // 检查生成结果是否有效
-          if (!lessonInfo || !lessonInfo.courseName) {
-            throw new Error(`生成第${i+1}节课程【${lesson.name}】失败`)
+          if (!lessonInfo || !lessonInfo.courseName || !lessonInfo.sections) {
+            throw new Error(`生成第${i+1}节课程【${lesson.name}】失败:返回数据不完整`)
           }
 
           console.log(`生成第${i+1}节课程【${lesson.name}】成功:`, lessonInfo)
           courseLessons.push(lessonInfo)
-
-          // 保存当前进度(包含小节索引)
-          saveTaskState(currentStep.value)
         }
 
-        // 重置小节索引为0,准备下一次生成
-        currentLessonIndex.value = 0
         scriptData.lessons = courseLessons;
         progressSteps.value[1].completed = true;
         progressSteps.value[1].active = false;
@@ -717,16 +592,14 @@ const generateScript = async (isResume = false) => {
 
       currentStep.value = 2
       
-      // 重置图片进度为0%,避免恢复后显示旧进度
+      // 重置图片进度为0%
       progressSteps.value[2].progress = 0
       progressCounters.value.images.current = 0
-      
-      saveTaskState(currentStep.value)
 
       try {
         progressSteps.value[2].visible = true;
         progressSteps.value[2].active = true;
-        await generateAllImages(isResume)
+        await generateAllImages()
 
         // 验证图片生成结果
         if (scriptData.coverImage && scriptData.coverImage.prompt && !scriptData.coverImage.url) {
@@ -747,7 +620,6 @@ const generateScript = async (isResume = false) => {
       if (isCancelled.value) return
 
       currentStep.value = 3
-      saveTaskState(currentStep.value)
 
       try {
         progressSteps.value[3].visible = true;
@@ -782,10 +654,6 @@ const generateScript = async (isResume = false) => {
   } finally {
     // 确保生成状态被重置
     isGenerating.value = false
-    // 如果不是用户主动取消,则标记任务完成
-    if (!isCancelled.value) {
-      markTaskCompleted()
-    }
   }
 }
 
@@ -878,6 +746,11 @@ const doSendMessageStream = async (conversationId, content, outputContent) => {
     throw new Error('输出对象无效')
   }
 
+  // 检查是否已取消或卸载
+  if (isCancelled.value || isUnmounted.value) {
+    throw new Error('生成已取消或组件已卸载')
+  }
+
   // 创建AbortController用于中止请求
   conversationInAbortController.value = new AbortController()
   conversationInProgress.value = true
@@ -971,18 +844,15 @@ const doSendMessageStream = async (conversationId, content, outputContent) => {
 /**
  * 生成所有背景图
  * 包括封面图和所有环节的背景图
- * @param {boolean} isResume - 是否为恢复任务
  * @returns {Promise}
  */
-const generateAllImages = async (isResume = false) => {
+const generateAllImages = async () => {
   return new Promise(async (resolve, reject) => {
     isGeneratingImages.value = true
     try {
       // ========== 计算总图片数 ==========
-      // 如果是恢复任务,保留已有的轮询列表;否则清空重新开始
-      if (!isResume) {
-        inProgressImageMap.value = {}
-      }
+      // 清空轮询列表,每次都重新生成
+      inProgressImageMap.value = {}
       
       // 统计需要生成的图片总数(有prompt且无url的图片)
       progressCounters.value.images.total = 0
@@ -1007,13 +877,12 @@ const generateAllImages = async (isResume = false) => {
         }
       }
       
-      // 计算已完成的图片数(总数 - 正在生成的数量)
-      progressCounters.value.images.current = progressCounters.value.images.total - Object.keys(inProgressImageMap.value).length
+      // 初始化已完成数为0
+      progressCounters.value.images.current = 0
       
       // 更新进度显示
       if (progressCounters.value.images.total > 0) {
-        const progress = Math.round((progressCounters.value.images.current / progressCounters.value.images.total) * 100)
-        progressSteps.value[2].progress = Math.min(progress, 100)
+        progressSteps.value[2].progress = 0
       } else {
         progressSteps.value[2].progress = 100
       }
@@ -1025,15 +894,7 @@ const generateAllImages = async (isResume = false) => {
 
       // ========== 生成封面图(如果尚未生成) ==========
       if (scriptData.coverImage.prompt && !scriptData.coverImage.url) {
-        // 在恢复模式下,检查封面图是否已经在生成中
-        const coverAlreadyGenerating = Object.keys(inProgressImageMap.value).some(
-          key => inProgressImageMap.value[key].type === 'cover'
-        )
-        if (!isResume || !coverAlreadyGenerating) {
-          await generateCoverImage()
-        } else {
-          console.log('封面图已在生成中,跳过重复生成')
-        }
+        await generateCoverImage()
       }
 
       // ========== 生成所有环节的背景图 ==========
@@ -1056,17 +917,7 @@ const generateAllImages = async (isResume = false) => {
                 }
                 // 如果有prompt但没有url,则生成图片
                 if (section.backgroundImage.prompt && !section.backgroundImage.url) {
-                  // 在恢复模式下,检查该图片是否已经在生成中
-                  const imageAlreadyGenerating = Object.keys(inProgressImageMap.value).some(
-                    key => inProgressImageMap.value[key].type === 'image' &&
-                           inProgressImageMap.value[key].lessonIndex === lessonIndex &&
-                           inProgressImageMap.value[key].sectionIndex === sectionIndex
-                  )
-                  if (!isResume || !imageAlreadyGenerating) {
-                    await generateImage(lessonIndex, sectionIndex)
-                  } else {
-                    console.log(`背景图[${lessonIndex}-${sectionIndex}]已在生成中,跳过重复生成`)
-                  }
+                  await generateImage(lessonIndex, sectionIndex)
                 }
               }
             }
@@ -1081,9 +932,11 @@ const generateAllImages = async (isResume = false) => {
       if (Object.keys(inProgressImageMap.value).length === 0) {
         progressSteps.value[2].progress = 100
         isGeneratingImages.value = false
-        saveTaskState(2)
         resolve()
       } else {
+        // 先停止之前的定时器,避免多个定时器同时运行
+        stopImagePolling()
+        
         // 启动轮询检查图片生成状态
         startImagePolling()
         console.log('启动图片完成检查定时器')
@@ -1107,7 +960,6 @@ const generateAllImages = async (isResume = false) => {
             clearInterval(checkImagesCompleteTimer.value)
             checkImagesCompleteTimer.value = null
             stopImagePolling()
-            saveTaskState(2)
             resolve()
           }
         }, 1000)
@@ -1149,10 +1001,12 @@ const generateCoverImage = async () => {
     } else {
       console.error('无效的图片ID:', response.data)
       scriptData.coverImage.generating = false
+      throw new Error('生成封面图失败:无效的图片ID')
     }
   } catch (error) {
     console.error('生成封面图失败:', error)
     scriptData.coverImage.generating = false
+    throw error
   }
 }
 
@@ -1197,14 +1051,12 @@ const generateImage = async (lessonIndex, sectionIndex) => {
     } else {
       console.error('无效的图片ID:', res.data)
       media.generating = false
+      throw new Error('生成背景图失败:无效的图片ID')
     }
   } catch (error) {
     console.error('生成图片失败:', error);
-    // 检查是否是速率限制错误
-    if (error.response && error.response.status === 429) {
-      console.error('图片生成速率限制,请稍后重试')
-    }
     media.generating = false
+    throw error
   }
 }
 
@@ -1323,8 +1175,6 @@ const refreshWatchImages = async () => {
       const newProgress = Math.round((completedImages / progressCounters.value.images.total) * 100)
       progressSteps.value[2].progress = Math.min(newProgress, 100)
     }
-
-    saveTaskState(2)
   } catch (error) {
     console.error('轮询图片状态失败:', error)
   }
@@ -1364,7 +1214,9 @@ const generateAllVoiceovers = async () => {
     if (scriptData.lessons && scriptData.lessons.length > 0) {
       for (let lessonIndex = 0; lessonIndex < scriptData.lessons.length; lessonIndex++) {
         // 检查是否已取消
-        if (isCancelled.value) return
+        if (isCancelled.value) {
+          throw new Error('生成配音已取消')
+        }
 
         const lesson = scriptData.lessons[lessonIndex]
         if (lesson && lesson.sections) {
@@ -1381,8 +1233,6 @@ const generateAllVoiceovers = async () => {
                   if (progressCounters.value.voiceovers.total > 0) {
                     progressSteps.value[3].progress = Math.round((progressCounters.value.voiceovers.current / progressCounters.value.voiceovers.total) * 100)
                   }
-                  // 保存当前进度
-                  saveTaskState(3)
                 }
               }
             }
@@ -1393,6 +1243,7 @@ const generateAllVoiceovers = async () => {
     progressSteps.value[3].progress = 100
   } catch (error) {
     console.error('生成所有语音失败:', error)
+    throw error
   } finally {
     isGeneratingVoiceovers.value = false
   }
@@ -1425,7 +1276,7 @@ const generateVoiceover = async (lessonIndex, sectionIndex, dialogueIndex) => {
   if (!role) {
     console.error('未找到角色:', dialogue.roleName)
     dialogue.generatingVoiceover = false
-    return
+    throw new Error('生成配音失败:未找到角色 ' + dialogue.roleName)
   }
 
   try {
@@ -1441,13 +1292,21 @@ const generateVoiceover = async (lessonIndex, sectionIndex, dialogueIndex) => {
       command: command
     })
 
+    if (!response.data) {
+      throw new Error('生成配音失败:返回无效数据')
+    }
+
     dialogue.voiceoverUrl = response.data
     // 保存脚本数据
     localStorage.setItem(SCRIPT_DATA_KEY, JSON.stringify(scriptData))
   } catch (error) {
     console.error('生成语音失败:', error)
-  } finally {
     dialogue.generatingVoiceover = false
+    throw error
+  } finally {
+    if (!dialogue.generatingVoiceover) {
+      dialogue.generatingVoiceover = false
+    }
   }
 }
 
@@ -1492,31 +1351,22 @@ const assistantOptions = computed(() => {
   return options;
 });
 
-// 计算生成主题下拉框选项
-const themeOptions = computed(() => {
-  const themes = [
-    {
-      name: '课程封面',
-      value: 255,
-      sections: [],
-      show: false
-    },
-    {
-      name: '诗词课',
-      value: 256,
-      sections: [1, 2, 3],
-      show: true
-    },
-    {
-      name: '通用课程',
-      value: 13,
-      sections: [1, 2, 3] ,
-      show: true
+// 生成主题选项(使用 ref 保持引用稳定)
+const themeOptions = ref([
+  {
+    name: '课程封面',
+    value: 255,
+    sections: [],
+    show: false,
+    get label() {
+      return '课程封面'
     }
-  ]
-
-  return themes.map(theme => ({
-    ...theme,
+  },
+  {
+    name: '诗词课',
+    value: 256,
+    sections: [1, 2, 3],
+    show: true,
     get label() {
       const lessonNames = this.sections
           .map(sectionId => {
@@ -1524,9 +1374,29 @@ const themeOptions = computed(() => {
             return lesson ? lesson.name : ''
           })
           .filter(Boolean)
-      return `${this.name}(${lessonNames.join(', ')})`
+      return `诗词课(${lessonNames.join(', ')})`
     }
-  }))
+  },
+  {
+    name: '通用课程',
+    value: 13,
+    sections: [1, 2, 3] ,
+    show: true,
+    get label() {
+      const lessonNames = this.sections
+          .map(sectionId => {
+            const lesson = lessonList.value.find(l => l.id === sectionId.toString())
+            return lesson ? lesson.name : ''
+          })
+          .filter(Boolean)
+      return `通用课程(${lessonNames.join(', ')})`
+    }
+  }
+])
+
+// 获取可用的主题选项(过滤掉不显示的选项)
+const availableThemeOptions = computed(() => {
+  return themeOptions.value.filter(item => item.show)
 })
 
 /**
@@ -1560,10 +1430,6 @@ const courseDropdown = ref({
   options: []
 });
 
-watch(themeOptions, (newOptions) => {
-  courseDropdown.value.options = newOptions;
-}, { immediate: true });
-
 const teacherDropdown = ref({
   key: 'teacher',
   value: '主讲',
@@ -1641,12 +1507,30 @@ const doSendMessage = async (content) => {
     return;
   }
 
+  // 弹出确认框
+  const confirmResult = await ElMessageBox.confirm(
+      '本次生成会一次性生成完整课程,预计需要3-5分钟时间。生成过程中请保持页面打开,不要刷新或退出,否则可能导致课程生成不完整。建议您在网络稳定的环境下进行此操作,确保获得最佳体验。',
+      '生成确认',
+      {
+        confirmButtonText: '确认生成',
+        cancelButtonText: '取消',
+        type: 'warning',
+        showClose: false
+      }
+  ).catch(() => {
+    return 'cancel'
+  })
+
   // 检查是否正在生成中
   if (isGenerating.value) {
     Message().info('正在生成中,请稍候')
     return
   }
 
+  if (confirmResult !== 'confirm') {
+    return
+  }
+
   // 清除之前的防抖定时器
   if (sendDebounceTimer) {
     clearTimeout(sendDebounceTimer)
@@ -1727,34 +1611,39 @@ onMounted(async () => {
   teacherDropdown.value.options = teacherOptions.value;
   assistantDropdown.value.options = assistantOptions.value;
 
-  // 检查是否有未完成的任务需要恢复
-  if (hasPendingTask.value) {
-    Message().info('检测到未完成的任务,正在恢复...')
-    restoreTaskState()
-    await generateScript(true)
-  }
+  // 清除可能存在的旧脚本数据缓存
+  localStorage.removeItem(SCRIPT_DATA_KEY)
 });
 
 /**
- * 组件卸载时执行
  * 清理所有资源,避免内存泄漏
  */
 onUnmounted(() => {
   // 标记组件已卸载
   isUnmounted.value = true
 
-  // 停止图片轮询定时器
+  // 标记为已取消,阻止所有后续操作
+  isCancelled.value = true
+
+  // 停止图片轮询定时器(包含所有相关定时器)
   stopImagePolling()
 
   // 中止正在进行的对话请求
   if (conversationInAbortController.value) {
     conversationInAbortController.value.abort()
+    conversationInAbortController.value = null
   }
 
-  // 如果正在生成中,保存当前状态以便下次恢复
-  if (isGenerating.value) {
-    saveTaskState(currentStep.value)
-  }
+  // 重置所有生成状态
+  isGenerating.value = false
+  isGeneratingImages.value = false
+  isGeneratingVoiceovers.value = false
+  conversationInProgress.value = false
+
+  // 清除脚本数据缓存
+  localStorage.removeItem(SCRIPT_DATA_KEY)
+  
+  console.log('组件已卸载,所有资源已清理')
 });
 </script>
 

+ 0 - 1
src/views/AIPage/AIGeneralCourse.vue

@@ -291,7 +291,6 @@ const saveActiveState = (menuId, index) => {
 const refreshData = async () => {
 
   const res = await ClassOutline(localStorage.getItem('selectedGradeId'), menuConfigTemp[3].type)
-  debugger
   if (res.code === 0) {
     selfStudyCourseData.value = processCourseData(res.data)
   }