index.tsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751
  1. import { defineComponent, onMounted, reactive, watch } from 'vue';
  2. import styles from './index.module.less';
  3. import {
  4. NButton,
  5. NModal,
  6. NScrollbar,
  7. NSelect,
  8. NSpace,
  9. NSpin,
  10. useMessage,
  11. useDialog
  12. } from 'naive-ui';
  13. import CardType from '/src/components/card-type';
  14. import AttendClass from '/src/views/prepare-lessons/model/attend-class';
  15. import { usePrepareStore } from '/src/store/modules/prepareLessons';
  16. import { useCatchStore } from '/src/store/modules/catchData';
  17. import TheEmpty from '/src/components/TheEmpty';
  18. import {
  19. courseScheduleStart,
  20. queryCourseware,
  21. saveCourseware,
  22. teacherKnowledgeMaterialDelete
  23. } from '../../../api';
  24. import Draggable from 'vuedraggable';
  25. import iconDelete from '../../../images/icon-delete.png';
  26. import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router';
  27. import deepClone from '/src/helpers/deep-clone';
  28. import CardPreview from '/src/components/card-preview';
  29. import PreviewWindow from '/src/views/preview-window';
  30. import { state } from '/src/state';
  31. import SubjectSync from '../../../model/subject-sync';
  32. import { eventGlobal } from '/src/utils';
  33. export default defineComponent({
  34. name: 'courseware-modal',
  35. setup() {
  36. const catchStore = useCatchStore();
  37. const prepareStore = usePrepareStore();
  38. const route = useRoute();
  39. const router = useRouter();
  40. const dialog = useDialog();
  41. const message = useMessage();
  42. const forms = reactive({
  43. className: route.query.name as any,
  44. classGroupId: route.query.classGroupId,
  45. subjectId: route.query.subjectId ? Number(route.query.subjectId) : null,
  46. coursewareList: [] as any,
  47. loadingStatus: false,
  48. showAttendClass: false,
  49. attendClassType: 'change', //
  50. removeIds: [] as any, // 临时删除的编号
  51. drag: false,
  52. isEdit: false, // 是否更新数据
  53. editSubjectIds: '', // 声部编号
  54. removeVisiable: false,
  55. removeVisiable1: false,
  56. subjectSyncVisiable: false, // 同步声部
  57. show: false,
  58. item: {} as any,
  59. previewModal: false,
  60. previewParams: {
  61. type: '',
  62. subjectId: '',
  63. detailId: ''
  64. } as any
  65. });
  66. // 获取列表
  67. const getList = async () => {
  68. forms.loadingStatus = true;
  69. try {
  70. // 判断是否有选择对应的课件 或声部
  71. if (!prepareStore.getSelectKey || !prepareStore.getSubjectId)
  72. return (forms.loadingStatus = false);
  73. const { data } = await queryCourseware({
  74. coursewareDetailKnowledgeId: prepareStore.getSelectKey,
  75. subjectId: prepareStore.getSubjectId,
  76. page: 1,
  77. rows: 99
  78. });
  79. const tempRows = data.rows || [];
  80. const temp: any = [];
  81. tempRows.forEach((row: any) => {
  82. temp.push({
  83. id: row.id,
  84. materialId: row.materialId,
  85. coverImg: row.coverImg,
  86. type: row.materialType,
  87. title: row.materialName,
  88. isCollect: !!row.favoriteFlag,
  89. isSelected: row.source === 'PLATFORM' ? true : false,
  90. content: row.content,
  91. removeFlag: row.removeFlag
  92. });
  93. });
  94. prepareStore.setCoursewareList(temp || []);
  95. const tempCourse: any = [];
  96. temp.forEach((item: any) => {
  97. if (!forms.removeIds.includes(item.id)) {
  98. tempCourse.push(item);
  99. }
  100. });
  101. forms.coursewareList = tempCourse;
  102. } catch {
  103. //
  104. }
  105. forms.loadingStatus = false;
  106. };
  107. // 监听选择的key 左侧选择了其它的课
  108. watch(
  109. () => prepareStore.getSelectKey,
  110. () => {
  111. forms.drag = false;
  112. prepareStore.setIsEditResource(false);
  113. getList();
  114. }
  115. );
  116. // 声部变化时
  117. watch(
  118. () => prepareStore.getSubjectId,
  119. () => {
  120. getList();
  121. }
  122. );
  123. watch(
  124. () => prepareStore.getIsAddResource,
  125. (val: boolean) => {
  126. if (val) {
  127. getList();
  128. prepareStore.setIsAddResource(false);
  129. }
  130. }
  131. );
  132. // 监听列表变化,如果变化了,则弹选择声部的
  133. watch(
  134. () => forms.coursewareList,
  135. () => {
  136. if (forms.drag) {
  137. forms.isEdit = true;
  138. }
  139. },
  140. {
  141. deep: true
  142. }
  143. );
  144. // 删除
  145. const onDelete = (item: any) => {
  146. //
  147. forms.removeIds.push(item.id);
  148. const index = forms.coursewareList.findIndex(
  149. (c: any) => c.id === item.id
  150. );
  151. forms.coursewareList.splice(index, 1);
  152. forms.isEdit = true;
  153. // prepareStore.setCoursewareList(forms.coursewareList);
  154. // console.log(prepareStore.getCoursewareList, 'getCourseware');
  155. };
  156. // 完成编辑
  157. const onOverEdit = async () => {
  158. try {
  159. const temp: any = [];
  160. forms.coursewareList.forEach((item: any) => {
  161. temp.push({
  162. materialName: item.title,
  163. materialType: item.type,
  164. materialId: item.materialId,
  165. id: item.id
  166. });
  167. });
  168. // 保存课件
  169. // 判断是否编辑,如果编辑则取选择的声部
  170. await saveCourseware({
  171. coursewareDetailKnowledgeId: prepareStore.getSelectKey,
  172. lessonCoursewareId: prepareStore.getLessonCoursewareId,
  173. lessonCoursewareDetailId: prepareStore.getLessonCoursewareDetailId,
  174. subjectId: forms.isEdit
  175. ? forms.editSubjectIds
  176. : prepareStore.getSubjectId,
  177. materialList: [...temp]
  178. });
  179. forms.drag = false;
  180. message.success('编辑成功');
  181. forms.removeVisiable = false;
  182. prepareStore.setIsEditResource(false);
  183. // 重置临时删除编号
  184. forms.removeIds = [];
  185. await getList();
  186. } catch {
  187. //
  188. }
  189. };
  190. // 预览上课
  191. const onPreviewAttend = () => {
  192. // 获取上架的数据
  193. let count = 0;
  194. forms.coursewareList.forEach((item: any) => {
  195. if (!item.removeFlag) {
  196. count++;
  197. }
  198. });
  199. if (count <= 0) {
  200. message.error('课件不能为空');
  201. return;
  202. }
  203. // 判断是否在应用里面
  204. if (window.matchMedia('(display-mode: standalone)').matches) {
  205. state.application = window.matchMedia(
  206. '(display-mode: standalone)'
  207. ).matches;
  208. forms.previewModal = true;
  209. fscreen();
  210. forms.previewParams = {
  211. type: 'preview',
  212. subjectId: prepareStore.getSubjectId,
  213. detailId: prepareStore.getSelectKey,
  214. lessonCourseId: prepareStore.getBaseCourseware.id
  215. };
  216. } else {
  217. const { href } = router.resolve({
  218. path: '/attend-class',
  219. query: {
  220. type: 'preview',
  221. subjectId: prepareStore.getSubjectId,
  222. detailId: prepareStore.getSelectKey,
  223. lessonCourseId: prepareStore.getBaseCourseware.id
  224. }
  225. });
  226. window.open(href, +new Date() + '');
  227. }
  228. };
  229. const fscreen = () => {
  230. const el = document.documentElement;
  231. const isFullscreen =
  232. document.fullScreen ||
  233. document.mozFullScreen ||
  234. document.webkitIsFullScreen;
  235. if (!isFullscreen) {
  236. //进入全屏
  237. (el.requestFullscreen && el.requestFullscreen()) ||
  238. (el.mozRequestFullScreen && el.mozRequestFullScreen()) ||
  239. (el.webkitRequestFullscreen && el.webkitRequestFullscreen()) ||
  240. (el.msRequestFullscreen && el.msRequestFullscreen());
  241. }
  242. };
  243. // 单个删除
  244. const onRemove = async (item: any) => {
  245. try {
  246. dialog.warning({
  247. title: '提示',
  248. content: '该资源已下架,是否删除?',
  249. positiveText: '确定',
  250. negativeText: '取消',
  251. onPositiveClick: async () => {
  252. forms.removeIds.push(item.id);
  253. await teacherKnowledgeMaterialDelete({ ids: item.id });
  254. message.success('删除成功');
  255. getList();
  256. }
  257. });
  258. } catch {
  259. //
  260. }
  261. };
  262. watch(
  263. () => prepareStore.getSubjectList,
  264. () => {
  265. checkSubjectIds();
  266. }
  267. );
  268. const checkSubjectIds = () => {
  269. const subjectList = prepareStore.getSubjectList;
  270. // 并且没有声部时才会更新
  271. if (subjectList.length > 0) {
  272. // 判断浏览器上面是否有
  273. const index = subjectList.findIndex(
  274. (subject: any) => subject.id == forms.subjectId
  275. );
  276. // 并且声部在列表中
  277. if (forms.subjectId && index >= 0) {
  278. prepareStore.setSubjectId(forms.subjectId);
  279. } else {
  280. // 判断是否有缓存
  281. prepareStore.setSubjectId(subjectList[0].id);
  282. }
  283. }
  284. };
  285. watch(
  286. () => route.query,
  287. async () => {
  288. forms.className = route.query.name as any;
  289. forms.classGroupId = route.query.classGroupId as any;
  290. forms.subjectId = route.query.subjectId
  291. ? Number(route.query.subjectId)
  292. : null;
  293. checkSubjectIds();
  294. await getList();
  295. }
  296. );
  297. onMounted(async () => {
  298. // 获取教材分类列表
  299. checkSubjectIds();
  300. await getList();
  301. // 动态添加数据
  302. eventGlobal.on('onPrepareAddItem', (item: any) => {
  303. forms.coursewareList.push(item);
  304. prepareStore.setCoursewareList(forms.coursewareList);
  305. forms.isEdit = true;
  306. });
  307. });
  308. return () => (
  309. <div class={styles.coursewareModal}>
  310. <div class={styles.btnGroup}>
  311. {forms.drag ? (
  312. <NSpace>
  313. <span class={styles.tips}>拖动可将资源进行排序哦~</span>
  314. </NSpace>
  315. ) : (
  316. <NSpace>
  317. {forms.classGroupId && (
  318. <div class={styles.btnItem}>
  319. <span class={styles.btnTitle}>上课班级:</span>
  320. <div
  321. onClick={() => {
  322. forms.showAttendClass = true;
  323. forms.attendClassType = 'change';
  324. }}>
  325. <NSelect
  326. placeholder="选择声部"
  327. labelField="name"
  328. valueField="id"
  329. class={styles.btnClassList}
  330. value={forms.className}
  331. disabled
  332. />
  333. </div>
  334. </div>
  335. )}
  336. <div class={styles.btnItem}>
  337. <span class={styles.btnTitle}>声部:</span>
  338. <NSelect
  339. placeholder="选择声部"
  340. class={styles.btnSubjectList}
  341. options={prepareStore.getSubjectList}
  342. labelField="name"
  343. valueField="id"
  344. value={prepareStore.getSubjectId}
  345. onUpdate:value={(val: any) => {
  346. prepareStore.setSubjectId(val);
  347. getList();
  348. }}
  349. />
  350. </div>
  351. </NSpace>
  352. )}
  353. {/* 编辑 */}
  354. {!forms.drag ? (
  355. <NSpace>
  356. <NButton
  357. type="default"
  358. onClick={() => {
  359. forms.drag = true;
  360. prepareStore.setIsEditResource(true);
  361. // forms.subjectSyncVisiable = true;
  362. }}>
  363. 编辑
  364. </NButton>
  365. </NSpace>
  366. ) : (
  367. <NSpace>
  368. <NButton
  369. type="error"
  370. onClick={() => {
  371. forms.removeVisiable1 = true;
  372. }}>
  373. 清空资源
  374. </NButton>
  375. <NButton
  376. type="error"
  377. onClick={() => {
  378. forms.drag = false;
  379. forms.isEdit = false;
  380. prepareStore.setIsEditResource(false);
  381. forms.removeIds = [];
  382. getList();
  383. }}>
  384. 取消编辑
  385. </NButton>
  386. <NButton
  387. type="default"
  388. onClick={() => {
  389. if (forms.isEdit) {
  390. forms.subjectSyncVisiable = true;
  391. } else {
  392. forms.removeVisiable = true;
  393. }
  394. }}>
  395. 完成编辑
  396. </NButton>
  397. </NSpace>
  398. )}
  399. </div>
  400. <NScrollbar
  401. class={[
  402. styles.listContainer,
  403. forms.drag ? styles.listContainerDrag : ''
  404. ]}
  405. {...{ id: 'lessons-2' }}>
  406. <NSpin show={forms.loadingStatus}>
  407. <div
  408. class={[
  409. styles.listSection,
  410. !forms.loadingStatus && forms.coursewareList.length <= 0
  411. ? styles.emptySection
  412. : ''
  413. ]}>
  414. {forms.coursewareList.length > 0 && (
  415. <>
  416. {forms.drag ? (
  417. <Draggable
  418. v-model:modelValue={forms.coursewareList}
  419. itemKey="id"
  420. componentData={{
  421. itemKey: 'id',
  422. tag: 'div',
  423. animation: 200,
  424. group: 'description',
  425. disabled: false
  426. }}
  427. class={styles.list}>
  428. {{
  429. item: (element: any) => {
  430. const item = element.element;
  431. return (
  432. <div
  433. data-id={item.id}
  434. class={[styles.itemBlock, 'row-nav']}>
  435. <CardType
  436. class={[styles.itemContent]}
  437. isShowCollect={false}
  438. offShelf={item.removeFlag ? true : false}
  439. onOffShelf={() => onRemove(item)}
  440. item={item}
  441. />
  442. <div class={styles.itemOperation}>
  443. <img
  444. src={iconDelete}
  445. class={styles.iconDelete}
  446. onClick={(e: MouseEvent) => {
  447. e.stopPropagation();
  448. onDelete(item);
  449. }}
  450. />
  451. </div>
  452. </div>
  453. );
  454. }
  455. }}
  456. </Draggable>
  457. ) : (
  458. <div class={styles.list}>
  459. {forms.coursewareList.map((item: any) => (
  460. <CardType
  461. class={[styles.itemContent, 'handle']}
  462. isShowCollect={false}
  463. item={item}
  464. offShelf={item.removeFlag ? true : false}
  465. onOffShelf={() => onRemove(item)}
  466. disabledMouseHover={false}
  467. onClick={() => {
  468. if (item.type === 'IMG') return;
  469. forms.show = true;
  470. forms.item = item;
  471. }}
  472. />
  473. ))}
  474. </div>
  475. )}
  476. </>
  477. )}
  478. {!forms.loadingStatus && forms.coursewareList.length <= 0 && (
  479. <TheEmpty description="暂无课件" />
  480. )}
  481. </div>
  482. </NSpin>
  483. </NScrollbar>
  484. {!forms.drag ? (
  485. <div class={styles.btnGroup} style={{ justifyContent: 'flex-end' }}>
  486. <NSpace justify="end">
  487. <NButton type="primary" onClick={onPreviewAttend}>
  488. 预览课件
  489. </NButton>
  490. <NButton
  491. {...{ id: 'lessons-3' }}
  492. type="error"
  493. class={styles.btnClassStart}
  494. onClick={async () => {
  495. let count = 0;
  496. forms.coursewareList.forEach((item: any) => {
  497. if (!item.removeFlag) {
  498. count++;
  499. }
  500. });
  501. if (count <= 0) {
  502. message.error('课件不能为空');
  503. return;
  504. }
  505. if (forms.classGroupId) {
  506. // 开始上课
  507. const res = await courseScheduleStart({
  508. lessonCoursewareKnowledgeDetailId: prepareStore.selectKey,
  509. classGroupId: forms.classGroupId
  510. });
  511. if (
  512. window.matchMedia('(display-mode: standalone)').matches
  513. ) {
  514. state.application = window.matchMedia(
  515. '(display-mode: standalone)'
  516. ).matches;
  517. forms.previewModal = true;
  518. fscreen();
  519. forms.previewParams = {
  520. type: 'class',
  521. classGroupId: forms.classGroupId,
  522. subjectId: prepareStore.getSubjectId,
  523. detailId: prepareStore.getSelectKey,
  524. classId: res.data,
  525. lessonCourseId: prepareStore.getBaseCourseware.id
  526. };
  527. } else {
  528. const { href } = router.resolve({
  529. path: '/attend-class',
  530. query: {
  531. type: 'class',
  532. classGroupId: forms.classGroupId,
  533. subjectId: prepareStore.getSubjectId,
  534. detailId: prepareStore.getSelectKey,
  535. classId: res.data,
  536. lessonCourseId: prepareStore.getBaseCourseware.id
  537. }
  538. });
  539. window.open(href, +new Date() + '');
  540. }
  541. } else {
  542. forms.showAttendClass = true;
  543. forms.attendClassType = 'change';
  544. }
  545. }}>
  546. 开始上课
  547. </NButton>
  548. </NSpace>
  549. </div>
  550. ) : (
  551. ''
  552. )}
  553. <NModal
  554. v-model:show={forms.showAttendClass}
  555. preset="card"
  556. showIcon={false}
  557. class={['modalTitle background', styles.attendClassModal]}
  558. title={'选择班级'}
  559. blockScroll={false}>
  560. <AttendClass
  561. onClose={() => (forms.showAttendClass = false)}
  562. type={forms.attendClassType}
  563. onPreview={(item: any) => {
  564. if (window.matchMedia('(display-mode: standalone)').matches) {
  565. state.application = window.matchMedia(
  566. '(display-mode: standalone)'
  567. ).matches;
  568. forms.previewModal = true;
  569. forms.previewParams = {
  570. ...item
  571. };
  572. } else {
  573. const { href } = router.resolve({
  574. path: '/attend-class',
  575. query: {
  576. ...item
  577. }
  578. });
  579. window.open(href, +new Date() + '');
  580. }
  581. }}
  582. onConfirm={async (item: any) => {
  583. if (forms.classGroupId) {
  584. forms.className = item.name;
  585. forms.classGroupId = item.classGroupId;
  586. forms.subjectId = item.subjectId;
  587. forms.showAttendClass = false;
  588. checkSubjectIds();
  589. // 声部切换时
  590. eventGlobal.emit('onChangeClass', {
  591. lastUseCoursewareId: item.lastUseCoursewareId,
  592. unit: item.unit
  593. });
  594. } else {
  595. const res = await courseScheduleStart({
  596. lessonCoursewareKnowledgeDetailId: prepareStore.selectKey,
  597. classGroupId: item.classGroupId
  598. });
  599. forms.showAttendClass = false;
  600. if (window.matchMedia('(display-mode: standalone)').matches) {
  601. state.application = window.matchMedia(
  602. '(display-mode: standalone)'
  603. ).matches;
  604. forms.previewModal = true;
  605. forms.previewParams = {
  606. type: 'class',
  607. classId: res.data, // 上课编号
  608. classGroupId: item.classGroupId,
  609. subjectId: prepareStore.getSubjectId,
  610. detailId: prepareStore.getSelectKey,
  611. lessonCourseId: prepareStore.getBaseCourseware.id
  612. };
  613. setTimeout(() => {
  614. fscreen();
  615. }, 200);
  616. } else {
  617. const { href } = router.resolve({
  618. path: '/attend-class',
  619. query: {
  620. type: 'class',
  621. classId: res.data, // 上课编号
  622. classGroupId: item.classGroupId,
  623. subjectId: prepareStore.getSubjectId,
  624. detailId: prepareStore.getSelectKey,
  625. lessonCourseId: prepareStore.getBaseCourseware.id
  626. }
  627. });
  628. window.open(href, +new Date() + '');
  629. }
  630. }
  631. }}
  632. />
  633. </NModal>
  634. {/* 弹窗查看 */}
  635. <CardPreview v-model:show={forms.show} item={forms.item} />
  636. <NModal
  637. v-model:show={forms.removeVisiable}
  638. preset="card"
  639. class={['modalTitle', styles.removeVisiable]}
  640. title={'提示'}>
  641. <div class={styles.studentRemove}>
  642. <p>是否完成编辑?</p>
  643. <NSpace class={styles.btnGroupModal} justify="center">
  644. <NButton round type="primary" onClick={onOverEdit}>
  645. 确定
  646. </NButton>
  647. <NButton round onClick={() => (forms.removeVisiable = false)}>
  648. 取消
  649. </NButton>
  650. </NSpace>
  651. </div>
  652. </NModal>
  653. <NModal
  654. v-model:show={forms.removeVisiable1}
  655. preset="card"
  656. class={['modalTitle', styles.removeVisiable1]}
  657. title={'清空资源'}>
  658. <div class={styles.studentRemove}>
  659. <p>
  660. 请确认是否要清空资源?
  661. <span>点击确认后所有的素材内容 将被清空掉。</span>
  662. </p>
  663. <NSpace class={styles.btnGroupModal} justify="center">
  664. <NButton
  665. round
  666. type="primary"
  667. onClick={() => {
  668. forms.coursewareList.forEach((item: any) => {
  669. forms.removeIds.push(item.id);
  670. });
  671. forms.coursewareList = [];
  672. forms.removeVisiable1 = false;
  673. forms.isEdit = true;
  674. // prepareStore.setCoursewareList([]);
  675. console.log(prepareStore.getCoursewareList, 'getCourseware1');
  676. }}>
  677. 确定
  678. </NButton>
  679. <NButton round onClick={() => (forms.removeVisiable1 = false)}>
  680. 取消
  681. </NButton>
  682. </NSpace>
  683. </div>
  684. </NModal>
  685. <PreviewWindow
  686. v-model:show={forms.previewModal}
  687. type="attend"
  688. params={forms.previewParams}
  689. />
  690. {/* 完成编辑时,选择声部 */}
  691. <NModal
  692. v-model:show={forms.subjectSyncVisiable}
  693. preset="card"
  694. class={['modalTitle background', styles.subjectSyncModal]}
  695. title={'同步声部'}>
  696. <SubjectSync
  697. subjectId={prepareStore.getSubjectId as any}
  698. onClose={() => (forms.subjectSyncVisiable = false)}
  699. onConfirm={async (subjectIds: any) => {
  700. //
  701. try {
  702. forms.editSubjectIds = subjectIds.join(',');
  703. await onOverEdit();
  704. forms.subjectSyncVisiable = false;
  705. } catch {
  706. //
  707. }
  708. }}
  709. />
  710. </NModal>
  711. </div>
  712. );
  713. }
  714. });