index.tsx 28 KB

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