index.tsx 60 KB

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