index.tsx 58 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837
  1. import {
  2. defineComponent,
  3. onMounted,
  4. reactive,
  5. onUnmounted,
  6. ref,
  7. Transition,
  8. computed,
  9. nextTick,
  10. watch
  11. } from 'vue';
  12. import styles from './index.module.less';
  13. import 'plyr/dist/plyr.css';
  14. import MusicScore from './component/musicScore';
  15. // import iconChange from './image/icon-change.png';
  16. // import iconMenu from './image/icon-menu.png';
  17. // import iconUp from './image/icon-up.png';
  18. // import iconDown from './image/icon-down.png';
  19. // import iconNote from './image/icon-note.png';
  20. // import iconWhiteboard from './image/icon-whiteboard.png';
  21. // import iconAssignHomework from './image/icon-assignHomework.png';
  22. // import iconClose from './image/icon-close.png';
  23. // import iconOverPreivew from './image/icon-over-preview.png';
  24. import { Vue3Lottie } from 'vue3-lottie';
  25. import playLoadData from './datas/data.json';
  26. // import Moveable from 'moveable';
  27. import VideoPlay from './component/video-play';
  28. import {
  29. useMessage,
  30. NDrawer,
  31. NDrawerContent,
  32. NModal,
  33. NSpace,
  34. NButton
  35. // NTooltip,
  36. // NPopover,
  37. // NImage
  38. } from 'naive-ui';
  39. import CardType from '@/components/card-type';
  40. import Pen from './component/tools/pen';
  41. import AudioPay from './component/audio-pay';
  42. import TrainSettings from './model/train-settings';
  43. import { useRoute } from 'vue-router';
  44. import {
  45. courseScheduleUpdate,
  46. lessonCoursewareDetail,
  47. lessonPreTrainingPage,
  48. queryCourseware
  49. } from '../prepare-lessons/api';
  50. // import Attentguide from '@/custom-plugins/guide-page/attent-guide';
  51. import { vaildUrl } from '/src/utils/urlUtils';
  52. import TimerMeter from '/src/components/timerMeter';
  53. // import toneImage from '/src/components/layout/images/toneImage.png';
  54. // import toolbox from '/src/components/layout/images/toolbox.png';
  55. // import setTimeIcon from '/src/components/layout/images/setTimeIcon.png';
  56. // import beatIcon from '/src/components/layout/images/beatIcon.png';
  57. // import toneIcon from '/src/components/layout/images/toneIcon.png';
  58. import { px2vw } from '/src/utils';
  59. import PlaceholderTone from '/src/components/layout/modals/placeholderTone';
  60. import { state as globalState } from '/src/state';
  61. import Chapter from './model/chapter';
  62. import { useRouter } from 'vue-router';
  63. import { useUserStore } from '@/store/modules/users';
  64. // import iconBeatIcon from './new-image/icon-beatIcon.png';
  65. // import iconChange from './new-image/icon-change.png';
  66. // import iconDown from './new-image/icon-down.png';
  67. // import iconMenu from './new-image/icon-menu.png';
  68. import iconNote from './new-image/icon-note.png';
  69. // import iconOverClass from './new-image/icon-overclass.png';
  70. // import iconSetTime from './new-image/icon-setTime.png';
  71. // import iconToneIcon from './new-image/icon-toneIcon.png';
  72. // import iconUp from './new-image/icon-up.png';
  73. import iconWhite from './new-image/icon-white.png';
  74. // import iconWork from './new-image/icon-work.png';
  75. import rightIconEnd from './image/right_icon1.png';
  76. import rightIconArrange from './image/right_icon2.png';
  77. import rightIconPostil from './image/right_icon3.png';
  78. import rightIconWhiteboard from './image/right_icon4.png';
  79. import rightIconMetronome from './image/right_icon5.png';
  80. import rightIconTuner from './image/right_icon6.png';
  81. import rightIconTimer from './image/right_icon7.png';
  82. import rightIconPackUp from './image/right_icon8.png';
  83. import rightIconMusic from './image/right_icon9.png';
  84. import bottomIconSwitch from './image/bottom_icon1.png';
  85. import bottomIconResource from './image/bottom_icon2.png';
  86. import bottomIconPre from './image/bottom_icon3.png';
  87. import bottomIconNext from './image/bottom_icon4.png';
  88. import rightHideIcon from './image/right_hide_icon.png';
  89. import SelectResources from '../prepare-lessons/model/select-resources';
  90. import TheNoticeBar from '/src/components/TheNoticeBar';
  91. import { getStudentAfterWork } from '../studentList/api';
  92. export type ToolType = 'init' | 'pen' | 'whiteboard';
  93. export type ToolItem = {
  94. type: ToolType;
  95. name: string;
  96. icon: string;
  97. };
  98. export default defineComponent({
  99. name: 'CoursewarePlay',
  100. props: {
  101. type: {
  102. type: String,
  103. default: ''
  104. },
  105. subjectId: {
  106. type: [String, Number],
  107. default: ''
  108. },
  109. // 教材编号
  110. lessonCourseId: {
  111. type: [String, Number],
  112. default: ''
  113. },
  114. detailId: {
  115. type: String,
  116. default: ''
  117. },
  118. // 班级编号
  119. classGroupId: {
  120. type: String,
  121. default: ''
  122. },
  123. // 上课记录编号
  124. classId: {
  125. type: String,
  126. defaault: ''
  127. },
  128. preStudentNum: {
  129. type: [String, Number],
  130. default: ''
  131. }
  132. },
  133. emits: ['close'],
  134. setup(props, { emit }) {
  135. const message = useMessage();
  136. const route = useRoute();
  137. const router = useRouter();
  138. const users = useUserStore();
  139. /** 设置播放容器 16:9 */
  140. const parentContainer = reactive({
  141. width: '100vw'
  142. });
  143. // const NPopoverRef = ref();
  144. // const setContainer = () => {
  145. // const min = Math.min(screen.width, screen.height);
  146. // const max = Math.max(screen.width, screen.height);
  147. // const width = min * (16 / 9);
  148. // if (width > max) {
  149. // parentContainer.width = '100vw';
  150. // return;
  151. // } else {
  152. // parentContainer.width = width + 'px';
  153. // }
  154. // };
  155. const handleInit = (type = 0) => {
  156. //设置容器16:9
  157. // setContainer();
  158. };
  159. handleInit();
  160. onUnmounted(() => {
  161. handleInit(1);
  162. });
  163. const drawerCardRef = ref(); // 资源列表对象
  164. const data = reactive({
  165. type: 'class' as '' | 'preview' | 'class', // 预览类型
  166. subjectId: '' as any, // 声部编号
  167. lessonCourseId: '' as any, // 教材编号
  168. lessonCoursewareDetailId: '' as any, // 章节
  169. detailId: '' as any, // 编号 - 课程编号
  170. classGroupId: '' as any, // 上课时需要 班级编号
  171. classId: '' as any, // 上课编号
  172. preStudentNum: '' as any, // 班上学生
  173. // detail: null,
  174. knowledgePointList: [] as any,
  175. itemList: [] as any,
  176. // showHead: true,
  177. // isCourse: false,
  178. // isRecordPlay: false,
  179. videoRefs: {} as any[],
  180. audioRefs: {} as any[],
  181. modelAttendStatus: false, // 布置作业提示弹窗
  182. modalAttendMessage: '本节课未设置课后作业,是否继续?',
  183. modelTrainStatus: false, // 训练设置
  184. homeworkStatus: true, // 布置作业完成时
  185. removeVisiable: false,
  186. removeTitle: '',
  187. removeContent: '',
  188. removeCourseStatus: false, // 是否布置作业
  189. selectResourceStatus: false,
  190. videoState: 'init' as 'init' | 'play',
  191. videoItemRef: null as any,
  192. animationState: 'start' as 'start' | 'end'
  193. });
  194. const activeData = reactive({
  195. // isAutoPlay: false, // 是否自动播放
  196. nowTime: 0,
  197. model: true, // 遮罩
  198. isAnimation: true, // 是否动画
  199. // videoBtns: true, // 视频
  200. // currentTime: 0,
  201. // duration: 0,
  202. timer: null as any,
  203. item: null as any
  204. });
  205. // 键盘事件监听状态
  206. const listenerKeyUpState = ref(false);
  207. const getDetail = async () => {
  208. try {
  209. const res = await queryCourseware({
  210. coursewareDetailKnowledgeId: data.detailId,
  211. subjectId: data.subjectId,
  212. pag: 1,
  213. rows: 99
  214. });
  215. const tempRows = res.data.rows || [];
  216. const temp: any = [];
  217. tempRows.forEach((row: any) => {
  218. if (!row.removeFlag) {
  219. temp.push({
  220. id: row.id,
  221. materialId: row.materialId,
  222. coverImg: row.coverImg,
  223. type: row.materialType,
  224. title: row.materialName,
  225. isCollect: !!row.favoriteFlag,
  226. isSelected: row.source === 'PLATFORM' ? true : false,
  227. content: row.content
  228. });
  229. }
  230. });
  231. data.knowledgePointList = temp;
  232. data.itemList = data.knowledgePointList.map((m: any) => {
  233. return {
  234. ...m,
  235. iframeRef: null,
  236. videoEle: null,
  237. audioEle: null,
  238. autoPlay: false, //加载完成是否自动播放
  239. isprepare: false, // 视频是否加载完成
  240. isRender: false // 是否渲染了
  241. };
  242. });
  243. setTimeout(() => {
  244. data.animationState = 'end';
  245. }, 500);
  246. } catch {
  247. //
  248. }
  249. };
  250. const showModalBeat = ref(false);
  251. const showModalTone = ref(false);
  252. const showModalTime = ref(false);
  253. // ifram事件处理
  254. const iframeHandle = (ev: MessageEvent) => {
  255. // console.log(ev.data?.api, ev.data, 'ev.data');
  256. if (ev.data?.api === 'headerTogge') {
  257. activeData.model =
  258. ev.data.show || (ev.data.playState == 'play' ? false : true);
  259. }
  260. //
  261. if (ev.data?.api === 'onAttendToggleMenu') {
  262. activeData.model = !activeData.model;
  263. }
  264. if (ev.data?.api === 'api_fingerPreView') {
  265. clearInterval(activeData.timer);
  266. activeData.model = !ev.data.state;
  267. }
  268. //
  269. if (ev.data?.api === 'documentBodyKeyup') {
  270. if (ev.data?.code === 'ArrowLeft') {
  271. setModalOpen();
  272. handlePreAndNext('up');
  273. }
  274. if (ev.data?.code === 'ArrowRight') {
  275. setModalOpen();
  276. handlePreAndNext('down');
  277. }
  278. }
  279. if (ev.data?.api === 'onLogin') {
  280. const documentDom: any = document;
  281. documentDom.exitFullscreen
  282. ? documentDom.exitFullscreen()
  283. : documentDom.mozCancelFullScreen
  284. ? documentDom.mozCancelFullScreen()
  285. : documentDom.webkitExitFullscreen
  286. ? documentDom.webkitExitFullscreen()
  287. : '';
  288. users.logout();
  289. router.replace('/login');
  290. }
  291. };
  292. onMounted(() => {
  293. // initMoveable();
  294. const query = route.query;
  295. // console.log(query, props.preStudentNum, '学生人数');
  296. // 先取参数,
  297. data.type = props.type || (query.type as any);
  298. data.subjectId = props.subjectId || query.subjectId;
  299. data.detailId = props.detailId || query.detailId;
  300. data.lessonCourseId = props.lessonCourseId || query.lessonCourseId;
  301. data.classGroupId = props.classGroupId || query.classGroupId;
  302. data.classId = props.classId || query.classId;
  303. data.preStudentNum = props.preStudentNum || query.preStudentNum;
  304. window.addEventListener('message', iframeHandle);
  305. getDetail();
  306. getLessonCoursewareDetail();
  307. if (data.type === 'preview') {
  308. rightList.splice(1, 1);
  309. }
  310. });
  311. // const onFullScreen = () => {
  312. // if (data.type === 'preview') {
  313. // const el: any = document.querySelector('#app');
  314. // if (el.mozRequestFullScreen) {
  315. // el.mozRequestFullScreen();
  316. // } else if (el.webkitRequestFullscreen) {
  317. // el.webkitRequestFullscreen();
  318. // } else if (el.requestFullScreen) {
  319. // el.requestFullscreen();
  320. // }
  321. // }
  322. // };
  323. const popupData = reactive({
  324. open: false,
  325. activeIndex: 0,
  326. toolOpen: false, // 工具弹窗控制
  327. chapterOpen: false, // 切换章节
  328. chapterDetails: [] as any,
  329. chapterLoading: false // 加载数据
  330. });
  331. const formatParentId = (id: any, list: any, ids = [] as any) => {
  332. for (const item of list) {
  333. if (item.knowledgeList && item.knowledgeList.length > 0) {
  334. const cIds: any = formatParentId(id, item.knowledgeList, [
  335. ...ids,
  336. item.id
  337. ]);
  338. if (cIds.includes(id)) {
  339. return cIds;
  340. }
  341. }
  342. if (item.id === id) {
  343. return [...ids, id];
  344. }
  345. }
  346. return ids;
  347. };
  348. /** 获取章节 */
  349. const getLessonCoursewareDetail = async () => {
  350. try {
  351. const res = await lessonCoursewareDetail({
  352. id: data.lessonCourseId,
  353. subjectId: data.subjectId
  354. });
  355. popupData.chapterDetails = res.data.lessonList || [];
  356. const ids = formatParentId(data.detailId, popupData.chapterDetails);
  357. data.lessonCoursewareDetailId = ids[0];
  358. } catch {
  359. //
  360. }
  361. };
  362. /** 更新上课记录 */
  363. const classCourseScheduleUpdate = async () => {
  364. try {
  365. if (!data.classId) return;
  366. await courseScheduleUpdate({
  367. lessonCoursewareKnowledgeDetailId: data.detailId,
  368. id: data.classId
  369. });
  370. } catch {
  371. //
  372. }
  373. };
  374. const activeName = computed(() => {
  375. let name = '';
  376. popupData.chapterDetails.forEach((chapter: any) => {
  377. if (chapter.id === data.lessonCoursewareDetailId) {
  378. // name = chapter.name;
  379. chapter.knowledgeList?.forEach((know: any) => {
  380. if (know.id === data.detailId) {
  381. name = know.name;
  382. }
  383. });
  384. }
  385. });
  386. return name;
  387. });
  388. /**停止所有的播放 */
  389. const handleStop = () => {
  390. for (let i = 0; i < data.itemList.length; i++) {
  391. const activeItem = data.itemList[i];
  392. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  393. // console.log(activeItem.videoEle, ' activeItem.videoEle');
  394. try {
  395. activeItem.videoEle?.currentTime(0);
  396. activeItem.videoEle?.pause();
  397. } catch (e: any) {
  398. // console.log(e, 'e');
  399. }
  400. }
  401. if (activeItem.type === 'SONG' && activeItem.audioEle) {
  402. activeItem.audioEle?.stop();
  403. }
  404. // console.log('🚀 ~ activeItem:', activeItem)
  405. // 停止曲谱的播放
  406. if (activeItem.type === 'MUSIC') {
  407. activeItem.iframeRef?.contentWindow?.postMessage(
  408. { api: 'setPlayState' },
  409. '*'
  410. );
  411. }
  412. }
  413. };
  414. // 切换素材
  415. const toggleMaterial = (itemActive: any) => {
  416. const index = data.itemList.findIndex((n: any) => n.id == itemActive);
  417. if (index > -1) {
  418. handleSwipeChange(index);
  419. }
  420. };
  421. /** 延迟收起模态框 */
  422. const setModelOpen = () => {
  423. clearTimeout(activeData.timer);
  424. message.destroyAll();
  425. activeData.timer = setTimeout(() => {
  426. activeData.model = false;
  427. Object.values(data.videoRefs).map((n: any) =>
  428. n?.toggleHideControl(false)
  429. );
  430. Object.values(data.audioRefs).map((n: any) =>
  431. n?.toggleHideControl(false)
  432. );
  433. }, 4000);
  434. };
  435. /** 立即收起所有的模态框 */
  436. const clearModel = () => {
  437. clearTimeout(activeData.timer);
  438. message.destroyAll();
  439. activeData.model = false;
  440. Object.values(data.videoRefs).map((n: any) =>
  441. n?.toggleHideControl(false)
  442. );
  443. Object.values(data.audioRefs).map((n: any) =>
  444. n?.toggleHideControl(false)
  445. );
  446. };
  447. const toggleModel = (type = true) => {
  448. activeData.model = type;
  449. Object.values(data.videoRefs).map((n: any) => n?.toggleHideControl(type));
  450. Object.values(data.audioRefs).map((n: any) => n?.toggleHideControl(type));
  451. };
  452. // 双击
  453. const handleDbClick = (item: any) => {
  454. if (item && item.type === 'VIDEO') {
  455. const videoEle: HTMLVideoElement = item.videoEle;
  456. if (videoEle) {
  457. if (videoEle.paused) {
  458. message.destroyAll();
  459. videoEle.play();
  460. } else {
  461. message.warning('已暂停');
  462. videoEle.pause();
  463. }
  464. }
  465. }
  466. };
  467. // ppt iframe 点击事件
  468. // const iframeClick = () => {
  469. // if(document.all) {
  470. // document.getElementById("iframe-ppt")?.attachEvent("click", () => {
  471. // activeData.model = !activeData.model
  472. // });
  473. // } else {
  474. // document.getElementById("iframe-ppt")?.contentWindow?.postMessage({
  475. // api: 'onAttendToggleMenu'
  476. // }, '*');
  477. // }
  478. // }
  479. // 切换播放
  480. // const togglePlay = (m: any, isPlay: boolean) => {
  481. // if (isPlay) {
  482. // m.videoEle?.play();
  483. // } else {
  484. // m.videoEle?.pause();
  485. // }
  486. // };
  487. // const showIndex = ref(-4);
  488. const effectIndex = ref(3);
  489. const effects = [
  490. {
  491. prev: {
  492. transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
  493. },
  494. next: {
  495. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
  496. }
  497. },
  498. {
  499. prev: {
  500. transform: 'translate3d(-100%, 0, -800px)'
  501. },
  502. next: {
  503. transform: 'translate3d(100%, 0, -800px)'
  504. }
  505. },
  506. {
  507. prev: {
  508. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
  509. },
  510. next: {
  511. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
  512. }
  513. },
  514. {
  515. prev: {
  516. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
  517. },
  518. next: {
  519. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
  520. }
  521. },
  522. // 风车4
  523. {
  524. prev: {
  525. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  526. opacity: 0
  527. },
  528. next: {
  529. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  530. opacity: 0
  531. }
  532. },
  533. // 翻页5
  534. {
  535. prev: {
  536. transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  537. opacity: 0
  538. },
  539. next: {
  540. transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  541. opacity: 0
  542. },
  543. current: { transitionDelay: '700ms' }
  544. }
  545. ];
  546. const acitveTimer = ref();
  547. // 轮播切换
  548. const handleSwipeChange = (index: number) => {
  549. // 如果是当前正在播放 或者是视频最后一个
  550. if (popupData.activeIndex == index) return;
  551. data.animationState = 'start';
  552. data.videoState = 'init';
  553. handleStop();
  554. clearTimeout(acitveTimer.value);
  555. activeData.model = true;
  556. checkedAnimation(popupData.activeIndex, index);
  557. popupData.activeIndex = index;
  558. // 处理资源列表滚动
  559. nextTick(() => {
  560. scrollResourceSection();
  561. });
  562. acitveTimer.value = setTimeout(
  563. () => {
  564. const item = data.itemList[index];
  565. if (item) {
  566. if (item.type == 'MUSIC') {
  567. activeData.model = true;
  568. }
  569. if (item.type === 'SONG') {
  570. // 自动播放下一个音频
  571. clearTimeout(activeData.timer);
  572. message.destroyAll();
  573. // item.autoPlay = false;
  574. // nextTick(() => {
  575. // item.audioEle?.onPlay();
  576. // });
  577. }
  578. if (item.type === 'VIDEO') {
  579. // 自动播放下一个视频
  580. clearTimeout(activeData.timer);
  581. message.destroyAll();
  582. nextTick(() => {
  583. if (item.error) {
  584. // console.log(item, 'item error');
  585. item.videoEle?.src(item.content);
  586. item.error = false;
  587. // item.videoEle?.onPlay();
  588. }
  589. data.animationState = 'end';
  590. });
  591. }
  592. if (item.type === 'PPT') {
  593. //
  594. }
  595. }
  596. },
  597. activeData.isAnimation ? 800 : 0
  598. );
  599. };
  600. /** 是否有转场动画 */
  601. const checkedAnimation = (index: number, nextIndex?: number) => {
  602. const item = data.itemList[index];
  603. const nextItem = data.itemList[nextIndex!];
  604. if (nextItem) {
  605. if (nextItem.knowledgePointId != item.knowledgePointId) {
  606. activeData.isAnimation = true;
  607. return;
  608. }
  609. const videoEle = item.videoEle;
  610. const nextVideo = nextItem.videoEle;
  611. if (videoEle && videoEle.duration < 8 && index < nextIndex!) {
  612. activeData.isAnimation = false;
  613. } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex!) {
  614. activeData.isAnimation = false;
  615. } else {
  616. activeData.isAnimation = true;
  617. }
  618. } else {
  619. activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true;
  620. }
  621. };
  622. // 上一个知识点, 下一个知识点
  623. const handlePreAndNext = async (type: string) => {
  624. // if (type === 'up') {
  625. // handleSwipeChange(popupData.activeIndex - 1);
  626. // } else {
  627. // handleSwipeChange(popupData.activeIndex + 1);
  628. // }
  629. if (type === 'up') {
  630. // 判断上面是否还有章节
  631. if (popupData.activeIndex > 0) {
  632. handleSwipeChange(popupData.activeIndex - 1);
  633. return;
  634. }
  635. // 获取当前是哪个章节
  636. let detailIndex = popupData.chapterDetails.findIndex(
  637. (item: any) => item.id == data.lessonCoursewareDetailId
  638. );
  639. const detailItem =
  640. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  641. let lessonIndex = detailItem.findIndex(
  642. (item: any) => item.id == data.detailId
  643. );
  644. let lessonStatus = false; // 当前章节上面是否有内容
  645. let lessonCoursewareDetailId = '';
  646. let coursewareDetailKnowledgeId = '';
  647. while (lessonIndex >= 0) {
  648. lessonIndex--;
  649. if (lessonIndex >= 0) {
  650. if (detailItem[lessonIndex].containMaterial) {
  651. lessonStatus = true;
  652. lessonCoursewareDetailId =
  653. detailItem[lessonIndex].lessonCoursewareDetailId;
  654. coursewareDetailKnowledgeId = detailItem[lessonIndex].id;
  655. }
  656. }
  657. if (lessonStatus) {
  658. break;
  659. }
  660. }
  661. // 判断当前章节下面课程是否有内容,否则往上一个章节走
  662. if (lessonStatus) {
  663. popupData.chapterLoading = true;
  664. data.detailId = coursewareDetailKnowledgeId;
  665. data.lessonCoursewareDetailId = lessonCoursewareDetailId;
  666. // 更新上课记录 上课的时候才更新
  667. if (data.type !== 'preview') {
  668. await classCourseScheduleUpdate();
  669. }
  670. await getDetail();
  671. popupData.activeIndex = data.itemList.length - 1 || 0;
  672. popupData.chapterOpen = false;
  673. popupData.chapterLoading = false;
  674. return;
  675. }
  676. let prevLessonStatus = false;
  677. while (detailIndex >= 0) {
  678. detailIndex--;
  679. const tempDetail =
  680. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  681. let tempLessonLength = tempDetail.length;
  682. while (tempLessonLength > 0) {
  683. if (tempDetail[tempLessonLength - 1].containMaterial) {
  684. prevLessonStatus = true;
  685. lessonCoursewareDetailId =
  686. tempDetail[tempLessonLength - 1].lessonCoursewareDetailId;
  687. coursewareDetailKnowledgeId = tempDetail[tempLessonLength - 1].id;
  688. }
  689. tempLessonLength--;
  690. if (prevLessonStatus) {
  691. break;
  692. }
  693. }
  694. if (prevLessonStatus) {
  695. break;
  696. }
  697. }
  698. // 判断当前章节下面课程是否有内容,否则往上一个章节走
  699. if (prevLessonStatus) {
  700. popupData.chapterLoading = true;
  701. data.detailId = coursewareDetailKnowledgeId;
  702. data.lessonCoursewareDetailId = lessonCoursewareDetailId;
  703. await getDetail();
  704. popupData.activeIndex = data.itemList.length - 1 || 0;
  705. popupData.chapterLoading = false;
  706. return;
  707. }
  708. } else {
  709. if (popupData.activeIndex < data.itemList.length - 1) {
  710. handleSwipeChange(popupData.activeIndex + 1);
  711. return;
  712. }
  713. // 获取当前是哪个章节
  714. let detailIndex = popupData.chapterDetails.findIndex(
  715. (item: any) => item.id == data.lessonCoursewareDetailId
  716. );
  717. const detailItem =
  718. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  719. let lessonIndex = detailItem.findIndex(
  720. (item: any) => item.id == data.detailId
  721. );
  722. let lessonStatus = false; // 当前章节下面是否有内容
  723. let lessonCoursewareDetailId = '';
  724. let coursewareDetailKnowledgeId = '';
  725. while (lessonIndex < detailItem.length - 1) {
  726. lessonIndex++;
  727. if (lessonIndex >= 0) {
  728. if (detailItem[lessonIndex].containMaterial) {
  729. lessonStatus = true;
  730. lessonCoursewareDetailId =
  731. detailItem[lessonIndex].lessonCoursewareDetailId;
  732. coursewareDetailKnowledgeId = detailItem[lessonIndex].id;
  733. }
  734. }
  735. if (lessonStatus) {
  736. break;
  737. }
  738. }
  739. // 判断当前章节下面课程是否有内容,否则往下一个章节走
  740. if (lessonStatus) {
  741. popupData.chapterLoading = true;
  742. data.detailId = coursewareDetailKnowledgeId;
  743. data.lessonCoursewareDetailId = lessonCoursewareDetailId;
  744. // 更新上课记录 上课的时候才更新
  745. if (data.type !== 'preview') {
  746. await classCourseScheduleUpdate();
  747. }
  748. await getDetail();
  749. popupData.activeIndex = 0;
  750. popupData.chapterOpen = false;
  751. popupData.chapterLoading = false;
  752. return;
  753. }
  754. let nextLessonStatus = false;
  755. while (detailIndex <= popupData.chapterDetails.length - 1) {
  756. detailIndex++;
  757. const tempDetail =
  758. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  759. let tempLessonLength = 0;
  760. while (tempLessonLength <= tempDetail.length - 1) {
  761. if (tempDetail[tempLessonLength].containMaterial) {
  762. nextLessonStatus = true;
  763. lessonCoursewareDetailId =
  764. tempDetail[tempLessonLength].lessonCoursewareDetailId;
  765. coursewareDetailKnowledgeId = tempDetail[tempLessonLength].id;
  766. }
  767. tempLessonLength++;
  768. if (nextLessonStatus) {
  769. break;
  770. }
  771. }
  772. if (nextLessonStatus) {
  773. break;
  774. }
  775. }
  776. // 判断当前章节下面课程是否有内容,否则往上一个章节走
  777. if (nextLessonStatus) {
  778. popupData.chapterLoading = true;
  779. data.detailId = coursewareDetailKnowledgeId;
  780. data.lessonCoursewareDetailId = lessonCoursewareDetailId;
  781. // 更新上课记录 上课的时候才更新
  782. if (data.type !== 'preview') {
  783. await classCourseScheduleUpdate();
  784. }
  785. await getDetail();
  786. popupData.activeIndex = 0;
  787. popupData.chapterOpen = false;
  788. popupData.chapterLoading = false;
  789. return;
  790. }
  791. }
  792. };
  793. /** 弹窗关闭 */
  794. const handleClosePopup = () => {
  795. const item = data.itemList[popupData.activeIndex];
  796. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  797. setModelOpen();
  798. }
  799. if (item?.type == 'SONG' && !item.audioEle?.paused) {
  800. setModelOpen();
  801. }
  802. };
  803. document.body.addEventListener('keyup', (e: KeyboardEvent) => {
  804. console.log(e, 'e');
  805. if (e.code === 'ArrowLeft') {
  806. // if (popupData.activeIndex === 0) return;
  807. setModalOpen();
  808. handlePreAndNext('up');
  809. } else if (e.code === 'ArrowRight') {
  810. // if (popupData.activeIndex === data.itemList.length - 1) return;
  811. setModalOpen();
  812. handlePreAndNext('down');
  813. } else if (e.code === 'Space') {
  814. // const activeItem = data.itemList[popupData.activeIndex];
  815. // // // 暂停视频和曲谱的播放
  816. // if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  817. // activeItem.videoEle?.play();
  818. // }
  819. // if (activeItem.type === 'SONG' && activeItem.audioEle) {
  820. // activeItem.audioEle?.play();
  821. // }
  822. // if (activeItem.type === 'MUSIC') {
  823. // activeItem.iframeRef?.contentWindow?.postMessage(
  824. // { api: 'setPlayState' },
  825. // '*'
  826. // );
  827. // }
  828. }
  829. });
  830. // const toggleListenerKeyUp = (type: string) => {
  831. // if (type === 'remove') {
  832. // document.body.removeEventListener('keyup', () => {
  833. // listenerKeyUpState.value = false;
  834. // });
  835. // } else {
  836. // // 监听页面键盘事件 - 上下切换
  837. // document.body.addEventListener('keyup', (e: KeyboardEvent) => {
  838. // // console.log(e, 'e');
  839. // if (e.code === 'ArrowLeft') {
  840. // // if (popupData.activeIndex === 0) return;
  841. // setModalOpen();
  842. // handlePreAndNext('up');
  843. // } else if (e.code === 'ArrowRight') {
  844. // // if (popupData.activeIndex === data.itemList.length - 1) return;
  845. // setModalOpen();
  846. // handlePreAndNext('down');
  847. // }
  848. // });
  849. // listenerKeyUpState.value = true;
  850. // }
  851. // };
  852. // 监听切换到ppt课件时,手动移除键盘监听器
  853. // watch(() => popupData.activeIndex, () => {
  854. // const activeItem = data.itemList[popupData.activeIndex];
  855. // if (activeItem?.type === 'PPT') {
  856. // toggleListenerKeyUp('remove')
  857. // } else {
  858. // !listenerKeyUpState.value && toggleListenerKeyUp('add')
  859. // }
  860. // })
  861. const setModalOpen = (status = true) => {
  862. clearTimeout(activeData.timer);
  863. activeData.model = status;
  864. Object.values(data.videoRefs).map((n: any) =>
  865. n?.toggleHideControl(status)
  866. );
  867. Object.values(data.audioRefs).map((n: any) =>
  868. n?.toggleHideControl(status)
  869. );
  870. };
  871. /** 教学数据 */
  872. const studyData = reactive({
  873. type: '' as ToolType,
  874. penShow: false,
  875. whiteboardShow: false
  876. });
  877. /** 打开教学工具 */
  878. const openStudyTool = (item: ToolItem) => {
  879. // const activeItem = data.itemList[popupData.activeIndex];
  880. // // 暂停视频和曲谱的播放
  881. // if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  882. // activeItem.videoEle?.pause();
  883. // }
  884. // if (activeItem.type === 'SONG' && activeItem.audioEle) {
  885. // activeItem.audioEle?.stop();
  886. // }
  887. // if (activeItem.type === 'MUSIC') {
  888. // activeItem.iframeRef?.contentWindow?.postMessage(
  889. // { api: 'setPlayState' },
  890. // '*'
  891. // );
  892. // }
  893. handleStop();
  894. clearModel();
  895. popupData.toolOpen = false;
  896. studyData.type = item.type;
  897. switch (item.type) {
  898. case 'pen':
  899. studyData.penShow = true;
  900. break;
  901. case 'whiteboard':
  902. studyData.whiteboardShow = true;
  903. }
  904. };
  905. /** 关闭教学工具 */
  906. const closeStudyTool = () => {
  907. studyData.type = 'init';
  908. toggleModel();
  909. setModelOpen();
  910. };
  911. const startShowModal = (
  912. val: 'setTimeIcon' | 'beatIcon' | 'toneIcon' | 'iconNote2'
  913. ) => {
  914. if (val == 'setTimeIcon') {
  915. showModalTime.value = true;
  916. }
  917. if (val == 'beatIcon') {
  918. showModalBeat.value = true;
  919. }
  920. if (val == 'toneIcon') {
  921. showModalTone.value = true;
  922. }
  923. };
  924. // 是否允许上一页
  925. const isUpArrow = computed(() => {
  926. /**
  927. * 1,判断当前课程中是否处在第一个资源;
  928. * 2,判断当前课程是否在当前章节的第一个;
  929. * 3,判断当前章节,当前课程上面还没有其它课程,是否有资源;
  930. * 4,判断当前章节上面还没有其它章节;
  931. * 5,判断上面章节里面课程是否有资源;
  932. */
  933. if (popupData.activeIndex > 0) {
  934. return true;
  935. }
  936. // 获取当前是哪个章节
  937. let detailIndex = popupData.chapterDetails.findIndex(
  938. (item: any) => item.id == data.lessonCoursewareDetailId
  939. );
  940. const detailItem =
  941. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  942. let lessonIndex = detailItem.findIndex(
  943. (item: any) => item.id == data.detailId
  944. );
  945. // 说明已经是第一单元,第一课
  946. if (detailIndex <= 0 && lessonIndex <= 0) {
  947. return false;
  948. }
  949. let lessonStatus = false; // 当前章节上面是否有内容
  950. while (lessonIndex >= 0) {
  951. lessonIndex--;
  952. if (lessonIndex >= 0) {
  953. if (detailItem[lessonIndex].containMaterial) {
  954. lessonStatus = true;
  955. }
  956. }
  957. }
  958. // 判断当前章节下面课程是否有内容,否则往上一个章节走
  959. if (lessonStatus) {
  960. return true;
  961. }
  962. // 已经是第一个章节了
  963. if (detailIndex <= 0) {
  964. return false;
  965. }
  966. let prevLessonStatus = false;
  967. while (detailIndex >= 0) {
  968. detailIndex--;
  969. const tempDetail =
  970. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  971. let tempLessonLength = tempDetail.length;
  972. while (tempLessonLength > 0) {
  973. if (tempDetail[tempLessonLength - 1].containMaterial) {
  974. prevLessonStatus = true;
  975. }
  976. tempLessonLength--;
  977. }
  978. if (prevLessonStatus) {
  979. return true;
  980. }
  981. }
  982. return false;
  983. });
  984. // 是否允许下一页
  985. const isDownArrow = computed(() => {
  986. if (popupData.activeIndex < data.itemList.length - 1) {
  987. return true;
  988. }
  989. // 获取当前是哪个章节
  990. let detailIndex = popupData.chapterDetails.findIndex(
  991. (item: any) => item.id == data.lessonCoursewareDetailId
  992. );
  993. const detailItem =
  994. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  995. let lessonIndex = detailItem.findIndex(
  996. (item: any) => item.id == data.detailId
  997. );
  998. // 说明已经是最后-单元,最后一课
  999. if (
  1000. detailIndex >= popupData.chapterDetails.length - 1 &&
  1001. lessonIndex >= detailItem.length - 1
  1002. ) {
  1003. return false;
  1004. }
  1005. let lessonStatus = false; // 当前章节下面是否有内容
  1006. while (lessonIndex < detailItem.length - 1) {
  1007. lessonIndex++;
  1008. if (lessonIndex >= 0) {
  1009. if (detailItem[lessonIndex].containMaterial) {
  1010. lessonStatus = true;
  1011. }
  1012. }
  1013. }
  1014. // 判断当前章节下面课程是否有内容,否则往下一个章节走
  1015. if (lessonStatus) {
  1016. return true;
  1017. }
  1018. // 已经是最后一个章节了
  1019. if (detailIndex >= popupData.chapterDetails.length - 1) {
  1020. return false;
  1021. }
  1022. let nextLessonStatus = false;
  1023. while (detailIndex < popupData.chapterDetails.length - 1) {
  1024. detailIndex++;
  1025. const tempDetail =
  1026. popupData.chapterDetails[detailIndex]?.knowledgeList || [];
  1027. let tempLessonLength = 0;
  1028. while (tempLessonLength <= tempDetail.length - 1) {
  1029. if (tempDetail[tempLessonLength].containMaterial) {
  1030. nextLessonStatus = true;
  1031. }
  1032. tempLessonLength++;
  1033. }
  1034. if (nextLessonStatus) {
  1035. return true;
  1036. }
  1037. }
  1038. return false;
  1039. });
  1040. const activeVideoItem = computed(() => {
  1041. const item = data.itemList[popupData.activeIndex];
  1042. if (item && item.type && item.type.toLocaleUpperCase() === 'VIDEO') {
  1043. return item;
  1044. }
  1045. return {};
  1046. });
  1047. // 右侧菜单栏
  1048. const rightList = reactive([
  1049. {
  1050. name: '结束课程',
  1051. name2: '结束预览',
  1052. icon: rightIconEnd,
  1053. id: 1
  1054. },
  1055. {
  1056. name: '布置作业',
  1057. icon: rightIconArrange,
  1058. id: 2
  1059. },
  1060. {
  1061. name: '曲目资源',
  1062. icon: rightIconMusic,
  1063. id: 9
  1064. },
  1065. {
  1066. name: '批注',
  1067. icon: rightIconPostil,
  1068. id: 3
  1069. },
  1070. {
  1071. name: '白板',
  1072. icon: rightIconWhiteboard,
  1073. id: 4
  1074. },
  1075. {
  1076. name: '节拍器',
  1077. icon: rightIconMetronome,
  1078. id: 5
  1079. },
  1080. {
  1081. name: '调音器',
  1082. icon: rightIconTuner,
  1083. id: 6
  1084. },
  1085. {
  1086. name: '计时器',
  1087. icon: rightIconTimer,
  1088. id: 7
  1089. },
  1090. {
  1091. name: '收起',
  1092. icon: rightIconPackUp,
  1093. id: 8
  1094. }
  1095. ]);
  1096. // 底部菜单栏
  1097. const bottomList = reactive([
  1098. {
  1099. name: '切换章节',
  1100. icon: bottomIconSwitch,
  1101. id: 1
  1102. },
  1103. {
  1104. name: '资源列表',
  1105. icon: bottomIconResource,
  1106. id: 2
  1107. },
  1108. {
  1109. name: '上一张',
  1110. icon: bottomIconPre,
  1111. id: 3
  1112. },
  1113. {
  1114. name: '下一张',
  1115. icon: bottomIconNext,
  1116. id: 4
  1117. }
  1118. ]);
  1119. const rightColumnShow = ref(true);
  1120. // 右边栏操作
  1121. const operateRightBtn = async (id: number) => {
  1122. switch (id) {
  1123. case 1:
  1124. if (data.type === 'preview') {
  1125. handleStop();
  1126. data.removeVisiable = true;
  1127. data.removeTitle = '结束预览';
  1128. data.removeContent = '请确认是否结束预览?';
  1129. } else {
  1130. const res = await getStudentAfterWork({
  1131. courseScheduleId: data.classId,
  1132. page: 1,
  1133. rows: 99
  1134. });
  1135. if (res.data.rows && res.data.rows.length) {
  1136. data.removeContent = '请确认是否结束课程?';
  1137. data.removeCourseStatus = false;
  1138. } else {
  1139. data.removeContent = '本次课堂尚未布置作业,是否结束课程?';
  1140. data.removeCourseStatus = true;
  1141. }
  1142. data.removeVisiable = true;
  1143. data.removeTitle = '结束课程';
  1144. }
  1145. break;
  1146. case 2:
  1147. // 学生人数必须大于0,才可以布置作业
  1148. if (data.preStudentNum <= 0) return;
  1149. const res = await lessonPreTrainingPage({
  1150. coursewareKnowledgeDetailId: data.detailId,
  1151. subjectId: data.subjectId,
  1152. page: 1,
  1153. rows: 99
  1154. });
  1155. if (res.data.rows && res.data.rows.length) {
  1156. data.modalAttendMessage = '本节课已设置课后作业,是否布置?';
  1157. }
  1158. data.modelAttendStatus = true;
  1159. break;
  1160. case 3:
  1161. openStudyTool({
  1162. type: 'pen',
  1163. icon: iconNote,
  1164. name: '批注'
  1165. });
  1166. break;
  1167. case 4:
  1168. openStudyTool({
  1169. type: 'whiteboard',
  1170. icon: iconWhite,
  1171. name: '白板'
  1172. });
  1173. break;
  1174. case 5:
  1175. startShowModal('beatIcon');
  1176. break;
  1177. case 6:
  1178. startShowModal('toneIcon');
  1179. break;
  1180. case 7:
  1181. startShowModal('setTimeIcon');
  1182. break;
  1183. case 8:
  1184. rightColumnShow.value = false;
  1185. break;
  1186. case 9:
  1187. // 选择曲目时需要暂停所有播放
  1188. handleStop();
  1189. data.selectResourceStatus = true;
  1190. break;
  1191. default:
  1192. break;
  1193. }
  1194. };
  1195. // 底部悬浮按钮操作
  1196. const operateBottomBtn = (id: number) => {
  1197. switch (id) {
  1198. case 1:
  1199. popupData.chapterOpen = true;
  1200. break;
  1201. case 2:
  1202. popupData.open = true;
  1203. nextTick(() => {
  1204. scrollResourceSection();
  1205. });
  1206. break;
  1207. case 3:
  1208. if (!isUpArrow.value) return;
  1209. handlePreAndNext('up');
  1210. break;
  1211. case 4:
  1212. if (!isDownArrow.value) return;
  1213. handlePreAndNext('down');
  1214. break;
  1215. default:
  1216. break;
  1217. }
  1218. };
  1219. // 滚动到某个元素的位置
  1220. const scrollResourceSection = () => {
  1221. const drawerCardItemRefs =
  1222. document.querySelectorAll('.drawerCardItemRef');
  1223. if (
  1224. popupData.activeIndex >= 0 &&
  1225. drawerCardItemRefs[popupData.activeIndex]
  1226. ) {
  1227. drawerCardItemRefs[popupData.activeIndex].scrollIntoView();
  1228. }
  1229. };
  1230. return () => (
  1231. <div id="playContent" class={[styles.playContent, 'wrap']}>
  1232. <div
  1233. onClick={() => {
  1234. clearTimeout(activeData.timer);
  1235. activeData.model = !activeData.model;
  1236. Object.values(data.videoRefs).map((n: any) =>
  1237. n?.toggleHideControl(activeData.model)
  1238. );
  1239. Object.values(data.audioRefs).map((n: any) =>
  1240. n?.toggleHideControl(activeData.model)
  1241. );
  1242. }}>
  1243. <div
  1244. class={styles.coursewarePlay}
  1245. style={{ width: parentContainer.width }}>
  1246. {!popupData.chapterLoading ? (
  1247. <div class={styles.wraps}>
  1248. <div
  1249. style={
  1250. activeVideoItem.value.type &&
  1251. data.animationState === 'end' &&
  1252. data.videoState === 'play'
  1253. ? {
  1254. zIndex: 15,
  1255. opacity: 1
  1256. }
  1257. : { opacity: 0, zIndex: -1 }
  1258. }
  1259. class={styles.itemDiv}>
  1260. <VideoPlay
  1261. ref={(el: any) => (data.videoItemRef = el)}
  1262. item={activeVideoItem.value}
  1263. showModel={activeData.model}
  1264. onClose={setModelOpen}
  1265. onCanplay={() => {
  1266. data.videoState = 'play';
  1267. }}
  1268. onPause={() => {
  1269. clearTimeout(activeData.timer);
  1270. activeData.model = true;
  1271. }}
  1272. onEnded={() => {
  1273. const _index = popupData.activeIndex + 1;
  1274. if (_index < data.itemList.length) {
  1275. handleSwipeChange(_index);
  1276. }
  1277. }}
  1278. />
  1279. </div>
  1280. {data.itemList.map((m: any, mIndex: number) => {
  1281. const isRender = Math.abs(popupData.activeIndex - mIndex) < 2;
  1282. const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
  1283. // if (isRender && m.type === 'PPT') {
  1284. // setTimeout(() => iframeClick() ,500)
  1285. // }
  1286. // if (isRender) {
  1287. // m.isRender = true;
  1288. // }
  1289. // console.log(isRender, 'isRender', mIndex);
  1290. return isRender ? (
  1291. <div
  1292. key={'index' + mIndex}
  1293. class={[
  1294. styles.itemDiv,
  1295. popupData.activeIndex === mIndex && styles.itemActive,
  1296. activeData.isAnimation && styles.acitveAnimation,
  1297. Math.abs(popupData.activeIndex - mIndex) < 2
  1298. ? styles.show
  1299. : styles.hide
  1300. ]}
  1301. style={
  1302. mIndex < popupData.activeIndex
  1303. ? effects[effectIndex.value].prev
  1304. : mIndex > popupData.activeIndex
  1305. ? effects[effectIndex.value].next
  1306. : {}
  1307. }
  1308. onClick={(e: Event) => {
  1309. e.stopPropagation();
  1310. clearTimeout(activeData.timer);
  1311. if (Date.now() - activeData.nowTime < 300) {
  1312. handleDbClick(m);
  1313. return;
  1314. }
  1315. activeData.nowTime = Date.now();
  1316. activeData.timer = setTimeout(() => {
  1317. activeData.model = !activeData.model;
  1318. Object.values(data.videoRefs).map((n: any) =>
  1319. n?.toggleHideControl(activeData.model)
  1320. );
  1321. Object.values(data.audioRefs).map((n: any) =>
  1322. n?.toggleHideControl(activeData.model)
  1323. );
  1324. if (activeData.model) {
  1325. setModelOpen();
  1326. }
  1327. }, 300);
  1328. }}>
  1329. {m.type === 'VIDEO' ? (
  1330. <>
  1331. <img
  1332. src={m.coverImg}
  1333. onLoad={() => {
  1334. m.isprepare = true;
  1335. }}
  1336. />
  1337. {/* <VideoPlay
  1338. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  1339. item={m}
  1340. isEmtry={isEmtry}
  1341. onLoadedmetadata={(videoItem: any) => {
  1342. m.videoEle = videoItem;
  1343. m.isprepare = true;
  1344. }}
  1345. onTogglePlay={(paused: boolean) => {
  1346. m.autoPlay = false;
  1347. if (paused || popupData.open) {
  1348. clearTimeout(activeData.timer);
  1349. } else {
  1350. setModelOpen();
  1351. }
  1352. }}
  1353. onReset={() => {
  1354. if (!m.videoEle?.paused) {
  1355. setModelOpen();
  1356. }
  1357. }}
  1358. onError={() => {
  1359. console.log('video error');
  1360. m.error = true;
  1361. }}
  1362. /> */}
  1363. <Transition name="van-fade">
  1364. {
  1365. <div class={styles.loadWrap}>
  1366. <Vue3Lottie
  1367. animationData={playLoadData}></Vue3Lottie>
  1368. </div>
  1369. }
  1370. </Transition>
  1371. </>
  1372. ) : m.type === 'IMG' ? (
  1373. <img src={m.content} />
  1374. ) : m.type === 'SONG' ? (
  1375. <AudioPay
  1376. item={m}
  1377. ref={(v: any) => (data.audioRefs[mIndex] = v)}
  1378. onLoadedmetadata={(audioItem: any) => {
  1379. m.audioEle = audioItem;
  1380. m.isprepare = true;
  1381. }}
  1382. onTogglePlay={(paused: boolean) => {
  1383. m.autoPlay = false;
  1384. if (paused || popupData.open) {
  1385. clearTimeout(activeData.timer);
  1386. } else {
  1387. setModelOpen();
  1388. }
  1389. }}
  1390. onEnded={() => {
  1391. const _index = popupData.activeIndex + 1;
  1392. if (_index < data.itemList.length) {
  1393. handleSwipeChange(_index);
  1394. }
  1395. }}
  1396. onReset={() => {
  1397. if (!m.audioEle?.paused) {
  1398. setModelOpen();
  1399. }
  1400. }}
  1401. />
  1402. ) : // : m.type === 'PPT' ? <div class={styles.iframePpt}>
  1403. // <div class={styles.pptBox}></div>
  1404. // <iframe src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(m.content)}`} width='100%' height='100%' frameborder='1'></iframe>
  1405. // </div>
  1406. m.type === 'PPT' ? (
  1407. <iframe
  1408. src={`https://view.officeapps.live.com/op/embed.aspx?src=${encodeURIComponent(
  1409. m.content
  1410. )}`}
  1411. width="100%"
  1412. height="100%"
  1413. frameborder="1"></iframe>
  1414. ) : (
  1415. <MusicScore
  1416. activeModel={activeData.model}
  1417. activeStatus={popupData.activeIndex === mIndex}
  1418. data-vid={m.id}
  1419. music={m}
  1420. onSetIframe={(el: any) => {
  1421. m.iframeRef = el;
  1422. }}
  1423. />
  1424. )}
  1425. </div>
  1426. ) : null;
  1427. })}
  1428. </div>
  1429. ) : (
  1430. ''
  1431. )}
  1432. </div>
  1433. </div>
  1434. {/* 右边操作栏 */}
  1435. <div
  1436. class={[
  1437. styles.rightColumn,
  1438. !rightColumnShow.value ? styles.rightColumnHide : '',
  1439. studyData.type !== 'init' &&
  1440. (studyData.penShow || studyData.whiteboardShow)
  1441. ? styles.rightColumnZ
  1442. : ''
  1443. ]}>
  1444. {rightList.map((item: any, index: number) => (
  1445. <div
  1446. class={[
  1447. styles.rightItem,
  1448. item.id === 2 && data.preStudentNum <= 0
  1449. ? styles.itemDisabled
  1450. : ''
  1451. ]}
  1452. onClick={() => operateRightBtn(item.id)}>
  1453. <img src={item.icon} />
  1454. <div class={styles.rightTips}>
  1455. {index === 0 && data.type === 'preview'
  1456. ? item.name2
  1457. : item.name}
  1458. </div>
  1459. </div>
  1460. ))}
  1461. </div>
  1462. {!rightColumnShow.value && (
  1463. <img
  1464. class={[
  1465. styles.rightHideIcon,
  1466. !rightColumnShow.value ? styles.rightIconShow : ''
  1467. ]}
  1468. src={rightHideIcon}
  1469. onClick={() => (rightColumnShow.value = true)}
  1470. />
  1471. )}
  1472. {/* 右下角悬浮按钮 */}
  1473. <div class={styles.bottomColumn}>
  1474. {bottomList.map((item: any, index: number) => (
  1475. <div
  1476. class={[
  1477. styles.bottomItem,
  1478. (item.id === 3 && !isUpArrow.value) ||
  1479. (item.id === 4 && !isDownArrow.value)
  1480. ? styles.itemDisabled
  1481. : ''
  1482. ]}
  1483. onClick={() => operateBottomBtn(item.id)}>
  1484. <img src={item.icon} />
  1485. <div class={styles.bottomTips}>{item.name}</div>
  1486. </div>
  1487. ))}
  1488. </div>
  1489. {/* 显示列表 */}
  1490. <NDrawer
  1491. v-model:show={popupData.open}
  1492. class={styles.drawerContainer}
  1493. onAfterLeave={handleClosePopup}
  1494. showMask={false}>
  1495. <NDrawerContent closable>
  1496. {{
  1497. header: () => (
  1498. <TheNoticeBar text={activeName.value || '资源列表'} />
  1499. ),
  1500. default: () => (
  1501. <div ref={drawerCardRef} id="drawerCardRef">
  1502. {data.knowledgePointList.map((item: any, index: number) => {
  1503. return (
  1504. <div class={[styles.cardContainer, 'drawerCardItemRef']}>
  1505. <CardType
  1506. item={item}
  1507. isActive={popupData.activeIndex === index}
  1508. isCollect={false}
  1509. isShowCollect={false}
  1510. onClick={(item: any) => {
  1511. popupData.open = false;
  1512. toggleMaterial(item.id);
  1513. }}
  1514. />
  1515. </div>
  1516. );
  1517. })}
  1518. </div>
  1519. )
  1520. }}
  1521. </NDrawerContent>
  1522. </NDrawer>
  1523. {/* 显示列表 */}
  1524. <NDrawer
  1525. v-model:show={popupData.chapterOpen}
  1526. class={styles.drawerContainer}
  1527. onAfterLeave={handleClosePopup}
  1528. showMask={false}
  1529. displayDirective="show">
  1530. <NDrawerContent title="切换章节" closable>
  1531. <Chapter
  1532. treeList={popupData.chapterDetails}
  1533. itemActive={data.detailId as any}
  1534. onHandleSelect={async (val: any) => {
  1535. popupData.chapterLoading = true;
  1536. try {
  1537. data.detailId = val.itemActive;
  1538. const ids = formatParentId(
  1539. val.itemActive,
  1540. popupData.chapterDetails
  1541. );
  1542. data.lessonCoursewareDetailId = ids[0];
  1543. // 更新上课记录 上课的时候才更新
  1544. if (data.type !== 'preview') {
  1545. await classCourseScheduleUpdate();
  1546. }
  1547. await getDetail();
  1548. popupData.activeIndex = 0;
  1549. popupData.chapterOpen = false;
  1550. } catch {
  1551. //
  1552. }
  1553. popupData.chapterLoading = false;
  1554. }}
  1555. />
  1556. </NDrawerContent>
  1557. </NDrawer>
  1558. {/* 批注 */}
  1559. {studyData.penShow && (
  1560. <Pen
  1561. show={studyData.type === 'pen'}
  1562. type={studyData.type}
  1563. close={() => closeStudyTool()}
  1564. />
  1565. )}
  1566. {studyData.whiteboardShow && (
  1567. <Pen
  1568. show={studyData.type === 'whiteboard'}
  1569. type={studyData.type}
  1570. close={() => closeStudyTool()}
  1571. />
  1572. )}
  1573. {/* 布置作业 */}
  1574. <NModal
  1575. transformOrigin="center"
  1576. v-model:show={data.modelAttendStatus}
  1577. preset="card"
  1578. // class={styles.attendClassModal}
  1579. title={'课后作业'}
  1580. class={['modalTitle', styles.removeVisiable]}>
  1581. <div class={styles.studentRemove}>
  1582. <p>{data.modalAttendMessage}</p>
  1583. {/* <div class={styles.modelAttendContent}>
  1584. {data.modalAttendMessage}
  1585. </div> */}
  1586. <NSpace class={styles.btnGroupModal} justify="center">
  1587. <NButton
  1588. type="default"
  1589. round
  1590. onClick={() => {
  1591. data.modelAttendStatus = false;
  1592. handleStop();
  1593. data.modelAttendStatus = false;
  1594. }}>
  1595. 暂不布置
  1596. </NButton>
  1597. <NButton
  1598. type="primary"
  1599. round
  1600. onClick={() => {
  1601. data.modelTrainStatus = true;
  1602. data.modelAttendStatus = false;
  1603. }}>
  1604. 布置
  1605. </NButton>
  1606. </NSpace>
  1607. </div>
  1608. </NModal>
  1609. {/* 训练设置 */}
  1610. <NModal
  1611. transformOrigin="center"
  1612. v-model:show={data.modelTrainStatus}
  1613. preset="card"
  1614. class={[styles.attendClassModal, styles.trainClassModal]}
  1615. title={'作业设置'}>
  1616. <TrainSettings
  1617. detailId={data.detailId}
  1618. subjectId={data.subjectId}
  1619. courseScheduleId={data.classId}
  1620. classGroupId={data.classGroupId}
  1621. onClose={() => (data.modelTrainStatus = false)}
  1622. onConfirm={() => {
  1623. // 布置完作业之后直接关闭
  1624. // setTimeout(() => {
  1625. // handleStop();
  1626. // if (state.application) {
  1627. // emit('close');
  1628. // } else {
  1629. // window.close();
  1630. // }
  1631. // }, 1000);
  1632. data.modelTrainStatus = false;
  1633. }}
  1634. />
  1635. </NModal>
  1636. <NModal
  1637. transformOrigin="center"
  1638. class={['modalTitle background']}
  1639. title={'节拍器'}
  1640. preset="card"
  1641. v-model:show={showModalBeat.value}
  1642. style={{ width: '687px' }}>
  1643. <div class={styles.modeWrap}>
  1644. <iframe
  1645. src={`${vaildUrl()}/metronome/?id=${new Date().getTime()}`}
  1646. scrolling="no"
  1647. frameborder="0"
  1648. width="100%"
  1649. height={'650px'}></iframe>
  1650. </div>
  1651. </NModal>
  1652. <NModal
  1653. transformOrigin="center"
  1654. class={['background']}
  1655. v-model:show={showModalTone.value}>
  1656. <div>
  1657. <PlaceholderTone
  1658. onClose={() => {
  1659. showModalTone.value = false;
  1660. }}></PlaceholderTone>
  1661. </div>
  1662. </NModal>
  1663. <NModal
  1664. transformOrigin="center"
  1665. v-model:show={showModalTime.value}
  1666. class={['modalTitle background']}
  1667. title={'计时器'}
  1668. preset="card"
  1669. style={{ width: px2vw(772) }}>
  1670. <div>
  1671. <TimerMeter></TimerMeter>
  1672. </div>
  1673. </NModal>
  1674. <NModal
  1675. v-model:show={data.selectResourceStatus}
  1676. class={['modalTitle', styles.selectMusicModal]}
  1677. preset="card"
  1678. title={'选择资源'}>
  1679. <SelectResources from="class" />
  1680. </NModal>
  1681. <NModal
  1682. transformOrigin="center"
  1683. v-model:show={data.removeVisiable}
  1684. preset="card"
  1685. class={['modalTitle', styles.removeVisiable]}
  1686. title={data.removeTitle}>
  1687. <div class={styles.studentRemove}>
  1688. <p>{data.removeContent}</p>
  1689. <NSpace class={styles.btnGroupModal} justify="center">
  1690. <NButton
  1691. round
  1692. onClick={() => {
  1693. if (data.removeCourseStatus) {
  1694. if (globalState.application) {
  1695. document.exitFullscreen
  1696. ? document.exitFullscreen()
  1697. : document.mozCancelFullScreen
  1698. ? document.mozCancelFullScreen()
  1699. : document.webkitExitFullscreen
  1700. ? document.webkitExitFullscreen()
  1701. : '';
  1702. emit('close');
  1703. } else {
  1704. window.close();
  1705. }
  1706. } else {
  1707. data.removeVisiable = false;
  1708. }
  1709. }}>
  1710. {data.removeCourseStatus ? '结束课程' : '取消'}
  1711. </NButton>
  1712. <NButton
  1713. round
  1714. type="primary"
  1715. onClick={() => {
  1716. //
  1717. if (data.removeCourseStatus) {
  1718. data.modelTrainStatus = true;
  1719. data.removeVisiable = false;
  1720. } else {
  1721. if (globalState.application) {
  1722. document.exitFullscreen
  1723. ? document.exitFullscreen()
  1724. : document.mozCancelFullScreen
  1725. ? document.mozCancelFullScreen()
  1726. : document.webkitExitFullscreen
  1727. ? document.webkitExitFullscreen()
  1728. : '';
  1729. emit('close');
  1730. } else {
  1731. window.close();
  1732. }
  1733. }
  1734. }}>
  1735. {data.removeCourseStatus ? '布置作业' : '确定'}
  1736. </NButton>
  1737. </NSpace>
  1738. </div>
  1739. </NModal>
  1740. </div>
  1741. );
  1742. }
  1743. });