index.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. // import { Popup } from 'vant';
  2. import {
  3. defineComponent,
  4. onMounted,
  5. reactive,
  6. nextTick,
  7. onUnmounted,
  8. ref,
  9. watch,
  10. Transition
  11. } from 'vue';
  12. // import iconBack from './image/back.svg';
  13. import styles from './index.module.less';
  14. import 'plyr/dist/plyr.css';
  15. import MusicScore from './component/musicScore';
  16. import iconMenu from './image/icon-menu.svg';
  17. // import iconDian from './image/icon-dian.svg';
  18. // import iconPoint from './image/icon-point.svg';
  19. import iconUp from './image/icon-up.svg';
  20. import iconUpDisabled from './image/icon-up-disabled.svg';
  21. import iconDown from './image/icon-down.svg';
  22. import iconDownDisabled from './image/icon-down-disabled.svg';
  23. // import iconWhiteboard from './image/icon-whiteboard.svg';
  24. import iconNote from './image/icon-note.png';
  25. // import Points from './component/points';
  26. import iconAssignHomework from './image/icon-assignHomework.svg';
  27. import { Vue3Lottie } from 'vue3-lottie';
  28. import playLoadData from './datas/data.json';
  29. import { usePageVisibility } from '@vant/use';
  30. import VideoPlay from './component/video-play';
  31. import {
  32. useMessage,
  33. NDrawer,
  34. NDrawerContent,
  35. NModal,
  36. NSpace,
  37. NButton
  38. } from 'naive-ui';
  39. import CardType from '@/components/card-type';
  40. import { ToolItem, ToolType } from './component/tool';
  41. import Pen from './component/tools/pen';
  42. import AudioPay from './component/audio-pay';
  43. import TrainSettings from './model/train-settings';
  44. export default defineComponent({
  45. name: 'CoursewarePlay',
  46. setup() {
  47. const message = useMessage();
  48. const pageVisibility = usePageVisibility();
  49. const isPlay = ref(false);
  50. /** 页面显示和隐藏 */
  51. watch(pageVisibility, value => {
  52. const activeItem = data.itemList[popupData.activeIndex];
  53. if (activeItem.type != 'VIDEO') return;
  54. if (value == 'hidden') {
  55. isPlay.value = !activeItem.videoEle?.paused;
  56. togglePlay(activeItem, false);
  57. } else {
  58. // 页面显示,并且
  59. if (isPlay.value) togglePlay(activeItem, true);
  60. }
  61. });
  62. /** 设置播放容器 16:9 */
  63. const parentContainer = reactive({
  64. width: '100vw'
  65. });
  66. const setContainer = () => {
  67. const min = Math.min(screen.width, screen.height);
  68. const max = Math.max(screen.width, screen.height);
  69. const width = min * (16 / 9);
  70. if (width > max) {
  71. parentContainer.width = '100vw';
  72. return;
  73. } else {
  74. parentContainer.width = width + 'px';
  75. }
  76. };
  77. const handleInit = (type = 0) => {
  78. //设置容器16:9
  79. setContainer();
  80. };
  81. handleInit();
  82. onUnmounted(() => {
  83. handleInit(1);
  84. });
  85. // const route = useRoute();
  86. // const router = useRouter();
  87. // const headeRef = ref();
  88. const data = reactive({
  89. detail: null,
  90. knowledgePointList: [] as any,
  91. itemList: [] as any,
  92. showHead: true,
  93. isCourse: false,
  94. isRecordPlay: false,
  95. videoRefs: {} as any[],
  96. modelAttendStatus: false, // 布置作业提示弹窗
  97. modelTrainStatus: false // 训练设置
  98. });
  99. const activeData = reactive({
  100. isAutoPlay: true, // 是否自动播放
  101. nowTime: 0,
  102. model: true, // 遮罩
  103. isAnimation: true, // 是否动画
  104. videoBtns: true, // 视频
  105. currentTime: 0,
  106. duration: 0,
  107. timer: null as any,
  108. item: null as any
  109. });
  110. // const getTempList = async (materialList: any, name: any) => {
  111. // const list: any = [];
  112. // const browserInfo = browser();
  113. // for (let j = 0; j < materialList.length; j++) {
  114. // const material = materialList[j];
  115. // list.push({
  116. // ...material,
  117. // iframeRef: null,
  118. // videoEle: null,
  119. // tabName: name,
  120. // autoPlay: false, //加载完成是否自动播放
  121. // isprepare: false, // 视频是否加载完成
  122. // isRender: false // 是否渲染了
  123. // });
  124. // }
  125. // return list;
  126. // };
  127. const getDetail = async () => {
  128. data.knowledgePointList = [
  129. // {
  130. // id: '5',
  131. // name: '歌曲表演 大鹿',
  132. // title: '歌曲表演 大鹿',
  133. // type: 'AUDIO',
  134. // content:
  135. // 'https://cloud-coach.ks3-cn-beijing.ksyuncs.com/1686819360752.mp3',
  136. // url: 'https://lanhu-dds-backend.oss-cn-beijing.aliyuncs.com/merge_image/imgs/23cc71b5d7874dcf8752cd257483e687_mergeImage.png'
  137. // },
  138. {
  139. id: '1',
  140. name: '其多列',
  141. title: '其多列',
  142. type: 'VIDEO',
  143. content:
  144. 'https://gyt.ks3-cn-beijing.ksyuncs.com/courseware/1687844560120.mp4',
  145. url: 'https://gyt.ks3-cn-beijing.ksyuncs.com/courseware/1687844640957.png'
  146. },
  147. // {
  148. // id: '1',
  149. // name: '歌曲表演 大鹿',
  150. // title: '歌曲表演 大鹿',
  151. // type: 'VIDEO',
  152. // content:
  153. // 'https://gyt.ks3-cn-beijing.ksyuncs.com/courseware/1687838624636.mp4',
  154. // url: 'https://daya.ks3-cn-beijing.ksyun.com/202306/TiLloDA.jpg'
  155. // },
  156. {
  157. id: '2',
  158. name: '知识 音的高低',
  159. title: '知识 音的高低',
  160. type: 'IMG',
  161. content: 'https://daya.ks3-cn-beijing.ksyun.com/202306/TiLlteU.png',
  162. url: 'https://daya.ks3-cn-beijing.ksyun.com/202306/TiLlteU.png'
  163. },
  164. {
  165. id: '3',
  166. name: '欣赏 永远在童话里',
  167. title: '欣赏 永远在童话里',
  168. type: 'IMG',
  169. content: 'https://daya.ks3-cn-beijing.ksyun.com/202306/TiLlxJ0.png',
  170. url: 'https://daya.ks3-cn-beijing.ksyun.com/202306/TiLlxJ0.png'
  171. },
  172. {
  173. id: '4',
  174. name: '彩虹岛',
  175. title: '彩虹岛',
  176. type: 'SONG',
  177. content: '22078',
  178. url: 'https://cloud-coach.ks3-cn-beijing.ksyuncs.com/music-sheet-fixed/1675770786664-1.png'
  179. }
  180. ];
  181. data.itemList = data.knowledgePointList.map((m: any) => {
  182. return {
  183. ...m,
  184. iframeRef: null,
  185. videoEle: null,
  186. autoPlay: false, //加载完成是否自动播放
  187. isprepare: false, // 视频是否加载完成
  188. isRender: false // 是否渲染了
  189. };
  190. });
  191. };
  192. // ifram事件处理
  193. const iframeHandle = (ev: MessageEvent) => {
  194. if (ev.data?.api === 'headerTogge') {
  195. activeData.model =
  196. ev.data.show || (ev.data.playState == 'play' ? false : true);
  197. }
  198. };
  199. onMounted(() => {
  200. getDetail();
  201. });
  202. // const playRef = ref();
  203. // 返回
  204. // const goback = () => {
  205. // try {
  206. // playRef.value?.handleOut();
  207. // } catch {
  208. // //
  209. // }
  210. // postMessage({ api: 'goBack' });
  211. // };
  212. const popupData = reactive({
  213. open: false,
  214. activeIndex: 0,
  215. // tabActive: '',
  216. // tabName: '',
  217. // itemActive: '',
  218. // itemName: ''
  219. // guideOpen: false,
  220. toolOpen: false // 工具弹窗控制
  221. });
  222. /**停止所有的播放 */
  223. const handleStop = () => {
  224. for (let i = 0; i < data.itemList.length; i++) {
  225. const activeItem = data.itemList[i];
  226. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  227. activeItem.videoEle.stop();
  228. }
  229. // console.log('🚀 ~ activeItem:', activeItem)
  230. // 停止曲谱的播放
  231. if (activeItem.type === 'SONG') {
  232. activeItem.iframeRef?.contentWindow?.postMessage(
  233. { api: 'setPlayState' },
  234. '*'
  235. );
  236. }
  237. }
  238. };
  239. // 切换素材
  240. const toggleMaterial = (itemActive: any) => {
  241. const index = data.itemList.findIndex((n: any) => n.id == itemActive);
  242. if (index > -1) {
  243. handleSwipeChange(index);
  244. }
  245. };
  246. /** 延迟收起模态框 */
  247. const setModelOpen = () => {
  248. clearTimeout(activeData.timer);
  249. message.destroyAll();
  250. activeData.timer = setTimeout(() => {
  251. activeData.model = false;
  252. Object.values(data.videoRefs).map((n: any) =>
  253. n.toggleHideControl(false)
  254. );
  255. }, 4000);
  256. };
  257. /** 立即收起所有的模态框 */
  258. const clearModel = () => {
  259. clearTimeout(activeData.timer);
  260. message.destroyAll();
  261. activeData.model = false;
  262. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(false));
  263. };
  264. const toggleModel = (type = true) => {
  265. activeData.model = type;
  266. Object.values(data.videoRefs).map((n: any) => n.toggleHideControl(type));
  267. };
  268. // 双击
  269. const handleDbClick = (item: any) => {
  270. if (item && item.type === 'VIDEO') {
  271. const videoEle: HTMLVideoElement = item.videoEle;
  272. if (videoEle) {
  273. if (videoEle.paused) {
  274. message.destroyAll();
  275. videoEle.play();
  276. } else {
  277. message.warning('已暂停');
  278. videoEle.pause();
  279. }
  280. }
  281. }
  282. };
  283. // 切换播放
  284. const togglePlay = (m: any, isPlay: boolean) => {
  285. if (isPlay) {
  286. m.videoEle?.play();
  287. } else {
  288. m.videoEle?.pause();
  289. }
  290. };
  291. const showIndex = ref(-4);
  292. const effectIndex = ref(3);
  293. const effects = [
  294. {
  295. prev: {
  296. transform: 'translate3d(0, 0, -800px) rotateX(180deg)'
  297. },
  298. next: {
  299. transform: 'translate3d(0, 0, -800px) rotateX(-180deg)'
  300. }
  301. },
  302. {
  303. prev: {
  304. transform: 'translate3d(-100%, 0, -800px)'
  305. },
  306. next: {
  307. transform: 'translate3d(100%, 0, -800px)'
  308. }
  309. },
  310. {
  311. prev: {
  312. transform: 'translate3d(-50%, 0, -800px) rotateY(80deg)'
  313. },
  314. next: {
  315. transform: 'translate3d(50%, 0, -800px) rotateY(-80deg)'
  316. }
  317. },
  318. {
  319. prev: {
  320. transform: 'translate3d(-100%, 0, -800px) rotateY(-120deg)'
  321. },
  322. next: {
  323. transform: 'translate3d(100%, 0, -800px) rotateY(120deg)'
  324. }
  325. },
  326. // 风车4
  327. {
  328. prev: {
  329. transform: 'translate3d(-50%, 50%, -800px) rotateZ(-14deg)',
  330. opacity: 0
  331. },
  332. next: {
  333. transform: 'translate3d(50%, 50%, -800px) rotateZ(14deg)',
  334. opacity: 0
  335. }
  336. },
  337. // 翻页5
  338. {
  339. prev: {
  340. transform: 'translateZ(-800px) rotate3d(0, -1, 0, 90deg)',
  341. opacity: 0
  342. },
  343. next: {
  344. transform: 'translateZ(-800px) rotate3d(0, 1, 0, 90deg)',
  345. opacity: 0
  346. },
  347. current: { transitionDelay: '700ms' }
  348. }
  349. ];
  350. const acitveTimer = ref();
  351. // 轮播切换
  352. const handleSwipeChange = (index: number) => {
  353. // 如果是当前正在播放 或者是视频最后一个
  354. if (popupData.activeIndex == index) return;
  355. handleStop();
  356. clearTimeout(acitveTimer.value);
  357. checkedAnimation(popupData.activeIndex, index);
  358. popupData.activeIndex = index;
  359. acitveTimer.value = setTimeout(
  360. () => {
  361. const item = data.itemList[index];
  362. if (item) {
  363. // popupData.tabActive = item.knowledgePointId;
  364. // popupData.itemActive = item.id;
  365. // popupData.itemName = item.name;
  366. // popupData.tabName = item.tabName;
  367. if (item.type == 'SONG') {
  368. activeData.model = true;
  369. }
  370. if (item.type === 'VIDEO') {
  371. // 自动播放下一个视频
  372. clearTimeout(activeData.timer);
  373. message.destroyAll();
  374. item.autoPlay = true;
  375. nextTick(() => {
  376. item.videoEle?.play();
  377. });
  378. }
  379. }
  380. // requestAnimationFrame(() => {
  381. // const _effectIndex = effectIndex.value + 1;
  382. // effectIndex.value =
  383. // _effectIndex >= effects.length - 1 ? 0 : _effectIndex;
  384. // });
  385. },
  386. activeData.isAnimation ? 800 : 0
  387. );
  388. };
  389. /** 是否有转场动画 */
  390. const checkedAnimation = (index: number, nextIndex?: number) => {
  391. const item = data.itemList[index];
  392. const nextItem = data.itemList[nextIndex!];
  393. if (nextItem) {
  394. if (nextItem.knowledgePointId != item.knowledgePointId) {
  395. activeData.isAnimation = true;
  396. return;
  397. }
  398. const videoEle = item.videoEle;
  399. const nextVideo = nextItem.videoEle;
  400. if (videoEle && videoEle.duration < 8 && index < nextIndex!) {
  401. activeData.isAnimation = false;
  402. } else if (nextVideo && nextVideo.duration < 8 && index > nextIndex!) {
  403. activeData.isAnimation = false;
  404. } else {
  405. activeData.isAnimation = true;
  406. }
  407. } else {
  408. activeData.isAnimation = item?.adviseStudyTimeSecond < 8 ? false : true;
  409. }
  410. };
  411. // 上一个知识点, 下一个知识点
  412. const handlePreAndNext = (type: string) => {
  413. if (type === 'up') {
  414. handleSwipeChange(popupData.activeIndex - 1);
  415. } else {
  416. handleSwipeChange(popupData.activeIndex + 1);
  417. }
  418. };
  419. /** 弹窗关闭 */
  420. const handleClosePopup = () => {
  421. const item = data.itemList[popupData.activeIndex];
  422. if (item?.type == 'VIDEO' && !item.videoEle?.paused) {
  423. setModelOpen();
  424. }
  425. };
  426. // 监听页面键盘事件 - 上下切换
  427. document.body.addEventListener('keyup', (e: KeyboardEvent) => {
  428. console.log(e, 'e');
  429. if (e.code === 'ArrowUp') {
  430. if (popupData.activeIndex === 0) return;
  431. handlePreAndNext('up');
  432. } else if (e.code === 'ArrowDown') {
  433. if (popupData.activeIndex === data.itemList.length - 1) return;
  434. handlePreAndNext('down');
  435. }
  436. // else if (e.code === 'Space') {
  437. // handleStop();
  438. // }
  439. });
  440. /** 教学数据 */
  441. const studyData = reactive({
  442. type: '' as ToolType,
  443. penShow: false
  444. });
  445. /** 打开教学工具 */
  446. const openStudyTool = (item: ToolItem) => {
  447. const activeItem = data.itemList[popupData.activeIndex];
  448. // 暂停视频和曲谱的播放
  449. if (activeItem.type === 'VIDEO' && activeItem.videoEle) {
  450. activeItem.videoEle.pause();
  451. }
  452. if (activeItem.type === 'SONG') {
  453. activeItem.iframeRef?.contentWindow?.postMessage(
  454. { api: 'setPlayState' },
  455. '*'
  456. );
  457. }
  458. clearModel();
  459. popupData.toolOpen = false;
  460. studyData.type = item.type;
  461. switch (item.type) {
  462. case 'pen':
  463. studyData.penShow = true;
  464. break;
  465. }
  466. };
  467. /** 关闭教学工具 */
  468. const closeStudyTool = () => {
  469. studyData.type = 'init';
  470. toggleModel();
  471. };
  472. onMounted(() => {
  473. window.addEventListener('message', iframeHandle);
  474. // window.onbeforeunload = e => {
  475. // console.log(e, 'e');
  476. // if (e) {
  477. // e.returnValue = '关闭提示';
  478. // }
  479. // return false;
  480. // };
  481. });
  482. return () => (
  483. <div id="playContent" class={styles.playContent}>
  484. <div
  485. onClick={() => {
  486. clearTimeout(activeData.timer);
  487. activeData.model = !activeData.model;
  488. Object.values(data.videoRefs).map((n: any) =>
  489. n.toggleHideControl(activeData.model)
  490. );
  491. }}>
  492. <div
  493. class={styles.coursewarePlay}
  494. style={{ width: parentContainer.width }}
  495. onClick={(e: Event) => {
  496. e.stopPropagation();
  497. setModelOpen();
  498. }}>
  499. <div class={styles.wraps}>
  500. {data.itemList.map((m: any, mIndex: number) => {
  501. const isRender =
  502. m.isRender || Math.abs(popupData.activeIndex - mIndex) < 2;
  503. const isEmtry = Math.abs(popupData.activeIndex - mIndex) > 4;
  504. if (isRender) {
  505. m.isRender = true;
  506. }
  507. return isRender ? (
  508. <div
  509. key={'index' + mIndex}
  510. class={[
  511. styles.itemDiv,
  512. popupData.activeIndex === mIndex && styles.itemActive,
  513. activeData.isAnimation && styles.acitveAnimation,
  514. Math.abs(popupData.activeIndex - mIndex) < 2
  515. ? styles.show
  516. : styles.hide
  517. ]}
  518. style={
  519. mIndex < popupData.activeIndex
  520. ? effects[effectIndex.value].prev
  521. : mIndex > popupData.activeIndex
  522. ? effects[effectIndex.value].next
  523. : {}
  524. }
  525. onClick={(e: Event) => {
  526. e.stopPropagation();
  527. clearTimeout(activeData.timer);
  528. if (Date.now() - activeData.nowTime < 300) {
  529. handleDbClick(m);
  530. return;
  531. }
  532. activeData.nowTime = Date.now();
  533. activeData.timer = setTimeout(() => {
  534. activeData.model = !activeData.model;
  535. Object.values(data.videoRefs).map((n: any) =>
  536. n.toggleHideControl(activeData.model)
  537. );
  538. if (activeData.model) {
  539. setModelOpen();
  540. }
  541. }, 300);
  542. }}>
  543. {m.type === 'VIDEO' ? (
  544. <>
  545. <VideoPlay
  546. ref={(v: any) => (data.videoRefs[mIndex] = v)}
  547. item={m}
  548. isEmtry={isEmtry}
  549. onLoadedmetadata={(videoItem: any) => {
  550. m.videoEle = videoItem;
  551. m.isprepare = true;
  552. }}
  553. onTogglePlay={(paused: boolean) => {
  554. m.autoPlay = false;
  555. if (paused || popupData.open) {
  556. clearTimeout(activeData.timer);
  557. } else {
  558. setModelOpen();
  559. }
  560. }}
  561. onEnded={() => {
  562. const _index = popupData.activeIndex + 1;
  563. if (_index < data.itemList.length) {
  564. handleSwipeChange(_index);
  565. }
  566. }}
  567. onReset={() => {
  568. if (!m.videoEle?.paused) {
  569. setModelOpen();
  570. }
  571. }}
  572. />
  573. <Transition name="van-fade">
  574. {!m.isprepare && (
  575. <div class={styles.loadWrap}>
  576. <Vue3Lottie
  577. animationData={playLoadData}></Vue3Lottie>
  578. </div>
  579. )}
  580. </Transition>
  581. </>
  582. ) : m.type === 'IMG' ? (
  583. <img src={m.content} />
  584. ) : m.type === 'AUDIO' ? (
  585. <AudioPay item={m} />
  586. ) : (
  587. <MusicScore
  588. activeModel={activeData.model}
  589. data-vid={m.id}
  590. music={m}
  591. onSetIframe={(el: any) => {
  592. m.iframeRef = el;
  593. }}
  594. />
  595. )}
  596. </div>
  597. ) : null;
  598. })}
  599. </div>
  600. <Transition name="right">
  601. {activeData.model && (
  602. <div
  603. class={styles.rightFixedBtns}
  604. onClick={(e: Event) => {
  605. e.stopPropagation();
  606. clearTimeout(activeData.timer);
  607. }}>
  608. <div
  609. class={[styles.fullBtn, styles.point]}
  610. onClick={() => (popupData.open = true)}>
  611. <img src={iconMenu} />
  612. </div>
  613. </div>
  614. )}
  615. </Transition>
  616. </div>
  617. </div>
  618. {/* <div
  619. style={{ transform: activeData.model ? '' : 'translateY(-100%)' }}
  620. class={styles.headerContainer}
  621. ref={headeRef}>
  622. <div class={styles.backBtn} onClick={() => goback()}>
  623. <Icon name={iconBack} />
  624. 返回
  625. </div>
  626. <div class={styles.menu}>{popupData.itemName}</div>
  627. </div> */}
  628. {/* 布置作业按钮 */}
  629. <div
  630. class={[
  631. styles.assignHomework,
  632. activeData.model ? '' : styles.sectionAnimateUp
  633. ]}
  634. onClick={() => (data.modelAttendStatus = true)}>
  635. <img src={iconAssignHomework} />
  636. </div>
  637. {/* 上下切换 */}
  638. <div
  639. class={[
  640. styles.switchChangeSection,
  641. activeData.model ? '' : styles.sectionAnimate
  642. ]}>
  643. <div
  644. class={[styles.switchBtn]}
  645. onClick={() => {
  646. if (popupData.activeIndex === 0) return;
  647. handlePreAndNext('up');
  648. }}>
  649. <img src={popupData.activeIndex === 0 ? iconUpDisabled : iconUp} />
  650. </div>
  651. <div
  652. class={[styles.switchBtn]}
  653. onClick={() => {
  654. if (popupData.activeIndex === data.itemList.length - 1) return;
  655. handlePreAndNext('down');
  656. }}>
  657. <img
  658. src={
  659. popupData.activeIndex === data.itemList.length - 1
  660. ? iconDownDisabled
  661. : iconDown
  662. }
  663. />
  664. </div>
  665. </div>
  666. {/* 白板 */}
  667. <div
  668. class={[
  669. styles.switchDisplaySection,
  670. activeData.model ? '' : styles.sectionAnimate
  671. ]}>
  672. <div
  673. class={styles.displayBtn}
  674. onClick={() =>
  675. openStudyTool({
  676. type: 'pen',
  677. icon: iconNote,
  678. name: '批注'
  679. })
  680. }>
  681. <img src={iconNote} />
  682. </div>
  683. </div>
  684. {/* 显示列表 */}
  685. <NDrawer
  686. v-model:show={popupData.open}
  687. class={styles.drawerContainer}
  688. onAfterLeave={handleClosePopup}
  689. showMask={false}>
  690. <NDrawerContent title="资源列表" closable>
  691. {data.knowledgePointList.map((item: any, index: number) => (
  692. <CardType
  693. item={item}
  694. isActive={popupData.activeIndex === index}
  695. isCollect={false}
  696. onClick={(item: any) => {
  697. popupData.open = false;
  698. toggleMaterial(item.id);
  699. }}
  700. />
  701. ))}
  702. </NDrawerContent>
  703. </NDrawer>
  704. {/* 批注 */}
  705. {studyData.penShow && (
  706. <Pen show={studyData.type === 'pen'} close={() => closeStudyTool()} />
  707. )}
  708. {/* 布置作业 */}
  709. <NModal
  710. v-model:show={data.modelAttendStatus}
  711. preset="card"
  712. class={styles.attendClassModal}
  713. title={'课后训练'}>
  714. <div class={styles.modelAttendContent}>
  715. 本节课已设置课后训练,是否布置?
  716. </div>
  717. <NSpace class={styles.modelAttendBtnGroup}>
  718. <NButton
  719. type="default"
  720. round
  721. onClick={() => {
  722. data.modelAttendStatus = false;
  723. window.close();
  724. }}>
  725. 暂不布置
  726. </NButton>
  727. <NButton
  728. type="primary"
  729. round
  730. onClick={() => {
  731. data.modelTrainStatus = true;
  732. data.modelAttendStatus = false;
  733. }}>
  734. 布置
  735. </NButton>
  736. </NSpace>
  737. </NModal>
  738. {/* 训练设置 */}
  739. <NModal
  740. v-model:show={data.modelTrainStatus}
  741. preset="card"
  742. class={[styles.attendClassModal, styles.trainClassModal]}
  743. title={'训练设置'}>
  744. <TrainSettings onClose={() => (data.modelTrainStatus = false)} />
  745. </NModal>
  746. </div>
  747. );
  748. }
  749. });