index.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812
  1. import {
  2. closeToast,
  3. Icon,
  4. Loading,
  5. Popup,
  6. showDialog,
  7. showToast,
  8. Slider,
  9. Swipe,
  10. SwipeInstance,
  11. SwipeItem
  12. } from 'vant'
  13. import {
  14. defineComponent,
  15. onMounted,
  16. reactive,
  17. nextTick,
  18. onUnmounted,
  19. ref,
  20. watch,
  21. Transition,
  22. TransitionGroup,
  23. onBeforeUnmount
  24. } from 'vue'
  25. import iconBack from './image/back.svg'
  26. import styles from './index.module.less'
  27. import 'plyr/dist/plyr.css'
  28. import request from '@/helpers/request'
  29. import { state } from '@/state'
  30. import { useRoute, useRouter } from 'vue-router'
  31. import { listenerMessage, postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  32. import MusicScore from './component/musicScore'
  33. import iconMenu from './image/icon-menu.svg'
  34. import iconDian from './image/icon-dian.svg'
  35. import iconTouping from './image/icon-touping.svg'
  36. import iconPoint from './image/icon-point.svg'
  37. import iconLoop from './image/icon-loop.svg'
  38. import iconLoopActive from './image/icon-loop-active.svg'
  39. import iconplay from './image/icon-play.svg'
  40. import iconpause from './image/icon-pause.svg'
  41. import iconUp from './image/icon-up.svg'
  42. import iconDown from './image/icon-down.svg'
  43. import Points from './component/points'
  44. import { browser, getSecondRPM } from '@/helpers/utils'
  45. import { Vue3Lottie } from 'vue3-lottie'
  46. import playLoadData from './datas/data.json'
  47. import { usePageVisibility, useRect } from '@vant/use'
  48. import PlayRecordTime from './playRecordTime'
  49. import VideoPlay from './component/video-play'
  50. import {
  51. Pagination,
  52. Navigation,
  53. Virtual,
  54. EffectFade,
  55. EffectFlip,
  56. EffectCreative,
  57. Lazy
  58. } from 'swiper'
  59. import { Swiper, SwiperSlide } from 'swiper/vue'
  60. import 'swiper/less'
  61. import 'swiper/less/effect-fade'
  62. import 'swiper/less/effect-flip'
  63. import 'swiper/less/effect-creative'
  64. import { handleCheckVip } from '../hook/useFee'
  65. import OGuide from '@/components/o-guide'
  66. export default defineComponent({
  67. name: 'CoursewarePlay',
  68. setup() {
  69. const pageVisibility = usePageVisibility()
  70. const isPlay = ref(false)
  71. /** 页面显示和隐藏 */
  72. watch(pageVisibility, (value) => {
  73. const activeItem = data.itemList[popupData.activeIndex]
  74. if (activeItem.type != 'VIDEO') return
  75. if (value == 'hidden') {
  76. isPlay.value = !activeItem.videoEle.paused
  77. togglePlay(activeItem, false)
  78. } else {
  79. // 页面显示,并且
  80. if (isPlay.value) togglePlay(activeItem, true)
  81. }
  82. })
  83. /** 设置播放容器 16:9 */
  84. const parentContainer = reactive({
  85. width: '100vw'
  86. })
  87. const setContainer = () => {
  88. let min = Math.min(screen.width, screen.height)
  89. let max = Math.max(screen.width, screen.height)
  90. let width = min * (16 / 9)
  91. if (width > max) {
  92. parentContainer.width = '100vw'
  93. return
  94. } else {
  95. parentContainer.width = width + 'px'
  96. }
  97. }
  98. const handleInit = (type = 0) => {
  99. //设置容器16:9
  100. setContainer()
  101. // 横屏
  102. postMessage(
  103. {
  104. api: 'setRequestedOrientation',
  105. content: {
  106. orientation: type
  107. }
  108. },
  109. () => {
  110. console.log(234)
  111. }
  112. )
  113. // 头,包括返回箭头
  114. postMessage({
  115. api: 'setTitleBarVisibility',
  116. content: {
  117. status: type
  118. }
  119. })
  120. // 安卓的状态栏
  121. postMessage({
  122. api: 'setStatusBarVisibility',
  123. content: {
  124. isVisibility: type
  125. }
  126. })
  127. // 进入页面设置常量
  128. postMessage({
  129. api: 'keepScreenLongLight',
  130. content: {
  131. isOpenLight: type ? true : false
  132. }
  133. })
  134. }
  135. handleInit()
  136. onUnmounted(() => {
  137. handleInit(1)
  138. window.removeEventListener('message', iframeHandle)
  139. })
  140. const route = useRoute()
  141. const router = useRouter()
  142. const headeRef = ref()
  143. const data = reactive({
  144. detail: null,
  145. knowledgePointList: [] as any,
  146. itemList: [] as any,
  147. showHead: true,
  148. isCourse: false,
  149. isRecordPlay: false,
  150. videoRefs: {}
  151. })
  152. const activeData = reactive({
  153. nowTime: 0,
  154. model: true, // 遮罩
  155. videoBtns: true, // 视频
  156. currentTime: 0,
  157. duration: 0,
  158. timer: null as any,
  159. item: null as any
  160. })
  161. // 获取缓存路径
  162. const getCacheFilePath = async (material: any) => {
  163. const res = await promisefiyPostMessage({
  164. api: 'getCourseFilePath',
  165. content: {
  166. url: material.content,
  167. localPath: '',
  168. materialId: material.materialId,
  169. updateTime: material.updateTime,
  170. type: material.type // SONG VIDEO IMAGE
  171. }
  172. })
  173. // console.log('缓存路径返回', res)
  174. return res
  175. }
  176. // 获取当前课程是否签退
  177. const getCourseSchedule = async () => {
  178. if (!route.query.courseId) return
  179. try {
  180. const res = await request.get(
  181. `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`,
  182. {
  183. hideLoading: true
  184. }
  185. )
  186. if (res?.data) {
  187. data.isCourse =
  188. res.data.status === 'ING' && state.platformType == 'TEACHER' ? true : false
  189. // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf()
  190. }
  191. } catch (e) {
  192. console.log(e)
  193. }
  194. }
  195. const getItemList = async () => {
  196. const list: any = []
  197. const browserInfo = browser()
  198. for (let i = 0; i < data.knowledgePointList.length; i++) {
  199. const item = data.knowledgePointList[i]
  200. const itemLength = item.materialList.length - 1
  201. for (let j = 0; j < item.materialList.length; j++) {
  202. const material = item.materialList[j]
  203. //请求本地缓存
  204. if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.type)) {
  205. const localData = await getCacheFilePath(material)
  206. if (localData?.content?.localPath) {
  207. material.url = material.content
  208. material.content = localData.content.localPath
  209. }
  210. }
  211. list.push({
  212. ...material,
  213. iframeRef: null,
  214. videoEle: null,
  215. tabName: item.name,
  216. isAnimation: true, // 当前知识点
  217. autoPlay: false, //加载完成是否自动播放
  218. isprepare: false, // 视频是否加载完成
  219. isRender: false // 是否渲染了
  220. })
  221. }
  222. }
  223. let _firstIndex = list.findIndex((n: any) => n.materialId == route.query.kId)
  224. _firstIndex = _firstIndex > -1 ? _firstIndex : 0
  225. const item = list[_firstIndex]
  226. item.autoPlay = true
  227. popupData.activeIndex = _firstIndex
  228. popupData.tabName = item.tabName
  229. popupData.tabActive = item.knowledgePointId
  230. popupData.itemActive = item.id
  231. popupData.itemName = item.name
  232. nextTick(() => {
  233. data.itemList = list
  234. postMessage({
  235. api: 'courseLoading',
  236. content: {
  237. show: false,
  238. type: 'fullscreen'
  239. }
  240. })
  241. //检测是否录屏
  242. // handleLimitScreenRecord()
  243. })
  244. }
  245. const getDetail = async () => {
  246. try {
  247. const res: any = await request.get(
  248. state.platformApi + `/lessonCoursewareDetail/detail/${route.query.id}`,
  249. {
  250. hideLoading: true
  251. }
  252. )
  253. if (Array.isArray(res?.data)) {
  254. data.detail = res.data
  255. }
  256. if (Array.isArray(res?.data?.knowledgePointList)) {
  257. let index = 0
  258. data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
  259. if (Array.isArray(n.materialList)) {
  260. n.materialList = n.materialList.map((item: any) => {
  261. index++
  262. return {
  263. ...item,
  264. materialId: item.id,
  265. id: index + ''
  266. }
  267. })
  268. }
  269. return n
  270. })
  271. getItemList()
  272. }
  273. } catch (error) {}
  274. }
  275. // ifram事件处理
  276. const iframeHandle = (ev: MessageEvent) => {
  277. if (ev.data?.api === 'headerTogge') {
  278. activeData.model = ev.data.show || (ev.data.playState == 'play' ? false : true)
  279. }
  280. }
  281. //录屏时间触发
  282. const handleLimitScreenRecord = async () => {
  283. const result = await promisefiyPostMessage({
  284. api: 'getDeviceStatus',
  285. content: { type: 'video' }
  286. })
  287. const { status } = result?.content || {}
  288. if (status == '1') {
  289. data.itemList.forEach((item: any) => (item.autoPlay = false))
  290. handleStop()
  291. showDialog({
  292. title: '温馨提示',
  293. message: '课件内容请勿录屏',
  294. beforeClose: () => {
  295. return new Promise(async (resolve) => {
  296. const { content } =
  297. (await promisefiyPostMessage({
  298. api: 'getDeviceStatus',
  299. content: { type: 'video' }
  300. })) || {}
  301. if (content?.status == '1') {
  302. resolve(false)
  303. } else {
  304. const activeItem = data.itemList[popupData.activeIndex]
  305. togglePlay(activeItem, true)
  306. resolve(true)
  307. }
  308. })
  309. }
  310. })
  311. }
  312. }
  313. onMounted(() => {
  314. const hasVip = handleCheckVip()
  315. if (!hasVip) {
  316. nextTick(() => {
  317. postMessage({
  318. api: 'courseLoading',
  319. content: {
  320. show: false,
  321. type: 'fullscreen'
  322. }
  323. })
  324. })
  325. return
  326. }
  327. getDetail()
  328. getCourseSchedule()
  329. window.addEventListener('message', iframeHandle)
  330. //禁止录屏 ios
  331. // listenerMessage('setVideoPlayer', (result) => {
  332. // if (result?.content?.status == 'pause'){
  333. // handleLimitScreenRecord()
  334. // }
  335. // })
  336. // // 安卓
  337. // postMessage({
  338. // api: 'limitScreenRecord',
  339. // content: {
  340. // type: 1
  341. // }
  342. // })
  343. })
  344. // onBeforeUnmount(() => {
  345. // postMessage({
  346. // api: 'limitScreenRecord',
  347. // content: {
  348. // type: 0
  349. // }
  350. // })
  351. // })
  352. const playRef = ref()
  353. // 返回
  354. const goback = () => {
  355. try {
  356. playRef.value?.handleOut()
  357. } catch (error) {}
  358. postMessage({ api: 'goBack' })
  359. }
  360. const popupData = reactive({
  361. open: false,
  362. activeIndex: 0,
  363. tabActive: '',
  364. tabName: '',
  365. itemActive: '',
  366. itemName: '',
  367. guideOpen: false
  368. })
  369. /**停止所有的播放 */
  370. const handleStop = () => {
  371. for (let i = 0; i < data.itemList.length; i++) {
  372. const activeItem = data.itemList[i]
  373. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  374. activeItem.videoEle.stop()
  375. }
  376. // console.log('🚀 ~ activeItem:', activeItem)
  377. // 停止曲谱的播放
  378. if (activeItem.type === 'SONG') {
  379. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  380. }
  381. }
  382. }
  383. // 切换素材
  384. const toggleMaterial = (itemActive: any) => {
  385. const index = data.itemList.findIndex((n: any) => n.id == itemActive)
  386. if (index > -1) {
  387. handleSwipeChange(index)
  388. }
  389. }
  390. /** 延迟收起模态框 */
  391. const setModelOpen = () => {
  392. clearTimeout(activeData.timer)
  393. closeToast()
  394. activeData.timer = setTimeout(() => {
  395. activeData.model = false
  396. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(false))
  397. }, 4000)
  398. }
  399. // 去点名,签退
  400. const gotoRollCall = (pageTag: string) => {
  401. postMessage({
  402. api: 'open_app_page',
  403. content: {
  404. action: 'app',
  405. pageTag: pageTag,
  406. url: '',
  407. params: JSON.stringify({ courseId: route.query.courseId })
  408. }
  409. })
  410. }
  411. // 双击
  412. const handleDbClick = (item: any) => {
  413. if (item && item.type === 'VIDEO') {
  414. const videoEle: HTMLVideoElement = item.videoEle
  415. if (videoEle) {
  416. if (videoEle.paused) {
  417. closeToast()
  418. videoEle.play()
  419. } else {
  420. showToast('已暂停')
  421. videoEle.pause()
  422. }
  423. }
  424. }
  425. }
  426. // 切换播放
  427. const togglePlay = (m: any, isPlay: boolean) => {
  428. if (isPlay) {
  429. m.videoEle?.play()
  430. } else {
  431. m.videoEle?.pause()
  432. }
  433. }
  434. const showIndex = ref(-4)
  435. const effectIndex = ref(0)
  436. const effects = [
  437. {
  438. prev: {
  439. transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
  440. },
  441. next: {
  442. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
  443. }
  444. },
  445. {
  446. prev: {
  447. transform: 'translate3d(-100%, 0, -800px)'
  448. },
  449. next: {
  450. transform: 'translate3d(100%, 0, -800px)'
  451. }
  452. },
  453. {
  454. prev: {
  455. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
  456. },
  457. next: {
  458. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
  459. }
  460. },
  461. {
  462. prev: {
  463. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
  464. },
  465. next: {
  466. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
  467. }
  468. },
  469. //风车4
  470. {
  471. prev: {
  472. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  473. opacity: 0
  474. },
  475. next: {
  476. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  477. opacity: 0
  478. }
  479. },
  480. //翻页5
  481. // {
  482. // prev: {
  483. // transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  484. // opacity: 0
  485. // },
  486. // next: {
  487. // transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  488. // opacity: 0
  489. // },
  490. // current: { transitionDelay: '700ms' }
  491. // }
  492. ]
  493. const acitveTimer = ref()
  494. // 轮播切换
  495. const handleSwipeChange = (index: number) => {
  496. if (popupData.activeIndex == index) return
  497. const oldIndex = popupData.activeIndex
  498. popupData.activeIndex = index
  499. handleStop()
  500. clearTimeout(acitveTimer.value)
  501. const item = data.itemList[index]
  502. const oldItem = data.itemList[oldIndex]
  503. if (item) {
  504. if (oldItem?.adviseStudyTimeSecond < 8){
  505. const preItem = data.itemList[index - 1]
  506. const nextItem = data.itemList[index + 1]
  507. preItem && (preItem.isAnimation = item.knowledgePointId != preItem.knowledgePointId)
  508. nextItem && (nextItem.isAnimation = item.knowledgePointId != nextItem.knowledgePointId)
  509. }
  510. }
  511. acitveTimer.value = setTimeout(
  512. () => {
  513. if (item) {
  514. popupData.tabActive = item.knowledgePointId
  515. popupData.itemActive = item.id
  516. popupData.itemName = item.name
  517. popupData.tabName = item.tabName
  518. if (item.type == 'SONG') {
  519. activeData.model = true
  520. }
  521. if (item.type === 'VIDEO') {
  522. // 自动播放下一个视频
  523. clearTimeout(activeData.timer)
  524. closeToast()
  525. item.autoPlay = true
  526. nextTick(() => {
  527. item.videoEle?.play()
  528. })
  529. }
  530. nextTick(() => {
  531. effectIndex.value = Math.round(Math.random() * (effects.length - 1))
  532. })
  533. }
  534. },
  535. item.isAnimation ? 800 : 0
  536. )
  537. }
  538. // 上一个知识点, 下一个知识点
  539. const handlePreAndNext = (type: string) => {
  540. if (type === 'up') {
  541. handleSwipeChange(popupData.activeIndex - 1)
  542. } else {
  543. handleSwipeChange(popupData.activeIndex + 1)
  544. }
  545. }
  546. return () => (
  547. <div class={styles.playContent}>
  548. <div
  549. onClick={() => {
  550. clearTimeout(activeData.timer)
  551. activeData.model = !activeData.model
  552. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(activeData.model))
  553. }}
  554. >
  555. <div
  556. class={styles.coursewarePlay}
  557. style={{ width: parentContainer.width }}
  558. onClick={(e: Event) => {
  559. e.stopPropagation()
  560. setModelOpen()
  561. }}
  562. >
  563. <div class={styles.wraps}>
  564. {data.itemList.map((m: any, mIndex: number) => {
  565. const isRender = m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2
  566. if (isRender) {
  567. m.isRender = true
  568. }
  569. return isRender ? (
  570. <div
  571. key={'index' + mIndex}
  572. class={[styles.itemDiv, popupData.activeIndex === mIndex && styles.itemActive]}
  573. style={{
  574. ...(mIndex < popupData.activeIndex
  575. ? effects[effectIndex.value].prev
  576. : mIndex > popupData.activeIndex
  577. ? effects[effectIndex.value].next
  578. : (effects[effectIndex.value] as any).current),
  579. ...(m.isAnimation ? {} : { 'transition-duration': '0s' })
  580. }}
  581. onClick={(e: Event) => {
  582. e.stopPropagation()
  583. clearTimeout(activeData.timer)
  584. if (Date.now() - activeData.nowTime < 300) {
  585. handleDbClick(m)
  586. return
  587. }
  588. activeData.nowTime = Date.now()
  589. activeData.timer = setTimeout(() => {
  590. activeData.model = !activeData.model
  591. Object.values(data.videoRefs).map((n: any) =>
  592. n.toggleHideControl(activeData.model)
  593. )
  594. if (activeData.model) {
  595. setModelOpen()
  596. }
  597. }, 300)
  598. }}
  599. >
  600. {m.type === 'VIDEO' ? (
  601. <>
  602. <VideoPlay
  603. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  604. item={m}
  605. onLoadedmetadata={(videoItem: any) => {
  606. m.videoEle = videoItem
  607. }}
  608. onTogglePlay={(paused: boolean) => {
  609. // console.log('播放切换', paused)
  610. // 首次播放完成
  611. if (!m.isprepare) {
  612. m.isprepare = true
  613. }
  614. m.autoPlay = false
  615. if (paused || popupData.open || popupData.guideOpen) {
  616. clearTimeout(activeData.timer)
  617. } else {
  618. setModelOpen()
  619. }
  620. }}
  621. onEnded={() => handleSwipeChange(popupData.activeIndex + 1)}
  622. onReset={() => {
  623. if (!m.videoEle?.paused) {
  624. setModelOpen()
  625. }
  626. }}
  627. />
  628. <Transition name="van-fade">
  629. {!m.isprepare && (
  630. <div class={styles.loadWrap}>
  631. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  632. </div>
  633. )}
  634. </Transition>
  635. </>
  636. ) : m.type === 'IMG' ? (
  637. <img src={m.content} />
  638. ) : (
  639. <MusicScore
  640. activeModel={activeData.model}
  641. data-vid={m.id}
  642. music={m}
  643. onSetIframe={(el: any) => {
  644. m.iframeRef = el
  645. }}
  646. />
  647. )}
  648. </div>
  649. ) : null
  650. })}
  651. </div>
  652. <Transition name="right">
  653. {activeData.model && (
  654. <div
  655. class={styles.rightFixedBtns}
  656. onClick={(e: Event) => {
  657. e.stopPropagation()
  658. clearTimeout(activeData.timer)
  659. }}
  660. >
  661. <div class={styles.btnsWrap}>
  662. <div
  663. class={[styles.fullBtn, styles.point]}
  664. onClick={() => (popupData.open = true)}
  665. >
  666. <img src={iconMenu} />
  667. <span>知识点</span>
  668. </div>
  669. </div>
  670. <div class={[styles.btnsWrap, styles.btnsBottom]}>
  671. <div class={styles.fullBtn} onClick={() => (popupData.guideOpen = true)}>
  672. <img src={iconTouping} />
  673. <span>投屏</span>
  674. </div>
  675. {data.isCourse && (
  676. <>
  677. <div
  678. class={styles.fullBtn}
  679. onClick={() => gotoRollCall('student_roll_call')}
  680. >
  681. <img src={iconDian} />
  682. <span>点名</span>
  683. </div>
  684. <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
  685. <img src={iconPoint} />
  686. <span>签退</span>
  687. </div>
  688. </>
  689. )}
  690. </div>
  691. </div>
  692. )}
  693. </Transition>
  694. <Transition name="left">
  695. {activeData.model && (
  696. <div class={styles.leftFixedBtns} onClick={(e: Event) => e.stopPropagation()}>
  697. {popupData.activeIndex != 0 && (
  698. <div class={[styles.btnsWrap, styles.prePoint]}>
  699. <div class={styles.fullBtn} onClick={() => handlePreAndNext('up')}>
  700. <img src={iconUp} />
  701. <span style={{ textAlign: 'center' }}>上一个</span>
  702. </div>
  703. </div>
  704. )}
  705. {popupData.activeIndex != data.itemList.length - 1 && (
  706. <div class={styles.btnsWrap}>
  707. <div class={styles.fullBtn} onClick={() => handlePreAndNext('down')}>
  708. <span style={{ textAlign: 'center' }}>下一个</span>
  709. <img src={iconDown} />
  710. </div>
  711. </div>
  712. )}
  713. </div>
  714. )}
  715. </Transition>
  716. </div>
  717. </div>
  718. <div
  719. style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
  720. id="coursePlayHeader"
  721. class={styles.headerContainer}
  722. ref={headeRef}
  723. >
  724. <div class={styles.backBtn} onClick={() => goback()}>
  725. <Icon name={iconBack} />
  726. 返回
  727. </div>
  728. <div
  729. class={styles.menu}
  730. onClick={() => {
  731. // if (showIndex.value > 0) {
  732. // effectIndex.value =
  733. // effectIndex.value < effects.length - 1 ? effectIndex.value + 1 : 0
  734. // } else {
  735. // showIndex.value += 1
  736. // }
  737. // setModelOpen()
  738. }}
  739. >
  740. {popupData.tabName}
  741. </div>
  742. {data.isCourse && <PlayRecordTime ref={playRef} list={data.itemList} />}
  743. </div>
  744. <Popup
  745. class={styles.popup}
  746. overlayClass={styles.overlayClass}
  747. position="right"
  748. round
  749. v-model:show={popupData.open}
  750. onClose={() => {
  751. const item = data.itemList[popupData.activeIndex]
  752. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  753. setModelOpen()
  754. }
  755. }}
  756. >
  757. <Points
  758. data={data.knowledgePointList}
  759. tabActive={popupData.tabActive}
  760. itemActive={popupData.itemActive}
  761. onHandleSelect={(res: any) => {
  762. popupData.open = false
  763. toggleMaterial(res.itemActive)
  764. }}
  765. />
  766. </Popup>
  767. <Popup
  768. class={styles.popup}
  769. overlayClass={styles.overlayClass}
  770. position="right"
  771. round
  772. v-model:show={popupData.guideOpen}
  773. onClose={() => {
  774. const item = data.itemList[popupData.activeIndex]
  775. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  776. setModelOpen()
  777. }
  778. }}
  779. >
  780. <OGuide />
  781. </Popup>
  782. </div>
  783. )
  784. }
  785. })