index.tsx 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951
  1. import { closeToast, Icon, Popup, showDialog } from 'vant'
  2. import {
  3. defineComponent,
  4. onMounted,
  5. reactive,
  6. nextTick,
  7. onUnmounted,
  8. ref,
  9. watch,
  10. Transition,
  11. computed
  12. } from 'vue'
  13. import iconBack from './image/back.svg'
  14. import styles from './index.module.less'
  15. import 'plyr/dist/plyr.css'
  16. import request from '@/helpers/request'
  17. import { state } from '@/state'
  18. import { useRoute } from 'vue-router'
  19. import { postMessage, promisefiyPostMessage } from '@/helpers/native-message'
  20. import MusicScore from './component/musicScore'
  21. import iconMenu from './image/icon-menu.svg'
  22. import iconDian from './image/icon-dian.svg'
  23. import iconTouping from './image/icon-touping.svg'
  24. import iconPoint from './image/icon-point.svg'
  25. import iconUp from './image/icon-up.svg'
  26. import iconDown from './image/icon-down.svg'
  27. import Points from './component/points'
  28. import { browser } from '@/helpers/utils'
  29. import { Vue3Lottie } from 'vue3-lottie'
  30. import playLoadData from './datas/data.json'
  31. import { usePageVisibility } from '@vant/use'
  32. import PlayRecordTime from './playRecordTime'
  33. import { handleCheckVip } from '../hook/useFee'
  34. import OGuide from '@/components/o-guide'
  35. import Tool, { ToolItem, ToolType } from './component/tool'
  36. import Pen from './component/tools/pen'
  37. import iconPen from './image/icon-pen.png'
  38. import VideoItem from './component/video-item'
  39. export default defineComponent({
  40. name: 'CoursewarePlay',
  41. setup() {
  42. const pageVisibility = usePageVisibility()
  43. /** 页面显示和隐藏 */
  44. watch(
  45. () => pageVisibility.value,
  46. (value) => {
  47. if (value == 'hidden') {
  48. handleStop()
  49. }
  50. }
  51. )
  52. /** 设置播放容器 16:9 */
  53. const parentContainer = reactive({
  54. width: '100vw'
  55. })
  56. const setContainer = () => {
  57. const min = Math.min(screen.width, screen.height)
  58. const max = Math.max(screen.width, screen.height)
  59. const width = min * (16 / 9)
  60. if (width > max) {
  61. parentContainer.width = '100vw'
  62. return
  63. } else {
  64. parentContainer.width = width + 'px'
  65. }
  66. }
  67. const handleInit = (type = 0) => {
  68. //设置容器16:9
  69. setContainer()
  70. // 横屏
  71. postMessage(
  72. {
  73. api: 'setRequestedOrientation',
  74. content: {
  75. orientation: type
  76. }
  77. },
  78. () => {
  79. console.log(234)
  80. }
  81. )
  82. // 头,包括返回箭头
  83. // postMessage({
  84. // api: 'setTitleBarVisibility',
  85. // content: {
  86. // status: type
  87. // }
  88. // })
  89. // 安卓的状态栏
  90. postMessage({
  91. api: 'setStatusBarVisibility',
  92. content: {
  93. isVisibility: type
  94. }
  95. })
  96. // 进入页面设置常量
  97. postMessage({
  98. api: 'keepScreenLongLight',
  99. content: {
  100. isOpenLight: type ? true : false
  101. }
  102. })
  103. }
  104. handleInit()
  105. onUnmounted(() => {
  106. handleInit(1)
  107. window.removeEventListener('message', iframeHandle)
  108. })
  109. const route = useRoute()
  110. const headeRef = ref()
  111. const data = reactive({
  112. detail: null,
  113. knowledgePointList: [] as any,
  114. itemList: [] as any,
  115. showHead: true,
  116. isCourse: false,
  117. isRecordPlay: false,
  118. videoRefs: {},
  119. videoState: 'init' as 'init' | 'play',
  120. animationState: 'start' as 'start' | 'end'
  121. })
  122. const activeData = reactive({
  123. isAutoPlay: true, // 是否自动播放
  124. nowTime: 0,
  125. model: true, // 遮罩
  126. isAnimation: true, // 是否动画
  127. videoBtns: true, // 视频
  128. currentTime: 0,
  129. duration: 0,
  130. timer: null as any,
  131. item: null as any
  132. })
  133. // 获取缓存路径
  134. const getCacheFilePath = async (material: any) => {
  135. const res = await promisefiyPostMessage({
  136. api: 'getCourseFilePath',
  137. content: {
  138. url: material.content,
  139. localPath: '',
  140. materialId: material.materialId,
  141. updateTime: material.updateTime,
  142. type: material.type // SONG VIDEO IMAGE
  143. }
  144. })
  145. // console.log('缓存路径返回', res)
  146. return res
  147. }
  148. // 获取当前课程是否签退
  149. const getCourseSchedule = async () => {
  150. if (!route.query.courseId) return
  151. try {
  152. const res = await request.get(
  153. `${state.platformApi}/courseSchedule/detail/${route.query.courseId}`,
  154. {
  155. hideLoading: true
  156. }
  157. )
  158. if (res?.data) {
  159. data.isCourse =
  160. res.data.status === 'ING' && state.platformType == 'TEACHER' ? true : false
  161. // data.isRecordPlay = Date.now() > dayjs(res.data.startTime).valueOf()
  162. }
  163. } catch (e) {
  164. console.log(e)
  165. }
  166. }
  167. const getTempList = async (materialList: any, name: any) => {
  168. const list: any = []
  169. const browserInfo = browser()
  170. for (let j = 0; j < materialList.length; j++) {
  171. const material = materialList[j]
  172. //请求本地缓存
  173. if (browserInfo.isApp && ['VIDEO', 'IMG'].includes(material.type)) {
  174. const localData = await getCacheFilePath(material)
  175. if (localData?.content?.localPath) {
  176. material.url = material.content
  177. material.content = localData.content.localPath
  178. }
  179. }
  180. list.push({
  181. ...material,
  182. iframeRef: null,
  183. videoEle: null,
  184. tabName: name,
  185. autoPlay: false, //加载完成是否自动播放
  186. isprepare: false, // 视频是否加载完成
  187. isRender: false // 是否渲染了
  188. })
  189. }
  190. return list
  191. }
  192. const getItemList = async () => {
  193. const list: any = []
  194. for (let i = 0; i < data.knowledgePointList.length; i++) {
  195. const item = data.knowledgePointList[i]
  196. if (item.materialList && item.materialList.length > 0) {
  197. const tempList = await getTempList(item.materialList, item.name)
  198. list.push(...tempList)
  199. }
  200. // 第二层级
  201. if (item.children && item.children.length > 0) {
  202. const childrenList = item.children || []
  203. for (let j = 0; j < childrenList.length; j++) {
  204. const childItem = childrenList[j]
  205. const tempList = await getTempList(childItem.materialList, childItem.name)
  206. list.push(...tempList)
  207. }
  208. }
  209. }
  210. // console.log(list, 'list')
  211. let _firstIndex = list.findIndex(
  212. (n: any) =>
  213. n.knowledgePointMaterialRelationId == route.query.kId || n.materialId == route.query.kId
  214. )
  215. _firstIndex = _firstIndex > -1 ? _firstIndex : 0
  216. const item = list[_firstIndex]
  217. // console.log(_firstIndex, '_firstIndex', route.query.kId, 'route.query.kId', item)
  218. // 是否自动播放
  219. if (activeData.isAutoPlay) {
  220. item.autoPlay = true
  221. }
  222. popupData.activeIndex = _firstIndex
  223. popupData.playIndex = _firstIndex
  224. popupData.tabName = item.tabName
  225. popupData.tabActive = item.knowledgePointId
  226. popupData.itemActive = item.id
  227. popupData.itemName = item.name
  228. nextTick(() => {
  229. data.itemList = list
  230. checkedAnimation(popupData.activeIndex)
  231. postMessage({
  232. api: 'courseLoading',
  233. content: {
  234. show: false,
  235. type: 'fullscreen'
  236. }
  237. })
  238. setTimeout(() => {
  239. data.animationState = 'end'
  240. }, 500)
  241. })
  242. }
  243. const getDetail = async () => {
  244. try {
  245. const res: any = await request.get(
  246. state.platformApi + `/lessonCoursewareDetail/detail/${route.query.id}`,
  247. {
  248. hideLoading: true
  249. }
  250. )
  251. data.detail = res.data
  252. if (res?.data?.lockFlag) {
  253. postMessage({
  254. api: 'courseLoading',
  255. content: {
  256. show: false,
  257. type: 'fullscreen'
  258. }
  259. })
  260. showDialog({
  261. title: '温馨提示',
  262. message: '课件已锁定'
  263. }).then(() => {
  264. goback()
  265. })
  266. return
  267. }
  268. if (Array.isArray(res?.data?.knowledgePointList)) {
  269. let index = 0
  270. data.knowledgePointList = res.data.knowledgePointList.map((n: any) => {
  271. if (Array.isArray(n.materialList)) {
  272. n.materialList = n.materialList.map((item: any) => {
  273. index++
  274. return {
  275. ...item,
  276. knowledgePointId: [item.knowledgePointId],
  277. materialId: item.id,
  278. id: index + ''
  279. }
  280. })
  281. }
  282. if (Array.isArray(n.children)) {
  283. n.children = n.children.map((cn: any) => {
  284. cn.materialList = cn.materialList.map((item: any) => {
  285. index++
  286. return {
  287. ...item,
  288. knowledgePointId: [n.id, item.knowledgePointId],
  289. materialId: item.id,
  290. id: index + ''
  291. }
  292. })
  293. return cn
  294. })
  295. }
  296. return n
  297. })
  298. getItemList()
  299. }
  300. } catch (error) {
  301. console.log(error)
  302. }
  303. }
  304. // ifram事件处理
  305. const iframeHandle = (ev: MessageEvent) => {
  306. if (ev.data?.api === 'headerTogge') {
  307. activeData.model = ev.data.show || (ev.data.playState == 'play' ? false : true)
  308. }
  309. }
  310. onMounted(() => {
  311. const hasVip = handleCheckVip()
  312. if (!hasVip) {
  313. nextTick(() => {
  314. postMessage({
  315. api: 'courseLoading',
  316. content: {
  317. show: false,
  318. type: 'fullscreen'
  319. }
  320. })
  321. })
  322. return
  323. }
  324. getDetail()
  325. getCourseSchedule()
  326. window.addEventListener('message', iframeHandle)
  327. })
  328. const playRef = ref()
  329. // 返回
  330. const goback = () => {
  331. try {
  332. playRef.value?.handleOut()
  333. } catch (error) {
  334. console.log(error)
  335. }
  336. postMessage({ api: 'goBack' })
  337. }
  338. const popupData = reactive({
  339. open: false,
  340. activeIndex: 0,
  341. playIndex: 0,
  342. tabActive: '',
  343. tabName: '',
  344. itemActive: '',
  345. itemName: '',
  346. guideOpen: false,
  347. toolOpen: false // 工具弹窗控制
  348. })
  349. const stopVideo = (el: HTMLVideoElement) => {
  350. return new Promise((resolve) => {
  351. if (el.paused) return resolve(true)
  352. el.onpause = () => {
  353. console.log('暂停')
  354. resolve(true)
  355. }
  356. el.pause()
  357. })
  358. }
  359. /**停止所有的播放 */
  360. const handleStop = async () => {
  361. const videos = document.querySelectorAll('video')
  362. for (let i = 0; i < videos.length; i++) {
  363. const videoEle = videos[i] as HTMLVideoElement
  364. await stopVideo(videoEle)
  365. }
  366. console.log('视频暂停完成')
  367. data.itemList.forEach((item: any) => {
  368. if (item.type === 'SONG') {
  369. item.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  370. }
  371. })
  372. }
  373. // 切换素材
  374. const toggleMaterial = (itemActive: any) => {
  375. const index = data.itemList.findIndex((n: any) => n.id == itemActive)
  376. if (index > -1) {
  377. handleSwipeChange(index)
  378. }
  379. }
  380. /** 延迟收起模态框 */
  381. const setModelOpen = () => {
  382. clearTimeout(activeData.timer)
  383. closeToast()
  384. activeData.timer = setTimeout(() => {
  385. activeData.model = false
  386. }, 4000)
  387. }
  388. /** 立即收起所有的模态框 */
  389. const clearModel = () => {
  390. clearTimeout(activeData.timer)
  391. closeToast()
  392. activeData.model = false
  393. }
  394. const toggleModel = (type = true) => {
  395. activeData.model = type
  396. }
  397. // 去点名,签退
  398. const gotoRollCall = (pageTag: string) => {
  399. postMessage({
  400. api: 'open_app_page',
  401. content: {
  402. action: 'app',
  403. pageTag: pageTag,
  404. url: '',
  405. params: JSON.stringify({ courseId: route.query.courseId })
  406. }
  407. })
  408. }
  409. // 双击
  410. const handleDbClick = () => {
  411. if (activeVideoItem.value.type === 'VIDEO') {
  412. handleStop()
  413. }
  414. }
  415. const effectIndex = ref(0)
  416. const effects = [
  417. {
  418. prev: {
  419. transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
  420. },
  421. next: {
  422. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
  423. }
  424. },
  425. {
  426. prev: {
  427. transform: 'translate3d(-100%, 0, -800px)'
  428. },
  429. next: {
  430. transform: 'translate3d(100%, 0, -800px)'
  431. }
  432. },
  433. {
  434. prev: {
  435. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
  436. },
  437. next: {
  438. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
  439. }
  440. },
  441. {
  442. prev: {
  443. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
  444. },
  445. next: {
  446. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
  447. }
  448. },
  449. // 风车4
  450. {
  451. prev: {
  452. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  453. opacity: 0
  454. },
  455. next: {
  456. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  457. opacity: 0
  458. }
  459. },
  460. // 翻页5
  461. {
  462. prev: {
  463. transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  464. opacity: 0
  465. },
  466. next: {
  467. transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  468. opacity: 0
  469. },
  470. current: { transitionDelay: '700ms' }
  471. }
  472. ]
  473. const acitveTimer = ref()
  474. // 轮播切换
  475. const handleSwipeChange = async (index: number) => {
  476. // 如果是当前正在播放 或者是视频最后一个
  477. if (popupData.activeIndex == index) return
  478. await handleStop()
  479. data.animationState = 'start'
  480. data.videoState = 'init'
  481. clearTimeout(acitveTimer.value)
  482. checkedAnimation(popupData.activeIndex, index)
  483. nextTick(() => {
  484. popupData.activeIndex = index
  485. acitveTimer.value = setTimeout(
  486. () => {
  487. popupData.playIndex = index
  488. const item = data.itemList[index]
  489. if (item) {
  490. popupData.tabActive = item.knowledgePointId
  491. popupData.itemActive = item.id
  492. popupData.itemName = item.name
  493. popupData.tabName = item.tabName
  494. if (item.type == 'SONG') {
  495. activeData.model = true
  496. }
  497. }
  498. requestAnimationFrame(() => {
  499. const _effectIndex = effectIndex.value + 1
  500. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  501. if (item && item.type === 'VIDEO') {
  502. // 自动播放下一个视频
  503. clearTimeout(activeData.timer)
  504. closeToast()
  505. item.autoPlay = true
  506. data.animationState = 'end'
  507. }
  508. })
  509. },
  510. activeData.isAnimation ? 850 : 0
  511. )
  512. })
  513. }
  514. /** 是否有转场动画 */
  515. const checkedAnimation = (index: number, nextIndex?: number) => {
  516. nextIndex = nextIndex ? nextIndex : index + 1;
  517. const item = data.itemList[index]
  518. const nextItem = data.itemList[nextIndex]
  519. if (nextItem) {
  520. if (nextItem.knowledgePointId != item.knowledgePointId) {
  521. activeData.isAnimation = true
  522. return
  523. }
  524. const videoEle = item.videoEle
  525. const nextVideo = nextItem.videoEle
  526. if (videoEle && videoEle.duration < 8 && index < nextIndex) {
  527. activeData.isAnimation = false
  528. } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex) {
  529. activeData.isAnimation = false
  530. } else {
  531. activeData.isAnimation = true
  532. }
  533. } else {
  534. activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true
  535. }
  536. }
  537. // 上一个知识点, 下一个知识点
  538. const handlePreAndNext = (type: string) => {
  539. if (type === 'up') {
  540. handleSwipeChange(popupData.activeIndex - 1)
  541. } else {
  542. handleSwipeChange(popupData.activeIndex + 1)
  543. }
  544. }
  545. /** 弹窗关闭 */
  546. const handleClosePopup = () => {
  547. const item = data.itemList[popupData.activeIndex]
  548. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  549. setModelOpen()
  550. }
  551. }
  552. /** 教学数据 */
  553. const studyData = reactive({
  554. type: '' as ToolType,
  555. penShow: false
  556. })
  557. /** 打开教学工具 */
  558. const openStudyTool = (item: ToolItem) => {
  559. const activeItem = data.itemList[popupData.activeIndex]
  560. // 暂停视频和曲谱的播放
  561. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  562. activeItem.videoEle.pause()
  563. }
  564. if (activeItem.type === 'SONG') {
  565. activeItem.iframeRef?.contentWindow?.postMessage({ api: 'setPlayState' }, '*')
  566. }
  567. clearModel()
  568. popupData.toolOpen = false
  569. studyData.type = item.type
  570. switch (item.type) {
  571. case 'pen':
  572. studyData.penShow = true
  573. break
  574. }
  575. }
  576. /** 关闭教学工具 */
  577. const closeStudyTool = () => {
  578. studyData.type = 'init'
  579. toggleModel()
  580. }
  581. const activeVideoItem = computed(() => {
  582. const item = data.itemList[popupData.activeIndex]
  583. if (item && item.type && item.type.toLocaleUpperCase() === 'VIDEO') {
  584. return item
  585. }
  586. return {}
  587. })
  588. return () => (
  589. <div id="playContent" class={styles.playContent}>
  590. <div
  591. class={styles.coursewarePlay}
  592. style={{ width: parentContainer.width }}
  593. onClick={() => {
  594. clearTimeout(activeData.timer)
  595. if (Date.now() - activeData.nowTime < 300) {
  596. activeData.model = false
  597. handleDbClick()
  598. return
  599. }
  600. activeData.nowTime = Date.now()
  601. activeData.model = !activeData.model
  602. }}
  603. >
  604. <div class={styles.wraps}>
  605. <div
  606. style={{
  607. zIndex: 15,
  608. opacity:
  609. activeVideoItem.value.type &&
  610. data.animationState === 'end' &&
  611. data.videoState === 'play'
  612. ? 1
  613. : 0
  614. }}
  615. class={styles.itemDiv}
  616. >
  617. <VideoItem
  618. item={activeVideoItem.value}
  619. activeModel={activeData.model}
  620. onClose={setModelOpen}
  621. onPlay={() => {
  622. data.videoState = 'play'
  623. }}
  624. onPause={() => {
  625. clearTimeout(activeData.timer)
  626. activeData.model = true
  627. }}
  628. onEnded={() => {
  629. const _index = popupData.activeIndex + 1
  630. if (_index < data.itemList.length) {
  631. setTimeout(() => {
  632. handleSwipeChange(_index)
  633. }, 500)
  634. }
  635. }}
  636. />
  637. </div>
  638. {data.itemList.map((m: any, mIndex: number) => {
  639. const isRenderItem = Math.abs(popupData.activeIndex - mIndex) < 2
  640. const isRender = Math.abs(popupData.playIndex - mIndex) < 2
  641. // 判断是否是当前选中的元素
  642. const activeEle = popupData.playIndex === mIndex ? true : false
  643. return isRenderItem ? (
  644. <div
  645. key={'index' + mIndex}
  646. data-id={'data' + mIndex}
  647. class={[
  648. styles.itemDiv,
  649. activeEle && styles.itemActive,
  650. activeData.isAnimation && styles.acitveAnimation,
  651. isRenderItem ? styles.show : styles.hide
  652. ]}
  653. style={
  654. mIndex < popupData.activeIndex
  655. ? effects[effectIndex.value].prev
  656. : mIndex > popupData.activeIndex
  657. ? effects[effectIndex.value].next
  658. : {}
  659. }
  660. >
  661. {/* {m.type === 'VIDEO' && (
  662. <>
  663. <VideoPlay
  664. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  665. item={m}
  666. isActive={activeEle}
  667. isEmtry={isEmtry}
  668. onPrepare={(val) => {
  669. m.isprepare = val
  670. }}
  671. onLoadedmetadata={(videoItem: any) => {
  672. m.videoEle = videoItem
  673. }}
  674. onTogglePlay={(paused: boolean) => {
  675. // console.log('播放切换', paused)
  676. if (!m.isprepare) {
  677. m.isprepare = true
  678. }
  679. m.autoPlay = false
  680. if (paused || popupData.open || popupData.guideOpen) {
  681. clearTimeout(activeData.timer)
  682. } else {
  683. setModelOpen()
  684. }
  685. }}
  686. onEnded={() => {
  687. const _index = popupData.activeIndex + 1
  688. if (_index < data.itemList.length) {
  689. handleSwipeChange(_index)
  690. }
  691. }}
  692. onReset={() => {
  693. if (!m.videoEle?.paused) {
  694. setModelOpen()
  695. }
  696. }}
  697. />
  698. <Transition name="van-fade">
  699. {!m.isprepare && (
  700. <div class={styles.loadWrap}>
  701. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  702. </div>
  703. )}
  704. </Transition>
  705. </>
  706. )} */}
  707. <Transition name="van-fade">
  708. {m.type === 'VIDEO' &&
  709. data.animationState !== 'end' &&
  710. data.videoState != 'play' && (
  711. <div class={styles.loadWrap}>
  712. <Vue3Lottie animationData={playLoadData}></Vue3Lottie>
  713. </div>
  714. )}
  715. </Transition>
  716. {isRender && m.type === 'IMG' && <img src={m.content} />}
  717. {isRender && m.type === 'SONG' && (
  718. <MusicScore
  719. activeModel={activeData.model}
  720. data-vid={m.id}
  721. music={m}
  722. onSetIframe={(el: any) => {
  723. m.iframeRef = el
  724. }}
  725. />
  726. )}
  727. </div>
  728. ) : (
  729. ''
  730. )
  731. })}
  732. </div>
  733. <Transition name="right">
  734. {activeData.model && (
  735. <div
  736. class={styles.rightFixedBtns}
  737. onClick={(e: Event) => {
  738. e.stopPropagation()
  739. clearTimeout(activeData.timer)
  740. }}
  741. >
  742. <div class={styles.btnsWrap}>
  743. <div
  744. class={[styles.fullBtn, styles.point]}
  745. onClick={() => (popupData.open = true)}
  746. >
  747. <img src={iconMenu} />
  748. <span>知识点</span>
  749. </div>
  750. </div>
  751. <div class={[styles.btnsWrap, styles.btnsBottom]}>
  752. {/* <div class={styles.fullBtn} onClick={() => (popupData.guideOpen = true)}>
  753. <img src={iconTouping} />
  754. <span>投屏</span>
  755. </div> */}
  756. {data.isCourse && (
  757. <>
  758. <div class={styles.fullBtn} onClick={() => gotoRollCall('student_roll_call')}>
  759. <img src={iconDian} />
  760. <span>点名</span>
  761. </div>
  762. <div class={styles.fullBtn} onClick={() => gotoRollCall('sign_out')}>
  763. <img src={iconPoint} />
  764. <span>签退</span>
  765. </div>
  766. </>
  767. )}
  768. </div>
  769. </div>
  770. )}
  771. </Transition>
  772. <Transition name="left">
  773. {activeData.model && (
  774. <div class={styles.leftFixedBtns} onClick={(e: Event) => e.stopPropagation()}>
  775. {popupData.activeIndex != 0 && (
  776. <div class={[styles.btnsWrap, styles.prePoint]}>
  777. <div
  778. class={styles.fullBtn}
  779. onClick={() => {
  780. // useThrottleFn(() => {
  781. // handlePreAndNext('up')
  782. // }, 300)
  783. // onChangeSwiper('up')
  784. handlePreAndNext('up')
  785. }}
  786. >
  787. <img src={iconUp} />
  788. <span style={{ textAlign: 'center' }}>上一个</span>
  789. </div>
  790. </div>
  791. )}
  792. {popupData.activeIndex != data.itemList.length - 1 && (
  793. <div class={styles.btnsWrap}>
  794. <div
  795. class={styles.fullBtn}
  796. onClick={() => {
  797. // console.log('click down')
  798. // useThrottleFn(() => {
  799. // console.log('click down pass')
  800. // handlePreAndNext('down')
  801. // }, 300)
  802. // onChangeSwiper('down')
  803. handlePreAndNext('down')
  804. }}
  805. >
  806. <span style={{ textAlign: 'center' }}>下一个</span>
  807. <img src={iconDown} />
  808. </div>
  809. </div>
  810. )}
  811. </div>
  812. )}
  813. </Transition>
  814. </div>
  815. <div
  816. style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
  817. id="coursePlayHeader"
  818. class={styles.headerContainer}
  819. ref={headeRef}
  820. >
  821. <div class={styles.backBtn} onClick={() => goback()}>
  822. <Icon name={iconBack} />
  823. 返回
  824. </div>
  825. {data.isCourse && <PlayRecordTime ref={playRef} list={data.knowledgePointList} />}
  826. <div
  827. class={styles.menu}
  828. onClick={() => {
  829. const _effectIndex = effectIndex.value + 1
  830. effectIndex.value = _effectIndex >= effects.length - 1 ? 0 : _effectIndex
  831. setModelOpen()
  832. }}
  833. >
  834. {popupData.tabName}
  835. </div>
  836. {state.platformType == 'TEACHER' && (
  837. <div
  838. class={styles.headRight}
  839. onClick={(e: Event) => {
  840. e.stopPropagation()
  841. clearTimeout(activeData.timer)
  842. }}
  843. >
  844. <div class={styles.rightBtn} onClick={() => (popupData.guideOpen = true)}>
  845. <img src={iconTouping} />
  846. </div>
  847. <div
  848. class={styles.rightBtn}
  849. onClick={() => {
  850. openStudyTool({
  851. type: 'pen',
  852. icon: iconPen,
  853. name: '批注'
  854. })
  855. }}
  856. >
  857. <img src={iconPen} />
  858. </div>
  859. {/* <div class={styles.rightBtn} onClick={() => (popupData.toolOpen = true)}>
  860. <img src={iconMore} />
  861. </div> */}
  862. </div>
  863. )}
  864. </div>
  865. {/* 更多弹窗 */}
  866. <Popup
  867. class={styles.popupMore}
  868. overlayClass={styles.overlayClass}
  869. position="right"
  870. round
  871. v-model:show={popupData.toolOpen}
  872. onClose={handleClosePopup}
  873. >
  874. <Tool onHandleTool={openStudyTool} />
  875. </Popup>
  876. <Popup
  877. class={styles.popup}
  878. style={{ background: 'rgba(0,0,0, 0.75)' }}
  879. overlayClass={styles.overlayClass}
  880. position="right"
  881. round
  882. v-model:show={popupData.open}
  883. onClose={handleClosePopup}
  884. >
  885. <Points
  886. data={data.knowledgePointList}
  887. tabActive={popupData.tabActive}
  888. itemActive={popupData.itemActive}
  889. onHandleSelect={(res: any) => {
  890. // onChangeSwiper('change', res.itemActive)
  891. popupData.open = false
  892. toggleMaterial(res.itemActive)
  893. }}
  894. />
  895. </Popup>
  896. <Popup
  897. class={styles.popup}
  898. overlayClass={styles.overlayClass}
  899. position="right"
  900. round
  901. v-model:show={popupData.guideOpen}
  902. onClose={handleClosePopup}
  903. >
  904. <OGuide />
  905. </Popup>
  906. {studyData.penShow && (
  907. <Pen show={studyData.type === 'pen'} close={() => closeStudyTool()} />
  908. )}
  909. </div>
  910. )
  911. }
  912. })