index.tsx 59 KB

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