index.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817
  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. checkedAnimation(popupData.activeIndex)
  235. postMessage({
  236. api: 'courseLoading',
  237. content: {
  238. show: false,
  239. type: 'fullscreen'
  240. }
  241. })
  242. //检测是否录屏
  243. // handleLimitScreenRecord()
  244. })
  245. }
  246. const getDetail = async () => {
  247. try {
  248. const res: any = await request.get(
  249. state.platformApi + `/lessonCoursewareDetail/detail/${route.query.id}`,
  250. {
  251. hideLoading: true
  252. }
  253. )
  254. if (Array.isArray(res?.data)) {
  255. data.detail = res.data
  256. }
  257. if (Array.isArray(res?.data?.knowledgePointList)) {
  258. let index = 0
  259. data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
  260. if (Array.isArray(n.materialList)) {
  261. n.materialList = n.materialList.map((item: any) => {
  262. index++
  263. return {
  264. ...item,
  265. materialId: item.id,
  266. id: index + ''
  267. }
  268. })
  269. }
  270. return n
  271. })
  272. getItemList()
  273. }
  274. } catch (error) {}
  275. }
  276. // ifram事件处理
  277. const iframeHandle = (ev: MessageEvent) => {
  278. if (ev.data?.api === 'headerTogge') {
  279. activeData.model = ev.data.show || (ev.data.playState == 'play' ? false : true)
  280. }
  281. }
  282. //录屏时间触发
  283. const handleLimitScreenRecord = async () => {
  284. const result = await promisefiyPostMessage({
  285. api: 'getDeviceStatus',
  286. content: { type: 'video' }
  287. })
  288. const { status } = result?.content || {}
  289. if (status == '1') {
  290. data.itemList.forEach((item: any) => (item.autoPlay = false))
  291. handleStop()
  292. showDialog({
  293. title: '温馨提示',
  294. message: '课件内容请勿录屏',
  295. beforeClose: () => {
  296. return new Promise(async (resolve) => {
  297. const { content } =
  298. (await promisefiyPostMessage({
  299. api: 'getDeviceStatus',
  300. content: { type: 'video' }
  301. })) || {}
  302. if (content?.status == '1') {
  303. resolve(false)
  304. } else {
  305. const activeItem = data.itemList[popupData.activeIndex]
  306. togglePlay(activeItem, true)
  307. resolve(true)
  308. }
  309. })
  310. }
  311. })
  312. }
  313. }
  314. onMounted(() => {
  315. const hasVip = handleCheckVip()
  316. if (!hasVip) {
  317. nextTick(() => {
  318. postMessage({
  319. api: 'courseLoading',
  320. content: {
  321. show: false,
  322. type: 'fullscreen'
  323. }
  324. })
  325. })
  326. return
  327. }
  328. getDetail()
  329. getCourseSchedule()
  330. window.addEventListener('message', iframeHandle)
  331. //禁止录屏 ios
  332. // listenerMessage('setVideoPlayer', (result) => {
  333. // if (result?.content?.status == 'pause'){
  334. // handleLimitScreenRecord()
  335. // }
  336. // })
  337. // // 安卓
  338. // postMessage({
  339. // api: 'limitScreenRecord',
  340. // content: {
  341. // type: 1
  342. // }
  343. // })
  344. })
  345. // onBeforeUnmount(() => {
  346. // postMessage({
  347. // api: 'limitScreenRecord',
  348. // content: {
  349. // type: 0
  350. // }
  351. // })
  352. // })
  353. const playRef = ref()
  354. // 返回
  355. const goback = () => {
  356. try {
  357. playRef.value?.handleOut()
  358. } catch (error) {}
  359. postMessage({ api: 'goBack' })
  360. }
  361. const popupData = reactive({
  362. open: false,
  363. activeIndex: 0,
  364. tabActive: '',
  365. tabName: '',
  366. itemActive: '',
  367. itemName: '',
  368. guideOpen: false
  369. })
  370. /**停止所有的播放 */
  371. const handleStop = () => {
  372. for (let i = 0; i < data.itemList.length; i++) {
  373. const activeItem = data.itemList[i]
  374. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  375. activeItem.videoEle.stop()
  376. }
  377. // console.log('🚀 ~ activeItem:', activeItem)
  378. // 停止曲谱的播放
  379. if (activeItem.type === 'SONG') {
  380. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  381. }
  382. }
  383. }
  384. // 切换素材
  385. const toggleMaterial = (itemActive: any) => {
  386. const index = data.itemList.findIndex((n: any) => n.id == itemActive)
  387. if (index > -1) {
  388. handleSwipeChange(index)
  389. }
  390. }
  391. /** 延迟收起模态框 */
  392. const setModelOpen = () => {
  393. clearTimeout(activeData.timer)
  394. closeToast()
  395. activeData.timer = setTimeout(() => {
  396. activeData.model = false
  397. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(false))
  398. }, 4000)
  399. }
  400. // 去点名,签退
  401. const gotoRollCall = (pageTag: string) => {
  402. postMessage({
  403. api: 'open_app_page',
  404. content: {
  405. action: 'app',
  406. pageTag: pageTag,
  407. url: '',
  408. params: JSON.stringify({ courseId: route.query.courseId })
  409. }
  410. })
  411. }
  412. // 双击
  413. const handleDbClick = (item: any) => {
  414. if (item && item.type === 'VIDEO') {
  415. const videoEle: HTMLVideoElement = item.videoEle
  416. if (videoEle) {
  417. if (videoEle.paused) {
  418. closeToast()
  419. videoEle.play()
  420. } else {
  421. showToast('已暂停')
  422. videoEle.pause()
  423. }
  424. }
  425. }
  426. }
  427. // 切换播放
  428. const togglePlay = (m: any, isPlay: boolean) => {
  429. if (isPlay) {
  430. m.videoEle?.play()
  431. } else {
  432. m.videoEle?.pause()
  433. }
  434. }
  435. const showIndex = ref(-4)
  436. const effectIndex = ref(0)
  437. const effects = [
  438. {
  439. prev: {
  440. transform: 'translate3d(0, 0, -800px) rotateX(180deg)',
  441. },
  442. next: {
  443. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)',
  444. }
  445. },
  446. {
  447. prev: {
  448. transform: 'translate3d(-100%, 0, -800px)',
  449. },
  450. next: {
  451. transform: 'translate3d(100%, 0, -800px)',
  452. }
  453. },
  454. {
  455. prev: {
  456. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)',
  457. },
  458. next: {
  459. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)',
  460. }
  461. },
  462. {
  463. prev: {
  464. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)',
  465. },
  466. next: {
  467. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)',
  468. }
  469. },
  470. //风车4
  471. {
  472. prev: {
  473. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  474. opacity: 0
  475. },
  476. next: {
  477. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  478. opacity: 0
  479. }
  480. },
  481. //翻页5
  482. // {
  483. // prev: {
  484. // transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  485. // opacity: 0
  486. // },
  487. // next: {
  488. // transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  489. // opacity: 0
  490. // },
  491. // current: { transitionDelay: '700ms' }
  492. // }
  493. ]
  494. const acitveTimer = ref()
  495. const animationTimer =ref()
  496. // 轮播切换
  497. const handleSwipeChange = (index: number) => {
  498. if (popupData.activeIndex == index) return
  499. checkedAnimation(popupData.activeIndex)
  500. popupData.activeIndex = index
  501. handleStop()
  502. clearTimeout(acitveTimer.value)
  503. clearTimeout(animationTimer.value)
  504. const item = data.itemList[index]
  505. acitveTimer.value = setTimeout(
  506. () => {
  507. if (item) {
  508. popupData.tabActive = item.knowledgePointId
  509. popupData.itemActive = item.id
  510. popupData.itemName = item.name
  511. popupData.tabName = item.tabName
  512. if (item.type == 'SONG') {
  513. activeData.model = true
  514. }
  515. if (item.type === 'VIDEO') {
  516. // 自动播放下一个视频
  517. clearTimeout(activeData.timer)
  518. closeToast()
  519. item.autoPlay = true
  520. nextTick(() => {
  521. item.videoEle?.play()
  522. })
  523. }
  524. }
  525. },
  526. item.isAnimation ? 800 : 0
  527. )
  528. animationTimer.value = setTimeout(() => {
  529. const index = effectIndex.value + 1
  530. effectIndex.value = index >= effects.length - 1 ? 0 : index
  531. // effectIndex.value = Math.round(Math.random() * (effects.length - 1))
  532. }, 1500)
  533. }
  534. /** 是否有转场动画 */
  535. const checkedAnimation = (index: number) => {
  536. const oldItem = data.itemList[index]
  537. const preItem = data.itemList[index - 1]
  538. const nextItem = data.itemList[index + 1]
  539. oldItem && (oldItem.isAnimation = oldItem?.adviseStudyTimeSecond < 8 ? false : true)
  540. preItem && (preItem.isAnimation = oldItem.knowledgePointId == preItem.knowledgePointId && oldItem?.adviseStudyTimeSecond < 8 ? false : true)
  541. nextItem && (nextItem.isAnimation = oldItem.knowledgePointId == nextItem.knowledgePointId && oldItem?.adviseStudyTimeSecond < 8 ? false : true)
  542. }
  543. // 上一个知识点, 下一个知识点
  544. const handlePreAndNext = (type: string) => {
  545. if (type === 'up') {
  546. handleSwipeChange(popupData.activeIndex - 1)
  547. } else {
  548. handleSwipeChange(popupData.activeIndex + 1)
  549. }
  550. }
  551. return () => (
  552. <div class={styles.playContent}>
  553. <div
  554. onClick={() => {
  555. clearTimeout(activeData.timer)
  556. activeData.model = !activeData.model
  557. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(activeData.model))
  558. }}
  559. >
  560. <div
  561. class={styles.coursewarePlay}
  562. style={{ width: parentContainer.width }}
  563. onClick={(e: Event) => {
  564. e.stopPropagation()
  565. setModelOpen()
  566. }}
  567. >
  568. <div class={styles.wraps}>
  569. {data.itemList.map((m: any, mIndex: number) => {
  570. const isRender = m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2
  571. if (isRender) {
  572. m.isRender = true
  573. }
  574. return isRender ? (
  575. <div
  576. key={'index' + mIndex}
  577. class={[styles.itemDiv, popupData.activeIndex === mIndex && styles.itemActive]}
  578. style={{
  579. ...(mIndex < popupData.activeIndex
  580. ? effects[effectIndex.value].prev
  581. : mIndex > popupData.activeIndex
  582. ? effects[effectIndex.value].next
  583. : (effects[effectIndex.value] as any).current),
  584. ...(m.isAnimation ? {} : { 'transition-duration': '0s'})
  585. }}
  586. onClick={(e: Event) => {
  587. e.stopPropagation()
  588. clearTimeout(activeData.timer)
  589. if (Date.now() - activeData.nowTime < 300) {
  590. handleDbClick(m)
  591. return
  592. }
  593. activeData.nowTime = Date.now()
  594. activeData.timer = setTimeout(() => {
  595. activeData.model = !activeData.model
  596. Object.values(data.videoRefs).map((n: any) =>
  597. n.toggleHideControl(activeData.model)
  598. )
  599. if (activeData.model) {
  600. setModelOpen()
  601. }
  602. }, 300)
  603. }}
  604. >
  605. {m.type === 'VIDEO' ? (
  606. <>
  607. <VideoPlay
  608. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  609. item={m}
  610. onLoadedmetadata={(videoItem: any) => {
  611. m.videoEle = videoItem
  612. }}
  613. onTogglePlay={(paused: boolean) => {
  614. // console.log('播放切换', paused)
  615. // 首次播放完成
  616. if (!m.isprepare) {
  617. m.isprepare = true
  618. }
  619. m.autoPlay = false
  620. if (paused || popupData.open || popupData.guideOpen) {
  621. clearTimeout(activeData.timer)
  622. } else {
  623. setModelOpen()
  624. }
  625. }}
  626. onEnded={() => handleSwipeChange(popupData.activeIndex + 1)}
  627. onReset={() => {
  628. if (!m.videoEle?.paused) {
  629. setModelOpen()
  630. }
  631. }}
  632. />
  633. <Transition name="van-fade">
  634. {!m.isprepare && (
  635. <div class={styles.loadWrap}>
  636. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  637. </div>
  638. )}
  639. </Transition>
  640. </>
  641. ) : m.type === 'IMG' ? (
  642. <img src={m.content} />
  643. ) : (
  644. <MusicScore
  645. activeModel={activeData.model}
  646. data-vid={m.id}
  647. music={m}
  648. onSetIframe={(el: any) => {
  649. m.iframeRef = el
  650. }}
  651. />
  652. )}
  653. </div>
  654. ) : null
  655. })}
  656. </div>
  657. <Transition name="right">
  658. {activeData.model && (
  659. <div
  660. class={styles.rightFixedBtns}
  661. onClick={(e: Event) => {
  662. e.stopPropagation()
  663. clearTimeout(activeData.timer)
  664. }}
  665. >
  666. <div class={styles.btnsWrap}>
  667. <div
  668. class={[styles.fullBtn, styles.point]}
  669. onClick={() => (popupData.open = true)}
  670. >
  671. <img src={iconMenu} />
  672. <span>知识点</span>
  673. </div>
  674. </div>
  675. <div class={[styles.btnsWrap, styles.btnsBottom]}>
  676. <div class={styles.fullBtn} onClick={() => (popupData.guideOpen = true)}>
  677. <img src={iconTouping} />
  678. <span>投屏</span>
  679. </div>
  680. {data.isCourse && (
  681. <>
  682. <div
  683. class={styles.fullBtn}
  684. onClick={() => gotoRollCall('student_roll_call')}
  685. >
  686. <img src={iconDian} />
  687. <span>点名</span>
  688. </div>
  689. <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
  690. <img src={iconPoint} />
  691. <span>签退</span>
  692. </div>
  693. </>
  694. )}
  695. </div>
  696. </div>
  697. )}
  698. </Transition>
  699. <Transition name="left">
  700. {activeData.model && (
  701. <div class={styles.leftFixedBtns} onClick={(e: Event) => e.stopPropagation()}>
  702. {popupData.activeIndex != 0 && (
  703. <div class={[styles.btnsWrap, styles.prePoint]}>
  704. <div class={styles.fullBtn} onClick={() => handlePreAndNext('up')}>
  705. <img src={iconUp} />
  706. <span style={{ textAlign: 'center' }}>上一个</span>
  707. </div>
  708. </div>
  709. )}
  710. {popupData.activeIndex != data.itemList.length - 1 && (
  711. <div class={styles.btnsWrap}>
  712. <div class={styles.fullBtn} onClick={() => handlePreAndNext('down')}>
  713. <span style={{ textAlign: 'center' }}>下一个</span>
  714. <img src={iconDown} />
  715. </div>
  716. </div>
  717. )}
  718. </div>
  719. )}
  720. </Transition>
  721. </div>
  722. </div>
  723. <div
  724. style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
  725. id="coursePlayHeader"
  726. class={styles.headerContainer}
  727. ref={headeRef}
  728. >
  729. <div class={styles.backBtn} onClick={() => goback()}>
  730. <Icon name={iconBack} />
  731. 返回
  732. </div>
  733. <div
  734. class={styles.menu}
  735. onClick={() => {
  736. // if (showIndex.value > 0) {
  737. // effectIndex.value =
  738. // effectIndex.value < effects.length - 1 ? effectIndex.value + 1 : 0
  739. // } else {
  740. // showIndex.value += 1
  741. // }
  742. // setModelOpen()
  743. }}
  744. >
  745. {popupData.tabName}
  746. </div>
  747. {data.isCourse && <PlayRecordTime ref={playRef} list={data.itemList} />}
  748. </div>
  749. <Popup
  750. class={styles.popup}
  751. overlayClass={styles.overlayClass}
  752. position="right"
  753. round
  754. v-model:show={popupData.open}
  755. onClose={() => {
  756. const item = data.itemList[popupData.activeIndex]
  757. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  758. setModelOpen()
  759. }
  760. }}
  761. >
  762. <Points
  763. data={data.knowledgePointList}
  764. tabActive={popupData.tabActive}
  765. itemActive={popupData.itemActive}
  766. onHandleSelect={(res: any) => {
  767. popupData.open = false
  768. toggleMaterial(res.itemActive)
  769. }}
  770. />
  771. </Popup>
  772. <Popup
  773. class={styles.popup}
  774. overlayClass={styles.overlayClass}
  775. position="right"
  776. round
  777. v-model:show={popupData.guideOpen}
  778. onClose={() => {
  779. const item = data.itemList[popupData.activeIndex]
  780. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  781. setModelOpen()
  782. }
  783. }}
  784. >
  785. <OGuide />
  786. </Popup>
  787. </div>
  788. )
  789. }
  790. })