- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
120 lines
3.6 KiB
Vue
120 lines
3.6 KiB
Vue
<template>
|
||
<div class="ai-practice-page">
|
||
<!-- 语音对话模式(默认) -->
|
||
<VoiceChat v-if="chatModel === 'voice'" />
|
||
|
||
<!-- 文本对话模式 -->
|
||
<TextChat v-else />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { onMounted, onUnmounted } from 'vue'
|
||
import { useRoute, useRouter } from 'vue-router'
|
||
import { storeToRefs } from 'pinia'
|
||
import { usePracticeStore } from '@/stores/practiceStore'
|
||
import { practiceApi } from '@/api/practice'
|
||
import { ElMessage } from 'element-plus'
|
||
import VoiceChat from '@/components/VoiceChat.vue'
|
||
import TextChat from '@/components/TextChat.vue'
|
||
|
||
const route = useRoute()
|
||
const router = useRouter()
|
||
const practiceStore = usePracticeStore()
|
||
const { chatModel } = storeToRefs(practiceStore)
|
||
|
||
// ==================== 生命周期 ====================
|
||
|
||
onMounted(async () => {
|
||
// 检查陪练模式:自由对话(mode=free)、直接模式(sceneId)或课程模式(sceneData)
|
||
const mode = route.query.mode as string
|
||
const sceneId = route.query.sceneId ? Number(route.query.sceneId) : null
|
||
const sceneData = route.query.sceneData as string
|
||
|
||
if (mode === 'free') {
|
||
// 自由对话模式:创建默认场景(id为null,表示无需数据库场景)
|
||
const defaultScene = {
|
||
id: null,
|
||
name: 'AI自由对话陪练',
|
||
description: '与AI进行自由对话,提升您的沟通能力',
|
||
background: '这是一个自由对话场景,您可以与AI进行任何话题的交流练习。',
|
||
ai_role: '我是您的AI陪练助手,我会根据您的话题提供专业的指导和反馈。',
|
||
objectives: [
|
||
'提升沟通表达能力',
|
||
'增强语言组织能力',
|
||
'培养自信心'
|
||
],
|
||
type: 'free',
|
||
difficulty: 'beginner',
|
||
status: 'active'
|
||
}
|
||
practiceStore.setCurrentScene(defaultScene)
|
||
console.log('自由对话模式启动')
|
||
} else if (mode === 'course' && sceneData) {
|
||
// 课程模式:从URL解析场景数据
|
||
try {
|
||
const scene = JSON.parse(sceneData)
|
||
practiceStore.setCurrentScene(scene)
|
||
console.log('课程模式场景加载成功:', scene.name)
|
||
} catch (error) {
|
||
console.error('解析场景数据失败:', error)
|
||
ElMessage.error('场景数据格式错误')
|
||
router.push('/trainee/ai-practice-center')
|
||
return
|
||
}
|
||
} else if (sceneId) {
|
||
// 直接模式:从数据库加载场景详情
|
||
await loadSceneDetail(sceneId)
|
||
} else {
|
||
// 缺少必要参数
|
||
ElMessage.error('缺少场景参数')
|
||
router.push('/trainee/ai-practice-center')
|
||
return
|
||
}
|
||
|
||
// 默认设置为语音模式
|
||
practiceStore.setChatModel('voice')
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
// 页面卸载时重置状态
|
||
practiceStore.resetChat()
|
||
|
||
// 如果是语音模式,断开连接
|
||
if (chatModel.value === 'voice') {
|
||
practiceStore.disconnectVoice()
|
||
}
|
||
})
|
||
|
||
// ==================== 方法 ====================
|
||
|
||
/**
|
||
* 加载场景详情
|
||
*/
|
||
const loadSceneDetail = async (sceneId: number) => {
|
||
try {
|
||
const response: any = await practiceApi.getSceneDetail(sceneId)
|
||
|
||
// http.ts响应拦截器已经返回了response.data,所以response直接是业务数据
|
||
if (response.code === 200 && response.data) {
|
||
practiceStore.setCurrentScene(response.data)
|
||
} else {
|
||
ElMessage.error(response.message || '加载场景失败')
|
||
router.push('/trainee/ai-practice-center')
|
||
}
|
||
} catch (error) {
|
||
console.error('加载场景失败:', error)
|
||
ElMessage.error('加载场景失败')
|
||
router.push('/trainee/ai-practice-center')
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.ai-practice-page {
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
</style>
|