Преглед изворни кода

feat(speaking): render video scene grid + gate start button

Add VideoSceneGrid and VideoPlayerModal integration to TopicDiscussionPreview,
replace topicMissing with startBlockReason/startBlocked, pass mode + selectedVideo
to createSession, and reset video state in resetPreview.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
jimmylee пре 1 месец
родитељ
комит
3dc8748437
1 измењених фајлова са 35 додато и 4 уклоњено
  1. 35 4
      src/views/Editor/EnglishSpeaking/preview/TopicDiscussionPreview.vue

+ 35 - 4
src/views/Editor/EnglishSpeaking/preview/TopicDiscussionPreview.vue

@@ -27,10 +27,17 @@
       <div class="ready-body">
         <!-- <span class="topic-emoji">🐼</span> -->
         <h3 class="topic-name">{{ speakingStore.config.topic || topic }}</h3>
+        <VideoSceneGrid
+          v-if="isVideoSceneMode"
+          :videos="videos"
+          :selected-id="selectedVideo?.id ?? null"
+          @select="selectedVideo = $event"
+          @play="playingVideo = $event"
+        />
       </div>
 
       <div class="ready-footer">
-        <button class="start-btn" :disabled="sessionCreating || topicMissing" @click="startDialogue">
+        <button class="start-btn" :disabled="sessionCreating || startBlocked" @click="startDialogue">
           <svg v-if="!sessionCreating" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
             stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
             <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
@@ -41,7 +48,7 @@
           <span v-else class="start-btn-spinner" />
           {{ sessionCreating ? '创建中…' : '开始对话' }}
         </button>
-        <p v-if="topicMissing" class="session-error-text">至少设置topic</p>
+        <p v-if="startBlockReason" class="session-error-text">{{ startBlockReason }}</p>
         <p v-else-if="sessionError" class="session-error-text">{{ sessionError }}</p>
       </div>
     </div>
@@ -78,12 +85,18 @@
     </div>
 
   </div>
+    <VideoPlayerModal
+      v-if="playingVideo"
+      :title="playingVideo.title"
+      :video-url="playingVideo.videoUrl"
+      @close="playingVideo = null"
+    />
   </div>
 </template>
 
 <script lang="ts" setup>
 import { ref, computed, watch, onMounted, onUnmounted, inject } from 'vue'
-import type { DialogueReport, OverallEvaluation, PreviewAIRole, PreviewDialogueState, SessionStartInfo } from '@/types/englishSpeaking'
+import type { DialogueReport, OverallEvaluation, PreviewAIRole, PreviewDialogueState, SessionStartInfo, VideoMeta } from '@/types/englishSpeaking'
 import { useSpeakingStore } from '@/store/speaking'
 import { getSpeakingConfig } from '@/services/speaking'
 import { createDialogueApi, DialogueApiError } from '../services/llmService'
@@ -91,6 +104,9 @@ import { createDialogueApi, DialogueApiError } from '../services/llmService'
 import DialogueChatView from './DialogueChatView.vue'
 import OverallReport from './OverallReport.vue'
 import DetailedReport from './DetailedReport.vue'
+import VideoSceneGrid from './VideoSceneGrid.vue'
+import VideoPlayerModal from './VideoPlayerModal.vue'
+import topicDiscussionVideos from '../data/topicDiscussionVideos.json'
 
 interface Props {
   topic?: string
@@ -157,7 +173,18 @@ const preparedSession = ref<SessionStartInfo | null>(null)
 const historyChecked = ref(false)
 const historyLoadToken = ref(0)
 
-const topicMissing = computed(() => !(speakingStore.config.topic ?? '').trim())
+const videos = topicDiscussionVideos as VideoMeta[]
+const selectedVideo = ref<VideoMeta | null>(null)
+const playingVideo = ref<VideoMeta | null>(null)
+
+const isVideoSceneMode = computed(() => speakingStore.config.videoScene?.enabled === true)
+
+const startBlockReason = computed<string | null>(() => {
+  if (!(speakingStore.config.topic ?? '').trim()) return '至少设置topic'
+  if (isVideoSceneMode.value && !selectedVideo.value) return '请先选择一个影片'
+  return null
+})
+const startBlocked = computed(() => startBlockReason.value !== null)
 
 const runtimeParams = computed(() => {
   const params = new URLSearchParams(window.location.search)
@@ -324,6 +351,8 @@ async function startDialogue() {
       sentences: speakingStore.config.learningGoals.sentences,
       configId: props.configId || null,
       userId: isStudentRuntime.value ? runtimeUserId.value : null,
+      mode: isVideoSceneMode.value ? 'video_scene' : 'normal',
+      selectedVideo: selectedVideo.value ?? undefined,
     })
     preparedSession.value = {
       sessionId: info.sessionId,
@@ -468,6 +497,8 @@ function resetPreview() {
   preparedSession.value = null
   sessionError.value = null
   sessionCreating.value = false
+  selectedVideo.value = null
+  playingVideo.value = null
 }
 
 // ── Sync with speakingStore (让 CanvasTool 可以驱动"重置预览") ──