Ver código fonte

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	src/views/AIDevelop.vue
liyanbo 9 meses atrás
pai
commit
c52dbd3bc7

+ 10 - 0
package-lock.json

@@ -15,6 +15,7 @@
         "element-plus": "^2.10.2",
         "highlight.js": "^11.11.1",
         "hls.js": "^1.6.7",
+        "js-cookie": "^3.0.5",
         "jsencrypt": "^3.3.2",
         "markdown-it": "^14.1.0",
         "router": "^2.2.0",
@@ -3688,6 +3689,15 @@
       "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
       "license": "MIT"
     },
+    "node_modules/js-cookie": {
+      "version": "3.0.5",
+      "resolved": "https://registry.npmmirror.com/js-cookie/-/js-cookie-3.0.5.tgz",
+      "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",

+ 1 - 0
package.json

@@ -16,6 +16,7 @@
     "element-plus": "^2.10.2",
     "highlight.js": "^11.11.1",
     "hls.js": "^1.6.7",
+    "js-cookie": "^3.0.5",
     "jsencrypt": "^3.3.2",
     "markdown-it": "^14.1.0",
     "router": "^2.2.0",

BIN
src/assets/icon/Human02.png


BIN
src/assets/icon/painting02.png


BIN
src/assets/icon/question02.png


+ 58 - 0
src/components/DefaultMessage/index.vue

@@ -0,0 +1,58 @@
+<template>
+  <!-- 问答页面的默认消息提示 -->
+  <div class="default-messages">
+    <span
+      v-for="(msg, index) in defaultMessages"
+      :key="index"
+      @click="useDefaultMessage(msg)"
+      class="default-message-item"
+    >
+      {{ msg }}
+    </span>
+  </div>
+</template>
+
+<script setup>
+import { ref,defineEmits } from 'vue'
+
+// 默认消息
+const defaultMessages = ref([
+  '什么是人工智能?',
+  '机器学习和深度学习的区别是什么?',
+  '如何使用AI生成图像?'
+])
+
+// 点击时通过 emit 触发该事件并传递消息内容
+const emit = defineEmits(['select-message'])
+const useDefaultMessage = message => {
+  emit('select-message', message)
+}
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+// 默认消息样式
+.default-messages {
+  display: flex;
+  flex-direction: column;
+  gap: rpx(4);
+  margin-left: rpx(10);
+}
+.default-message-item {
+  border: 2px solid #fff;
+  background: rgba($color: #fff, $alpha: 0.5);
+  border-radius: rpx(4);
+  padding: rpx(3);
+  cursor: pointer;
+  transition: all 0.2s ease;
+  text-align: left;
+  width: fit-content;
+}
+.default-message-item:hover {
+  border-color: #ffce1b;
+}
+</style>

+ 24 - 12
src/components/HomePage.vue

@@ -78,10 +78,11 @@
         <span>AI实验室</span>
       </div>
       <div class="right-box-in-box2">
+        <!-- @click="goToEvaluation" -->
         <div
           class="top-sub-box"
           :style="{ backgroundImage: `url(${indexImages[2]})` }"
-          @click="notOpen()"
+           @click="notOpen()"
         >
           <span>能力测评</span>
         </div>
@@ -143,6 +144,13 @@ const goToAILab = () => {
   })
 }
 
+// 能力测评
+//const goToEvaluation = () =>{
+  //router.push({
+   // path:'/evaluation'
+ // })
+//}
+
 // 添加下拉菜单选中项
 const selectedGrade = ref(localStorage.getItem('selectedGrade') || '')
 // 获取年级
@@ -346,7 +354,7 @@ onMounted(() => {
   background-color: transparent;
   color: white;
   border: none; // 移除默认边框
-  font-size: rpx(7); // 使用 rpx 函数设置字体大小
+  font-size: rpx(8); // 使用 rpx 函数设置字体大小
   outline: none; // 移除默认的外边框
 }
 .top-right-btn.is-active,
@@ -369,14 +377,14 @@ onMounted(() => {
 
 
 .dropdown-box .el-button {
-  width: rpx(55); // 设置按钮宽度;
+  width: rpx(60); // 设置按钮宽度;
   height: rpx(15); // 设置按钮高度;
   background-color: rgb(255, 255, 255, 0.7);
   border: 1px white solid;
   box-shadow: 0 4px 8px rgb(0, 0, 0, 0.3);
   color: black;
   border-radius: rpx(12);
-  font-size: rpx(7); // 设置字体大小;
+  font-size: rpx(8); // 设置字体大小;
 }
 .dropdown-box .el-button:hover,
 .dropdown-box .el-button:focus,
@@ -386,11 +394,13 @@ onMounted(() => {
 }
 .dropdown-menu {
   width: rpx(100);
-  height: rpx(60);
+  // height: rpx(60);
   border-radius: rpx(5);
   border: 1px white solid;
-  background-color: rgba(161, 161, 161, 0.5);
+  background-color: rgb(255, 255, 255,0.5);
+  backdrop-filter: blur(rpx(5));
   box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
+  margin-left: rpx(40);
 }
 
 .el-scrollbar__view .el-dropdown__list{
@@ -414,22 +424,24 @@ onMounted(() => {
     to bottom,
     #fee78a,
     #ffce1b
-  ); /* 设置悬停、聚焦、点击状态下的背景色 */
-  color: black;
+  );
+  box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
 }
+</style>
 
 
+<style lang="scss">
+/* 消除小三角 */
 .el-popper__arrow{
   display: none;
 }
 .el-popper.is-light,
 .el-dropdown__popper.el-popper{
-  background: transparent !important;
-  border: none !important;
-  box-shadow: none !important;
+  background: transparent;
+  border: none;
+  box-shadow: none;
 }
 .el-dropdown__popper{
   --el-dropdown-menuItem-hover-color: none;
 }
-
 </style>

+ 59 - 34
src/components/LeftPanel.vue

@@ -1,11 +1,11 @@
 <template>
   <!-- 左侧折叠面板 -->
   <transition name="drawer-slide">
-    <div class="left-group" >
+    <div class="left-group">
       <el-row class="tac">
         <el-col :span="12">
           <el-menu
-            default-active="2"
+            default-active="3"
             class="el-menu-vertical-demo"
             @open="handleOpen"
             @close="handleClose"
@@ -13,9 +13,18 @@
             <el-menu-item
               v-for="(item, index) in groupList"
               :key="index"
+              :index="index.toString()"
               @click="navigateToAI(item)"
+              v-model="currentActiveIndex"
+              @mouseover="item.isHover = true"
+              @mouseout="item.isHover = false"
             >
-              <img :src="item.icon" alt="" class="menu-icon" />
+              <!-- 根据状态切换图片currentActiveIndex === index.toString() || item.isHover ? item.hoverIcon :  -->
+               <img
+                :src="item.icon"
+                alt=""
+                class="menu-icon"
+              />
               {{ item.title }}
             </el-menu-item>
           </el-menu>
@@ -26,37 +35,57 @@
 </template>
 
 <script setup>
-import { ref,onMounted } from 'vue'
-import { useRouter,useRoute } from 'vue-router'
+import { ref, onMounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
 
-// 导入图片
+// 导入图片 白色
 import question from '@/assets/icon/question.png'
 import painting from '@/assets/icon/painting.png'
-import human from '@/assets/icon/human.png'
+import Human from '@/assets/icon/human.png'
+// 黑色
+import question02 from '@/assets/icon/question02.png'
+import painting02 from '@/assets/icon/painting02.png'
+import Human02 from '@/assets/icon/Human02.png'
 
 import { teacherList } from '@/api/teachers.js'
 
-
 const router = useRouter()
 const route = useRoute()
 
 // 添加抽屉显示状态
 const drawerVisible = ref(true)
+// 添加当前选中索引状态
+const currentActiveIndex = ref('2')
 // 渲染侧边栏
 const groupList = ref([
   {
-    icon: question,
+    icon: question, // 默认图片
+    hoverIcon: question02, // 交互图片
     title: '智能问答'
   },
   {
-    icon: painting,
+    icon: painting, // 默认图片
+    hoverIcon: painting02, // 交互图片
     title: '智能绘画'
   },
   {
-    icon: human,
+    icon: Human, // 默认图片
+    hoverIcon: Human02, // 交互图片
     title: '数字人老师'
   }
 ])
+// 组件挂载时确保默认选中状态
+onMounted(() => {
+  // 从路由判断当前应该选中的菜单项
+  const path = route.path
+  if (path.includes('ai-questions')) {
+    currentActiveIndex.value = '0'
+  } else if (path.includes('ai-painting')) {
+    currentActiveIndex.value = '1'
+  } else if (path.includes('ai-laboratory')) {
+    currentActiveIndex.value = '2'
+  }
+})
 
 // 存储小智数据
 const personData = ref([])
@@ -64,10 +93,12 @@ const personData = ref([])
 const navigateToAI = async (group) => {
   if (group.title === '智能问答') {
     try {
-       const grade = route.query.grade || localStorage.getItem('selectedGrade')
+      const grade = route.query.grade || localStorage.getItem('selectedGrade')
       // 获取小学低年级AI数据
       const juniorAIRes = await teacherList({ category: grade + 'AI' })
-      const aiPerson = juniorAIRes.data.list.find(person => person.name === '小智')
+      const aiPerson = juniorAIRes.data.list.find(
+        person => person.name === '小智'
+      )
       if (aiPerson) {
         personData.value = {
           id: aiPerson.id,
@@ -75,13 +106,14 @@ const navigateToAI = async (group) => {
           image: aiPerson.model2dPath,
           message: aiPerson.systemMessage
         }
-        console.log(personData.value);
-        router.push({
-          path: '/ai-questions',
-          query: personData.value
-        })
+        console.log(personData.value)
+        router
+          .push({
+            path: '/ai-questions',
+            query: personData.value
+          })
       } else {
-        console.warn('未找到名为小智的数据');
+        console.warn('未找到名为小智的数据')
       }
     } catch (error) {
       console.error('获取小学低年级AI数据失败:', error)
@@ -95,7 +127,6 @@ const navigateToAI = async (group) => {
   }
 }
 
-
 // 处理菜单展开和关闭
 const handleOpen = () => {}
 const handleClose = () => {}
@@ -116,13 +147,14 @@ defineExpose({
   @return math.div($px, 750) * 100vw;
 }
 /* 添加过渡样式 */
-.drawer-slide-enter-active,
-.drawer-slide-leave-active {
+::v-deep .drawer-slide-enter-active,
+::v-deep .drawer-slide-leave-active {
   transition: all 0.3s ease;
 }
-.drawer-slide-enter-from,
-.drawer-slide-leave-to {
+::v-deep .drawer-slide-enter-from,
+::v-deep .drawer-slide-leave-to {
   transform: translateX(-100%);
+  opacity: 0;
 }
 
 // 侧边栏
@@ -154,19 +186,13 @@ defineExpose({
   font-size: rpx(15);
   color: white;
 }
-.el-menu ::v-deep(.el-menu-item:hover) {
-  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 {
+.el-menu .el-menu-item:hover {
   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 {
+:deep(.el-menu .el-menu-item.is-active) {
   background: linear-gradient(to bottom, #fee78a, #ffce1b);
   color: black;
   font-size: rpx(8);
@@ -177,5 +203,4 @@ defineExpose({
   height: rpx(11);
   margin-right: rpx(2);
 }
-
-</style>
+</style>

+ 5 - 0
src/router/index.js

@@ -21,6 +21,11 @@ const routes = [
     path: '/ai-laboratory',
     component: () => import('../views/AILaboratory.vue')
   },
+  // 能力测评
+  {
+    path: '/evaluation',
+    component: () => import('../views/evaluation/testTopic.vue')
+  },
   // 智能绘画
   {
     path: '/ai-painting',

+ 48 - 43
src/views/AIDevelop.vue

@@ -1,7 +1,6 @@
 <template>
   <!-- AI发展历程 -->
   <div class="home-container">
-
     <!-- 展开收起侧边栏 -->
     <div
       class="icon-expand"
@@ -20,16 +19,16 @@
       >
     </div>
 
-      <el-drawer
-          v-model="drawerVisible"
-          direction="ltr"
-          size="18%"
-          :with-header="false"
-      >
-        <!-- 添加抽屉 -->
-        <div class="drawer-box">
-          <el-row class="tac">
-            <el-col :span="12">
+    <el-drawer
+      v-model="drawerVisible"
+      direction="ltr"
+      size="18%"
+      :with-header="false"
+    >
+      <!-- 添加抽屉 -->
+      <div class="drawer-box">
+        <el-row class="tac">
+          <el-col :span="12">
             <span class="mb-2">
               <img :src="classImages" alt="课程小节图标" />
               课程小节
@@ -119,7 +118,7 @@
           <!-- 下一个视频 -->
           <div class="caret-right" @click="playNextVideo">
             <el-button type="warning" round
-            >下一节
+              >下一节
               <img :src="rightImg" alt="Right" />
             </el-button>
           </div>
@@ -174,10 +173,9 @@
             </div>
             <!-- 右侧小图标 -->
             <div
-                v-if="courseConfig.ccAiAnswer !== null"
-                class="ai-icon-container"
-                @click="handleAIClick"
-
+              v-if="courseConfig.ccAiAnswer !== null"
+              class="ai-icon-container"
+              @click="handleAIClick"
             >
               <img
                 src="@/assets/images/xiaozhi.png"
@@ -242,14 +240,8 @@
 </template>
 
 <script setup>
-import { ref, onMounted,onUnmounted, onBeforeUnmount } from 'vue'
+import { ref, onMounted, onUnmounted, onBeforeUnmount } from 'vue'
 import { useRouter } from 'vue-router'
-import videojs from 'video.js';
-import 'video.js/dist/video-js.css';
-import '@videojs/http-streaming'; // 支持HLS分片
-const videoPlayer = ref(null);
-let player = null;
-
 import {
   Expand,
   Fold,
@@ -284,7 +276,6 @@ const toggleDrawer = () => {
   drawerVisible.value = !drawerVisible.value
 }
 
-
 // 返回上一页
 const goBack = () => {
   router.go(-1)
@@ -300,7 +291,7 @@ const course = ref({})
 // 菜单数据
 const menuItems = ref([])
 // 课程集合数据
-const videoPathMap  = ref({})
+const videoPathMap = ref({})
 
 //课程小节字典(需要新加接口调取字典)
 const menuDict = ref({
@@ -377,7 +368,7 @@ const handleClose = () => {}
 // 菜单选择的处理函数
 const handleSelect = index => {
   //测试账号禁用视频
-  if (disableVideo(index))return;
+  if (disableVideo(index)) return
 
   const findTitle = items => {
     for (const item of items) {
@@ -398,11 +389,11 @@ const handleSelect = index => {
     course.value = videoPathMap.value[index]
     // 切换标题后,关闭抽屉
     drawerVisible.value = false
-  }else {
+  } else {
     //视频不存在
-    Message().notifyWarning('视频不存在!', true);
+    Message().notifyWarning('视频不存在!', true)
   }
-  
+
   //测试账号禁用视频
   if (disableVideo()) return
 }
@@ -426,7 +417,7 @@ const flattenMenuItems = () => {
 // 播放下一个视频
 const playNextVideo = () => {
   //测试账号禁用视频
-  if (disableVideo())return;
+  if (disableVideo()) return
 
   const allIndices = flattenMenuItems()
   const currentIndexInList = allIndices.indexOf(course.value.key)
@@ -442,7 +433,7 @@ const playNextVideo = () => {
   }
 
   // 重置
-  pausedIndices = ref({time: [], newTime: []})
+  pausedIndices = ref({ time: [], newTime: [] })
   userMessage = ref('')
   messageHistory = ref([])
 }
@@ -451,7 +442,7 @@ const playNextVideo = () => {
 // 播放上一个视频
 const playPreviousVideo = () => {
   //测试账号禁用视频
-  if (disableVideo())return;
+  if (disableVideo()) return
 
   const allIndices = flattenMenuItems()
   const currentIndexInList = allIndices.indexOf(course.value.key)
@@ -470,7 +461,7 @@ const playPreviousVideo = () => {
 // 尝试播放视频,处理浏览器自动播放限制
 const tryPlayVideo = () => {
   //测试账号禁用视频
-  if (disableVideo())return;
+  if (disableVideo()) return
 
   const playPromise = videoRef.value.play()
   if (playPromise !== undefined) {
@@ -498,8 +489,8 @@ const checkVideoPermission = () => {
     }
   }
   //记录已暂停的内容
-  setVideoStop();
-};
+  setVideoStop()
+}
 
 //禁用视频
 const disableVideo = (index = course.value.key) => {
@@ -523,7 +514,7 @@ const disableVideo = (index = course.value.key) => {
 // 视频 ref
 const videoRef = ref(null)
 // 记录已经暂停过的时间点索引
-let pausedIndices = ref({time: [], newTime: []})
+let pausedIndices = ref({ time: [], newTime: [] })
 // 试题弹框显示状态
 const questionDialogVisible = ref(false)
 // 当前显示的试题
@@ -578,7 +569,7 @@ const handleCloseQuestionDialog = () => {
   videoRef.value.play()
 
   //记录已暂停的内容
-  setVideoStop();
+  setVideoStop()
 }
 
 //记录已暂停的内容
@@ -603,7 +594,7 @@ const handleSubmitAnswer = () => {
   selectedOption.value = null
 
   //记录已暂停的内容
-  setVideoStop();
+  setVideoStop()
 }
 
 // 发送消息
@@ -751,6 +742,10 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
   height: rpx(20);
   margin-bottom: rpx(5);
   border-radius: rpx(6);
+  // 添加flex布局使标题和图标两端对齐
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
 }
 .el-menu ::v-deep(.el-sub-menu__title:hover),
 .el-menu ::v-deep(.el-sub-menu__title:focus),
@@ -758,8 +753,18 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
   background: linear-gradient(to bottom, #fee78a, #ffce1b);
   background-color: transparent;
 }
-.el-menu ::v-deep(.el-icon svg) {
-  font-size: rpx(9);
+// 添加二级标题折叠图标样式
+::v-deep(.el-sub-menu__icon-arrow) {
+  color: white;
+  font-size: rpx(10); // 增大图标尺寸
+  margin-left: auto; // 将图标推到右侧
+  margin-top: rpx(-5);
+  display: block;
+  width: rpx(16); // 确保有明确宽度
+}
+// 鼠标悬停时的图标样式
+.el-menu ::v-deep(.el-sub-menu__title:hover .el-sub-menu__icon-arrow) {
+  color: black; // 与悬停状态的文字颜色保持一致
 }
 
 .mb-2 {
@@ -1015,9 +1020,9 @@ video::-webkit-media-controls-panel {
     border: none;
     border-radius: rpx(20);
     background: linear-gradient(
-            135deg,
-            $light-color,
-            #d8bfd8
+      135deg,
+      $light-color,
+      #d8bfd8
     ); // 柔和的蓝紫色渐变
     overflow: hidden;
     display: flex; // 添加 flex 布局

+ 122 - 61
src/views/AIGeneralCourse.vue

@@ -2,51 +2,55 @@
   <!-- AI智能课 -->
   <div class="home-container">
     <!-- 展开收起侧边栏 -->
-    <div
-      class="icon-expand"
-      :style="{
-        backgroundColor: drawerVisible ? '#44449c' : '#7F70C840',
-        left: drawerVisible ? '18%' : '0'
-      }"
-      @click="toggleDrawer"
-    >
-      <span
-        class="vertical-lines"
-        :style="{
-          color: drawerVisible ? '#8a78d0' : 'white'
-        }"
-        >||</span
-      >
-    </div>
+    <div class="sidebar-container">
+      <div class="sidebar-layout">
+        <div
+          class="icon-expand"
+          :style="{
+            backgroundColor: drawerVisible ? '#44449c' : '#7F70C840',
+            left: drawerVisible ? '18%' : '0'
+          }"
+          @click="toggleDrawer"
+        >
+          <span
+            class="vertical-lines"
+            :style="{
+              color: drawerVisible ? '#8a78d0' : 'white'
+            }"
+            >||</span
+          >
+        </div>
+      </div>
 
-    <transition name="drawer-slide">
-      <div class="main-content" v-if="drawerVisible">
-        <!-- 菜单栏 -->
-        <div class="drawer-box">
-          <el-row class="tac">
-            <el-col :span="12">
-              <span class="mb-2">
-                <img :src="teachingImg" alt="教学" />
-                教学大纲
-              </span>
-              <el-menu
-                default-active="1"
-                :class="{ 'el-menu-vertical-demo': true }"
-              >
-                <el-menu-item
-                  v-for="(title, index) in courseTitles"
-                  :key="index + 1"
-                  :index="(index + 1).toString()"
-                  @click="goToAIExperience(index + 1)"
+      <transition name="drawer-slide">
+        <div class="main-content" v-if="drawerVisible">
+          <!-- 菜单栏 -->
+          <div class="drawer-box">
+            <el-row class="tac">
+              <el-col :span="12">
+                <span class="mb-2">
+                  <img :src="teachingImg" alt="教学" />
+                  教学大纲
+                </span>
+                <el-menu
+                  default-active="1"
+                  :class="{ 'el-menu-vertical-demo': true }"
                 >
-                  0{{ index + 1 }} {{ title }}
-                </el-menu-item>
-              </el-menu>
-            </el-col>
-          </el-row>
+                  <el-menu-item
+                    v-for="(title, index) in courseTitles"
+                    :key="index + 1"
+                    :index="(index + 1).toString()"
+                    @click="goToAIExperience(index + 1)"
+                  >
+                    0{{ index + 1 }} {{ title }}
+                  </el-menu-item>
+                </el-menu>
+              </el-col>
+            </el-row>
+          </div>
         </div>
-      </div>
-    </transition>
+      </transition>
+    </div>
 
     <div class="content-box">
       <div class="box-1">
@@ -106,7 +110,7 @@
           ></div>
           <div class="additional-text">
             0{{ outlineData.ctTypeSort }} {{ outlineData.ctType }}
-          </div> 
+          </div>
         </div>
       </div>
     </div>
@@ -140,8 +144,19 @@ const selectedGrade = ref('')
 // 添加抽屉显示状态
 const drawerVisible = ref(true)
 // 处理下拉菜单选择
-const handleGradeSelect = (command) => {
+const handleGradeSelect = command => {
   selectedGrade.value = command
+  // 保存选中值到localStorage
+  localStorage.setItem('selectedGrade', command)
+  // 年级切换时重新加载数据的逻辑
+  const selectedItem = classData.value.find(item => item.ctType === command)
+  if (selectedItem) {
+    ClassOutline(selectedItem.id).then(res => {
+      if (res.code === 0) {
+        classOutlineData.value = res.data
+      }
+    })
+  }
 }
 // 添加切换抽屉显示状态的函数
 const toggleDrawer = () => {
@@ -154,15 +169,20 @@ const classOutlineData = ref([])
 
 const fetchCtTypes = async () => {
   try {
-    const response = await ClassList() 
-    console.log(response);
+    const response = await ClassList()
+    console.log(response)
     if (response.code === 0) {
       classData.value = response.data
-      // 获取到数据,将第一个选项的值作为默认选中值
-      if (classData.value.length > 0) {
-        selectedGrade.value = classData.value[0].ctType
-        // 初始化时获取课程大纲数据
-        const selectedItem = classData.value[0]
+      // 获取到数据,优先从localStorage读取选中值
+      const savedGrade = localStorage.getItem('selectedGrade')
+      selectedGrade.value =
+        savedGrade ||
+        (classData.value.length > 0 ? classData.value[0].ctType : '')
+      // 初始化时获取课程大纲数据
+      const selectedItem =
+        classData.value.find(item => item.ctType === selectedGrade.value) ||
+        classData.value[0]
+      if (selectedItem) {
         ClassOutline(selectedItem.id).then(res => {
           if (res.code === 0) {
             classOutlineData.value = res.data
@@ -240,7 +260,6 @@ const goToAIExperience = outlineData => {
     }
   }
 }
-
 </script>
 
 <style scoped lang="scss">
@@ -259,6 +278,7 @@ const goToAIExperience = outlineData => {
 .drawer-slide-leave-to {
   transform: translateX(-100%);
   opacity: 0;
+  transition: all 0.3s ease;
 }
 .home-container {
   position: fixed;
@@ -275,13 +295,35 @@ const goToAIExperience = outlineData => {
   ); /* 设置悬停、聚焦、点击状态下的背景色 */
   gap: rpx(0);
 }
+// // 添加新的容器样式
+// .sidebar-wrapper {
+//   display: flex;
+//   align-items: stretch;
+//   transition: all 0.3s ease;
+// }
+// // 折叠状态样式
+// .sidebar-wrapper.collapsed .icon-expand {
+//   border-radius: 0 4px 4px 0;
+// }
+.sidebar-layout {
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+}
+.icon-wrapper {
+  width: 40px; /* 根据实际需要调整宽度 */
+  flex-shrink: 0;
+  background-color: saddlebrown;
+}
 .main-content {
   width: rpx(135);
   height: 100%;
+  flex-grow: 1;
   position: relative;
   background: linear-gradient(to bottom, #001169, #8a78d0);
   overflow-y: auto; /* 添加垂直滚动条 */
   max-height: 100%; /* 设置最大高度 */
+  transition: all 0.3s ease;
 }
 .icon-expand {
   width: rpx(8);
@@ -291,14 +333,14 @@ const goToAIExperience = outlineData => {
   z-index: 9999;
   position: absolute;
   top: 50%;
-  left: 18%;
   transform: translateY(-50%);
-  background-color: #44449c;
   cursor: pointer; // 添加鼠标指针样式
-  clip-path: polygon(0 0, 100% 15%, 100% 85%, 0 100%);
+  // 修改裁剪路径使右侧边缘垂直无缝贴合
+  clip-path: polygon(0 0, 100% 15%, 100% 90%, 0 100%);
   display: flex;
   justify-content: center;
   align-items: center;
+  // 统一过渡时间与菜单保持同步
   transition: all 0.3s ease;
 }
 .icon-expand .vertical-lines {
@@ -451,7 +493,7 @@ const goToAIExperience = outlineData => {
   color: white;
 }
 .dropdown-box {
-  width: 40%;
+  width: 100%;
   height: 100%;
   display: flex; // 添加 flex 布局;
   flex: 1;
@@ -459,7 +501,7 @@ const goToAIExperience = outlineData => {
   // padding-right: rpx(0); // 添加右侧内边距;
 }
 .dropdown-box .el-button {
-  width: rpx(55); // 设置按钮宽度;
+  width: rpx(60); // 设置按钮宽度;
   height: rpx(15); // 设置按钮高度;
   background-color: rgb(255, 255, 255, 0.7);
   border: 1px white solid;
@@ -468,7 +510,7 @@ const goToAIExperience = outlineData => {
   border: none;
   margin-left: rpx(10);
   border-radius: rpx(12);
-  font-size: rpx(7); // 设置字体大小;
+  font-size: rpx(8); // 设置字体大小;
 }
 .dropdown-box .el-button:hover,
 .dropdown-box .el-button:focus,
@@ -478,17 +520,20 @@ const goToAIExperience = outlineData => {
 }
 .dropdown-menu {
   width: rpx(100);
-  height: rpx(50);
+  // height: rpx(60);
   border-radius: rpx(5);
-  background-color: rgba(161, 161, 161, 0.5);
+  border: 1px white solid;
+  background-color: rgb(255, 255, 255, 0.5);
+  backdrop-filter: blur(rpx(5));
   box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
+  margin-left: rpx(40);
 }
 .dropdown-menu ::v-deep(.el-dropdown-menu__item) {
   font-size: rpx(8);
   color: black;
   border-radius: rpx(5);
   width: rpx(78);
-  height: rpx(15);
+  height: rpx(20);
   margin-left: rpx(4);
   margin-bottom: rpx(8);
 }
@@ -581,3 +626,19 @@ const goToAIExperience = outlineData => {
   font-size: rpx(8);
 }
 </style>
+
+<style lang="scss">
+/* 消除小三角 */
+.el-popper__arrow {
+  display: none;
+}
+.el-popper.is-light,
+.el-dropdown__popper.el-popper {
+  background: transparent;
+  border: none;
+  box-shadow: none;
+}
+.el-dropdown__popper {
+  --el-dropdown-menuItem-hover-color: none;
+}
+</style>

+ 2 - 1
src/views/AILaboratory.vue

@@ -20,7 +20,7 @@
     </div>
 
     <!-- 侧边栏组件 -->
-      <LeftPanel ref="leftPanelRef" v-if="drawerVisible"/>
+      <LeftPanel ref="leftPanelRef" v-show="drawerVisible"/>
 
     <!-- 右侧数字人 -->
     <div class="number-people">
@@ -150,6 +150,7 @@ const groupList = ref([
 .drawer-slide-leave-to {
   transform: translateX(-100%);
   opacity: 0;
+  transition: all 0.3s ease;
 }
 
 .home-container {

+ 40 - 12
src/views/AIQuestions.vue

@@ -1,8 +1,8 @@
 <template>
   <!-- 数字人智能问答 -->
   <div class="home-container">
-     <!-- 展开收起侧边栏 -->
-  <div
+    <!-- 展开收起侧边栏 -->
+    <div
       class="icon-expand"
       :style="{
         backgroundColor: drawerVisible ? '#44449c' : '#7F70C840',
@@ -20,7 +20,7 @@
     </div>
 
     <!-- 左侧折叠面板 -->
-    <LeftPanel ref="leftPanelRef"  v-if="drawerVisible"/>
+    <LeftPanel ref="leftPanelRef" v-if="drawerVisible" />
 
     <!-- 原左侧折叠面板和右侧AI问答 -->
     <div class="content-wrapper">
@@ -55,6 +55,11 @@
                 </div>
               </div>
             </div>
+            <!-- 默认消息 -->
+            <!-- <DefaultMessage
+              v-if="showDefaultMessages"
+              @select-message="handleDefaultMessageSelect"
+            /> -->
             <!-- 输入框和发送按钮 -->
             <div class="input-section">
               <input
@@ -92,14 +97,23 @@ import {
   User
 } 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 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)
 
+// 默认消息控制
+// const showDefaultMessages = ref(true)
+// const handleDefaultMessageSelect = message => {
+//   prompt.value = message
+//   handleSendByButton()
+//   showDefaultMessages.value = false
+// }
 
 // 添加抽屉显示状态
 const drawerVisible = ref(true)
@@ -108,14 +122,13 @@ const toggleDrawer = () => {
   drawerVisible.value = !drawerVisible.value
 }
 
-
 // 处理菜单展开和关闭
 const handleOpen = () => {}
 const handleClose = () => {}
 
 // 返回上一页
 const goBack = () => {
-   router.push('/ai-laboratory')
+  router.push('/ai-laboratory')
 }
 const router = useRouter()
 const route = useRoute()
@@ -479,6 +492,7 @@ onMounted(async () => {
   gap: rpx(0);
   background: linear-gradient(to bottom, #e2ddfc, #f1effd);
 }
+
 .icon-expand {
   width: rpx(8);
   height: rpx(35);
@@ -502,7 +516,7 @@ onMounted(async () => {
   font-size: rpx(10);
 }
 .menu-icon {
-  width:rpx(11);
+  width: rpx(11);
   height: rpx(11);
   margin-right: rpx(2);
 }
@@ -514,7 +528,6 @@ onMounted(async () => {
   width: rpx(135);
   height: 100%;
   background: linear-gradient(to bottom, #001169, #8a78d0);
-
 }
 .mb-2 {
   color: black;
@@ -553,7 +566,7 @@ onMounted(async () => {
   font-size: rpx(8);
 }
 .el-menu .el-menu-item.is-active {
-   background: linear-gradient(to bottom, #fee78a, #ffce1b);
+  background: linear-gradient(to bottom, #fee78a, #ffce1b);
   color: black;
   font-size: rpx(8);
   box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
@@ -619,11 +632,26 @@ onMounted(async () => {
   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; // 宽度随文字内容变化

+ 422 - 0
src/views/evaluation/testTopic.vue

@@ -0,0 +1,422 @@
+<template>
+  <!-- 能力测评 -->
+  <div class="home-container">
+    <el-row class="test-paper">
+      <el-col class="answer-sheet" span="12">
+        <el-card style="min-height: 100%" shadow="hover">
+          <h4>序号:</h4>
+          <div class="all-serial-number">
+            <el-button
+              @click="handleLeft(index)"
+              v-for="(item, index) in state.examDetails"
+              :key="index"
+              class="serial-number"
+              size="mini"
+              :class="{ 'yi-zuo-da': item.da.length != 0 }"
+            >
+              {{ index + 1 }}
+            </el-button>
+          </div>
+          <div>
+            <el-button @click="getSubmit" type="primary" plain>提 交</el-button>
+          </div>
+        </el-card>
+      </el-col>
+
+      <el-col class="test-questions" span="12">
+        <el-card shadow="hover">
+          <div
+            @click="getChenge"
+            v-for="(shiTi, index) in state.examDetails"
+            :key="index"
+            :id="'id' + index"
+            class="exam-details"
+            :class="{ 'skip-style': index === state.navgatorIndex }"
+          >
+            <!-- 试题 -->
+            <div class="shi-ti-style">
+              <!-- 题目 -->
+              <div>{{ index + 1 }}、{{ shiTi.timu }}</div>
+              <!-- 选项 -->
+              <div class="xuan-xiang-style">
+                <!-- 单选 -->
+                <el-radio-group
+                  @input="gegegegeg(shiTi, index)"
+                  v-if="shiTi.type == '单选'"
+                  v-model="shiTi.da"
+                >
+                  <el-radio
+                    v-for="(dan, danIdnex) in shiTi.xuanxiang"
+                    :label="danIdnex"
+                    :key="danIdnex"
+                  >
+                    {{ dan }}
+                  </el-radio>
+                </el-radio-group>
+                <!-- 多选 -->
+                <el-checkbox-group
+                  @change="gegegegeg(shiTi, index)"
+                  v-else-if="shiTi.type == '多选'"
+                  v-model="shiTi.da"
+                >
+                  <el-checkbox
+                    v-for="(duo, duoIndex) in shiTi.xuanxiang"
+                    :label="duoIndex"
+                    :key="duoIndex"
+                  >
+                    {{ duo }}
+                  </el-checkbox>
+                </el-checkbox-group>
+                <!-- 问答 -->
+                <div v-else>
+                  <el-input
+                    type="textarea"
+                    :rows="2"
+                    placeholder="请输入内容"
+                    v-model="shiTi.da"
+                  />
+                </div>
+              </div>
+            </div>
+            <!-- 标记 @click="getChangMark(index)"-->
+            <div class="mark-style">
+              <el-tooltip
+                :content="shiTi.mark ? '已标记' : '可标记'"
+                placement="right"
+                effect="light"
+              >
+                <a
+                  @click="getChangMark(index)"
+                  class="el-icon-star-on"
+                  :class="{ mark: shiTi.mark }"
+                />
+              </el-tooltip>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+
+<script setup>
+import { reactive, onMounted, onUnmounted, ref } from 'vue'
+import { ElMessage } from 'element-plus'
+
+// 定义响应式数据
+const state = reactive({
+  examDetails: [
+    {
+      timu: '《山行》是描绘了( C )的景色。',
+      type: '单选',
+      xuanxiang: ['A、春天', 'B、夏天', 'C、秋天', ' D、冬天'],
+      // 答案存放字符串
+      da: '',
+      mark: false
+    },
+    {
+      timu: '劝君更尽一杯酒,西出阳关无故人。”出自( B )的名句。',
+      type: '单选',
+      xuanxiang: ['A、李白', 'B、王维', 'C、王昌龄', 'D、杜牧'],
+      // 答案存放字符串
+      da: '',
+      mark: false
+    },
+    {
+      timu: '把“春风”比作“剪刀”的是哪首诗?( C )',
+      type: '单选',
+      xuanxiang: [
+        'A、《忆江南》',
+        'B、《滁州西涧》',
+        'C、《咏柳》',
+        'D、《游园不值》'
+      ],
+      // 答案存放字符串
+      da: '',
+      mark: false
+    },
+    {
+      timu: '横看成岭侧成峰,远近高低各不一样。”诗中写的名胜是( D )。',
+      type: '单选',
+      xuanxiang: ['A、泰山', 'B、华山', 'C、黄山', 'D、庐山'],
+      // 答案存放字符串
+      da: '',
+      mark: false
+    },
+    {
+      timu: '解落三秋叶,能开二月花。过江千尺浪,入竹万竿斜。”这首诗写的是( B )。',
+      type: '单选',
+      xuanxiang: ['A、花', 'B、风', 'C、竹', 'D、水'],
+      // 答案存放字符串
+      da: '',
+      mark: false
+    },
+    {
+      timu: '以下哪些诗句出自白居易的作品?( A、C、E )',
+      type: '多选',
+      xuanxiang: [
+        'A、春风又绿江南岸',
+        'B、白日依山尽',
+        'C、人生自古谁无死',
+        'D、大漠孤烟直',
+        'E、举杯邀明月',
+        'F、月落乌啼霜满天'
+      ],
+      // 答案存放数组
+      da: [0, 2, 4],
+      mark: false
+    },
+    {
+      timu: '以下哪些是唐代诗人?( A、B、C、E、F )',
+      type: '多选',
+      xuanxiang: [
+        'A、杜甫',
+        'B、苏轼',
+        'C、白居易',
+        'D、李清照',
+        'E、辛弃疾',
+        'F、陆游'
+      ],
+      // 答案存放数组
+      da: [],
+      mark: false
+    },
+    {
+      timu: '以下哪些诗人被称为“唐三百首”?( A、B、C )',
+      type: '多选',
+      xuanxiang: ['A、李白', 'B、杜甫', 'C、白居易', 'D、苏轼', 'E、王之涣'],
+      // 答案存放数组
+      da: [],
+      mark: false
+    },
+    {
+      timu: '以下哪些诗人被称为“宋词四大家”?( A、B、C、D  )',
+      type: '多选',
+      xuanxiang: ['A、李清照', 'B、辛弃疾', 'C、苏轼', 'D、杜牧', 'E、杨万里'],
+      // 答案存放数组
+      da: [],
+      mark: false
+    },
+    // {
+    //   timu: '具有诗仙、诗圣、诗鬼称号的的诗人分别是谁?',
+    //   type: '问答',
+    //   xuanxiang: ['李白 杜甫 李贺'],
+    //   // 答案存放字符串
+    //   da: '',
+    //   mark: false
+    // },
+    // {
+    //   timu: '在苏轼写的《惠崇春江晚景》这首中,让我们明白了有一种味美但内脏有毒的鱼叫什么?',
+    //   type: '问答',
+    //   xuanxiang: ['河豚'],
+    //   // 答案存放字符串
+    //   da: '',
+    //   mark: false
+    // }
+  ],
+  navgatorIndex: null,
+  listBoxState: true,
+  isSkip: true,
+  isShiTi: false,
+  listBox: null
+})
+
+// DOM引用
+const scrollContainer = ref(null)
+let timeId = null
+
+// 页面滚动处理函数
+const handleScroll = () => {
+  clearTimeout(timeId)
+  timeId = setTimeout(() => {
+    scrollToTop()
+    console.log('执行完成')
+  }, 100)
+}
+
+// 点击导航菜单滚动到指定位置
+const handleLeft = index => {
+  state.navgatorIndex = index
+  const element = document.getElementById(`id${index}`)
+  if (element) {
+    element.scrollIntoView({
+      top: '100px',
+      behavior: 'smooth',
+      block: 'center'
+    })
+  }
+  state.listBoxState = false
+  clearTimeout(timeId)
+  timeId = setTimeout(() => {
+    state.listBoxState = true
+  }, 200)
+}
+
+// 监听页面滚动改变导航选中状态
+const scrollToTop = () => {
+  const scrollTop =
+    window.pageYOffset ||
+    document.documentElement.scrollTop ||
+    document.body.scrollTop
+  if (state.listBoxState && state.listBox) {
+    state.listBox.forEach((_, i) => {
+      const element = document.getElementById(`id${i}`)
+      if (element) {
+        const offsetTop = element.offsetTop
+        const scrollHeight = element.scrollHeight
+        if (scrollTop >= offsetTop && scrollTop <= offsetTop + scrollHeight) {
+          state.navgatorIndex = i
+        }
+      }
+    })
+  }
+}
+
+// 标记或收藏题目
+// const getChangMark = num => {
+//   state.examDetails[num].mark = !state.examDetails[num].mark
+// }
+
+const gegegegeg = (val, num) => {
+  console.log(val)
+}
+
+// 取消跳转样式
+const getChenge = () => {
+  state.navgatorIndex = null
+}
+
+// 提交答案
+const getSubmit = () => {
+  console.log(state.examDetails)
+  const list = state.examDetails.map(item => item.da)
+  console.log(list)
+  ElMessage({
+    message: JSON.stringify(list),
+    type: 'success'
+  })
+}
+
+// 生命周期钩子
+onMounted(() => {
+  window.addEventListener('scroll', handleScroll, true)
+})
+
+onUnmounted(() => {
+  window.removeEventListener('scroll', handleScroll, true)
+  clearTimeout(timeId)
+})
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+.home-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(to bottom, #001169, #8a78d0);
+  display: flex;
+  flex-direction: column;
+  gap: rpx(0);
+  font-size: rpx(8);
+}
+.test-paper {
+  width: 100%;
+  height: 100%;
+}
+:deep(.el-card__body) {
+  min-height: 100%;
+}
+.el-card {
+  border: none;
+  // background: linear-gradient(to bottom, #e2ddfc, #f1effd);
+}
+.answer-sheet {
+  width: rpx(140);
+  display: inline-block;
+  margin-right: rpx(5);
+  text-align: left;
+}
+.test-questions {
+  flex: 1;
+  height: 100%;
+  display: inline-block;
+  text-align: left;
+  overflow-y: auto; /* 启用垂直滚动 */
+}
+.test-questions .el-card::-webkit-scrollbar {
+  width: rpx(2);
+}
+.test-questions .el-card::-webkit-scrollbar-thumb {
+  background-color: #ddd;
+  border-radius: 3px;
+}
+</style>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+/* 具体样式:答题卡样式 */
+.all-serial-number {
+}
+.serial-number {
+  margin: rpx(5);
+  width: rpx(15);
+  height: rpx(15);
+  font-size: rpx(8);
+}
+/* 具体样式:试题样式 */
+.exam-details {
+  margin-bottom: rpx(5);
+  padding: rpx(5);
+  border: 1px solid #001169;
+  border-radius: rpx(5);
+  height: rpx(55);
+}
+/* 指定跳转题目样式 */
+.skip-style {
+  border: 1px solid rgb(255, 0, 0);
+}
+.shi-ti-style {
+  display: inline-block;
+  // width: calc(100% - 30px);
+}
+/* 选项样式 */
+.xuan-xiang-style {
+  margin-top: rpx(10);
+}
+/* 标记样式 */
+// .mark-style {
+//   float: right;
+//   text-align: center;
+//   width: 30px;
+// }
+// .el-icon-star-on {
+//   font-size: 25px;
+// }
+// .mark {
+//   color: #f7ba2a;
+// }
+</style>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+/* 联动样式 */
+.yi-zuo-da {
+  color: #fff;
+  background: dodgerblue;
+}
+</style>