index.tsx 31 KB

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