Files
012-kaopeilian/frontend/src/views/trainee/ai-practice.vue
111 998211c483 feat: 初始化考培练系统项目
- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
2026-01-24 19:33:28 +08:00

120 lines
3.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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>