Pārlūkot izejas kodu

Merge branch 'master' into muzi

liyanbo 1 mēnesi atpakaļ
vecāks
revīzija
b6e39be32e

+ 182 - 5
src/App.vue

@@ -1,18 +1,198 @@
 <template>
   <!-- 移动端横屏提示遮罩(全局统一) -->
-  <div class="mobile-landscape-hint mobile-show">
+  <div class="mobile-landscape-hint" :class="{ 'mobile-show': showLandscapeHint }">
     <div class="hint-icon">📱</div>
     <div class="hint-text">请横屏使用</div>
     <div class="hint-desc">为获得更好的体验,请将设备横屏放置</div>
+    <button class="hint-close-btn" @click="closeLandscapeHint">知道了({{ countdown }}秒)</button>
   </div>
   <!-- 路由视图 -->
   <router-view></router-view>
 </template>
 
 <script setup>
+import { ref, onMounted, onUnmounted, watch } from 'vue'
+import { useRoute } from 'vue-router'
+
+const route = useRoute()
+const showLandscapeHint = ref(false)
+const countdown = ref(3) // 倒计时初始值
+let countdownTimer = null
+let hasShownOnce = false // 用于跟踪当前会话中是否已经显示过提示
+
+// 检测是否为移动设备
+const isMobileDevice = () => {
+  return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
+         (window.innerWidth <= 768 && window.innerHeight <= 1024)
+}
+
+// 检测设备是否为横屏
+const isLandscape = () => {
+  return window.innerWidth > window.innerHeight
+}
+
+// 检查当前路由是否为排除页面
+const isExcludedRoute = () => {
+  return route.path.includes('/parent-mobile-course') ||
+         route.path.includes('/parent-mobile-course-detail') ||
+         route.path.includes('/login') ||
+         route.path.includes('/ai-develop') ||
+         route.path.includes('/ai-general-course')
+}
+
+// 关闭横屏提示
+const closeLandscapeHint = () => {
+  showLandscapeHint.value = false
+  hasShownOnce = true // 标记本次会话中已显示过提示
+  // 清理倒计时定时器
+  if (countdownTimer) {
+    clearInterval(countdownTimer)
+    countdownTimer = null
+  }
+}
+
+// 开始倒计时
+const startCountdown = () => {
+  // 重置倒计时
+  countdown.value = 3
+  
+  // 清理之前的定时器
+  if (countdownTimer) {
+    clearInterval(countdownTimer)
+  }
+  
+  // 开始新的倒计时
+  countdownTimer = setInterval(() => {
+    countdown.value--
+    if (countdown.value <= 0) {
+      closeLandscapeHint()
+    }
+  }, 1000)
+}
+
+// 检查是否应该显示横屏提示
+const checkLandscapeHint = () => {
+  // 对于排除的路由,始终不显示横屏提示
+  if (isExcludedRoute()) {
+    showLandscapeHint.value = false
+    return
+  }
+  
+  if (isMobileDevice() && !isLandscape() && !isExcludedRoute() && !hasShownOnce) {
+    // 如果是移动设备、竖屏、不在排除路由中,且本次会话中未显示过提示,则显示横屏提示
+    showLandscapeHint.value = true
+    // 开始倒计时
+    startCountdown()
+  }
+}
+
+// 监听窗口大小变化(仅处理屏幕旋转)
+const handleResize = () => {
+  // 如果是由于旋转导致的横屏,则关闭提示
+  if (isLandscape()) {
+    closeLandscapeHint()
+  } else {
+    // 如果变为竖屏,且不在排除路由中,且本次会话中未显示过提示,则检查是否显示提示
+    if (!isExcludedRoute() && isMobileDevice() && !hasShownOnce) {
+      showLandscapeHint.value = true
+      startCountdown()
+    }
+  }
+}
+
+// 监听路由变化
+watch(() => route.path, () => {
+  // 如果进入排除路由,立即关闭提示
+  if (isExcludedRoute()) {
+    showLandscapeHint.value = false
+    // 清理可能存在的倒计时
+    if (countdownTimer) {
+      clearInterval(countdownTimer)
+      countdownTimer = null
+    }
+  }
+  // 每次路由变化时都检查是否应该显示横屏提示
+  checkLandscapeHint()
+}, { immediate: true })
+
+// 组件挂载时更新状态
+onMounted(() => {
+  checkLandscapeHint() // 页面加载时检查
+  window.addEventListener('resize', handleResize)
+  
+  // 监听设备方向变化(如果支持的话)
+  if (window.screen && window.screen.orientation) {
+    window.screen.orientation.addEventListener('change', handleResize)
+  }
+})
+
+// 组件卸载时移除事件监听
+onUnmounted(() => {
+  window.removeEventListener('resize', handleResize)
+  if (window.screen && window.screen.orientation) {
+    window.screen.orientation.removeEventListener('change', handleResize)
+  }
+  // 清理定时器
+  if (countdownTimer) {
+    clearInterval(countdownTimer)
+  }
+})
 </script>
 
 <style scoped>
+/* 移动端横屏提示遮罩 */
+.mobile-landscape-hint {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.9);
+  display: none;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  z-index: 9999;
+  color: white;
+  text-align: center;
+}
+
+.mobile-landscape-hint.mobile-show {
+  display: flex;
+}
+
+.hint-icon {
+  font-size: 60px;
+  margin-bottom: 20px;
+}
+
+.hint-text {
+  font-size: 24px;
+  margin-bottom: 10px;
+  font-weight: bold;
+}
+
+.hint-desc {
+  font-size: 16px;
+  opacity: 0.8;
+  margin-bottom: 15px;
+}
+
+.hint-close-btn {
+  background-color: #409eff;
+  color: white;
+  border: none;
+  padding: 8px 16px;
+  border-radius: 4px;
+  cursor: pointer;
+  font-size: 16px;
+  margin-top: 10px;
+}
+
+.hint-close-btn:hover {
+  background-color: #66b1ff;
+}
+
 /* 移动端样式 - 屏幕宽度小于 768px */
 @media (max-width: 768px) {
   router-view {
@@ -31,10 +211,7 @@
   padding: 0;
 }
 html,
-body,
-.common-layout,
-.el-container,
-#app {
+body{
   width: 100vw;
   height: 100vh;
   overflow: hidden;

+ 2 - 0
src/components/videopage/VideoPlayer.vue

@@ -286,11 +286,13 @@ const closeHistoryTip = () => {
 // 处理视频暂停事件
 const handleVideoPause = () => {
   isVideoPaused.value = true
+  emits('videoPause')  // 触发暂停事件
 }
 
 // 处理视频播放事件
 const handleVideoPlay = () => {
   isVideoPaused.value = false
+  emits('videoPlay')  // 触发播放事件
 }
 
 // 处理视频点击事件

+ 21 - 1
src/router/index.js

@@ -7,6 +7,11 @@ import {refreshAllDictData} from "@/utils/dictUtils.js";
 const routes = [
   { path: '/', component: () => import('../views/Login.vue') },
   { path: '/login', component: () => import('../views/Login.vue') },
+  { 
+    path: '/login-mobile', 
+    meta: { defaultQuery: { loginPath: '/parent-mobile-course' } },
+    component: () => import('../views/Login.vue') 
+  },
 
   // 免登录
   { path: '/quick-login', component: () => import('../views/QuickLogin.vue') },
@@ -105,11 +110,23 @@ const routes = [
     path: '/ai-questions',
     component: () => import('../views/AIPage/AIQuestions.vue')
   },
-  // 发展历程
+  // 通识课
   {
     path: '/ai-develop',
     component: () => import('../views/AIPage/AIDevelop.vue')
   },
+
+  // 家长课堂-手机端课程列表
+  {
+    path: '/parent-mobile-course',
+    component: () => import('../views/ParentMobile/ParentMobileCourse.vue')
+  },
+  // 家长课堂-手机端课程列表详情
+  {
+    path: '/parent-mobile-course-detail',
+    component: () => import('../views/ParentMobile/ParentMobileCourseDetail.vue')
+  },
+
   // 虚拟实验室
   {
     path: '/virtual-laboratory',
@@ -194,6 +211,8 @@ const homeRoutes = {
     '/smartHome',
     '/smartDeskLamp',
     '/ai-poetry',
+    '/parent-mobile-course-detail',
+    '/parent-mobile-course',
   ]
 }
 
@@ -244,6 +263,7 @@ router.beforeEach(async (to, from, next) => {
   // 注册页面始终允许访问,不需要登录状态
   if ( ['/register-login',
     '/reg',
+    '/login-mobile',
     '/quick-login',
     '/promotion-login'].includes(to.path)) {
     next()

+ 138 - 0
src/styles/parentMobileCourse.css

@@ -0,0 +1,138 @@
+/* ============================================ */
+/* 家长课堂-手机端课程列表样式 */
+/* 包含电脑端和移动端适配 */
+/* ============================================ */
+
+/* -------------------------- */
+/* 默认状态(电脑端) */
+/* -------------------------- */
+
+/* 默认隐藏移动端容器 */
+.parent-mobile-container {
+  display: none !important;
+}
+
+/* 默认显示电脑端课程项 */
+.parent-mobile-course-item {
+  display: flex !important;
+}
+
+/* -------------------------- */
+/* 移动端样式(≤768px) */
+/* -------------------------- */
+
+@media screen and (max-width: 768px) {
+  /* 隐藏电脑端课程项 */
+  .parent-mobile-course-item {
+    display: none !important;
+  }
+  
+  /* 显示移动端容器 */
+  .parent-mobile-container {
+    display: block !important;
+    width: 100%;
+    max-width: 100vw; /* 最大宽度不超过视口宽度 */
+    padding: 8px;
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+  
+  /* 移动端课程项 */
+  .parent-mobile-item {
+    display: flex;
+    align-items: center;
+    background: #fff;
+    border-radius: 12px;
+    padding: 10px;
+    margin-bottom: 10px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+    cursor: pointer;
+    transition: box-shadow 0.2s ease;
+    box-sizing: border-box;
+    overflow: hidden;
+    width: 100%;
+    max-width: 100%;
+  }
+  
+  .parent-mobile-item:active {
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
+  }
+  
+  /* 左侧图片 */
+  .parent-mobile-image {
+    width: 80px;
+    height: 60px;
+    border-radius: 8px;
+    overflow: hidden;
+    flex-shrink: 0;
+  }
+  
+  .parent-mobile-image img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+  
+  /* 中间内容 */
+  .parent-mobile-content {
+    flex: 1;
+    padding: 0 12px 0 16px; /* 增加左边距,距离图片更远 */
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+    max-width: calc(100% - 130px); /* 减去图片和三点按钮的宽度 */
+    align-items: flex-start; /* 居左显示 */
+  }
+  
+  .parent-mobile-title {
+    font-size: 14px;
+    font-weight: 600;
+    color: #333;
+    margin-bottom: 4px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+    word-break: break-all;
+  }
+  
+  .parent-mobile-index {
+    margin-right: 6px;
+    color: #999;
+    font-weight: normal;
+  }
+  
+  .parent-mobile-desc {
+    font-size: 12px;
+    color: #999;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  
+  /* 右侧三点按钮 */
+  .parent-mobile-more {
+    width: 36px;
+    height: 36px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    color: #ccc;
+    font-size: 18px;
+  }
+}
+
+/* -------------------------- */
+/* 电脑端样式(>768px) */
+/* -------------------------- */
+
+@media screen and (min-width: 769px) {
+  /* 确保移动端容器隐藏 */
+  .parent-mobile-container {
+    display: none !important;
+  }
+  
+  /* 确保电脑端课程项显示 */
+  .parent-mobile-course-item {
+    display: flex !important;
+  }
+}

+ 6 - 5
src/utils/loginUtils.js

@@ -146,7 +146,7 @@ const getTenantId = async (tenantName) => {
 }
 
 // 登录逻辑
-const loginLogic = async (loginForm, tenantId, isAuthorized, router) => {
+const loginLogic = async (loginForm, tenantId, isAuthorized, router, loginPath = managementRoutes.home) => {
   const loginLoading = ref(false)
   const loading = ref()
   const isLoggedIn = ref(false)
@@ -217,8 +217,8 @@ const loginLogic = async (loginForm, tenantId, isAuthorized, router) => {
       // 获取角色路由数据
       await refreshRoleRouteData();
 
-      // 登录成功后,跳转到指定的页面
-      router.push(managementRoutes.home)
+      // 登录成功后,跳转到默认页面
+      router.push(loginPath)
 
       return true
     } else if (res.code === 1002000009) {
@@ -264,7 +264,7 @@ const loadLoginData = (loginData) => {
 }
 
 // 检查登录状态
-const checkLoginStatus = (router) => {
+const checkLoginStatus = (router, loginPath = managementRoutes.home) => {
   const storedStatus = localStorage.getItem('isLoggedIn')
   if (storedStatus === 'true') {
 
@@ -274,7 +274,8 @@ const checkLoginStatus = (router) => {
     // 获取角色路由数据
     refreshRoleRouteData();
 
-    router.push(managementRoutes.home)
+    // 如果有指定的跳转路径,则跳转到该路径,否则跳转到默认主页
+    router.push(loginPath)
     return true
   }
   return false

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

@@ -23,7 +23,7 @@
       </div>
 
       <transition name="drawer-slide">
-        <div class="main-content" v-if="drawerVisible">
+        <div class="main-content" :class="{ 'hidden': !drawerVisible }" v-if="drawerVisible">
           <!-- 菜单栏 -->
           <div class="drawer-box">
             <el-row class="tac">
@@ -64,7 +64,7 @@
 
     <div class="content-box">
       <div class="box-1">
-        <div class="inner-box left-box">
+        <div class="inner-box left-box" v-if="currentOpenedMenu !== '/parent-mobile'">
           <div class="box-icon" @click="goBack">
             <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
             {{ pageTitle }}
@@ -127,10 +127,62 @@
         <SelfDirectedLearning v-if="currentOpenedMenu === menuConfigTemp[3].id"
                               @refreshData="refreshData" />
 
+        <!-- 家长课堂-手机端 - 与其他课程保持一致的样式 -->
+        <template v-else-if="currentOpenedMenu === '/parent-mobile'">
+          <!-- 电脑端样式:与原课程相同的布局结构 -->
+          <div
+              class="small-box parent-mobile-course-item"
+              v-for="(course, index) in currentCourseData"
+              :key="course.id"
+          >
+            <div
+                class="nested-box"
+                :style="{
+                backgroundImage: `url(${course.ctTypeImage})`,
+                backgroundSize: 'cover'
+              }"
+                @click="goToParentMobileCourse(course)"
+            ></div>
+            <div class="additional-text">
+              {{ course.ctTypeSort }} {{ course.ctType }}
+            </div>
+          </div>
+
+          <!-- 移动端样式 -->
+          <div class="parent-mobile-container">
+            <div
+                class="parent-mobile-item"
+                v-for="(course, index) in currentCourseData"
+                :key="course.id"
+                @click="goToParentMobileCourse(course)"
+            >
+              <!-- 左侧图片 -->
+              <div class="parent-mobile-image">
+                <img :src="course.ctTypeImage" :alt="course.title" />
+              </div>
+              <!-- 右侧内容 -->
+              <div class="parent-mobile-content">
+                <div class="parent-mobile-title">
+                  <span class="parent-mobile-index">{{ course.ctTypeSort }}</span>
+                  {{ course.ctType }}
+                </div>
+                <div class="parent-mobile-desc">{{ course.ctTypeDescribe || '' }}</div>
+              </div>
+            </div>
+          </div>
+
+          <!-- 空状态 -->
+          <div v-if="currentCourseData.length === 0" class="empty-state" style="width: 100%; text-align: center; padding: 40px;">
+            <p>暂无课程</p>
+          </div>
+        </template>
+
+        <!-- 其他课程类型的默认显示 -->
         <div
             class="small-box"
             v-for="(outlineData, index) in currentCourseData"
             :key="index"
+            v-else
         >
           <div
               class="nested-box"
@@ -173,12 +225,14 @@ import SelfDirectedLearning from '@/components/study/SelfDirectedLearning.vue'
 import DialogContent from "@/views/AIPage/aiGenerate/DialogContent.vue";
 import {teacherList} from "@/api/teachers.js";
 import { CONFIG } from '@/utils/roleUtils.js'
+import '@/styles/parentMobileCourse.css'
 
 
 const router = useRouter() // 获取当前路由对象
 // 下拉菜单选中项
 const selectedGrade = ref('')
 // 抽屉显示状态
+const isMobile = ref(false)
 const drawerVisible = ref(true)
 // 下拉框可见性状态
 const dropdownVisible = ref(false)
@@ -192,6 +246,9 @@ const handleVisibleChange = (visible) => {
 const generalCourseData = ref([])
 const practicalCourseData = ref([])
 const aiCourseData = ref([])
+
+// 家长课堂-手机端列表数据(模拟数据)
+const parentMobileCourseList = ref([])
 const selfStudyCourseData = ref([])
 
 // 菜单配置
@@ -199,7 +256,9 @@ const menuConfigTemp = [
   { id: '/general', title: 'AI通识课', type: '1', data: generalCourseData },
   { id: '/practical', title: 'AI实操课', type: '2', data: practicalCourseData },
   { id: '/poetry', title: 'AI古诗词', type: '3', data: aiCourseData },
-  { id: '/selfstudy', title: 'AI自主学习', type: '4', data: selfStudyCourseData }
+  { id: '/selfstudy', title: 'AI自主学习', type: '4', data: selfStudyCourseData },
+  // 家长课堂-手机端(独立数据处理)
+  { id: '/parent-mobile', title: '家长课堂', type: '5', data: ref([]) }
 ]
 
 
@@ -221,6 +280,9 @@ const menuList = computed(() => {
 const roleRouteMenuSet = computed(() => {
   try {
     const roleRouteMenuStr = localStorage.getItem(CONFIG.USER_ROLE_ROUTE_MENU_KEY)
+    // const roleRouteMenu = JSON.parse(roleRouteMenuStr)
+    // roleRouteMenu.push('/parent-mobile')
+    // return roleRouteMenu
     return roleRouteMenuStr ? JSON.parse(roleRouteMenuStr) : []
   } catch (error) {
     console.error('Error parsing roleRouteMenuSet:', error)
@@ -314,6 +376,16 @@ const handleGradeSelect = command => {
     })
   }
 }
+// 检测是否为移动设备
+const checkIsMobile = () => {
+  if (typeof window !== 'undefined') {
+    // 使用多种方式检测移动设备
+    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
+        window.innerWidth <= 768; // 或者使用屏幕宽度作为判断标准
+  }
+  return false;
+}
+
 // 切换抽屉显示状态的函数
 const toggleDrawer = () => {
   drawerVisible.value = !drawerVisible.value
@@ -358,6 +430,13 @@ const getCourseTitle = index => {
 // 首页点击渲染后的页面title
 const pageTitle = ref('返回首页')
 onMounted(() => {
+  // 检测是否为移动设备
+  isMobile.value = checkIsMobile();
+  // 在移动端自动收起菜单
+  if (isMobile.value) {
+    drawerVisible.value = false;
+  }
+
   //加载所有数据
   fetchCtTypes()
 
@@ -457,6 +536,24 @@ const goToAIExperience = outlineData => {
     state: { typeId: outlineData.id, typeName: outlineData.ctType, typeSort: outlineData.ctTypeSort }
   })
 }
+
+// 家长课堂-手机端跳转方法
+const goToParentMobileCourse = (outlineData) => {
+  const menuId = currentOpenedMenu.value
+  const menu = menuConfig.value.find(m => m.id === menuId)
+  const currentData = menu ? menu.data.value : []
+  const index = currentData.findIndex(item => item.id === outlineData.id)
+
+  if (index !== -1) {
+    const menuIndex = menuId + '-' + (index + 1)
+    saveActiveState(menuId, menuIndex)
+  }
+
+  router.push({
+    path: '/parent-mobile-course-detail',
+    state: { typeId: outlineData.id, typeName: outlineData.ctType, typeSort: outlineData.ctTypeSort }
+  })
+}
 </script>
 
 <style scoped lang="scss">
@@ -856,7 +953,7 @@ const goToAIExperience = outlineData => {
   display: flex; // 确保子元素水平排列
   flex-wrap: wrap; // 允许子元素换行;
   cursor: pointer; // 添加鼠标指针样式
-  // margin: rpx(10) 0; 
+  // margin: rpx(10) 0;
   overflow-y: auto;
 }
 // Chrome、Edge等浏览器的滚动条样式
@@ -903,6 +1000,8 @@ const goToAIExperience = outlineData => {
   margin-bottom: rpx(4);
   font-size: rpx(8);
 }
+
+
 </style>
 
 <style lang="scss">
@@ -947,4 +1046,4 @@ const goToAIExperience = outlineData => {
           #ffcc00
   );
 }
-</style>
+</style>

+ 13 - 3
src/views/Login.vue

@@ -176,8 +176,13 @@ const handleLogin = async () => {
         }
       }
 
+      // 获取路由参数中的重定向路径
+      // 优先使用查询参数,如果没有则使用路由元数据中的默认值
+      const currentRoute = router.currentRoute.value;
+      const loginPath = currentRoute.query.loginPath || currentRoute.meta.defaultQuery?.loginPath || null
+      
       // 调用登录逻辑
-      await loginLogic(loginData.value.loginForm, tenantId, isAuthorized, router)
+      await loginLogic(loginData.value.loginForm, tenantId, isAuthorized, router, loginPath)
     }
   })
 }
@@ -192,8 +197,13 @@ onMounted(() => {
     router.replace(homeRoutes.login)
   }
 
-  // 检查登录状态,如果已登录则直接跳转到管理界面
-  checkLoginStatus(router)
+  // 获取路由参数中登陆成功后跳转页面
+  // 优先使用查询参数,如果没有则使用路由元数据中的默认值
+  const currentRoute = router.currentRoute.value;
+  const loginPath = currentRoute.query.loginPath || currentRoute.meta.defaultQuery?.loginPath || null;
+
+  // 检查登录状态,如果已登录则根据loginPath参数决定跳转位置
+  checkLoginStatus(router, loginPath)
 
   const handleKeyPress = (event) => {
     // 检查是否按下回车键(keyCode 13)

+ 411 - 0
src/views/ParentMobile/ParentMobileCourse.vue

@@ -0,0 +1,411 @@
+<template>
+  <!-- 家长课堂-手机端页面 -->
+  <div class="parent-mobile-course-container">
+    <!-- 页面标题和返回按钮 -->
+    <div class="page-header">
+      <h1 class="page-title">家长课堂</h1>
+    </div>
+    
+    <!-- 搜索框 -->
+    <div class="search-section">
+      <el-autocomplete
+          v-model="SearchInput"
+          :fetch-suggestions="querySearch"
+          placeholder="搜索课程"
+          @select="handleSearchSelect"
+          class="search-input autocomplete-round"
+          value-key="ctType"
+          :trigger-on-focus="false"
+          :key="searchKey"
+      >
+        <template #prefix>
+          <el-icon class="el-input__icon"><Search /></el-icon>
+        </template>
+        <!-- 下拉项模板 -->
+        <template #default="{ item }">
+          <div class="scrollbar">
+            <!-- 序号和标题 -->
+            {{ item.ctTypeSort }} {{ item.ctType }}
+          </div>
+        </template>
+      </el-autocomplete>
+    </div>
+
+    <!-- 课程列表 -->
+    <div class="course-list">
+      <!-- 电脑端样式:与原课程相同的布局结构 -->
+      <div
+          class="small-box parent-mobile-course-item"
+          v-for="(course, index) in parentMobileCourseData"
+          :key="course.id"
+      >
+        <div
+            class="nested-box"
+            :style="{
+              backgroundImage: `url(${course.ctTypeImage})`,
+              backgroundSize: 'cover'
+            }"
+            @click="goToParentMobileCourse(course)"
+        ></div>
+        <div class="additional-text">
+          {{ course.ctTypeSort }} {{ course.ctType }}
+        </div>
+      </div>
+
+      <!-- 移动端样式 -->
+      <div class="parent-mobile-container">
+        <div
+            class="parent-mobile-item"
+            v-for="(course, index) in parentMobileCourseData"
+            :key="course.id"
+            @click="goToParentMobileCourse(course)"
+        >
+          <!-- 左侧图片 -->
+          <div class="parent-mobile-image">
+            <img :src="course.ctTypeImage" :alt="course.title" />
+          </div>
+          <!-- 右侧内容 -->
+          <div class="parent-mobile-content">
+            <div class="parent-mobile-title">
+              <span class="parent-mobile-index">{{ course.ctTypeSort }}</span>
+              {{ course.ctType }}
+            </div>
+            <div class="parent-mobile-desc">{{ course.ctTypeDescribe || '' }}</div>
+          </div>
+        </div>
+      </div>
+
+      <!-- 空状态 -->
+      <div v-if="parentMobileCourseData.length === 0" class="empty-state" style="width: 100%; text-align: center; padding: 40px;">
+        <p>暂无课程</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { ClassList, ClassOutline } from '@/api/class.js'
+import { Search, ArrowLeftBold } from '@element-plus/icons-vue'
+import { Message } from '@/utils/message/Message.js'
+import { CONFIG } from '@/utils/roleUtils.js'
+import '@/styles/parentMobileCourse.css'
+
+const router = useRouter()
+
+// 搜索框
+const SearchInput = ref('')
+// 用于强制重新渲染搜索组件的key
+const searchKey = ref(Date.now())
+
+// 家长课堂-手机端数据
+const parentMobileCourseData = ref([])
+
+// 检测是否为移动设备
+const isMobile = ref(false)
+
+const checkIsMobile = () => {
+  if (typeof window !== 'undefined') {
+    // 使用多种方式检测移动设备
+    return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
+        window.innerWidth <= 768; // 或者使用屏幕宽度作为判断标准
+  }
+  return false;
+}
+
+// 获取家长课堂-手机端数据
+const fetchParentMobileCourseData = async () => {
+  try {
+    // 获取年级数据
+    const classResponse = await ClassList()
+    if (classResponse.code === 0) {
+      const classData = classResponse.data
+      
+      // 获取到数据,优先从localStorage读取选中值
+      const savedGrade = localStorage.getItem('selectedGrade')
+      const selectedGrade = savedGrade || (classData.length > 0 ? classData[0].ctType : '')
+      
+      // 查找对应的年级ID
+      const selectedItem = classData.find(item => item.ctType === selectedGrade) || classData[0]
+      if (selectedItem) {
+        // 获取该年级下的课程大纲数据(家长课堂-手机端类型,根据原页面配置)
+        const outlineRes = await ClassOutline(selectedItem.id, '5')
+        if (outlineRes.code === 0) {
+          // 处理课程数据,添加序号
+          parentMobileCourseData.value = outlineRes.data.map((item, index) => {
+            item.ctTypeSort = index + 1
+            item.ctTypeSort = item.ctTypeSort > 9 ? item.ctTypeSort : "0" + item.ctTypeSort
+            return item
+          })
+        }
+      }
+    }
+  } catch (error) {
+    console.error('获取家长课堂-手机端数据失败:', error)
+    Message().notifyWarning('获取课程数据失败', true)
+  }
+}
+
+// 搜索建议查询方法
+const querySearch = (queryString, cb) => {
+  const results = queryString
+      ? parentMobileCourseData.value.filter(item => {
+        // 课程标题和序号查询
+        return item.ctType.toLowerCase().includes(queryString.toLowerCase()) ||
+            // 类型检查,确保ctTypeSort是字符串类型
+            (item.ctTypeSort && item.ctTypeSort.toString().includes(queryString))
+      })
+      : parentMobileCourseData.value
+  cb(results)
+}
+
+// 搜索选择处理方法
+const handleSearchSelect = item => {
+  goToParentMobileCourse(item)
+  // 清空输入框
+  SearchInput.value = '';
+}
+
+// 返回首页
+const goBack = () => {
+  router.push('/home')
+}
+
+// 跳转到家长课堂-手机端详情页
+const goToParentMobileCourse = (outlineData) => {
+  router.push({
+    path: '/parent-mobile-course-detail',
+    state: { typeId: outlineData.id, typeName: outlineData.ctType, typeSort: outlineData.ctTypeSort }
+  })
+}
+
+onMounted(() => {
+  // 检测是否为移动设备
+  isMobile.value = checkIsMobile();
+  
+  // 获取家长课堂-手机端数据
+  fetchParentMobileCourseData()
+})
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+
+.parent-mobile-course-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: column;
+  background: linear-gradient(
+          to bottom,
+          #e2ddfc,
+          #f1effd
+  );
+  padding: rpx(20);
+  box-sizing: border-box;
+  overflow-y: auto;
+}
+
+.page-header {
+  align-items: center;
+  margin-bottom: rpx(30);
+  padding: rpx(20);
+  border-bottom: 1px solid #dcdcff;
+}
+
+.page-title {
+  font-size: rpx(36);
+  color: #44449c;
+  margin: 0;
+  text-align: center;
+}
+
+.search-section {
+  margin-bottom: rpx(30);
+}
+
+.search-input {
+  width: 100%;
+  max-width: rpx(600);
+}
+
+:deep(.autocomplete-round .el-input__wrapper) {
+  border-radius: rpx(30) !important;
+  padding-left: rpx(15);
+  overflow: hidden;
+  border: none !important;
+  box-shadow: none !important;
+}
+
+:deep(.autocomplete-round .el-input__inner) {
+  border-radius: rpx(30) !important;
+}
+
+:deep(.autocomplete-round) {
+  border-radius: rpx(30) !important;
+}
+
+.course-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: rpx(20);
+  justify-content: flex-start;
+}
+
+.small-box {
+  width: calc(25% - rpx(15)); /* 每行4个 */
+  aspect-ratio: 1 / 1;
+  position: relative;
+  border-radius: rpx(10);
+  overflow: hidden;
+  cursor: pointer;
+  transition: transform 0.3s ease, box-shadow 0.3s ease;
+
+  &:hover {
+    transform: translateY(rpx(-5));
+    box-shadow: 0 rpx(10) rpx(20) rgba(0, 0, 0, 0.1);
+  }
+}
+
+.nested-box {
+  width: 100%;
+  height: 100%;
+  transition: all 0.3s ease;
+  cursor: pointer;
+
+  &:hover {
+    transform: scale(1.05);
+  }
+}
+
+.additional-text {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: rgba(0, 0, 0, 0.6);
+  color: white;
+  padding: rpx(10);
+  text-align: center;
+  font-size: rpx(24);
+  backdrop-filter: blur(rpx(5));
+}
+
+/* 移动端样式 */
+.parent-mobile-container {
+  display: none;
+  flex-direction: column;
+  gap: rpx(20);
+  width: 100%;
+}
+
+.parent-mobile-item {
+  display: flex;
+  align-items: center;
+  background: white;
+  border-radius: rpx(15);
+  padding: rpx(20);
+  box-shadow: 0 rpx(5) rpx(15) rgba(0, 0, 0, 0.05);
+  cursor: pointer;
+  transition: transform 0.2s ease, box-shadow 0.2s ease;
+
+  &:hover {
+    transform: translateY(rpx(-2));
+    box-shadow: 0 rpx(8) rpx(20) rgba(0, 0, 0, 0.1);
+  }
+}
+
+.parent-mobile-image {
+  width: rpx(120);
+  height: rpx(120);
+  border-radius: rpx(10);
+  overflow: hidden;
+  margin-right: rpx(20);
+
+  img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+}
+
+.parent-mobile-content {
+  flex: 1;
+}
+
+.parent-mobile-title {
+  font-size: rpx(28);
+  font-weight: 500;
+  color: #333;
+  margin-bottom: rpx(10);
+  display: flex;
+  align-items: center;
+  gap: rpx(10);
+}
+
+.parent-mobile-index {
+  background: #44449c;
+  color: white;
+  width: rpx(30);
+  height: rpx(30);
+  border-radius: rpx(5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: rpx(20);
+}
+
+.parent-mobile-desc {
+  font-size: rpx(24);
+  color: #666;
+  line-height: rpx(32);
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .parent-mobile-course-container {
+    padding: rpx(15);
+  }
+
+  .page-header {
+    flex-direction: column;
+    align-items: flex-start;
+    gap: rpx(15);
+  }
+
+  .small-box {
+    width: calc(50% - rpx(10)); /* 移动端每行2个 */
+  }
+
+  .parent-mobile-container {
+    display: flex;
+  }
+
+  .course-list {
+    justify-content: center;
+  }
+}
+
+@media (max-width: 480px) {
+  .small-box {
+    width: 100%; /* 超小屏幕上每行1个 */
+  }
+}
+
+.empty-state {
+  width: 100%;
+  text-align: center;
+  padding: rpx(40);
+  color: #999;
+  font-size: rpx(28);
+}
+</style>

+ 690 - 0
src/views/ParentMobile/ParentMobileCourseDetail.vue

@@ -0,0 +1,690 @@
+<template>
+  <!-- ======================================== -->
+  <!-- 手机端:完整布局 -->
+  <!-- ======================================== -->
+  <div class="mobile-container" :class="{ 'fullscreen-active': isFullscreen }">
+    <!-- 手机端:顶部导航栏 - 悬浮在视频上方 -->
+    <div class="mobile-header" :class="{ 'header-hidden': headerHidden }">
+      <button class="mobile-back-btn" @click="goBack">
+        <el-icon><ArrowLeftBold /></el-icon>
+      </button>
+      <span class="mobile-header-title">{{ courseInfo.title }}</span>
+      <div class="mobile-header-placeholder"></div>
+    </div>
+
+    <!-- 手机端:视频播放区域 -->
+    <div class="mobile-video-area">
+      <!-- 手机端视频播放器 -->
+      <VideoPlayer
+        v-if="currentCourse && currentCourse.courseContentType === 'video'"
+        :key="'mobile-' + (currentCourse?.id || currentChapterId)"
+        :contentType="currentCourse.courseContentType"
+        :videoPath="currentCourse.courseVideoPath"
+        :courseId="currentCourse.id || ''"
+        :typeId="typeId"
+        :courseConfigList="currentCourse.courseConfigList || []"
+        :allIndices="chapterIds"
+        :currentIndex="currentChapterId"
+        :isFullscreen="isFullscreen"
+        @switchVideo="handleSelect"
+        @saveProgress="handleSaveProgress"
+        @videoPlay="onVideoPlay"
+        @videoPause="onVideoPause"
+        @fullscreenChange="handleFullscreenChange"
+      />
+    </div>
+
+    <!-- 手机端:搜索框 -->
+    <div class="mobile-search-container">
+      <el-input
+        v-model="searchKeyword"
+        placeholder="搜索课程内容..."
+        class="mobile-search-input"
+        clearable
+        @keyup.enter="handleSearchSubmit"
+      >
+        <template #prefix>
+          <el-icon><Search /></el-icon>
+        </template>
+      </el-input>
+    </div>
+
+    <!-- 手机端:章节列表 -->
+    <div class="mobile-chapter-list">
+      <div
+        v-for="(chapter, index) in filteredTitles"
+        :key="chapter.id"
+        class="mobile-chapter-item"
+        :class="{ active: currentChapterId === chapter.id }"
+        @click="playChapter(chapter)"
+      >
+        <div class="mobile-chapter-info">
+          <div class="mobile-chapter-title-row">
+            <span class="mobile-chapter-number">{{ String(index + 1).padStart(2, '0') }}.</span>
+            <span class="mobile-chapter-title">{{ videoPathMap[chapter.id]?.courseName || ''  }}</span>
+          </div>
+          <div class="mobile-chapter-meta">
+            <span class="mobile-chapter-type">视频时长</span>
+            <span class="mobile-chapter-divider">|</span>
+            <span class="mobile-chapter-duration">{{ formatDuration(videoPathMap[chapter.id]?.courseTime || 0) }}</span>
+          </div>
+        </div>
+        <div class="mobile-chapter-play-icon">
+          <el-icon
+            v-if="currentChapterId === chapter.id && isPlaying"
+            class="playing-icon"
+          >
+            <div class="audio-wave">
+              <div class="wave-bar"></div>
+              <div class="wave-bar"></div>
+              <div class="wave-bar"></div>
+            </div>
+          </el-icon>
+          <el-icon
+            v-else
+            class="play-icon"
+          >
+            <CaretRight />
+          </el-icon>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import {
+  Search,
+  ArrowLeftBold,
+  CaretRight
+} from '@element-plus/icons-vue'
+import VideoPlayer from '@/components/videopage/VideoPlayer.vue'
+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'
+
+const router = useRouter()
+const route = useRoute()
+
+const searchKeyword = ref('')
+const currentChapterId = ref('')
+const isPlaying = ref(false)
+// 强制设为移动端,不再检测设备类型
+const isMobile = ref(true)
+const headerHidden = ref(false)
+
+const courseInfo = ref({
+  title: '',
+  subtitle: '',
+  coverImage: '',
+  progress: 0
+})
+
+// 跟踪是否处于全屏状态
+const isFullscreen = ref(false)
+
+const chapters = ref([])
+const videoPathMap = ref({})
+const gradeId = ref('')
+const typeId = ref('')
+const currentCourse = ref({})
+
+const chapterIds = computed(() => {
+  return chapters.value.map(c => c.id)
+})
+
+const filteredTitles = computed(() => {
+  if (!searchKeyword.value) {
+    return chapters.value
+  }
+  return chapters.value.filter(chapter =>
+    chapter.title.toLowerCase().includes(searchKeyword.value.toLowerCase())
+  )
+})
+
+const querySearch = (queryString, cb) => {
+  const results = queryString
+    ? chapters.value.filter(item => item.title.toLowerCase().includes(queryString.toLowerCase()))
+    : chapters.value
+  cb(results)
+}
+
+const handleSaveProgress = async (type, progress) => {
+  if (!progress) return
+  const saveProgressData = {
+    brpNjId: gradeId.value,
+    brpType: type,
+    brpProgress: progress,
+    brpCtId: typeId.value,
+    brpCourseId: currentCourse.value.id
+  }
+  try {
+    await saveRecord(saveProgressData)
+  } catch (error) {
+    console.error('保存视频进度失败:', error)
+  }
+}
+
+const goBack = () => {
+  router.go(-1)
+}
+
+const handleSelect = (index) => {
+  if (videoPathMap.value[index]) {
+    currentCourse.value = videoPathMap.value[index]
+    currentChapterId.value = index
+  } else {
+    Message().notifyWarning('视频不存在!', true)
+  }
+}
+
+const handleSearchSelect = (item) => {
+  handleSelect(item.id)
+  searchKeyword.value = ''
+}
+
+const handleSearchSubmit = () => {
+  if (searchKeyword.value.trim()) {
+    // 可以在这里实现具体的搜索逻辑
+    console.log('搜索关键词:', searchKeyword.value)
+    // 例如:可以触发课程内容搜索、跳转到搜索结果页等
+    Message().success(`搜索: ${searchKeyword.value}`, true)
+  }
+}
+
+const playPreviousVideo = () => {
+  const currentIndex = chapterIds.value.indexOf(currentChapterId.value)
+  if (currentIndex > 0) {
+    handleSelect(chapterIds.value[currentIndex - 1])
+  }
+}
+
+const playNextVideo = () => {
+  const currentIndex = chapterIds.value.indexOf(currentChapterId.value)
+  if (currentIndex !== -1 && currentIndex < chapterIds.value.length - 1) {
+    handleSelect(chapterIds.value[currentIndex + 1])
+  }
+}
+
+const playChapter = (chapter) => {
+  handleSelect(chapter.id)
+  isPlaying.value = true
+}
+
+const togglePlay = () => {
+  isPlaying.value = !isPlaying.value
+}
+
+const onVideoPlay = () => {
+  isPlaying.value = true
+  // 视频播放时隐藏头部导航栏
+  headerHidden.value = true
+}
+
+const onVideoPause = () => {
+  isPlaying.value = false
+  // 视频暂停时显示头部导航栏
+  headerHidden.value = false
+}
+
+const handlePageClick = () => {}
+
+const formatDuration = (seconds) => {
+  const mins = Math.floor(seconds / 60)
+  const secs = Math.floor(seconds % 60)
+  return `${mins.toString().padStart(2, '0')}分${secs.toString().padStart(2, '0')}秒`
+}
+
+const handleFullscreenChange = (isFullscreenNow) => {
+  isFullscreen.value = isFullscreenNow
+}
+
+// 监听设备方向变化
+const handleOrientationChange = () => {
+  // 在全屏状态下,设备方向改变时强制重新调整视频容器尺寸
+  if (isFullscreen.value) {
+    // 使用setTimeout确保DOM更新完成后再执行
+    setTimeout(() => {
+      // 触发一次强制重绘
+      const videoArea = document.querySelector('.mobile-video-area');
+      if (videoArea) {
+        videoArea.style.display = 'none';
+        void videoArea.offsetWidth; // 强制重排
+        videoArea.style.display = '';
+      }
+    }, 100);
+  }
+};
+
+onMounted(async () => {
+  // 监听设备方向变化事件
+  window.addEventListener('orientationchange', handleOrientationChange);
+  
+  const typeIdParam = history.state?.typeId || router.currentRoute.value.query.typeId
+  if (typeIdParam) {
+    typeId.value = typeIdParam
+    try {
+      const res = await ClassType(typeIdParam)
+      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
+      })
+
+      const videoCourses = processedData.filter(course => course.courseContentType === 'video')
+
+      videoCourses.forEach((course, index) => {
+        const menuIndex = String(index + 1).padStart(2, '0')
+        course.key = menuIndex
+        videoPathMap.value[menuIndex] = course
+
+        const menuItem = {
+          id: menuIndex,
+          key: menuIndex,
+          title: course.courseName
+        }
+        chapters.value.push(menuItem)
+
+        if (index === 0) {
+          currentCourse.value = course
+          currentChapterId.value = menuIndex
+        }
+      })
+    } catch (error) {
+      console.error('获取课程数据失败:', error)
+    }
+  }
+
+  const title = history.state?.typeName || router.currentRoute.value.query.typeName
+  if (title) {
+    courseInfo.value.title = String(title)
+  }
+
+  gradeId.value = globalState.initGradeId()
+})
+
+onUnmounted(() => {
+  // 移除设备方向变化事件监听器
+  window.removeEventListener('orientationchange', handleOrientationChange);
+});
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+
+/* ======================================== */
+/* 手机端样式 */
+/* ======================================== */
+
+.mobile-container {
+  display: flex;
+  flex-direction: column;
+  width: 100% !important;
+  min-width: 100% !important;
+  max-width: 100% !important;
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding: 0 !important;
+  margin: 0 !important;
+  box-sizing: border-box !important;
+  position: fixed !important;
+  top: 0 !important;
+  left: 0 !important;
+  right: 0 !important;
+  bottom: 0 !important;
+  overflow-y: auto !important;
+}
+
+.mobile-header {
+  width: 100% !important;
+  height: 44px;
+  background: linear-gradient(135deg, #44449c80, #7F70C880); /* 半透明背景 */
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 12px;
+  box-sizing: border-box;
+  position: fixed; /* 固定定位,悬浮在顶部 */
+  top: 0;
+  left: 0;
+  right: 0;
+  z-index: 1000; /* 提高层级确保在最顶层 */
+  transition: transform 0.3s ease; /* 添加过渡动画 */
+}
+
+.mobile-header.header-hidden {
+  transform: translateY(-100%); /* 向上隐藏 */
+}
+
+.mobile-back-btn {
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(255, 255, 255, 0.2);
+  border-radius: 50%;
+  border: none;
+  color: white;
+  font-size: 16px;
+  cursor: pointer;
+  flex-shrink: 0;
+}
+
+.mobile-header-title {
+  flex: 1;
+  text-align: center;
+  color: white;
+  font-size: 16px;
+  font-weight: 500;
+  padding: 0 40px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.mobile-header-placeholder {
+  width: 32px;
+  flex-shrink: 0;
+}
+
+.mobile-video-area {
+  width: 100% !important;
+  min-width: 100% !important;
+  max-width: 100% !important;
+  height: auto !important;
+  background: #000;
+  margin-top: 44px; /* 为固定在顶部的导航栏预留空间 */
+  position: relative;
+  padding: 0 !important;
+  margin: 0 !important;
+  box-sizing: border-box !important;
+  flex-shrink: 0;
+  border-radius: 0 !important;
+  overflow: hidden;
+}
+
+/* 修复全屏模式下的视频显示问题 */
+.mobile-container.fullscreen-active .mobile-video-area {
+  position: fixed !important;
+  top: 0 !important;
+  left: 0 !important;
+  right: 0 !important;
+  bottom: 0 !important;
+  z-index: 9999 !important;
+  margin-top: 0 !important;
+  /* 使用视口单位确保在设备方向改变时适应屏幕 */
+  width: 100vw !important;
+  height: 100vh !important;
+  min-height: 100vh !important;
+  max-height: 100vh !important;
+  /* 确保视频容器占据全部可用空间 */
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+}
+
+.mobile-container.fullscreen-active .mobile-header {
+  z-index: 10000;
+}
+
+.mobile-container.fullscreen-active .mobile-search-container,
+.mobile-container.fullscreen-active .mobile-chapter-list {
+  display: none;
+}
+
+/* 手机端搜索框容器 */
+.mobile-search-container {
+  width: 100%;
+  padding: rpx(16);
+  background: #f5f5f5;
+  box-sizing: border-box;
+  display: flex;
+  align-items: center;
+}
+
+.mobile-search-input {
+  width: 100%;
+  height: rpx(64);
+}
+
+.mobile-search-input :deep(.el-input__wrapper) {
+  border-radius: rpx(32);
+  width: 100%;
+  height: 100%;
+  background: #fff;
+  border: none;
+  box-shadow: none;
+}
+
+.mobile-search-input :deep(.el-input__inner) {
+  height: 100%;
+  line-height: rpx(64);
+  font-size: rpx(28);
+  color: #999;
+}
+
+/* 手机端视频播放器去圆角 */
+.mobile-video-area :deep(.d-player-wrap),
+.mobile-video-area :deep(.full-box-video),
+.mobile-video-area :deep(.ppt-container) {
+  border-radius: 0 !important;
+  width: 100% !important;
+  height: 100% !important;
+  max-width: 100% !important;
+  max-height: 100% !important;
+}
+
+.mobile-video-area :deep(*) {
+  width: 100% !important;
+  height: 100% !important;
+  max-width: 100% !important;
+  max-height: 100% !important;
+  object-fit: contain !important; /* 使用contain确保视频完全可见 */
+  border-radius: 0 !important;
+}
+
+/* 特别处理全屏模式下的视频播放器 */
+.mobile-container.fullscreen-active .mobile-video-area :deep(.d-player-wrap),
+.mobile-container.fullscreen-active .mobile-video-area :deep(.full-box-video),
+.mobile-container.fullscreen-active .mobile-video-area :deep(.ppt-container) {
+  width: 100% !important;
+  height: 100% !important;
+  max-width: 100% !important;
+  max-height: 100% !important;
+  position: relative !important;
+}
+
+.mobile-container.fullscreen-active .mobile-video-area :deep(video) {
+  object-fit: contain !important;
+  width: 100% !important;
+  height: 100% !important;
+  max-width: 100% !important;
+  max-height: 100% !important;
+}
+
+.mobile-chapter-list {
+  flex: 1;
+  padding: 0;
+  background: #fff;
+  margin-top: rpx(10); /* 与视频信息区域分开 */
+}
+
+.mobile-chapter-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px;
+  border-bottom: 1px solid #f0f0f0;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+  background: #fff;
+}
+
+.mobile-chapter-item:last-child {
+  border-bottom: none;
+}
+
+.mobile-chapter-item.active {
+  background: #fff;
+}
+
+.mobile-chapter-item.locked {
+  opacity: 0.6;
+}
+
+.mobile-chapter-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.mobile-chapter-title-row {
+  display: flex;
+  align-items: flex-start;
+  gap: 6px;
+}
+
+.mobile-chapter-number {
+  font-size: 15px;
+  font-weight: 600;
+  color: #333;
+  flex-shrink: 0;
+}
+
+.mobile-chapter-title {
+  font-size: 15px;
+  font-weight: 500;
+  color: #333;
+  line-height: 1.4;
+}
+
+.mobile-chapter-meta {
+  font-size: 12px;
+  color: #999;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding-left: 28px;
+}
+
+.mobile-chapter-type {
+  color: #999;
+}
+
+.mobile-chapter-divider {
+  color: #ddd;
+}
+
+.mobile-chapter-duration {
+  color: #999;
+}
+
+.mobile-chapter-play-icon {
+  flex-shrink: 0;
+  margin-left: 12px;
+  width: 24px;
+  height: 24px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.mobile-chapter-play-icon .el-icon {
+  font-size: 20px;
+  color: #409EFF;
+}
+
+.mobile-chapter-play-icon .playing-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.audio-wave {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 2px;
+  height: 20px;
+}
+
+.wave-bar {
+  width: 2px;
+  height: 8px;
+  background-color: #409EFF;
+  border-radius: 1px;
+  animation: wave-animation 1.2s ease-in-out infinite;
+}
+
+.wave-bar:nth-child(1) {
+  animation-delay: 0s;
+}
+
+.wave-bar:nth-child(2) {
+  animation-delay: 0.2s;
+}
+
+.wave-bar:nth-child(3) {
+  animation-delay: 0.4s;
+}
+
+@keyframes wave-animation {
+  0%, 100% {
+    transform: scaleY(1);
+    opacity: 0.7;
+  }
+  50% {
+    transform: scaleY(1.8);
+    opacity: 1;
+  }
+}
+
+.mobile-chapter-action {
+  width: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.mobile-lock-icon {
+  color: #ccc;
+  font-size: 16px;
+}
+
+/* 处理设备方向变化时的全屏视频 */
+@media screen and (orientation: landscape) {
+  .mobile-container.fullscreen-active .mobile-video-area {
+    width: 100vw !important;
+    height: 100vh !important;
+    min-height: 100vh !important;
+    max-height: 100vh !important;
+  }
+}
+
+@media screen and (orientation: portrait) {
+  .mobile-container.fullscreen-active .mobile-video-area {
+    width: 100vw !important;
+    height: 100vh !important;
+    min-height: 100vh !important;
+    max-height: 100vh !important;
+  }
+}
+</style>