Ver Fonte

课程加入实验室

丸子 há 7 meses atrás
pai
commit
4d5ac8829b

+ 10 - 0
src/api/login/login.js

@@ -17,4 +17,14 @@ export function getTenantIdByName (name){
         url: "bjdxWeb/web/getTenantIdByName?name=" + name,
         method: 'get'
     })
+}
+
+
+// 退出登录
+export function logout (data){
+    return axios({
+        url: "system/auth/logout",
+        method: 'post',
+        data
+    })
 }

+ 24 - 8
src/components/HomePage.vue

@@ -113,6 +113,8 @@ import studyImg from '@/assets/images/study.png'
 
 // 退出登录图标
 import logoutIcon from '@/assets/icon/logout.png'
+// 退出登录
+import { logout } from '@/api/login/login.js'
 import { Message } from '@/utils/message/Message.js'
 
 // 平台标题响应式变量
@@ -125,18 +127,32 @@ const updatePlatformTitle = () => {
 // 获取当前路由对象
 const router = useRouter()
 // 退出登录
-const LogoutClick = () => {
-  // 清空 token 和登录状态
-  localStorage.removeItem('token')
-  localStorage.removeItem('isLoggedIn')
-  localStorage.removeItem('maxCourseSections')
-  router.push({ path: '/login' })
+const LogoutClick = async () => {
+  try {
+    // 调用logout API 退出登录
+     const logoutRes = await logout()
+     console.log('退出登录:', logoutRes);
+    // 清空 token 和登录状态
+    localStorage.removeItem('token')
+    localStorage.removeItem('isLoggedIn')
+    localStorage.removeItem('maxCourseSections')
+    // 跳转到登录页面
+    router.push({ path: '/login' })
+  } catch (error) {
+    console.error('退出登录失败:', error)
+    // API调用失败,也清空本地存储
+    localStorage.removeItem('token')
+    localStorage.removeItem('isLoggedIn')
+    localStorage.removeItem('maxCourseSections')
+    Message().notifyError('退出登录失败,请重试', true)
+    router.push({ path: '/login' })
+  }
 }
 
 // 默认选中 AI 通识课
 const selectedButton = ref('AI通识课')
 
-// 添加图片路径
+// 图片路径
 const indexImages = ref([intelligenceImg, roomImg, testImg, studyImg])
 // AI初体验
 const goToAIGeneralCourse = title => {
@@ -331,7 +347,7 @@ window.updateTenantName = (newName) => {
 .top-sub-box,
 .bottom-sub-box {
   background-repeat: no-repeat;
-  background-size: 100% 100%;
+  background-size: cover;
   background-position: center;
 }
 .right-box-in-box2 {

+ 4 - 4
src/components/ai/image/ImageToImage.vue

@@ -461,14 +461,14 @@ const inProgressTimerFun = () => {
   flex: 1;
   height: 100%;
   display: flex;
-  //background-color: #ece9fd;
+  background-color: #ece9fd;
 }
 
 .content-box {
   flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
+  // margin-top: rpx(10);
+  // margin-bottom: rpx(10);
+  margin: rpx(7);
   border-radius: rpx(15);
   background: rgba($color: #ffffff, $alpha: 0.5);
   overflow-y: auto;

+ 6 - 6
src/components/ai/image/TextToImage.vue

@@ -476,17 +476,17 @@ const inProgressTimerFun = () => {
 }
 //===========================================全局除了删除侧边栏内容唯一改动的地方
 .number-people {
-  //flex: 1;
-  height: 80%;
+  flex: 1;
+  height: 100%;
   display: flex;
-  //background-color: #ece9fd;
+  background-color: #ece9fd;
 }
 
 .content-box {
   flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
+  // margin-top: rpx(10);
+  // margin-bottom: rpx(10);
+  margin: rpx(7);
   border-radius: rpx(15);
   background: rgba($color: #ffffff, $alpha: 0.5);
   overflow-y: auto;

+ 13 - 9
src/components/ai/text/TextToText.vue

@@ -1,7 +1,7 @@
 <template>
   <!-- 原左侧折叠面板和右侧AI问答 -->
   <div class="content-wrapper">
-      <div class="left-group2">
+      <div class="people-image">
         <div class="selected-image">
           <img :src="selectedImage" alt="" />
         </div>
@@ -748,20 +748,24 @@ onUnmounted(() => {
   box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
 }
 // 侧边栏
-.left-group2 {
-  width: rpx(150);
+.people-image {
+  width: rpx(130);
   height: 100%;
   display: flex;
   background-color: #ece9fd;
+  overflow: hidden;
 }
-.left-group2 img {
+.people-image img {
   width: rpx(120);
-  // height: auto;
+  height: auto;
+  object-fit: contain;
 }
 .selected-image {
   flex: 1;
   margin: auto;
-  margin-left: rpx(-60);
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 .title-box {
   height: rpx(50);
@@ -790,9 +794,9 @@ onUnmounted(() => {
 }
 .content-box {
   flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
+  // margin-top: rpx(10);
+  // margin-bottom: rpx(10);
+  margin: rpx(7);
   border-radius: rpx(15);
   background: rgba($color: #ffffff, $alpha: 0.5);
 }

+ 5 - 5
src/components/ai/video/ImageToVideo.vue

@@ -465,17 +465,17 @@ const inProgressTimerFun = () => {
 }
 //===========================================全局除了删除侧边栏内容唯一改动的地方
 .number-people {
-  //flex: 1;
+  flex: 1;
   height: 100%;
   display: flex;
-  //background-color: #ece9fd;
+  background-color: #ece9fd;
 }
 
 .content-box {
   flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
+  // margin-top: rpx(10);
+  // margin-bottom: rpx(10);
+  margin: rpx(7);
   border-radius: rpx(15);
   background: rgba($color: #ffffff, $alpha: 0.5);
   overflow-y: auto;

+ 5 - 5
src/style.css

@@ -1,12 +1,12 @@
 /* 添加字体声明 */
-/* @font-face {
-  font-family: 'SourceHanSansCN-Regular';
-  src: url('../assets/typeface/SourceHanSansCN-Regular_0.otf') format('opentype');
+@font-face {
+  font-family: 'SourceHanSansCN-Normal';
+  src: url('@/assets/typeface/SourceHanSansCN-Medium_0.otf') format('opentype');
   font-weight: normal;
   font-style: normal;
-} */
+}
 :root {
-  font-family: 'SemiBold';
+  font-family: 'SourceHanSansCN-Normal';
   /* font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; */
   line-height: 1.5;
   font-weight: normal;

+ 9 - 5
src/views/AIDevelop.vue

@@ -106,7 +106,8 @@
         </div>
 
         <!-- 视频组件 -->
-        <VideoPlayer v-if="course.courseContentType === 'video2'"
+         <VideoPlayer 
+          v-if="course.courseContentType === 'video'"
           :contentType="course.courseContentType"
           :videoPath="course.courseVideoPath"
           :courseId="course.id || ''"
@@ -124,21 +125,21 @@
         <!-- PPT -->
         <PptView v-if="course.courseContentType === 'ppt'" :pptPath="course.pptPath" ref="pptRef"></PptView>
 
-        <!--文生-->
+        <!--文生-->
         <TextToText class="contentClass" v-if="course.courseContentType === 'aiTextToText'" ref="aiTextToText"></TextToText>
 
         <!--文生图-->
         <TextToImage class="contentClass" v-if="course.courseContentType === 'aiTextToImage'" ref="aiTextToImage"></TextToImage>
 
         <!--图生图-->
-        <ImageToImage class="contentClass" v-if="course.courseContentType === 'aiImageToImage'" ref="aiImagetoImage"></ImageToImage>
+        <ImageToImage class="contentClass" v-if="course.courseContentType === 'aiImagetoImage'" ref="aiImagetoImage"></ImageToImage>
 
         <!--图生视频-->
         <ImageToVideo class="contentClass" v-if="course.courseContentType === 'aiImageToVideo'" ref="aiImageToVideo"></ImageToVideo>
 
         <!-- 视频切换按钮 - 始终显示 -->
         <div class="video-switch">
-          <div class="caret-left" @click="playPreviousVideo">
+          <div class="caret-left" @click="playPreviousVideo"> 
             <el-button type="warning" round>
               <img :src="leftImg" alt="Left" />上一节</el-button
             >
@@ -159,7 +160,7 @@
       :currentQuestion="courseConfig"
       :gradeId="gradeId"
       :typeId="typeId"
-      :courseId="course.id || ''"
+      :courseId="course.id || ''" 
       @closeQuestionDialog="closeQuestionDialog"
       @submitAnswer="handleSubmitAnswer"
     />
@@ -192,6 +193,7 @@ import DialogComponents from '@/components/videopage/DialogComponents.vue'
 import PptView from "@/components/PPT/PptView.vue";
 import ImageView from '@/components/Image/ImageView.vue'
 
+// AI实验室
 import TextToText from "@/components/ai/text/TextToText.vue";
 import TextToImage from "@/components/ai/image/TextToImage.vue";
 import ImageToImage from "@/components/ai/image/ImageToImage.vue";
@@ -1445,5 +1447,7 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
   width: 70%;
   height: 80%;
   margin: 0 auto;
+  border-radius: rpx(15);
+  overflow: hidden;
 }
 </style>

+ 28 - 722
src/views/AIImageToImage.vue

@@ -1,7 +1,7 @@
 <template>
   <!-- 图生图 -->
   <div class="home-container">
-     <!-- 展开收起侧边栏 -->
+    <!-- 展开收起侧边栏 -->
     <div
       class="icon-expand"
       :style="{
@@ -32,7 +32,7 @@
       <div class="img-box">
         <p>
           <img
-              style=" width: fit-content; height: 180px; margin: 10px;"
+              style="width: fit-content; height: 180px; margin: 10px;"
               src="@/assets/images/color.png"
               class="avatar user"
           />
@@ -41,474 +41,30 @@
       </div>
     </div>
 
-    <!-- 右侧AI问答 -->
-    <div class="number-people">
-      <div class="content-box">
-        <!-- AI对话框 -->
-        <div class="chat-dialog">
-          <!-- 对话消息列表 -->
-          <div class="message-list">
-            <div v-if="imageAllList.length > 0">
-                <div  v-for="(item, index) in imageAllList" :key="index">
-              <!-- 用户消息 -->
-              <div class="user-message" v-if="item.type === 'user'">
-                {{ item.content }}
-                <div class="user-image-list" v-if="item.imageUrl">
-                  <el-image
-                    style="width: fit-content; height: 180px; margin: 10px;"
-                    :src="item.imageUrl"
-                    :preview-src-list="[item.imageUrl]"
-                    fit="cover"
-                    show-progress
-                  >
-                    <template
-                      #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
-                    >
-                      <el-icon @click="prev"><Back /></el-icon>
-                      <el-icon @click="next"><Right /></el-icon>
-                      <el-icon @click="setActiveItem(item.imageList.length - 1)">
-                        <DArrowRight />
-                      </el-icon>
-                      <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
-                      <el-icon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })"><ZoomIn /></el-icon>
-                      <el-icon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })"><RefreshRight /></el-icon>
-                      <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
-                      <el-icon @click="reset"><Refresh /></el-icon>
-                      <el-icon @click="download(activeIndex)"><Download /></el-icon>
-                    </template>
-                  </el-image>
-                </div>
-              </div>
-              <!-- AI生成图片对话框 -->
-              <div class="ai-message" v-if="item.type !== 'user'">
-                {{ item.content }}
-                <span v-if="item.loading" class="loading-dots">
-                  <span class="dot"></span>
-                  <span class="dot"></span>
-                  <span class="dot"></span>
-                </span>
-                <div class="image-list" v-if="item.imageList">
-                  <el-image
-                      v-for="(image, index) in item.imageList"
-                      :key="index"
-                      style=" width: fit-content; height: 220px; margin: 10px;"
-                      :src="image"
-                      :preview-src-list="item.imageList"
-                      fit="cover"
-                      show-progress
-                  >
-                    <template
-                        #toolbar="{ actions,  reset, activeIndex}"
-                    >
-                      <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
-                      <el-icon
-                          @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
-                        <ZoomIn />
-                      </el-icon>
-                      <el-icon
-                          @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
-                        <RefreshRight />
-                      </el-icon>
-                      <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
-                      <el-icon @click="reset"><Refresh /></el-icon>
-                      <el-icon @click="download(activeIndex)"><Download /></el-icon>
-                    </template>
-                  </el-image>
-                </div>
-              </div>
-               </div>
-            </div>
-          </div>
-
-          <!-- 输入框和发送按钮 -->
-          <div class="input-section">
-            <input
-              type="text"
-              v-model="inputMessage"
-              placeholder="描述任何画面..."
-              @keyup.enter="sendMessage"
-              style="flex: 1; margin-right: 8px;"
-            />
-            <!-- 参考图 -->
-             <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
-            <!-- 语音输入按钮 -->
-            <button
-                @click="toggleSpeechInput"
-                class="speech-btn"
-                :class="{ 'recording': isRecording }"
-            >
-              <el-icon v-if="!isRecording"><Microphone /></el-icon>
-              <el-icon v-else><Mute /></el-icon>
-              <!-- 显示倒计时(仅录音时显示) -->
-              <span v-if="isRecording" class="countdown-text">{{ countdown }}s</span>
-            </button>
-
-            <!-- 终止按钮 --> 
-            <div
-              v-if="conversationInProgress"
-              @click="stopStream"
-              class="stop-btn"
-              title="终止问答"
-            >
-              <img :src="stopicon" alt="停止" />
-            </div>
-            <button v-if="!conversationInProgress"
-              @click="sendMessage">发送</button>
-          </div>
-        </div>
-      </div>
-    </div>
+    <!-- 图生图 -->
+    <imageToImage />
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted,onUnmounted} from 'vue'
-import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
-import { useRouter, useRoute } from 'vue-router'
-import {
-  Document,
-  Menu as IconMenu,
-  Location,
-  Setting,
-  ArrowLeftBold,
-  Fold,
-  Expand,
-  ChatLineRound,
-  Picture,
-  MagicStick,
-  Tickets,
-  User
-} from '@element-plus/icons-vue'
-
-import { saveRecord } from '@/api/personalized/index.js'
-
-// 导入全局状态
-import { globalState } from '@/utils/globalState.js'
-// 语音图标
-import { Microphone, Mute } from "@element-plus/icons-vue";
-// 终止按钮
-import stopicon from "@/assets/icon/stopicon.png";
-// 消息组件
-import {Message} from "@/utils/message/Message.js";
-
-// 图生图
-import ImageUpload from '@/components/ImageUpload/index.vue';
-
-// 导入getModelIdByType接口
-import { getModelIdByType } from '@/api/teachers.js'
-import { ModelTypeEnum } from '@/api/teachers.js'
-
-// 存储上传的图片
-const uploadedImage = ref('');
-
-const imageUploadRef = ref(null);
-
-// 语音输入响应式变量
-const isRecording = ref(false); // 录音状态
-const recognition = ref(null); // 语音识别实例
-const countdown = ref(0); // 倒计时剩余秒数
-const countdownTimer = ref(null); // 倒计时定
-// 对话状态变量
-const conversationInProgress = ref(false); // 对话是否正在进行中
-const conversationInAbortController = ref(); // 对话进行中 abort 控制器
-
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { ArrowLeftBold } from '@element-plus/icons-vue'
+import LeftPanel from '@/components/LeftPanel.vue'
+import imageToImage from '@/components/ai/image/imageToImage.vue'
 
-// 返回上一页
-const goBack = () => {
-  router.push('/ai-laboratory')
-}
 const router = useRouter()
-const route = useRoute()
-
-// 导入图片
-import question from '@/assets/icon/question.png'
-import painting from '@/assets/icon/painting.png'
-import human from '@/assets/icon/human.png'
-
-import LeftPanel from '@/components/LeftPanel.vue'
 const leftPanelRef = ref(null)
-
-
-// tts 语音
-import { useAudioPlayer } from '@/api/tts/useAudioPlayer';
-const { playAudioChunk } = useAudioPlayer();
-
-// 添加抽屉显示状态
 const drawerVisible = ref(true)
-// 添加切换抽屉显示状态的函数
+
+// 切换抽屉显示状态
 const toggleDrawer = () => {
   drawerVisible.value = !drawerVisible.value
 }
 
-
-  // 年级ID相关
-const gradeId = ref('')
-// 添加消息计数器变量
-const messageCount = ref(0)
-// modelId响应式变量
-const modelId = ref(0)
-// 保存记录
-onMounted(async () => {
-    // 从全局状态初始化年级ID
-  gradeId.value = globalState.initGradeId()
-  try{
-    const res = await saveRecord({
-        brpNjId: gradeId.value,
-        brpType: "aiCount",
-        brpProgress: 1
-      });
-      // 获取modelId
-    const modelRes = await getModelIdByType({ type: ModelTypeEnum.IMAGE_TO_IMAGE, platform: "DouBao" })
-    modelId.value = modelRes.data
-  }catch(error){
-    console.error('保存记录失败:', error);
-  }
-});
-
-// 消息列表和输入内容的响应式变量
-const messages = ref([])
-const inputMessage = ref('')
-
-// 初始化语音识别
-const initSpeechRecognition = () => {
-  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
-  if (!SpeechRecognition) {
-    alert("当前浏览器不支持语音输入功能");
-    return null;
-  }
-
-  const instance = new SpeechRecognition();
-  instance.lang = 'zh-CN';
-  instance.interimResults = false;
-
-  instance.onresult = (event) => {
-    if (event.results?.[0]?.[0]) {
-      inputMessage.value += event.results[0][0].transcript;
-    }
-  };
-
-  // 识别器结束时清除定时器
-  instance.onend = () => {
-    clearInterval(countdownTimer.value);
-    isRecording.value = false;
-    countdown.value = 0;
-  };
-
-  instance.onerror = (event) => {
-    console.error('语音识别错误:', event.error);
-    clearInterval(countdownTimer.value); // 出错时清除定时器
-    isRecording.value = false;
-    Message().error('语音输入失败,请重试!', true)
-    countdown.value = 0;
-  };
-
-  return instance;
-};
-
-// 切换录音状态
-const toggleSpeechInput = () => {
-  // 清除可能存在的旧定时器
-  clearInterval(countdownTimer.value);
-  countdownTimer.value = null;
-
-  if (isRecording.value) {
-    // 手动停止时重置状态
-    countdown.value = 0;
-    recognition.value?.stop();
-    isRecording.value = false;
-  } else {
-    // 初始化倒计时前再次清除定时器(防止快速点击)
-    clearInterval(countdownTimer.value);
-    countdown.value = 10; // 重置为10秒
-
-    recognition.value = initSpeechRecognition();
-    if (!recognition.value) return;
-
-    navigator.mediaDevices.getUserMedia({ audio: true })
-      .then(() => {
-        recognition.value.start();
-        isRecording.value = true;
-
-        // 启动新的倒计时定时器
-        countdownTimer.value = setInterval(() => {
-          countdown.value--;
-          if (countdown.value <= 0) {
-            clearInterval(countdownTimer.value); // 倒计时结束清除
-            recognition.value.stop();
-            isRecording.value = false;
-            countdown.value = 0;
-          }
-        }, 1000);
-      })
-      .catch((err) => {
-        console.error("麦克风权限获取失败:", err);
-        alert("请允许麦克风权限以使用语音输入");
-        // 出错时重置状态
-        isRecording.value = false;
-        countdown.value = 0;
-      });
-  }
-};
-
-// 停止操作函数
-const stopStream = async () => {
-  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
-  if (conversationInAbortController.value) {
-    conversationInAbortController.value.abort();
-  }
-  // 设置为 false
-  conversationInProgress.value = false;
-};
-
-// 发送消息函数,图片数据
-const sendMessage = async() => {
-  if (uploadedImage.value) {
-    // 创建 AbortController 实例,以便中止请求
-    conversationInAbortController.value = new AbortController();
-    // 标记对话进行中
-    conversationInProgress.value = true;
-    // 先保存内容 再置空输入框
-    let content = inputMessage.value;
-    inputMessage.value = '';
-    
-
-    // 创建用户消息对象,包含可能的图片
-    const userMessage = {
-      type: 'user',
-      content: content,
-    };
-    
-    // 如果有上传的图片,添加到用户消息中
-    if (uploadedImage.value) {
-      userMessage.imageUrl = uploadedImage.value;
-      // 清空上传的图片
-      // uploadedImage.value = '';
-    }
-    
-    imageAllList.value.push(userMessage);
-    imageAllList.value.push({
-      type: 'ai',
-      content: "正在为您生成图片,请稍等",
-      loading: true
-    })
-
-    // 递增消息计数器
-    messageCount.value++
-    // 发送saveRecord请求 保存消息次数
-    try{
-      await saveRecord({
-         brpNjId: gradeId.value,
-         brpType: "aiCount",
-         brpProgress: messageCount.value
-       });
-       console.log('保存记录成功,消息次数:', messageCount.value);
-   }catch(error){
-     console.error('保存记录失败:', error);
-     conversationInProgress.value = false;
-   }
-
-    try {
-      CreatePainting({
-        "modelId": modelId.value,
-        "prompt":content,
-        "width":1024,
-        "height":1024,
-        "promptImage":uploadedImage.value
-      }).then(res=>{
-        console.log("生成图片",res)
-        //目前写死调用已生成的图片,全部通了后再改
-        inProgressImageMap.value[res.data] = {id:res.data,status:AiImageStatusEnum.IN_PROGRESS}
-        // inProgressImageMap.value[260] = {id:260,status:AiImageStatusEnum.IN_PROGRESS}
-      }).finally(() => {
-        // 图片生成请求完成后更新状态
-        conversationInProgress.value = false;
-      });
-    } catch (error) {
-      console.error('生成图片失败:', error);
-      conversationInProgress.value = false;
-    }
-  } else {
-    // 如果没有上传图片,显示提示信息
-    Message().warning('请先上传参考图!', true);
-  }
-  // 调用子组件的方法清除预览图
-  imageUploadRef.value?.clearPreview();
-};
-
-
-// 生成图片
-import { ElIcon } from 'element-plus'
-import {
-  Back,
-  DArrowRight,
-  Download,
-  Refresh,
-  RefreshLeft,
-  RefreshRight,
-  Right,
-  ZoomIn,
-  ZoomOut,
-} from '@element-plus/icons-vue'
-
-
-const imageAllList = ref([]) // 对话的消息列表
-const imageList = ref([]) // image 列表
-// 图片轮询相关的参数(正在生成中的)
-const inProgressImageMap = ref({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
-const inProgressTimer = ref() // 生成中的 image 定时器,轮询生成进展
-
-
-/** 轮询生成中的 image 列表 */
-const refreshWatchImages = async () => {
-  const imageIds = Object.keys(inProgressImageMap.value).map(Number)
-  if (imageIds.length === 0) {
-    return
-  }
-  const list = await PaintingGetMys(imageIds)
-  const newWatchImages = {}
-  list.data.forEach((image) => {
-    
-    if (image.status === AiImageStatusEnum.IN_PROGRESS) {
-      newWatchImages[image.id] = image
-    } else {
-      imageAllList.value.pop();
-      console.log('AI生成的图片地址:', image.picUrl);
-      imageAllList.value.push({
-        type: 'ai',
-        content: "已为您生成图片:",
-        imageList: [image.picUrl],
-      })
-    }
-  })
-  inProgressImageMap.value = newWatchImages
-  if (newWatchImages.size === 0) {
-    inProgressTimerFun()
-  }
-}
-
-
-/** 组件挂在的时候 */
-onMounted(async () => {
-  refreshWatchImagesFun()
-})
-
-/** 组件取消挂在的时候 */
-onUnmounted(async () => {
-  inProgressTimerFun()
-})
-
-// 自动刷新 image 列表
-const refreshWatchImagesFun = () => {
-  inProgressTimer.value = setInterval(async () => {
-    await refreshWatchImages()
-  }, 1000 * 3)
-}
-
-// 停止刷新image列表
-const inProgressTimerFun = () => {
-  if (inProgressTimer.value) {
-    clearInterval(inProgressTimer.value)
-  }
+// 返回上一页
+const goBack = () => {
+  router.push('/ai-laboratory')
 }
 </script>
 
@@ -518,25 +74,11 @@ const inProgressTimerFun = () => {
 @function rpx($px) {
   @return math.div($px, 750) * 100vw;
 }
-// 用户图片列表样式
-.user-image-list {
-  display: flex;
-  flex-wrap: wrap;
-  margin-top: rpx(5);
-}
-/* 添加过渡样式 */
-.drawer-slide-enter-active,
-.drawer-slide-leave-active {
-  transition: all 0.3s ease;
-}
-.drawer-slide-enter-from,
-.drawer-slide-leave-to {
-  transform: translateX(-100%);
-  opacity: 0;
-}
+
 :deep(.el-image-viewer__wrapper) {
   z-index: 10000 !important;
 }
+
 .icon-expand {
   width: rpx(8);
   height: rpx(35);
@@ -555,22 +97,12 @@ const inProgressTimerFun = () => {
   align-items: center;
   transition: all 0.3s ease;
 }
+
 .icon-expand .vertical-lines {
   color: #8a78d0;
   font-size: rpx(10);
 }
-.menu-icon {
-  width:rpx(11);
-  height: rpx(11);
-  margin-right: rpx(2);
-}
-// 侧边栏
-.left-group1 {
-  width: rpx(135);
-  height: 100%;
-  background: linear-gradient(to bottom, #001169, #8a78d0);
 
-}
 .home-container {
   position: fixed;
   top: 0;
@@ -580,81 +112,30 @@ const inProgressTimerFun = () => {
   display: flex;
   flex-direction: row;
   gap: rpx(0);
-   background: linear-gradient(
-    to bottom,
-    #e2ddfc,
-    #f1effd
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-}
-
-// 侧边栏
-.left-group {
-  width: rpx(135);
-  height: 100%;
-  background: linear-gradient(to bottom, #001169, #8a78d0);
-}
-.mb-2 {
-  color: black;
-  margin-top: rpx(1);
-}
-.tac ::v-deep(.el-menu) {
-  background-color: transparent;
-  border: none;
-  width: 100%;
-  margin-top: rpx(55);
-  margin-left: rpx(10);
-}
-.el-menu-item {
-  width: rpx(115);
-  height: rpx(25);
-  margin-bottom: rpx(5);
-  border-radius: rpx(6);
-  color: white;
-  font-size: rpx(8);
-}
-.el-menu-item .el-icon svg {
-  font-size: rpx(15);
-  color: white;
-}
-
-.el-menu ::v-deep(.el-menu-item:hover),
-.el-menu ::v-deep(.el-menu-item:focus),
-.el-menu ::v-deep(.el-menu-item:active) {
-  background: linear-gradient(
-    to bottom,
-    #ffefb0,
-    #ffcc00
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
-  color: black;
-  font-size: rpx(8);
-}
-.el-menu-vertical-demo .el-menu-item.is-active {
-  /* 可根据需求修改选中样式 */
-  background: linear-gradient(
-    to bottom,
-    #ffefb0,
-    #ffcc00
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
-  color: black;
-  font-size: rpx(8);
+  background-color: #ece9fd;
+  // background: linear-gradient(
+  //   to bottom,
+  //   #e2ddfc,
+  //   #f1effd
+  // );
 }
 
 // 侧边栏
 .left-group2 {
   width: rpx(150);
   height: 100%;
-  background-color: #ece9fd;
 }
+
 .left-group2 img {
   width: rpx(110);
   height: auto;
   margin-top: rpx(30);
 }
+
 .title-box {
   height: rpx(50);
 }
+
 .box-icon {
   width: 100%;
   height: 100%;
@@ -666,189 +147,14 @@ const inProgressTimerFun = () => {
   font-size: rpx(10); // 设置图标大小,可按需调整
   cursor: pointer; // 添加鼠标指针样式
 }
+
 .box-icon .left-icon {
   margin-left: rpx(10);
   margin-right: rpx(5); // 设置图标和文字之间的间距 ;
 }
 
-.number-people {
-  flex: 1;
-  height: 100%;
-  display: flex;
-  background-color: #ece9fd;
-}
-
-.content-box {
-  flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
-  border-radius: rpx(15);
-  background: rgba($color: #ffffff, $alpha: 0.5);
-  overflow-y: auto;
-}
-
-//左侧展览区图标
 .img-box {
   margin-top: rpx(50);
   color: #a39dce;
 }
-// 对话框
-.chat-dialog {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-}
-.message-list {
-  flex: 1;
-  overflow-y: auto;
-  padding: rpx(15);
-}
-/* 自定义滚动条样式 */
-.message-list::-webkit-scrollbar {
-  width: rpx(2); /* 滚动条宽度 */
-}
-.message-list::-webkit-scrollbar-track {
-  background: #f1effd; /* 滚动条轨道背景色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb {
-  background: #e2ddfc; /* 滚动条滑块颜色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb:hover {
-  background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
-}
-
-.message-list .user-message {
-  background-color: #ffffff;
-  margin-left: auto; // 消息靠右显示
-  margin-right: 0; // 重置右边距
-  max-width: rpx(400);
-  font-size: rpx(8);
-  width: fit-content; // 宽度随文字内容变化
-  border-radius: rpx(5);
-  padding: rpx(5);
-  text-align: left; // 文字左对齐
-}
-
-.message-list .ai-message {
-  background-color: #ffdd55;
-  margin-left: 0; // 消息靠左显示
-  margin-right: auto; // 重置右边距
-  margin-bottom: rpx(10);
-  width: fit-content;
-  max-width: rpx(400);
-  padding: rpx(5);
-  font-size: rpx(8);
-  border-radius: rpx(5);
-  text-align: left; // 文字左对齐
-}
-
-// 加载动画效果
-.loading-dots {
-  display: inline-block;
-  margin-left: rpx(5);
-}
-.loading-dots .dot {
-  display: inline-block;
-  width: rpx(3);
-  height: rpx(3);
-  border-radius: 50%;
-  background-color: #333;
-  margin: 0 rpx(1);
-  animation: loading-dot 1.4s infinite ease-in-out both;
-}
-.loading-dots .dot:nth-child(1) {
-  animation-delay: -0.32s;
-}
-.loading-dots .dot:nth-child(2) {
-  animation-delay: -0.16s;
-}
-@keyframes loading-dot {
-  0%, 80%, 100% {
-    transform: scale(0);
-  }
-  40% {
-    transform: scale(1);
-  }
-}
-
-.image-list {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-
-.content-demo {
-  background-color: #f4f2fa;
-  border-radius: 15px;
-  padding: 30px 10px;
-}
-
-.input-section {
-  display: flex;
-  padding: rpx(10);
-  gap: rpx(5);
-  .speech-btn {
-    padding: rpx(5) rpx(10);
-    background: #fff;
-    border: 1px solid #ffce1b;
-    border-radius: rpx(5);
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    &.recording {
-      background: #ffeeba;
-      border-color: #ffc107;
-
-      .el-icon {
-        color: #dc3545;
-      }
-    }
-    .el-icon {
-      font-size: rpx(8);
-      color: #666;
-    }
-  }
-  // 终止按钮样式
-  .stop-btn {
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    img {
-      width: rpx(20);
-      height: rpx(20);
-    }
-  }
-}
-.input-section input {
-  flex: 1;
-  padding: rpx(5);
-  font-size: rpx(7);
-  border: 1px solid #ccc;
-  border-radius: rpx(5);
-}
-.input-section button {
-  padding: rpx(5) rpx(15);
-  background: linear-gradient(
-    to bottom,
-    #fee78a,
-    #ffce1b
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  color: black;
-  border: none;
-  font-size: rpx(7);
-  border-radius: rpx(5);
-  cursor: pointer;
-    box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
-
-}
-
-.image-upload-section {
-  padding: rpx(10);
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-</style>
+</style>

+ 38 - 724
src/views/AIImageToVideo.vue

@@ -41,457 +41,31 @@
       </div>
     </div>
 
-    <!-- 右侧AI问答 -->
-    <div class="number-people">
-      <div class="content-box">
-        <!-- AI对话框 -->
-        <div class="chat-dialog">
-          <!-- 对话消息列表 -->
-          <div class="message-list">
-            <div v-if="imageAllList.length > 0">
-                <div  v-for="(item, index) in imageAllList" :key="index">
-              <!-- 用户消息 -->
-              <div class="user-message" v-if="item.type === 'user'">
-                {{ item.content }}
-                <div class="user-image-list" v-if="item.imageUrl">
-                  <el-image
-                    style="width: fit-content; height: 180px; margin: 10px;"
-                    :src="item.imageUrl"
-                    :preview-src-list="[item.imageUrl]"
-                    fit="cover"
-                    show-progress
-                  >
-                    <template
-                      #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
-                    >
-                      <el-icon @click="prev"><Back /></el-icon>
-                      <el-icon @click="next"><Right /></el-icon>
-                      <el-icon @click="setActiveItem(item.imageList.length - 1)">
-                        <DArrowRight />
-                      </el-icon>
-                      <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
-                      <el-icon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })"><ZoomIn /></el-icon>
-                      <el-icon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })"><RefreshRight /></el-icon>
-                      <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
-                      <el-icon @click="reset"><Refresh /></el-icon>
-                      <el-icon @click="download(activeIndex)"><Download /></el-icon>
-                    </template>
-                  </el-image>
-                </div>
-              </div>
-                  <!-- AI生成图片对话框 -->
-                 <div class="ai-message" v-if="item.type !== 'user'">
-                {{ item.content }}
-                  <span v-if="item.loading" class="loading-dots">
-                    <span class="dot"></span>
-                    <span class="dot"></span>
-                    <span class="dot"></span>
-                  </span>
-                  <div class="image-list" v-if="item.imageList && item.imageList.length > 0">
-                      <video
-                        v-for="(video, index) in item.imageList"
-                        :key="index"
-                        style="width: 60%; max-width: 600px; height: auto; margin: 10px;border-radius: 2%;"
-                        :src="video"
-                        controls
-                        playsinline
-                      >
-                        您的浏览器不支持视频播放
-                    </video>
-                  </div>
-              </div>
-                
-            </div>
-            </div>
-          </div>
-
-          <!-- 输入框和发送按钮 -->
-          <div class="input-section">
-            <input
-              type="text"
-              v-model="inputMessage"
-              placeholder="描述任何画面..."
-              @keyup.enter="sendMessage"
-              style="flex: 1; margin-right: 8px;"
-            />
-             <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
-            <!-- 语音输入按钮 -->
-            <button
-                @click="toggleSpeechInput"
-                class="speech-btn"
-                :class="{ 'recording': isRecording }"
-            >
-              <el-icon v-if="!isRecording"><Microphone /></el-icon>
-              <el-icon v-else><Mute /></el-icon>
-              <!-- 显示倒计时(仅录音时显示) -->
-              <span v-if="isRecording" class="countdown-text">{{ countdown }}s</span>
-            </button>
-
-            <!-- 终止按钮 -->
-            <div
-              v-if="conversationInProgress"
-              @click="stopStream"
-              class="stop-btn"
-              title="终止问答"
-            >
-              <img :src="stopicon" alt="停止" />
-            </div>
-            <button v-if="!conversationInProgress"
-              @click="sendMessage">发送</button>
-          </div>
-        </div>
-      </div>
-    </div>
+    <!-- 右侧图生视频组件 -->
+    <ImageToVideo />
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted,onUnmounted} from 'vue'
-import {AiImageStatusEnum, CreatePainting, PaintingGetMys,CreateVideo, VideoGetMys} from '@/api/questions.js'
-import { useRouter, useRoute } from 'vue-router'
-import {
-  Document,
-  Menu as IconMenu,
-  Location,
-  Setting,
-  ArrowLeftBold,
-  Fold,
-  Expand,
-  ChatLineRound,
-  Picture,
-  MagicStick,
-  Tickets,
-  User
-} from '@element-plus/icons-vue'
-
-import { saveRecord } from '@/api/personalized/index.js'
-
-// 导入全局状态
-import { globalState } from '@/utils/globalState.js'
-// 语音图标
-import { Microphone, Mute } from "@element-plus/icons-vue";
-// 终止按钮
-import stopicon from "@/assets/icon/stopicon.png";
-// 消息组件
-import {Message} from "@/utils/message/Message.js";
-
-// 上传参考图
-import ImageUpload from '@/components/ImageUpload/index.vue';
-// 导入getModelIdByType接口
-import { getModelIdByType } from '@/api/teachers.js'
-import { ModelTypeEnum } from '@/api/teachers.js'
-
-// 存储上传的图片
-const uploadedImage = ref('');
-const imageUploadRef = ref(null);
-
-// 语音输入响应式变量
-const isRecording = ref(false); // 录音状态
-const recognition = ref(null); // 语音识别实例
-const countdown = ref(0); // 倒计时剩余秒数
-const countdownTimer = ref(null); // 倒计时定
-// 对话状态变量
-const conversationInProgress = ref(false); // 对话是否正在进行中
-const conversationInAbortController = ref(); // 对话进行中 abort 控制器
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { ArrowLeftBold } from '@element-plus/icons-vue'
+import LeftPanel from '@/components/LeftPanel.vue'
+import ImageToVideo from '@/components/ai/video/ImageToVideo.vue'
 
+const router = useRouter()
+const leftPanelRef = ref(null)
+const drawerVisible = ref(true)
 
 // 返回上一页
 const goBack = () => {
   router.push('/ai-laboratory')
 }
-const router = useRouter()
-const route = useRoute()
-
-// 导入图片
-import question from '@/assets/icon/question.png'
-import painting from '@/assets/icon/painting.png'
-import human from '@/assets/icon/human.png'
 
-import LeftPanel from '@/components/LeftPanel.vue'
-const leftPanelRef = ref(null)
-
-
-// tts 语音
-import { useAudioPlayer } from '@/api/tts/useAudioPlayer';
-const { playAudioChunk } = useAudioPlayer();
-
-// 添加抽屉显示状态
-const drawerVisible = ref(true)
 // 添加切换抽屉显示状态的函数
 const toggleDrawer = () => {
   drawerVisible.value = !drawerVisible.value
 }
-
-
-  // 年级ID相关
-const gradeId = ref('')
-// 添加消息计数器变量
-const messageCount = ref(0)
-// modelId响应式变量
-const modelId = ref(0)
-// 保存记录
-onMounted(async () => {
-    // 从全局状态初始化年级ID
-  gradeId.value = globalState.initGradeId()
-  try{
-    const res = await saveRecord({
-        brpNjId: gradeId.value,
-        brpType: "aiCount",
-        brpProgress: 1
-      });
-      // 获取modelId
-    const modelRes = await getModelIdByType({ type: ModelTypeEnum.IMAGE_TO_VIDEO, platform: "DouBao" })
-    modelId.value = modelRes.data
-  }catch(error){
-    console.error('保存记录失败:', error);
-  }
-});
-
-// 消息列表和输入内容的响应式变量
-const messages = ref([])
-const inputMessage = ref('')
-
-// 初始化语音识别
-const initSpeechRecognition = () => {
-  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
-  if (!SpeechRecognition) {
-    alert("当前浏览器不支持语音输入功能");
-    return null;
-  }
-
-  const instance = new SpeechRecognition();
-  instance.lang = 'zh-CN';
-  instance.interimResults = false;
-
-  instance.onresult = (event) => {
-    if (event.results?.[0]?.[0]) {
-      inputMessage.value += event.results[0][0].transcript;
-    }
-  };
-
-  // 识别器结束时清除定时器
-  instance.onend = () => {
-    clearInterval(countdownTimer.value);
-    isRecording.value = false;
-    countdown.value = 0;
-  };
-
-  instance.onerror = (event) => {
-    console.error('语音识别错误:', event.error);
-    clearInterval(countdownTimer.value); // 出错时清除定时器
-    isRecording.value = false;
-    Message().error('语音输入失败,请重试!', true)
-    countdown.value = 0;
-  };
-
-  return instance;
-};
-
-// 切换录音状态
-const toggleSpeechInput = () => {
-  // 清除可能存在的旧定时器
-  clearInterval(countdownTimer.value);
-  countdownTimer.value = null;
-
-  if (isRecording.value) {
-    // 手动停止时重置状态
-    countdown.value = 0;
-    recognition.value?.stop();
-    isRecording.value = false;
-  } else {
-    // 初始化倒计时前再次清除定时器(防止快速点击)
-    clearInterval(countdownTimer.value);
-    countdown.value = 10; // 重置为10秒
-
-    recognition.value = initSpeechRecognition();
-    if (!recognition.value) return;
-
-    navigator.mediaDevices.getUserMedia({ audio: true })
-      .then(() => {
-        recognition.value.start();
-        isRecording.value = true;
-
-        // 启动新的倒计时定时器
-        countdownTimer.value = setInterval(() => {
-          countdown.value--;
-          if (countdown.value <= 0) {
-            clearInterval(countdownTimer.value); // 倒计时结束清除
-            recognition.value.stop();
-            isRecording.value = false;
-            countdown.value = 0;
-          }
-        }, 1000);
-      })
-      .catch((err) => {
-        console.error("麦克风权限获取失败:", err);
-        alert("请允许麦克风权限以使用语音输入");
-        // 出错时重置状态
-        isRecording.value = false;
-        countdown.value = 0;
-      });
-  }
-};
-
-// 停止操作函数
-const stopStream = async () => {
-  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
-  if (conversationInAbortController.value) {
-    conversationInAbortController.value.abort();
-  }
-  // 设置为 false
-  conversationInProgress.value = false;
-};
-
-// 发送消息函数
-const sendMessage = async() => {
-  if (uploadedImage.value) {
-    // 创建 AbortController 实例,以便中止请求
-    conversationInAbortController.value = new AbortController();
-    // 标记对话进行中
-    conversationInProgress.value = true;
-    // messages.value.push(inputMessage.value.trim())
-    // 先保存内容 再置空输入框
-    let content = inputMessage.value;
-    inputMessage.value = ''
-    // 创建用户消息对象,包含可能的图片
-    const userMessage = {
-      type: 'user',
-      content: content,
-    };
-    
-    // 如果有上传的图片,添加到用户消息中
-    if (uploadedImage.value) {
-      userMessage.imageUrl = uploadedImage.value;
-      // 清空上传的图片
-      // uploadedImage.value = '';
-    }
-    // 添加用户消息到消息列表
-    imageAllList.value.push(userMessage);
-    imageAllList.value.push({
-      type: 'ai',
-      content: "正在为您生成视频,请稍等",
-      loading: true
-    })
-
-    // 递增消息计数器
-    messageCount.value++
-    // 发送saveRecord请求 保存消息次数
-     try{
-       await saveRecord({
-          brpNjId: gradeId.value,
-          brpType: "aiCount",
-          brpProgress: messageCount.value
-        });
-        console.log('保存记录成功,消息次数:', messageCount.value);
-    }catch(error){
-      console.error('保存记录失败:', error);
-      conversationInProgress.value = false;
-    }
-
-    try {
-      CreateVideo({
-        "modelId": modelId.value,
-        "prompt":content,
-        "duration":4,
-        "resolution":"1080P",
-        "promptImage":[uploadedImage.value]
-      }).then(res=>{
-        console.log("生成视频",res)
-        //目前写死调用已生成的图片,全部通了后再改
-        inProgressImageMap.value[res.data] = {id:res.data,status:AiImageStatusEnum.IN_PROGRESS}
-        // inProgressImageMap.value[260] = {id:260,status:AiImageStatusEnum.IN_PROGRESS}
-      }).finally(() => {
-        // 图片生成请求完成后更新状态
-        conversationInProgress.value = false;
-      });
-    } catch (error) {
-      console.error('生成视频失败:', error);
-      conversationInProgress.value = false;
-    }
-  } else {
-    // 如果没有上传图片,显示提示信息
-    Message().warning('请先上传参考图!', true);
-  }
-  // 调用子组件的方法清除预览图
-  imageUploadRef.value?.clearPreview();
-};
-
-
-// 生成图片
-import { ElIcon } from 'element-plus'
-import {
-  Back,
-  DArrowRight,
-  Download,
-  Refresh,
-  RefreshLeft,
-  RefreshRight,
-  Right,
-  ZoomIn,
-  ZoomOut,
-} from '@element-plus/icons-vue'
-
-
-const imageAllList = ref([]) // 对话的消息列表
-const imageList = ref([]) // image 列表
-// 图片轮询相关的参数(正在生成中的)
-const inProgressImageMap = ref({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
-const inProgressTimer = ref() // 生成中的 image 定时器,轮询生成进展
-
-
-/** 轮询生成中的 image 列表 */
-const refreshWatchImages = async () => {
-  const imageIds = Object.keys(inProgressImageMap.value).map(Number)
-  if (imageIds.length === 0) {
-    return
-  }
-  const list = await VideoGetMys(imageIds)
-  
-      console.log('AI生成的视频地址2222222222:', list,imageIds);
-  const newWatchImages = {}
-  list?.data.forEach((image) => {
-    if (image.status === AiImageStatusEnum.IN_PROGRESS) {
-      newWatchImages[image.id] = image
-    } else {
-      imageAllList.value.pop();
-      console.log('AI生成的视频地址:', image.videoUrl);
-      imageAllList.value.push({
-        type: 'ai',
-        content: "已为您生成视频:",
-        imageList: [image.videoUrl],
-      })
-    }
-  })
-  inProgressImageMap.value = newWatchImages
-  if (newWatchImages.size === 0) {
-    inProgressTimerFun()
-  }
-}
-
-
-/** 组件挂在的时候 */
-onMounted(async () => {
-  refreshWatchImagesFun()
-})
-
-/** 组件取消挂在的时候 */
-onUnmounted(async () => {
-  inProgressTimerFun()
-})
-
-// 自动刷新 image 列表
-const refreshWatchImagesFun = () => {
-  inProgressTimer.value = setInterval(async () => {
-    await refreshWatchImages()
-  }, 1000 * 3)
-}
-
-// 停止刷新image列表
-const inProgressTimerFun = () => {
-  if (inProgressTimer.value) {
-    clearInterval(inProgressTimer.value)
-  }
-}
 </script>
 
 <style scoped lang="scss">
@@ -500,25 +74,23 @@ const inProgressTimerFun = () => {
 @function rpx($px) {
   @return math.div($px, 750) * 100vw;
 }
-// 用户图片列表样式
-.user-image-list {
+
+.home-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
   display: flex;
-  flex-wrap: wrap;
-  margin-top: rpx(5);
-}
-/* 添加过渡样式 */
-.drawer-slide-enter-active,
-.drawer-slide-leave-active {
-  transition: all 0.3s ease;
-}
-.drawer-slide-enter-from,
-.drawer-slide-leave-to {
-  transform: translateX(-100%);
-  opacity: 0;
-}
-:deep(.el-image-viewer__wrapper) {
-  z-index: 10000 !important;
+  flex-direction: row;
+  gap: rpx(0);
+  background: linear-gradient(
+    to bottom,
+    #e2ddfc,
+    #f1effd
+  );
 }
+
 .icon-expand {
   width: rpx(8);
   height: rpx(35);
@@ -530,312 +102,54 @@ const inProgressTimerFun = () => {
   left: 18%;
   transform: translateY(-50%);
   background-color: #44449c;
-  cursor: pointer; // 添加鼠标指针样式
+  cursor: pointer; 
   clip-path: polygon(0 0, 100% 15%, 100% 85%, 0 100%);
   display: flex;
   justify-content: center;
   align-items: center;
   transition: all 0.3s ease;
 }
+
 .icon-expand .vertical-lines {
   color: #8a78d0;
   font-size: rpx(10);
 }
-.menu-icon {
-  width:rpx(11);
-  height: rpx(11);
-  margin-right: rpx(2);
-}
-// 侧边栏
-.left-group1 {
-  width: rpx(135);
-  height: 100%;
-  background: linear-gradient(to bottom, #001169, #8a78d0);
-
-}
-.home-container {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  flex-direction: row;
-  gap: rpx(0);
-   background: linear-gradient(
-    to bottom,
-    #e2ddfc,
-    #f1effd
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-}
-
-// 侧边栏
-.left-group {
-  width: rpx(135);
-  height: 100%;
-  background: linear-gradient(to bottom, #001169, #8a78d0);
-}
-.mb-2 {
-  color: black;
-  margin-top: rpx(1);
-}
-.tac ::v-deep(.el-menu) {
-  background-color: transparent;
-  border: none;
-  width: 100%;
-  margin-top: rpx(55);
-  margin-left: rpx(10);
-}
-.el-menu-item {
-  width: rpx(115);
-  height: rpx(25);
-  margin-bottom: rpx(5);
-  border-radius: rpx(6);
-  color: white;
-  font-size: rpx(8);
-}
-.el-menu-item .el-icon svg {
-  font-size: rpx(15);
-  color: white;
-}
-
-.el-menu ::v-deep(.el-menu-item:hover),
-.el-menu ::v-deep(.el-menu-item:focus),
-.el-menu ::v-deep(.el-menu-item:active) {
-  background: linear-gradient(
-    to bottom,
-    #ffefb0,
-    #ffcc00
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
-  color: black;
-  font-size: rpx(8);
-}
-.el-menu-vertical-demo .el-menu-item.is-active {
-  /* 可根据需求修改选中样式 */
-  background: linear-gradient(
-    to bottom,
-    #ffefb0,
-    #ffcc00
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
-  color: black;
-  font-size: rpx(8);
-}
 
-// 侧边栏
 .left-group2 {
   width: rpx(150);
   height: 100%;
   background-color: #ece9fd;
 }
+
 .left-group2 img {
   width: rpx(110);
   height: auto;
   margin-top: rpx(30);
 }
+
 .title-box {
   height: rpx(50);
 }
+
 .box-icon {
   width: 100%;
   height: 100%;
   flex: 1;
-  display: flex; // 添加 flex 布局
-  align-items: center; // 垂直居中
-  color: black; // 设置图标颜色为白色
+  display: flex;
+  align-items: center;
+  color: black;
   padding-left: rpx(15);
-  font-size: rpx(10); // 设置图标大小,可按需调整
-  cursor: pointer; // 添加鼠标指针样式
+  font-size: rpx(10);
+  cursor: pointer;
 }
+
 .box-icon .left-icon {
   margin-left: rpx(10);
-  margin-right: rpx(5); // 设置图标和文字之间的间距 ;
-}
-
-.number-people {
-  flex: 1;
-  height: 100%;
-  display: flex;
-  background-color: #ece9fd;
+  margin-right: rpx(5);
 }
 
-.content-box {
-  flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
-  border-radius: rpx(15);
-  background: rgba($color: #ffffff, $alpha: 0.5);
-  overflow-y: auto;
-}
-
-//左侧展览区图标
 .img-box {
   margin-top: rpx(50);
   color: #a39dce;
 }
-// 对话框
-.chat-dialog {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-}
-.message-list {
-  flex: 1;
-  overflow-y: auto;
-  padding: rpx(15);
-}
-/* 自定义滚动条样式 */
-.message-list::-webkit-scrollbar {
-  width: rpx(2); /* 滚动条宽度 */
-}
-.message-list::-webkit-scrollbar-track {
-  background: #f1effd; /* 滚动条轨道背景色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb {
-  background: #e2ddfc; /* 滚动条滑块颜色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb:hover {
-  background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
-}
-
-.message-list .user-message {
-  background-color: #ffffff;
-  margin-left: auto; // 消息靠右显示
-  margin-right: 0; // 重置右边距
-  max-width: rpx(400);
-  font-size: rpx(8);
-  width: fit-content; // 宽度随文字内容变化
-  border-radius: rpx(5);
-  padding: rpx(5);
-  text-align: left; // 文字左对齐
-}
-
-.message-list .ai-message {
-  background-color: #ffdd55;
-  margin-left: 0; // 消息靠左显示
-  margin-right: auto; // 重置右边距
-  margin-bottom: rpx(10);
-  width: fit-content;
-  max-width: rpx(400);
-  padding: rpx(5);
-  font-size: rpx(8);
-  border-radius: rpx(5);
-  text-align: left; // 文字左对齐
-}
-
-// 加载动画效果
-.loading-dots {
-  display: inline-block;
-  margin-left: rpx(5);
-}
-
-.loading-dots .dot {
-  display: inline-block;
-  width: rpx(3);
-  height: rpx(3);
-  border-radius: 50%;
-  background-color: #333;
-  margin: 0 rpx(1);
-  animation: loading-dot 1.4s infinite ease-in-out both;
-}
-
-.loading-dots .dot:nth-child(1) {
-  animation-delay: -0.32s;
-}
-
-.loading-dots .dot:nth-child(2) {
-  animation-delay: -0.16s;
-}
-
-@keyframes loading-dot {
-  0%, 80%, 100% {
-    transform: scale(0);
-  }
-  40% {
-    transform: scale(1);
-  }
-}
-
-
-.image-list {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-
-.content-demo {
-  background-color: #f4f2fa;
-  border-radius: 15px;
-  padding: 30px 10px;
-}
-
-.input-section {
-  display: flex;
-  padding: rpx(10);
-  gap: rpx(5);
-  .speech-btn {
-    padding: rpx(5) rpx(10);
-    background: #fff;
-    border: 1px solid #ffce1b;
-    border-radius: rpx(5);
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    &.recording {
-      background: #ffeeba;
-      border-color: #ffc107;
-
-      .el-icon {
-        color: #dc3545;
-      }
-    }
-    .el-icon {
-      font-size: rpx(8);
-      color: #666;
-    }
-  }
-  // 终止按钮样式
-  .stop-btn {
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    img {
-      width: rpx(20);
-      height: rpx(20);
-    }
-  }
-}
-.input-section input {
-  flex: 1;
-  padding: rpx(5);
-  font-size: rpx(7);
-  border: 1px solid #ccc;
-  border-radius: rpx(5);
-}
-.input-section button {
-  padding: rpx(5) rpx(15);
-  background: linear-gradient(
-    to bottom,
-    #fee78a,
-    #ffce1b
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  color: black;
-  border: none;
-  font-size: rpx(7);
-  border-radius: rpx(5);
-  cursor: pointer;
-    box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
-
-}
-
-.image-upload-section {
-  padding: rpx(10);
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-</style>
+</style>

+ 10 - 623
src/views/AIPainting.vue

@@ -41,214 +41,25 @@
       </div>
     </div>
 
-    <!-- 右侧AI问答 -->
-    <div class="number-people">
-      <div class="content-box">
-        <!-- AI对话框 -->
-        <div class="chat-dialog">
-          <!-- 对话消息列表 -->
-          <div class="message-list">
-            <div v-if="imageAllList.length > 0">
-              <div v-for="(item, index) in imageAllList" :key="index">
-              <!-- 用户消息 -->
-              <div class="user-message" v-if="item.type === 'user'">
-                {{ item.content }}
-              </div>
-              <!-- AI生成图片对话框 -->
-              <div class="ai-message" v-if="item.type !== 'user'">
-                {{ item.content }}
-                <span v-if="item.loading" class="loading-dots">
-                  <span class="dot"></span>
-                  <span class="dot"></span>
-                  <span class="dot"></span>
-                </span>
-                <div class="image-list" v-if="item.imageList">
-                  <el-image
-                      v-for="(image, index) in item.imageList"
-                      :key="index"
-                      style=" width: fit-content; height: 220px; margin: 10px;"
-                      :src="image"
-                      :preview-src-list="item.imageList"
-                      fit="cover"
-                      show-progress
-                  >
-                    <template
-                        #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
-                    >
-                      <el-icon @click="prev"><Back /></el-icon>
-                      <el-icon @click="next"><Right /></el-icon>
-                      <el-icon @click="setActiveItem(item.imageList.length - 1)">
-                        <DArrowRight />
-                      </el-icon>
-                      <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
-                      <el-icon
-                          @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
-                        <ZoomIn />
-                      </el-icon>
-                      <el-icon
-                          @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
-                        <RefreshRight />
-                      </el-icon>
-                      <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
-                      <el-icon @click="reset"><Refresh /></el-icon>
-                      <el-icon @click="download(activeIndex)"><Download /></el-icon>
-                    </template>
-                  </el-image>
-                </div>
-              </div>
-            </div>
-            </div>
-            
-
-            <div v-else class="content-demo">
-              <h3>请参考示例:</h3>
-              <!-- 用户消息 -->
-              <div class="user-message">
-                生成粉色的会飞的猪
-              </div>
-              <!-- AI生成图片对话框 -->
-              <div class="ai-message" >
-                为您生成图片:
-                <div class="image-list" v-if="demoImageList">
-                  <el-image
-                      v-for="(image, index) in demoImageList"
-                      :key="index"
-                      style=" width: fit-content; height: 180px; margin: 10px;"
-                      :src="image"
-                      :preview-src-list="demoImageList"
-                      fit="cover"
-                      show-progress
-                  >
-                    <template
-                        #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
-                    >
-                      <el-icon @click="prev"><Back /></el-icon>
-                      <el-icon @click="next"><Right /></el-icon>
-                      <el-icon @click="setActiveItem(demoImageList.length - 1)">
-                        <DArrowRight />
-                      </el-icon>
-                      <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
-                      <el-icon
-                          @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
-                        <ZoomIn />
-                      </el-icon>
-                      <el-icon
-                          @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
-                        <RefreshRight />
-                      </el-icon>
-                      <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
-                      <el-icon @click="reset"><Refresh /></el-icon>
-                      <el-icon @click="download(activeIndex)"><Download /></el-icon>
-                    </template>
-                  </el-image>
-                </div>
-              </div>
-            </div>
-          </div>
-          <!-- 输入框和发送按钮 -->
-          <div class="input-section">
-            <input
-              type="text"
-              v-model="inputMessage"
-              placeholder="描述任何画面..."
-              @keyup.enter="sendMessage"
-            />
-            <!-- 语音输入按钮 -->
-            <button
-                @click="toggleSpeechInput"
-                class="speech-btn"
-                :class="{ 'recording': isRecording }"
-            >
-              <el-icon v-if="!isRecording"><Microphone /></el-icon>
-              <el-icon v-else><Mute /></el-icon>
-              <!-- 显示倒计时(仅录音时显示) -->
-              <span v-if="isRecording" class="countdown-text">{{ countdown }}s</span>
-            </button>
-            <!-- 终止按钮 -->
-            <div
-              v-if="conversationInProgress"
-              @click="stopStream"
-              class="stop-btn"
-              title="终止问答"
-            >
-              <img :src="stopicon" alt="停止" />
-            </div>
-            <button v-if="!conversationInProgress"
-              @click="sendMessage">发送</button>
-          </div>
-        </div>
-      </div>
-    </div>
+    <!-- 右侧TextToImage组件 -->
+    <TextToImage />
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted,onUnmounted} from 'vue'
-import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
-import { useRouter, useRoute } from 'vue-router'
-import demo1 from '@/assets/images/ai-demo/ai-image-demo1.png'
-import demo2 from '@/assets/images/ai-demo/ai-image-demo2.png'
-import demo3 from '@/assets/images/ai-demo/ai-image-demo3.png'
-import demo4 from '@/assets/images/ai-demo/ai-image-demo4.png'
-import {
-  Document,
-  Menu as IconMenu,
-  Location,
-  Setting,
-  ArrowLeftBold,
-  Fold,
-  Expand,
-  ChatLineRound,
-  Picture,
-  MagicStick,
-  Tickets,
-  User
-} from '@element-plus/icons-vue'
-
-import { saveRecord } from '@/api/personalized/index.js'
-
-// 导入全局状态
-import { globalState } from '@/utils/globalState.js'
-// 语音图标
-import { Microphone, Mute } from "@element-plus/icons-vue";
-// 终止按钮
-import stopicon from "@/assets/icon/stopicon.png";
-// 消息组件
-import {Message} from "@/utils/message/Message.js";
-
-// 导入getModelIdByType接口
-import { getModelIdByType } from '@/api/teachers.js'
-import { ModelTypeEnum } from '@/api/teachers.js'
-
-// 语音输入响应式变量
-const isRecording = ref(false); // 录音状态
-const recognition = ref(null); // 语音识别实例
-const countdown = ref(0); // 倒计时剩余秒数
-const countdownTimer = ref(null); // 倒计时定
-// 对话状态变量
-const conversationInProgress = ref(false); // 对话是否正在进行中
-const conversationInAbortController = ref(); // 对话进行中 abort 控制器
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { ArrowLeftBold } from '@element-plus/icons-vue'
+import LeftPanel from '@/components/LeftPanel.vue'
+import TextToImage from '@/components/ai/image/TextToImage.vue'
 
+const router = useRouter()
+const leftPanelRef = ref(null)
 
 // 返回上一页
 const goBack = () => {
   router.push('/ai-laboratory')
 }
-const router = useRouter()
-const route = useRoute()
-
-// 导入图片
-import question from '@/assets/icon/question.png'
-import painting from '@/assets/icon/painting.png'
-import human from '@/assets/icon/human.png'
-
-import LeftPanel from '@/components/LeftPanel.vue'
-const leftPanelRef = ref(null)
-
-
-// tts 语音
-import { useAudioPlayer } from '@/api/tts/useAudioPlayer';
-const { playAudioChunk } = useAudioPlayer();
 
 // 添加抽屉显示状态
 const drawerVisible = ref(true)
@@ -256,259 +67,6 @@ const drawerVisible = ref(true)
 const toggleDrawer = () => {
   drawerVisible.value = !drawerVisible.value
 }
-
-const demoImageList = [demo1, demo2, demo3, demo4]
-
-  // 年级ID相关
-const gradeId = ref('')
-// 添加消息计数器变量
-const messageCount = ref(0)
-// modelId响应式变量
-const modelId = ref(0)
-
-// 保存记录
-onMounted(async () => {
-    // 从全局状态初始化年级ID
-  gradeId.value = globalState.initGradeId()
-  try{
-    const res = await saveRecord({
-        brpNjId: gradeId.value,
-        brpType: "aiCount",
-        brpProgress: 1
-      });
-      // 获取modelId
-    const modelRes = await getModelIdByType({ type: ModelTypeEnum.TEXT_TO_IMAGE, platform: "DouBao" })
-    modelId.value = modelRes.data
-    
-  }catch(error){
-    console.error('保存记录失败:', error);
-  }
-});
-
-// 消息列表和输入内容的响应式变量
-const messages = ref([])
-const inputMessage = ref('')
-
-// 初始化语音识别
-const initSpeechRecognition = () => {
-  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
-  if (!SpeechRecognition) {
-    alert("当前浏览器不支持语音输入功能");
-    return null;
-  }
-
-  const instance = new SpeechRecognition();
-  instance.lang = 'zh-CN';
-  instance.interimResults = false;
-
-  instance.onresult = (event) => {
-    if (event.results?.[0]?.[0]) {
-      inputMessage.value += event.results[0][0].transcript;
-    }
-  };
-
-  // 识别器结束时清除定时器
-  instance.onend = () => {
-    clearInterval(countdownTimer.value);
-    isRecording.value = false;
-    countdown.value = 0;
-  };
-
-  instance.onerror = (event) => {
-    console.error('语音识别错误:', event.error);
-    clearInterval(countdownTimer.value); // 出错时清除定时器
-    isRecording.value = false;
-    Message().error('语音输入失败,请重试!', true)
-    countdown.value = 0;
-  };
-
-  return instance;
-};
-
-// 切换录音状态
-const toggleSpeechInput = () => {
-  // 清除可能存在的旧定时器
-  clearInterval(countdownTimer.value);
-  countdownTimer.value = null;
-
-  if (isRecording.value) {
-    // 手动停止时重置状态
-    countdown.value = 0;
-    recognition.value?.stop();
-    isRecording.value = false;
-  } else {
-    // 初始化倒计时前再次清除定时器(防止快速点击)
-    clearInterval(countdownTimer.value);
-    countdown.value = 10; // 重置为10秒
-
-    recognition.value = initSpeechRecognition();
-    if (!recognition.value) return;
-
-    navigator.mediaDevices.getUserMedia({ audio: true })
-      .then(() => {
-        recognition.value.start();
-        isRecording.value = true;
-
-        // 启动新的倒计时定时器
-        countdownTimer.value = setInterval(() => {
-          countdown.value--;
-          if (countdown.value <= 0) {
-            clearInterval(countdownTimer.value); // 倒计时结束清除
-            recognition.value.stop();
-            isRecording.value = false;
-            countdown.value = 0;
-          }
-        }, 1000);
-      })
-      .catch((err) => {
-        console.error("麦克风权限获取失败:", err);
-        alert("请允许麦克风权限以使用语音输入");
-        // 出错时重置状态
-        isRecording.value = false;
-        countdown.value = 0;
-      });
-  }
-};
-
-// 停止操作函数
-const stopStream = async () => {
-  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
-  if (conversationInAbortController.value) {
-    conversationInAbortController.value.abort();
-  }
-  // 设置为 false
-  conversationInProgress.value = false;
-};
-
-// 发送消息函数
-const sendMessage = async() => {
-  if (inputMessage.value.trim()) {
-    // 创建 AbortController 实例,以便中止请求
-    conversationInAbortController.value = new AbortController();
-    // 标记对话进行中
-    conversationInProgress.value = true;
-    // messages.value.push(inputMessage.value.trim())
-    // 先保存内容 再置空输入框
-    let content = inputMessage.value;
-    inputMessage.value = ''
-    imageAllList.value.push({
-      type: 'user',
-      content: content,
-    })
-    imageAllList.value.push({
-      type: 'ai',
-      content: "正在为您生成图片,请稍等",
-      loading: true
-    })
-
-    // 递增消息计数器
-    messageCount.value++
-    // 发送saveRecord请求 保存消息次数
-     try{
-       await saveRecord({
-          brpNjId: gradeId.value,
-          brpType: "aiCount",
-          brpProgress: messageCount.value
-        });
-        console.log('保存记录成功,消息次数:', messageCount.value);
-    }catch(error){
-      console.error('保存记录失败:', error);
-      conversationInProgress.value = false;
-    }
-
-    try {
-      CreatePainting({
-        "modelId": modelId.value,
-        "prompt":content,
-        "width":1024,
-        "height":1024
-      }).then(res=>{
-        console.log("生成图片",res)
-        //目前写死调用已生成的图片,全部通了后再改
-        inProgressImageMap.value[res.data] = {id:res.data,status:AiImageStatusEnum.IN_PROGRESS}
-        // inProgressImageMap.value[260] = {id:260,status:AiImageStatusEnum.IN_PROGRESS}
-      }).finally(() => {
-        // 图片生成请求完成后更新状态
-        conversationInProgress.value = false;
-      });
-    } catch (error) {
-      console.error('生成图片失败:', error);
-      conversationInProgress.value = false;
-    }
-  }
-};
-// 生成图片
-import { ElIcon } from 'element-plus'
-import {
-  Back,
-  DArrowRight,
-  Download,
-  Refresh,
-  RefreshLeft,
-  RefreshRight,
-  Right,
-  ZoomIn,
-  ZoomOut,
-} from '@element-plus/icons-vue'
-
-
-const imageAllList = ref([]) // 对话的消息列表
-const imageList = ref([]) // image 列表
-// 图片轮询相关的参数(正在生成中的)
-const inProgressImageMap = ref({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
-const inProgressTimer = ref() // 生成中的 image 定时器,轮询生成进展
-
-
-/** 轮询生成中的 image 列表 */
-const refreshWatchImages = async () => {
-  const imageIds = Object.keys(inProgressImageMap.value).map(Number)
-  if (imageIds.length === 0) {
-    return
-  }
-  const list = await PaintingGetMys(imageIds)
-  const newWatchImages = {}
-  list.data.forEach((image) => {
-    if (image.status === AiImageStatusEnum.IN_PROGRESS) {
-      newWatchImages[image.id] = image
-    } else {
-      imageAllList.value.pop();
-      imageAllList.value.push({
-        type: 'ai',
-        content: "已为您生成图片:",
-        imageList: [image.picUrl],
-      })
-    }
-  })
-  inProgressImageMap.value = newWatchImages
-  if (newWatchImages.size === 0) {
-    inProgressTimerFun()
-  }
-}
-
-
-/** 组件挂在的时候 */
-onMounted(async () => {
-  refreshWatchImagesFun()
-})
-
-/** 组件取消挂在的时候 */
-onUnmounted(async () => {
-  inProgressTimerFun()
-})
-
-// 自动刷新 image 列表
-const refreshWatchImagesFun = () => {
-  inProgressTimer.value = setInterval(async () => {
-    await refreshWatchImages()
-  }, 1000 * 3)
-}
-
-// 停止刷新image列表
-const inProgressTimerFun = () => {
-  if (inProgressTimer.value) {
-    clearInterval(inProgressTimer.value)
-  }
-}
 </script>
 
 <style scoped lang="scss">
@@ -562,7 +120,6 @@ const inProgressTimerFun = () => {
   width: rpx(135);
   height: 100%;
   background: linear-gradient(to bottom, #001169, #8a78d0);
-
 }
 .home-container {
   position: fixed;
@@ -664,179 +221,9 @@ const inProgressTimerFun = () => {
   margin-right: rpx(5); // 设置图标和文字之间的间距 ;
 }
 
-.number-people {
-  flex: 1;
-  height: 100%;
-  display: flex;
-  background-color: #ece9fd;
-}
-
-.content-box {
-  flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
-  border-radius: rpx(15);
-  background: rgba($color: #ffffff, $alpha: 0.5);
-  overflow-y: auto;
-
-}
-
 //左侧展览区图标
 .img-box {
   margin-top: rpx(50);
   color: #a39dce;
 }
-// 对话框
-.chat-dialog {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-}
-.message-list {
-  flex: 1;
-  overflow-y: auto;
-  padding: rpx(15);
-}
-/* 自定义滚动条样式 */
-.message-list::-webkit-scrollbar {
-  width: rpx(2); /* 滚动条宽度 */
-}
-.message-list::-webkit-scrollbar-track {
-  background: #f1effd; /* 滚动条轨道背景色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb {
-  background: #e2ddfc; /* 滚动条滑块颜色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb:hover {
-  background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
-}
-
-.message-list .user-message {
-  background-color: #ffffff;
-  margin-left: auto; // 消息靠右显示
-  margin-right: 0; // 重置右边距
-  max-width: rpx(400);
-  font-size: rpx(8);
-  width: fit-content; // 宽度随文字内容变化
-  border-radius: rpx(5);
-  padding: rpx(5);
-  text-align: left; // 文字左对齐
-}
-
-.message-list .ai-message {
-  background-color: #ffdd55;
-  margin-left: 0; // 消息靠左显示
-  margin-right: auto; // 重置右边距
-  margin-bottom: rpx(10);
-  width: fit-content;
-  max-width: rpx(400);
-  padding: rpx(5);
-  font-size: rpx(8);
-  border-radius: rpx(5);
-  text-align: left; // 文字左对齐
-}
-
-// 加载动画效果
-.loading-dots {
-  display: inline-block;
-  margin-left: rpx(5);
-}
-.loading-dots .dot {
-  display: inline-block;
-  width: rpx(3);
-  height: rpx(3);
-  border-radius: 50%;
-  background-color: #333;
-  margin: 0 rpx(1);
-  animation: loading-dot 1.4s infinite ease-in-out both;
-}
-.loading-dots .dot:nth-child(1) {
-  animation-delay: -0.32s;
-}
-.loading-dots .dot:nth-child(2) {
-  animation-delay: -0.16s;
-}
-@keyframes loading-dot {
-  0%, 80%, 100% {
-    transform: scale(0);
-  }
-  40% {
-    transform: scale(1);
-  }
-}
-
-
-.image-list {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-
-.content-demo {
-  background-color: #f4f2fa;
-  border-radius: 15px;
-  padding: 30px 10px;
-}
-
-.input-section {
-  display: flex;
-  padding: rpx(10);
-  gap: rpx(5);
-  .speech-btn {
-    padding: rpx(5) rpx(10);
-    background: #fff;
-    border: 1px solid #ffce1b;
-    border-radius: rpx(5);
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    &.recording {
-      background: #ffeeba;
-      border-color: #ffc107;
-
-      .el-icon {
-        color: #dc3545;
-      }
-    }
-    .el-icon {
-      font-size: rpx(8);
-      color: #666;
-    }
-  }
-  // 终止按钮样式
-  .stop-btn {
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    img {
-      width: rpx(20);
-      height: rpx(20);
-    }
-  }
-}
-.input-section input {
-  flex: 1;
-  padding: rpx(5);
-  font-size: rpx(7);
-  border: 1px solid #ccc;
-  border-radius: rpx(5);
-}
-.input-section button {
-  padding: rpx(5) rpx(15);
-  background: linear-gradient(
-    to bottom,
-    #fee78a,
-    #ffce1b
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  color: black;
-  border: none;
-  font-size: rpx(7);
-  border-radius: rpx(5);
-  cursor: pointer;
-    box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
-
-}
-</style>
+</style>

+ 29 - 830
src/views/AIQuestions.vue

@@ -22,7 +22,7 @@
     <!-- 左侧折叠面板 -->
     <LeftPanel ref="leftPanelRef" v-if="drawerVisible" />
 
-    <!-- 原左侧折叠面板和右侧AI问答 -->
+    <!-- 原左侧折叠面板和右侧AI问答 --> 
     <div class="content-wrapper">
       <div class="left-group2">
         <div class="title-box">
@@ -31,148 +31,40 @@
             {{ personName }}
           </div>
         </div>
-        <div class="selected-image">
-          <img :src="selectedImage" alt="" />
-        </div>
-      </div>
-      <!-- 右侧AI问答 -->
-      <div class="number-people">
-        <div class="content-box">
-          <!-- AI对话框 -->
-          <div class="chat-dialog">
-            <!-- 对话消息列表 -->
-            <div class="message-list" ref="messageListRef" @scroll="handleScroll">
-              <div v-for="(item, index) in messageList" :key="index">
-                <!-- AI消息 -->
-                <div class="ai-message" v-if="item.type !== 'user'">
-                  <MarkdownView class="left-text" :content="item.content" />
-                  <!-- {{item.content}} -->
-                </div>
-
-                <!-- 用户消息 -->
-                <div class="user-message" v-if="item.type === 'user'">
-                  {{ item.content }}
-                </div>
-              </div>
-            </div>
-            <!-- 默认消息 -->
-            <DefaultMessage
-              v-if="showDefaultMessages"
-              @select-message="handleDefaultMessageSelect"
-              :category="route.query.category"
-              :quest-tip="route.query.default"
-            />
-            <!-- 输入框和发送按钮 -->
-            <div class="input-section">
-              <input
-                type="text"
-                v-model="prompt"
-                placeholder="问我任何问题..."
-                @keyup.enter="handleSendByKeydown"
-              />
-              <!-- 语音输入按钮 -->
-              <button
-                  @click="toggleSpeechInput"
-                  class="speech-btn"
-                  :class="{ 'recording': isRecording }"
-              >
-                <el-icon v-if="!isRecording"><Microphone /></el-icon>
-                <el-icon v-else><Mute /></el-icon>
-                <!-- 显示倒计时(仅录音时显示) -->
-                <span v-if="isRecording" class="countdown-text">{{ countdown }}s</span>
-              </button>
-
-              <!-- 终止问答按钮 -->
-              <div
-                v-if="conversationInProgress"
-                @click="stopStream"
-                class="stop-btn"
-                title="终止问答"
-              >
-                <img :src="stopicon" alt="停止" />
-              </div>
-              <button
-                v-if="!conversationInProgress"
-                @click="handleSendByButton"
-              >
-                发送
-              </button>
-            </div>
-          </div>
-        </div>
       </div>
+      
+      <TextToText
+        :personId="personId"
+        :personName="personName"
+        :personImage="personImage"
+        :personIntroduce="personIntroduce"
+      />
     </div>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted,onUnmounted, computed, watch, nextTick } from "vue";
-import { CreateDialogue, sendChatMessageStream } from "@/api/questions.js";
+import { ref, onMounted, watch } from "vue";
 import { useRouter, useRoute } from "vue-router";
-import { saveRecord } from "@/api/personalized/index.js";
-// 导入全局状态
-import { globalState } from "@/utils/globalState.js";
-
-// 终止按钮
-import stopicon from "@/assets/icon/stopicon.png";
-
-import MarkdownView from "@/components/MarkdownView/index.vue";
-import {
-  Document,
-  Menu as IconMenu,
-  Location,
-  Setting,
-  ArrowLeftBold,
-  MagicStick,
-  ChatLineRound,
-  Fold,
-  Expand,
-  Picture,
-  Tickets,
-  User,
-  Search, // 使用Search图标作为替代
-} from "@element-plus/icons-vue";
-
-import DefaultMessage from "@/components/DefaultMessage/index.vue";
-
-
-// 语音图标
-import { Microphone, Mute } from "@element-plus/icons-vue";
-
 import LeftPanel from "@/components/LeftPanel.vue";
-const leftPanelRef = ref(null);
+import { ArrowLeftBold } from "@element-plus/icons-vue";
+import TextToText from "@/components/ai/text/TextToText.vue";
 
-// 语音输入响应式变量
-const isRecording = ref(false); // 录音状态
-const recognition = ref(null); // 语音识别实例
-const countdown = ref(0); // 倒计时剩余秒数
-const countdownTimer = ref(null); // 倒计时定时器
-
-// 默认消息控制
-const showDefaultMessages = ref(true);
-const handleDefaultMessageSelect = (message) => {
-  prompt.value = message;
-  handleSendByButton();
-  showDefaultMessages.value = false;
-};
+const leftPanelRef = ref(null);
 
 // 抽屉显示状态
 const drawerVisible = ref(true);
+
 // 添加切换抽屉显示状态的函数
 const toggleDrawer = () => {
   drawerVisible.value = !drawerVisible.value;
 };
 
-// 处理菜单展开和关闭
-const handleOpen = () => {};
-const handleClose = () => {};
-
 // 返回上一页
 const goBack = () => {
-  // 停止语音播放
-  stopPlayback();
   router.push("/ai-laboratory");
 };
+
 const router = useRouter();
 const route = useRoute();
 
@@ -181,523 +73,21 @@ const personName = ref(route.query.name);
 const personIntroduce = ref(route.query.message);
 const personImage = ref(route.query.image);
 
-// 渲染实验室携带的人物形象图片
-const selectedImage = ref("");
-onMounted(() => {
-  const image = route.query.image;
-  if (image) {
-    selectedImage.value = image;
-  }
-});
-
-// 聊天对话
-const activeConversationModelPath = ref(null); // 选中的对话编号
-const activeConversationId = ref(null); // 选中的对话编号
-const activeConversation = ref(null); // 选中的 Conversation
-const conversationInProgress = ref(false); // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作,导致 stream 中断
-
-// 消息列表
-const messageRef = ref();
-const activeMessageList = ref([]); // 选中对话的消息列表
-const activeMessageListLoading = ref(false); // activeMessageList 是否正在加载中
-const activeMessageListLoadingTimer = ref(); // activeMessageListLoading Timer 定时器。如果加载速度很快,就不进入加载中
-// 消息滚动
-const textSpeed = ref(50); // Typing speed in milliseconds
-const textRoleRunning = ref(false); // Typing speed in milliseconds
-
-// 发送消息输入框
-const isComposing = ref(false); // 判断用户是否在输入
-const conversationInAbortController = ref(); // 对话进行中 abort 控制器(控制 stream 对话)
-const inputTimeout = ref(); // 处理输入中回车的定时器
-const prompt = ref(""); // prompt
-const enableContext = ref(true); // 是否开启上下文
-// 接收 Stream 消息
-const receiveMessageFullText = ref("");
-const receiveMessageDisplayedText = ref("");
-const messageListRef = ref(null);
-const userScrolled = ref(false)//是否用户手动滚动
-
-// =========== 【聊天对话】相关 ===========
-
-/** 获取对话信息 */
-const getConversation = async (id) => {
-  if (!id) {
-    return;
-  }
-  const conversation = ref({});
-  if (!conversation) {
-    return;
-  }
-  conversation.systemMessage = personIntroduce.value;
-  activeConversation.value = conversation;
-  // activeConversationId.value = personId.value
-  activeConversationModelPath.value = personImage.value;
-};
-
-// =========== 【语音录入】相关 ===========
-// 初始化语音识别
-const initSpeechRecognition = () => {
-  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
-  if (!SpeechRecognition) {
-    alert("当前浏览器不支持语音输入功能");
-    return null;
-  }
-
-  const instance = new SpeechRecognition();
-  instance.lang = 'zh-CN';
-  instance.interimResults = false;
-
-  instance.onresult = (event) => {
-    if (event.results?.[0]?.[0]) {
-      prompt.value += event.results[0][0].transcript;
-    }
-  };
-
-  //识别器结束时清除定时器
-  instance.onend = () => {
-    clearInterval(countdownTimer.value);
-    isRecording.value = false;
-    countdown.value = 0;
-  };
-
-  instance.onerror = (event) => {
-    console.error('语音识别错误:', event.error);
-    clearInterval(countdownTimer.value); // 出错时清除定时器
-    isRecording.value = false;
-    Message().error('语音输入失败,请重试!', true)
-    countdown.value = 0;
-  };
-
-  return instance;
-};
-
-
-// 切换录音状态
-const toggleSpeechInput = () => {
-  // 无论当前状态如何,先清除可能存在的旧定时器
-  clearInterval(countdownTimer.value);
-  countdownTimer.value = null;
-
-  if (isRecording.value) {
-    // 手动停止时重置状态
-    countdown.value = 0;
-    recognition.value?.stop();
-    isRecording.value = false;
-  } else {
-    // 初始化倒计时前再次清除定时器(防止快速点击)
-    clearInterval(countdownTimer.value);
-    countdown.value = 10; // 重置为10秒
-
-    recognition.value = initSpeechRecognition();
-    if (!recognition.value) return;
-
-    navigator.mediaDevices.getUserMedia({ audio: true })
-      .then(() => {
-        recognition.value.start();
-        isRecording.value = true;
-
-        // 启动新的倒计时定时器
-        countdownTimer.value = setInterval(() => {
-          countdown.value--;
-          if (countdown.value <= 0) {
-            clearInterval(countdownTimer.value); // 倒计时结束清除
-            recognition.value.stop();
-            isRecording.value = false;
-            countdown.value = 0;
-          }
-        }, 1000);
-      })
-      .catch((err) => {
-        console.error("麦克风权限获取失败:", err);
-        alert("请允许麦克风权限以使用语音输入");
-        // 出错时重置状态
-        isRecording.value = false;
-        countdown.value = 0;
-      });
-  }
-};
-
-// =========== 【聊天对话】相关 ===========
-
-/** 处理来自 keydown 的发送消息 */
-const handleSendByKeydown = async (event) => {
-  // 判断用户是否在输入
-  if (isComposing.value) {
-    return;
-  }
-  // 进行中不允许发送
-  if (conversationInProgress.value) {
-    return;
-  }
-  const content = prompt.value?.trim();
-  if (event.key === "Enter") {
-    if (event.shiftKey) {
-      // 插入换行
-      prompt.value += "\r\n";
-      event.preventDefault(); // 防止默认的换行行为
-    } else {
-      // 发送消息
-      await doSendMessage(content);
-      event.preventDefault(); // 防止默认的提交行为
-    }
-  }
-};
-
-/** 处理来自【发送】按钮的发送消息 */
-const handleSendByButton = () => {
-  doSendMessage(prompt.value?.trim());
-};
-
-/** 处理 prompt 输入变化 */
-const handlePromptInput = (event) => {
-  // 非输入法 输入设置为 true
-  if (!isComposing.value) {
-    // 回车 event data 是 null
-    if (event.data == null) {
-      return;
-    }
-    isComposing.value = true;
-  }
-  // 清理定时器
-  if (inputTimeout.value) {
-    clearTimeout(inputTimeout.value);
-  }
-  // 重置定时器
-  inputTimeout.value = setTimeout(() => {
-    isComposing.value = false;
-  }, 400);
-};
-// TODO注:是不是可以通过 @keydown.enter、@keydown.shift.enter 来实现,回车发送、shift+回车换行;主要看看,是不是可以简化 isComposing 相关的逻辑
-const onCompositionstart = () => {
-  isComposing.value = true;
-};
-const onCompositionend = () => {
-  setTimeout(() => {
-    isComposing.value = false;
-  }, 200);
-};
-
-// 保存记录
-// 年级ID相关
-const gradeId = ref("");
-// 添加消息计数器变量
-const messageCount = ref(0);
-onMounted(() => {
-  // 从全局状态初始化年级ID
-  gradeId.value = globalState.initGradeId();
-});
-
-/** 真正执行【发送】消息操作 */
-const doSendMessage = async (content) => {
-  // 校验
-  if (content.length < 1) {
-    console.error("发送失败,原因:内容为空!");
-    return;
-  }
-  if (activeConversationId.value == null) {
-    console.error("还没创建对话,不能发送!");
-    return;
-  }
-  // 递增消息计数器
-  messageCount.value++;
-  // 发送saveRecord请求 保存消息次数
-  try {
-    await saveRecord({
-      brpNjId: gradeId.value,
-      brpType: "aiCount",
-      brpProgress: messageCount.value,
-    });
-  } catch (error) {
-    console.error("保存记录失败:", error);
-  }
-  // 清空输入框
-  prompt.value = "";
-  // 执行发送
-  await doSendMessageStream({
-    conversationId: activeConversationId.value,
-    content: content,
-  });
-};
-
-
-import { useAudioPlayer } from '@/api/tts/useAudioPlayer';
-import {Message} from "@/utils/message/Message.js";
-
-// 解构 stopPlayback 方法
-const { playAudioChunk,stopPlayback } = useAudioPlayer();
-
-/** 真正执行【发送】消息操作 */
-const doSendMessageStream = async (userMessage) => {
-  // 创建 AbortController 实例,以便中止请求
-  conversationInAbortController.value = new AbortController();
-  // 标记对话进行中
-  conversationInProgress.value = true;
-  // 设置为空
-  receiveMessageFullText.value = "";
-
-  try {
-    // 1.1 先添加两个假数据,等 stream 返回再替换
-    activeMessageList.value.push({
-      id: -1,
-      conversationId: activeConversationId.value,
-      type: "user",
-      content: userMessage.content,
-      createTime: new Date(),
-    });
-    activeMessageList.value.push({
-      id: -2,
-      conversationId: activeConversationId.value,
-      type: "assistant",
-      content: "思考中...",
-      createTime: new Date(),
-    });
-
-    // 1.2 开始滚动
-    textRoll();
-
-    // 2. 发送 event stream
-    let isFirstChunk = true; // 是否是第一个 chunk 消息段
-
-    // 销毁语音读取
-    stopPlayback();
-
-    await sendChatMessageStream(
-      userMessage.conversationId,
-      userMessage.content, null,
-      conversationInAbortController.value,
-      enableContext.value,
-      async (res) => {
-        const { code, data, msg } = JSON.parse(res.data);
-        if (code !== 0) {
-          console.log(`对话异常! ${msg}`);
-          return;
-        }
-
-        // 根据事件类型处理
-        if (data.eventType === "TEXT") {
-          // 如果内容为空,就不处理。
-          if (data.receive?.content === "") {
-            return;
-          }
-
-          // 处理文本消息
-          receiveMessageFullText.value += data.receive.content;
-
-          // 首次返回需要添加一个 message 到页面,后面的都是更新
-          if (isFirstChunk) {
-            isFirstChunk = false;
-            // 弹出两个假数据
-            activeMessageList.value.pop();
-            activeMessageList.value.pop();
-            // 更新返回的数据
-            activeMessageList.value.push(data.send);
-            activeMessageList.value.push(data.receive);
-          }
-        } else if (data.eventType === "AUDIO") {
-          // 处理音频消息
-          await playAudioChunk(data.audioData);
-        }
-      },
-      (error) => {
-        console.log(`对话异常! ${error}`);
-        stopStream();
-        // 需要抛出异常,禁止重试
-        throw error;
-      },
-      () => {
-        console.log(`结束对话! `)
-        stopStream();
-      }
-    );
-  } catch (error) {
-    console.error('发送消息失败:', error)
-    stopStream()
-  }
-};
-
-/** 停止 stream 流式调用 */
-const stopStream = async () => {
-  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
-  if (conversationInAbortController.value) {
-    conversationInAbortController.value.abort();
-  }
-  // 设置为 false
-  conversationInProgress.value = false;
-};
-
-/**
- * 消息列表
- *
- * 和 {@link #getMessageList()} 的差异是,把 systemMessage 考虑进去
- */
-const messageList = computed(() => {
-  if (activeMessageList.value.length > 0) {
-    return activeMessageList.value;
-  }
-
-  // 没有消息时,如果有 systemMessage 则展示它
-  if (activeConversation.value?.systemMessage) {
-    let systemMessage = {
-      id: 0,
-      type: "system",
-      content: activeConversation.value.systemMessage,
-    };
-    activeMessageList.value.push(systemMessage);
-    return [systemMessage];
-  }
-  return [];
-});
-
-// ============== 【消息滚动】相关 =============
-
-//处理滚动事件,判断用户是否手动滚动
-const handleScroll = () => {
-  if (messageListRef.value) {
-    const { scrollTop, scrollHeight, clientHeight } = messageListRef.value
-    // 当用户滚动距离底部超过50px时,认为是手动滚动
-    userScrolled.value = scrollTop + clientHeight < scrollHeight - 50
-  }
-}
-
-/** 滚动到 message 底部 */
-const scrollToBottom = async (isIgnore = false) => {
-  // 如果用户手动滚动过,不自动滚动
-  if (userScrolled.value) return
-
-  await nextTick();
-  if (messageListRef.value) {
-    requestAnimationFrame(() => {
-      messageListRef.value.scrollTop = messageListRef.value.scrollHeight;
-    });
-  }
-};
-
-/** 自提滚动效果 */
-const textRoll = async () => {
-
-  let index = 0;
-  try {
-    // 只能执行一次
-    if (textRoleRunning.value) {
-      return;
-    }
-    // 设置状态
-    textRoleRunning.value = true;
-    receiveMessageDisplayedText.value = "";
-    const task = async () => {
-      // 调整速度
-      const diff =
-        (receiveMessageFullText.value.length -
-          receiveMessageDisplayedText.value.length) /
-        10;
-      if (diff > 5) {
-        textSpeed.value = 10;
-      } else if (diff > 2) {
-        textSpeed.value = 30;
-      } else if (diff > 1.5) {
-        textSpeed.value = 50;
-      } else {
-        textSpeed.value = 100;
-      }
-      // 对话结束,就按 30 的速度
-      if (!conversationInProgress.value) {
-        textSpeed.value = 10;
-      }
-
-      if (index < receiveMessageFullText.value.length) {
-        receiveMessageDisplayedText.value +=
-          receiveMessageFullText.value[index];
-        index++;
-
-        // 更新 message
-        const lastMessage =
-          activeMessageList.value[activeMessageList.value.length - 1];
-        lastMessage.content = receiveMessageDisplayedText.value;
-
-        // 滚动到住下面
-        await scrollToBottom();
-        // 重新设置任务
-        timer = setTimeout(task, textSpeed.value);
-      } else {
-        // 不是对话中可以结束
-        if (!conversationInProgress.value) {
-          textRoleRunning.value = false;
-          clearTimeout(timer);
-        } else {
-          // 重新设置任务
-          timer = setTimeout(task, textSpeed.value);
-        }
-      }
-    };
-    let timer = setTimeout(task, textSpeed.value);
-  } catch {}
-};
-
-
-// 监听消息列表变化,自动滚动到底部
-watch(
-    () => messageList.value,
-    () => {
-      scrollToBottom();
-    },
-    { deep: true }
-);
-
-/** 初始化 **/
-onMounted(async () => {
-  if (personId.value) {
-    // 智能问答
-    CreateDialogue({ roleId: personId.value })
-      .then((res) => {
-        console.log("创建会话:", res);
-        activeConversationId.value = res.data;
-      })
-      .catch((error) => {
-        console.error("请求出错:", error);
-      });
-    await getConversation(personId.value);
-  }
-  // 获取列表数据
-  // activeMessageListLoading.value = true
-});
-
 // 路由参数变化监听
 watch(
   () => route.query,
   (newQuery, oldQuery) => {
     // 只有当id变化时才更新数据,避免不必要的刷新
     if (newQuery.id && newQuery.id !== oldQuery?.id) {
-       // 停止语音播放
-      stopPlayback();
       // 更新相关数据
       personId.value = newQuery.id;
       personName.value = newQuery.name;
       personIntroduce.value = newQuery.message;
       personImage.value = newQuery.image;
-      selectedImage.value = newQuery.image;
-
-      // 重新初始化对话
-      CreateDialogue({ roleId: newQuery.id })
-        .then((res) => {
-          activeConversationId.value = res.data;
-        })
-        .catch((error) => {
-          console.error("请求出错:", error);
-        });
-
-      getConversation(newQuery.id);
-
-      // 重置消息列表和默认消息显示状态
-      activeMessageList.value = [];
-      showDefaultMessages.value = true;
     }
   },
   { immediate: true, deep: true }
 );
-// 组件卸载时清理语音资源
-onUnmounted(() => {
-  stopPlayback();
-});
 </script>
 
 <style scoped lang="scss">
@@ -706,17 +96,7 @@ onUnmounted(() => {
 @function rpx($px) {
   @return math.div($px, 750) * 100vw;
 }
-/* 添加过渡样式 */
-.drawer-slide-enter-active,
-.drawer-slide-leave-active {
-  transition: all 0.3s ease;
-}
 
-.drawer-slide-enter-from,
-.drawer-slide-leave-to {
-  transform: translateX(-100%);
-  opacity: 0;
-}
 .home-container {
   position: fixed;
   top: 0;
@@ -740,232 +120,51 @@ onUnmounted(() => {
   left: 18%;
   transform: translateY(-50%);
   background-color: #44449c;
-  cursor: pointer; // 添加鼠标指针样式
+  cursor: pointer;
   clip-path: polygon(0 0, 100% 15%, 100% 85%, 0 100%);
   display: flex;
   justify-content: center;
   align-items: center;
   transition: all 0.3s ease;
 }
+
 .icon-expand .vertical-lines {
   color: #8a78d0;
   font-size: rpx(10);
 }
-.menu-icon {
-  width: rpx(11);
-  height: rpx(11);
-  margin-right: rpx(2);
-}
+
 .content-wrapper {
   display: flex;
   flex: 1;
 }
-.left-group {
-  width: rpx(135);
-  height: 100%;
-  background: linear-gradient(to bottom, #001169, #8a78d0);
-}
-.mb-2 {
-  color: black;
-  margin-top: rpx(1);
-}
-.tac ::v-deep(.el-menu) {
-  background-color: transparent;
-  border: none;
-  width: 100%;
-  margin-top: rpx(55);
-  margin-left: rpx(10);
-}
-.el-menu-item {
-  width: rpx(115);
-  height: rpx(25);
-  margin-bottom: rpx(5);
-  border-radius: rpx(6);
-  color: white;
-  font-size: rpx(8);
-}
-.el-menu-item .el-icon svg {
-  font-size: rpx(15);
-  color: white;
-}
 
-.el-menu ::v-deep(.el-menu-item:hover),
-.el-menu ::v-deep(.el-menu-item:focus),
-.el-menu ::v-deep(.el-menu-item:active) {
-  background: linear-gradient(
-    to bottom,
-    #ffefb0,
-    #ffcc00
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
-  color: black;
-  font-size: rpx(8);
-}
-.el-menu .el-menu-item.is-active {
-  background: linear-gradient(to bottom, #fee78a, #ffce1b);
-  color: black;
-  font-size: rpx(8);
-  box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
-}
 // 侧边栏
 .left-group2 {
-  width: rpx(150);
+  // width: rpx(150);
   height: 100%;
   display: flex;
   background-color: #ece9fd;
 }
-.left-group2 img {
-  width: rpx(120);
-  // height: auto;
-}
-.selected-image {
-  flex: 1;
-  margin: auto;
-  margin-left: rpx(-60);
-}
+
 .title-box {
   height: rpx(50);
+  position: absolute;
 }
+
 .box-icon {
   width: 100%;
   height: 100%;
   flex: 1;
-  display: flex; // 添加 flex 布局
-  align-items: center; // 垂直居中
-  color: black; // 设置图标颜色为白色
+  display: flex;
+  align-items: center;
+  color: black;
   padding-left: rpx(15);
-  font-size: rpx(10); // 设置图标大小,可按需调整
-  cursor: pointer; // 添加鼠标指针样式
+  font-size: rpx(10);
+  cursor: pointer;
 }
+
 .box-icon .left-icon {
   margin-left: rpx(10);
-  margin-right: rpx(5); // 设置图标和文字之间的间距 ;
-}
-
-.number-people {
-  flex: 1;
-  height: 100%;
-  display: flex;
-  background-color: #ece9fd;
-}
-.content-box {
-  flex: 1;
-  margin-top: rpx(10);
-  margin-bottom: rpx(10);
-  margin-right: rpx(10);
-  border-radius: rpx(15);
-  background: rgba($color: #ffffff, $alpha: 0.5);
-}
-
-// 对话框
-.chat-dialog {
-  display: flex;
-  flex-direction: column;
-  height: 100%;
-}
-.message-list {
-  flex: 1;
-  overflow-y: auto;
-  padding: rpx(15);
-}
-/* 自定义滚动条样式 */
-.message-list::-webkit-scrollbar {
-  width: rpx(2); /* 滚动条宽度 */
-}
-.message-list::-webkit-scrollbar-track {
-  background: #f1effd; /* 滚动条轨道背景色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb {
-  background: #e2ddfc; /* 滚动条滑块颜色 */
-  border-radius: rpx(4);
-}
-.message-list::-webkit-scrollbar-thumb:hover {
-  background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
-}
-.message-list .user-message {
-  background-color: #ffffff;
-  margin-left: auto; // 消息靠右显示
-  margin-right: 0; // 重置右边距
-  margin-bottom: rpx(10);
-  max-width: rpx(400);
-  font-size: rpx(8);
-  width: fit-content; // 宽度随文字内容变化
-  border-radius: rpx(5);
-  padding: rpx(5);
-  text-align: left; // 文字左对齐
-}
-.message-list .ai-message {
-  background-color: #ffdd55;
-  margin-left: 0; // 消息靠左显示
-  margin-right: auto; // 重置右边距
-  margin-bottom: rpx(10);
-  width: fit-content;
-  max-width: rpx(400);
-  padding: rpx(5);
-  font-size: rpx(8);
-  border-radius: rpx(5);
-  text-align: left; // 文字左对齐
-}
-.input-section {
-  display: flex;
-  padding: rpx(10);
-  gap: rpx(5);
-
-  .speech-btn {
-    padding: rpx(5) rpx(10);
-    background: #fff;
-    border: 1px solid #ffce1b;
-    border-radius: rpx(5);
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-
-    &.recording {
-      background: #ffeeba;
-      border-color: #ffc107;
-
-      .el-icon {
-        color: #dc3545;
-      }
-    }
-
-    .el-icon {
-      font-size: rpx(8);
-      color: #666;
-    }
-  }
-
-  // 终止按钮样式
-  .stop-btn {
-    cursor: pointer;
-    display: flex;
-    align-items: center;
-    img {
-      width: rpx(20);
-      height: rpx(20);
-    }
-  }
-}
-.input-section input {
-  flex: 1;
-  padding: rpx(5);
-  font-size: rpx(7);
-  border: 1px solid #ccc;
-  border-radius: rpx(5);
-}
-.input-section button {
-  padding: rpx(5) rpx(15);
-  background: linear-gradient(
-    to bottom,
-    #fee78a,
-    #ffce1b
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  color: black;
-  border: none;
-  font-size: rpx(7);
-  border-radius: rpx(5);
-  cursor: pointer;
-  box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
+  margin-right: rpx(5);
 }
-</style>
+</style>