index.tsx 61 KB

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