Przeglądaj źródła

feat: render player state on voice-bar play buttons

Each play button derives idle / loading / playing / error from
the player refs. Errors swap "0:04" for "点击重试"; clicking
the warning button retries via the same togglePlay path. AI and
student variants share the same logic with the existing color
schemes preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jimmylee 1 miesiąc temu
rodzic
commit
373c7051b8

+ 97 - 10
src/views/Editor/EnglishSpeaking/preview/DialogueChatView.vue

@@ -48,13 +48,40 @@
           <div class="msg-col">
             <!-- 音频条 -->
             <div v-if="message.content || message.status === 'done'" class="voice-bar voice-ai">
-              <button class="play-btn play-ai" @click="togglePlay(message.id)">
-                <svg v-if="player.playingId.value !== message.id" width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
-                  <polygon points="5 3 19 12 5 21 5 3" />
+              <button
+                class="play-btn play-ai"
+                :class="{ 'play-btn-error': player.errorId.value === message.id }"
+                @click="togglePlay(message.id)"
+              >
+                <svg
+                  v-if="player.loadingId.value === message.id"
+                  class="play-spinner"
+                  width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+                  stroke-width="2" stroke-linecap="round"
+                >
+                  <path d="M21 12a9 9 0 1 1-6.219-8.56" />
                 </svg>
-                <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
+                <svg
+                  v-else-if="player.playingId.value === message.id"
+                  width="12" height="12" viewBox="0 0 24 24" fill="currentColor"
+                >
                   <rect x="6" y="4" width="4" height="16" /><rect x="14" y="4" width="4" height="16" />
                 </svg>
+                <svg
+                  v-else-if="player.errorId.value === message.id"
+                  width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+                  stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
+                >
+                  <path d="M12 9v4" />
+                  <path d="M12 17h.01" />
+                  <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
+                </svg>
+                <svg
+                  v-else
+                  width="12" height="12" viewBox="0 0 24 24" fill="currentColor"
+                >
+                  <polygon points="5 3 19 12 5 21 5 3" />
+                </svg>
               </button>
               <div class="wave-bar-group">
                 <div
@@ -64,7 +91,14 @@
                   :style="{ height: `${Math.abs(Math.sin(i * 0.7)) * 8 + 3}px` }"
                 />
               </div>
-              <span class="voice-duration voice-duration-ai">0:04</span>
+              <span
+                v-if="player.errorId.value === message.id"
+                class="play-error-hint"
+              >点击重试</span>
+              <span
+                v-else
+                class="voice-duration voice-duration-ai"
+              >0:04</span>
             </div>
 
             <!-- 英文文本 -->
@@ -95,7 +129,14 @@
         <div v-else class="msg-row msg-student fade-in">
           <!-- 音频条(橙色) -->
           <div v-if="message.content || message.status !== 'loading'" class="voice-bar voice-student">
-            <span class="voice-duration voice-duration-student">0:04</span>
+            <span
+              v-if="player.errorId.value === message.id"
+              class="play-error-hint play-error-hint-student"
+            >点击重试</span>
+            <span
+              v-else
+              class="voice-duration voice-duration-student"
+            >0:04</span>
             <div class="wave-bar-group">
               <div
                 v-for="i in 14"
@@ -104,13 +145,40 @@
                 :style="{ height: `${Math.abs(Math.sin(i * 0.7)) * 8 + 3}px` }"
               />
             </div>
-            <button class="play-btn play-student" @click="togglePlay(message.id)">
-              <svg v-if="player.playingId.value !== message.id" width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
-                <polygon points="5 3 19 12 5 21 5 3" />
+            <button
+              class="play-btn play-student"
+              :class="{ 'play-btn-error': player.errorId.value === message.id }"
+              @click="togglePlay(message.id)"
+            >
+              <svg
+                v-if="player.loadingId.value === message.id"
+                class="play-spinner"
+                width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+                stroke-width="2" stroke-linecap="round"
+              >
+                <path d="M21 12a9 9 0 1 1-6.219-8.56" />
               </svg>
-              <svg v-else width="12" height="12" viewBox="0 0 24 24" fill="currentColor">
+              <svg
+                v-else-if="player.playingId.value === message.id"
+                width="12" height="12" viewBox="0 0 24 24" fill="currentColor"
+              >
                 <rect x="6" y="4" width="4" height="16" /><rect x="14" y="4" width="4" height="16" />
               </svg>
+              <svg
+                v-else-if="player.errorId.value === message.id"
+                width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor"
+                stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
+              >
+                <path d="M12 9v4" />
+                <path d="M12 17h.01" />
+                <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z" />
+              </svg>
+              <svg
+                v-else
+                width="12" height="12" viewBox="0 0 24 24" fill="currentColor"
+              >
+                <polygon points="5 3 19 12 5 21 5 3" />
+              </svg>
             </button>
           </div>
 
@@ -1077,6 +1145,25 @@ onUnmounted(() => {
   color: #fff;
   &:hover { background: rgba(255,255,255,0.3); }
 }
+.play-btn-error {
+  background: #fef2f2 !important;
+  color: #dc2626 !important;
+  border: 1px solid #fecaca;
+  &:hover { background: #fee2e2 !important; }
+}
+.play-spinner {
+  animation: spin 1s linear infinite;
+}
+.play-error-hint {
+  font-size: 10px;
+  color: #dc2626;
+  font-weight: 500;
+  flex-shrink: 0;
+  white-space: nowrap;
+}
+.play-error-hint-student {
+  color: #fff;
+}
 .wave-bar-group {
   flex: 1;
   display: flex;