Просмотр исходного кода

fix(speaking): keep restart/complete buttons visible when overall is missing

Decouple OverallReport's action buttons from the evaluation prop so users
can always restart or finish the dialogue, even when the overall report is
null/failed/incomplete. Parents now render OverallReport unconditionally
and pass the (possibly null) evaluation through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jimmylee 2 месяцев назад
Родитель
Сommit
86c967b9b6

+ 75 - 66
src/views/Editor/EnglishSpeaking/preview/OverallReport.vue

@@ -1,84 +1,92 @@
 <template>
   <div class="overall-report">
-    <!-- 完成提示 -->
-    <div class="completion-header">
-      <div class="completion-emoji">🎉</div>
-      <h2 class="completion-title">对话完成!</h2>
-      <p class="completion-subtitle">
-        与 {{ role?.name || 'AI' }} 对话 {{ evaluation.statistics.totalRounds }} 轮 · {{ formatDuration(evaluation.statistics.totalDuration) }}
-      </p>
-    </div>
+    <template v-if="evaluation">
+      <!-- 完成提示 -->
+      <div class="completion-header">
+        <div class="completion-emoji">🎉</div>
+        <h2 class="completion-title">对话完成!</h2>
+        <p class="completion-subtitle">
+          与 {{ role?.name || 'AI' }} 对话 {{ evaluation.statistics.totalRounds }} 轮 · {{ formatDuration(evaluation.statistics.totalDuration) }}
+        </p>
+      </div>
 
-    <!-- 综合评分区域 -->
-    <div v-if="scoreDisplayMode !== 'comment_only'" class="score-section">
-      <div class="score-header">
-        <div>
-          <div class="score-label">综合评分</div>
-          <!-- 数字模式 -->
-          <div v-if="scoreDisplayMode === 'numeric'" class="score-display">
-            <span class="score-number">{{ evaluation.overallScore }}</span>
-            <span class="score-unit">分</span>
-          </div>
-          <!-- 字母模式 -->
-          <div v-else class="score-display">
-            <span class="score-letter" :style="{ color: letterColorMap[scoreToLetter(evaluation.overallScore)] }">
-              {{ scoreToLetter(evaluation.overallScore) }}
-            </span>
-            <span class="score-unit">级</span>
-          </div>
-        </div>
-        <div class="score-meta">
-          <div class="level-badge" :style="{ backgroundColor: levelConfig.bgColor, color: levelConfig.color }">
-            {{ levelConfig.label }}
+      <!-- 综合评分区域 -->
+      <div v-if="scoreDisplayMode !== 'comment_only'" class="score-section">
+        <div class="score-header">
+          <div>
+            <div class="score-label">综合评分</div>
+            <!-- 数字模式 -->
+            <div v-if="scoreDisplayMode === 'numeric'" class="score-display">
+              <span class="score-number">{{ evaluation.overallScore }}</span>
+              <span class="score-unit">分</span>
+            </div>
+            <!-- 字母模式 -->
+            <div v-else class="score-display">
+              <span class="score-letter" :style="{ color: letterColorMap[scoreToLetter(evaluation.overallScore)] }">
+                {{ scoreToLetter(evaluation.overallScore) }}
+              </span>
+              <span class="score-unit">级</span>
+            </div>
           </div>
-          <div class="percentile-text">
-            超过 <span class="percentile-value">{{ evaluation.percentile }}%</span> 同学
+          <div class="score-meta">
+            <div class="level-badge" :style="{ backgroundColor: levelConfig.bgColor, color: levelConfig.color }">
+              {{ levelConfig.label }}
+            </div>
+            <div class="percentile-text">
+              超过 <span class="percentile-value">{{ evaluation.percentile }}%</span> 同学
+            </div>
           </div>
         </div>
-      </div>
 
-      <!-- 四维能力分析 -->
-      <div class="ability-section">
-        <div class="ability-label">能力分析</div>
-        <div class="ability-bars">
-          <div v-for="dim in visibleDimensions" :key="dim.key" class="ability-row">
-            <span class="ability-name">{{ dim.label }}</span>
-            <div class="ability-track">
-              <div class="ability-fill" :style="{ width: `${getDimValue(dim.key)}%` }" />
+        <!-- 四维能力分析 -->
+        <div class="ability-section">
+          <div class="ability-label">能力分析</div>
+          <div class="ability-bars">
+            <div v-for="dim in visibleDimensions" :key="dim.key" class="ability-row">
+              <span class="ability-name">{{ dim.label }}</span>
+              <div class="ability-track">
+                <div class="ability-fill" :style="{ width: `${getDimValue(dim.key)}%` }" />
+              </div>
+              <span class="ability-value">
+                {{ scoreDisplayMode === 'letter' ? scoreToLetter(getDimValue(dim.key)) : getDimValue(dim.key) }}
+              </span>
             </div>
-            <span class="ability-value">
-              {{ scoreDisplayMode === 'letter' ? scoreToLetter(getDimValue(dim.key)) : getDimValue(dim.key) }}
-            </span>
           </div>
         </div>
       </div>
-    </div>
 
-    <!-- AI 点评 -->
-    <div class="ai-comment-section">
-      <div class="ai-comment-inner">
-        <div class="ai-avatar-circle">{{ role?.avatar || '👦' }}</div>
-        <div class="ai-comment-body">
-          <div class="ai-comment-name">{{ role?.name || 'AI' }} 说</div>
-          <p class="ai-comment-text">{{ evaluation.aiComment }}</p>
+      <!-- AI 点评 -->
+      <div class="ai-comment-section">
+        <div class="ai-comment-inner">
+          <div class="ai-avatar-circle">{{ role?.avatar || '👦' }}</div>
+          <div class="ai-comment-body">
+            <div class="ai-comment-name">{{ role?.name || 'AI' }} 说</div>
+            <p class="ai-comment-text">{{ evaluation.aiComment }}</p>
+          </div>
         </div>
       </div>
-    </div>
 
-    <!-- 亮点与建议 -->
-    <div class="highlights-grid">
-      <div class="highlight-card">
-        <div class="highlight-header"><span class="highlight-icon-good">✓</span> 亮点</div>
-        <div class="highlight-list">
-          <div v-for="(h, i) in evaluation.highlights.slice(0, 3)" :key="i" class="highlight-item">• {{ h }}</div>
+      <!-- 亮点与建议 -->
+      <div class="highlights-grid">
+        <div class="highlight-card">
+          <div class="highlight-header"><span class="highlight-icon-good">✓</span> 亮点</div>
+          <div class="highlight-list">
+            <div v-for="(h, i) in evaluation.highlights.slice(0, 3)" :key="i" class="highlight-item">• {{ h }}</div>
+          </div>
         </div>
-      </div>
-      <div class="highlight-card">
-        <div class="highlight-header"><span class="highlight-icon-improve">→</span> 建议</div>
-        <div class="highlight-list">
-          <div v-for="(h, i) in evaluation.improvements.slice(0, 3)" :key="i" class="highlight-item">• {{ h }}</div>
+        <div class="highlight-card">
+          <div class="highlight-header"><span class="highlight-icon-improve">→</span> 建议</div>
+          <div class="highlight-list">
+            <div v-for="(h, i) in evaluation.improvements.slice(0, 3)" :key="i" class="highlight-item">• {{ h }}</div>
+          </div>
         </div>
       </div>
+    </template>
+
+    <!-- 无评分时的占位提示 -->
+    <div v-else class="completion-header">
+      <div class="completion-emoji">🎉</div>
+      <h2 class="completion-title">对话完成!</h2>
     </div>
 
     <!-- 统计数据 -->
@@ -132,7 +140,7 @@ import { computed } from 'vue'
 import type { OverallEvaluation, ScoreLevel, ScoreDisplayMode, PreviewAIRole } from '@/types/englishSpeaking'
 
 interface Props {
-  evaluation: OverallEvaluation
+  evaluation: OverallEvaluation | null
   role: PreviewAIRole | null
   scoreDisplayMode?: ScoreDisplayMode
 }
@@ -164,7 +172,7 @@ const dimensionConfig = [
   { label: '流畅度', key: 'fluency' },
 ]
 
-const levelConfig = computed(() => scoreLevelConfig[props.evaluation.scoreLevel])
+const levelConfig = computed(() => scoreLevelConfig[props.evaluation?.scoreLevel ?? 'good'])
 const visibleDimensions = computed(() => dimensionConfig)
 
 function scoreToLetter(score: number): string {
@@ -182,7 +190,8 @@ function formatDuration(seconds: number) {
 }
 
 function getDimValue(key: string): number {
-  return (props.evaluation.dimensions as Record<string, number>)[key] || 0
+  const dims = props.evaluation?.dimensions as Record<string, number> | undefined
+  return dims?.[key] || 0
 }
 </script>
 

+ 1 - 2
src/views/Editor/EnglishSpeaking/preview/TopicDiscussionPreview.vue

@@ -66,14 +66,13 @@
           {{ reportError }}
         </div>
         <OverallReport
-          v-if="overallEvaluationForDisplay"
           :evaluation="overallEvaluationForDisplay"
           :role="mockRole"
           :scoreDisplayMode="'numeric'"
           @restart="resetPreview"
           @complete="resetPreview"
         />
-        <div v-if="shouldShowOverallReport" class="report-divider" />
+        <div class="report-divider" />
         <DetailedReport :sentenceEvaluations="displaySentenceEvaluations" />
       </div>
     </div>

+ 1 - 2
src/views/Student/components/SpeakingClassPanel/StudentReportModal.vue

@@ -40,8 +40,7 @@
 
         <template v-else>
           <OverallReport
-            v-if="showOverall && report?.evaluation"
-            :evaluation="report.evaluation"
+            :evaluation="showOverall ? (report?.evaluation ?? null) : null"
             :role="role"
             :scoreDisplayMode="'numeric'"
             @restart="$emit('close')"