Ver código fonte

Merge branch 'wanzi'

# Conflicts:
#	src/views/block/MapGame.vue
liyanbo 5 meses atrás
pai
commit
dc4be4f58f

+ 1 - 1
.env

@@ -5,7 +5,7 @@ VITE_APP_TITLE=AI课程网
 # VITE_BASE_URL='http://59.110.91.129:8088/admin-api'
 # VITE_BASE_URL='https://learn-ai.com.cn/admin-api'
 VITE_BASE_URL='http://192.168.110.8:8080/admin-api'
-
+    
 # 默认账户密码
 VITE_APP_DEFAULT_LOGIN_TENANT =
 VITE_APP_DEFAULT_LOGIN_USERNAME =

+ 27 - 0
src/api/programming/index.js

@@ -0,0 +1,27 @@
+import axios from '@/utils/request'
+
+
+// 获取课程主题
+export function CategoryList (data) {
+  return axios({
+    url: 'bjdxWeb/blockly/getTypeTheme',
+    method: 'get',
+    data
+  })
+}
+
+// 获取课程主题列表
+export function TopicList (id) {
+  return axios({
+    url: 'bjdxWeb/blockly/getTypeByThemeId?id=' + id,
+    method: 'get'
+  })
+}
+
+// 根据ID获取列表信息
+export function TopicType (typeId) {
+  return axios({
+    url: 'bjdxWeb/blockly/getBlocklyByTypeId?typeId=' + typeId,
+    method: 'get' 
+  })
+}

+ 5 - 0
src/router/index.js

@@ -105,6 +105,11 @@ const routes = [
     path: '/programmingcourset', 
     component: () => import('../views/programming/ProgrammingCourset.vue')
   },
+  // 编程课视频
+  {
+    path: '/programmingvideo', 
+    component: () => import('../views/programming/ProgrammingVideo.vue')
+  },
 ]
 const router = createRouter({
   history: createWebHistory(),

+ 2 - 2
src/views/block/MapGame.vue

@@ -297,7 +297,6 @@ onMounted(async () => {
   await fetchGameData();
   // 初始化可行走点集合
   initWalkablePointsSet();
-
   // 注册自定义积木
   registerCustomBlocks();
   // 注册JavaScript生成器
@@ -441,7 +440,7 @@ const playerStyle = computed(() => ({
 
 // 计算携带物品样式
 function getCarriedItemStyle(index, item) {
-  const baseSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.3;
+  const baseSize = tileSize.value * CONFIG.STYLES.PLAYER_SIZE_RATIO * 0.5;
 
   return {
     position: 'relative',
@@ -1596,6 +1595,7 @@ onUnmounted(() => {
   bottom: 0;
   background: transparent;
   overflow-y: auto;
+  background-color: rgba(255, 255, 255);
 }
 
 /* 自定义滚动条样式 */

+ 84 - 26
src/views/programming/ProgrammingCourset.vue

@@ -33,6 +33,7 @@
           v-for="item in courseItems" 
           :key="item.id"
           :class="item.positionClass"
+          @click="handleCourseItemClick(item)"
         >
           <div class="box-content">
             <img :src="item.image" :alt="item.title" class="box-image" />
@@ -41,6 +42,13 @@
         </div>
       </div>
     </div>
+    
+    <!-- 编程视频界面 -->
+    <ProgrammingVideo v-if="showVideo" />
+    
+    <!-- 编程游戏界面 -->
+    <MapGame v-if="showGame" />
+
   </div>
 </template>
 
@@ -50,43 +58,33 @@ import { ArrowLeftBold } from '@element-plus/icons-vue';
 import { ref, onMounted } from 'vue';
 // 导入路由
 import { useRouter, useRoute } from 'vue-router';
-
+// 导入接口
+import { TopicType } from '@/api/programming/index.js'
 // 导入图片
 import  explanation  from '@/assets/programming/explanation.png'
 import  practice  from '@/assets/programming/practice.png'
 import  summary  from '@/assets/programming/summary.png'
+// 编程视频界面
+import ProgrammingVideo from '../programming/ProgrammingVideo.vue'
+// 编程游戏界面
+import MapGame from '../block/MapGame.vue'
 
 // 获取路由实例
 const router = useRouter()
 const route = useRoute()
 // 页面标题
 const pageTitle = ref('')
-// 返回编程课列表
-const goBackIndex = () => {
-  router.push('/programminglist')
-}
-
+// 保存原始的课程ID和标题
+const originalCourseId = ref('')
+const originalCourseTitle = ref('')
+// 主题ID(从ProgrammingList页面传递过来)
+const topicId = ref('')
+// 控制视频界面显示
+const showVideo = ref(false) 
+ // 控制游戏界面显示
+const showGame = ref(false)
 // 动态课程项数据
-const courseItems = ref([
-  {
-    id: 1,
-    title: '知识讲解',
-    image: explanation,
-    positionClass: 'left-content-box'
-  },
-  {
-    id: 2,
-    title: '实操练习',
-    image: practice,
-    positionClass: 'center-content-box'
-  },
-  {
-    id: 3,
-    title: '课程小结',
-    image: summary,
-    positionClass: 'right-content-box'
-  }
-])
+const courseItems = ref([])
 
 // 组件挂载时获取路由参数设置标题
 onMounted(() => {
@@ -96,7 +94,67 @@ onMounted(() => {
     // 设置页面标题
     pageTitle.value = courseTitle
   }
+  // 获取当前主题ID
+  topicId.value = route.query.topicId
+  
+  // 保存原始的课程ID和标题,返回时使用
+  originalCourseId.value = route.query.originalCourseId
+  originalCourseTitle.value = route.query.originalCourseTitle
+  
+  // 获取到topicId后,再调用TopicType接口
+  if (topicId.value) {
+    TopicType(topicId.value).then(res => {
+      console.log(topicId.value, res);
+      if (res && res.data && Array.isArray(res.data)) {
+        // 创建映射,保持原有的图片和位置样式
+        const positionMap = {
+          1: { image: explanation, positionClass: 'left-content-box' },
+          2: { image: practice, positionClass: 'center-content-box' },
+          3: { image: summary, positionClass: 'right-content-box' }
+        }
+        // 遍历接口返回的数据,设置课程项
+        res.data.forEach((item, index) => {
+          // 每个课程项都对应位置样式和图片
+          const positionIndex = (index % 3) + 1
+          const positionInfo = positionMap[positionIndex]
+          courseItems.value.push({
+            id: item.id,
+            title: item.bcName,
+            image: positionInfo.image,
+            positionClass: positionInfo.positionClass,
+            contentType: item.bcContentType
+          })
+        })
+      }
+    })
+  }
 })
+
+// 处理课程项点击事件
+const handleCourseItemClick = (item) => {
+  if (item.contentType === 'video') {
+    showVideo.value = true
+    showGame.value = false
+  } else if (item.contentType === 'blockly') {
+    showVideo.value = false
+    showGame.value = true
+  }
+}
+
+// 返回编程课列表
+const goBackIndex = () => {
+  // 隐藏视频和游戏界面
+  showVideo.value = false
+  showGame.value = false
+  // 返回时携带原始的课程参数,使用categoryId保持与ProgrammingList.vue中参数名一致
+  router.push({
+    path: '/programminglist',
+    query: {
+      categoryId: originalCourseId.value,
+      courseTitle: originalCourseTitle.value
+    }
+  })
+}
 </script>
 
 <style scoped lang="scss">

+ 39 - 35
src/views/programming/ProgrammingGame.vue

@@ -66,46 +66,38 @@ import { ref, reactive, watch, onMounted, onUnmounted } from 'vue'
 import { ArrowLeftBold } from '@element-plus/icons-vue';
 // 导入路由
 import { useRouter } from 'vue-router';
+// 导入课程类别接口
+import { CategoryList } from '@/api/programming/index.js'
 
 // 创建路由实例
 const router = useRouter()
-
-// 跳转到编程课程列表页
-const goToProgrammingList = (course, index) => {
-  // 跳转到programminglist页面,并传递课程信息作为参数
-  router.push({
-    path: '/programminglist',
-    query: {
-      courseIndex: index,
-      courseTitle: course.title
-    }
-  })
-}
-
-// 导入背景图片
-import bg01 from '@/assets/programming/bg01.png'
-import bg02 from '@/assets/programming/bg02.png'
-import bg03 from '@/assets/programming/bg03.png'
-import bg04 from '@/assets/programming/bg04.png'
-import bg05 from '@/assets/programming/bg05.png'
-import bg06 from '@/assets/programming/bg06.png'
-
-
-// 定义课程类别数据
-const courseCategories = reactive([
-  { title: '初始编程', bgImage: bg01 },
-  { title: '顺序编程', bgImage: bg02 },
-  { title: '编程挑战', bgImage: bg03 },
-  { title: '参数编程', bgImage: bg04 },
-  { title: '循环编程', bgImage: bg05 },
-  { title: '条件编程', bgImage: bg06 }
-])
-
 // 当前激活的按钮索引
 const activeButton = ref(0)
-
-// 定义圆形按钮数据(根据课程类别数量动态生成)
-const circleButtons = reactive(courseCategories.map((_, index) => ({ text: String(index + 1) })))
+// 定义圆形按钮数据
+const circleButtons = reactive([])
+// 定义课程类别数据
+const courseCategories = reactive([])
+
+// 获取课程类别
+CategoryList().then(res => {
+  console.log(res);
+  if (res && res.data) {
+    // 清空现有数据
+    courseCategories.length = 0;
+    res.data.forEach(item => {
+      courseCategories.push({
+        id: item.id,
+        title: item.ctType,
+        bgImage: item.ctTypeImage
+      });
+    });
+    // 更新圆形按钮数据
+    circleButtons.length = 0;
+    courseCategories.forEach((_, index) => {
+      circleButtons.push({ text: String(index + 1) });
+    });
+  }
+})
 
 // 自动滚动到中间位置
 watch(activeButton, (newIndex) => {
@@ -164,6 +156,18 @@ const middleBox = ref(null)
 
 
 
+// 跳转到编程课程列表页
+const goToProgrammingList = (course, index) => {
+  router.push({
+    path: '/programminglist',
+    query: {
+      courseIndex: index,
+      courseTitle: course.title,
+      categoryId: course.id
+    }
+  })
+}
+
 </script>
 
 <style scoped lang="scss">

+ 67 - 39
src/views/programming/ProgrammingList.vue

@@ -71,59 +71,66 @@ import { ref, reactive, watch, onMounted, onUnmounted } from 'vue'
 import { ArrowLeftBold } from '@element-plus/icons-vue';
 // 导入路由
 import { useRouter, useRoute } from 'vue-router';
+// 导入主题列表接口
+import { TopicList } from '@/api/programming/index.js'
+// 导入图片
+import unlockImage from '@/assets/programming/unlock.png'
 
 // 获取路由实例
 const router = useRouter()
 // 获取当前路由信息
 const route = useRoute()
-// 页面标题,默认为'AI编程课'
-const pageTitle = ref('AI编程课')
+// 页面标题
+const pageTitle = ref('')
+// 课程类别ID
+const categoryId = ref('')
 
 // 返回上一页
 const goBackIndex = () => {
-  // 导航到ProgrammingGame页面
   router.push('/programming02')
 }
 
-// 跳转到课程详情页面
-const goToProgrammingList = (course, index) => {
-  // 跳转ProgrammingCourset页面,并传递课程信息作为参数
-  router.push({
-    path: '/programmingCourset',
-    query: {
-      courseTitle: course.title,
-      courseIndex: index
-    }
-  })
-}
-
-
-// 导入背景图片
-import list_img01 from '@/assets/programming/list_img01.png'
-import list_img02 from '@/assets/programming/list_img02.png'
-import list_img03 from '@/assets/programming/list_img03.png'
-import unlockImage from '@/assets/programming/unlock.png'
-
-
 // 定义课程数据
-const specificCourses = reactive([
-  { title: '汽车世界', bgImage: list_img01 },
-  { title: '汽车制造', bgImage: list_img02 },
-  { title: '红绿灯', bgImage: list_img03 },
-  { title: '汽车世界', bgImage: list_img01 },
-  { title: '汽车制造', bgImage: list_img02 },
-  { title: '红绿灯', bgImage: list_img03 },
-])
+const specificCourses = reactive([])
 
 // 当前激活的按钮索引
-const activeButton = ref(0)
+const activeButton = ref(-1)
+
+// 在获取到categoryId后再调用TopicList接口
+const fetchTopicList = () => {
+  if (categoryId.value) {
+    TopicList(categoryId.value).then(res => {
+      console.log(categoryId.value, res);
+      // 更新课程数据,使用接口返回的数据
+      if (res && res.data && Array.isArray(res.data)) {
+        // 清空原有数据
+        specificCourses.splice(0, specificCourses.length);
+        res.data.forEach(item => {
+          specificCourses.push({
+            id: item.id,
+            title: item.ctType,
+            bgImage: item.ctTypeImage
+          });
+        });
+        // 更新圆形按钮数据
+        circleButtons.splice(0, circleButtons.length);
+        specificCourses.forEach((_, index) => {
+          circleButtons.push({ text: String(index + 1) });
+        });
+        // 重置激活按钮索引,但不默认选中第一项
+        activeButton.value = -1;
+      }
+    })
+  }
+}
+
 
 // 定义圆形按钮数据(根据课程数量动态生成)
 const circleButtons = reactive(specificCourses.map((_, index) => ({ text: String(index + 1) })))
 
 // 自动滚动到中间位置
 watch(activeButton, (newIndex) => {
-  if (middleBox.value) {
+  if (middleBox.value && newIndex !== -1) {
     // 找到对应的课程卡片元素
     const courseElement = middleBox.value.querySelector(`.middle-inner-box:nth-child(${newIndex + 1})`);
     if (courseElement) {
@@ -176,17 +183,38 @@ const handleMouseLeave = () => {
 // 获取middleBox元素的引用
 const middleBox = ref(null)
 
-// 组件挂载时获取路由参数设置标题
+// 组件挂载时获取路由参数设置标题和课程ID
 onMounted(() => {
-  // 检查路由参数中是否有courseTitle
-  const courseTitle = route.query.courseTitle
-  if (courseTitle) {
-    // 如果有courseTitle参数,则设置页面标题
-    pageTitle.value = courseTitle
+  const title = route.query.courseTitle
+  if (title) {
+    pageTitle.value = title
+  }
+  
+  const id = route.query.categoryId
+  if (id) {
+    categoryId.value = id
+    // 获取到categoryId后调用TopicList接口
+    fetchTopicList()
   }
 })
 
 
+// 跳转到课程详情页面
+const goToProgrammingList = (course, index) => {
+  // 设置当前选中项
+  activeButton.value = index;
+  // 跳转ProgrammingCourset页面,并传递课程信息作为参数
+  router.push({
+    path: '/programmingCourset',
+    query: {
+      courseTitle: course.title,
+      courseIndex: index,
+      topicId: course.id, // 当前主题的id,避免与课程ID混淆
+      originalCourseId: categoryId.value, // 原始的课程ID
+      originalCourseTitle: pageTitle.value // 原始的课程标题
+    }
+  })
+}
 
 </script>
 

+ 1462 - 0
src/views/programming/ProgrammingVideo.vue

@@ -0,0 +1,1462 @@
+<template>
+  <!-- 编程课程视频页面 -->
+  <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>
+
+    <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="course.key"
+              @open="handleOpen"
+              @close="handleClose"
+              @select="handleSelect"
+              :default-openeds="['3','5']"
+            >
+              <template v-for="item in menuItems" :key="item.key">
+                <el-menu-item v-if="!item.children" :index="item.key">{{
+                  item.title
+                }}</el-menu-item>
+                <el-sub-menu v-else :index="item.key">
+                  <template #title>
+                    <span>{{ item.title }}</span>
+                  </template>
+                  <el-menu-item-group v-if="item.children">
+                    <template v-for="child in item.children" :key="child.key">
+                      <el-menu-item :index="child.key"
+                        >•{{ 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="content-box">
+      <div class="box-1">
+        <div class="inner-box left-box">
+          <div class="box-icon" @click="goBack">
+            <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
+            {{ boxIconTitle }}
+          </div>
+        </div>
+        <div class="inner-box right-box">
+          <div class="top-right-box">
+            <el-autocomplete
+              v-model="SearchInput"
+              :fetch-suggestions="querySearch"
+              placeholder="搜索"
+              @select="handleSearchSelect"
+              class="search-input"
+              value-key="title"
+              :trigger-on-focus="false"
+            >
+              <template #prefix>
+                <el-icon class="el-input__icon"><search /></el-icon>
+              </template>
+              <template #popper-append-to-body>
+                <el-option
+                  class="scrollbar"
+                  v-for="item in filteredTitles"
+                  :key="item.key"
+                  :label="item.title"
+                  :value="item"
+                ></el-option>
+              </template>
+            </el-autocomplete>
+          </div>
+        </div>
+      </div>
+
+      <div class="box-2">
+        <!-- 课程标题 -->
+        <div class="small-title">
+          <span>{{ course.courseName }}</span>
+        </div>
+
+        <el-empty v-if="isDisabled"
+                  image-size="500"
+          description="您无权查看该课程!"
+          :image="isDisabledImage"
+        />
+
+        <template v-else>
+          <!-- 视频组件 -->
+          <VideoPlayer
+              v-if="course.courseContentType === 'video'"
+              :contentType="course.courseContentType"
+              :videoPath="course.courseVideoPath"
+              :courseId="course.id || ''"
+              :typeId="typeId"
+              :courseConfigList="course.courseConfigList || []"
+              :allIndices="flattenMenuItems()"
+              :currentIndex="course.key || ''"
+              @timeUpdate="handleVideoTimeUpdate"
+              @videoEnded="handleVideoEnded"
+              @switchVideo="handleSelect"
+          />
+          <!-- 图片 -->
+          <ImageView v-if="course.courseContentType === 'image'" :imagePath="course.courseImagePath" altText="课程图片"></ImageView>
+
+          <!-- PPT -->
+          <PptView v-if="course.courseContentType === 'ppt'" :pptPath="course.pptPath" ref="pptRef"></PptView>
+
+          <!--文生文-->
+          <TextToText class="contentClass" v-if="course.courseContentType === 'aiTextToText'" ref="aiTextToText"></TextToText>
+
+          <!--文生图-->
+          <TextToImage class="contentClass" v-if="course.courseContentType === 'aiTextToImage'" ref="aiTextToImage"></TextToImage>
+
+          <!--图生图-->
+          <ImageToImage class="contentClass" v-if="course.courseContentType === 'aiImageToImage'" ref="aiImageToImage"></ImageToImage>
+
+          <!--图生视频-->
+          <ImageToVideo class="contentClass" v-if="course.courseContentType === 'aiImageToVideo'" ref="aiImageToVideo"></ImageToVideo>
+        </template>
+
+        <!-- 视频切换按钮 - 始终显示 -->
+        <div class="video-switch">
+          <div class="caret-left" @click="playPreviousVideo">
+            <el-button type="warning" round>
+              <img :src="leftImg" alt="Left" />上一节</el-button
+            >
+          </div>
+          <div class="caret-right" @click="playNextVideo">
+            <el-button type="warning" round
+              >下一节<img :src="rightImg" alt="Right" />
+            </el-button>
+          </div>
+        </div>
+
+      </div>
+    </div>
+
+    <!-- 弹框组件 -->
+    <DialogComponents
+      :questionDialogVisible="questionDialogVisible"
+      :currentQuestion="courseConfig"
+      :gradeId="gradeId"
+      :typeId="typeId"
+      :courseId="course.id || ''" 
+      @closeQuestionDialog="closeQuestionDialog"
+      @submitAnswer="handleSubmitAnswer"
+    />
+
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { Expand, Fold, Memo } from '@element-plus/icons-vue'
+import { Search, ArrowLeftBold } from '@element-plus/icons-vue'
+import { ElMessage, ElMessageBox, ElNotification, valueEquals } from 'element-plus'
+import isDisabledImage from '@/assets/images/permission/isDisabled.png'
+
+import classImages from '@/assets/icon/class.png'
+import { ClassType } from '@/api/class.js'
+import { Message } from '@/utils/message/Message.js'
+import { saveRecord } from '@/api/personalized/index.js'
+
+// 导入全局状态
+import { globalState } from '@/utils/globalState.js'
+
+// 导入图标
+import leftImg from '@/assets/icon/backward.png'
+import rightImg from '@/assets/icon/f-backward.png'
+
+// 导入新创建的组件
+import VideoPlayer from '@/components/videopage/VideoPlayer.vue'
+import DialogComponents from '@/components/videopage/DialogComponents.vue'
+import PptView from "@/components/PPT/PptView.vue";
+import ImageView from '@/components/Image/ImageView.vue'
+
+// AI实验室
+import TextToText from "@/components/ai/text/TextToText.vue";
+import TextToImage from "@/components/ai/image/TextToImage.vue";
+import ImageToImage from "@/components/ai/image/ImageToImage.vue";
+import ImageToVideo from "@/components/ai/video/ImageToVideo.vue";
+
+const router = useRouter() // 获取当前路由对象
+const route = useRoute()
+
+// 添加按钮显示状态
+const buttonVisible = ref(false)
+// 添加抽屉显示状态
+const drawerVisible = ref(false)
+// 渲染页面标题
+const boxIconTitle = ref('')
+// 课程集合数据
+const courseList = ref([])
+//当前课程
+const course = ref({})
+// 菜单数据
+const menuItems = ref([])
+// 课程集合数据
+const videoPathMap = ref({})
+// 已观看课程ID列表
+const watchedCourseIds = ref([])
+// 试题弹框显示状态
+const questionDialogVisible = ref(false)
+// 当前显示的试题
+const courseConfig = ref({})
+// 搜索框
+const SearchInput = ref('')
+// 年级id
+const gradeId = ref('')
+// 课程大纲id
+const typeId = ref('')
+// 课程小节id
+const courseId = ref('')
+// 课程排序
+const typeSort = ref('')
+
+// 课程权限
+const courseDataScope = ref([])
+// 测试账号禁用视频
+const isDisabled = ref(false)
+
+
+//课程小节字典
+const menuDict = ref({
+  1: '课前回顾',
+  2: '课程引入',
+  3: '知识讲解',
+  4: '趣味实操',
+  5: '课程总结'
+})
+
+// 切换抽屉显示状态的函数
+const toggleDrawer = () => {
+  drawerVisible.value = !drawerVisible.value
+}
+
+// 返回上一页
+const goBack = () => {
+  router.go(-1)
+}
+
+// 菜单打开和关闭的处理函数
+const handleOpen = () => {}
+const handleClose = () => {}
+
+// 菜单选择的处理函数
+const handleSelect = index => {
+
+  // 根据索引切换视频
+  if (videoPathMap.value[index]) {
+    course.value = videoPathMap.value[index]
+    courseId.value = course.value.id
+    console.log("课程id:",courseId.value)
+    // 切换标题后,关闭抽屉
+    drawerVisible.value = false
+  } else {
+    //视频不存在
+    Message().notifyWarning('视频不存在!', true)
+  }
+
+  //测试账号禁用视频
+  if (disableVideo(index)) return
+}
+
+// 展平所有菜单项索引
+const flattenMenuItems = () => {
+  const indices = []
+  const traverse = items => {
+    for (const item of items) {
+      if (!item.children) {
+        indices.push(item.key)
+      } else {
+        traverse(item.children)
+      }
+    }
+  }
+  traverse(menuItems.value)
+  return indices
+}
+
+// 播放上一个视频
+const playPreviousVideo = () => {
+  const allIndices = flattenMenuItems()
+  const currentIndexInList = allIndices.indexOf(course.value.key)
+  if (currentIndexInList > 0) {
+    const previousIndex = allIndices[currentIndexInList - 1]
+    handleSelect(previousIndex)
+  }
+}
+
+// 播放下一个视频
+const playNextVideo = () => {
+  const allIndices = flattenMenuItems()
+  const currentIndexInList = allIndices.indexOf(course.value.key)
+  if (currentIndexInList !== -1 && currentIndexInList < allIndices.length - 1) {
+    const nextIndex = allIndices[currentIndexInList + 1]
+    handleSelect(nextIndex)
+  }
+}
+
+
+// 播放下一个视频
+const handleVideoEnded = () => {
+  // 记录当前视频ID为已观看
+  if (
+    course.value &&
+    course.value.id &&
+    !watchedCourseIds.value.includes(course.value.id)
+  ) {
+    watchedCourseIds.value.push(course.value.id)
+    localStorage.setItem(
+      'watchedCourseIds',
+      JSON.stringify(watchedCourseIds.value)
+    )
+  }
+
+  // 如果是图片类型,不需要自动播放下一个
+  if (course.value.courseContentType === 'video') {
+    const allIndices = flattenMenuItems()
+    const currentIndexInList = allIndices.indexOf(course.value.key)
+    if (currentIndexInList !== -1 && currentIndexInList < allIndices.length - 1) {
+      const nextIndex = allIndices[currentIndexInList + 1]
+      handleSelect(nextIndex)
+    }
+  }
+
+  const allIndices = flattenMenuItems()
+  const currentIndexInList = allIndices.indexOf(course.value.key)
+  if (currentIndexInList !== -1 && currentIndexInList < allIndices.length - 1) {
+    const nextIndex = allIndices[currentIndexInList + 1]
+    handleSelect(nextIndex)
+  }
+}
+
+// 禁用视频
+const disableVideo = (index = course.value.key) => {
+
+  // 未配置课程权限,不禁用视频
+  if (!courseDataScope.value || courseDataScope.value.length === 0) {
+    return false
+  }
+
+  //配置了课程权限,且视频id不在权限列表中
+  isDisabled.value = !courseDataScope.value.some(item => Number(item) === videoPathMap.value[index].id)
+  if (isDisabled.value) {
+    Message().notifyWarning('您的账号并未开放此课程!', true)
+    return isDisabled.value;
+  }
+
+  return isDisabled.value;
+}
+
+// 处理视频时间更新事件
+const handleVideoTimeUpdate = ({ currentTime, progressPercentage, courseConfig: config }) => {
+  if (config) {
+    questionDialogVisible.value = true
+    courseConfig.value = config
+    // 保存试题进度
+    const saveQuestProgress = async () =>{
+      try {
+        // 确保courseId已经设置
+        if (!courseId.value && typeId.value) {
+          const courseRes = await ClassType(typeId.value)
+          if (courseRes.data && courseRes.data.length > 0) {
+            courseId.value = course.value && course.value.id ? course.value.id : courseRes.data[0].id
+          }
+        }
+        if (config.id) {
+          // 保存弹窗问题进度
+          await saveRecord({
+            brpNjId: gradeId.value,
+            brpCtId: typeId.value,
+            brpCourseConfigId: config.id,
+            brpCourseId: courseId.value,
+            brpType: 'courseQuest',
+            brpProgress: progressPercentage
+          })
+        } else {
+          console.error('无法保存试题进度: 试题id不存在')
+        }
+      }catch(error){
+        console.error(`保存试题进度失败:`, error)
+      }
+    }
+    // 调用异步函数
+    saveQuestProgress()
+  }
+}
+
+// 关闭试题弹框
+const closeQuestionDialog = () => {
+  questionDialogVisible.value = false
+}
+
+// 提交答案
+const handleSubmitAnswer = ({ selectedOption }) => {
+  questionDialogVisible.value = false
+}
+
+// 搜索
+const querySearch = (queryString, cb) => {
+  const sections = getAllCourseSections()
+  const results = queryString
+    ? sections.filter(section =>
+        section.title.toLowerCase().includes(queryString.toLowerCase())
+      )
+    : sections
+  cb(results)
+}
+
+const filteredTitles = computed(() => {
+  const sections = getAllCourseSections()
+  if (!SearchInput.value) {
+    return sections
+  }
+  return sections.filter(section =>
+    section.title.toLowerCase().includes(SearchInput.value.toLowerCase())
+  )
+})
+
+const handleSearchSelect = item => {
+  handleSelect(item.key)
+  // 清空输入框
+  SearchInput.value = ''
+}
+
+// 课程小节数据提取
+const getAllCourseSections = () => {
+  let sections = []
+  const traverse = items => {
+    items.forEach(item => {
+      if (item.children) {
+        traverse(item.children)
+      } else {
+        sections.push({ title: item.title, key: item.key })
+      }
+    })
+  }
+  traverse(menuItems.value)
+  return sections
+}
+
+// 渲染 课程数据结构 以及 视频
+onMounted(async () => {
+
+  if (localStorage.getItem("courseDataScope")) {
+    courseDataScope.value = localStorage.getItem("courseDataScope").split(",");
+  }
+
+   const typeIdParam = router.currentRoute.value.query.typeId
+  if (typeIdParam) {
+    typeId.value = typeIdParam
+    try {
+      // 取接口课程数据
+      const res = await ClassType(typeIdParam)
+      // 对返回的课程数据进行处理,确保ccTime为有效秒数
+      const processedData = res.data.map(course => {
+        // 检查并处理courseConfigList
+        if (course.courseConfigList && Array.isArray(course.courseConfigList)) {
+          // 过滤掉ccTime为0的配置项
+          const validConfigList = course.courseConfigList.filter(config => 
+            config.ccTime !== undefined && config.ccTime !== null && config.ccTime > 0
+          )
+          return {
+            ...course,
+            courseConfigList: validConfigList
+          }
+        }
+        return course
+      })
+      courseList.value = processedData
+      // 初始化已观看课程ID
+      const savedWatchedIds = localStorage.getItem('watchedCourseIds')
+      if (savedWatchedIds) {
+        watchedCourseIds.value = JSON.parse(savedWatchedIds)
+      }
+      //课程数据
+      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
+        courseTemp['key'] = menuIndex
+        videoPathMap.value[menuIndex] = courseTemp
+
+        //确定默认课程
+        if (index === 0) {
+          courseId.value = courseTemp.id
+          if(!disableVideo(menuIndex)){
+            course.value = courseTemp
+          }else{
+            course.value.key = courseTemp.key
+            course.value.courseName = courseTemp.courseName
+          }
+        }
+      })
+
+    } catch (error) {
+      console.error('获取课程数据失败:', error)
+    }
+  }
+
+  const title = router.currentRoute.value.query.typeName
+  if (title) {
+    boxIconTitle.value = String(title)
+  }
+
+  typeSort.value = router.currentRoute.value.query.typeSort
+  // 初始化年级ID
+  gradeId.value = globalState.initGradeId()
+
+})
+
+onBeforeUnmount(() => {
+  // 组件卸载时的清理
+})
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+@use 'sass:color'; // 引入 color 模块
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+
+// 定义儿童风格的蓝紫色调
+$primary-color: rgba(106, 90, 205, 0.52); // 主色调:蓝紫色
+$secondary-color: rgba(147, 112, 219, 0.66); // 辅助色:亮蓝紫色
+$accent-color: rgb(133, 89, 220); // 强调色:暗蓝紫色
+$light-color: #ffffff; // 浅色背景:淡紫色
+$text-color: #483d8b; // 文本颜色:靛蓝色
+
+
+// 视频切换按钮样式
+.video-switch {
+  width: 100%;
+  display: flex;
+  margin-top: rpx(5);
+  margin-bottom: rpx(15);
+  justify-content: center;
+}
+
+.caret-right,
+.caret-left {
+  width: rpx(50);
+   margin: 0 rpx(20);
+  margin: auto;
+  display: flex;
+  justify-content: center;
+}
+
+.caret-left ::v-deep(.el-button.is-round),
+.caret-right ::v-deep(.el-button.is-round) {
+  width: rpx(50);
+  height: rpx(15);
+  color: white;
+  font-size: rpx(7);
+  border-radius: none;
+  border: 1px white solid;
+  background-color: rgb(255, 255, 255, 0.5);
+  box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
+}
+
+.caret-right img,
+.caret-left img {
+  width: rpx(12);
+}
+
+.default-messages {
+  margin-top: rpx(-10);
+  margin-bottom: rpx(5);
+}
+
+/* 添加过渡样式 */
+.drawer-slide-enter-active,
+.drawer-slide-leave-active {
+  transition: all 0.3s ease;
+}
+.drawer-slide-enter-from,
+.drawer-slide-leave-to {
+  transform: translateX(-100%);
+  opacity: 0;
+}
+.home-container ::v-deep(.el-drawer__body) {
+  width: rpx(135);
+  height: 100%;
+  position: relative;
+  background: linear-gradient(to bottom, #001169, #8a78d0);
+}
+.content-box {
+  flex: 1;
+  height: 100%;
+  display: flex;
+  flex-direction: column; /* 子元素上下排列 */
+  background: linear-gradient(to bottom, #001169, #8a78d0);
+}
+.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);
+}
+.home-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(to bottom, #001169, #b4a8e1);
+  display: flex;
+}
+.el-row {
+  margin: auto;
+  margin-top: rpx(20);
+}
+.tac ::v-deep(.el-menu) {
+  background-color: transparent;
+  border: none;
+  width: 100%;
+  margin-top: rpx(8);
+}
+
+/* 取消点击后的蓝色字体 el-sub-menu__title*/
+.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);
+  // 添加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),
+.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);
+}
+.drawer-box {
+  position: absolute;
+  display: flex;
+  align-items: center;
+  height: 100%;
+  width: 80%;
+}
+.drawer-box .toggle-button {
+  width: rpx(10);
+  height: rpx(50);
+  font-size: rpx(7);
+  background-color: rgba(17, 23, 29, 0.2);
+  border: none;
+  position: relative;
+  writing-mode: vertical-lr; // 文字垂直排列,从左到右
+  text-orientation: upright; // 文字保持正立
+}
+.toggle-button:hover {
+  left: 0;
+}
+
+.toggle-button.is-active,
+.toggle-button:active,
+.toggle-button:focus {
+  background-color: rgba(165, 209, 247, 0.8);
+  color: white;
+  border: none; // 移除点击时的边框
+  outline: none; // 移除点击时的外边框
+}
+
+.box-1 {
+  width: 100%;
+  // height: rpx(50);
+  margin-top: rpx(10);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-sizing: border-box;
+  font-size: rpx(15); // 默认字体大小
+}
+
+.inner-box {
+  height: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: rpx(16); // 默认字体大小
+}
+
+.left-box {
+  position: relative;
+  justify-content: flex-start;
+  align-items: flex-start;
+  flex: 1; // 设置左侧盒子占比为 2
+  cursor: pointer; // 添加鼠标指针样式
+}
+.box-icon {
+  width: 100%;
+  height: 100%;
+  flex: 1;
+  display: flex; // 添加 flex 布局
+  align-items: center; // 垂直居中
+  color: white; // 设置图标颜色为白色
+  padding-left: rpx(15);
+  font-size: rpx(10); // 设置图标大小,可按需调整
+}
+.box-icon .left-icon {
+  margin-left: rpx(10);
+  margin-right: rpx(5); // 设置图标和文字之间的间距 ;
+}
+
+.left-box span {
+  position: absolute;
+  font-size: rpx(11); // 默认字体大小
+  color: white;
+}
+
+.right-box {
+  flex: 1;
+  position: relative; // 添加相对定位;
+}
+.top-right-box {
+  position: absolute; // 添加绝对定位
+  // margin-top: rpx(9); // 调整上边距离
+  margin-left: rpx(260); // 调整右边距离
+  width: rpx(100); // 设置盒子宽度,可按需调整
+  // height: 60px; // 设置盒子高度,可按需调整
+  margin-right: rpx(20);
+  display: flex;
+  justify-content: flex-end;
+}
+.top-right-box {
+  ::v-deep(.el-input__wrapper) {
+    height: rpx(15);
+    font-size: rpx(6);
+    background-color: rgb(255, 255, 255, 0.5);
+    border-radius: rpx(12);
+    border: white 1px solid;
+    color: white;
+  }
+  ::v-deep(.el-input__icon) {
+    color: white; // 设置输入框图标颜色为白色
+  }
+  // 添加占位符样式
+  ::v-deep(.el-input__inner::placeholder) {
+    color: white;
+  }
+  // 添加输入框文字颜色样式
+  ::v-deep(.el-input__inner) {
+    color: black;
+  }
+}
+// 搜索框
+.search-input {
+  width: rpx(100);
+  height: rpx(15);
+  font-size: rpx(7);
+}
+.box-2 {
+  width: 100%;
+  flex: 1;
+  box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
+  box-sizing: border-box;
+  // display: flex; // 确保子元素水平排列
+  flex-wrap: wrap; // 允许子元素换行;
+  align-content: center; // 顶部对齐;
+  cursor: pointer; // 添加鼠标指针样式
+}
+
+.small-title {
+  width: 100%;
+  // margin-top: rpx(-20);
+  height: rpx(20);
+  color: white;
+  font-size: rpx(10);
+  justify-content: center; //使子元素水平居中
+}
+// 图片容器样式
+.image-container {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  // padding: rpx(20) 0;
+}
+
+// 图片样式
+.course-image {
+  max-width: 70%;
+  max-height: rpx(400);
+  object-fit: contain;
+  border-radius: rpx(12);
+  box-shadow: 0 rpx(10) rpx(20) rgba(0, 0, 0, 0.1);
+}
+
+
+// 儿童风格试题弹框样式
+.child-dialog {
+  .el-dialog__header {
+    display: none; // 隐藏原有的标题栏
+  }
+
+  .el-dialog__body {
+    padding: rpx(20);
+    position: relative;
+  }
+
+  .el-dialog__footer {
+    border-top: none;
+    padding: rpx(10) rpx(20);
+    text-align: center;
+    margin-top: auto; // 使底部按钮位于底部
+  }
+
+  .el-dialog__wrapper {
+    // 修改半透明背景色
+    background-color: rgba(0, 0, 0, 0.6);
+  }
+
+  .el-dialog {
+    border: none;
+    border-radius: rpx(20);
+    background: linear-gradient(
+      135deg,
+      $light-color,
+      #d8bfd8
+    ); // 柔和的蓝紫色渐变
+    overflow: hidden;
+    display: flex; // 添加 flex 布局
+    flex-direction: column; // 设置垂直布局
+    min-height: 0; // 防止子元素溢出
+    // 添加装饰元素
+    &::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: rpx(10);
+      background: linear-gradient(90deg, $secondary-color, $accent-color);
+    }
+  }
+}
+
+// 问题标题样式
+.question-title {
+  padding: rpx(15);
+  border-radius: rpx(12);
+  margin-bottom: rpx(20);
+  color: #483d8b;
+  font-weight: bold;
+  font-size: rpx(12);
+  position: relative;
+  display: flex;
+  // align-items: center;
+
+  .question-icon {
+    background-color: $accent-color;
+    color: white;
+    width: rpx(24);
+    height: rpx(24);
+    border-radius: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: rpx(10);
+    font-weight: bold;
+    box-shadow: 0 rpx(2) rpx(5) rgba($accent-color, 0.3);
+  }
+}
+
+// 选项容器样式
+.options-container {
+  margin-bottom: rpx(20);
+}
+
+// 问题选项样式
+.question-option {
+  margin: rpx(8) 0;
+  padding: rpx(10) rpx(15);
+  border-radius: rpx(12);
+  border: rpx(1) solid rgba($primary-color, 0.3);
+  transition: all 0.3s ease;
+  display: flex;
+  align-items: center;
+  background-color: white;
+  box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.05);
+
+  ::v-deep(.el-radio__label) {
+    color: $text-color;
+    margin-left: rpx(8);
+    flex: 1;
+    text-align: left;
+    // 增大字体大小
+    font-size: rpx(12);
+  }
+
+  // 选中时的样式变化
+  .el-radio__input.is-checked + .el-radio__label {
+    font-weight: bold;
+    color: $accent-color;
+  }
+
+  &:hover {
+    background-color: rgba($primary-color, 0.05);
+    border-color: rgba($primary-color, 0.5);
+    transform: translateY(-rpx(1));
+  }
+}
+
+// 暂无选项样式
+.no-options {
+  color: rgba($text-color, 0.7);
+  text-align: center;
+  padding: rpx(20);
+  font-size: rpx(12);
+}
+
+// 底部按钮样式
+.child-button {
+  min-width: rpx(80);
+  height: rpx(30);
+  border-radius: rpx(8);
+  font-size: rpx(12);
+  font-weight: 500;
+  transition: all 0.3s ease;
+  box-shadow: 0 rpx(2) rpx(8) rgba(0, 0, 0, 0.1);
+
+  &.confirm {
+    background: linear-gradient(to bottom, #ab81ff, #8559dc);
+    border: none;
+    border-right: 15px;
+    color: white;
+
+    &:hover {
+      background: linear-gradient(
+        to bottom,
+        color.adjust(#ab81ff, $lightness: -5%),
+        color.adjust(#8559dc, $lightness: -5%)
+      );
+      transform: translateY(-rpx(1));
+      color: white;
+    }
+  }
+
+  &.cancel {
+    background: white;
+    border: rpx(1) solid rgba($primary-color, 0.3);
+    color: $text-color;
+
+    &:hover {
+      background: rgba($primary-color, 0.05);
+      border-color: rgba($primary-color, 0.5);
+      transform: translateY(-rpx(1));
+    }
+  }
+}
+
+// AI对话图标样式
+.ai-icon-container {
+  position: absolute;
+  bottom: rpx(20);
+  right: rpx(20);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  cursor: pointer;
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-rpx(2));
+  }
+
+  .ai-icon {
+    width: rpx(30);
+    height: rpx(30);
+    margin-bottom: rpx(0);
+    filter: drop-shadow(0 rpx(2) rpx(4) rgba($primary-color, 0.3));
+    // 添加过渡动画
+    transition: transform 0.3s ease;
+  }
+
+  // 悬浮时放大效果
+  .ai-icon:hover {
+    transform: scale(1.5);
+  }
+
+  .ai-text {
+    color: $text-color;
+    font-size: rpx(8);
+    background-color: rgba(255, 255, 255, 0.7);
+    padding: rpx(2) rpx(5);
+    border-radius: rpx(5);
+  }
+}
+
+// AI消息样式
+.ai-message {
+  display: flex;
+  align-items: flex-start;
+  margin-bottom: rpx(15);
+
+  .ai-avatar {
+    width: rpx(30);
+    height: rpx(30);
+    border-radius: 50%;
+    margin-right: rpx(10);
+    background-color: $primary-color;
+    padding: rpx(5);
+    // box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.2);
+  }
+
+  .ai-text-content {
+    background-color: $light-color;
+    padding: rpx(8) rpx(12);
+    border-radius: rpx(10);
+    font-size: rpx(10);
+    color: $text-color;
+    max-width: 80%;
+    // box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
+  }
+}
+
+// 用户输入框样式
+.user-input {
+  ::v-deep(.el-input__wrapper) {
+    height: rpx(23);
+    border-top-left-radius: rpx(5);
+    border-bottom-left-radius: rpx(5);
+    border-color: rgba($primary-color, 0.3);
+
+    &:focus-within {
+      box-shadow: 0 0 0 rpx(1) rgba($primary-color, 0.5);
+    }
+  }
+
+  ::v-deep(.el-input__inner) {
+    font-size: rpx(10);
+    // color: $text-color;
+    text-indent: 1em;
+  }
+  ::v-deep(.el-input-group__append, .el-input-group__prepend) {
+    background: linear-gradient(to bottom, #ab81ff, #8559dc);
+    border-top-right-radius: rpx(5);
+    border-bottom-right-radius: rpx(5);
+    color: white;
+    font-size: rpx(9);
+  }
+}
+/* 定义淡入和缩放动画 */
+.fade-scale-enter-active,
+.fade-scale-leave-active {
+  transition: all 0.5s ease;
+}
+
+.fade-scale-enter-from,
+.fade-scale-leave-to {
+  opacity: 0.1;
+  transform: scale(0.9);
+}
+
+// 自定义试题弹框背景
+.child-dialog-wrapper {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(0, 0, 0, 0.6); // 半透明背景
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.child-dialog {
+  border: none;
+  border-radius: rpx(15);
+  background: rgb(255, 255, 255, 0.8); // 柔和的蓝紫色渐变
+  overflow: hidden;
+  padding: rpx(5);
+  // width: 40%;
+  // height: 60%;
+  position: relative;
+}
+
+// AI对话弹框样式
+.ai-dialog-wrapper {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  z-index: 1001;
+  pointer-events: none;
+}
+
+.ai-dialog {
+  border: none;
+  border-radius: rpx(15);
+  background: rgb(255, 255, 255, 0.8);
+  overflow: hidden;
+  padding: rpx(20);
+  width: 30%;
+  // 增加高度
+  height: 80%;
+  margin-right: rpx(50);
+  pointer-events: auto;
+  position: relative;
+  display: flex;
+  flex-direction: column;
+
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: rpx(10);
+  }
+}
+
+.ai-dialog-header {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: rpx(15);
+  margin-top: rpx(-10);
+
+  img {
+    width: rpx(15);
+  }
+
+  h3 {
+    color: black;
+    font-size: rpx(12);
+  }
+
+  .close-btn {
+    padding: 0;
+    width: rpx(20);
+    height: rpx(20);
+    font-size: rpx(16);
+    line-height: 1;
+    position: absolute;
+    background-color: transparent;
+    border: none;
+    margin-left: rpx(200);
+  }
+}
+
+.ai-dialog-content {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.ai-message-history {
+  flex: 1;
+  // 当内容超出容器高度时,显示垂直滚动条
+  overflow-y: auto;
+  margin-bottom: rpx(15);
+  // 可以根据实际情况调整最大高度
+  font-size: rpx(5);
+  max-height: 50vh;
+  // padding: 5px 10px;
+
+  .message {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: rpx(10);
+
+    &.user {
+      flex-direction: row-reverse;
+    }
+
+    .avatar {
+      width: rpx(30);
+      height: rpx(30);
+      border-radius: 50%;
+      margin: 0 rpx(10);
+    }
+
+    .user {
+      width: 15px;
+      height: 15px;
+    }
+
+    .message-content {
+      background-color: #ffffff;
+      font-size: rpx(5);
+      max-width: 80%;
+      color: black;
+      box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
+    }
+  }
+  // 滚动条整体样式
+  &::-webkit-scrollbar {
+    width: rpx(4); // 滚动条宽度
+  }
+
+  // 滚动条滑块样式
+  &::-webkit-scrollbar-thumb {
+    background-color: $primary-color; // 滑块颜色
+    border-radius: rpx(4); // 滑块圆角
+    transition: background-color 0.3s ease; // 颜色过渡效果
+
+    &:hover {
+      //background-color: darken($primary-color, 10%); // 悬停时滑块颜色加深
+    }
+  }
+
+  // 滚动条轨道样式
+  &::-webkit-scrollbar-track {
+    background-color: rgba($primary-color, 0.2); // 轨道颜色
+    border-radius: rpx(4); // 轨道圆角
+  }
+
+  .message {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: rpx(10);
+
+    &.user {
+      flex-direction: row-reverse;
+    }
+
+    .avatar {
+      width: rpx(30);
+      height: rpx(30);
+      border-radius: 50%;
+      margin: 0 rpx(10);
+    }
+
+    .message-content {
+      background-color: white;
+      padding: rpx(8) rpx(12);
+      border-radius: rpx(5);
+      font-size: rpx(8);
+      color: black;
+      max-width: 80%;
+      box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
+    }
+  }
+}
+
+// 优化发送按钮样式
+.send-button {
+  //background: linear-gradient(90deg, $primary-color, $secondary-color);
+  border: none;
+  color: white;
+
+  &:hover {
+    //background: linear-gradient(90deg, darken($primary-color, 5%), darken($secondary-color, 5%));
+  }
+}
+</style>
+
+<style lang="scss">
+// 搜索下拉框样式
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+/* 消除小三角 */
+.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;
+}
+.el-autocomplete-suggestion .el-scrollbar__wrap {
+  margin: 0 auto;
+  background-color: rgba(255, 255, 255, 0.7);
+  border: 2px solid white;
+  border-radius: rpx(5);
+  backdrop-filter: blur(rpx(5));
+}
+.el-autocomplete-suggestion li {
+  color: black;
+  font-size: rpx(7);
+  padding: rpx(5) rpx(8); // 调整下拉项内边距
+}
+.el-autocomplete-suggestion li:hover {
+  background: linear-gradient(to bottom, #ffefb0, #ffcc00);
+}
+</style>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+.default-messages {
+  margin-top: rpx(-10);
+  margin-bottom: rpx(5);
+}
+
+
+.contentClass{
+  width: 70%;
+  height: 80%;
+  margin: 0 auto;
+  border-radius: rpx(15);
+  overflow: hidden;
+}
+</style>