index.tsx 32 KB

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