index.tsx 25 KB

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