|
@@ -7,7 +7,7 @@
|
|
|
<div class="back-icon-circle" @click="goBackToMain">
|
|
<div class="back-icon-circle" @click="goBackToMain">
|
|
|
<el-icon class="back-icon"><ArrowLeftBold /></el-icon>
|
|
<el-icon class="back-icon"><ArrowLeftBold /></el-icon>
|
|
|
</div>
|
|
</div>
|
|
|
- <span class="back-text" @click="goBackToMain">返回课程</span>
|
|
|
|
|
|
|
+ <span class="back-text" @click="goBackToMain">{{ backText }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
<!-- 标题 -->
|
|
<!-- 标题 -->
|
|
|
<div class="title-center">
|
|
<div class="title-center">
|
|
@@ -121,6 +121,10 @@ const props = defineProps({
|
|
|
scriptRoles: {
|
|
scriptRoles: {
|
|
|
type: Array,
|
|
type: Array,
|
|
|
default: () => []
|
|
default: () => []
|
|
|
|
|
+ },
|
|
|
|
|
+ backText: {
|
|
|
|
|
+ type: String,
|
|
|
|
|
+ default: '返回课程'
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
|
|
@@ -131,6 +135,8 @@ const currentSectionIndex = ref(0)
|
|
|
const currentDialogueIndex = ref(0)
|
|
const currentDialogueIndex = ref(0)
|
|
|
// 是否正在播放
|
|
// 是否正在播放
|
|
|
const isPlaying = ref(false)
|
|
const isPlaying = ref(false)
|
|
|
|
|
+// 用户输入前是否正在播放(用于AI回答完成后继续播放)
|
|
|
|
|
+const wasPlayingBeforeUserInput = ref(false)
|
|
|
// 用户输入内容
|
|
// 用户输入内容
|
|
|
const userInput = ref('')
|
|
const userInput = ref('')
|
|
|
// 语音录音状态
|
|
// 语音录音状态
|
|
@@ -355,6 +361,12 @@ const togglePlay = () => {
|
|
|
const playSequence = async () => {
|
|
const playSequence = async () => {
|
|
|
if (!isPlaying.value) return
|
|
if (!isPlaying.value) return
|
|
|
|
|
|
|
|
|
|
+ // 如果当前是用户输入卡片,暂停播放等待用户输入
|
|
|
|
|
+ if (currentDialogue.value?.type === 'user') {
|
|
|
|
|
+ wasPlayingBeforeUserInput.value = true
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
// 播放当前对话语音
|
|
// 播放当前对话语音
|
|
|
await playDialogueAudio()
|
|
await playDialogueAudio()
|
|
|
|
|
|
|
@@ -435,6 +447,11 @@ const goBackToMain = () => {
|
|
|
|
|
|
|
|
// 键盘事件处理,键盘左右箭头控制对话
|
|
// 键盘事件处理,键盘左右箭头控制对话
|
|
|
const handleKeydown = (event) => {
|
|
const handleKeydown = (event) => {
|
|
|
|
|
+ // 如果当前是用户输入对话,不处理键盘事件,让默认行为生效(在输入框中左右移动光标)
|
|
|
|
|
+ if (currentDialogue.value?.type === 'user') {
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (event.key === 'ArrowLeft') {
|
|
if (event.key === 'ArrowLeft') {
|
|
|
playPrevious()
|
|
playPrevious()
|
|
|
} else if (event.key === 'ArrowRight') {
|
|
} else if (event.key === 'ArrowRight') {
|
|
@@ -603,6 +620,19 @@ const stopStream = async () => {
|
|
|
conversationInProgress.value = false
|
|
conversationInProgress.value = false
|
|
|
|
|
|
|
|
console.log(`结束对话!更改状态: `,conversationInProgress.value)
|
|
console.log(`结束对话!更改状态: `,conversationInProgress.value)
|
|
|
|
|
+
|
|
|
|
|
+ // AI回答完成后,如果之前是播放状态,继续播放
|
|
|
|
|
+ if (wasPlayingBeforeUserInput.value) {
|
|
|
|
|
+ wasPlayingBeforeUserInput.value = false
|
|
|
|
|
+ // 跳到下一个对话并继续播放
|
|
|
|
|
+ if (playNext()) {
|
|
|
|
|
+ playSequence()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 播放完毕
|
|
|
|
|
+ isPlaying.value = false
|
|
|
|
|
+ stopAllAudio()
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 组件挂载时添加键盘事件监听
|
|
// 组件挂载时添加键盘事件监听
|
|
@@ -779,38 +809,48 @@ onUnmounted(() => {
|
|
|
background-position: bottom;
|
|
background-position: bottom;
|
|
|
background-repeat: no-repeat;
|
|
background-repeat: no-repeat;
|
|
|
opacity: 0;
|
|
opacity: 0;
|
|
|
- transform: scale(0.5);
|
|
|
|
|
- animation: characterEnter 0.8s ease forwards;
|
|
|
|
|
z-index: 1;
|
|
z-index: 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.character.left {
|
|
.character.left {
|
|
|
left: rpx(30);
|
|
left: rpx(30);
|
|
|
|
|
+ transform: translateX(-100%);
|
|
|
|
|
+ animation: characterEnterLeft 0.8s ease forwards;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.character.right {
|
|
.character.right {
|
|
|
right: rpx(30);
|
|
right: rpx(30);
|
|
|
|
|
+ transform: translateX(100%) scaleX(-1);
|
|
|
|
|
+ animation: characterEnterRight 0.8s ease forwards;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.character.right {
|
|
|
|
|
- transform: scale(1) scaleX(-1) !important;
|
|
|
|
|
|
|
+@keyframes characterEnterLeft {
|
|
|
|
|
+ 0% {
|
|
|
|
|
+ opacity: 0;
|
|
|
|
|
+ transform: translateX(-100%) scale(0.8);
|
|
|
|
|
+ }
|
|
|
|
|
+ 70% {
|
|
|
|
|
+ opacity: 0.9;
|
|
|
|
|
+ transform: translateX(10%) scale(1.05);
|
|
|
|
|
+ }
|
|
|
|
|
+ 100% {
|
|
|
|
|
+ opacity: 1;
|
|
|
|
|
+ transform: translateX(0) scale(1);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-@keyframes characterEnter {
|
|
|
|
|
|
|
+@keyframes characterEnterRight {
|
|
|
0% {
|
|
0% {
|
|
|
opacity: 0;
|
|
opacity: 0;
|
|
|
- transform: scale(0.5);
|
|
|
|
|
- bottom: -rpx(50);
|
|
|
|
|
|
|
+ transform: translateX(100%) scale(0.8) scaleX(-1);
|
|
|
}
|
|
}
|
|
|
70% {
|
|
70% {
|
|
|
opacity: 0.9;
|
|
opacity: 0.9;
|
|
|
- transform: scale(1.05);
|
|
|
|
|
- bottom: rpx(10);
|
|
|
|
|
|
|
+ transform: translateX(-10%) scale(1.05) scaleX(-1);
|
|
|
}
|
|
}
|
|
|
100% {
|
|
100% {
|
|
|
opacity: 1;
|
|
opacity: 1;
|
|
|
- transform: scale(1);
|
|
|
|
|
- bottom: 0;
|
|
|
|
|
|
|
+ transform: translateX(0) scale(1) scaleX(-1);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|