<template>
  <div class="pptist-student-viewer" :class="{ 'fullscreen': isFullscreen }">
    <!-- Loading状态显示 -->
    <div v-if="isLoading" class="loading-overlay">
      <div class="loading-content">
        <div class="loading-spinner"></div>
        <div class="loading-text">{{ lang.ssCourseLoading }}</div>
      </div>
    </div>
    <!-- 左侧导航栏 -->
    <div class="layout-content-left" v-show="type == '1' || (type == '2' && !isFollowModeActive)" :class="{ collapsed: slidePanelCollapsed }">
      <div class="thumbnails">
        <div class="viewer-header slide-header">
          <h3 v-show="!slidePanelCollapsed">{{ lang.ssCourseOutline }}</h3>
          <button class="collapse-btn" @click="slidePanelCollapsed = !slidePanelCollapsed" :title="slidePanelCollapsed ? lang.ssExpand : lang.ssCollapse" style="right: 8px;">
            <span v-if="slidePanelCollapsed">
              <img src="@/assets/img/arrow.svg">
            </span>
            <span v-else>
              <img src="@/assets/img/arrow.svg" style="transform: rotate(180deg);">
            </span>
          </button>
        </div>
        <div v-if="!slidePanelCollapsed" class="panel-content">
          <div class="thumbnail-list">
            <div v-for="(slide, index) in slides" :key="slide.id" class="thumbnail-item"
              :class="{ 'active': slideIndex === index }" @click="goToSlide(index)">
              <div class="label">{{ fillDigit(index + 1, 2) }}</div>
              <ThumbnailSlide class="thumbnail" :slide="slide" :size="168" :visible="true" @click="goToSlide(index)" />
            </div>
          </div>
        </div>
      </div>
    </div>

    <!-- 中间放映区域 -->
    <div class="layout-content-center">
      <div class="viewer-header" :class="{ 'hidden': isFullscreen }" style="display: none;">
        <div class="slide-title">{{ lang.ssSlidePage }} {{ slideIndex + 1 }}</div>
        <div class="viewer-controls">
          <button @click="previousSlide" :disabled="slideIndex === 0" :title="lang.ssPrevPage" v-if="!isFollowModeActive || props.type == '1'">
            <IconLeftTwo class="control-icon" />
          </button>
          <button @click="nextSlide" :disabled="slideIndex === slides.length - 1" :title="lang.ssNextPage" v-if="!isFollowModeActive || props.type == '1'">
            <IconRightTwo class="control-icon" />
          </button>
          <!-- <button @click="resetZoom" title="重置缩放">
                        <IconUndo class="control-icon" />
                    </button> -->
          <button @click="enterFullscreen" :title="lang.ssFullscreen">
            <IconFullScreenOne class="control-icon" />
          </button>
          <!-- 只有创建人才显示跟随模式按钮 -->
          <button 
            v-if="isCreator" 
            @click="toggleFollowMode" 
            :class="{ 'follow-active': isFollowModeActive }"
          >
            {{ isFollowModeActive ? lang.ssFollowOff : lang.ssFollowOn }}
          </button>
          <!-- <button @click="backToEditor" class="back-btn" title="返回编辑">
                        <IconEdit class="control-icon" />
                    </button> -->
        </div>
      </div>

      <div class="viewer-canvas" ref="viewerCanvasRef">

        <!-- 全屏时：使用放映功能 -->
        <!-- <ScreenSlideList :slideWidth="slideWidth"
                    :slideHeight="slideHeight" :animationIndex="0" :turnSlideToId="() => { }"
                    :manualExitFullscreen="() => { }" /> -->

        <!-- 不全屏时：使用编辑模式的显示比例和居中逻辑 -->
        <div class="slide-list-wrap" :class="{'slide-list-wrap-n': !isFullscreen, 'laser-pen': laserPen }" :style="{
          width: isFullscreen ? '100%' : (slideWidth * canvasScale) + 'px',
          height: isFullscreen ? '100%' : (slideHeight * canvasScale) + 'px',
          left: isFullscreen ? '0' : `${(containerWidth - slideWidth * canvasScale) / 2}px`,
          top: isFullscreen ? '0' : `${(containerHeight - slideHeight * canvasScale) / 2}px`
        }" @mousemove="handleLaserMove">
          <div class="viewport" v-if="false">
            <div class="background" :style="backgroundStyle"></div>

            <ScreenElement v-for="(element, index) in elementList" :key="element.id" :elementInfo="element"
              :elementIndex="index + 1" :animationIndex="0" :turnSlideToId="() => { }"
              :manualExitFullscreen="() => { }" :is-visible="slideIndex === index" />
          </div>

          <ScreenSlideList :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale"
            :animationIndex="0" :turnSlideToId="() => { }" :manualExitFullscreen="() => { }"  :slideIndex="slideIndex" v-show="!choiceQuestionDetailDialogOpenList.includes(slideIndex)"/>

          <choiceQuestionDetailDialog v-if="choiceQuestionDetailDialogOpenList.includes(slideIndex)" :courseDetail="courseDetail" :workArray="workArray" @changeWorkIndex="changeWorkIndex" v-model:visible="choiceQuestionDetailDialogOpenList" :showData="answerTheResultRef" :slideIndex="slideIndex" :workIndex="0" :style="{ width: isFullscreen ? '100%' : slideWidth2 * canvasScale + 'px', height: isFullscreen ? '100%' : slideHeight2 * canvasScale + 'px', margin: '0 auto' }" :slideWidth="isFullscreen ? slideWidth * canvasScale : slideWidth2 * canvasScale" :slideHeight="isFullscreen ? slideHeight * canvasScale : slideHeight2 * canvasScale"/>


          <div class="slide-bottom" v-if="!isFullscreen">
              <div class="slide-bottom-center" v-if="!isFullscreen && (!isFollowModeActive || props.type == '1')">
                <div class="slide-bottom-center-item">
                  <img src="@/assets/img/left-a.svg" alt="" @click="previousSlide">
                  <div class="slide-bottom-center-item-page">
                      <span>{{ slideIndex + 1 }}</span>
                      <span>/</span>
                      <span>{{ slides.length }}</span>
                  </div>
                  <img src="@/assets/img/right-a.svg" alt="" @click="nextSlide">
                </div>
              </div>
              <div class="slide-bottom-right" v-if="!isFullscreen">
                <Refresh class="tool-btn" v-tooltip="lang.ssRefresh" @click="handleRefreshPage" v-if="currentSlideHasIframe"/>
                <UpTwo @click="handleHomeworkSubmit" v-if="currentSlideHasIframe && !currentSlideHasBilibiliVideo && !isSubmitting" class="tool-btn upBtn" v-tooltip="lang.ssSubmitHW"/>
                <IconLoading v-else-if="currentSlideHasIframe && !currentSlideHasBilibiliVideo" class="tool-btn loading" v-tooltip="lang.ssSubmitting"></IconLoading>
                <IconStopwatchStart v-if="props.type == '1' && courseDetail.userid == props.userid && isFollowModeActive" class="tool-btn" v-tooltip="lang.ssTimer" @click="timerlVisible = !timerlVisible"  />
                <IconWrite v-if="isFollowModeActive && props.type == '1' && courseDetail.userid == props.userid" class="tool-btn" v-tooltip="lang.ssPenTool" @click="writingBoardToolVisible = true"  />
                <IconMagic v-if="isFollowModeActive && props.type == '1' && courseDetail.userid == props.userid" class="tool-btn" v-tooltip="lang.ssLaserPen" :class="{ 'active': laserPen }" @click="toggleLaserPen"  />
                <IconFullScreenOne class="tool-btn" v-tooltip="lang.ssOpenFull" @click="enterFullscreen" />
              </div>
          </div>
        </div>
        <!-- 全屏时的左右下角工具按钮 -->
        <div v-if="isFullscreen && (!isFollowModeActive || props.type == '1')" class="tools-left">
          <IconLeftTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="previousSlide" />
          <IconRightTwo class="tool-btn" theme="two-tone" :fill="['#111', '#fff']" @click="nextSlide" />
        </div>

        <!-- 作业提交按钮 - 当当前幻灯片包含iframe时显示（排除B站视频） -->
        <div v-if="currentSlideHasIframe && !currentSlideHasBilibiliVideo && isFullscreen" class="homework-submit-btn" :class="{ 'submitting': isSubmitting }"
          :style="{ right: getHomeworkButtonRight() + 'px' }" @click="handleHomeworkSubmit"
          v-tooltip="isSubmitting ? lang.ssHwSubmitting : lang.ssHwSubmit">
          <!-- <IconEdit v-if="!isSubmitting" class="tool-btn" />
            <div v-else class="loading-spinner"></div> -->
          <span class="btn-text">{{ isSubmitting ? lang.ssSubmittingEll : lang.ssSubmit }}</span>
        </div>

        <!-- 刷新iframe按钮 -->
        <div class="refresh-page-btn" 
          v-if="currentSlideHasIframe && isFullscreen"
          :style="{ right: getRefreshButtonRight() + 'px' }" 
          @click="handleRefreshPage"
          v-tooltip="lang.ssRefreshIframe">
          <Refresh class="tool-btn" />
          <span class="btn-text">{{ lang.ssRefresh }}</span>
        </div>

        <!-- 功能组件 -->
        <SlideThumbnails v-if="slideThumbnailModelVisible" :turnSlideToIndex="goToSlide"
          @close="slideThumbnailModelVisible = false" />

        <WritingBoardTool 
          :slideWidth="slideWidth" 
          :slideHeight="slideHeight" 
          v-if="writingBoardToolVisible || (props.type == '2' && isFollowModeActive && writingBoardSyncDataURL && writingBoardSyncDataURL.trim() !== '')"
          :readonly="props.type == '2'"
          :syncDataURL="props.type == '2' ? writingBoardSyncDataURL : null"
          :syncBlackboard="props.type == '2' ? writingBoardSyncBlackboard : null"
          @close="handleWritingBoardClose"
          @drawing-end="handleDrawingEnd"
          @blackboard-change="handleBlackboardChange"
        />

        <CountdownTimer 
          v-if="timerlVisible" 
          @close="timerlVisible = false" 
          @timer-start="onTimerStart" 
          @timer-pause="onTimerPause" 
          @timer-reset="onTimerReset"
          @timer-stop="onTimerStop"
          @timer-finish="onTimerFinish"
          @timer-update="onTimerUpdate"
        />

        <div v-if="isFullscreen && (!isFollowModeActive || props.type == '1')" class="tools-right" :class="{ 'visible': rightToolsVisible }"
          @mouseleave="rightToolsVisible = false" @mouseenter="rightToolsVisible = true">
          <div class="content">
            <div class="tool-btn page-number" @click="slideThumbnailModelVisible = true">
              {{ lang.ssSlidePage }} {{slideIndex + 1}} / {{slides.length}}
            </div>
            <IconWrite class="tool-btn" v-if="isFollowModeActive && props.type == '1' && courseDetail.userid == props.userid" v-tooltip="lang.ssPenTool" @click="writingBoardToolVisible = true" />
            <IconMagic class="tool-btn" v-if="isFollowModeActive && props.type == '1' && courseDetail.userid == props.userid" v-tooltip="lang.ssLaserPen" :class="{ 'active': laserPen }" @click="toggleLaserPen" />
            <IconStopwatchStart v-if="(props.type == '1' && courseDetail.userid == props.userid && isFollowModeActive)" class="tool-btn" v-tooltip="lang.ssTimer" @click="timerlVisible = !timerlVisible" />
            <IconOffScreenOne class="tool-btn" v-tooltip="lang.ssExitFull" @click="enterFullscreen" />
          </div>
        </div>
      </div>
    </div>
    <div class="layout-content-right" v-show="type == '1'" :class="{ collapsed: workPanelCollapsed }">
      <div class="thumbnails">
        <div class="viewer-header right-panel-header">
          <button class="collapse-btn" @click="workPanelCollapsed = !workPanelCollapsed" :title="workPanelCollapsed ? lang.ssExpand : lang.ssCollapse" v-if="rightPanelMode != ''" style="left: 8px;">
            <span v-if="workPanelCollapsed">
              <img src="@/assets/img/arrow.svg" style="transform: rotate(180deg);">
            </span>
            <span v-else>
              <img src="@/assets/img/arrow.svg">
            </span>
          </button>
          <!-- 标签页切换按钮 -->
          <div v-show="!workPanelCollapsed" class="tab-switcher">
            <button 
              v-if="currentSlideHasIframe && !currentSlideHasBilibiliVideo"
              v-show="currentSlideHasIframe"
              class="tab-btn" 
              :class="{ active: rightPanelMode === 'homework' }"
              @click="switchToHomework"
              :title="lang.ssAnswerRes"
            >
              {{ lang.ssAnswerRes }}
            </button>
            <button 
              class="tab-btn" 
              :class="{ active: rightPanelMode === 'dialogue' }"
              @click="switchToDialogue"
              :title="lang.ssDialogArea"
            >
              {{ lang.ssDialogArea }}
            </button>
            <!-- <button 
              v-if="isChoiceQuestion"
              class="tab-btn" 
              :class="{ active: rightPanelMode === 'choice' }"
              @click="switchToChoice"
              title="统计"
            >
              统计
            </button> -->
          </div>
          

        </div>
        
        <!-- 侧边导航标签 - 无论展开还是收缩都显示在左侧 -->
        <!-- <div class="side-nav-tabs">
          <button 
            v-if="currentSlideHasIframe"
            class="side-nav-btn" 
            :class="{ active: rightPanelMode === 'homework' }"
            @click="switchToHomework"
            title="作业"
          >
            <img :src="rightPanelMode === 'homework' ? homeworkActiveIcon : homeworkIcon" alt="作业">
          </button>
          <button 
            v-if="isChoiceQuestion"
            class="side-nav-btn" 
            :class="{ active: rightPanelMode === 'choice' }"
            @click="switchToChoice"
            title="统计"
          >
            <img :src="rightPanelMode === 'choice' ? choiceActiveIcon : choiceIcon" alt="统计">
          </button>
          <button 
            class="side-nav-btn" 
            :class="{ active: rightPanelMode === 'dialogue' }"
            @click="switchToDialogue"
            title="对话"
          >
            <img :src="rightPanelMode === 'dialogue' ? dialogueActiveIcon : dialogueIcon" alt="对话">
          </button>
        </div> -->
        

        
        <!-- 回答结果内容 -->
        <div v-show="!workPanelCollapsed && rightPanelMode === 'homework'" class="panel-content">
          <div v-if="workLoading" class="homework-loading">{{ lang.ssHwLoading }}</div>
          <answerTheResult :toolType="toolType" :workId="workId" :workArray="workArray" :unsubmittedStudents="unsubmittedStudents" :slideIndex="slideIndex" v-else ref="answerTheResultRef" @openChoiceQuestionDetail="openChoiceQuestionDetail" @openWorkModal="openWorkModal"/>
          <!--<div class="homework-title">已提交</div>
          <div v-if="workLoading" class="homework-loading">正在加载作业...</div>
          <div v-else>
            <div v-if="workArray && workArray.length" class="homework-grid">
              <button class="homework-btn" v-for="(work, idx) in workArray" :key="work.id ?? idx" :title="work.name" @click="openWorkModal(work)">
                <span class="homework-btn__text">{{ work.name }}</span>
              </button>
            </div>
            <div class="homework-empty" v-else>
              暂无作业提交
            </div>
          </div>-->
          
       
          <!--<div v-if="unsubmittedStudents && unsubmittedStudents.length > 0" class="homework-title" style="margin-top: 20px;">未提交</div>
          <div v-if="unsubmittedStudents && unsubmittedStudents.length > 0">
            <div v-if="studentLoading" class="homework-loading">正在加载学生信息...</div>
            <div v-else>
              <div class="homework-grid">
                <button class="homework-btn unsubmitted" v-for="(student, idx) in unsubmittedStudents" :key="student.id ?? idx" :title="student.name" disabled>
                  <span class="homework-btn__text">{{ student.name }}</span>
                </button>
              </div>
            </div>
          </div>-->
        </div>
        
        <!-- 对话区内容 -->
        <div v-show="!workPanelCollapsed && rightPanelMode === 'dialogue'" class="panel-content">
          <DialoguePanel :userid="props.userid" :courseid="props.courseid"/>
        </div>
        
        <!-- 选择题统计内容 -->
        <div v-show="!workPanelCollapsed && rightPanelMode === 'choice'" class="panel-content">
          <ChoiceStatistics :workArray="workArray" :elementList="elementList" />
        </div>
      </div>
    </div>
    <!-- 右上角计时状态指示器（块状样式） -->
    <div 
      v-if="timerIndicator.visible" 
      class="timer-indicator" 
      :class="{ 'countdown': timerIndicator.isCountdown, 'timeout': timerIndicator.isCountdown && timerIndicator.remainingSec !== null && timerIndicator.remainingSec <= 0 }"
      :style="{ right: getTimerIndicatorRight() + 'px', top: isFullscreen ? '16px' : '12px' }"
    >
      <div class="blocks">
        <template v-if="timerBlocksVisibility().showH">
          <span class="block">{{ timerBlocks().h }}</span>
          <span class="colon"></span>
        </template>
        <template v-if="timerBlocksVisibility().showM">
          <span class="block">{{ timerBlocks().m }}</span>
          <span class="colon"></span>
        </template>
        <span class="block">{{ timerBlocks().s }}</span>
      </div>
    </div>
  </div>

  <ShotWorkModal v-model:visible="visibleShot" :work="selectedWork" />
  <QAWorkModal v-model:visible="visibleQA" :work="selectedWork" />
  <ChoiceWorkModal v-model:visible="visibleChoice" :work="selectedWork" />
  <AIWorkModal v-model:visible="visibleAI" :work="selectedWork" />

  <!-- 学生端激光笔覆盖层（拦截点击） -->
  <div 
    v-if="props.type == '2' && laserPenOverlay.visible"
    class="laser-pointer-overlay"
    :style="laserOverlayStyle"
  >
    <div class="laser-pointer-dot" ref="laserDotRef"></div>
  </div>

  <!-- 在适当位置添加连接状态指示器 -->
  <div class="connection-status" v-if="connectionStatus !== 'connected'">
    <div class="status-indicator" :class="connectionStatus">
      <span v-if="connectionStatus === 'connecting'">{{ lang.ssConnecting }}</span>
      <span v-else-if="connectionStatus === 'disconnected'">{{ lang.ssConnLost }}</span>
    </div>
    <button v-if="connectionStatus === 'disconnected'" @click="manualReconnect" class="reconnect-btn">
      {{ lang.ssReconnect }}
    </button>
  </div>

  <div class="connection-status" v-if="false">
    <div class="status-indicator" :class="'disconnected'">
      <span>{{ lang.ssConnLost }}</span>
    </div>
    <button @click="manualReconnect" class="reconnect-btn">
      {{ lang.ssReconnect }}
    </button>
  </div>
</template>

<script lang="ts" setup>
import { computed, ref, onMounted, onUnmounted, nextTick, inject, watch, provide } from 'vue'
import { storeToRefs } from 'pinia'
import { useSlidesStore } from '@/store'
import { ElementTypes } from '@/types/slides'
import { fillDigit } from '@/utils/common'
import ThumbnailSlide from '@/views/components/ThumbnailSlide/index.vue'
import ScreenSlideList from '@/views/Screen/ScreenSlideList.vue'
import ScreenElement from '@/views/Screen/ScreenElement.vue'
import SlideThumbnails from '@/views/Screen/SlideThumbnails.vue'
import WritingBoardTool from '@/views/Screen/WritingBoardTool.vue'
import CountdownTimer from '@/views/Screen/CountdownTimer.vue'
import useSlideBackgroundStyle from '@/hooks/useSlideBackgroundStyle'
import useImport from '@/hooks/useImport'
import message from '@/utils/message'
import api, { API_URL } from '@/services/course'
import axios from '@/services/config'
import {currentVersion, lang} from '@/main'
import ShotWorkModal from './components/ShotWorkModal.vue'
import QAWorkModal from './components/QAWorkModal.vue'
import ChoiceWorkModal from './components/ChoiceWorkModal.vue'
import AIWorkModal from './components/AIWorkModal.vue'
import DialoguePanel from './components/DialoguePanel.vue'
import ChoiceStatistics from './components/ChoiceStatistics.vue'
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { Refresh } from '@icon-park/vue-next'
import answerTheResult from './components/answerTheResult.vue'
import choiceQuestionDetailDialog from './components/choiceQuestionDetailDialog.vue'

// 生成标准 UUID v4 格式（36位，符合 [0-9a-fA-F-] 格式）
const generateUUID = (): string => {
  // 优先使用浏览器原生 API
  if (typeof crypto !== 'undefined' && crypto.randomUUID) {
    return crypto.randomUUID()
  }
  
  // 降级方案：手动生成 UUID v4
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
    const r = (Math.random() * 16) | 0
    const v = c === 'x' ? r : (r & 0x3) | 0x8
    return v.toString(16)
  })
}


// 导入图片资源
import homeworkIcon from '@/assets/img/homework.png'
import homeworkActiveIcon from '@/assets/img/homework-active.png'
import dialogueIcon from '@/assets/img/dialogue.png'
import dialogueActiveIcon from '@/assets/img/dialogue-active.png'
import choiceIcon from '@/assets/img/choice.png'
import choiceActiveIcon from '@/assets/img/choice-active.png'

// 定义组件props
interface Props {
  courseid?: string | null
  userid?: string | null
  oid?: string | null
  org?: string | null
  cid?: string | null
  type?: string | null
}

const props = withDefaults(defineProps<Props>(), {
  courseid: null,
  userid: null,
  oid: null,
  org: null,
  cid: null,
  type: null,
})

// 图标组件通过全局注册，无需导入

const slidesStore = useSlidesStore()
const { slides, slideIndex, currentSlide, viewportSize, viewportRatio } = storeToRefs(slidesStore)

// 添加容器引用，用于计算幻灯片尺寸
const viewerCanvasRef = ref<HTMLElement>()

// 放映相关的状态
const canvasScale = ref(1) // 画布缩放比例
const isFullscreen = ref(false) // 是否全屏
const containerWidth = ref(0) // 容器宽度
const containerHeight = ref(0) // 容器高度

// 全屏工具相关状态
const rightToolsVisible = ref(false)
const writingBoardToolVisible = ref(false)
const timerlVisible = ref(false)
const slideThumbnailModelVisible = ref(false)
const laserPen = ref(false)
// 学生端激光笔覆盖层与位置（百分比）
const laserPenOverlay = ref<{ visible: boolean; xPct: number; yPct: number }>({ visible: false, xPct: 0, yPct: 0 })
const laserDotRef = ref<HTMLElement | null>(null)
let laserMoveRafId: number | null = null
let lastLayout: { w: number; h: number } | null = null
// 学生端覆盖层矩形（固定定位）
const laserOverlayRect = ref<{ left: number; top: number; width: number; height: number }>({ left: 0, top: 0, width: 0, height: 0 })
const laserOverlayStyle = computed(() => ({
  position: 'fixed' as const,
  left: laserOverlayRect.value.left + 'px',
  top: laserOverlayRect.value.top + 'px',
  width: laserOverlayRect.value.width + 'px',
  height: laserOverlayRect.value.height + 'px',
  pointerEvents: 'auto' as const,
  zIndex: 1000
}))
const refreshLaserOverlayRect = () => {
  const wrap = (viewerCanvasRef.value?.querySelector('.slide-list-wrap') as HTMLElement) || null
  if (!wrap) return
  const rect = wrap.getBoundingClientRect()
  laserOverlayRect.value = { left: rect.left, top: rect.top, width: rect.width, height: rect.height }
}
const updateLaserDotPosition = () => {
  if (!laserDotRef.value || !viewerCanvasRef.value) return
  const wrap = (viewerCanvasRef.value.querySelector('.slide-list-wrap') as HTMLElement) || viewerCanvasRef.value
  const w = wrap.clientWidth
  const h = wrap.clientHeight
  lastLayout = { w, h }
  const left = (laserPenOverlay.value.xPct / 100) * w
  const top = (laserPenOverlay.value.yPct / 100) * h
  // 减去半径使光点中心对齐
  laserDotRef.value.style.transform = `translate3d(${left - 12}px, ${top - 12}px, 0)`
}
const answerTheResultRef = ref(null)

// 计时状态指示器
const timerIndicator = ref<{ visible: boolean; isCountdown: boolean; startAt: string | null; durationSec: number | null; elapsedSec: number | null; remainingSec: number | null; finished: boolean }>({
  visible: false,
  isCountdown: false,
  startAt: null,
  durationSec: null,
  elapsedSec: null,
  remainingSec: null,
  finished: false,
})
const timerInterval = ref<number | null>(null)

// 作业提交状态
const isSubmitting = ref(false)

// 控制组件显示的开关
const showSlideList = ref(true)
const slideWidth = ref(0)
const slideHeight = ref(0)
const slideWidth2 = ref(0)
const slideHeight2 = ref(0)


// 添加loading状态
const isLoading = ref(false)
const workLoading = ref(false)
const studentLoading = ref(false)

// 作业数组
type WorkItem = {
  id?: string | number
  name: string
  type: number | string
  [key: string]: any
}
const workArray = ref<WorkItem[]>([])

// 作业弹窗相关
const selectedWork = ref<any>(null)
const visibleShot = ref(false)
const visibleQA = ref(false)
const visibleChoice = ref(false)
const visibleAI = ref(false)
const choiceQuestionDetailDialogOpenList = ref<number[]>([])

// 提供给子组件使用
provide('choiceQuestionDetailDialogOpenList', choiceQuestionDetailDialogOpenList)

// 当前作业选择/问答题的ID
const workId = ref<string>('')
// 当前作业的type
const toolType = ref<string>('')

// 回答结果收缩状态
const workPanelCollapsed = ref(true)
// 幻灯片导航收缩状态
const slidePanelCollapsed = ref(true)
// 右侧面板当前显示的内容：'homework' | 'dialogue' | 'choice'
const rightPanelMode = ref<'homework' | 'dialogue' | 'choice' | ''>('homework')

// 移除定时器相关代码，改用socket监听

const courseDetail = ref<any>({})
const studentArray = ref<any>([])

// 跟随模式相关状态
const isCreator = ref(false) // 是否为创建人
const isFollowModeActive = ref(false) // 跟随模式是否开启
const isFirstEnter = ref(true) // 是否首次进入

// 用户信息
const userJson = ref<any>(null)

// 计算未提交作业的学生
const unsubmittedStudents = computed(() => {
  if (!studentArray.value || !workArray.value) return []
  
  // 获取已提交作业的学生姓名
  const submittedNames = workArray.value.map(work => work.name)
  
  // 过滤出未提交作业的学生
  return studentArray.value.filter((student: any) => !submittedNames.includes(student.name))
})

const docSocket = ref<Y.Doc | null>(null)
const yMessage = ref<any | null>(null)
const yTimerState = ref<any | null>(null)
const yLaserState = ref<any | null>(null)
const yWritingBoardState = ref<any | null>(null)
// 独立的数组存储特殊类型的消息
const yTimerMessages = ref<any | null>(null)
const yLaserMessages = ref<any | null>(null)
const yWritingBoardMessages = ref<any | null>(null)
const providerSocket = ref<WebsocketProvider | null>(null)
// 学生端画图同步数据
const writingBoardSyncDataURL = ref<string | null>(null)
const writingBoardSyncBlackboard = ref<boolean | null>(null)
const mId = ref<string | null>(null)
// 画图延迟发送定时器
const drawingDelayTimer = ref<NodeJS.Timeout | null>(null)

// WebSocket重连相关变量
const reconnectAttempts = ref(0)
const maxReconnectAttempts = ref(5) // 最大重连次数
const reconnectInterval = ref(5000) // 重连间隔(毫秒)
const reconnectTimer = ref<NodeJS.Timeout | null>(null)
const isConnecting = ref(false)
const connectionStatus = ref<'disconnected' | 'connecting' | 'connected'>('disconnected')
// 认证 token 相关变量
const authToken = ref<string | null>(null)
const authTokenUpdateTimer = ref<NodeJS.Timeout | null>(null)
const socketCheckTimer = ref<NodeJS.Timeout | null>(null)

// 同步数据最大保留时间（40分钟）
const SYNC_DATA_MAX_AGE = 40 * 60 * 1000 // 40分钟 = 40 * 60 * 1000毫秒

//  切换选择题题目
const changeWorkIndex = (type:number) => {
  if (answerTheResultRef.value && answerTheResultRef.value.changeWorkIndex) {
    answerTheResultRef.value.changeWorkIndex(type)
  }
}

// 切换到回答结果
const switchToHomework = () => {
  rightPanelMode.value = 'homework'
  if (workPanelCollapsed.value) {
    workPanelCollapsed.value = false
  }
}

// 切换到对话区
const switchToDialogue = () => {
  rightPanelMode.value = 'dialogue'
  if (workPanelCollapsed.value) {
    workPanelCollapsed.value = false
  }
}

// 切换到选择题统计
const switchToChoice = () => {
  rightPanelMode.value = 'choice'
  if (workPanelCollapsed.value) {
    workPanelCollapsed.value = false
  }
}

// 自动切换到可用的面板
const autoSwitchToAvailablePanel = () => {
  // 如果当前在回答结果但没有iframe，自动切换到其他可用面板
  if (rightPanelMode.value === 'homework' && !currentSlideHasIframe.value && !currentSlideHasBilibiliVideo.value) {
    if (isChoiceQuestion.value) {
      rightPanelMode.value = 'choice'
      console.log('自动切换到统计面板')
    }
    else {
      rightPanelMode.value = 'dialogue'
      console.log('自动切换到对话面板')
    }
  }
  // 如果当前在统计面板但不是选择题，自动切换到对话面板
  else if (rightPanelMode.value === 'choice' && !isChoiceQuestion.value) {
    rightPanelMode.value = 'dialogue'
    console.log('自动切换到对话面板')
  }
  else if (currentSlideHasIframe.value && rightPanelMode.value !== 'homework' && !currentSlideHasBilibiliVideo.value) {
    rightPanelMode.value = 'homework'
  }
}

// 移除定时器相关函数，改用socket监听

// 收缩/展开后重新计算中间画布尺寸（在 DOM 更新并完成过渡后）
watch([() => workPanelCollapsed.value, () => slidePanelCollapsed.value], async () => {
  // 等待本次 DOM 更新
  await nextTick()
  // 先在下一帧计算一次，确保初步布局就绪
  requestAnimationFrame(() => {
    calculateScale()
  })
  // 再在过渡结束后（与左右栏 width .2s 过渡一致）复算一次，确保最终尺寸
  setTimeout(() => {
    calculateScale()
  }, 220)
}, { flush: 'post' })

const openWorkModal = (work: WorkItem) => {
  selectedWork.value = work
  const t = Number(work?.type)
  // if (t !== 1) {
  //   message.warning('暂未开发完成')
  //   return
  // }
  visibleShot.value = false
  visibleQA.value = false
  visibleChoice.value = false
  visibleAI.value = false
  if (t === 1) {
    visibleShot.value = true
  }
  else if (t === 3) {
    visibleQA.value = true
  }
  else if (t === 8) {
    visibleChoice.value = true
  }
  else if (t === 20) {
    visibleAI.value = true
  }
  else {
    message.info(lang.ssHwTypeUnsup)
  }
}

// 计算幻灯片尺寸的函数
const calculateSlideSize = () => {
  const slideWrapRef = isFullscreen.value ? document.body : viewerCanvasRef.value
  const winWidth = slideWrapRef?.clientWidth || 0
  const winHeight = slideWrapRef?.clientHeight || 0
  const winWidth2 = slideWrapRef && typeof slideWrapRef.clientWidth === 'number' ? slideWrapRef.clientWidth - 40 : 0
  const winHeight2 = slideWrapRef && typeof slideWrapRef.clientHeight === 'number' ? slideWrapRef.clientHeight - 85 : 0


  // 根据视口比例计算最佳尺寸
  if (winHeight / winWidth === viewportRatio.value) {
    slideWidth.value = winWidth
    slideHeight.value = winHeight
  }
  else if (winHeight / winWidth > viewportRatio.value) {
    slideWidth.value = winWidth
    slideHeight.value = winWidth * viewportRatio.value
  }
  else {
    slideWidth.value = winHeight / viewportRatio.value
    slideHeight.value = winHeight
  }

  // 这里的逻辑存在一些问题和可以优化的地方：
  // 1. winWidth2 或 winHeight2 可能为0，导致后续计算为NaN。
  // 2. slideHeight.value - slideHeight2.value < 85 这个判断，slideHeight2.value 可能还未被合理赋值，导致判断不准确。
  // 3. 反复赋值 slideHeight2/slideWidth2，可能导致宽高比被破坏。
  // 4. 代码重复，可合并优化。

  // 先按比例计算
  let tempWidth = 0
  let tempHeight = 0
  if (winHeight2 / winWidth2 === viewportRatio.value) {
    tempWidth = winWidth2
    tempHeight = winHeight2
  }
  else if (winHeight2 / winWidth2 > viewportRatio.value) {
    tempWidth = winWidth2
    tempHeight = winWidth2 * viewportRatio.value
  }
  else {
    tempHeight = winHeight2
    tempWidth = winHeight2 / viewportRatio.value
  }
  // 检查底部空间
  if (slideHeight.value - tempHeight < 85) {
    tempHeight = Math.max(slideHeight.value - 85, 0)
    tempWidth = tempHeight > 0 ? tempHeight / viewportRatio.value : 0
  }
  slideWidth2.value = tempWidth
  slideHeight2.value = tempHeight


  console.log('calculateSlideSize', slideWidth.value, slideHeight.value, viewportRatio.value, canvasScale.value)
  console.log('calculateSlideSize', slideWidth2.value, slideHeight2.value, viewportRatio.value, canvasScale.value)
}

// 使用编辑模式的缩放逻辑
const calculateScale = () => {
  console.log('calculateScale 开始执行')


  // 获取容器尺寸
  const container = viewerCanvasRef.value || document.querySelector('.viewer-canvas')
  if (container) {
    containerWidth.value = container.clientWidth
    containerHeight.value = container.clientHeight

    console.log('容器尺寸:', {
      width: containerWidth.value,
      height: containerHeight.value
    })

    // 计算基础尺寸
    const baseWidth = viewportSize.value
    const baseHeight = viewportSize.value * viewportRatio.value

    console.log('基础尺寸:', {
      baseWidth,
      baseHeight,
      viewportSize: viewportSize.value,
      viewportRatio: viewportRatio.value
    })

    // 计算缩放比例，让幻灯片能够合理利用空间
    const scaleX = containerWidth.value / baseWidth
    const scaleY = containerHeight.value / baseHeight

    console.log('原始缩放比例:', { scaleX, scaleY })

    // 选择较小的缩放比例，确保幻灯片完全显示且居中，留10%边距
    const scale = Math.min(scaleX, scaleY) * 0.9

    console.log('最终缩放比例:', scale)
    canvasScale.value = isFullscreen.value ? 1 : props.type == '1' ? 1 : 1
    // canvasScale.value = 1
  }
  else {
    console.error('找不到容器元素')
  }
  // 计算幻灯片尺寸
  nextTick(() => {
    setTimeout(() => {
      calculateSlideSize()  
      if (laserPenOverlay.value.visible) {
        refreshLaserOverlayRect()
        requestAnimationFrame(updateLaserDotPosition)
      }
    }, 500)
  })
}

// 简化：直接使用放映功能的缩放逻辑
const resetZoom = () => {
  calculateScale()
}

// 背景样式
const background = computed(() => currentSlide.value?.background)
const { backgroundStyle } = useSlideBackgroundStyle(background)

// 计算当前幻灯片的元素列表
const elementList = computed(() => {
  return currentSlide.value?.elements || []
})

// 检查当前是否为选择题（toolType为45）
const isChoiceQuestion = computed(() => {
  const frame = elementList.value.find(element => element.type === ElementTypes.FRAME)
  return frame?.toolType === 45
})

// 检测当前幻灯片是否包含iframe元素
const currentSlideHasIframe = computed(() => {
  console.log('elementList.value', elementList.value)
  return elementList.value.some(element => element.type === ElementTypes.FRAME)
})

// 检测当前幻灯片是否包含B站视频
const currentSlideHasBilibiliVideo = computed(() => {
  return elementList.value.some(element => 
    element.type === ElementTypes.FRAME && (element.toolType === 75 || element.toolType === 74 || element.toolType === 76)
  )
})

// 跳转到指定幻灯片
const goToSlide = (index: number) => {
  console.log('goToSlide 被调用，目标索引:', index)
  console.log('当前索引:', slideIndex.value)
  
  if (index >= 0 && index < slides.value.length) {
    slidesStore.updateSlideIndex(index)
    console.log('更新后的索引:', slideIndex.value)
  }
  else {
    console.warn('goToSlide: 无效的索引:', index)
  }
}

// 上一页
const previousSlide = () => {
  if (slideIndex.value > 0) {
    const newIndex = slideIndex.value - 1
    console.log('上一页，从', slideIndex.value, '到', newIndex)
    slidesStore.updateSlideIndex(newIndex)
  }
}

// 下一页
const nextSlide = () => {
  if (slideIndex.value < slides.value.length - 1) {
    const newIndex = slideIndex.value + 1
    console.log('下一页，从', slideIndex.value, '到', newIndex)
    slidesStore.updateSlideIndex(newIndex)
  }
}



// 监听幻灯片切换，清除不匹配的画图数据
watch(() => slideIndex.value, () => {
  if (props.type == '2' && yWritingBoardState.value && currentSlide.value) {
    const snap = yWritingBoardState.value.toJSON()
    console.log('📝 幻灯片切换，检查画图数据:', { snap, currentSlideId: currentSlide.value.id })
    if (snap && snap.slideId === currentSlide.value.id && snap.dataURL) {
      // 当前幻灯片有画图数据，显示
      writingBoardSyncDataURL.value = snap.dataURL
      writingBoardSyncBlackboard.value = snap.blackboard !== undefined ? snap.blackboard : null
      console.log('📝 当前幻灯片有画图数据，显示画图工具，小黑板状态:', writingBoardSyncBlackboard.value)
    }
    else {
      // 当前幻灯片没有画图数据，隐藏
      writingBoardSyncDataURL.value = null
      writingBoardSyncBlackboard.value = null
      console.log('📝 当前幻灯片没有画图数据，隐藏画图工具')
    }
  }
})

// 监听 currentSlide 变化，确保刷新后能获取到画图状态
watch(() => currentSlide.value?.id, (newSlideId, oldSlideId) => {
  // 只在学生端且跟随模式下检查
  if (props.type == '2' && isFollowModeActive.value && yWritingBoardState.value && newSlideId) {
    const snap = yWritingBoardState.value.toJSON()
    console.log('📝 currentSlide变化，检查画图数据:', { snap, newSlideId, oldSlideId })
    if (snap && snap.slideId === newSlideId && snap.dataURL) {
      // 当前幻灯片有画图数据，显示
      writingBoardSyncDataURL.value = snap.dataURL
      writingBoardSyncBlackboard.value = snap.blackboard !== undefined ? snap.blackboard : null
      console.log('📝 currentSlide变化后找到画图数据，显示画图工具，小黑板状态:', writingBoardSyncBlackboard.value)
    }
    else if (snap && snap.slideId !== newSlideId) {
      // 当前幻灯片没有画图数据，隐藏
      writingBoardSyncDataURL.value = null
      writingBoardSyncBlackboard.value = null
      console.log('📝 currentSlide变化后没有匹配的画图数据，隐藏画图工具')
    }
  }
}, { immediate: true })

// 监听slideIndex变化，调用getWork
watch(() => slideIndex.value, (newIndex, oldIndex) => {
  console.log('slideIndex变化，调用getWork', { newIndex, oldIndex })
  if (newIndex !== oldIndex && typeof newIndex === 'number') {
    // 检查新页面是否有iframe
    const hasIframe = currentSlideHasIframe.value
    if (hasIframe) {
      console.log('当前页面有iframe，获取作业数据')
      console.log('触发getWork，当前幻灯片索引:', newIndex)
      getWork()
    }
    if (props.type == '1' && isFollowModeActive.value && isCreator.value) {
      
      api.updateCourseFollowC(newIndex, props.courseid as string)
      sendMessage({slideIndex: newIndex, courseid: props.courseid, type: 'slideIndex'})
    }
    // 自动切换到可用的面板
    autoSwitchToAvailablePanel()

    if (isSubmitting.value) {
      isSubmitting.value = false
    }
    if (timerlVisible.value) {
      timerlVisible.value = false
    }
  }

  getWorkId()
}, { immediate: false, deep: false })

// 监听iframe状态变化，自动切换面板
watch(() => currentSlideHasIframe.value, (hasIframe) => {
  if (!hasIframe) {
    autoSwitchToAvailablePanel()
  }
}, { immediate: false })

// 全屏
const enterFullscreen = () => {
  if (document.fullscreenElement) {
    document.exitFullscreen()
  }
  else {
    document.documentElement.requestFullscreen()
  }
}

// 监听全屏状态变化
const handleFullscreenChange = () => {
  isFullscreen.value = !!document.fullscreenElement

  if (isFullscreen.value) {
    // 全屏时不需要计算缩放，直接使用放映功能
    console.log('进入全屏模式')
  }
  else {
    // 退出全屏时重置所有工具状态并重新计算缩放比例
    console.log('退出全屏模式，重置工具状态')

    // 重置所有工具状态
    rightToolsVisible.value = false
    writingBoardToolVisible.value = false
    slideThumbnailModelVisible.value = false
    laserPen.value = false

    // 重新计算缩放比例
    nextTick(() => {
      setTimeout(() => {
        calculateScale()
      }, 1000)
    })
  }
}

const getWorkId = () => {
  // 修复类型报错：elementList 可能没有 toolType 和 url 字段，需先判断类型
  const element = elementList.value.find((i:any) => i.type === 'frame')
  console.log(element)
  if (
    element &&
    typeof element === 'object' &&
    ('toolType' in element) &&
    (element as any).toolType !== undefined &&
    ((element as any).toolType === 45 || (element as any).toolType === 15 || (element as any).toolType === 73 || (element as any).toolType === 72)
  ) {
    // 提取链接中的id参数
    const url = (element as any).url
    let id = ''
    toolType.value = (element as any).toolType
    if (typeof url === 'string') {
      const match = url.match(/[?&]id=([^&]+)/)
      if (match) {
        id = match[1]
      }
      workId.value = id
    }
    else {
      workId.value = ''
    }
  }
  else {
    workId.value = ''
  }
}

// 处理画图关闭事件
const handleWritingBoardClose = () => {
  // 学生端只读模式下，不应该响应关闭事件（因为关闭按钮已隐藏）
  // 只有老师端可以关闭
  if (props.type == '2') {
    console.log('📝 学生端收到关闭事件，但只读模式下不应该关闭，忽略')
    return
  }
  writingBoardToolVisible.value = false
  // 老师端关闭时，清空共享状态并通知学生端
  if (props.type == '1' && isFollowModeActive.value && isCreator.value) {
    clearWritingBoardState()
  }
}

// 清空画图共享状态（仅创建人）
const clearWritingBoardState = () => {
  try {
    if (props.type == '1' && isCreator.value && yWritingBoardState.value) {
      docSocket.value?.transact(() => {
        yWritingBoardState.value.clear()
      })
      sendMessage({ 
        type: 'writing_board_close', 
        courseid: props.courseid 
      })
    }
  }
  catch (e) {
    console.warn('清空画图状态失败', e)
  }
}

// 处理小黑板状态变化（老师端）
const handleBlackboardChange = (blackboard: boolean) => {
  if (props.type == '1' && isFollowModeActive.value && isCreator.value) {
    // 同步到共享 Map
    if (yWritingBoardState.value) {
      docSocket.value?.transact(() => {
        yWritingBoardState.value.set('blackboard', blackboard)
      })
    }
    // 广播消息
    sendMessage({ 
      type: 'writing_board_blackboard', 
      blackboard: blackboard,
      courseid: props.courseid 
    })
  }
}

// 处理画图结束事件（老师端）
const handleDrawingEnd = (dataURL: string) => {
  if (props.type == '1' && isFollowModeActive.value && isCreator.value) {
    // 同步到共享 Map
    if (yWritingBoardState.value) {
      docSocket.value?.transact(() => {
        yWritingBoardState.value.set('slideId', currentSlide.value.id)
        yWritingBoardState.value.set('dataURL', dataURL)
        // 保持小黑板状态
        const currentBlackboard = yWritingBoardState.value.get('blackboard')
        if (currentBlackboard !== undefined) {
          yWritingBoardState.value.set('blackboard', currentBlackboard)
        }
      })
    }

    // 延迟5秒后广播消息，避免频繁发送
    if (drawingDelayTimer.value) {
      clearTimeout(drawingDelayTimer.value)
    }

    drawingDelayTimer.value = setTimeout(() => {
      const currentBlackboard = yWritingBoardState.value?.get('blackboard') || false
      sendMessage({
        type: 'writing_board_update',
        slideId: currentSlide.value.id,
        dataURL: dataURL,
        blackboard: currentBlackboard,
        courseid: props.courseid
      })
      drawingDelayTimer.value = null
    }, 5000) // 延迟5秒发送
  }
}

// 应用画图共享状态（任意端）
const applyWritingBoardStateSnapshot = (snap: any) => {
  console.log('📝 应用画图状态快照:', snap, '当前幻灯片ID:', currentSlide.value?.id, '跟随模式:', isFollowModeActive.value, '用户类型:', props.type)
  if (!snap || !snap.dataURL || typeof snap.dataURL !== 'string' || snap.dataURL.trim() === '') {
    writingBoardSyncDataURL.value = null
    writingBoardSyncBlackboard.value = null
    console.log('📝 画图状态为空，隐藏画图工具')
    return
  }
  const slideId = snap.slideId
  const dataURL = snap.dataURL
  const blackboardState = snap.blackboard !== undefined ? snap.blackboard : null
  // 只有当前幻灯片匹配时才显示
  if (slideId && currentSlide.value && slideId === currentSlide.value.id) {
    writingBoardSyncDataURL.value = dataURL
    writingBoardSyncBlackboard.value = blackboardState
    console.log('📝 画图数据匹配，显示画图工具，数据长度:', dataURL.length, '小黑板状态:', blackboardState, '显示条件:', {
      type: props.type,
      isFollowModeActive: isFollowModeActive.value,
      hasData: !!writingBoardSyncDataURL.value
    })
  }
  else {
    writingBoardSyncDataURL.value = null
    writingBoardSyncBlackboard.value = null
    console.log('📝 画图数据不匹配，隐藏画图工具', { slideId, currentSlideId: currentSlide.value?.id })
  }
}

// 切换激光笔模式
const toggleLaserPen = () => {
  laserPen.value = !laserPen.value
  console.log('激光笔状态:', laserPen.value ? '开启' : '关闭')
  // 老师端广播激光笔开关
  if (props.type == '1') {
    sendMessage({ type: 'laser_toggle', enabled: laserPen.value, courseid: props.courseid })
    // 同步到共享 Map，方便后来者拿到状态
    if (yLaserState.value) {
      if (laserPen.value) {
        const state: any = { enabled: true }
        if (lastSent.x >= 0 && lastSent.y >= 0) {
          state.x = lastSent.x; state.y = lastSent.y
        }
        docSocket.value?.transact(() => {
          Object.entries(state).forEach(([k, v]) => yLaserState.value.set(k, v as any))
        })
      }
      else {
        // 关闭时清空共享状态
        docSocket.value?.transact(() => {
          yLaserState.value.clear()
        })
      }
    }
  }
}

// 老师端移动时广播激光笔位置（百分比坐标）
let sendRafPending = false
let lastSent = { x: -1, y: -1 }
const handleLaserMove = (e: MouseEvent) => {
  if (!(props.type == '1' && laserPen.value)) return
  // 始终以中间画布 .slide-list-wrap 为基准，避免外层左右留白导致的偏差
  const wrap = (viewerCanvasRef.value?.querySelector('.slide-list-wrap') as HTMLElement) || (e.currentTarget as HTMLElement)
  const rect = wrap.getBoundingClientRect()
  const x = Math.min(Math.max(e.clientX - rect.left, 0), rect.width)
  const y = Math.min(Math.max(e.clientY - rect.top, 0), rect.height)
  const xPct = (x / rect.width) * 100
  const yPct = (y / rect.height) * 100
  // 小幅度移动忽略（阈值 0.4%）
  if (Math.abs(xPct - lastSent.x) < 0.4 && Math.abs(yPct - lastSent.y) < 0.4) return
  lastSent = { x: xPct, y: yPct }
  if (sendRafPending) return
  sendRafPending = true
  requestAnimationFrame(() => {
    sendRafPending = false
    // sendMessage 中已经直接更新 Map，不需要重复更新
    sendMessage({ type: 'laser_move', x: lastSent.x, y: lastSent.y, courseid: props.courseid })
  })
}

// 清空激光笔共享状态（仅创建人）
const clearLaserState = () => {
  try {
    if (props.type == '1' && isCreator.value && yLaserState.value) {
      docSocket.value?.transact(() => {
        yLaserState.value.clear()
      })
      sendMessage({ type: 'laser_toggle', enabled: false, courseid: props.courseid })
    }
  }
  catch (e) {
    console.warn('清空激光笔状态失败', e)
  }
}

// 清空所有同步状态（仅创建人）
const clearAllSyncStates = () => {
  try {
    if (props.type == '1' && isCreator.value && docSocket.value) {
      console.log('🧹 创建老师退出，清空所有同步状态')
      docSocket.value.transact(() => {
        // 清空普通消息
        const messageArray = docSocket.value?.getArray?.('message')
        if (messageArray) {
          messageArray.delete(0, messageArray.length)
        }
        // 清空计时器消息数组
        const timerMessagesArray = docSocket.value?.getArray?.('timerMessages')
        if (timerMessagesArray) {
          timerMessagesArray.delete(0, timerMessagesArray.length)
        }
        // 清空激光笔消息数组
        const laserMessagesArray = docSocket.value?.getArray?.('laserMessages')
        if (laserMessagesArray) {
          laserMessagesArray.delete(0, laserMessagesArray.length)
        }
        // 清空画图消息数组
        const writingBoardMessagesArray = docSocket.value?.getArray?.('writingBoardMessages')
        if (writingBoardMessagesArray) {
          writingBoardMessagesArray.delete(0, writingBoardMessagesArray.length)
        }
        // 清空计时器状态
        const timerStateMap = docSocket.value?.getMap?.('timerState')
        if (timerStateMap) {
          timerStateMap.clear()
        }
        // 清空激光笔状态
        const laserStateMap = docSocket.value?.getMap?.('laserState')
        if (laserStateMap) {
          laserStateMap.clear()
        }
        // 清空画图状态
        const writingBoardStateMap = docSocket.value?.getMap?.('writingBoardState')
        if (writingBoardStateMap) {
          writingBoardStateMap.clear()
        }
      })
    }
  }
  catch (e) {
    console.warn('清空所有同步状态失败', e)
  }
}

// 获取导入导出功能
const { readJSON, exportJSON2, getFile, getFile2 } = useImport()

// 根据iframe的URL查找对应的幻灯片索引
const findSlideIndexByIframeUrl = (iframeUrl: string): number => {
  try {
    console.log('查找iframe对应的幻灯片索引，iframe URL:', iframeUrl)

    // 遍历所有幻灯片，查找包含该iframe URL的幻灯片
    for (let i = 0; i < slides.value.length; i++) {
      const slide = slides.value[i]

      // 检查幻灯片的元素中是否有iframe
      if (slide.elements && slide.elements.length > 0) {
        for (const element of slide.elements) {
          // 检查是否是iframe元素
          if (element.type === ElementTypes.FRAME) {
            // 检查iframe的src是否匹配
            if (element.url === iframeUrl) {
              console.log(`找到匹配的幻灯片，索引: ${i}, 幻灯片ID: ${slide.id}`)
              return i
            }
          }
        }
      }
    }

    // 如果没有找到匹配的幻灯片，返回当前幻灯片索引
    console.log('未找到匹配的幻灯片，使用当前幻灯片索引:', slideIndex.value)
    return slideIndex.value
  }
  catch (error) {
    console.error('查找幻灯片索引时出错:', error)
    return slideIndex.value
  }
}

// 处理iframe链接，为包含workPage的iframe添加必要参数
// 处理iframe链接，为包含workPage的iframe添加必要参数
const processIframeLinks = async () => {
  try {
    console.log('开始处理iframe链接')
    console.log('当前props:', { courseid: props.courseid, userid: props.userid })

    // 从slides数据中查找包含iframe的元素
    let hasIframe = false
    // 由于有异步操作，需整体用Promise.all处理
    const updatedSlides = await Promise.all(
      slides.value.map(async (slide, slideIndex) => {
        if (slide.elements && slide.elements.length > 0) {
          // 这里不能直接用async map，否则会导致类型不对
          const updatedElements = await Promise.all(
            slide.elements.map(async (element) => {
              // 检查是否是iframe元素
              if (element.type === ElementTypes.FRAME && element.url) {
                let iframeSrc = element.url
                const toolType = element.toolType
                console.log('当前版本:', currentVersion)
                // 替换beta环境域名
                iframeSrc = iframeSrc.replace(/https?:\/\/beta\.pbl\.cocorobo\.cn/g, 'https://pbl.cocorobo.cn')

                // 根据当前版本统一域名
                const versionMap = {
                  cn: /cocorobo\.(hk|com)/g,
                  hk: /cocorobo\.(cn|com)/g,
                  com: /cocorobo\.(cn|hk)/g
                }

                const targetDomain = `cocorobo.${currentVersion}`
                iframeSrc = iframeSrc.replace(versionMap[currentVersion], targetDomain)

                if (iframeSrc.includes('setWorkPage')) {
                  iframeSrc = iframeSrc.replace(/setWorkPage/g, 'workPageNew')
                }

                if (iframeSrc.includes('workPage')) {
                  hasIframe = true
                  console.log(`处理幻灯片 ${slideIndex + 1} 中的iframe链接:`, iframeSrc)

                  try {
                    // 解析URL，处理hash部分
                    let baseUrl = iframeSrc
                    let hashPart = ''

                    // 分离base URL和hash部分
                    if (iframeSrc.includes('#')) {
                      const parts = iframeSrc.split('#')
                      baseUrl = parts[0]
                      hashPart = parts[1]
                    }

                    // 构建新的hash部分，添加参数
                    // 使用当前幻灯片索引作为task参数
                    let newHash = hashPart
                    if (newHash.includes('?')) {
                      // 如果hash中已经有查询参数，添加&
                      newHash += `&courseid=${props.courseid || ''}&userid=${props.userid || ''}&stage=0&task=${slideIndex}&tool=0`
                    } 
                    else {
                      // 如果hash中没有查询参数，添加?
                      newHash += `?courseid=${props.courseid || ''}&userid=${props.userid || ''}&stage=0&task=${slideIndex}&tool=0`
                    }

                    // 构建新的URL
                    let newUrl = `${baseUrl}#${newHash}`

                    console.log(`幻灯片 ${slideIndex + 1} 的iframe链接已更新:`, newUrl)
                      
                    if (window.location.href.includes('beta') && !newUrl.includes('beta')) {
                      newUrl = newUrl.replace('pbl.cocorobo.cn', 'beta.pbl.cocorobo.cn')
                    }
                    else if (newUrl.includes('beta') && !window.location.href.includes('beta')) {
                      newUrl = newUrl.replace('beta.pbl.cocorobo.cn', 'pbl.cocorobo.cn')
                    }
                    // 返回更新后的元素
                    return {
                      ...element,
                      url: newUrl
                    }
                  } 
                  catch (error) {
                    console.error(`处理幻灯片 ${slideIndex + 1} 的iframe链接时出错:`, error)
                    return element
                  }
                }
                else if (iframeSrc.includes('aichat.cocorobo') || iframeSrc.includes('knowledge.cocorobo')) {
                  hasIframe = true
                  try {
                    // 解析URL，处理hash部分
                    let baseUrl = iframeSrc
                    let hashPart = ''
                    let isHashPart = false
                    // 分离base URL和hash部分
                    if (iframeSrc.includes('#')) {
                      const parts = iframeSrc.split('#')
                      baseUrl = parts[0]
                      hashPart = parts[1]
                      isHashPart = true
                    }

                    // 构建新的hash部分，添加参数
                    // 使用当前幻灯片索引作为task参数
                    let newHash = hashPart
                    if (newHash.includes('?')) {
                      // 如果hash中已经有查询参数，添加&
                      newHash += `&courseid=${props.courseid || ''}&layout=laptop`
                    } 
                    else {
                      // 如果hash中没有查询参数，添加?
                      newHash += `?courseid=${props.courseid || ''}&layout=laptop`
                    }

                    // 构建新的URL
                    let newUrl = `${baseUrl}#${newHash}`
                    if (!isHashPart) {
                      newUrl = `${baseUrl}${newHash}`
                    }

                    console.log(`幻灯片 ${slideIndex + 1} 的iframe链接已更新:`, newUrl)
                    // 返回更新后的元素
                    return {
                      ...element,
                      url: newUrl
                    }
                  }
                  catch (error) {
                    console.error(`处理幻灯片 ${slideIndex + 1} 的iframe链接时出错:`, error)
                    return element
                  }
                }
                else if (toolType == 76) {
                  hasIframe = true
                  try {
                    // 解析URL，处理hash部分
                    let baseUrl = iframeSrc
                    let hashPart = ''

                    // 分离base URL和hash部分
                    if (iframeSrc.includes('#')) {
                      const parts = iframeSrc.split('#')
                      baseUrl = parts[0]
                      hashPart = parts[1]
                    }

                    // 构建新的hash部分，添加参数
                    // 使用当前幻灯片索引作为task参数
                    let newHash = hashPart
                    if (newHash.includes('?')) {
                      // 如果hash中已经有查询参数，添加&
                      newHash += `&mode=pptMode`
                    } 
                    else {
                      // 如果hash中没有查询参数，添加?
                      newHash += `?mode=pptMode`
                    }

                    // 构建新的URL
                    const newUrl = `${baseUrl}#${newHash}`

                    console.log(`幻灯片 ${slideIndex + 1} 的iframe链接已更新:`, newUrl)
                    // 返回更新后的元素
                    return {
                      ...element,
                      url: newUrl
                    }
                  }
                  catch (error) {
                    console.error(`处理幻灯片 ${slideIndex + 1} 的iframe链接时出错:`, error)
                    return element
                  }
                }
                else if (toolType == 73) {
                  hasIframe = true
                  
                  // 先尝试获取iframe的contentWindow，如果获取不到再使用HTML方式
                  try {
                    // 创建一个临时的iframe来测试是否能获取contentWindow
                    const tempIframe = document.createElement('iframe')
                    tempIframe.style.display = 'none'
                    tempIframe.src = iframeSrc
                    
                    // 先将临时iframe添加到body，否则onload事件不会触发
                    document.body.appendChild(tempIframe)
                    // 等待iframe加载完成
                    await new Promise((resolve, reject) => {
                      tempIframe.onload = resolve
                      tempIframe.onerror = reject
                      // 可选：设置超时时间，避免长时间无响应
                      setTimeout(() => reject(new Error('Timeout')), 5000)
                    })
                    
                    // 尝试获取contentWindow
                    if (tempIframe.contentWindow && tempIframe.contentWindow.document) {
                      console.log(`iframe ${iframeSrc} 可以获取contentWindow，使用直接加载方式`)
                      // 移除临时iframe
                      document.body.removeChild(tempIframe)
                      
                      return {
                        ...element,
                        isHTML: false,
                        url: iframeSrc
                      }
                    } 
                    // 加载完成但无法获取contentWindow，也要移除iframe
                    document.body.removeChild(tempIframe)
                    
                  }
                  catch (error) {
                    console.log(`iframe ${iframeSrc} 无法获取contentWindow，使用HTML方式:`, error)
                  }
                  
                  // 如果无法获取contentWindow，使用HTML方式
                  let html = null
                  try {
                    console.log(`getFile2 失败，尝试使用 getHTML:`, error2)
                    try {
                      html = await api.getHTML(iframeSrc)
                      console.log('getHTML 成功获取内容:', html)
                    }
                    catch (htmlError) {
                      console.error('getHTML 也失败:', htmlError)
                      console.error('无法获取内容: getFile、getFile2 和 getHTML 都失败了')
                      // throw new Error(`无法获取内容: getFile、getFile2 和 getHTML 都失败了`)
                    }
                  }
                  catch (error) {
                    console.log(`getFile 失败，尝试使用 getFile2:`, error)
                    try {
                      const fileData = await getFile(iframeSrc)
                      if (fileData && fileData.data) {
                        const uint8Array = new Uint8Array(fileData.data)
                        html = new TextDecoder('utf-8').decode(uint8Array)
                        console.log('getFile 成功获取内容:', html)
                      }
                    }
                    catch (error2) {
                      const fileData2 = await getFile2(iframeSrc)
                      if (fileData2 && fileData2.data) {
                        const uint8Array = new Uint8Array(fileData2.data)
                        html = new TextDecoder('utf-8').decode(uint8Array)
                        console.log('getFile2 成功获取内容:', html)
                      }
                    }
                  }
                  console.log(`处理幻灯片 ${slideIndex + 1} 中的iframe链接:`, iframeSrc)
                  return {
                    ...element,
                    isHTML: true,  
                    url: html
                  }
                }
              }

              // 不是iframe元素或不需要处理，直接返回
              return element
            })
          )

          // 返回更新后的幻灯片
          return {
            ...slide,
            elements: updatedElements
          }
        }

        // 没有元素的幻灯片直接返回
        return slide
      })
    )

    if (hasIframe) {
      console.log('找到iframe元素，更新slides数据')
      // 更新store中的slides数据
      slidesStore.setSlides(updatedSlides)
      console.log('slides数据更新完成')
    }
    else {
      console.log('未找到包含workPage的iframe元素')
    }

    console.log('iframe链接处理完成')
  }
  catch (error) {
    console.error('处理iframe链接时出错:', error)
  }
}

// 导入JSON功能
const importJSON = (jsonData: any) => {
  try {
    console.log('Student importJSON 开始执行')

    const result = readJSON(jsonData, true)

    if (result.success) {
      console.log('Student importJSON 成功，开始重新渲染')

      // 强制重新渲染：先隐藏组件
      showSlideList.value = false

      // 重新计算画布尺寸和缩放比例
      nextTick(() => {
        calculateScale()

        // 延迟500ms后重新显示组件，确保重新渲染完成
        setTimeout(() => {
          showSlideList.value = true
          // 只有当当前页面存在iframe时才获取作业数据
          if (currentSlideHasIframe.value && props.type == '1') {
            getWork()
          }
          selectCourseSLook(1)
          console.log('组件重新渲染完成')
        }, 500)
      })

      return true
    }

    console.error('Student importJSON 失败:', result.error)
    return false

  }
  catch (error) {
    console.error('Student importJSON 执行失败:', error)
    return false
  }
}

// 导出JSON功能
const exportJSON = () => {
  try {
    console.log('Student exportJSON 开始执行，调用 useImport.exportJSON2')

    // 直接调用 useImport 中的 exportJSON2 函数
    const exportData = exportJSON2()

    if (exportData) {

      return exportData
    }
    console.error('Student exportJSON 失败: exportJSON2 返回空数据')
    return false

  }
  catch (error) {
    console.error('Student exportJSON 执行失败:', error)
    return false
  }
}

// 返回编辑器
const backToEditor = () => {
  // 通过路由跳转到编辑模式
  window.location.href = '/'
}

const submitWork = async (slideIndex: number, atool: string, content: string, type: string) => {
  const res = await api.submitWork({
    uid: props.userid as string,
    cid: props.courseid as string,
    stage: '0',
    task: String(slideIndex), // 转为字符串
    tool: '0',
    atool: atool,
    content: content,
    type: type
  })
  getWork()
  console.log(res)
}
// 文件上传到AWS S3的函数
const uploadFile = (file: File): Promise<string> => {
  return new Promise((resolve, reject) => {
    try {
      // 检查AWS SDK是否可用
      if (!(window as any).AWS) {
        reject(new Error('AWS SDK not loaded'))
        return
      }

      const credentials = {
        accessKeyId: 'AKIATLPEDU37QV5CHLMH',
        secretAccessKey: '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'
      }

      // 配置AWS
      ;(window as any).AWS.config.update(credentials)
      ;(window as any).AWS.config.region = 'cn-northwest-1'

      // 创建S3实例
      const bucket = new (window as any).AWS.S3({ params: { Bucket: 'ccrb' } })

      if (file) {
        // 生成唯一的文件名
        const fileExtension = file.name.split('.').pop()
        const fileName = `${file.name.split('.')[0]}_${Date.now()}.${fileExtension}`

        const params = {
          Key: fileName,
          ContentType: file.type,
          Body: file,
          ACL: 'public-read'
        }

        const options = {
          partSize: 5 * 1024 * 1024, // 2GB分片
          queueSize: 2,
          leavePartsOnError: true
        }

        bucket
          .upload(params, options)
          .on('httpUploadProgress', (evt: any) => {
            // 这里可以添加进度条逻辑
            const progress = Math.round((evt.loaded * 100) / evt.total)
            console.log(`Uploaded: ${progress}%`)
          })
          .send((err: any, data: any) => {
            if (err) {
              console.error('Upload failed:', err)
              message.error(lang.ssFileUploadFail)
              reject(err)
            }
            else {
              console.log('Upload successful:', data.Location)
              resolve(data.Location)
            }
          })
      }
      else {
        reject(new Error('No file provided'))
      }
    }
    catch (error) {
      console.error('Upload error:', error)
      reject(error)
    }
  })
}
// 作业提交功能（优化版）
const handleHomeworkSubmit = async () => {
  console.log('作业提交按钮被点击')

  // 防抖：如果正在提交中，直接返回
  if (isSubmitting.value) {
    console.log('作业正在提交中，忽略重复点击')
    return
  }

  isSubmitting.value = true
  let homeworkContent: string = lang.ssHwSubmit // 默认作业内容
  let hasSubmitWork = false // 标记是否成功提交作业

  try {
    // 获取所有iframe元素
    const iframes = document.querySelectorAll('.viewer-canvas .screen-slide')[slideIndex.value].querySelectorAll('iframe')
    console.log('找到iframe元素数量:', iframes.length)

    if (iframes.length === 0) {
      message.warning('当前页面没有找到iframe元素')
      return
    }

    for (let i = 0; i < iframes.length; i++) {
      const iframe = iframes[i] as HTMLIFrameElement
      const iframeSrc = iframe.src
      console.log(`iframe ${i + 1} 链接:`, iframeSrc)

      // 检查iframe链接是否包含workPage
      if (iframeSrc && iframeSrc.includes('workPage')) {
        console.log('找到包含workPage的iframe，尝试执行submitWork')

        try {
          const iframeWindow = iframe.contentWindow as Window & { submitWork?: (...args: any[]) => unknown }

          if (iframeWindow && typeof iframeWindow.submitWork === 'function') {
            console.log('执行iframe中的submitWork方法，参数可变')
            const iframeSlideIndex = slideIndex.value
            const submitArgs = [iframeSlideIndex]

            // 支持同步和异步submitWork
            const result = await iframeWindow.submitWork(...submitArgs)
            console.log('submitWork同步执行完成')
            // 尝试从结果中获取作业内容
            if (result && typeof result === 'object') {
              homeworkContent = JSON.stringify(result)
            }
            else if (result) {
              homeworkContent = String(result)
            }
            else {
              homeworkContent = lang.ssHwSubmitWp
            }
            message.success(lang.ssHwSubmitSucc)
            hasSubmitWork = true
            
            // 发送作业提交成功的socket消息
            sendMessage({
              type: 'homework_submitted',
              courseid: props.courseid,
              slideIndex: slideIndex.value,
              userid: props.userid
            })


            break
          } 
          else {
            console.log('iframe中没有找到submitWork方法')
            isSubmitting.value = false
          }
        } 
        catch (error) {
          console.error('访问iframe内容时出错:', error)
          isSubmitting.value = false
        }
      } 
      else if (iframeSrc && (iframeSrc.includes('aichat.cocorobo') || iframeSrc.includes('knowledge.cocorobo'))) {
        console.log('找到包含aichat.cocorobo或knowledge.cocorobo的iframe，尝试执行submitWork')
        // 由于TS类型检查，需通过 any 绕过类型限制
        const iframeWindow = iframe.contentWindow as any
        if (iframeWindow && iframeWindow.exposed_outputs) {
          if (iframeWindow.exposed_outputs.length === 0) {
            message.warning('没有找到作业内容')
            hasSubmitWork = true
            continue
          }
          console.log('执行iframe中的submitWork方法，参数可变')
          const iframeSlideIndex = slideIndex.value
          const jsonString = JSON.stringify(iframeWindow.exposed_outputs)
          
          // 将 JSON 字符串转成文件并上传
          try {
            const blob = new Blob([jsonString], { type: 'application/json' })
            const file = new File([blob], `ai_work_${Date.now()}.json`, { type: 'application/json' })
            const fileUrl = await uploadFile(file)
            console.log('文件上传成功，链接:', fileUrl)
            homeworkContent = fileUrl // 保存AI作业内容
            
            // 使用上传后的链接提交作业
            await submitWork(iframeSlideIndex, '72', fileUrl, '20')
            message.success(lang.ssHwSubmitSucc)
            hasSubmitWork = true
            
            // 发送作业提交成功的socket消息
            sendMessage({
              type: 'homework_submitted',
              courseid: props.courseid,
              slideIndex: slideIndex.value,
              userid: props.userid
            })
          }
          catch (error) {
            console.error('文件上传失败:', error)
            isSubmitting.value = false
            message.error(lang.ssHwSubmitRetry)
          }
        }
      }
      else if (slides.value[slideIndex.value].elements.some((element: any) => element.isHTML)) {
        message.info(lang.ssTryingScreenshot)
        console.log('尝试截图当前页面并提交ISHTML')
        // return
        try {
          // 尝试使用html2canvas，对iframe支持更好
          let imageData: string
          const screenSlides = document.querySelectorAll('.viewer-canvas .screen-slide')
          let iframeElement: HTMLIFrameElement | null = null
          let iframeBody: HTMLElement | null = null
          let iframehtml: HTMLElement | null = null

          // 获取iframe元素
          if (
            screenSlides &&
            screenSlides[slideIndex.value] &&
            screenSlides[slideIndex.value].querySelector('iframe')
          ) {
            iframeElement = screenSlides[slideIndex.value].querySelector('iframe') as HTMLIFrameElement
          }
          else {
            message.error(lang.ssFailedGetIframe)
            throw new Error('未能获取到iframe元素，无法截图')
          }



          // 获取iframe内部的body元素（同源）
          if (
            iframeElement.contentWindow &&
            iframeElement.contentWindow.document &&
            iframeElement.contentWindow.document.body
          ) {
            // 获取页面的所有注释节点
            const comments = []
            const childNodes = iframeElement.contentWindow.document.createTreeWalker(iframeElement.contentWindow.document.body, NodeFilter.SHOW_COMMENT, null)

            while (childNodes.nextNode()) {
              comments.push(childNodes.currentNode)
            }

            // 移除所有注释节点
            comments.forEach(comment => {
              comment?.parentNode?.removeChild(comment)
            })

            iframeBody = iframeElement.contentWindow.document.body as HTMLElement
            iframehtml = iframeElement.contentWindow.document.getElementsByTagName('html')[0] as HTMLElement
          }
          else {
            message.error(lang.ssFailedGetIframeBody)
            throw new Error('未能获取到iframe的body元素，无法截图')
          }

          try {
            const a = iframeBody.getElementsByTagName('img')
            const b = iframeBody.getElementsByTagName('video')
            // const c = iframeBody.getElementsByTagName('canvas')
            iframeBody.style.cssText += 'width:100%;height:100%;position:absolute;top:0;left:0;'
            iframehtml.style.cssText += 'width:100%;height:100%;position:absolute;top:0;left:0;'
            for (let i = 0;i < a.length;i++) {
              a[i].crossOrigin = 'anonymous'
            }
            for (let i = 0;i < b.length;i++) {
              b[i].crossOrigin = 'anonymous'
            }
            
            // 直接对iframe内部的body进行截图
            const html2canvas = await import('html2canvas')
            const canvas = await html2canvas.default(iframeBody, {
              // useCORS: true,
              // allowTaint: true,
              // scale: 1,
              // backgroundColor: '#ffffff',
              // logging: false,
              // foreignObjectRendering: true,
              // removeContainer: true
              scale: 2, // 提高清晰度
              allowTaint: false, // 是否允许跨域污染画布
              useCORS: true, // 尝试跨域加载图片
              logging: true,
            })
            imageData = canvas.toDataURL('image/png', 0.95)
            
            console.log('成功截图iframe内部内容')
          }
          catch (html2canvasError) {
            console.log('html2canvas失败，尝试html-to-image:', html2canvasError)
            message.error(lang.ssHtml2canvasFailed + html2canvasError)
            
            try {
              // 回退到html-to-image
              const { toPng } = await import('html-to-image')
              imageData = await toPng(iframeBody, {
                quality: 0.95,
                backgroundColor: '#ffffff',
                filter: (node) => {
                  if (node.tagName === 'SCRIPT' || node.tagName === 'STYLE') {
                    return false
                  }
                  return true
                }
              })
              console.log('使用html-to-image截图成功')
            }
            catch (htmlToImageError) {
              console.log('html-to-image也失败了，使用canvas绘制方案:', htmlToImageError)
              message.error(lang.ssHtmlToImageFailed + htmlToImageError)
              message.error(lang.ssShotFail)
              return
              /*
              // 最后的备用方案：使用canvas绘制
              const canvas = document.createElement('canvas')
              const ctx = canvas.getContext('2d')
              if (ctx) {
                canvas.width = iframeElement.offsetWidth || 800
                canvas.height = iframeElement.offsetHeight || 600
                
                // 绘制背景
                const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height)
                gradient.addColorStop(0, '#f8f9fa')
                gradient.addColorStop(1, '#e9ecef')
                ctx.fillStyle = gradient
                ctx.fillRect(0, 0, canvas.width, canvas.height)
                
                // 绘制边框
                ctx.strokeStyle = '#dee2e6'
                ctx.lineWidth = 3
                ctx.strokeRect(2, 2, canvas.width - 4, canvas.height - 4)
                
                // 绘制内边框
                ctx.strokeStyle = '#ffffff'
                ctx.lineWidth = 1
                ctx.strokeRect(5, 5, canvas.width - 10, canvas.height - 10)
                
                // 绘制iframe图标
                ctx.fillStyle = '#6c757d'
                ctx.font = 'bold 48px Arial'
                ctx.textAlign = 'center'
                ctx.fillText('', canvas.width / 2, canvas.height / 2 - 40)
                
                // 绘制标题
                ctx.font = 'bold 20px Arial'
                ctx.fillStyle = '#495057'
                ctx.fillText('iframe内容', canvas.width / 2, canvas.height / 2 + 20)
                
                // 绘制URL
                const src = iframeElement.srcf
                if (src) {
                  ctx.font = '14px Arial'
                  ctx.fillStyle = '#6c757d'
                  const url = src.length > 80 ? src.substring(0, 80) + '...' : src
                  ctx.fillText(url, canvas.width / 2, canvas.height / 2 + 50)
                }
                
                // 绘制提示信息
                ctx.font = '12px Arial'
                ctx.fillStyle = '#adb5bd'
                ctx.fillText('(截图失败)', canvas.width / 2, canvas.height / 2 + 80)
                
                // 绘制装饰性元素
                ctx.strokeStyle = '#dee2e6'
                ctx.lineWidth = 1
                ctx.setLineDash([5, 5])
                ctx.strokeRect(20, 20, canvas.width - 40, canvas.height - 40)
                ctx.setLineDash([])
                
                imageData = canvas.toDataURL('image/png', 0.95)
                console.log('使用canvas绘制方案截图成功')
              }
              else {
                throw new Error('无法创建canvas上下文')
              }*/
            }
          }
          const _a = iframeBody.getElementsByTagName('img')
          const _b = iframeBody.getElementsByTagName('video')
          for (let i = 0; i < _a.length; i++) {
            _a[i].removeAttribute('crossorigin')
          }
          for (let i = 0; i < _b.length; i++) {
            _b[i].removeAttribute('crossorigin')
          }
          // 将base64字符串转换为File对象
          const base64ToFile = (base64String: string, filename: string): File => {
            const arr = base64String.split(',')
            const mime = arr[0].match(/:(.*?);/)?.[1] || 'image/png'
            const bstr = atob(arr[1])
            let n = bstr.length
            const u8arr = new Uint8Array(n)
            while (n--) {
              u8arr[n] = bstr.charCodeAt(n)
            }
            return new File([u8arr], filename, { type: mime })
          }
          
          const imageFile = base64ToFile(imageData, `screenshot_${Date.now()}.png`)
          const imageUrl = await uploadFile(imageFile)
          homeworkContent = imageUrl // 保存截图URL作为作业内容
          // 提交截图
          await submitWork(slideIndex.value, '73', imageUrl, '1') // 73表示截图工具，21表示图片类型
          message.success(lang.ssShotSucc)
          hasSubmitWork = true
          
          // 发送作业提交成功的socket消息
          sendMessage({
            type: 'homework_submitted',
            courseid: props.courseid,
            slideIndex: slideIndex.value,
            userid: props.userid
          })
        }
        catch (error) {
          message.error(lang.ssScreenshotSubmitFailed + error)
          console.error('截图提交失败:', error)
          isSubmitting.value = false
          message.error(lang.ssShotFail)
        }
      }
      else {
        // message.info('尝试截图当前页面并提交')
        const screenSlides = document.querySelectorAll('.viewer-canvas .screen-slide')
        let iframeElement: HTMLIFrameElement | null = null

        // 获取iframe元素
        if (
          screenSlides &&
            screenSlides[slideIndex.value] &&
            screenSlides[slideIndex.value].querySelector('iframe')
        ) {
          iframeElement = screenSlides[slideIndex.value].querySelector('iframe') as HTMLIFrameElement
        }
        else {
          message.error(lang.ssFailedGetIframe)
          throw new Error('未能获取到iframe元素，无法截图')
        }



        // 获取iframe内部的body元素（同源）
        if (
          iframeElement.contentWindow &&
            iframeElement.contentWindow.document &&
            iframeElement.contentWindow.document.body
        ) {
          iframeElement.contentWindow.document.body.style.cssText += 'width:100%;height:100%;position:absolute;top:0;left:0;'
          iframeElement.contentWindow.document.getElementsByTagName('html')[0].style.cssText += 'width:100%;height:100%;position:absolute;top:0;left:0;'
          try {
            isSubmitting.value = true

            const _ajs = iframeElement.contentWindow.document.createElement('script')
            _ajs.type = 'text/javascript'
            _ajs.innerHTML =
          'var _js = document.createElement("script");\n' +
          '_js.type="text/javascript";\n' +
          '_js.src="https://beta.cloud.cocorobo.cn/js/Common/html2canvas-pro.min.js";\n' +
          '_js.onload = function(){\n' +
          ' var a = document.getElementsByTagName("img")\n' +
          ' for(var i = 0;i<a.length;i++){a[i].crossOrigin="anonymous"}\n' +
          ' html2canvas(document.body, {scale: 2,allowTaint: false,useCORS: true,logging: true,}).then(canvas => {\n' +
          '  var base64Url = canvas.toDataURL("image/png");\n' +
          'var base64 = "<img src=" + base64Url + " />"\n' +
          'var file = dataURLtoFile_shishi(base64Url, "截图")\n' +
          'beforeUpload_shishi(file,' +
          "'" +
          props.userid +
          "'" +
          ', ' +
          "'" +
          props.courseid +
          "'" +
          ', ' +
          "'" +
          slideIndex.value +
          "'" +
          ', ' +
          "'0'" +
          ', ' +
          "'73'" +
          ', ' +
          "'1'" +
          ')\n' +
          ' });\n' +
          '}\n' +
          'document.head.appendChild(_js);\n'
            iframeElement.contentWindow.document.head.appendChild(_ajs)
            return
          }
          catch (error) {
            message.error(lang.ssFailedGetIframeBodyElement)
            throw new Error('获取iframe内部body元素失败，无法截图')
          }
          
        }
        
        
       
      }
    }

    if (!hasSubmitWork) {
      message.info(lang.ssHwNoFunc)
    }
    isSubmitting.value = false

  }
  catch (error) {
    console.error('作业提交过程中出错:', error)
    message.error('作业提交过程中出错:' + error)
    message.error(lang.ssHwSubmitFail)
    isSubmitting.value = false
    addOp3(1, new Date().getTime(), { courseid: props.courseid, homeworkContent }, 'error')
  }
  finally {
    // isSubmitting.value = false
    getWork(true)
    if (hasSubmitWork) {
      addOp3(1, new Date().getTime(), { courseid: props.courseid, homeworkContent }, 'success')
    }
    else {
      addOp3(1, new Date().getTime(), { courseid: props.courseid, homeworkContent: '未找到可用的作业提交功能' }, 'error')
    }
  }
}

const successSubmit = () => {
  message.success(lang.ssHwSubmitSucc)
  sendMessage({
    type: 'homework_submitted',
    courseid: props.courseid,
    slideIndex: slideIndex.value,
    userid: props.userid
  })
  isSubmitting.value = false
  getWork(true)
}

// 刷新iframe功能
const handleRefreshPage = () => {
  console.log('刷新iframe按钮被点击')
  
  try {
    // 获取当前幻灯片中的所有iframe元素
    const iframes = document.querySelectorAll('.viewer-canvas .screen-slide')[slideIndex.value].querySelectorAll('iframe')
    console.log('找到iframe元素数量:', iframes.length)

    if (iframes.length === 0) {
      message.warning(lang.ssNoIframe)
      return
    }

    let refreshedCount = 0

    // 遍历所有iframe并刷新
    for (let i = 0; i < iframes.length; i++) {
      const iframe = iframes[i] as HTMLIFrameElement
      // 优化刷新方式，避免闪烁和兼容 srcdoc 场景
      if (iframe.src) {
        // 仅当有src属性时刷新
        const originalSrc = iframe.src
        // 通过重新赋值src实现刷新，避免先清空再赋值导致的闪烁
        iframe.src = ''
        setTimeout(() => {
          iframe.src = originalSrc
          console.log(`刷新iframe ${i + 1}:`, originalSrc)
        }, 50)
        refreshedCount++
      }
      else if (iframe.srcdoc) {
        // srcdoc场景下，重新赋值srcdoc内容
        const originalSrcdoc = iframe.srcdoc
        iframe.srcdoc = ''
        setTimeout(() => {
          iframe.srcdoc = originalSrcdoc
          console.log(`iframe ${i + 1} (srcdoc) 刷新完成`)
        }, 50)
        refreshedCount++
      }
    }

    if (refreshedCount > 0) {
      message.success(lang.ssRefreshDone)
      
      // 如果当前页面有iframe，重新获取作业数据
      if (currentSlideHasIframe.value && props.type == '1') {
        setTimeout(() => {
          getWork()
        }, 500) // 延迟500ms等待iframe加载完成
      }
        
      isSubmitting.value = false
    }
    else {
      message.info(lang.ssNoIframeRef)
    }
  }
  catch (error) {
    console.error('刷新iframe时出错:', error)
    message.error(lang.ssRefreshFail)
  }
}

// 获取作业提交按钮的右侧位置
const getHomeworkButtonRight = () => {
  if (isFullscreen.value) {
    return 70 // 全屏时按钮在右侧30px
  }
  if (props.type === '1') {
    // 展开回答结果：按钮更靠左；收起时：按钮更靠右侧
    return workPanelCollapsed.value ? 100 : 430
  }
  return 30 // type=2时按钮在右侧30px
}

// 获取刷新按钮的右侧位置
const getRefreshButtonRight = () => {
  if (isFullscreen.value) {
    return 160 // 全屏时按钮在右侧150px
  }
  if (props.type === '1') {
    // 展开回答结果：按钮更靠左；收起时：按钮更靠右侧
    return workPanelCollapsed.value ? 190 : 560
  }
  return 160 // type=2时按钮在右侧150px
}


// 键盘快捷键
const handleKeydown = (e: KeyboardEvent) => {
  switch (e.key) {
    case 'ArrowLeft':
      e.preventDefault()
      if (!isFollowModeActive.value || props.type == '1') {
        previousSlide()
      }
      break
    case 'ArrowUp':
      e.preventDefault()
      if (!isFollowModeActive.value || props.type == '1') {
        previousSlide()
      }
      break
    case 'ArrowRight':
      e.preventDefault()
      if (!isFollowModeActive.value || props.type == '1') {
        nextSlide()
      }
      break
    case 'ArrowDown':
    case ' ':
      e.preventDefault()
      if (!isFollowModeActive.value || props.type == '1') {
        nextSlide()
      }
      break
    case 'F11':
      e.preventDefault()
      enterFullscreen()
      break
    case 'Escape':
      if (document.fullscreenElement) {
        document.exitFullscreen()
      }
      break
    default:
      break
  }
}

// 事件处理函数
const handleSlidesDataUpdated = () => {
  console.log('收到 slidesDataUpdated 事件')

  // 强制重新渲染：先隐藏组件
  showSlideList.value = false

  nextTick(() => {
    calculateScale()

    // 延迟500ms后重新显示组件，确保重新渲染完成
    setTimeout(() => {
      showSlideList.value = true
      console.log('组件重新渲染完成')

      // 重新处理iframe链接
      processIframeLinks()
    }, 500)

    console.log('slidesDataUpdated 事件处理完成')
  })
}

const handleViewportSizeUpdated = (event: any) => {
  console.log('收到 viewportSizeUpdated 事件:', event.detail)

  // 重新计算缩放比例
  nextTick(() => {
    calculateScale()
    console.log('viewportSizeUpdated 事件处理完成')
  })
}

const pptJsonFileid = ref<string>('')

// 上传文件
const uploadFile2 = async (file: File, pptid: string): Promise<void> => {
  try {
    const uuid = generateUUID()
    const formData = new FormData()
    const timestamp = Date.now()
    const finalExtension = file.name.split('.').pop()?.toLowerCase() || ''
    const baseName = file.name.slice(0, -(finalExtension.length + 1))
    
    formData.append(
      'file',
      new File([file], `${baseName}${timestamp}.${finalExtension}`)
    )
    formData.append('collection_ids', JSON.stringify([]))
    formData.append('id', uuid)
    formData.append('metadata', JSON.stringify({ title: file.name }))
    formData.append('ingestion_mode', 'fast')
    formData.append('run_with_orchestration', 'true')

    // 同步知识库
    await axios.post(
      'https://r2rserver.cocorobo.cn/v3/documents',
      formData,
      {
        headers: {
          'Content-Type': 'multipart/form-data',
        },
      }
    )
    
    const ptype = '1' // 根据实际业务定义类型
    const fileid = uuid // 如果需要唯一fileid可以和pptid保持一致或按需更改
    
    await axios.post(`${API_URL}addPPTFile`, [{
      pptid: pptid,
      ptype: ptype,
      fileid: fileid,
      classid: '',
      task: '',
      tool: ''
    }])
  }
  catch (err) {
    console.error(err)
    throw err
  }
}

const checkPPTFile = async (jsonObj: any) => {
  const res = await api.getPPTFile(props.courseid as string, props.cid as string)
  console.log(res)
  const data1 = res[0]
  const data2 = res[1]
  const data3 = res[2]
  console.log(data1, data2, data3)
  if (res[0].length) {
    pptJsonFileid.value = data1[0].fileid
  }
  else {
    const pptJsonFile = new File([jsonObj], courseDetail.value.title + '.json', { type: 'application/json' })
    uploadFile2(pptJsonFile, props.courseid as string)
  }
}

const getCourseDetail = async () => {
  isLoading.value = true
  try {
    const res = await api.getCourseDetail(props.courseid as string)
    console.log(res)
    const courseData = res[0][0]
    courseDetail.value = courseData
    selectWorksStudent()
    checkIsCreator()
    const pptJSONUrl = JSON.parse(courseData.chapters).pptData ? JSON.parse(courseData.chapters).pptData : ''
    console.log(pptJSONUrl)
    
    if (pptJSONUrl) { 
      const pptdata = await getFile(pptJSONUrl)
      // pptdata.data 是 ArrayBuffer，需要先转成字符串再解析为 JSON
      let jsonStr = ''
      if (pptdata && pptdata.data) {
        // 先将 ArrayBuffer 转为字符串
        const uint8Array = new Uint8Array(pptdata.data)
        jsonStr = new TextDecoder('utf-8').decode(uint8Array)
        try {
          const jsonObj = JSON.parse(jsonStr)
          // 过滤掉 elements 中 type 为 image 的内容
          const jsonObj2 = JSON.parse(JSON.stringify(jsonObj))
          if (jsonObj2.slides) {
            jsonObj2.slides.forEach((slide: any) => {
              if (slide.elements) {
                slide.elements = slide.elements.filter((element: any) => element.type !== 'image')
              }
            })
          }
          checkPPTFile(JSON.stringify(jsonObj2, null, 2))
          importJSON(jsonObj)
        }
        catch (e) {
          console.error('解析pptdata.data失败:', e)
        }
      }
    }
    getWorkId()
    autoSwitchToAvailablePanel()
  }
  catch (error) {
    console.error('获取课程详情失败:', error)
    message.error(lang.ssFetchCourseFail)
    isLoading.value = false
  }
  finally {
    isLoading.value = false
    // if (props.type == '2') {
    //   console.log('判断是否是学生进入全屏')
    //   function panFull() {
    //     console.log('判断是否是学生进入全屏111')
    //     if (!document.fullscreenElement) {
    //       setTimeout(() => {
    //         if (!document.fullscreenElement) {
    //           if (document.documentElement.requestFullscreen) {
    //             document.documentElement.requestFullscreen()
    //           }
    //           else if (document.documentElement.mozRequestFullScreen) { // Firefox
    //             document.documentElement.mozRequestFullScreen()
    //           }
    //           else if (document.documentElement.webkitRequestFullscreen) { // Chrome, Safari and Opera
    //             document.documentElement.webkitRequestFullscreen()
    //           }
    //           else if (document.documentElement.msRequestFullscreen) { // IE/Edge
    //             document.documentElement.msRequestFullscreen()
    //           }
    //           panFull()
    //         }
    //       }, 50)
    //     }           
    //   }
    //   nextTick(() => {
    //     setTimeout(() => {
    //       // enterFullscreen(); 
    //       panFull()     
    //     }, 50)
    //   })
    // }
  }
}

const getWorkLoading = ref<any>(false)


const getWork = async (isUpdate = false) => {
  try {
    if (getWorkLoading.value) {
      return
    }
    if (!isUpdate) {
      workLoading.value = true
    }
    getWorkLoading.value = true
    console.log('getWork 开始执行，参数:', {
      courseid: props.courseid,
      slideIndex: slideIndex.value,
      type: props.type,
      isUpdate
    })
    
    if (!props.courseid) {
      console.warn('getWork: courseid 未提供，跳过执行')
      if (!isUpdate) workLoading.value = false
      return
    }
    
    const res = await api.selectSWorks(props.courseid, '0', slideIndex.value.toString())
    console.log('getWork 执行成功，结果:', res)
    const frame = elementList.value.find(element => element.type === ElementTypes.FRAME)
    console.log('frame:', frame)
    const toolType = frame?.toolType ?? ''
    const newWorkArray = props.cid
      ? res[0].filter((work: any) => {
        // console.log(work.ttype == '1' || (work.ttype == '2' && work.classid.includes(props.cid)) && (work.atool === toolType.value || !toolType.value))
        return work.ttype == '1' || (work.ttype == '2' && work.classid.includes(props.cid)) && (work.atool == toolType.value || !toolType.value)
      })
      : res[0]
    
    // 如果是更新模式，只有当数据真正变化时才更新
    if (isUpdate) {
      const hasChanged = checkWorkArrayChanged(workArray.value, newWorkArray)
      if (hasChanged) {
        console.log('检测到作业数据变化，更新显示')
        workArray.value = newWorkArray
      }
      else {
        console.log('作业数据无变化，跳过更新')
      }
    }
    else {
      workArray.value = newWorkArray
    }
    
    console.log('getWork 执行成功，结果:', workArray.value)
    getWorkLoading.value = false
  }
  catch (error) {
    console.error('getWork 执行失败:', error)
    if (!isUpdate) {
      message.error(lang.ssWorkInfoFail)
    }
    getWorkLoading.value = false
  }
  finally {
    if (!isUpdate) {
      workLoading.value = false
    }
    getWorkLoading.value = false
  }
}

const selectWorksStudent = async () => {
  studentLoading.value = true
  try {
    const res = await api.selectWorksStudent(props.oid as string, courseDetail.value.juri as string)
    console.log('selectWorksStudent', res)
    const students = res[0]
    console.log('students', students)
    if (props.cid) {
      studentArray.value = students.filter((student: any) => student.classid.includes(props.cid))
    }
    else {
      studentArray.value = students
    }
  }
  catch (error) {
    console.error('获取学生信息失败:', error)
    message.error(lang.ssStuInfoFail)
  }
  finally {
    studentLoading.value = false
  }
}

// 检查作业数组是否发生变化
const checkWorkArrayChanged = (oldArray: WorkItem[], newArray: WorkItem[]): boolean => {
  if (oldArray.length !== newArray.length) return true
  
  // 检查每个作业的 id 和 name 是否一致
  for (let i = 0; i < oldArray.length; i++) {
    const oldWork = oldArray[i]
    const newWork = newArray[i]
    
    if (oldWork.id !== newWork.id || oldWork.name !== newWork.name || oldWork.content !== newWork.content) {
      return true
    }
  }
  
  return false
}

// 查询课程跟随状态
const selectCourseSLook = async (type = 2) => {
  const res = await api.selectCourseSLook(props.courseid as string)
  console.log('selectCourseSLook', res)
  if (res[0][0].follow == 2) {
    if (props.type == '2') {
      goToSlide(Number(res[0][0].followC))
    }
    isFollowModeActive.value = true
    if (props.userid == courseDetail.value.userid && props.type == '1') {
      api.updateCourseFollowC(slideIndex.value, props.courseid as string)
      sendMessage({slideIndex: slideIndex.value, courseid: props.courseid, type: 'slideIndex'})
      console.log('设置当前幻灯片为跟随目标:', slideIndex.value)
    }
    if (props.type == '2' && slidePanelCollapsed.value) {
      slidePanelCollapsed.value = false
    }
  }
  else {
    isFollowModeActive.value = false
    if (type === 1 && props.userid == courseDetail.value.userid && props.type == '1') {
      toggleFollowMode()
    }
  }
  if (props.type == '2') {
    message.success(isFollowModeActive.value ? lang.ssFollowOnTip : lang.ssFreeOnTip)
  }
  
  checkParentMode()
}

// 切换跟随模式
const toggleFollowMode = async () => {
  try {
    const newFollowState = !isFollowModeActive.value
    const sopen = newFollowState ? 2 : 1
    
    // 调用API更新跟随状态
    const res = await api.updateCourseFollow(sopen, props.courseid as string)
    console.log('更新跟随模式状态:', res)
    sendMessage({sopen: newFollowState, courseid: props.courseid, type: 'sopen'})
    
    if (res) {
      isFollowModeActive.value = newFollowState
      message.success(newFollowState ? lang.ssFollowOnTip : lang.ssFreeOnTip)
      
      // 如果开启跟随模式，设置当前幻灯片为跟随目标
      if (newFollowState) {
        await api.updateCourseFollowC(slideIndex.value, props.courseid as string)
        console.log('设置当前幻灯片为跟随目标:', slideIndex.value)
      }
      if (timerlVisible.value) {
        timerlVisible.value = false
      }
      handleWritingBoardClose() 
    }
    else {
      message.error(lang.ssOpFailRetry)
    }
    checkParentMode()
  }
  catch (error) {
    console.error('切换跟随模式失败:', error)
    message.error(lang.ssOpFailRetry)
  }
}

const checkParentMode = () => {
  // @ts-ignore
  if (window.parent && typeof window.parent.onFreeBrowseChange === 'function') {
    // @ts-ignore
    window.parent.onFreeBrowseChange(!isFollowModeActive.value)
  }
}

const forceLogout = () => {
  sendMessage({ type: 'logout' })
}

const logout = () => {
  // @ts-ignore
  if (window.parent && typeof window.parent.topU.U.MD.U.LO.logoutSystemQ === 'function') {
    // @ts-ignore
    window.parent.topU.U.MD.U.LO.logoutSystemQ()
  }
}

// 检查是否为创建人
const checkIsCreator = () => {
  // 这里可以根据实际业务逻辑判断是否为创建人
  // 比如通过props中的userid与课程创建者ID比较
  if (courseDetail.value && props.userid) {
    isCreator.value = courseDetail.value.userid === props.userid
  }
}

/**
 * 初始化消息监听
 */
const messageInit = () => {
  if (!docSocket.value) return

  // 初始化普通消息数组（只保留最后一条）
  if (!yMessage.value) {
    console.log('初始化普通消息数组')
    yMessage.value = docSocket.value.getArray('message')
    yMessage.value.observe((e: any) => {
      e.changes.added.forEach((i: any) => {
        console.log('yMessage', yMessage.value.length)
        console.log('yMessage', yMessage.value)
        const message = i.content.getContent()[0]
        console.log('yMessage', message)
        if (message.mId !== mId.value) {
          getMessages(message)
        }
      })
    })
  }

  // 初始化计时器消息数组
  if (!yTimerMessages.value) {
    console.log('初始化计时器消息数组')
    yTimerMessages.value = docSocket.value.getArray('timerMessages')
    
    // 初始化时检查一次是否需要清理
    if (yTimerMessages.value && yTimerMessages.value.length > 500 && isCreator.value && docSocket.value) {
      smartCleanupMessages(yTimerMessages.value, 500, 'timer', docSocket.value)
    }
    
    yTimerMessages.value.observe((e: any) => {
      console.log('yTimerMessages', yTimerMessages.value.length)
      console.log('yTimerMessages', yTimerMessages.value)
      
      // 每次有新消息添加后，检查是否需要清理
      if (yTimerMessages.value && yTimerMessages.value.length > 500 && isCreator.value && docSocket.value) {
        // 使用 setTimeout 确保在 observe 回调执行完成后再清理
        setTimeout(() => {
          if (docSocket.value && yTimerMessages.value && yTimerMessages.value.length > 500) {
            smartCleanupMessages(yTimerMessages.value, 500, 'timer', docSocket.value)
          }
        }, 0)
      }
      
      e.changes.added.forEach((i: any) => {
        const message = i.content.getContent()[0]
        if (message.mId !== mId.value) {
          getMessages(message)
        }
      })
    })
  }

  // 初始化激光笔消息数组
  if (!yLaserMessages.value) {
    console.log('初始化激光笔消息数组')
    yLaserMessages.value = docSocket.value.getArray('laserMessages')
    
    // 初始化时检查一次是否需要清理
    if (yLaserMessages.value && yLaserMessages.value.length > 500 && isCreator.value && docSocket.value) {
      smartCleanupMessages(yLaserMessages.value, 500, 'laser', docSocket.value)
    }
    
    yLaserMessages.value.observe((e: any) => {
      console.log('yLaserMessages', yLaserMessages.value.length)
      console.log('yLaserMessages', yLaserMessages.value)
      
      // 每次有新消息添加后，检查是否需要清理
      if (yLaserMessages.value && yLaserMessages.value.length > 500 && isCreator.value && docSocket.value) {
        // 使用 setTimeout 确保在 observe 回调执行完成后再清理
        setTimeout(() => {
          if (docSocket.value && yLaserMessages.value && yLaserMessages.value.length > 500) {
            smartCleanupMessages(yLaserMessages.value, 500, 'laser', docSocket.value)
          }
        }, 0)
      }
      
      e.changes.added.forEach((i: any) => {
        const message = i.content.getContent()[0]
        // 只处理开关消息，位置消息直接通过 Map 同步
        if (message.type === 'laser_toggle' && message.mId !== mId.value) {
          getMessages(message)
        }
      })
    })
  }

  // 初始化画图消息数组
  if (!yWritingBoardMessages.value) {
    console.log('初始化画图消息数组')
    yWritingBoardMessages.value = docSocket.value.getArray('writingBoardMessages')
    
    // 初始化时检查一次是否需要清理
    if (yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500 && isCreator.value && docSocket.value) {
      smartCleanupMessages(yWritingBoardMessages.value, 500, 'writingBoard', docSocket.value)
    }
    
    yWritingBoardMessages.value.observe((e: any) => {
      console.log('yWritingBoardMessages', yWritingBoardMessages.value.length)
      console.log('yWritingBoardMessages', yWritingBoardMessages.value)
      
      // 每次有新消息添加后，检查是否需要清理
      if (yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500 && isCreator.value && docSocket.value) {
        // 使用 setTimeout 确保在 observe 回调执行完成后再清理
        setTimeout(() => {
          if (docSocket.value && yWritingBoardMessages.value && yWritingBoardMessages.value.length > 500) {
            smartCleanupMessages(yWritingBoardMessages.value, 500, 'writingBoard', docSocket.value)
          }
        }, 0)
      }
      
      e.changes.added.forEach((i: any) => {
        const message = i.content.getContent()[0]
        if (message.mId !== mId.value) {
          getMessages(message)
        }
      })
    })
  }

  // 如果是首次进入且是创建者，清空所有同步状态
  if (isFirstEnter.value && isCreator.value && docSocket.value) {
    console.log('🧹 首次进入且为创建者，清空所有同步状态')
    docSocket.value.transact(() => {
      // 清空普通消息（只保留最后一条的逻辑在 sendMessage 中处理）
      const messageArray = docSocket.value?.getArray?.('message')
      if (messageArray) {
        messageArray.delete(0, messageArray.length)
      }
      // 清空计时器消息数组
      const timerMessagesArray = docSocket.value?.getArray?.('timerMessages')
      if (timerMessagesArray) {
        timerMessagesArray.delete(0, timerMessagesArray.length)
      }
      // 清空激光笔消息数组
      const laserMessagesArray = docSocket.value?.getArray?.('laserMessages')
      if (laserMessagesArray) {
        laserMessagesArray.delete(0, laserMessagesArray.length)
      }
      // 清空画图消息数组
      const writingBoardMessagesArray = docSocket.value?.getArray?.('writingBoardMessages')
      if (writingBoardMessagesArray) {
        writingBoardMessagesArray.delete(0, writingBoardMessagesArray.length)
      }
      // 清空计时器状态
      const timerStateMap = docSocket.value?.getMap?.('timerState')
      if (timerStateMap) {
        timerStateMap.clear()
      }
      // 清空激光笔状态
      const laserStateMap = docSocket.value?.getMap?.('laserState')
      if (laserStateMap) {
        laserStateMap.clear()
      }
      // 清空画图状态
      const writingBoardStateMap = docSocket.value?.getMap?.('writingBoardState')
      if (writingBoardStateMap) {
        writingBoardStateMap.clear()
      }
    })
    // 标记已不再是首次进入
    isFirstEnter.value = false
  }
  // 初始化计时器状态 Map 并监听
  if (docSocket.value && !yTimerState.value) {
    yTimerState.value = docSocket.value.getMap('timerState')
    // 初始状态同步（后加入用户会立即拿到当前 map 值）
    const snapshot = yTimerState.value.toJSON()
    applyTimerStateSnapshot(snapshot)
    // 监听变化
    yTimerState.value.observe((event: any) => {
      const snap = yTimerState.value.toJSON()
      applyTimerStateSnapshot(snap)
    })
  }
  // 初始化激光笔状态 Map 并监听
  if (docSocket.value && !yLaserState.value) {
    yLaserState.value = docSocket.value.getMap('laserState')
    const lsnap = yLaserState.value.toJSON()
    applyLaserStateSnapshot(lsnap)
    yLaserState.value.observe(() => {
      const s = yLaserState.value.toJSON()
      applyLaserStateSnapshot(s)
    })
  }
  // 初始化画图状态 Map 并监听
  if (docSocket.value && !yWritingBoardState.value) {
    yWritingBoardState.value = docSocket.value.getMap('writingBoardState')
    const wsnap = yWritingBoardState.value.toJSON()
    console.log('📝 初始化画图状态Map，快照:', wsnap, '当前幻灯片:', currentSlide.value?.id)
    // 延迟应用，确保 currentSlide 已初始化
    nextTick(() => {
      // 如果 currentSlide 还没准备好，再等一帧
      if (currentSlide.value && currentSlide.value.id) {
        applyWritingBoardStateSnapshot(wsnap)
      }
      else {
        // 如果还没准备好，等待 currentSlide 变化（最多等待3秒）
        let timeoutId: any = null
        const unwatch = watch(() => currentSlide.value?.id, (slideId) => {
          if (slideId) {
            applyWritingBoardStateSnapshot(wsnap)
            unwatch()
            if (timeoutId) clearTimeout(timeoutId)
          }
        }, { immediate: true })
        // 3秒后如果还没准备好，强制应用一次
        timeoutId = setTimeout(() => {
          if (currentSlide.value && currentSlide.value.id) {
            applyWritingBoardStateSnapshot(wsnap)
          }
          unwatch()
        }, 3000)
      }
    })
    yWritingBoardState.value.observe(() => {
      const s = yWritingBoardState.value.toJSON()
      if (currentSlide.value && currentSlide.value.id) {
        applyWritingBoardStateSnapshot(s)
      }
    })
  }
}

/**
 * 判断是否为特殊类型的消息（计时器、激光笔、画图）
 * 注意：laser_move 不通过消息数组，直接通过 Map 同步，所以不包含在这里
 */
const isSpecialMessageType = (type: string): boolean => {
  const timerTypes = ['timer_start', 'timer_pause', 'timer_reset', 'timer_stop', 'timer_finish', 'timer_update']
  const laserTypes = ['laser_toggle'] // laser_move 直接通过 Map，不通过消息数组
  const writingBoardTypes = ['writing_board_update', 'writing_board_close', 'writing_board_blackboard']
  return timerTypes.includes(type) || laserTypes.includes(type) || writingBoardTypes.includes(type)
}

/**
 * 判断是否为开关类型的消息（需要保留的状态消息）
 */
const isToggleMessageType = (type: string, category: 'timer' | 'laser' | 'writingBoard'): boolean => {
  if (category === 'laser') {
    return type === 'laser_toggle'
  }
  if (category === 'writingBoard') {
    return type === 'writing_board_close'
  }
  if (category === 'timer') {
    return ['timer_start', 'timer_stop', 'timer_reset'].includes(type)
  }
  return false
}

/**
 * 智能清理消息数组，保留最新的开关消息
 */
const smartCleanupMessages = (messageArray: any, maxLength: number, category: 'timer' | 'laser' | 'writingBoard', docSocket: Y.Doc) => {
  if (!messageArray || messageArray.length <= maxLength) return
  
  const allMessages = messageArray.toArray()
  
  // 找到所有开关类型的消息及其索引
  const toggleMessages: Array<{ index: number; message: any }> = []
  for (let i = 0; i < allMessages.length; i++) {
    const msg = allMessages[i]
    if (msg && typeof msg === 'object' && msg.type && isToggleMessageType(msg.type, category)) {
      toggleMessages.push({ index: i, message: msg })
    }
  }
  
  // 如果有关键的开关消息，需要确保它们被保留
  if (toggleMessages.length > 0) {
    // 找到最新的开关消息索引
    const latestToggleIndex = toggleMessages[toggleMessages.length - 1].index
    
    // 如果最新的开关消息在最后500条内，直接保留最后500条
    if (latestToggleIndex >= allMessages.length - maxLength) {
      const excessCount = allMessages.length - maxLength
      docSocket.transact(() => {
        messageArray.delete(0, excessCount)
      })
      console.log(`🧹 清理了 ${excessCount} 条${category}消息，保留最新${maxLength}条（包含最新开关状态）`)
    }
    else {
      // 如果最新的开关消息不在最后500条内，需要特殊处理
      // 保留从最新开关消息开始到末尾的所有消息
      const keepFromIndex = Math.max(0, latestToggleIndex)
      const excessCount = keepFromIndex
      docSocket.transact(() => {
        messageArray.delete(0, excessCount)
      })
      console.log(`🧹 清理了 ${excessCount} 条${category}消息，保留从最新开关状态开始的所有消息`)
    }
  }
  else {
    // 没有开关消息，直接保留最后500条
    const excessCount = allMessages.length - maxLength
    docSocket.transact(() => {
      messageArray.delete(0, excessCount)
    })
    console.log(`🧹 清理了 ${excessCount} 条${category}消息，保留最新${maxLength}条`)
  }
}

/**
 * 获取消息类型对应的数组
 * 注意：laser_move 不通过消息数组，直接通过 Map 同步
 */
const getMessageArrayByType = (type: string): any | null => {
  const timerTypes = ['timer_start', 'timer_pause', 'timer_reset', 'timer_stop', 'timer_finish', 'timer_update']
  const laserTypes = ['laser_toggle'] // laser_move 直接通过 Map，不通过消息数组
  const writingBoardTypes = ['writing_board_update', 'writing_board_close', 'writing_board_blackboard']
  
  if (timerTypes.includes(type)) {
    return yTimerMessages.value
  }
  if (laserTypes.includes(type)) {
    return yLaserMessages.value
  }
  if (writingBoardTypes.includes(type)) {
    return yWritingBoardMessages.value
  }
  return null
}

/**
 * 发送消息
 */
const sendMessage = (obj: any) => {
  if (!docSocket.value) return
  
  const message = obj
  message.timestamp = new Date().toISOString()
  message.mId = mId.value
  
  const messageType = message.type
  
  // laser_move 消息直接通过 Map 同步，不通过消息数组（减少延迟）
  if (messageType === 'laser_move') {
    if (yLaserState.value) {
      docSocket.value.transact(() => {
        yLaserState.value.set('x', message.x)
        yLaserState.value.set('y', message.y)
      })
    }
    return // 不存储到消息数组
  }
  
  const isSpecial = isSpecialMessageType(messageType)
  
  docSocket.value.transact(() => {
    if (isSpecial) {
      // 特殊类型消息：存储到对应的独立数组
      const targetArray = getMessageArrayByType(messageType)
      if (targetArray) {
        targetArray.push([message])
      }
    }
    else {
      // 普通消息：只保留最后一条
      if (yMessage.value) {
        // 清空数组，只保留新消息
        yMessage.value.delete(0, yMessage.value.length)
        yMessage.value.push([message])
      }
    }
  })
}

/**
 * 处理收到的消息
 */
const getMessages = (msgObj: any) => {
  console.log('message', msgObj)
  
  // 处理幻灯片切换消息
  if (props.type == '2' && msgObj.type === 'slideIndex') {
    goToSlide(msgObj.slideIndex)
  }
  
  // 处理跟随模式状态变化
  if (props.type == '2' && msgObj.type === 'sopen') {
    selectCourseSLook()
  }
  
  if (props.type == '2' && msgObj.type === 'logout') {
    logout()
  }

  // 处理作业提交消息 - 当有人提交作业时，重新获取作业数据
  if (props.type == '1' && msgObj.type === 'homework_submitted' && msgObj.courseid === props.courseid) {
    console.log('收到作业提交消息，重新获取作业数据')
    // 延迟一点时间，确保后端数据已更新
    setTimeout(() => {
      if (currentSlideHasIframe.value) {
        getWork(true) // 传入true表示是更新模式
      }
    }, 1000)
  }

  // 计时器消息 - 学生与老师端实时显示
  if (msgObj.type === 'timer_start' && msgObj.courseid === props.courseid) {
    applyTimerStart(msgObj.payload)
  }
  if (msgObj.type === 'timer_pause' && msgObj.courseid === props.courseid) {
    applyTimerPause()
  }
  if (msgObj.type === 'timer_reset' && msgObj.courseid === props.courseid) {
    applyTimerReset()
  }
  if (msgObj.type === 'timer_stop' && msgObj.courseid === props.courseid) {
    applyTimerStop()
  }
  if (msgObj.type === 'timer_finish' && msgObj.courseid === props.courseid) {
    applyTimerFinish()
  }
  if (msgObj.type === 'timer_update' && msgObj.courseid === props.courseid) {
    applyTimerUpdate(msgObj.payload)
  }

  // 激光笔：老师广播的开关
  if (props.type == '2' && msgObj.type === 'laser_toggle' && msgObj.courseid === props.courseid) {
    laserPenOverlay.value.visible = !!msgObj.enabled
    // 开关时立即刷新一次位置
    if (laserPenOverlay.value.visible) {
      refreshLaserOverlayRect()
      if (laserMoveRafId) cancelAnimationFrame(laserMoveRafId)
      laserMoveRafId = requestAnimationFrame(updateLaserDotPosition)
    }
  }
  // 注意：laser_move 现在直接通过 Map 同步，不通过消息数组，所以这里不需要处理

  // 画图：老师广播的画图数据
  if (props.type == '2' && msgObj.type === 'writing_board_update' && msgObj.courseid === props.courseid) {
    console.log('📝 学生端收到画图更新消息:', { slideId: msgObj.slideId, currentSlideId: currentSlide.value?.id, hasData: !!msgObj.dataURL })
    if (currentSlide.value && msgObj.slideId === currentSlide.value.id) {
      writingBoardSyncDataURL.value = msgObj.dataURL || null
      // 如果消息中包含小黑板状态，也更新
      if (msgObj.blackboard !== undefined) {
        writingBoardSyncBlackboard.value = msgObj.blackboard
      }
      console.log('📝 画图数据匹配当前幻灯片，显示画图工具')
      // 同步到共享 Map
      if (yWritingBoardState.value) {
        docSocket.value?.transact(() => {
          yWritingBoardState.value.set('slideId', msgObj.slideId)
          yWritingBoardState.value.set('dataURL', msgObj.dataURL)
          if (msgObj.blackboard !== undefined) {
            yWritingBoardState.value.set('blackboard', msgObj.blackboard)
          }
        })
      }
    }
    else {
      // 不是当前幻灯片，但也要更新到 Map（供后续切换时使用）
      if (yWritingBoardState.value) {
        docSocket.value?.transact(() => {
          yWritingBoardState.value.set('slideId', msgObj.slideId)
          yWritingBoardState.value.set('dataURL', msgObj.dataURL)
          if (msgObj.blackboard !== undefined) {
            yWritingBoardState.value.set('blackboard', msgObj.blackboard)
          }
        })
      }
      console.log('📝 画图数据不匹配当前幻灯片，已保存到Map供后续使用')
    }
  }

  // 画图：老师关闭画图工具
  if (props.type == '2' && msgObj.type === 'writing_board_close' && msgObj.courseid === props.courseid) {
    writingBoardSyncDataURL.value = null
    writingBoardSyncBlackboard.value = null
    // 清空共享 Map
    if (yWritingBoardState.value) {
      docSocket.value?.transact(() => {
        yWritingBoardState.value.clear()
      })
    }
  }

  // 画图：老师切换小黑板状态
  if (props.type == '2' && msgObj.type === 'writing_board_blackboard' && msgObj.courseid === props.courseid) {
    writingBoardSyncBlackboard.value = msgObj.blackboard || false
    // 同步到共享 Map
    if (yWritingBoardState.value) {
      docSocket.value?.transact(() => {
        yWritingBoardState.value.set('blackboard', msgObj.blackboard)
      })
    }
  }

}


// 打开作业查看详细
const openChoiceQuestionDetail = (index:number) => {
  if (!choiceQuestionDetailDialogOpenList.value.includes(index)) {
    choiceQuestionDetailDialogOpenList.value.push(index)
  }
  else {
    choiceQuestionDetailDialogOpenList.value = choiceQuestionDetailDialogOpenList.value.filter(i => i !== index)
  }
}

const handlePageUnload = () => {
  if (isCreator.value && timerIndicator.value.visible && props.type === '1') {
    sendMessage({ type: 'timer_stop', courseid: props.courseid })
  }
  // 创建老师刷新/关闭页面时，清空所有同步状态
  if (isCreator.value && props.type === '1') {
    clearAllSyncStates()
  }

  // 清理画图延迟发送定时器
  if (drawingDelayTimer.value) {
    clearTimeout(drawingDelayTimer.value)
    drawingDelayTimer.value = null
  }
}

// 检测浏览器类型
const detectBrowser = () => {
  const ua = navigator.userAgent

  // 按优先级顺序检测
  if (ua.includes('Edg/') || ua.includes('Edge/')) {
    return 'Microsoft Edge'
  }
  if (ua.includes('Firefox')) {
    return 'Mozilla Firefox'
  }
  if (ua.includes('Trident') || ua.includes('MSIE')) {
    return 'Internet Explorer'
  }
  if (ua.includes('360EE')) {
    return '360 Browser (极速模式)'
  }
  if (ua.includes('360SE')) {
    return '360 Browser (安全模式)'
  }
  if (ua.includes('SLBrowser')) {
    return 'QQ Browser'
  }
  if (ua.includes('UCBrowser')) {
    return 'UC Browser'
  }
  if (ua.includes('Opera') || ua.includes('OPR/')) {
    return 'Opera'
  }
  if (ua.includes('Chrome') && !ua.includes('Edg/')) {
    return 'Google Chrome'
  }
  if (ua.includes('Safari/') && !ua.includes('Chrome')) {
    return 'Safari'
  }
  return 'Other Browser'
}

// 用户数据上报功能
const addOp3 = async (userTime: any, loadTime: any, object: any, status: any) => {
  if (!props.userid) return

  try {
    if (!userJson.value || !userJson.value.accountNumber) {
      const res = await axios.get('https://pbl.cocorobo.cn/api/pbl/selectUser', {
        params: { userid: props.userid }
      })
      userJson.value = res[0][0]
      console.log(userJson.value)
      console.log(res[0][0])
    }
  }
  catch (e) {
    console.log(e)
    return addOp3(userTime, loadTime, object, status)
  }

  const _time = new Date()
    .toLocaleString('zh-CN', { hour12: false, timeZone: 'Asia/Shanghai' })
    .replace(/\//g, '-')

  const browser = detectBrowser()
  const params = {
    userid: props.userid,
    username: userJson.value.username,
    accountNumber: userJson.value.accountNumber,
    org: userJson.value.orgName,
    school: userJson.value.schoolName,
    role: userJson.value.type === '1' ? '老师' : '学生',
    browser,
    userTime: userTime === '1' ? _time : userTime, // 使用时间 1次的就1 其次传秒
    loadTime, // load的时间没有就''
    object: JSON.stringify(object), // 执行信息传json
    status // 成功返回success。失败返回error的信息
  }

  console.log('params', params)

  axios
    .post('https://pbl.cocorobo.cn/api/mongo/updateUserData2', [params])
    .then(res => {
      if (res.status === 1) {
        console.log('保存成功')
      }
      else {
        console.log('保存失败')
      }
    })
    .catch(e => {
      console.log('保存失败')
      console.log(e)
    })
}

onMounted(() => {
  
  document.addEventListener('keydown', handleKeydown)

  // 处理URL参数
  if (props.courseid || props.type) {
    console.log('收到URL参数:', { courseid: props.courseid, type: props.type })

    // 这里可以根据courseid和type进行相应的处理
    // 比如加载特定的课程数据、设置特定的显示模式等
    if (props.courseid) {
      console.log('课程ID:', props.courseid)
      // TODO: 根据courseid加载对应的课程数据
    }

    if (props.type) {
      console.log('类型:', props.type)
      // TODO: 根据type设置特定的显示模式或功能
    }
  }
  getCourseDetail()


  // 计算初始缩放比例
  nextTick(() => {
    calculateScale()

    // 处理iframe链接
    processIframeLinks()
    
    // 初始化时检查并自动切换到可用面板
    autoSwitchToAvailablePanel()
  })

  // 监听窗口大小变化
  window.addEventListener('resize', calculateScale)

  // 监听全屏状态变化
  document.addEventListener('fullscreenchange', handleFullscreenChange)

  // 监听幻灯片数据更新事件（来自useImport的readJSON）
  window.addEventListener('slidesDataUpdated', handleSlidesDataUpdated)

  // 监听视口尺寸更新事件
  window.addEventListener('viewportSizeUpdated', handleViewportSizeUpdated)

  // 将导入导出功能暴露到window上，方便调试和外部调用
  ; (window as any).PPTistStudent = {
    importJSON,
    exportJSON,
    slides: slidesStore.slides,
    currentSlide: computed(() => slidesStore.currentSlide),
    slideIndex: computed(() => slidesStore.slideIndex),
    goToSlide,
    previousSlide,
    nextSlide,
    enterFullscreen,
    toggleLaserPen,
    // 添加URL参数到全局对象中
    courseid: props.courseid,
    type: props.type,
    successSubmit,
    toggleFollowMode,
    // 添加重连功能
    manualReconnect,
    connectionStatus: computed(() => connectionStatus.value),
    forceLogout,
  }

  console.log('PPTist Student View 已加载，可通过 window.PPTistStudent 访问功能')
  console.log('URL参数:', { courseid: props.courseid, type: props.type })

  // 初始化WebSocket连接
  if (api.yweb_socket) {
    createWebSocketConnection()
  }

  // 创建人离开页面时，广播停止计时
 
  
  // beforeunload 事件（页面刷新或关闭）
  window.addEventListener('beforeunload', handlePageUnload)
  
  // visibilitychange 事件（适用于 iframe 嵌套场景，当外层页面返回时触发）
  const handleVisibilityChange = () => {
    if (document.hidden && isCreator.value) {
      // 页面被隐藏时，清空所有同步状态
      clearAllSyncStates()
      if (timerIndicator.value.visible) {
        sendMessage({ type: 'timer_stop', courseid: props.courseid })
      }
    }
  }
  document.addEventListener('visibilitychange', handleVisibilityChange)
  
  // pagehide 事件（作为补充，某些浏览器中比 beforeunload 更可靠）
  window.addEventListener('pagehide', handlePageUnload)
  
  // 存储清理函数，方便在 onUnmounted 中移除
  ;(window as any).__pptistStudentUnloadHandlers = {
    beforeunload: handlePageUnload,
    visibilitychange: handleVisibilityChange,
    pagehide: handlePageUnload
  }
})


onUnmounted(() => {
  document.removeEventListener('keydown', handleKeydown)
  window.removeEventListener('resize', calculateScale)
  document.removeEventListener('fullscreenchange', handleFullscreenChange)

  // 移除幻灯片数据更新事件监听器
  window.removeEventListener('slidesDataUpdated', handleSlidesDataUpdated)

  // 移除视口尺寸更新事件监听器
  window.removeEventListener('viewportSizeUpdated', handleViewportSizeUpdated)

  // 清理WebSocket连接
  if (reconnectTimer.value) {
    clearTimeout(reconnectTimer.value)
    reconnectTimer.value = null
  }

  // 清理认证 token 更新定时器
  if (authTokenUpdateTimer.value) {
    clearTimeout(authTokenUpdateTimer.value)
    authTokenUpdateTimer.value = null
  }

  // 清理 socket 连接检查定时器
  if (socketCheckTimer.value) {
    clearInterval(socketCheckTimer.value)
    socketCheckTimer.value = null
  }

  // if (providerSocket.value) {
  //   providerSocket.value.destroy()
  //   providerSocket.value = null
  // }

  // 清理画图延迟发送定时器
  if (drawingDelayTimer.value) {
    clearTimeout(drawingDelayTimer.value)
    drawingDelayTimer.value = null
  }

  // 清理页面卸载相关的事件监听器
  if ((window as any).__pptistStudentUnloadHandlers) {
    const handlers = (window as any).__pptistStudentUnloadHandlers
    window.removeEventListener('beforeunload', handlers.beforeunload)
    document.removeEventListener('visibilitychange', handlers.visibilitychange)
    window.removeEventListener('pagehide', handlers.pagehide)
    delete (window as any).__pptistStudentUnloadHandlers
  }

  // 清理window上的引用
  if ((window as any).PPTistStudent) {
    delete (window as any).PPTistStudent
    console.log('PPTist Student View 已卸载，window.PPTistStudent 已清理')
  }
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }

  handlePageUnload()
})

// 获取认证 token
const getAuthToken = async (): Promise<string> => {
  try {
    // 使用代理路径避免跨域问题
    // 开发环境：通过 vite 代理 /yjs-auth/auth/token -> https://yjsredis.cocorobo.cn/auth/token
    // 生产环境：需要配置服务器代理或使用后端 API

    // 兼容性修复：不直接使用 import.meta.env.DEV
    let isDev = false
    // 判断如果有 window 对象且以 localhost/127.0.0.1 开头，则认为是开发环境
    if (typeof window !== 'undefined') {
      const hostname = window.location.hostname
      if (
        hostname === 'localhost' ||
        hostname === '127.0.0.1' ||
        hostname === '::1' ||
        /^192\.168\.\d+\.\d+$/.test(hostname)
      ) {
        isDev = true
      }
    }

    let authUrl = ''
    if (isDev) {
      // 开发环境使用 vite 代理
      authUrl = '/yjs-auth/auth/token'
    }
    else {
      // 生产环境：如果服务器有代理则使用代理，否则直接访问（需要服务器配置 CORS）
      // 或者通过后端 API 获取 token
      let baseUrl = 'https://yjsredis.cocorobo.cn/'
      baseUrl = baseUrl.replace(/\/+$/, '')
      authUrl = `${baseUrl}/auth/token`
    }

    console.log('🔐 获取认证 token，URL:', authUrl)
    const response = await axios.get(authUrl)
    console.log('🔐 获取认证 token 成功', response)
    
    return response
  }
  catch (error) {
    console.error('🔐 获取认证 token 失败:', error)
    throw error
  }
}

// 定期更新认证 token
const updateAuthToken = async () => {
  try {
    if (!providerSocket.value) return
    
    const newToken = await getAuthToken()
    authToken.value = newToken
    // 更新 provider 的 auth 参数
    if (providerSocket.value.params) {
      providerSocket.value.params.yauth = newToken
    }
    console.log('🔐 认证 token 已更新')
    
    // 30分钟后再次更新
    if (authTokenUpdateTimer.value) {
      clearTimeout(authTokenUpdateTimer.value)
    }
    authTokenUpdateTimer.value = setTimeout(updateAuthToken, 30 * 60 * 1000) as unknown as NodeJS.Timeout
  }
  catch (error) {
    console.error('🔐 更新认证 token 失败:', error)
    // 失败后1秒重试
    if (authTokenUpdateTimer.value) {
      clearTimeout(authTokenUpdateTimer.value)
    }
    authTokenUpdateTimer.value = setTimeout(updateAuthToken, 1000) as unknown as NodeJS.Timeout
  }
}

// 手动重连
const manualReconnect = () => {
  if (isConnecting.value) return
  
  reconnectAttempts.value = 0 // 重置重连次数
  createWebSocketConnection()
}

// 创建WebSocket连接
const createWebSocketConnection = async (type = 1) => {
  if (!api.yweb_socket || isConnecting.value) return
  
  isConnecting.value = true
  connectionStatus.value = 'connecting'
  
  try {
    // 清理之前的连接
    // if (providerSocket.value && type == 1) {
    //   providerSocket.value.destroy()
    //   providerSocket.value = null
    // }
    
    // 清理之前的 token 更新定时器
    if (authTokenUpdateTimer.value) {
      clearTimeout(authTokenUpdateTimer.value)
      authTokenUpdateTimer.value = null
    }
    
    // 获取认证 token
    // try {
    //   authToken.value = await getAuthToken()
    //   console.log('🔐 认证 token 获取成功，准备连接 WebSocket')
    // }
    // catch (error) {
    //   console.error('🔐 获取认证 token 失败，连接可能失败:', error)
    //   connectionStatus.value = 'disconnected'
    //   isConnecting.value = false
    //   handleDisconnection()
    //   return
    // }

    docSocket.value = new Y.Doc()
    docSocket.value.gc = true
    providerSocket.value = new WebsocketProvider(
      api.yweb_socket,
      'PPT' + props.courseid,
      docSocket.value,
      // { params: { yauth: authToken.value } }
    )
    
    // 启动定期更新 token
    // updateAuthToken()

    providerSocket.value.on('status', (event: any) => {
      console.log('👉 WebSocket状态:', event.status)
      
      if (event.status === 'connected') {
        console.log('👉连接成功websocket(teachingMode)')
        connectionStatus.value = 'connected'
        isConnecting.value = false
        reconnectAttempts.value = 0 // 重置重连次数
        
        // 清理重连定时器
        if (reconnectTimer.value) {
          clearTimeout(reconnectTimer.value)
          reconnectTimer.value = null
        }
        
        mId.value = Math.random().toString(36).substr(2, 9)
        messageInit()
        // 连接成功后，读取当前计时器状态（Map）
        if (docSocket.value) {
          yTimerState.value = docSocket.value.getMap('timerState')
          const snapshot = yTimerState.value.toJSON()
          applyTimerStateSnapshot(snapshot)
          // 激光笔 map
          yLaserState.value = docSocket.value.getMap('laserState')
          const ls = yLaserState.value.toJSON()
          applyLaserStateSnapshot(ls)
          yLaserState.value.observe(() => {
            const s = yLaserState.value.toJSON()
            applyLaserStateSnapshot(s)
          })
          // 画图 map
          yWritingBoardState.value = docSocket.value.getMap('writingBoardState')
          const ws = yWritingBoardState.value.toJSON()
          console.log('📝 WebSocket连接成功，读取画图状态:', ws, '当前幻灯片:', currentSlide.value?.id)
          // 延迟应用，确保 currentSlide 已初始化
          nextTick(() => {
            // 如果 currentSlide 还没准备好，再等一帧
            if (currentSlide.value && currentSlide.value.id) {
              applyWritingBoardStateSnapshot(ws)
            }
            else {
              // 如果还没准备好，等待 currentSlide 变化（最多等待3秒）
              let timeoutId: any = null
              const unwatch = watch(() => currentSlide.value?.id, (slideId) => {
                if (slideId) {
                  applyWritingBoardStateSnapshot(ws)
                  unwatch()
                  if (timeoutId) clearTimeout(timeoutId)
                }
              }, { immediate: true })
              // 3秒后如果还没准备好，强制应用一次
              timeoutId = setTimeout(() => {
                if (currentSlide.value && currentSlide.value.id) {
                  applyWritingBoardStateSnapshot(ws)
                }
                unwatch()
              }, 3000)
            }
          })
          yWritingBoardState.value.observe(() => {
            const s = yWritingBoardState.value.toJSON()
            if (currentSlide.value && currentSlide.value.id) {
              applyWritingBoardStateSnapshot(s)
            }
          })
        }
      }
      else if (event.status === 'disconnected') {
        console.log('👉 WebSocket连接断开')
        connectionStatus.value = 'disconnected'
        isConnecting.value = false
        createWebSocketConnection(2)
      }
    })
    
    // 监听连接错误
    providerSocket.value.on('connection-error', (error: any) => {
      console.error('👉 WebSocket连接错误:', error)
      connectionStatus.value = 'disconnected'
      isConnecting.value = false
      createWebSocketConnection(2)
    })
    
  }
  catch (error) {
    console.error('👉 创建WebSocket连接失败:', error)
    connectionStatus.value = 'disconnected'
    isConnecting.value = false
    createWebSocketConnection(2)
  }

  // 启动 socket 连接检查定时器
  startSocketCheckTimer()
}

// 处理连接断开
const handleDisconnection = () => {
  if (reconnectAttempts.value < maxReconnectAttempts.value) {
    reconnectAttempts.value++
    console.log(`👉 尝试重连 (${reconnectAttempts.value}/${maxReconnectAttempts.value})`)
    
    reconnectTimer.value = setTimeout(() => {
      createWebSocketConnection()
    }, reconnectInterval.value) as unknown as NodeJS.Timeout
  }
  else {
    console.error('👉 WebSocket重连次数已达上限，停止重连')
    // 可以在这里显示用户提示
    message.error( lang.ssNetError )
  }
}

// 启动 socket 连接检查定时器
const startSocketCheckTimer = () => {
  // 清理之前的定时器
  if (socketCheckTimer.value) {
    clearInterval(socketCheckTimer.value)
    socketCheckTimer.value = null
  }
  
  // 每10秒检查一次 socket 连接状态
  socketCheckTimer.value = setInterval(() => {
    if (providerSocket.value) {
      // 直接检查 providerSocket 的连接状态
      // WebsocketProvider 有一个 connected 属性来表示连接状态
      const isConnected = (providerSocket.value as any).ws.readyState
      console.log('🔍 定时器检查 socket 连接状态:', isConnected)
      if (isConnected !== 1) {
        console.log('🔍 定时器检查发现 socket 未连接，执行重连')
        createWebSocketConnection(2)
      }
    }
  }, 10000) as unknown as NodeJS.Timeout
}

// 工具函数：格式化时间
const formatTime = (totalSec: number) => {
  const m = Math.floor(totalSec / 60)
  const s = Math.floor(totalSec % 60)
  return `${fillDigit(m, 2)}:${fillDigit(s, 2)}`
}

// 块状时间显示
const timerBlocks = () => {
  const total = timerIndicator.value.isCountdown
    ? Math.max(timerIndicator.value.remainingSec || 0, 0)
    : Math.max(timerIndicator.value.elapsedSec || 0, 0)
  const h = Math.floor(total / 3600)
  const m = Math.floor((total % 3600) / 60)
  const s = Math.floor(total % 60)
  return {
    h: fillDigit(h, 2),
    m: fillDigit(m, 2),
    s: fillDigit(s, 2),
  }
}

// 块可见性：始终显示时分秒
const timerBlocksVisibility = () => {
  return {
    showH: true,
    showM: true,
  }
}

// 根据布局避免遮挡右侧面板
const getTimerIndicatorRight = () => {
  if (isFullscreen.value) {
    return 16
  }
  if (props.type === '1') {
    // 右侧面板展开时向左让位
    return workPanelCollapsed.value ? 65 : 420
  }
  return 65
}

// 计时器本地更新
const startLocalTick = (isCountdown: boolean) => {
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
  timerInterval.value = setInterval(() => {
    if (isCountdown) {
      if (timerIndicator.value.remainingSec !== null) {
        const newRemaining = (timerIndicator.value.remainingSec as number) - 1
        timerIndicator.value.remainingSec = Math.max(newRemaining, 0)
        // 时间到了，标记为完成但保持显示
        if (newRemaining <= 0) {
          timerIndicator.value.finished = true
          timerIndicator.value.remainingSec = 0
          // 保持 visible 为 true，不隐藏
          timerIndicator.value.visible = true
        }
      }
    }
    else {
      if (timerIndicator.value.elapsedSec !== null) {
        timerIndicator.value.elapsedSec = (timerIndicator.value.elapsedSec as number) + 1
      }
    }
  }, 1000) as unknown as number
}

// CountdownTimer 事件（仅创建人触发发送）
const onTimerStart = (payload: { isCountdown: boolean; startAt: string; durationSec?: number }) => {
  timerIndicator.value.visible = true
  timerIndicator.value.isCountdown = payload.isCountdown
  timerIndicator.value.startAt = payload.startAt
  timerIndicator.value.durationSec = payload.isCountdown ? (payload.durationSec || 0) : null
  timerIndicator.value.elapsedSec = payload.isCountdown ? null : 0
  timerIndicator.value.remainingSec = payload.isCountdown ? (payload.durationSec || 0) : null
  timerIndicator.value.finished = false
  startLocalTick(payload.isCountdown)
  if (isCreator.value) {
    sendMessage({ type: 'timer_start', courseid: props.courseid, payload })
    // 持久化状态到 YMap（带运行状态与基线）
    const isCd = payload.isCountdown
    const state: any = {
      visible: true,
      isCountdown: isCd,
      status: 'running',
      startAt: payload.startAt,
      durationSec: isCd ? (payload.durationSec || 0) : null,
      finished: false,
      stopped: false,
    }
    if (isCd) {
      state.remainingBaseSec = payload.durationSec || 0
      state.elapsedBaseSec = null
    }
    else {
      state.elapsedBaseSec = 0
      state.remainingBaseSec = null
    }
    setTimerState(state)
  }
}
const onTimerPause = () => {
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
  if (isCreator.value) {
    sendMessage({ type: 'timer_pause', courseid: props.courseid })
    // 将当前显示值作为基线写入，并标记暂停
    const isCd = !!timerIndicator.value.isCountdown
    const payload: any = {
      ...getTimerState(),
      status: 'paused',
      pausedAt: new Date().toISOString(),
      finished: !!timerIndicator.value.finished,
      stopped: false,
    }
    if (isCd) {
      payload.remainingBaseSec = Math.max(Number(timerIndicator.value.remainingSec || 0), 0)
      payload.elapsedBaseSec = null
    }
    else {
      payload.elapsedBaseSec = Math.max(Number(timerIndicator.value.elapsedSec || 0), 0)
      payload.remainingBaseSec = null
    }
    setTimerState(payload)
  }
}
const onTimerReset = () => {
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
  timerIndicator.value = { visible: false, isCountdown: false, startAt: null, durationSec: null, elapsedSec: null, remainingSec: null, finished: false }
  if (isCreator.value) {
    sendMessage({ type: 'timer_reset', courseid: props.courseid })
    clearTimerState()
  }
}
const onTimerStop = () => {
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
  timerIndicator.value = { visible: false, isCountdown: false, startAt: null, durationSec: null, elapsedSec: null, remainingSec: null, finished: false }
  if (isCreator.value) {
    sendMessage({ type: 'timer_stop', courseid: props.courseid })
    clearTimerState()
  }
}
const onTimerFinish = () => {
  timerIndicator.value.finished = true
  // 保持 visible 为 true，时间到了也不消失
  timerIndicator.value.visible = true
  if (timerIndicator.value.isCountdown) {
    timerIndicator.value.remainingSec = 0
  }
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
  if (isCreator.value) {
    sendMessage({ type: 'timer_finish', courseid: props.courseid })
    const snap = getTimerState()
    setTimerState({ ...snap, status: 'finished', finished: true, stopped: true, remainingBaseSec: 0 })
  }
}
const onTimerUpdate = (payload: { durationSec: number }) => {
  if (isCreator.value && timerIndicator.value.visible && timerIndicator.value.isCountdown) {
    // 重新设置开始时间，重置整个计时
    const newStartAt = new Date().toISOString()
    
    // 更新本地状态
    timerIndicator.value.startAt = newStartAt
    timerIndicator.value.durationSec = payload.durationSec
    timerIndicator.value.remainingSec = payload.durationSec
    timerIndicator.value.finished = false
    
    // 重新开始本地计时
    startLocalTick(true)
    
    // 更新 YMap 状态
    const snap = getTimerState()
    setTimerState({
      ...snap,
      status: 'running',
      startAt: newStartAt,
      durationSec: payload.durationSec,
      remainingBaseSec: payload.durationSec,
      finished: false,
    })
    
    // 发送消息通知其他用户（使用 timer_start 消息重新开始计时）
    sendMessage({ 
      type: 'timer_start', 
      courseid: props.courseid, 
      payload: { 
        isCountdown: true, 
        startAt: newStartAt, 
        durationSec: payload.durationSec 
      } 
    })
  }
}

// 消息应用（任意端）
const applyTimerStart = (payload: { isCountdown: boolean; startAt: string; durationSec?: number }) => {
  timerIndicator.value.visible = true
  timerIndicator.value.isCountdown = payload.isCountdown
  timerIndicator.value.startAt = payload.startAt
  timerIndicator.value.durationSec = payload.isCountdown ? (payload.durationSec || 0) : null
  // 以消息时间为基准纠正进度
  const startTs = new Date(payload.startAt).getTime()
  const nowTs = Date.now()
  if (payload.isCountdown) {
    const elapsed = Math.floor((nowTs - startTs) / 1000)
    timerIndicator.value.remainingSec = Math.max((payload.durationSec || 0) - elapsed, 0)
    timerIndicator.value.elapsedSec = null
  }
  else {
    timerIndicator.value.elapsedSec = Math.floor((nowTs - startTs) / 1000)
    timerIndicator.value.remainingSec = null
  }
  timerIndicator.value.finished = false
  startLocalTick(payload.isCountdown)
}
const applyTimerPause = () => {
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
}
const applyTimerReset = () => {
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
  timerIndicator.value = { visible: false, isCountdown: false, startAt: null, durationSec: null, elapsedSec: null, remainingSec: null, finished: false }
}
const applyTimerStop = () => {
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
  timerIndicator.value = { visible: false, isCountdown: false, startAt: null, durationSec: null, elapsedSec: null, remainingSec: null, finished: false }
}
const applyTimerFinish = () => {
  timerIndicator.value.finished = true
  // 保持 visible 为 true，时间到了也不消失
  timerIndicator.value.visible = true
  if (timerIndicator.value.isCountdown) {
    timerIndicator.value.remainingSec = 0
  }
  if (timerInterval.value) {
    clearInterval(timerInterval.value)
    timerInterval.value = null
  }
}
const applyTimerUpdate = (payload: { durationSec: number; startAt?: string }) => {
  if (timerIndicator.value.visible && timerIndicator.value.isCountdown) {
    const newStartAt = payload.startAt || new Date().toISOString()
    
    // 更新状态
    timerIndicator.value.startAt = newStartAt
    timerIndicator.value.durationSec = payload.durationSec
    timerIndicator.value.remainingSec = payload.durationSec
    timerIndicator.value.finished = false
    
    // 重新开始本地计时
    startLocalTick(true)
  }
}

// 应用激光笔共享状态（任意端）
const applyLaserStateSnapshot = (snap: any) => {
  if (!snap) return
  const enabled = snap.enabled !== undefined ? !!snap.enabled : laserPenOverlay.value.visible
  const x = typeof snap.x === 'number' ? snap.x : null
  const y = typeof snap.y === 'number' ? snap.y : null
  
  if (props.type == '2') {
    // 更新开关状态
    if (snap.enabled !== undefined) {
      laserPenOverlay.value.visible = enabled
    }
    
    // 如果激光笔是开启状态，更新位置
    if (laserPenOverlay.value.visible && x != null && y != null) {
      laserPenOverlay.value.xPct = x
      laserPenOverlay.value.yPct = y
      refreshLaserOverlayRect()
      if (laserMoveRafId) cancelAnimationFrame(laserMoveRafId)
      laserMoveRafId = requestAnimationFrame(updateLaserDotPosition)
    }
  }
}

// YMap 状态应用
const applyTimerStateSnapshot = (snap: any) => {
  if (!snap || !snap.visible) {
    return
  }
  const isCountdown = !!snap.isCountdown
  const status = snap.status as string | undefined
  const startAt = snap.startAt as string
  const durationSec = isCountdown ? Number(snap.durationSec || 0) : null
  const finished = !!snap.finished
  const elapsedBaseSec = snap.elapsedBaseSec != null ? Number(snap.elapsedBaseSec) : null
  const remainingBaseSec = snap.remainingBaseSec != null ? Number(snap.remainingBaseSec) : null
  timerIndicator.value.visible = true
  timerIndicator.value.isCountdown = isCountdown
  timerIndicator.value.startAt = startAt
  timerIndicator.value.durationSec = durationSec
  const startTs = new Date(startAt).getTime()
  const nowTs = Date.now()
  if (isCountdown) {
    if (status === 'paused') {
      timerIndicator.value.remainingSec = Math.max(remainingBaseSec || 0, 0)
      timerIndicator.value.elapsedSec = null
      timerIndicator.value.finished = !!finished || (timerIndicator.value.remainingSec as number) <= 0
      if (timerInterval.value) {
        clearInterval(timerInterval.value); timerInterval.value = null 
      }
      return
    }
    const base = remainingBaseSec != null ? remainingBaseSec : (durationSec || 0)
    const elapsed = Math.floor((nowTs - startTs) / 1000)
    timerIndicator.value.remainingSec = Math.max(base - elapsed, 0)
    timerIndicator.value.elapsedSec = null
    if (finished || (timerIndicator.value.remainingSec as number) <= 0) {
      timerIndicator.value.finished = true
      timerIndicator.value.remainingSec = 0
      // 保持 visible 为 true，时间到了也不消失
      timerIndicator.value.visible = true
      if (timerInterval.value) {
        clearInterval(timerInterval.value); timerInterval.value = null 
      }
    }
    else {
      timerIndicator.value.finished = false
      startLocalTick(true)
    }
  }
  else {
    if (status === 'paused') {
      timerIndicator.value.elapsedSec = Math.max(elapsedBaseSec || 0, 0)
      timerIndicator.value.remainingSec = null
      timerIndicator.value.finished = !!finished
      if (timerInterval.value) {
        clearInterval(timerInterval.value); timerInterval.value = null 
      }
      return
    }
    const base = elapsedBaseSec != null ? elapsedBaseSec : 0
    const elapsed = Math.floor((nowTs - startTs) / 1000)
    timerIndicator.value.elapsedSec = base + elapsed
    timerIndicator.value.remainingSec = null
    if (finished) {
      if (timerInterval.value) {
        clearInterval(timerInterval.value); timerInterval.value = null 
      }
      timerIndicator.value.finished = true
    }
    else {
      timerIndicator.value.finished = false
      startLocalTick(false)
    }
  }
}

// 读写 YMap 工具
const getTimerState = () => {
  if (!yTimerState.value) return {}
  return yTimerState.value.toJSON()
}
const setTimerState = (state: any) => {
  if (!yTimerState.value) return
  docSocket.value?.transact(() => {
    Object.entries(state).forEach(([k, v]) => yTimerState.value.set(k, v as any))
    yTimerState.value.set('visible', true)
  })
}
const clearTimerState = () => {
  if (!yTimerState.value) return
  docSocket.value?.transact(() => {
    yTimerState.value.clear()
  })
}
</script>

<style lang="scss" scoped>
.pptist-student-viewer {
  height: 100vh;
  display: flex;
  background-color: #f4f4f4;
  padding: 15px 0;
  box-sizing: border-box;

  // 全屏模式样式
  &.fullscreen {
    padding: 0;
    .layout-content-left {
      display: none; // 全屏时隐藏左侧导航栏
    }

    .layout-content-right {
      display: none; // 全屏时隐藏左侧导航栏
    }

    .viewer-header {
      display: none; // 全屏时隐藏顶部标题栏
    }
  }

  // 激光笔模式样式
}

.layout-content-left {
  width: 200px;
  height: 100%;
  background-color: #fff;
  border-radius: 0 5px 0 5px;
  overflow: hidden;
  transition: width .2s ease;
  margin: 10px;
}
.layout-content-left.collapsed {
  width: 48px;
  margin-left: 0;
}
.slide-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
/* 收缩时头部仅显示按钮，并保持按钮在可用宽度内水平居中 */
.layout-content-left.collapsed .slide-header {
  justify-content: center;
  padding: 8px;
}

.layout-content-right {
  width: 350px;
  height: 100%;
  background-color: #fff;
  border-radius: 5px 0 5px 0;
  overflow: hidden;
  transition: width .2s ease;
  position: relative;
  margin: 10px;
}

.panel-content {
  margin-right: 0;
  // padding: 0 8px;
  height: calc(100% - 65px);
  overflow: auto;
}
.layout-content-right.collapsed {
  width: 52px;
  margin-right: 0px;
}
.right-panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* 侧边导航标签样式 */
.side-nav-tabs {
  position: absolute;
  right: 0;
  top: 60px;
  bottom: 0;
  width: 52px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 8px 0;
  align-items: center;
  background: #fff;
  border-left: 1px solid #e0e0e0;
  z-index: 10;
}

.side-nav-btn {
  width: 45px;
  height: 45px;
  min-height: 45px;
  border: 1px solid #d9d9d9;
  background: #fff;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  gap: 8px;
  overflow: hidden;
  
  &:hover {
    border-color: #1890ff;
    transform: scale(1.02);
  }
  
  &.active {
    border-color: #3681fc;
    background: #3681fc;
    box-shadow: 0 0 0 2px rgba(54, 129, 252, 0.2);
  }
  
  img {
    width: 25px;
    height: 25px;
    object-fit: contain;
    flex-shrink: 0;
  }
  
  span {
    font-size: 12px;
    font-weight: 500;
    color: #333;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  
  &.active span {
    color: #fff;
  }
}

/* 收缩时头部仅显示按钮，并保持按钮在可用宽度内水平居中 */
.layout-content-right.collapsed .right-panel-header {
  justify-content: center;
  padding: 8px;
}

/* 收缩状态下的切换按钮 */
.collapsed-tabs {
  display: flex;
  flex-direction: column;
  gap: 8px;
  // padding: 8px;
  align-items: center;
}

.collapsed-tab-btn {
  width: 32px;
  height: 32px;
  border: 1px solid #d9d9d9;
  background: #fff;
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  overflow: hidden;
  
  &:hover {
    border-color: #1890ff;
    transform: scale(1.05);
  }
  
  &.active {
    border-color: #3681fc;
    background: #3681fc;
    box-shadow: 0 0 0 2px rgba(54, 129, 252, 0.2);
  }
  
  img {
    width: 20px;
    height: 20px;
    object-fit: contain;
    transition: transform 0.2s;
  }
  
  &:hover img {
    transform: scale(1.1);
  }
}
.collapse-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  // border: 1px solid #d9d9d9;
  border: none;
  border-radius: 8px;
  background: #fff;
  color: #333;
  cursor: pointer;
  line-height: 1;
  font-weight: 700;
  position: absolute;
}
.collapse-btn:hover {
  // border-color: #1890ff;
  // color: #1890ff;
}

.homework-title {
  padding: 12px 12px 0 12px;
  color: #333;
  font-size: 14px;
  font-weight: 600;
}
.homework-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  padding: 12px;
}
.homework-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-width: 0;
  height: 35px;
  border: 1px solid #2f80ed;
  color: #2f80ed;
  background: #fff;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  overflow: hidden;
  padding: 0 10px;
  text-align: center;
}
.homework-btn__text {
  display: block;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.homework-btn:hover {
  box-shadow: 0 2px 10px rgba(0,0,0,0.08);
}

.homework-btn.unsubmitted {
  border-color: #d9d9d9;
  color: #999;
  background: #f5f5f5;
  cursor: not-allowed;
}

.homework-btn.unsubmitted:hover {
  box-shadow: none;
  transform: none;
}
.homework-loading {
  padding: 12px;
  color: #666;
  font-size: 13px;
}
.homework-empty {
  padding: 12px;
  color: #999;
  font-size: 13px;
}



.thumbnails {
  padding: 0;
  height: 100%;

  .viewer-header {
    margin-bottom: 16px;

    h3 {
      margin: 0;
      font-size: 16px;
      font-weight: 600;
      color: #333;
      text-align: center;
      width: 100%;
    }
  }

  .thumbnail-list {
    width: 100%;
    padding: 0 16px;
    box-sizing: border-box;

    .thumbnail-item {
      position: relative;
      margin-bottom: 12px;
      cursor: pointer;
      border-radius: 8px;
      overflow: hidden;
      transition: all 0.2s ease;
      border: 2px solid rgba(24, 144, 255, 0.2);

      &:hover {
        transform: translateY(-2px);
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
        border-color: rgba(24, 144, 255, 0.4);
      }

      &.active {
        border: 2px solid #1890ff;
        box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
      }

      .label {
        position: absolute;
        top: 8px;
        left: 8px;
        background-color: rgba(0, 0, 0, 0.6);
        color: #fff;
        padding: 2px 6px;
        border-radius: 4px;
        font-size: 12px;
        font-weight: 600;
        z-index: 1;
      }

      .thumbnail {
        width: 100%;
        height: auto;
      }
    }
  }

  .page-number {
    text-align: center;
    margin-top: 16px;
    padding: 8px;
    background-color: #f0f0f0;
    border-radius: 4px;
    font-size: 14px;
    color: #666;
  }

  .progress-bar {
    margin-top: 12px;
    height: 6px;
    background-color: #f0f0f0;
    border-radius: 3px;
    overflow: hidden;

    .progress-fill {
      height: 100%;
      background: linear-gradient(90deg, #1890ff, #40a9ff);
      border-radius: 3px;
      transition: width 0.3s ease;
    }
  }
}

.layout-content-center {
  flex: 1;
  display: flex;
  flex-direction: column;
  background-color: #000;
}

.viewer-header {
  height: 45px;
  background-color: #fff;
  border-bottom: 1px solid #e0e0e0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 8px;
  transition: transform 0.3s ease;
  position: relative;

  &.hidden {
    transform: translateY(-100%);
  }

  .slide-title {
    font-size: 18px;
    font-weight: 600;
    color: #333;
  }

  .viewer-controls {
    display: flex;
    gap: 12px;

    button {
      padding: 8px 12px;
      border: 1px solid #d9d9d9;
      border-radius: 6px;
      background-color: #fff;
      color: #333;
      cursor: pointer;
      transition: all 0.2s ease;
      display: flex;
      align-items: center;
      justify-content: center;
      min-width: 40px;
      height: 36px;

      &:hover:not(:disabled) {
        border-color: #1890ff;
        color: #1890ff;
      }

      &:disabled {
        opacity: 0.5;
        cursor: not-allowed;
      }

      &.back-btn {
        background-color: #1890ff;
        color: #fff;
        border-color: #1890ff;

        &:hover {
          background-color: #40a9ff;
          border-color: #40a9ff;
        }
      }

      &.follow-active {
        background-color: #3681fc;
        color: #fff !important;
        border-color: #3681fc;

        &:hover {
          background-color: #2d6fd9;
          border-color: #2d6fd9;
          color: #fff !important;
        }
      }

      .control-icon {
        font-size: 16px;
      }
    }
  }
}

.viewer-canvas {
  flex: 1;
  position: relative;
  background-color: rgb(244, 244, 244);
  overflow: hidden;

  // 全屏时隐藏滚动条和边框
  &.fullscreen-mode {
    overflow: hidden !important;
    background-color: transparent !important;
  }
}

.slide-list-wrap {
  position: absolute;
  overflow: hidden;
  background-color: #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.01), 0 0 12px 0 rgba(0, 0, 0, 0.1);
  border-radius: 8px;

  /* 全屏时去掉圆角 */
  .pptist-student-viewer.fullscreen & {
    border-radius: 0;
  }
}

.slide-list-wrap-n{
  border: 5px solid #595959;
  background: #000;
  padding: 15px 0 0 0;
  box-sizing: border-box;
}

/* 学生端激光笔覆盖层与小圆点样式（拦截点击） */
.laser-pointer-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  pointer-events: auto;
}
.laser-pointer-dot {
  position: absolute;
  width: 24px;
  height: 24px;
  pointer-events: none;
  /* 复用 .laser-pen 的光点视觉（使用与 cursor 相同的图） */
  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABHNCSVQICAgIfAhkiAAACCJJREFUWIXtmLuO3MYShv/qZl9IzqwXo2BkSAtsIK+z8wwOBcOJ9C56Cr2LlThQcgBnfofVBnswXlgTaLHaIdk3dtcJOKOzd8n2MeDABRDDgKz/m+pudv0N/BN/Luj/kYSZJQBxJR8DKESU/2zuPwTIzAKnpxqHhxUuLir0vYSUAkS0ewA5F7Rtxv7+iNPTEYeHkYjKXwrIzHK9XtultRohaKSkkFIVhqGCEAIxTvm0ZpRSTNOMUGqEUgnGxLX3cblc+t9T2S8GXK1W9dP53OLiwoLZhMtLQ4CiGBVKkchZIOcpn5QMKQuEyKx1YiCZvb0AooD9ff/rZuMPDg7cl+hWn3uAmQWABut1g/PzOnZdTd5bMY6aQtAIQQGQGEd5bYirKgPIZExiY2IKIbK1XpeinzaN2s7b4XPD/iAgM0ucn7fYbNrQ963Juaauq8k5i3E01PcG46iQs0TO1wGlzJAyo6oS2jagqgLGUQNQwTllvJeYzwUz9w8N+b2AzCxwft6i72fBuZkYhnbcbBqKsSbvazhnEIJBzqrEqGQpAlO1AaKShShC6wQpE4UQUNcBKenReyXm8yoIIYwQtNXq7qvkQxVssNm0wbmZuLiYUQgtnGtps2ngfQ3vLaVkEKOmGKcqMtMWkEnKTFonaB3Z+4AQPFmreD6vSAghxpECAFMKY7EoALovBlytVjXW6yb0fSuGoaUQWrq8nKHvW/R9S943xbmavJ+qmNIO8FMFIWXert7A1gYxjprHsSLmaTHt7UF0HYdSilmv82q1ynctnFuAzCzx8aPF+Xltcq7HzaaBcy36vsUwzKjrZhiGRgxDA+8tUjIUgkbOEqVMgEIUkjLDmAjvgwjBI6WKxlHybp5KyVRKMcaMGIb0dLFIzBxvzsdbgOv12i69t7HrpgURY02bTYO+b6nrZui6qZLONdz3jTg5ORDHx0f48OExQpgBAIzp8OjRez46Oi7Pnq1ot5BKETQVgYmosJRj6rrEQNJCxLX3EUB/LyAzC3z8qOGcIe8tOWdpmm81ed9gGJpdJdF1rXz79jucnX1za454P8fZ2ZzOzr6Rx8fvyvPnP38afiEKVVXmqhrJ+wSlIqoqYj73S2s1M7urC0ZcS3x6qhGCDpeXBuOoMY4Gzhl4b4tzNYahgXMNuq4Vb978cCfczTg7+0a8efMDuq6Fcw2GoSnO1fDewjmDcTQYx0kzBI3TU3319euAh4cVUlIEKApBU98bhGAoJSO8N/Dect834u3b73B+/vVn4XZxfv61ePv2O+77Bt5b4b2hlKbcfW8oBE2AQkoKh4fXRvU64MVFhZQqilEhBLX9CCvEqLer1YiTk4MvqtxdlTw5OcAWDDFq5DxphDBtmSlNzcddgMws0fcyDEOFUiQAiZxliVGVGFVJSXEImo6Pj3433Dbo+PiIQ9AlJbXLi5wnrVIm7b6X223wOiAAASkFhBDIWWAcJXKWshQhcpYiZ0k5S3z48PhO9ZcvgV9+ma6XL+8m/PDhMW1ziW1u5Cy3WpO2lOIq11VAAhEhRkLO0z0RgVmAefotRXz6lNyMV6+AxWK6Xr26GzCEGXZb4i7nTifnSXv6Tn7qssTdmf4+cRWQwczQmiHldM/MICogmn6FKDDmzj0Tr18D5+fT9fr13WrGdBCiXMu505Fy0mZmTJYBwPUPdUHOBaUUSFlQVRlS5rzbtqTMJGXGo0fvcXY2vyX+44/T9VA8evSepcy8zcdCFDG1ZBlSTto5FwC3P9RElNG22TTNCCEygAwps9A6Ca2TUCqRMZGPjo4fprg/+OjomIyJQqm0ywspJy0hJu22zVf34+tzcH9/hFIja51gTEJVJUiZoHWEMQFKhfLs2QpPnrz73XRPnrwrz56toFSAMQFaR0g5aRiTWOsEpUbs749XX7u51Y1QKjGQ2JjIbRtgTGClQrE2wFpPbTuU589/xmLx2xfDLRa/lefPf6a2HWCtL9YG3oJy2wY2JjKQoFTC6ekDgIeHEcZEs7cXUFURVTV1wtZ6UdcOTTOgrgfMZn158eKnL6rkkyfvyosXP2E261HXA5pmEHXtYK1HXU9WoKomTWMiDg/j1devbStEVN6/fx+XRIGt9RhHjZQ0Wat4HCsax//1fEQlf//9v8XJyTF9rt1q2+mPtW2PphnY2gHWOrbWcV17ttaDKKy9j4/398u9gACwXC49Pn7UuhQNQI3eT206s2DadptCFEiZqaoS/+tfvnz77X/oRsPKUmYyJpJSAdZ6NM2Aphl4Pu/QND3P5wO0dmo2c5jNHPb3/fKrr/xNnluARJRXq5V/2jQqOKfE1kPsPC8zM1VVLkqNwpiAEAxbq+hGy89SZtq2/MXaIOrasbUDmqZH2/Zo257bdghSOtM07tfNxh/s799yd3d6koODA8fM0ngvw9bgYG9vatOJClfVSFUVYe3UldxhmiBlxtY0kVLTlLHW8Xw+oG17NqYvs1lv6rrHcjkcEN1p5B9ydQPmc2GEoABAdB1TKYWlnDph5wJvbSdPpwvXbCcLUXhrO2FMQF0HttZBa8dtO5TZrDdt26FtewDDfRD3AhJRYeYemKxh2Bqc1HVTm17Xn4y7yFnyDeMurhh33hp3rmuvZjMXpHSmrqehXiz6h04XHjxZIKLMzB0Wi2LW64xhSAwkVFXEOGpo/dmjD2yPPlBVka31mM2caRqH5XLAnz362FUSQLdarfLTxSJpISLmcx8uLw217R8/PLpnzt3S/5KHdvG3Pn67Afr3PMB8APgvOwL+J/5s/BeEBm1u1Gu4+QAAAABJRU5ErkJggg==);
  background-repeat: no-repeat;
  background-position: center center;
  background-size: contain;
  /* 居中到指针 */
  transform: translate3d(-12px, -12px, 0);
  will-change: transform;
}

.laser-pen {
  cursor: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAABHNCSVQICAgIfAhkiAAACCJJREFUWIXtmLuO3MYShv/qZl9IzqwXo2BkSAtsIK+z8wwOBcOJ9C56Cr2LlThQcgBnfofVBnswXlgTaLHaIdk3dtcJOKOzd8n2MeDABRDDgKz/m+pudv0N/BN/Luj/kYSZJQBxJR8DKESU/2zuPwTIzAKnpxqHhxUuLir0vYSUAkS0ewA5F7Rtxv7+iNPTEYeHkYjKXwrIzHK9XtultRohaKSkkFIVhqGCEAIxTvm0ZpRSTNOMUGqEUgnGxLX3cblc+t9T2S8GXK1W9dP53OLiwoLZhMtLQ4CiGBVKkchZIOcpn5QMKQuEyKx1YiCZvb0AooD9ff/rZuMPDg7cl+hWn3uAmQWABut1g/PzOnZdTd5bMY6aQtAIQQGQGEd5bYirKgPIZExiY2IKIbK1XpeinzaN2s7b4XPD/iAgM0ucn7fYbNrQ963Juaauq8k5i3E01PcG46iQs0TO1wGlzJAyo6oS2jagqgLGUQNQwTllvJeYzwUz9w8N+b2AzCxwft6i72fBuZkYhnbcbBqKsSbvazhnEIJBzqrEqGQpAlO1AaKShShC6wQpE4UQUNcBKenReyXm8yoIIYwQtNXq7qvkQxVssNm0wbmZuLiYUQgtnGtps2ngfQ3vLaVkEKOmGKcqMtMWkEnKTFonaB3Z+4AQPFmreD6vSAghxpECAFMKY7EoALovBlytVjXW6yb0fSuGoaUQWrq8nKHvW/R9S943xbmavJ+qmNIO8FMFIWXert7A1gYxjprHsSLmaTHt7UF0HYdSilmv82q1ynctnFuAzCzx8aPF+Xltcq7HzaaBcy36vsUwzKjrZhiGRgxDA+8tUjIUgkbOEqVMgEIUkjLDmAjvgwjBI6WKxlHybp5KyVRKMcaMGIb0dLFIzBxvzsdbgOv12i69t7HrpgURY02bTYO+b6nrZui6qZLONdz3jTg5ORDHx0f48OExQpgBAIzp8OjRez46Oi7Pnq1ot5BKETQVgYmosJRj6rrEQNJCxLX3EUB/LyAzC3z8qOGcIe8tOWdpmm81ed9gGJpdJdF1rXz79jucnX1za454P8fZ2ZzOzr6Rx8fvyvPnP38afiEKVVXmqhrJ+wSlIqoqYj73S2s1M7urC0ZcS3x6qhGCDpeXBuOoMY4Gzhl4b4tzNYahgXMNuq4Vb978cCfczTg7+0a8efMDuq6Fcw2GoSnO1fDewjmDcTQYx0kzBI3TU3319euAh4cVUlIEKApBU98bhGAoJSO8N/Dect834u3b73B+/vVn4XZxfv61ePv2O+77Bt5b4b2hlKbcfW8oBE2AQkoKh4fXRvU64MVFhZQqilEhBLX9CCvEqLer1YiTk4MvqtxdlTw5OcAWDDFq5DxphDBtmSlNzcddgMws0fcyDEOFUiQAiZxliVGVGFVJSXEImo6Pj3433Dbo+PiIQ9AlJbXLi5wnrVIm7b6X223wOiAAASkFhBDIWWAcJXKWshQhcpYiZ0k5S3z48PhO9ZcvgV9+ma6XL+8m/PDhMW1ziW1u5Cy3WpO2lOIq11VAAhEhRkLO0z0RgVmAefotRXz6lNyMV6+AxWK6Xr26GzCEGXZb4i7nTifnSXv6Tn7qssTdmf4+cRWQwczQmiHldM/MICogmn6FKDDmzj0Tr18D5+fT9fr13WrGdBCiXMu505Fy0mZmTJYBwPUPdUHOBaUUSFlQVRlS5rzbtqTMJGXGo0fvcXY2vyX+44/T9VA8evSepcy8zcdCFDG1ZBlSTto5FwC3P9RElNG22TTNCCEygAwps9A6Ca2TUCqRMZGPjo4fprg/+OjomIyJQqm0ywspJy0hJu22zVf34+tzcH9/hFIja51gTEJVJUiZoHWEMQFKhfLs2QpPnrz73XRPnrwrz56toFSAMQFaR0g5aRiTWOsEpUbs749XX7u51Y1QKjGQ2JjIbRtgTGClQrE2wFpPbTuU589/xmLx2xfDLRa/lefPf6a2HWCtL9YG3oJy2wY2JjKQoFTC6ekDgIeHEcZEs7cXUFURVTV1wtZ6UdcOTTOgrgfMZn158eKnL6rkkyfvyosXP2E261HXA5pmEHXtYK1HXU9WoKomTWMiDg/j1devbStEVN6/fx+XRIGt9RhHjZQ0Wat4HCsax//1fEQlf//9v8XJyTF9rt1q2+mPtW2PphnY2gHWOrbWcV17ttaDKKy9j4/398u9gACwXC49Pn7UuhQNQI3eT206s2DadptCFEiZqaoS/+tfvnz77X/oRsPKUmYyJpJSAdZ6NM2Aphl4Pu/QND3P5wO0dmo2c5jNHPb3/fKrr/xNnluARJRXq5V/2jQqOKfE1kPsPC8zM1VVLkqNwpiAEAxbq+hGy89SZtq2/MXaIOrasbUDmqZH2/Zo257bdghSOtM07tfNxh/s799yd3d6koODA8fM0ngvw9bgYG9vatOJClfVSFUVYe3UldxhmiBlxtY0kVLTlLHW8Xw+oG17NqYvs1lv6rrHcjkcEN1p5B9ydQPmc2GEoABAdB1TKYWlnDph5wJvbSdPpwvXbCcLUXhrO2FMQF0HttZBa8dtO5TZrDdt26FtewDDfRD3AhJRYeYemKxh2Bqc1HVTm17Xn4y7yFnyDeMurhh33hp3rmuvZjMXpHSmrqehXiz6h04XHjxZIKLMzB0Wi2LW64xhSAwkVFXEOGpo/dmjD2yPPlBVka31mM2caRqH5XLAnz362FUSQLdarfLTxSJpISLmcx8uLw217R8/PLpnzt3S/5KHdvG3Pn67Afr3PMB8APgvOwL+J/5s/BeEBm1u1Gu4+QAAAABJRU5ErkJggg==) 20 20, default !important;
}

.slide-bottom{
  height: 60px;
  background: #000;
  position: relative;
  z-index: 10;
  posttion: relative;
}

.slide-bottom-center{
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}

.slide-bottom-center-item{
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 15px;

  img{
    width: 24px;
    height: 24px;
    cursor: pointer;
  }

  .slide-bottom-center-item-page{
    color: #fff;
    font-size: 16px;
    font-weight: 600;
    display: flex;
    gap: 5px;
  }
}
.slide-bottom-right{
  position: absolute;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 15px;
  font-size: 24px;
  color: #fff;

  .tool-btn {
    cursor: pointer;

    &:hover,
    &.active {
      color: #1890ff;
    }

    &+.tool-btn {
      margin-left: 15px;
    }
      
  }
  .upBtn{
      border-bottom: 3px solid #fff;
      padding-bottom: 3px;

      &:hover,
      &.active {
        border-color: #1890ff;
      }
      

    }
  .tool-btn.loading {
    animation: icon-rotate 1s linear infinite;
  }
  @keyframes icon-rotate {
    100% { transform: rotate(360deg); }
  }
}

.upBtn :deep(svg) {
    width: calc(1em - 3px) !important;
    height: calc(1em - 3px) !important;
}

.loading-indicator {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 10;

  .loading-text {
    color: #666;
    font-size: 14px;
    background-color: rgba(255, 255, 255, 0.9);
    padding: 12px 20px;
    border-radius: 6px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  }
}

// 全屏模式样式
.fullscreen-slide {
  // 使用放映功能的默认样式
}

// 全屏工具按钮样式，直接复制放映功能的样式
.tools-left {
  position: fixed;
  bottom: 8px;
  left: 8px;
  font-size: 25px;
  color: #666;
  z-index: 10;

  .tool-btn {
    opacity: .3;
    cursor: pointer;
    transition: opacity 0.3s;

    &:hover {
      opacity: .95;
    }

    &+.tool-btn {
      margin-left: 8px;
    }
  }
}

.tools-right {
  height: 66px;
  position: fixed;
  bottom: -66px;
  right: 0;
  z-index: 5;
  padding: 8px;
  transition: bottom 0.3s;

  &.visible {
    bottom: 0;
  }

  &::after {
    content: '';
    width: 100%;
    height: 66px;
    position: absolute;
    left: 0;
    top: -66px;
  }

  .content {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: 4px;
    font-size: 25px;
    background-color: #fff;
    color: #333;
    padding: 8px 10px;
    box-shadow: 0 2px 12px 0 rgb(56, 56, 56, .2);
    border: 1px solid #e2e6ed;
  }

  .tool-btn {
    cursor: pointer;

    &:hover,
    &.active {
      color: #1890ff;
    }

    &+.tool-btn {
      margin-left: 15px;
    }
  }

  .page-number {
    font-size: 12px;
    padding: 0 12px;
    cursor: pointer;
  }
}

// 右上角计时状态指示器样式
.timer-indicator {
  position: fixed;
  z-index: 1000;
  // background: rgba(0, 0, 0, 0.75);
  color: #fff;
  border-radius: 8px;
  padding: 8px 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  // border: 1px solid rgba(255, 255, 255, 0.15);

  .label {
    font-size: 12px;
    opacity: .9;
    margin-right: 2px;
    white-space: nowrap;
  }

  .blocks {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .block {
    min-width: 45px;
    height: 35px;
    padding: 0 8px;
    border-radius: 6px;
    background: #111;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: 22px;
    letter-spacing: 1px;
  }
  .colon {
    position: relative;
    width: 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
  .colon::before,
  .colon::after {
    content: '';
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: #000;
    display: block;
    opacity: .9;
    position: absolute;
    left: 0;
  }
  .colon::before { top: 4px; }
  .colon::after { bottom: 4px; }

  // 全屏尺寸略大
  .pptist-student-viewer.fullscreen & .block {
    min-width: 40px;
    height: 30px;
    font-size: 18px;
  }

  &.countdown .block {
    background: #222;
  }
  &.timeout .block,
  &.timeout .colon {
    background: #ff4d4f;
    color: #fff;
  }
}

.viewport {
  position: relative;
  width: 100%;
  height: 100%;
  background-color: #fff;
}

.background {
  width: 100%;
  height: 100%;
  background-position: center;
  position: absolute;
}

// 响应式设计
@media (max-width: 768px) {
  .layout-content-left {
    width: 160px;
  }

  .layout-content-right {
    width: 160px;
  }

  .viewer-header {
    padding: 0 16px;

    .slide-title {
      font-size: 16px;
    }

    .viewer-controls button {
      padding: 6px 12px;
      font-size: 14px;
    }
  }
}

/* 作业提交按钮样式 */
.homework-submit-btn {
  position: fixed;
  bottom: 160px;
  z-index: 100;
  background: #191a19;
  color: white;
  padding: 5px 20px;
  border-radius: 5px;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  border: 2px solid #191a19;
  transition: all 0.3s ease;
  font-size: 16px;
  font-weight: 500;

  &:hover:not(.submitting) {
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
  }

  &:active:not(.submitting) {
    transform: translateY(0);
  }

  &.submitting {
    cursor: not-allowed;
    opacity: 0.8;
    background: linear-gradient(135deg, #999 0%, #666 100%);
  }

  .btn-text {
    white-space: nowrap;
  }

  .tool-btn {
    background: transparent;
    color: white;
    width: 20px;
    height: 20px;
    font-size: 16px;

    &:hover {
      background: transparent;
      transform: none;
    }
  }

  .loading-spinner {
    width: 20px;
    height: 20px;
    border: 2px solid rgba(255, 255, 255, 0.3);
    border-top: 2px solid white;
    border-radius: 50%;
    animation: spin 1s linear infinite;
  }
}

/* 刷新网页按钮样式 */
.refresh-page-btn {
  position: fixed;
  bottom: 160px;
  z-index: 100;
  color: #000;
  padding: 5px 20px;
  border-radius: 5px;
  background: #fff;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 8px;
  border: 2px solid #e9e9e9;
  transition: all 0.3s ease;
  font-size: 16px;
  font-weight: 500;

  &:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
  }

  &:active {
    transform: translateY(0);
  }

  .btn-text {
    white-space: nowrap;
  }

  .tool-btn {
    background: transparent;
    color: #000;
    width: 20px;
    height: 20px;
    font-size: 16px;
    display: flex;
    align-items: center;

    &:hover {
      background: transparent;
      transform: none;
    }
  }
}

/* Loading状态样式 */
.loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.95);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;

    .loading-spinner {
      width: 40px;
      height: 40px;
      border: 4px solid #f3f3f3;
      border-top: 4px solid #1890ff;
      border-radius: 50%;
      animation: spin 1s linear infinite;
      margin: 0 auto 16px;
  }
}

.loading-content {
    text-align: center;
    color: #666;
}


.loading-text {
    font-size: 14px;
    color: #666;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

/* 标签页切换器样式 */
.tab-switcher {
  display: flex;
  flex: 1;
  margin-right: 12px;
  border-bottom: 1px solid #e0e0e0;
  padding-bottom: 0;
  height: 100%;
  justify-content: center;
  gap: 20px;
}

.tab-btn {
  // flex: 1;
  // padding: 12px 16px;
  border: none;
  background: transparent;
  color: #666;
  cursor: pointer;
  transition: all 0.2s ease;
  font-size: 14px;
  font-weight: 500;
  text-align: center;
  white-space: nowrap;
  position: relative;
  border-radius: 0;
  
  &:hover {
    color: #333;
  }
  
  &.active {
    color: #333;
    font-weight: 600;
    
    &::after {
      content: '';
      position: absolute;
      bottom: -1px;
      left: 0;
      right: 0;
      height: 2px;
      background: #333;
      border-radius: 1px;
    }
  }
}

// 在适当位置添加连接状态指示器
.connection-status {
  position: fixed;
  top: 10px;
  right: 10px;
  background-color: rgba(255, 255, 255, 0.9);
  border-radius: 5px;
  padding: 5px 10px;
  display: flex;
  align-items: center;
  z-index: 1000;

  .status-indicator {
    // 胶囊浅蓝底 + 蓝色文字
    padding: 5px 12px 5px 22px;
    border-radius: 5px;
    margin-right: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    position: relative;
    background: transparent; // 根据具体状态设置渐变
    // 边框去除
    // border: 1px solid rgba(59, 111, 255, 0.35);
    box-shadow: 0 2px 8px rgba(59, 111, 255, 0.15);

    // 左侧状态圆点（不同状态不同颜色）
    &::before {
      content: "";
      position: absolute;
      left: 8px;
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background-color: #1890ff; // 默认使用连接中蓝色
      box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.12);
    }

    &.connected::before {
      background-color: #52c41a; // 原始绿色
      box-shadow: 0 0 0 2px rgba(82, 196, 26, 0.18);
    }

    &.connecting::before {
      background-color: #1890ff; // 原始蓝色
      box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.18);
    }

    &.disconnected::before {
      background-color: #ff4d4f; // 原始红色
      box-shadow: 0 0 0 2px rgba(255, 77, 79, 0.18);
    }

    span {
      color: #1890ff; // 默认蓝色，具体状态里覆盖
      font-size: 12px;
      // font-weight: 600;
      letter-spacing: 0.2px;
    }

    // 以原始主色为基础的浅色渐变与文字色
    &.connected {
      background: rgba(82, 196, 26, 0.15);
      span { color: #52c41a; }
    }

    &.connecting {
      background: rgba(24, 144, 255, 0.15);
      span { color: #1890ff; }
    }

    &.disconnected {
      background: rgba(255, 77, 79, 0.15);
      span { color: #ff4d4f; }
    }
  }

  .reconnect-btn {
    background: linear-gradient(180deg, #eaf2ff 0%, #ddebff 100%);
    color: #3b6fff;
    border: none;
    border-radius: 5px;
    padding: 6px 14px;
    cursor: pointer;
    transition: all 0.2s ease;
    // font-weight: 600;
    box-shadow: 0 2px 8px rgba(59, 111, 255, 0.15);

    &:hover {
      background: linear-gradient(180deg, #e2edff 0%, #d3e4ff 100%);
      border-color: rgba(59, 111, 255, 0.55);
      box-shadow: 0 4px 12px rgba(59, 111, 255, 0.2);
    }
  }
}
</style>