|
|
@@ -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 {
|