index.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702
  1. import { defineComponent, onMounted, reactive, ref, watch } from 'vue';
  2. import styles from './index.module.less';
  3. import {
  4. NButton,
  5. NCarousel,
  6. NIcon,
  7. NImage,
  8. NInput,
  9. NModal,
  10. NScrollbar,
  11. NSelect,
  12. NSpace,
  13. NSpin,
  14. useMessage
  15. } from 'naive-ui';
  16. import { usePrepareStore } from '/src/store/modules/prepareLessons';
  17. import add from '@/views/studentList/images/add.png';
  18. import iconSlideRight from '../../../images/icon-slide-right.png';
  19. import CoursewareType from '../../../model/courseware-type';
  20. import TheEmpty from '/src/components/TheEmpty';
  21. import RelatedClass from '../../../model/related-class';
  22. import { state } from '/src/state';
  23. import { useResizeObserver } from '@vueuse/core';
  24. import AttendClass from '/src/views/prepare-lessons/model/attend-class';
  25. import {
  26. api_addByOpenCourseware,
  27. api_teacherChapterLessonCoursewareRemove,
  28. api_queryOpenCoursewareByPage,
  29. api_updateCoursewareInfo,
  30. teacherChapterLessonCoursewareList,
  31. courseScheduleStart
  32. } from '../../../api';
  33. import { useRoute, useRouter } from 'vue-router';
  34. import TheMessageDialog from '/src/components/TheMessageDialog';
  35. import { eventGlobal, fscreen } from '/src/utils';
  36. import PreviewWindow from '/src/views/preview-window';
  37. export default defineComponent({
  38. name: 'courseware-presets',
  39. emits: ['change'],
  40. setup(props, { emit }) {
  41. const prepareStore = usePrepareStore();
  42. const message = useMessage();
  43. const route = useRoute();
  44. const router = useRouter();
  45. const localStorageSubjectId = localStorage.getItem(
  46. 'prepareLessonSubjectId'
  47. );
  48. const forms = reactive({
  49. // 选取参数带的,后取缓存
  50. messageLoading: false,
  51. subjectId: route.query.subjectId
  52. ? Number(route.query.subjectId)
  53. : localStorageSubjectId
  54. ? Number(localStorageSubjectId)
  55. : '',
  56. courseScheduleSubjectId: route.query.courseScheduleSubjectId,
  57. classGroupId: route.query.classGroupId,
  58. preStudentNum: route.query.preStudentNum,
  59. bodyWidth: '100%',
  60. loading: false,
  61. openLoading: false,
  62. showRelatedClass: false,
  63. tableList: [] as any,
  64. openTableList: [] as any,
  65. selectItem: {} as any,
  66. editTitleVisiable: false,
  67. editTitle: null,
  68. editBtnLoading: false,
  69. preRemoveVisiable: false,
  70. carouselIndex: 0,
  71. showAttendClass: false,
  72. attendClassType: 'change', //
  73. attendClassItem: {} as any,
  74. previewModal: false,
  75. previewParams: {
  76. type: '',
  77. courseId: '',
  78. subjectId: '',
  79. detailId: ''
  80. } as any
  81. });
  82. const getCoursewareList = async () => {
  83. forms.loading = true;
  84. try {
  85. // 判断是否有选择对应的课件 或声部
  86. if (!prepareStore.getSelectKey) return (forms.loading = false);
  87. const { data } = await teacherChapterLessonCoursewareList({
  88. subjectId: prepareStore.getSubjectId,
  89. coursewareDetailKnowledgeId: prepareStore.getSelectKey
  90. });
  91. if (!Array.isArray(data)) {
  92. return;
  93. }
  94. const tempList: any = [];
  95. data.forEach((item: any) => {
  96. const firstItem: any =
  97. item.chapterKnowledgeList[0]?.chapterKnowledgeMaterialList[0];
  98. tempList.push({
  99. id: item.id,
  100. openFlag: item.openFlag,
  101. openFlagEnable: item.openFlagEnable,
  102. subjectNames: item.subjectNames,
  103. fromChapterLessonCoursewareId: item.fromChapterLessonCoursewareId,
  104. name: item.name,
  105. coverImg: firstItem?.bizInfo.coverImg,
  106. type: firstItem?.bizInfo.type
  107. });
  108. });
  109. forms.tableList = tempList;
  110. } catch {
  111. //
  112. }
  113. forms.loading = false;
  114. };
  115. const getOpenCoursewareList = async () => {
  116. // 查询公开课件列表
  117. forms.openLoading = true;
  118. try {
  119. // 判断是否有选择对应的课件 或声部
  120. if (!prepareStore.getSelectKey) return (forms.openLoading = false);
  121. const { data } = await api_queryOpenCoursewareByPage({
  122. subjectId: prepareStore.getSubjectId,
  123. coursewareDetailKnowledgeId: prepareStore.getSelectKey,
  124. page: 1,
  125. rows: 20
  126. });
  127. const result = data.rows || [];
  128. const tempList: any = [];
  129. result.forEach((item: any) => {
  130. // const index = forms.tableList.findIndex(
  131. // (i: any) => i.fromChapterLessonCoursewareId === item.id
  132. // );
  133. const firstItem: any =
  134. item.chapterKnowledgeList[0]?.chapterKnowledgeMaterialList[0];
  135. tempList.push({
  136. id: item.id,
  137. openFlag: item.openFlag,
  138. openFlagEnable: item.openFlagEnable,
  139. subjectNames: item.subjectNames,
  140. fromChapterLessonCoursewareId: item.fromChapterLessonCoursewareId,
  141. name: item.name,
  142. coverImg: firstItem?.bizInfo.coverImg,
  143. type: firstItem?.bizInfo.type,
  144. isAdd: item.addFlag
  145. });
  146. });
  147. forms.openTableList = chunkArray(tempList, 4);
  148. } catch {
  149. //
  150. }
  151. forms.openLoading = false;
  152. };
  153. const chunkArray = (array: any, size: number) => {
  154. const result = [];
  155. for (let i = 0; i < array.length; i += size) {
  156. result.push(array.slice(i, i + size));
  157. }
  158. return result;
  159. };
  160. // 监听选择的key 左侧选择了其它的课
  161. watch(
  162. () => [prepareStore.getSelectKey, prepareStore.getSubjectId],
  163. async () => {
  164. await getCoursewareList();
  165. await getOpenCoursewareList();
  166. }
  167. );
  168. watch(
  169. () => prepareStore.getSubjectList,
  170. () => {
  171. checkSubjectIds();
  172. }
  173. );
  174. const checkSubjectIds = () => {
  175. const subjectList = prepareStore.getSubjectList;
  176. // 并且没有声部时才会更新
  177. if (subjectList.length > 0) {
  178. const prepareLessonCourseWareSubjectIsNull = sessionStorage.getItem(
  179. 'prepareLessonCourseWareSubjectIsNull'
  180. );
  181. if (prepareLessonCourseWareSubjectIsNull === 'true') {
  182. prepareStore.setSubjectId('');
  183. return;
  184. }
  185. // 并且声部在列表中
  186. const localStorageSubjectId = localStorage.getItem(
  187. 'prepareLessonSubjectId'
  188. );
  189. // // 先取 上次上课声部,在取班级声部 最后取缓存
  190. let subjectId = null;
  191. let index = -1;
  192. if (forms.courseScheduleSubjectId) {
  193. // 判断浏览器上面是否有
  194. index = subjectList.findIndex(
  195. (subject: any) => subject.id == forms.courseScheduleSubjectId
  196. );
  197. if (index >= 0) {
  198. subjectId = Number(forms.courseScheduleSubjectId);
  199. }
  200. }
  201. // 判断班级上面声部 & 还没有声部
  202. if (forms.subjectId && !subjectId) {
  203. // 判断浏览器上面是否有
  204. index = subjectList.findIndex(
  205. (subject: any) => subject.id == forms.subjectId
  206. );
  207. if (index >= 0) {
  208. subjectId = Number(forms.subjectId);
  209. }
  210. }
  211. // 缓存声部 & 还没有声部
  212. if (localStorageSubjectId && !subjectId) {
  213. // 判断浏览器上面是否有
  214. index = subjectList.findIndex(
  215. (subject: any) => subject.id == localStorageSubjectId
  216. );
  217. if (index >= 0) {
  218. subjectId = Number(localStorageSubjectId);
  219. }
  220. }
  221. // 判断是否选择为空
  222. if (subjectId && index >= 0) {
  223. prepareStore.setSubjectId(subjectId);
  224. // forms.subjectId = subjectId;
  225. } else {
  226. // 判断是否有缓存
  227. // prepareStore.setSubjectId(subjectList[0].id);
  228. // forms.subjectId = subjectList[0].id;
  229. }
  230. // 保存
  231. localStorage.setItem(
  232. 'prepareLessonSubjectId',
  233. prepareStore.getSubjectId as any
  234. );
  235. }
  236. };
  237. onMounted(async () => {
  238. prepareStore.setClassGroupId(route.query.classGroupId as any);
  239. if (!prepareStore.getSubjectId) {
  240. // 获取教材分类列表
  241. checkSubjectIds();
  242. }
  243. // useResizeObserver(
  244. // document.querySelector('#coursewarePresets') as HTMLElement,
  245. // (entries: any) => {
  246. // const entry = entries[0];
  247. // const { width } = entry.contentRect;
  248. // forms.bodyWidth = width + 'px';
  249. // }
  250. // );
  251. await getCoursewareList();
  252. await getOpenCoursewareList();
  253. });
  254. // 重命名
  255. const onEditTitleSubmit = async () => {
  256. try {
  257. await api_updateCoursewareInfo({
  258. id: forms.selectItem.id,
  259. name: forms.editTitle
  260. });
  261. message.success('修改成功');
  262. getCoursewareList();
  263. // getOpenCoursewareList()
  264. forms.editTitleVisiable = false;
  265. } catch {
  266. //
  267. }
  268. };
  269. // 删除
  270. const onRemove = async () => {
  271. forms.messageLoading = true;
  272. try {
  273. await api_teacherChapterLessonCoursewareRemove({
  274. id: forms.selectItem.id
  275. });
  276. message.success('删除成功');
  277. getCoursewareList();
  278. getOpenCoursewareList();
  279. forms.preRemoveVisiable = false;
  280. } catch {
  281. //
  282. }
  283. setTimeout(() => {
  284. forms.messageLoading = false;
  285. }, 100);
  286. };
  287. // 添加课件
  288. const onAddCourseware = async (item: any) => {
  289. if (forms.messageLoading) return;
  290. forms.messageLoading = true;
  291. try {
  292. await api_addByOpenCourseware({ id: item.id });
  293. message.success('添加成功');
  294. getCoursewareList();
  295. getOpenCoursewareList();
  296. } catch {
  297. //
  298. }
  299. setTimeout(() => {
  300. forms.messageLoading = false;
  301. }, 100);
  302. };
  303. // 预览上课
  304. const onPreviewAttend = (id: string) => {
  305. // 判断是否在应用里面
  306. if (window.matchMedia('(display-mode: standalone)').matches) {
  307. state.application = window.matchMedia(
  308. '(display-mode: standalone)'
  309. ).matches;
  310. forms.previewModal = true;
  311. fscreen();
  312. forms.previewParams = {
  313. type: 'preview',
  314. courseId: id,
  315. subjectId: prepareStore.getSubjectId,
  316. detailId: prepareStore.getSelectKey,
  317. lessonCourseId: prepareStore.getBaseCourseware.id
  318. };
  319. } else {
  320. const { href } = router.resolve({
  321. path: '/attend-class',
  322. query: {
  323. type: 'preview',
  324. courseId: id,
  325. subjectId: prepareStore.getSubjectId,
  326. detailId: prepareStore.getSelectKey,
  327. lessonCourseId: prepareStore.getBaseCourseware.id
  328. }
  329. });
  330. window.open(href, +new Date() + '');
  331. }
  332. };
  333. const onStartClass = async (item: any, classGroupId: any) => {
  334. if (classGroupId) {
  335. // 开始上课
  336. const res = await courseScheduleStart({
  337. lessonCoursewareKnowledgeDetailId: prepareStore.selectKey,
  338. classGroupId: classGroupId,
  339. useChapterLessonCoursewareId: item.id,
  340. subjectId: prepareStore.getSubjectId
  341. });
  342. if (window.matchMedia('(display-mode: standalone)').matches) {
  343. state.application = window.matchMedia(
  344. '(display-mode: standalone)'
  345. ).matches;
  346. forms.previewModal = true;
  347. fscreen();
  348. forms.previewParams = {
  349. type: 'class',
  350. classGroupId: classGroupId,
  351. courseId: item.id,
  352. subjectId: prepareStore.getSubjectId,
  353. detailId: prepareStore.getSelectKey,
  354. classId: res.data,
  355. lessonCourseId: prepareStore.getBaseCourseware.id,
  356. preStudentNum: forms.preStudentNum
  357. };
  358. } else {
  359. const { href } = router.resolve({
  360. path: '/attend-class',
  361. query: {
  362. type: 'class',
  363. classGroupId: classGroupId,
  364. courseId: item.id,
  365. subjectId: prepareStore.getSubjectId,
  366. detailId: prepareStore.getSelectKey,
  367. classId: res.data,
  368. lessonCourseId: prepareStore.getBaseCourseware.id,
  369. preStudentNum: forms.preStudentNum
  370. }
  371. });
  372. window.open(href, +new Date() + '');
  373. }
  374. } else {
  375. forms.showAttendClass = true;
  376. forms.attendClassType = 'change';
  377. forms.attendClassItem = item;
  378. }
  379. };
  380. const carouselRef = ref();
  381. const onChangeSlide = (type: 'left' | 'right') => {
  382. if (type === 'left') {
  383. carouselRef.value?.prev();
  384. } else if (type === 'right') {
  385. carouselRef.value?.next();
  386. }
  387. };
  388. return () => (
  389. <div class={styles.coursewarePresetsContainer}>
  390. <NScrollbar class={styles.coursewarePresets}>
  391. <div class={styles.title} id="coursewarePresets">
  392. <div class={styles.titleLeft}>
  393. <i class={[styles.icon, styles.iconWork]}></i>
  394. 我的课件
  395. </div>
  396. </div>
  397. <NSpace>
  398. <NSelect
  399. placeholder="选择声部"
  400. class={styles.btnSubjectList}
  401. options={[
  402. { name: '全部声部', id: '' },
  403. ...prepareStore.getSubjectList
  404. ]}
  405. labelField="name"
  406. valueField="id"
  407. value={prepareStore.getSubjectId}
  408. onUpdate:value={(val: any) => {
  409. prepareStore.setSubjectId(val);
  410. // 保存
  411. forms.subjectId = val;
  412. if (!val) {
  413. sessionStorage.setItem(
  414. 'prepareLessonCourseWareSubjectIsNull',
  415. val ? false : true
  416. );
  417. }
  418. }}
  419. />
  420. <NButton
  421. class={styles.addBtn}
  422. type="primary"
  423. onClick={() => {
  424. eventGlobal.emit('teacher-slideshow', true);
  425. emit('change', {
  426. status: true,
  427. type: 'create'
  428. });
  429. }}>
  430. <NImage
  431. class={styles.addBtnIcon}
  432. previewDisabled
  433. src={add}></NImage>
  434. 创建课件
  435. </NButton>
  436. </NSpace>
  437. <div style={{ overflow: 'hidden' }}>
  438. <NSpin show={forms.loading}>
  439. <div class={styles.list}>
  440. {forms.tableList.map((item: any) => (
  441. <div class={[styles.itemWrap, styles.itemBlock, 'row-nav']}>
  442. <div class={styles.itemWrapBox}>
  443. <CoursewareType
  444. operate
  445. isEditName
  446. item={item}
  447. onClick={() => onPreviewAttend(item.id)}
  448. onEditName={() => {
  449. forms.selectItem = item;
  450. forms.editTitle = item.name;
  451. forms.editTitleVisiable = true;
  452. }}
  453. onEdit={() => {
  454. //
  455. eventGlobal.emit('teacher-slideshow', true);
  456. emit('change', {
  457. status: true,
  458. type: 'update',
  459. groupItem: { id: item.id }
  460. });
  461. }}
  462. onStartClass={() =>
  463. onStartClass(item, forms.classGroupId)
  464. }
  465. onDelete={() => {
  466. forms.selectItem = item;
  467. forms.preRemoveVisiable = true;
  468. }}
  469. />
  470. </div>
  471. </div>
  472. ))}
  473. {!forms.loading && forms.tableList.length <= 0 && (
  474. <TheEmpty class={styles.empty1} description="暂无课件" />
  475. )}
  476. </div>
  477. </NSpin>
  478. </div>
  479. {forms.openTableList.length > 0 && (
  480. <>
  481. <div class={[styles.title, styles.line]}>
  482. <div class={styles.titleLeft}>
  483. <i class={[styles.icon, styles.iconCourseware]}></i>
  484. 相关课件
  485. {forms.openTableList.length > 1 && (
  486. <span
  487. class={styles.more}
  488. onClick={() => (forms.showRelatedClass = true)}>
  489. 查看更多
  490. {/* <NIcon size={15}>
  491. <svg
  492. xmlns="http://www.w3.org/2000/svg"
  493. viewBox="0 0 24 24">
  494. <path
  495. d="M8.59 16.59L13.17 12L8.59 7.41L10 6l6 6l-6 6l-1.41-1.41z"
  496. fill="currentColor"></path>
  497. </svg>
  498. </NIcon> */}
  499. <i class={styles.iconP}></i>
  500. </span>
  501. )}
  502. </div>
  503. {forms.openTableList.length > 1 && (
  504. <NSpace class={styles.swipeControll}>
  505. <div onClick={() => onChangeSlide('left')}>
  506. <NImage
  507. previewDisabled
  508. class={[
  509. styles.leftIcon,
  510. forms.carouselIndex === 0 && styles.disabled
  511. ]}
  512. src={iconSlideRight}
  513. />
  514. </div>
  515. <div onClick={() => onChangeSlide('right')}>
  516. <NImage
  517. class={
  518. forms.carouselIndex ==
  519. forms.openTableList.length - 1 && styles.disabled
  520. }
  521. previewDisabled
  522. src={iconSlideRight}
  523. />
  524. </div>
  525. </NSpace>
  526. )}
  527. </div>
  528. <NSpin show={forms.openLoading} class={styles.openLoading}>
  529. <NCarousel
  530. slidesPerView={1}
  531. loop={false}
  532. ref={carouselRef}
  533. // style={{ width: forms.bodyWidth }}
  534. v-model:currentIndex={forms.carouselIndex}>
  535. {forms.openTableList.map((item: any) => (
  536. <div class={[styles.list, styles.listSame]}>
  537. {item.map((child: any) => (
  538. <div
  539. class={[
  540. styles.itemWrap,
  541. styles.itemBlock,
  542. 'row-nav'
  543. ]}>
  544. <div class={styles.itemWrapBox}>
  545. <CoursewareType
  546. isShowOpenFlag={false}
  547. isShowAdd
  548. isHoverShowAdd={false}
  549. item={child}
  550. onClick={() => onPreviewAttend(child.id)}
  551. onAdd={() => onAddCourseware(child)}
  552. />
  553. </div>
  554. </div>
  555. ))}
  556. </div>
  557. ))}
  558. </NCarousel>
  559. </NSpin>
  560. </>
  561. )}
  562. </NScrollbar>
  563. <NModal
  564. v-model:show={forms.showRelatedClass}
  565. preset="card"
  566. showIcon={false}
  567. class={['modalTitle background', styles.attendClassModal1]}
  568. title={'相关课件'}
  569. blockScroll={false}>
  570. <RelatedClass
  571. tableList={forms.tableList}
  572. subjectList={prepareStore.getSubjectList}
  573. subjectId={prepareStore.getSubjectId as any}
  574. coursewareDetailKnowledgeId={prepareStore.getSelectKey}
  575. onClose={() => (forms.showRelatedClass = false)}
  576. onAdd={(item: any) => onAddCourseware(item)}
  577. onClick={(item: any) => {
  578. onPreviewAttend(item.id);
  579. forms.showRelatedClass = false;
  580. }}
  581. />
  582. </NModal>
  583. <NModal
  584. v-model:show={forms.editTitleVisiable}
  585. preset="card"
  586. class={['modalTitle', styles.removeVisiable1]}
  587. title={'课件重命名'}>
  588. <div class={styles.studentRemove}>
  589. <NInput
  590. placeholder="请输入课件名称"
  591. v-model:value={forms.editTitle}
  592. maxlength={15}
  593. onKeyup={(e: any) => {
  594. if (e.code === 'ArrowLeft' || e.code === 'ArrowRight') {
  595. e.stopPropagation();
  596. }
  597. }}
  598. />
  599. <NSpace class={styles.btnGroupModal} justify="center">
  600. <NButton round onClick={() => (forms.editTitleVisiable = false)}>
  601. 取消
  602. </NButton>
  603. <NButton
  604. round
  605. type="primary"
  606. onClick={onEditTitleSubmit}
  607. loading={forms.editBtnLoading}>
  608. 确定
  609. </NButton>
  610. </NSpace>
  611. </div>
  612. </NModal>
  613. <NModal
  614. v-model:show={forms.preRemoveVisiable}
  615. preset="card"
  616. class={['modalTitle', styles.removeVisiable1]}
  617. title={'删除课件'}>
  618. <TheMessageDialog
  619. content={`<p style="text-align: left;">请确认是否删除【${forms.selectItem.name}】,删除后不可恢复</p>`}
  620. cancelButtonText="取消"
  621. confirmButtonText="确认"
  622. loading={forms.messageLoading}
  623. onClose={() => (forms.preRemoveVisiable = false)}
  624. onConfirm={() => onRemove()}
  625. />
  626. </NModal>
  627. {/* 应用内预览或上课 */}
  628. <PreviewWindow
  629. v-model:show={forms.previewModal}
  630. type="attend"
  631. params={forms.previewParams}
  632. />
  633. <NModal
  634. v-model:show={forms.showAttendClass}
  635. preset="card"
  636. showIcon={false}
  637. class={['modalTitle background', styles.attendClassModal]}
  638. title={'选择班级'}
  639. blockScroll={false}>
  640. <AttendClass
  641. onClose={() => (forms.showAttendClass = false)}
  642. type={forms.attendClassType}
  643. onPreview={(item: any) => {
  644. if (window.matchMedia('(display-mode: standalone)').matches) {
  645. state.application = window.matchMedia(
  646. '(display-mode: standalone)'
  647. ).matches;
  648. forms.previewModal = true;
  649. forms.previewParams = {
  650. ...item
  651. };
  652. } else {
  653. const { href } = router.resolve({
  654. path: '/attend-class',
  655. query: {
  656. ...item
  657. }
  658. });
  659. window.open(href, +new Date() + '');
  660. }
  661. }}
  662. onConfirm={async (item: any) => {
  663. onStartClass(forms.attendClassItem, item.classGroupId);
  664. }}
  665. />
  666. </NModal>
  667. </div>
  668. );
  669. }
  670. });