|
|
@@ -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;
|