Переглянути джерело

优化AI生成课页面结构,数据结构

liyanbo 2 місяців тому
батько
коміт
05209f48db

+ 0 - 6
src/router/index.js

@@ -154,11 +154,6 @@ const routes = [
     path: '/experimental-interface',
     component: () => import('../views/laboratory/ExperimentalInterface.vue')
   },
-  // 对话界面
-  {
-    path: '/dialog-interface',
-    component: () => import('../views/video-generation/DialogInterface.vue')
-  }
 ]
 
 // 定义主页及其子路由映射
@@ -183,7 +178,6 @@ const homeRoutes = {
     '/ai-grandcanal',
     '/ai-plantexperts',
     '/virtual-laboratory',
-    '/dialog-interface'
   ]
 }
 

+ 47 - 3
src/views/AIPage/AIDevelop.vue

@@ -163,12 +163,19 @@
                         :cacheDataHistoryKey="course.cacheDataHistoryKey"
                      @saveProgress="handleSaveProgress"></ImageToImage>
 
-          <!--图生视频-->
+          <!--图生视频-->  
           <ImageToVideo class="contentClass" v-if="course.courseContentType === 'aiImageToVideo'" ref="aiImageToVideo"
                         :isCourse="true"
                         :cacheDataKey="course.cacheDataKey"
                         :cacheDataHistoryKey="course.cacheDataHistoryKey"
                      @saveProgress="handleSaveProgress"></ImageToVideo>
+
+          <!-- 对话界面(ailab课程类型) -->
+          <DialogContent 
+            v-if="course.courseContentType === 'ailab'"
+            :scriptRoles="scriptRoles"
+            :scriptData="course.courseContent"
+          />
         </template>
 
         <!-- 视频切换按钮 - 始终显示 -->
@@ -249,7 +256,9 @@ import TextToText from "@/components/ai/text/TextToText.vue";
 import TextToImage from "@/components/ai/image/TextToImage.vue";
 import ImageToImage from "@/components/ai/image/ImageToImage.vue";
 import ImageToVideo from "@/components/ai/video/ImageToVideo.vue";
+import DialogContent from '@/views/AIPage/aiGenerate/DialogContent.vue';
 import {DICT_TYPE} from "@/utils/dictUtils.js";
+import {teacherList} from "@/api/teachers.js";
 
 const router = useRouter() // 获取当前路由对象
 
@@ -290,6 +299,9 @@ const promptPopupVisible = ref(false)
 // 即将播放下一节提示显示状态
 const playPromptVisible = ref(false)
 
+// 脚本角色数据
+const scriptRoles = ref([])
+
 // 保存视频进度接口
 const handleSaveProgress = async (type, progress) => {
 
@@ -355,7 +367,7 @@ const handleSelect = index => {
   if (disableMsg(index)) return
 }
 
-//填充阎村key
+//填充缓存key
 const setCacheDataKey = () => {
   // 设置缓存key
   course.value.cacheDataKey = localStorage.getItem("token") + "_course_" + course.value.id
@@ -644,6 +656,14 @@ onMounted(async () => {
         }
         topName = courseTemp.courseLabel
         courseTemp['key'] = menuIndex
+        //解析AI生成课脚本json数据
+        if (courseTemp.courseContentType === 'ailab') {
+          try {
+            courseTemp.courseContent = JSON.parse(courseTemp.courseContent)
+          } catch (e) {
+            console.error('解析courseContent失败:', e)
+          }
+        }
         videoPathMap.value[menuIndex] = courseTemp
 
         //确定默认课程
@@ -681,8 +701,32 @@ onMounted(async () => {
     acc[item.value] = item.label;
     return acc;
   }, {});
+
+  // 初始化角色列表
+  await getRoleList();
 })
 
+/**
+ * 获取角色列表
+ * @returns {Promise<void>}
+ */
+const getRoleList = async () => {
+  try {
+    let grade = localStorage.getItem('selectedGrade')
+    grade = grade ? grade : '小学低年级'
+    // 获取小学低年级AI数据
+    const listAiRes = await teacherList({ category: grade + 'AI' })
+    const listRes = await teacherList({ category: grade })
+    debugger
+    const listAi = listAiRes.data.list || []
+    const list = listRes.data.list || []
+    listAi.push(...list)
+    scriptRoles.value = listAi
+  } catch (error) {
+    console.error('获取角色数据失败:', error)
+  }
+}
+
 onBeforeUnmount(() => {
   // 组件卸载时的清理
 })
@@ -1556,4 +1600,4 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
   border-radius: rpx(15);
   overflow: auto;
 }
-</style>
+</style>

+ 5 - 31
src/views/AIPage/AIGeneralCourse.vue

@@ -375,37 +375,11 @@ const goBack = () => {
 }
 
 const goToAIExperience = outlineData => {
-  // if (localStorage.getItem('userName') === "aiTest") {
-  //   if (localStorage.getItem('selectedGradeId') !== "1" || outlineData.ctTypeSort !== "02") {
-  //     Message().notifyWarning('此版本未开放,敬请期待!', true)
-  //     return
-  //   }
-  // }
-
-  // 检查是否是实操课的最后四节课
-  // if (showPracticalCourse.value && ClassOutlineScData.value.length > 0) {
-  //   const totalLessons = ClassOutlineScData.value.length;
-  //   const currentIndex = parseInt(outlineData.ctTypeSort);
-  //   // 禁用最后四节课
-  //   if (currentIndex > totalLessons - 4) {
-  //     Message().notifyWarning('此课程暂未开放,敬请期待!', true)
-  //     return
-  //   }
-  // }
-  // 根据课程类型跳转到不同页面
-  if (showAICourse.value) {
-    // AI生成课跳转到对话界面
-    router.push({
-      path: '/dialog-interface',
-      state: { typeId: outlineData.id, typeName: outlineData.ctType, typeSort: outlineData.ctTypeSort }
-    })
-  } else {
-    // 其他课程保持原有的跳转逻辑
-    router.push({
-      path: '/ai-develop', // 跳转视频页面
-      state: { typeId: outlineData.id, typeName: outlineData.ctType, typeSort: outlineData.ctTypeSort }
-    })
-  }
+  // 课程跳转逻辑
+  router.push({
+    path: '/ai-develop', // 跳转视频页面
+    state: { typeId: outlineData.id, typeName: outlineData.ctType, typeSort: outlineData.ctTypeSort }
+  })
 }
 </script>
 

+ 159 - 437
src/views/video-generation/DialogInterface.vue → src/views/AIPage/aiGenerate/DialogContent.vue

@@ -1,68 +1,5 @@
 <template>
-    <!-- 对话界面 -->
-  <div class="dialog-interface">
-    <!-- 展开收起侧边栏 -->
-    <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>
-
-    <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="课程小节图标" />
-              课程小节
-            </span>
-            <el-menu
-              :default-active="currentSectionIndex.toString()"
-              @open="handleOpen"
-              @close="handleClose"
-              @select="handleSelect"
-              :default-openeds="['0']"
-            >
-              <template v-for="(item, index) in menuItems" :key="index">
-                <el-menu-item v-if="!item.children" :index="index.toString()">{{
-                  item.title
-                }}</el-menu-item>
-                <el-sub-menu v-else :index="index.toString()">
-                  <template #title>
-                    <span>{{ item.title }}</span>
-                  </template>
-                  <el-menu-item-group v-if="item.children">
-                    <template v-for="(child, childIndex) in item.children" :key="childIndex">
-                      <el-menu-item :index="`${index}-${childIndex}`"
-                        >•{{ child.title }}</el-menu-item
-                      >
-                    </template>
-                  </el-menu-item-group>
-                </el-sub-menu>
-              </template>
-            </el-menu>
-          </el-col>
-        </el-row>
-      </div>
-    </el-drawer>
-
+  <div class="dialog-content-wrapper">
     <!-- 标题 -->
     <div class="title-box">
       <!-- 返回 -->
@@ -101,7 +38,7 @@
           'left': getCharacterSide(currentDialogue.roleName) === 'left', 
           'right': getCharacterSide(currentDialogue.roleName) === 'right'
         }"
-        :style="{ backgroundImage: `url(${currentDialogue?.url})` }"
+        :style="{ backgroundImage: `url(${getCharacterImage(currentDialogue.roleName)})` }"
       ></div>
       
       <!-- 对话卡片 -->
@@ -110,8 +47,8 @@
         :key="`dialogue-${currentDialogueIndex}`"
         class="dialogue-card" 
         :class="{ 
-          'left': getCharacterSide(currentDialogue.roleId) === 'left', 
-          'right': getCharacterSide(currentDialogue.roleId) === 'right'
+          'left': getCharacterSide(currentDialogue.roleName) === 'left', 
+          'right': getCharacterSide(currentDialogue.roleName) === 'right'
         }"
       >
         <div class="dialogue-header">
@@ -186,7 +123,6 @@
 
     <img :src="currentBackgroundImage" alt="背景图" class="background-image">
   </div>
-  
 </template>
 
 <script setup>
@@ -195,11 +131,10 @@ import { useRouter } from 'vue-router'
 import { ArrowLeftBold, CaretLeft, CaretRight, Grid } from '@element-plus/icons-vue'
 import VoiceInput from '@/components/ai/voice/VoiceInput.vue'
 import MarkdownIt from 'markdown-it'
-import classImages from '@/assets/icon/class.png'
-import { ClassType } from '@/api/class.js'
-import { globalState } from '@/utils/globalState.js'
-import { DICT_TYPE } from '@/utils/dictUtils.js'
+import {CreateDialogue, sendChatMessageStream} from "@/api/questions.js";
+import {useAudioPlayer} from "@/api/tts/useAudioPlayer.js";
 
+const { playAudioChunk , stopPlayback  } = useAudioPlayer();
 
 // 路由实例
 const router = useRouter()
@@ -214,44 +149,10 @@ const props = defineProps({
   },
   scriptRoles: {
     type: Array,
-    default: () => [
-      {
-        id: "牛顿",
-        name: "牛顿",
-        image: "/src/assets/images/number-people03.png"
-      },
-      {
-        id: "小智",
-        name: "小智",
-        image: "/src/assets/images/xiaozhi.png"
-      },
-      {
-        id: "园子",
-        name: "园子",
-        image: "/src/assets/images/number-people02.png"
-      }
-    ]
+    default: () => []
   }
 })
 
-// 侧边栏相关状态
-// 抽屉显示状态
-const drawerVisible = ref(false)
-// 菜单数据
-const menuItems = ref([])
-
-// 课程相关数据
-const courseList = ref([])
-const gradeId = ref('')
-const typeId = ref('')
-const courseId = ref('')
-const menuDict = ref({})
-// 课程内容数据
-const courseContent = ref({
-  title: "",
-  sections: []
-})
-
 // 对话相关状态
 // 当前章节索引
 const currentSectionIndex = ref(0)
@@ -259,13 +160,6 @@ const currentSectionIndex = ref(0)
 const currentDialogueIndex = ref(0)
 // 是否正在播放
 const isPlaying = ref(false)
-
-// 从history.state接收的数据
-const receivedState = ref({
-  typeId: '',
-  typeName: '',
-  typeSort: ''
-})
 // 当前激活的输入模式:'voice' 或 'keyboard'
 const activeInputMode = ref('voice')
 // 输入卡片是否可见
@@ -281,142 +175,10 @@ const backgroundAudio = ref(null)
 // 对话音频
 const dialogueAudio = ref(null)
 
-// 切换抽屉显示状态的函数
-const toggleDrawer = () => {
-  drawerVisible.value = !drawerVisible.value
-}
-
-
-// 菜单打开和关闭的处理函数
-const handleOpen = () => {}
-const handleClose = () => {}
-
-// 菜单选择的处理函数
-const handleSelect = (index) => {
-  // 解析索引(格式:"sectionIndex" 或 "sectionIndex-dialogueIndex")
-  const parts = index.split('-')
-  const sectionIdx = parseInt(parts[0])
-  
-  if (sectionIdx >= 0 && sectionIdx < props.scriptData.sections.length) {
-    // 停止当前音频
-    stopAllAudio()
-    
-    // 切换章节
-    currentSectionIndex.value = sectionIdx
-    
-    // 如果有对话索引,设置对话索引
-    if (parts.length > 1) {
-      const dialogueIdx = parseInt(parts[1])
-      const section = props.scriptData.sections[sectionIdx]
-      if (dialogueIdx >= 0 && dialogueIdx < section.dialogues.length) {
-        currentDialogueIndex.value = dialogueIdx
-      } else {
-        currentDialogueIndex.value = 0
-      }
-    } else {
-      currentDialogueIndex.value = 0
-    }
-    
-    // 播放背景音
-    playBackgroundAudio()
-    // 播放当前对话语音
-    playDialogueAudio()
-    
-    // 关闭抽屉
-    drawerVisible.value = false
-  }
-}
-
-// 构建菜单项
-const buildMenuItems = async () => {
-  // 如果有typeId,调用ClassType接口获取课程数据
-  if (typeId.value) {
-    try {
-      const res = await ClassType(typeId.value)
-      console.log(res)
-      const processedData = res.data.map(course => {
-        if (course.courseConfigList && Array.isArray(course.courseConfigList)) {
-          const validConfigList = course.courseConfigList.filter(config => 
-            config.ccTime !== undefined && config.ccTime !== null && config.ccTime > 0
-          )
-          return {
-            ...course,
-            courseConfigList: validConfigList
-          }
-        }
-        return course
-      })
-      courseList.value = processedData
-      
-      // 构建菜单数据结构
-      let topName = ''
-      courseList.value.forEach((courseTemp, index) => {
-        let menuIndex = courseTemp.courseLabel + '-' + (index + 1)
-        let menu = {
-          key: menuIndex,
-          index: menuIndex,
-          title: courseTemp.courseName
-        }
-
-        if (topName === courseTemp.courseLabel) {
-          let topMenu = menuItems.value[menuItems.value.length - 1]
-          let topMenuChildren = topMenu.children;
-          if (topMenuChildren) {
-            topMenuChildren.push(menu)
-          } else {
-            menu = {
-              key: menuIndex,
-              index: menuIndex,
-              title: menuDict.value[courseTemp.courseLabel],
-              children: [
-                {
-                  key: topMenu.key,
-                  index: topMenu.index,
-                  title: topMenu.title,
-                },
-                {
-                  key: menuIndex,
-                  index: menuIndex,
-                  title: courseTemp.courseName
-                }
-              ]
-            }
-            menuItems.value[menuItems.value.length-1] = menu
-          }
-        }else{
-          menuItems.value.push(menu)
-        }
-        topName = courseTemp.courseLabel
-        
-        // 处理courseContent字段
-        if (courseTemp.courseContent) {
-          try {
-            const content = JSON.parse(courseTemp.courseContent)
-            courseContent.value = content
-          } catch (e) {
-            console.error('解析courseContent失败:', e)
-          }
-        }
-      })
-    } catch (error) {
-      console.error('获取课程数据失败:', error)
-    }
-  }
-}
-
-// 返回主页按钮点击事件
-const goBackToMain = () => {
-  // 停止所有音频
-  stopAllAudio()
-  // 跳转到 ai-general-course 页面
-  router.push('/ai-general-course')
-}
-
 // 计算属性
 // 当前章节信息
 const currentSection = computed(() => {
-  const data = (courseContent.value && courseContent.value.sections && courseContent.value.sections.length > 0) ? courseContent.value : props.scriptData
-  return data.sections[currentSectionIndex.value]
+  return props.scriptData.sections[currentSectionIndex.value]
 })
 // 当前对话信息
 const currentDialogue = computed(() => {
@@ -467,12 +229,13 @@ const submitUserInput = () => {
 }
 
 // 提交语音输入
-const submitVoiceInput = () => {
+const submitVoiceInput = async () => {
   if (voiceInput.value.trim()) {
     console.log('语音输入:', voiceInput.value)
-    // 提交后隐藏输入卡片
-    isInputCardVisible.value = false
-    voiceInput.value = ''
+
+    await createAiChart();
+
+    await doSendMessage();
   }
 }
 
@@ -495,25 +258,24 @@ const parseMarkdown = (content) => {
   return md.render(content)
 }
 
-const getCharacterSide = (roleId) => {
+const getCharacterSide = (roleName) => {
   const sideMap = {
     '牛顿': 'left',
     '小智': 'right',
     '园子': 'right'
   }
-  return sideMap[roleId] || 'left'
+  return sideMap[roleName] || 'left'
   
 }
 
 // 根据角色ID获取角色名称
-const getRoleName = (roleId) => {
-  const role = props.scriptRoles.find(r => r.id === roleId)
-  return role ? role.name : '未知角色'
+const getRole = (roleName) => {
+  return props.scriptRoles.find(r => r.name === roleName)
 }
 
-const getCharacterImage = (roleId) => {
-  const role = props.scriptRoles.find(r => r.id === roleId)
-  return role ? role.image : ''
+const getCharacterImage = (roleName) => {
+  const role = getRole(roleName)
+  return role ? role.avatar : ''
 }
 
 const stopAllAudio = () => {
@@ -661,6 +423,14 @@ const playNext = () => {
   return false
 }
 
+// 返回主页按钮点击事件
+const goBackToMain = () => {
+  // 停止所有音频
+  stopAllAudio()
+  // 跳转到 ai-general-course 页面
+  router.push('/ai-general-course')
+}
+
 // 键盘事件处理,键盘左右箭头控制对话
 const handleKeydown = (event) => {
   if (event.key === 'ArrowLeft') {
@@ -669,6 +439,7 @@ const handleKeydown = (event) => {
     playNext()
   }
 }
+
 // 监听环节变化
 watch(currentSectionIndex, () => {
   playBackgroundAudio()
@@ -681,42 +452,140 @@ watch(currentDialogue, () => {
   }
 })
 
-// 组件挂载时添加键盘事件监听和构建菜单
-onMounted(() => {
-  window.addEventListener('keydown', handleKeydown)
-  // 接收AIGeneralCourse传递的数据
-  const state = window.history.state
-  if (state) {
-    receivedState.value = {
-      typeId: state.typeId || '',
-      typeName: state.typeName || '',
-      typeSort: state.typeSort || ''
-    }
-    typeId.value = state.typeId || ''
-    console.log('接收到的数据:', receivedState.value)
+// 会话ID
+const activeConversationId = ref(null)
+const conversationInProgress = ref(false)
+const conversationInAbortController = ref()
+const receiveMessageFullText = ref('')
+
+//创建对话
+const createAiChart = async () => {
+  // let role = props.scriptRoles.find(r => r.id === "roleName")
+  // 智能问答
+  await CreateDialogue({ roleId: 54 })
+      .then(res => {
+        console.log("创建会话:", res.data);
+        activeConversationId.value = res.data
+      })
+      .catch(error => {
+        console.error('请求出错:', error)
+      })
+}
+
+/** 真正执行【发送】消息操作 */
+const doSendMessage = async () => {
+  // 校验
+  if (voiceInput.value.length < 1) {
+    console.error('发送失败,原因:内容为空!')
+    return
   }
-  
-  // 初始化年级ID
-  gradeId.value = globalState.initGradeId()
-  
-  // 课程小节字典
-  let menuDictStr = localStorage.getItem(DICT_TYPE.BJDX_COURSE_LABEL);
-  let menuDictJson = menuDictStr ? JSON.parse(menuDictStr) : [];
-  menuDict.value = menuDictJson.reduce((acc, item) => {
-    acc[item.value] = item.label;
-    return acc;
-  }, {});
-  
-  // 构建菜单项
-  buildMenuItems()
-})
 
+  if (activeConversationId.value == null) {
+    console.error('还没创建对话,不能发送!')
+    return
+  }
+
+  // 清空输入框
+  voiceInput.value = ''
+
+  // 执行发送
+  await doSendMessageStream({
+    conversationId: activeConversationId.value,
+    content: voiceInput.value,
+    contentAnswer: null,
+  })
+}
+
+/** 真正执行【发送】消息操作 */
+const doSendMessageStream = async userMessage => {
+  // 创建 AbortController 实例,以便中止请求
+  conversationInAbortController.value = new AbortController()
+  // 标记对话进行中
+  conversationInProgress.value = true
+  // 设置为空
+  receiveMessageFullText.value = ''
+
+  try {
+
+    // 发送 event stream
+    let isFirstChunk = true // 是否是第一个 chunk 消息段
+    await sendChatMessageStream(
+        userMessage.conversationId,
+        userMessage.content,
+        userMessage.contentAnswer,
+        conversationInAbortController.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
+              //第一次返回
+            } 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 () => {
+  // 如果 stream 进行中的 message,就需要调用 controller 结束
+  if (conversationInAbortController.value) {
+    conversationInAbortController.value.abort()
+  }
+  // 销毁语音读取
+  // stopPlayback();
+  // 设置为 false
+  conversationInProgress.value = false
 
+  console.log(`结束对话!更改状态: `,conversationInProgress.value)
+}
+
+// 组件挂载时添加键盘事件监听
+onMounted(() => {
+  window.addEventListener('keydown', handleKeydown)
+  // 播放背景音
+  playBackgroundAudio()
+  // 播放当前对话语音
+  playDialogueAudio()
+})
 
 // 组件卸载时移除键盘事件监听和停止音频
 onUnmounted(() => {
   window.removeEventListener('keydown', handleKeydown)
   stopAllAudio()
+  stopPlayback()
 })
 </script>
 
@@ -727,160 +596,13 @@ onUnmounted(() => {
   @return math.div($px, 750) * 100vw;
 }
 
-.dialog-interface {
+.dialog-content-wrapper {
   width: 100%;
   height: 100%;
-  position: fixed;
+  position: absolute;
   top: 0;
   left: 0;
-  right: 0;
-  bottom: 0;
-}
-
-/* 侧边栏展开收起图标 */
-.icon-expand {
-  width: rpx(8);
-  height: rpx(35);
-  border-top-right-radius: rpx(5);
-  border-bottom-right-radius: rpx(5);
-  z-index: 9999;
-  position: absolute;
-  top: 50%;
-  transform: translateY(-50%);
-  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);
-}
-
-/* 抽屉样式 */
-.dialog-interface ::v-deep(.el-drawer__body) {
-  width: rpx(135);
-  height: 100%;
-  position: relative;
-  background: linear-gradient(to bottom, #001169, #8a78d0);
-}
-
-.drawer-box {
-  position: absolute;
-  display: flex;
-  align-items: center;
-  height: 100%;
-  width: 80%;
-}
-
-.el-row {
-  margin: auto;
-  margin-top: rpx(20);
-}
-
-.tac ::v-deep(.el-menu) {
-  background-color: transparent;
-  border: none;
-  width: 100%;
-  margin-top: rpx(8);
-}
-
-/* 取消点击后的蓝色字体 */
-.tac ::v-deep(.el-menu-item.is-active),
-.tac ::v-deep(.el-sub-menu__title.is-active) {
-  color: white;
-}
-
-// 取消鼠标悬浮颜色
-.tac ::v-deep(.el-sub-menu__title) {
-  width: rpx(130);
-  height: rpx(20);
-  margin-bottom: rpx(5);
-  border-radius: rpx(6);
-  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),
-.el-menu ::v-deep(.el-sub-menu__title:active) {
-  background: linear-gradient(to bottom, #fee78a, #ffce1b);
-  background-color: transparent;
-}
-
-// 添加二级标题折叠图标样式
-::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;
-}
-
-.el-menu ::v-deep(.el-icon svg) {
-  font-size: rpx(9);
-}
-
-.mb-2 {
-  color: white;
-  font-size: rpx(9);
-}
-
-.mb-2 img {
-  width: rpx(10);
-  height: rpx(10);
-  vertical-align: middle;
-  margin-top: rpx(-2);
-}
-
-.el-menu-item {
-  width: rpx(100);
-  height: rpx(20);
-  margin-bottom: rpx(5);
-  border-radius: rpx(6);
-  color: white;
-  font-size: rpx(8);
-}
-
-.el-menu ::v-deep(.el-sub-menu__title) {
-  color: white;
-  width: rpx(100);
-  height: rpx(20);
-  margin-bottom: rpx(5);
-  font-size: rpx(8);
-}
-
-.el-menu ::v-deep(.el-sub-menu__title:hover),
-.el-menu ::v-deep(.el-sub-menu__title:focus),
-.el-menu ::v-deep(.el-sub-menu__title:active) {
-  background: linear-gradient(to bottom, #fee78a, #ffce1b);
-  color: black;
-}
-
-.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, #fee78a, #ffce1b);
-  color: black;
-  font-size: rpx(8);
-  box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
-}
-
-.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);
+  z-index: 100;
 }
 
 .title-box {