index.tsx 23 KB

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