Răsfoiți Sursa

终止按钮

丸子 8 luni în urmă
părinte
comite
15b3d42fff

BIN
src/assets/icon/sendicon.png


BIN
src/assets/icon/sendicon02.png


BIN
src/assets/icon/starticon.png


BIN
src/assets/icon/stopicon.png


+ 28 - 14
src/components/LeftPanel.vue

@@ -35,7 +35,7 @@
 </template>
 
 <script setup>
-import { ref, onMounted} from 'vue'
+import { ref, onMounted,watch} from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 
 // 导入图片 白色
@@ -74,19 +74,32 @@ const groupList = ref([
     title: '数字人老师'
   }
 ])
-// 组件挂载时确保默认选中状态
-onMounted(() => {
-  // 从路由判断当前应该选中的菜单项
-  const path = route.path
-  const from = route.query.from
-   if (path.includes('ai-questions') && from === 'ai-laboratory') {
-    currentActiveIndex.value = '2'  // 数字人老师
+
+
+// 提取更新选中状态的逻辑为单独函数
+const updateActiveIndex = () => {
+  const path = route.path;
+  const from = route.query.from;
+  if (path.includes('ai-questions') && from === 'ai-laboratory') {
+    currentActiveIndex.value = '2'; // 数字人老师
   } else if (path.includes('ai-questions')) {
-    currentActiveIndex.value = '0'  // 智能问答
+    currentActiveIndex.value = '0'; // 智能问答
   } else if (path.includes('ai-painting')) {
-    currentActiveIndex.value = '1'  // 智能绘画
+    currentActiveIndex.value = '1'; // 智能绘画
+  } else if (path.includes('ai-laboratory')) {
+    currentActiveIndex.value = '2'; // 数字人老师
   }
-})
+};
+
+// 组件挂载时确保默认选中状态
+onMounted(() => {
+  updateActiveIndex();
+});
+
+// 添加路由变化监听,更新选中状态
+watch(() => route, () => {
+  updateActiveIndex();
+}, { immediate: true, deep: true });
 
 // 存储小智数据
 const personData = ref([])
@@ -97,7 +110,6 @@ const navigateToAI = async (group) => {
       const grade = route.query.grade || localStorage.getItem('selectedGrade')
       // 获取小学低年级AI数据
       const juniorAIRes = await teacherList({ category: grade + 'AI' })
-      console.log(juniorAIRes);
       const aiPerson = juniorAIRes.data.list.find(
         person => person.name === '小智'
       )
@@ -113,8 +125,10 @@ const navigateToAI = async (group) => {
         router
           .push({
             path: '/ai-questions',
-            query: personData.value,
-            category: grade + 'AI'
+            query: {
+              ...personData.value,
+              category: grade + 'AI'
+            }
           })
       } else {
         console.warn('未找到名为小智的数据')

+ 1 - 0
src/components/PPT/PptView.vue

@@ -34,6 +34,7 @@
             <img :src="rightImg" alt="下一页" />
           </button>
         </div>
+        
       </div>
     </div>
   </div>

+ 1 - 2
src/components/videopage/DialogComponents.vue

@@ -556,6 +556,7 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
     margin-right: rpx(10);
     font-weight: bold;
     box-shadow: 0 rpx(2) rpx(5) rgba($accent-color, 0.3);
+    flex-shrink: 0;  // 防止图标被压缩
   }
 }
 
@@ -644,11 +645,9 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
   align-items: center;
   cursor: pointer;
   transition: all 0.3s ease;
-
   &:hover {
     transform: translateY(-rpx(2));
   }
-
   .ai-icon {
     width: rpx(30);
     height: rpx(30);

+ 0 - 5
src/router/index.js

@@ -55,11 +55,6 @@ const routes = [
   {
     path: '/ai-develop',
     component: () => import('../views/AIDevelop.vue')
-  },
-  // 高年级
-  {
-    path: '/senior-grade',
-    component: () => import('../views/SeniorGrade.vue')
   }
 ]
 const router = createRouter({

+ 0 - 1
src/views/AIDevelop.vue

@@ -432,7 +432,6 @@ onMounted(async () => {
     try {
       // 取接口课程数据
       const res = await ClassType(typeIdParam)
-      console.log(res);
       courseList.value = res.data
       // 初始化已观看课程ID
       const savedWatchedIds = localStorage.getItem('watchedCourseIds')

+ 1 - 4
src/views/AILaboratory.vue

@@ -98,10 +98,8 @@ const peopleList = ref([])
 onMounted(async () => {
   try {
     grade.value = route.query.grade || localStorage.getItem('selectedGrade')
-    console.log(grade)
     // 获取小学低年级数据
     const juniorRes = await teacherList({ category: grade.value })
-    console.log(juniorRes)
     peopleList.value = juniorRes.data.list.map(person => ({
       id: person.id,
       name: person.name,
@@ -109,7 +107,6 @@ onMounted(async () => {
       message: person.systemMessage,
       default: person.questTip
     }))
-    console.log(peopleList.value)
   } catch (error) {
     console.error('获取小学低年级数据失败:', error)
   }
@@ -122,8 +119,8 @@ const navigateToAIQuestions = person => {
     query: {
       ...person,
       from: 'ai-laboratory',  // 添加来源标识
+      category: grade.value
     },
-    category: grade.value
   })
 }
 

+ 0 - 2
src/views/AIPainting.vue

@@ -254,14 +254,12 @@ const messageCount = ref(0)
 onMounted(async () => {
     // 从全局状态初始化年级ID
   gradeId.value = globalState.initGradeId()
-  console.log(gradeId.value);
   try{
     const res = await saveRecord({
         brpNjId: gradeId.value,
         brpType: "aiCount",
         brpProgress: 1
       });
-      console.log(res);
   }catch(error){
     console.error('保存记录失败:', error);
   }

+ 124 - 72
src/views/AIQuestions.vue

@@ -65,23 +65,40 @@
             <!-- 输入框和发送按钮 -->
             <div class="input-section">
               <input
-                  type="text"
-                  v-model="prompt"
-                  placeholder="问我任何问题..."
-                  @keyup.enter="handleSendByKeydown"
+                type="text"
+                v-model="prompt"
+                placeholder="问我任何问题..."
+                @keyup.enter="handleSendByKeydown"
               />
               <!-- 添加语音输入按钮 -->
               <button
-                  @click="toggleSpeechInput"
-                  class="speech-btn"
-                  :class="{ 'recording': isRecording }"
+                @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>
+                <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>
-              <button @click="handleSendByButton">发送</button>
             </div>
           </div>
         </div>
@@ -91,13 +108,15 @@
 </template>
 
 <script setup>
-import {ref, onMounted, computed, watch, nextTick} from "vue";
+import { ref, onMounted, computed, watch, nextTick } from "vue";
 import { CreateDialogue, sendChatMessageStream } from "@/api/questions.js";
 import { useRouter, useRoute } from "vue-router";
-import { saveRecord } from '@/api/personalized/index.js'
+import { saveRecord } from "@/api/personalized/index.js";
 // 导入全局状态
-import { globalState } from '@/utils/globalState.js'
+import { globalState } from "@/utils/globalState.js";
 
+// 终止按钮
+import stopicon from "@/assets/icon/stopicon.png";
 
 import MarkdownView from "@/components/MarkdownView/index.vue";
 import {
@@ -113,23 +132,18 @@ import {
   Picture,
   Tickets,
   User,
+  Search, // 使用Search图标作为替代
 } from "@element-plus/icons-vue";
 
 import DefaultMessage from "@/components/DefaultMessage/index.vue";
 
-// 导入图片
-// import question from '@/assets/icon/question.png'
-// import painting from '@/assets/icon/painting.png'
-// import human from '@/assets/icon/human.png'
 
 // 语音图标
 import { Microphone, Mute } from "@element-plus/icons-vue";
 
-
 import LeftPanel from "@/components/LeftPanel.vue";
 const leftPanelRef = ref(null);
 
-
 // 语音输入响应式变量
 const isRecording = ref(false); // 录音状态
 const recognition = ref(null); // 语音识别实例
@@ -195,14 +209,13 @@ 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 prompt = ref(""); // prompt
 const enableContext = ref(true); // 是否开启上下文
 // 接收 Stream 消息
 const receiveMessageFullText = ref("");
 const receiveMessageDisplayedText = ref("");
 const messageListRef = ref(null);
 
-
 // =========== 【聊天对话】相关 ===========
 
 /** 获取对话信息 */
@@ -223,14 +236,15 @@ const getConversation = async (id) => {
 // =========== 【语音录入】相关 ===========
 // 初始化语音识别
 const initSpeechRecognition = () => {
-  const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+  const SpeechRecognition =
+    window.SpeechRecognition || window.webkitSpeechRecognition;
   if (!SpeechRecognition) {
     alert("当前浏览器不支持语音输入功能");
     return null;
   }
 
   const instance = new SpeechRecognition();
-  instance.lang = 'zh-CN';
+  instance.lang = "zh-CN";
   instance.interimResults = false;
 
   instance.onresult = (event) => {
@@ -247,10 +261,10 @@ const initSpeechRecognition = () => {
   };
 
   instance.onerror = (event) => {
-    console.error('语音识别错误:', event.error);
+    console.error("语音识别错误:", event.error);
     clearInterval(countdownTimer.value); // 出错时清除定时器
     isRecording.value = false;
-    alert('语音输入失败,请重试');
+    alert("语音输入失败,请重试");
     countdown.value = 0;
   };
 
@@ -276,29 +290,30 @@ const toggleSpeechInput = () => {
     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;
-        });
+    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;
+      });
   }
 };
 
@@ -364,12 +379,12 @@ const onCompositionend = () => {
 
 // 保存记录
 // 年级ID相关
-const gradeId = ref('')
+const gradeId = ref("");
 // 添加消息计数器变量
-const messageCount = ref(0)
+const messageCount = ref(0);
 onMounted(() => {
-   // 从全局状态初始化年级ID
-  gradeId.value = globalState.initGradeId()
+  // 从全局状态初始化年级ID
+  gradeId.value = globalState.initGradeId();
 });
 
 /** 真正执行【发送】消息操作 */
@@ -383,17 +398,17 @@ const doSendMessage = async (content) => {
     console.error("还没创建对话,不能发送!");
     return;
   }
-   // 递增消息计数器
-  messageCount.value++
+  // 递增消息计数器
+  messageCount.value++;
   // 发送saveRecord请求 保存消息次数
-  try{
+  try {
     await saveRecord({
-        brpNjId: gradeId.value,
-        brpType: "aiCount",
-        brpProgress: messageCount.value
-      });
-  }catch(error){
-    console.error('保存记录失败:', error);
+      brpNjId: gradeId.value,
+      brpType: "aiCount",
+      brpProgress: messageCount.value,
+    });
+  } catch (error) {
+    console.error("保存记录失败:", error);
   }
   // 清空输入框
   prompt.value = "";
@@ -404,8 +419,7 @@ const doSendMessage = async (content) => {
   });
 };
 
-
-import { useAudioPlayer } from '@/components/TTS/useAudioPlayer';
+import { useAudioPlayer } from "@/components/TTS/useAudioPlayer";
 
 const { playAudioChunk } = useAudioPlayer();
 
@@ -454,11 +468,10 @@ const doSendMessageStream = async (userMessage) => {
         }
 
         // 根据事件类型处理
-        if (data.eventType === 'TEXT') {
-
+        if (data.eventType === "TEXT") {
           // 如果内容为空,就不处理。
-          if (data.receive?.content === '') {
-            return
+          if (data.receive?.content === "") {
+            return;
           }
 
           // 处理文本消息
@@ -474,7 +487,7 @@ const doSendMessageStream = async (userMessage) => {
             activeMessageList.value.push(data.send);
             activeMessageList.value.push(data.receive);
           }
-        } else if (data.eventType === 'AUDIO') {
+        } else if (data.eventType === "AUDIO") {
           // 处理音频消息
           await playAudioChunk(data.audioData);
         }
@@ -567,7 +580,8 @@ const textRoll = async () => {
       }
 
       if (index < receiveMessageFullText.value.length) {
-        receiveMessageDisplayedText.value += receiveMessageFullText.value[index];
+        receiveMessageDisplayedText.value +=
+          receiveMessageFullText.value[index];
         index++;
 
         // 更新 message
@@ -593,7 +607,6 @@ const textRoll = async () => {
   } catch {}
 };
 
-
 /** 初始化 **/
 onMounted(async () => {
   if (personId.value) {
@@ -612,9 +625,37 @@ onMounted(async () => {
   // activeMessageListLoading.value = true
 });
 
+// 路由参数变化监听
+watch(
+  () => route.query,
+  (newQuery, oldQuery) => {
+    // 只有当id变化时才更新数据,避免不必要的刷新
+    if (newQuery.id && newQuery.id !== oldQuery?.id) {
+      // 更新相关数据
+      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 }
+);
 </script>
 
 <style scoped lang="scss">
@@ -827,7 +868,7 @@ onMounted(async () => {
 .input-section {
   display: flex;
   padding: rpx(10);
-  gap: rpx(10);
+  gap: rpx(5);
 
   .speech-btn {
     padding: rpx(5) rpx(10);
@@ -852,6 +893,17 @@ onMounted(async () => {
       color: #666;
     }
   }
+
+  // 终止按钮样式
+  .stop-btn {
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    img {
+      width: rpx(20);
+      height: rpx(20);
+    }
+  }
 }
 .input-section input {
   flex: 1;
@@ -872,6 +924,6 @@ onMounted(async () => {
   font-size: rpx(7);
   border-radius: rpx(5);
   cursor: pointer;
-  box-shadow: 0 4px 8px rgba(202, 52, 52, 0.3);
+  box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
 }
 </style>

+ 0 - 1
src/views/personalized/Personalized.vue

@@ -103,7 +103,6 @@ onMounted(async()=>{
   gradeId.value = globalState.initGradeId()
   try {
     const res = await getReport({ brpNjId: gradeId.value });
-    console.log(res);
     // 赋值三个数据以及评语
     aiCount.value = res.aiCount;
     njCourseConfigProgress.value = res.njCourseConfigProgress;