index.tsx 51 KB

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