addCourseware.tsx 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. import {
  2. defineComponent,
  3. nextTick,
  4. onMounted,
  5. onUnmounted,
  6. reactive,
  7. ref,
  8. watch
  9. } from 'vue';
  10. import styles from './addCourseware.module.less';
  11. import {
  12. NButton,
  13. NModal,
  14. NScrollbar,
  15. NSpace,
  16. NSpin,
  17. useMessage,
  18. NInput,
  19. NTooltip,
  20. NForm,
  21. NFormItem
  22. } from 'naive-ui';
  23. import CardType from '/src/components/card-type';
  24. import { usePrepareStore } from '/src/store/modules/prepareLessons';
  25. import {
  26. api_teacherChapterLessonCoursewareAdd,
  27. api_teacherChapterLessonCoursewareUpdate,
  28. api_teacherChapterLessonCoursewareDetail,
  29. api_materialDetail
  30. } from '../../../api';
  31. import Draggable from 'vuedraggable';
  32. import iconDelete from '../../../images/icon-delete-default.png';
  33. import iconAddMusic from '../../../images/icon-add-music.png';
  34. import CardPreview from '/src/components/card-preview';
  35. import PreviewWindow from '/src/views/preview-window';
  36. import { eventGlobal } from '/src/utils';
  37. import TheMessageDialog from '/src/components/TheMessageDialog';
  38. import AddItemModel from '../../../model/add-item-model';
  39. import AddOtherSource from '../../../model/add-other-source';
  40. import deepClone from '/src/helpers/deep-clone';
  41. import AddCoursewareProtocol from '../../../model/add-courseware-protocol';
  42. import { useUserStore } from '/src/store/modules/users';
  43. import { modalClickMask } from '/src/state';
  44. export default defineComponent({
  45. name: 'courseware-modal',
  46. props: {
  47. groupItem: {
  48. type: Object,
  49. default: () => ({})
  50. }
  51. },
  52. emits: ['change'],
  53. setup(props, { emit }) {
  54. const userStore = useUserStore();
  55. const prepareStore = usePrepareStore();
  56. const message = useMessage();
  57. const forms = reactive({
  58. subjects: [] as any,
  59. openFlagEnable: true, // 是否支持修改公开状态
  60. autoPlay: false,
  61. name: '',
  62. openFlag: false,
  63. createId: null,
  64. baseCoursewareList: [
  65. {
  66. name: '',
  67. id: null,
  68. list: [] as any
  69. }
  70. ] as any, // 基础数据
  71. baseInfo: {
  72. subjects: [] as any,
  73. autoPlay: false,
  74. name: '',
  75. openFlag: false
  76. }, // 基础数据
  77. coursewareList: [
  78. {
  79. name: '',
  80. id: null,
  81. list: [] as any
  82. }
  83. ] as any,
  84. loadingStatus: false,
  85. dragLoadingStatus: false, // 拖动时
  86. showAttendClass: false,
  87. attendClassType: 'change', //
  88. removeIds: [] as any, // 临时删除的编号
  89. editSubjectIds: '', // 声部编号
  90. addCoursewareVisiable: false,
  91. addCoursewareItem: {} as any,
  92. messageCallBack: null as any,
  93. messageOperation: {
  94. visiable: false,
  95. loading: false, // 是否显示加载
  96. type: 'delete' as 'delete' | 'addItem' | 'save' | 'pageLive' | 'checkInstrument',
  97. contentDirection: 'center' as 'left' | 'center' | 'right',
  98. title: '删除知识点',
  99. content: '请确认是否删除该知识点,删除知识点后将同步删除知识点下的资源',
  100. cancelButtonText: '取消',
  101. confirmButtonText: '确认',
  102. index: 0
  103. },
  104. show: false,
  105. item: {} as any,
  106. previewModal: false,
  107. previewParams: {
  108. type: '',
  109. subjectId: '',
  110. detailId: ''
  111. } as any,
  112. addOtherSource: false,
  113. addOtherIndex: 0 // 添加其它的索引
  114. });
  115. const coursewareListRef = ref();
  116. const showModalMask = ref(false);
  117. // 获取列表
  118. const getList = async () => {
  119. forms.loadingStatus = true;
  120. try {
  121. if (!props.groupItem.id) return (forms.loadingStatus = false);
  122. const { data } = await api_teacherChapterLessonCoursewareDetail(
  123. props.groupItem.id
  124. );
  125. const tempRows = data.chapterKnowledgeList || [];
  126. forms.name = data.name;
  127. forms.subjects = data.instrumentIds
  128. ? data.instrumentIds.split(',').map((s: any) => {
  129. return s;
  130. })
  131. : [];
  132. forms.openFlag = data.openFlag;
  133. forms.openFlagEnable = data.openFlagEnable;
  134. forms.autoPlay = data.autoPlay;
  135. const temp: any = [];
  136. tempRows.forEach((row: any) => {
  137. const child: any = row.chapterKnowledgeMaterialList;
  138. const childList: any[] = [];
  139. if (Array.isArray(child) && child.length > 0) {
  140. child.forEach((sub: any) => {
  141. childList.push({
  142. id: sub.id,
  143. materialId: sub.bizId,
  144. coverImg: sub.bizInfo.coverImg,
  145. type: sub.type,
  146. title: sub.bizInfo.name,
  147. dataJson: sub.dataJson,
  148. instrumentIds: sub.instrumentIds, // 素材编号
  149. hasNotCheck: sub.hasNotCheck,
  150. isError: checkCurrentIsInstrument(sub.instrumentIds, sub.type, sub.hasNotCheck), // 是否异常 当前素材是否在选中的乐器里面
  151. // isCollect: !!sub.favoriteFlag,
  152. isSelected: sub.source === 'PLATFORM' ? true : false,
  153. audioPlayTypeArray: sub.audioPlayTypes
  154. ? sub.audioPlayTypes.split(',')
  155. : [],
  156. content: sub.bizInfo.content,
  157. removeFlag: sub.removeFlag
  158. });
  159. });
  160. }
  161. temp.push({
  162. name: row.name,
  163. id: row.id,
  164. list: [...childList]
  165. });
  166. });
  167. forms.coursewareList = temp;
  168. forms.baseCoursewareList = deepClone(temp);
  169. forms.baseInfo = deepClone({
  170. subjects: forms.subjects,
  171. autoPlay: forms.autoPlay,
  172. name: forms.name,
  173. openFlag: forms.openFlag
  174. });
  175. /** 给头部组件分发消息 */
  176. eventGlobal.emit('updateCoursewareHeadInfo', {
  177. name: forms.name,
  178. subjects: forms.subjects,
  179. openFlag: forms.openFlag,
  180. openFlagEnable: forms.openFlagEnable,
  181. autoPlay: forms.autoPlay
  182. });
  183. } catch (e) {
  184. //
  185. console.log(e);
  186. }
  187. forms.loadingStatus = false;
  188. };
  189. /** 检测当前素材是否包含所选声部 */
  190. const checkCurrentIsInstrument = (instruments: string, type: string, hasNotCheck = false) => {
  191. // 当前素材是否在选中的乐器里面
  192. let isError = false
  193. if(forms.subjects.length <= 0) {
  194. return true
  195. }
  196. // 过滤一些不做校验的素材
  197. const checkType = ['IMG', 'VIDEO', 'SONG', 'MUSIC', 'PPT', 'LISTEN']
  198. if(!checkType.includes(type)) {
  199. return false
  200. }
  201. forms.subjects.forEach((item: any) => {
  202. if(!instruments?.includes(item) && !hasNotCheck) {
  203. isError = true
  204. }
  205. })
  206. return isError
  207. }
  208. /** 检测之后的提示 */
  209. const checkCurrentInstrumentTip = (isError = false) => {
  210. if(isError) {
  211. message.error('您添加的资源与适用乐器不符')
  212. } else {
  213. message.success('添加成功');
  214. }
  215. }
  216. // 删除
  217. const onDelete = (j: number, index: number) => {
  218. const coursewareItem = forms.coursewareList[index];
  219. if (!coursewareItem) return;
  220. coursewareItem.list.splice(j, 1);
  221. // 内容有更新 - 相关资源会刷新
  222. eventGlobal.emit('onCoursewareUpdate');
  223. };
  224. const isPointInsideElement = (element: any, x: number, y: number) => {
  225. const rect = element.getBoundingClientRect();
  226. return (
  227. x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom
  228. );
  229. };
  230. const isPointOnLeft = (element: any, x: number) => {
  231. const rect = element.getBoundingClientRect();
  232. const elementCenterX = rect.left + rect.width / 2;
  233. return x < elementCenterX;
  234. };
  235. // 操作
  236. const onChangePoint = (type: string, index: number, item?: any) => {
  237. if (type === 'up') {
  238. // 向上移动
  239. if (index === 0) return;
  240. const temp = forms.coursewareList[index - 1];
  241. forms.coursewareList[index - 1] = forms.coursewareList[index];
  242. forms.coursewareList[index] = temp;
  243. } else if (type === 'down') {
  244. // 向下移动
  245. if (index >= forms.coursewareList.length - 1) return;
  246. const temp = forms.coursewareList[index + 1];
  247. forms.coursewareList[index + 1] = forms.coursewareList[index];
  248. forms.coursewareList[index] = temp;
  249. } else if (type === 'remove') {
  250. forms.messageOperation = {
  251. visiable: true,
  252. type: 'delete',
  253. contentDirection: 'left',
  254. title: '删除知识点',
  255. loading: false,
  256. content: `请确认是否删除${
  257. item.name ? '【' + item.name + '】' : '该知识点'
  258. },删除知识点后将同步删除知识点下的资源`,
  259. cancelButtonText: '取消',
  260. confirmButtonText: '确认',
  261. index
  262. };
  263. }
  264. };
  265. //
  266. const onMessageConfirm = async () => {
  267. const type = forms.messageOperation.type;
  268. if (type === 'delete') {
  269. forms.coursewareList.splice(forms.messageOperation.index, 1);
  270. eventGlobal.emit('onCoursewareUpdate');
  271. } else if (type === 'addItem') {
  272. forms.coursewareList.push({ name: '', list: [] });
  273. addCoursewareItem(forms.addCoursewareItem);
  274. } else if (type === 'save' || type === 'pageLive' || type === "checkInstrument") {
  275. if (forms.messageOperation.loading) return;
  276. if (!forms.name) {
  277. message.error('请输入课件名称');
  278. forms.messageOperation.visiable = false;
  279. return;
  280. }
  281. if (forms.subjects.length <= 0) {
  282. message.error('请选择适用乐器');
  283. forms.messageOperation.visiable = false;
  284. return;
  285. }
  286. if (forms.coursewareList.length <= 0) {
  287. message.error('未配置知识点');
  288. forms.messageOperation.visiable = false;
  289. return;
  290. }
  291. let isNotAdd = false;
  292. for (const item of forms.coursewareList) {
  293. if (!item.name) {
  294. message.error('请输入知识点名称');
  295. forms.messageOperation.visiable = false;
  296. return;
  297. }
  298. if (Array.isArray(item.list) && item.list.length <= 0) {
  299. isNotAdd = true;
  300. }
  301. }
  302. if (isNotAdd) {
  303. message.error('请至少添加一个资源');
  304. forms.messageOperation.visiable = false;
  305. return;
  306. }
  307. // 检测是否有异常资源
  308. if(checkInstrumentIdsSubmit() && type !== 'checkInstrument') return
  309. forms.messageOperation.loading = true;
  310. const resultStatus = await onSaveCourseWare();
  311. forms.messageOperation.loading = false;
  312. if (resultStatus) {
  313. if (
  314. (type === 'pageLive' || type === 'checkInstrument') &&
  315. typeof forms.messageCallBack === 'function'
  316. ) {
  317. forms.messageCallBack();
  318. }
  319. onCancelSave()
  320. }
  321. }
  322. forms.messageOperation.visiable = false;
  323. };
  324. let timer: any = null;
  325. const addCoursewareItem = async (
  326. item: any,
  327. point?: any,
  328. insert = false
  329. ) => {
  330. clearTimeout(timer);
  331. const materialList: any[] = [];
  332. if (!insert) {
  333. try {
  334. const { data } = await api_materialDetail(item.materialId);
  335. if (Array.isArray(data.materialRefs)) {
  336. data.materialRefs.forEach((item: any) => {
  337. if (item.refType === 'STRONG') {
  338. const relateMaterialInfo = item.relateMaterialInfo || {};
  339. materialList.push({
  340. content: relateMaterialInfo.content,
  341. coverImg: relateMaterialInfo.coverImg,
  342. // isCollect: relateMaterialInfo.,
  343. isSelected:
  344. relateMaterialInfo.sourceFrom === 'PLATFORM' ? true : false,
  345. instrumentIds: relateMaterialInfo.instrumentIds,
  346. materialId: relateMaterialInfo.id,
  347. isError: checkCurrentIsInstrument(relateMaterialInfo.instrumentIds, relateMaterialInfo.type),
  348. // removeFlag: relateMaterialInfo.,
  349. title: relateMaterialInfo.name,
  350. type: relateMaterialInfo.type
  351. });
  352. }
  353. });
  354. }
  355. } catch {
  356. //
  357. }
  358. }
  359. nextTick(() => {
  360. if (point) {
  361. const rowGroupDom = document.querySelectorAll('.row-group');
  362. const dom = rowGroupDom[item.index].querySelectorAll('.row-nav');
  363. // const dom = document.querySelectorAll('.row-nav');
  364. let isAdd = false;
  365. dom.forEach((child: any, index: number) => {
  366. // console.log(child);
  367. const status = isPointInsideElement(child, point.x, point.y);
  368. if (status) {
  369. const array: any =
  370. forms.coursewareList[item.index || 0].list || [];
  371. const left = isPointOnLeft(child, point.x);
  372. if (!left) {
  373. array.splice(index + 1, 0, item);
  374. materialList.forEach((m: any) => {
  375. array.splice(index + 1, 0, m);
  376. });
  377. } else {
  378. materialList.forEach((m: any) => {
  379. array.splice(index, 0, m);
  380. });
  381. array.splice(index, 0, item);
  382. }
  383. isAdd = true;
  384. forms.coursewareList[item.index || 0].list = array;
  385. }
  386. });
  387. if (!isAdd) {
  388. forms.coursewareList[item.index || 0].list.push(item);
  389. materialList.forEach((m: any) => {
  390. forms.coursewareList[item.index || 0].list.push(m);
  391. });
  392. }
  393. } else {
  394. forms.coursewareList[item.index || 0].list.push(item);
  395. materialList.forEach((m: any) => {
  396. forms.coursewareList[item.index || 0].list.push(m);
  397. });
  398. }
  399. timer = setTimeout(() => {
  400. // 内容有更新 - 相关资源会刷新
  401. eventGlobal.emit('onCoursewareUpdate');
  402. }, 100);
  403. });
  404. };
  405. // 拖拽添加数据
  406. const addDragCoursewareItem = async (item: any, newIndex: number) => {
  407. clearTimeout(timer);
  408. forms.dragLoadingStatus = true;
  409. const materialList: any[] = [];
  410. try {
  411. const { data } = await api_materialDetail(item.materialId);
  412. if (Array.isArray(data.materialRefs)) {
  413. data.materialRefs.forEach((item: any) => {
  414. if (item.refType === 'STRONG') {
  415. const relateMaterialInfo = item.relateMaterialInfo || {};
  416. materialList.push({
  417. content: relateMaterialInfo.content,
  418. coverImg: relateMaterialInfo.coverImg,
  419. isSelected:
  420. relateMaterialInfo.sourceFrom === 'PLATFORM' ? true : false,
  421. instrumentIds: relateMaterialInfo.instrumentIds,
  422. isError: checkCurrentIsInstrument(relateMaterialInfo.instrumentIds, relateMaterialInfo.type),
  423. materialId: relateMaterialInfo.id,
  424. title: relateMaterialInfo.name,
  425. type: relateMaterialInfo.type
  426. });
  427. }
  428. });
  429. }
  430. } catch {
  431. //
  432. }
  433. nextTick(() => {
  434. const array: any = forms.coursewareList[item.index || 0].list || [];
  435. array[newIndex] = item;
  436. materialList.forEach((m: any) => {
  437. array.splice(newIndex + 1, 0, m);
  438. });
  439. forms.coursewareList[item.index || 0].list = array;
  440. if(item.isError) {
  441. message.error('您添加的资源与适用乐器不符')
  442. }
  443. timer = setTimeout(() => {
  444. // 内容有更新 - 相关资源会刷新
  445. eventGlobal.emit('onCoursewareUpdate');
  446. }, 100);
  447. forms.dragLoadingStatus = false;
  448. });
  449. };
  450. /** 取消保存时处理 */
  451. const onCancelSave = () => {
  452. emit('change', {
  453. status: false,
  454. addParam: {
  455. isAdd: false,
  456. name: forms.name,
  457. id: forms.createId
  458. }
  459. });
  460. eventGlobal.emit('teacher-slideshow', false);
  461. }
  462. /** 提交数据前做拦截提示 */
  463. const checkInstrumentIdsSubmit = () => {
  464. let status = false
  465. // 修改声部后重新检测状态
  466. forms.coursewareList.forEach((item: any) => {
  467. const childList = item.list || []
  468. childList.forEach((child: any) => {
  469. if(child.isError) {
  470. status = true
  471. }
  472. })
  473. })
  474. if(status) {
  475. forms.messageOperation = {
  476. visiable: true,
  477. type: 'checkInstrument',
  478. loading: false,
  479. contentDirection: 'center',
  480. title: '温馨提示',
  481. content: '课件中含有不符合适用乐器的资源,是否继续保存?',
  482. cancelButtonText: '取消',
  483. confirmButtonText: '继续保存',
  484. index: 0
  485. };
  486. }
  487. return status
  488. }
  489. // 提交
  490. const onSubmit = async () => {
  491. try {
  492. coursewareListRef.value.validate();
  493. eventGlobal.emit('checkCoursewareForm');
  494. if (!forms.name) {
  495. message.error('请输入课件名称');
  496. return;
  497. }
  498. if (forms.subjects.length <= 0) {
  499. message.error('请选择适用乐器');
  500. return;
  501. }
  502. if (forms.coursewareList.length <= 0) {
  503. message.error('请至少添加一个知识点');
  504. return;
  505. }
  506. let isNotAdd = false;
  507. for (const item of forms.coursewareList) {
  508. if (!item.name) {
  509. message.error('请输入知识点名称');
  510. return;
  511. }
  512. if (Array.isArray(item.list) && item.list.length <= 0) {
  513. isNotAdd = true;
  514. }
  515. }
  516. if (isNotAdd) {
  517. message.error('请至少添加一个资源');
  518. return;
  519. }
  520. // 检测是否有异常资源
  521. if(checkInstrumentIdsSubmit()) return
  522. if (forms.openFlag && !userStore.getReadCoursewareOpenAgreement) {
  523. showModalMask.value = true;
  524. return;
  525. }
  526. const resultStatus = await onSaveCourseWare();
  527. if (resultStatus) {
  528. onCancelSave()
  529. }
  530. } catch {
  531. //
  532. }
  533. };
  534. const onSaveCourseWare = async () => {
  535. try {
  536. const params = {
  537. name: forms.name,
  538. instrumentIds: forms.subjects.join(','),
  539. openFlag: forms.openFlag,
  540. autoPlay: forms.autoPlay,
  541. coursewareDetailKnowledgeId: prepareStore.getSelectKey,
  542. chapterKnowledgeList: [] as any
  543. };
  544. forms.coursewareList.forEach((item: any) => {
  545. let tempItem: any = [];
  546. if (Array.isArray(item.list) && item.list.length > 0) {
  547. tempItem = item.list.map((child: any) => {
  548. return {
  549. bizId: child.materialId,
  550. type: child.type,
  551. dataJson: !['IMG', 'VIDEO', 'SONG', 'MUSIC', 'PPT'].includes(
  552. child.type
  553. )
  554. ? JSON.stringify({
  555. setting: child.dataJson,
  556. coverImg: child.coverImg,
  557. bizId: child.bizId,
  558. content: child.content,
  559. name: child.title
  560. })
  561. : ''
  562. };
  563. });
  564. }
  565. params.chapterKnowledgeList.push({
  566. name: item.name,
  567. chapterKnowledgeMaterialList: tempItem
  568. });
  569. });
  570. if (props.groupItem?.id) {
  571. await api_teacherChapterLessonCoursewareUpdate({
  572. id: props.groupItem.id,
  573. ...params
  574. });
  575. message.success('保存成功');
  576. } else {
  577. const { data } = await api_teacherChapterLessonCoursewareAdd(params);
  578. forms.createId = data.id;
  579. }
  580. return true;
  581. } catch {
  582. //
  583. return false;
  584. }
  585. };
  586. const addItem = (item: any, point?: any) => {
  587. if(forms.subjects.length <= 0) {
  588. message.error('请先选择适用乐器')
  589. eventGlobal.emit('checkCoursewareForm', 'subject')
  590. return
  591. }
  592. item.isError = checkCurrentIsInstrument(item.instrumentIds, item.type, item.hasNotCheck) // 是否异常
  593. if (forms.coursewareList.length <= 0) {
  594. // 添加到临时对象
  595. forms.addCoursewareItem = item;
  596. forms.messageOperation = {
  597. visiable: true,
  598. type: 'addItem',
  599. contentDirection: 'center',
  600. title: '添加到知识点',
  601. loading: false,
  602. content: '当前课件暂无知识点,请添加知识点后操作',
  603. cancelButtonText: '取消',
  604. confirmButtonText: '添加知识点',
  605. index: 0
  606. };
  607. } else if (forms.coursewareList.length > 1 && item.addType !== 'drag') {
  608. forms.addCoursewareVisiable = true;
  609. forms.addCoursewareItem = item;
  610. } else {
  611. addCoursewareItem(item, point);
  612. checkCurrentInstrumentTip(item.isError)
  613. }
  614. };
  615. // 当页面离开时
  616. const onPageBeforeLeave = (event: any) => {
  617. const objA = JSON.stringify(forms.coursewareList);
  618. const objB = JSON.stringify(forms.baseCoursewareList);
  619. const baseA = JSON.stringify({
  620. subjects: forms.subjects,
  621. autoPlay: forms.autoPlay,
  622. name: forms.name,
  623. openFlag: forms.openFlag
  624. });
  625. const baseB = JSON.stringify(forms.baseInfo);
  626. if (objA === objB && baseA === baseB) {
  627. if (typeof event == 'function') {
  628. event();
  629. onCancelSave()
  630. }
  631. } else {
  632. forms.messageCallBack = event;
  633. forms.messageOperation = {
  634. visiable: true,
  635. type: 'pageLive',
  636. loading: false,
  637. contentDirection: 'center',
  638. title: '保存课件',
  639. content: '当前课件暂未保存,是否保存?',
  640. cancelButtonText: '不保存',
  641. confirmButtonText: '保存',
  642. index: 0
  643. };
  644. }
  645. };
  646. const onCancelCourseware = (item: any) => {
  647. forms.subjects = item.subjects;
  648. forms.openFlagEnable = item.openFlagEnable;
  649. forms.autoPlay = item.autoPlay;
  650. forms.name = item.name;
  651. forms.openFlag = item.openFlag;
  652. const objA = JSON.stringify(forms.coursewareList);
  653. const objB = JSON.stringify(forms.baseCoursewareList);
  654. const baseA = JSON.stringify({
  655. subjects: forms.subjects,
  656. autoPlay: forms.autoPlay,
  657. name: forms.name,
  658. openFlag: forms.openFlag
  659. });
  660. const baseB = JSON.stringify(forms.baseInfo);
  661. if (objA === objB && baseA === baseB) {
  662. onCancelSave()
  663. } else {
  664. forms.messageOperation = {
  665. visiable: true,
  666. type: 'save',
  667. loading: false,
  668. contentDirection: 'center',
  669. title: '保存课件',
  670. content: '当前课件暂未保存,是否保存?',
  671. cancelButtonText: '不保存',
  672. confirmButtonText: '保存',
  673. index: 0
  674. };
  675. }
  676. };
  677. const onSubmitCourseware = (item: any) => {
  678. forms.subjects = item.subjects;
  679. forms.openFlagEnable = item.openFlagEnable;
  680. forms.autoPlay = item.autoPlay;
  681. forms.name = item.name;
  682. forms.openFlag = item.openFlag;
  683. onSubmit();
  684. };
  685. const syncLeftFormData = (item: any) => {
  686. forms.subjects = item.subjects;
  687. forms.openFlagEnable = item.openFlagEnable;
  688. forms.autoPlay = item.autoPlay;
  689. forms.name = item.name;
  690. forms.openFlag = item.openFlag;
  691. }
  692. // 声部变化时
  693. const onCourseWareSubjectChange = (subjects: any) => {
  694. forms.subjects = subjects
  695. let isTips = false
  696. // 修改声部后重新检测状态
  697. forms.coursewareList.forEach((item: any) => {
  698. const childList = item.list || []
  699. childList.forEach((child: any) => {
  700. child.isError = checkCurrentIsInstrument(child.instrumentIds, child.type, child.hasNotCheck)
  701. if(child.isError) {
  702. isTips = true
  703. }
  704. })
  705. })
  706. if(isTips) {
  707. message.error('您添加的资源与适用乐器不符')
  708. }
  709. }
  710. onMounted(async () => {
  711. // 修改时重置默认数据
  712. if (props.groupItem?.id) {
  713. forms.coursewareList = [];
  714. forms.baseCoursewareList = [];
  715. }
  716. await getList();
  717. // 动态添加数据
  718. eventGlobal.on('onPrepareAddItem', addItem);
  719. eventGlobal.on('pageBeforeLeave', onPageBeforeLeave);
  720. // 选择乐器改变时
  721. eventGlobal.on('coursewareSubjectChange', onCourseWareSubjectChange)
  722. // 取消
  723. eventGlobal.on('coursewareClosed', onCancelCourseware);
  724. // 保存
  725. eventGlobal.on('coursewareSave', onSubmitCourseware);
  726. eventGlobal.on('coursewareHeadSyncData', syncLeftFormData);
  727. });
  728. onUnmounted(() => {
  729. eventGlobal.off('onPrepareAddItem', addItem);
  730. eventGlobal.off('pageBeforeLeave', onPageBeforeLeave);
  731. eventGlobal.off('coursewareSubjectChange', onCourseWareSubjectChange)
  732. eventGlobal.off('coursewareClosed', onCancelCourseware);
  733. eventGlobal.off('coursewareSave', onSubmitCourseware);
  734. eventGlobal.off('coursewareHeadSyncData', syncLeftFormData);
  735. });
  736. // 当列表数据更新时同步缓存数据
  737. watch(
  738. () => forms.coursewareList,
  739. () => {
  740. prepareStore.setCoursewareList(forms.coursewareList);
  741. },
  742. {
  743. deep: true
  744. }
  745. );
  746. return () => (
  747. <NForm
  748. class={styles.coursewareModal}
  749. model={forms}
  750. ref={coursewareListRef}>
  751. <NScrollbar
  752. class={[styles.listContainer, 'listContainerWrap']}
  753. {...{ id: 'lessonsIn-1' }}>
  754. <NSpin show={forms.loadingStatus}>
  755. <div
  756. class={[styles.listSection, 'listSectionWrap']}
  757. id="listSectionWrap">
  758. {forms.coursewareList.map((item: any, index: number) => (
  759. <div
  760. class={[styles.listItems, 'row-group']}
  761. >
  762. <div class={styles.knowledgePoint}>
  763. <NFormItem
  764. class={styles.btnItem}
  765. label="知识点名称"
  766. showFeedback={false}
  767. requireMarkPlacement="left"
  768. path={`coursewareList.${index}.name`}
  769. rule={[
  770. {
  771. required: true,
  772. trigger: ['input', 'blur']
  773. }
  774. ]}>
  775. <NInput
  776. placeholder="未命名知识点"
  777. v-model:value={item.name}
  778. maxlength={15}
  779. clearable
  780. />
  781. </NFormItem>
  782. </div>
  783. <NSpace class={styles.operationGroup}>
  784. {index > 0 && (
  785. <NTooltip showArrow={false}>
  786. {{
  787. trigger: () => (
  788. <i
  789. class={styles.iconCUp}
  790. onClick={() => onChangePoint('up', index)}></i>
  791. ),
  792. default: () => '上移知识点'
  793. }}
  794. </NTooltip>
  795. )}
  796. {index < forms.coursewareList.length - 1 && (
  797. <NTooltip showArrow={false}>
  798. {{
  799. trigger: () => (
  800. <i
  801. class={styles.iconCDown}
  802. onClick={() => onChangePoint('down', index)}></i>
  803. ),
  804. default: () => '下移知识点'
  805. }}
  806. </NTooltip>
  807. )}
  808. <NTooltip showArrow={false}>
  809. {{
  810. trigger: () => (
  811. <i
  812. class={styles.iconCRemove}
  813. onClick={() =>
  814. onChangePoint('remove', index, item)
  815. }></i>
  816. ),
  817. default: () => '删除知识点'
  818. }}
  819. </NTooltip>
  820. </NSpace>
  821. {/* {item.list.length > 0 && ( */}
  822. <Draggable
  823. v-model:modelValue={item.list}
  824. itemKey="id"
  825. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  826. // @ts-ignore
  827. group="description"
  828. scroll={true}
  829. animation={200}
  830. onAdd={async (evt: any) => {
  831. // 锁
  832. if(forms.dragLoadingStatus) return
  833. const list = forms.coursewareList[index].list;
  834. const dropItem = list[evt.newDraggableIndex];
  835. if (dropItem.sourceForm === 'resource-item') {
  836. if(forms.subjects.length <= 0) {
  837. list.splice(evt.newDraggableIndex, 1)
  838. message.error('请先选择适用乐器')
  839. eventGlobal.emit('checkCoursewareForm', 'subject')
  840. return
  841. }
  842. await addDragCoursewareItem(
  843. {
  844. materialId: dropItem.id,
  845. coverImg: dropItem.coverImg,
  846. type: dropItem.type,
  847. title: dropItem.title,
  848. refFlag: dropItem.refFlag,
  849. isCollect: dropItem.isCollect,
  850. isSelected: dropItem.isSelected,
  851. hasNotCheck: dropItem.hasNotCheck,
  852. isError: checkCurrentIsInstrument(dropItem.instrumentIds, dropItem.type, dropItem.hasNotCheck), // 是否异常
  853. content: dropItem.content,
  854. audioPlayTypeArray: dropItem.audioPlayTypeArray,
  855. removeFlag: false,
  856. index
  857. },
  858. evt.newDraggableIndex
  859. );
  860. }
  861. }}
  862. onDrag={(event: any) => {
  863. // 修复滚动超出范围
  864. const container: any = document.querySelector(
  865. '.listContainerWrap .n-scrollbar-container'
  866. );
  867. const sensitivity = 150; // 自定义灵敏度
  868. if (event.clientY < sensitivity) {
  869. container.scrollTop -= 8;
  870. } else if (
  871. window.innerHeight - event.clientY <
  872. sensitivity
  873. ) {
  874. container.scrollTop += 8;
  875. }
  876. }}
  877. componentData={{
  878. draggable: 'row-nav',
  879. // scroll: false,
  880. itemKey: 'id',
  881. tag: 'div',
  882. animation: 200,
  883. // pull: true,
  884. // put: true,
  885. group: 'description'
  886. // disabled: false
  887. }}
  888. class={styles.list}>
  889. {{
  890. item: (element: any) => {
  891. const item = element.element;
  892. return (
  893. <div
  894. data-id={item.id}
  895. class={[
  896. styles.itemWrap,
  897. styles.itemBlock,
  898. 'row-nav'
  899. ]}>
  900. <div class={styles.itemWrapBox}>
  901. <CardType
  902. class={[styles.itemContent]}
  903. isError={item.isError}
  904. isShowCollect={false}
  905. offShelf={item.removeFlag ? true : false}
  906. // onOffShelf={() => onRemove(item)}
  907. item={item}
  908. audioPlayTypeSize="small"
  909. disabledMouseHover={false}
  910. onClick={() => {
  911. if (item.type === 'IMG') return;
  912. forms.show = true;
  913. forms.item = item;
  914. }}
  915. />
  916. <div class={styles.itemOperation}>
  917. <img
  918. src={iconDelete}
  919. class={styles.iconDelete}
  920. onClick={(e: MouseEvent) => {
  921. e.stopPropagation();
  922. onDelete(element.index, index);
  923. }}
  924. />
  925. </div>
  926. </div>
  927. </div>
  928. );
  929. },
  930. footer: () => (
  931. <div class={styles.itemWrap}>
  932. <div class={styles.itemWrapBox}>
  933. <div
  934. class={[
  935. styles.itemContent,
  936. styles.addMusicItem,
  937. 'handle'
  938. ]}
  939. onClick={() => {
  940. if(forms.subjects.length <= 0) {
  941. message.error('请先选择适用乐器')
  942. eventGlobal.emit('checkCoursewareForm', 'subject')
  943. return
  944. }
  945. forms.addOtherSource = true;
  946. forms.addOtherIndex = index;
  947. }}>
  948. <img src={iconAddMusic} />
  949. <p class={styles.addMusicName}>添加资源</p>
  950. </div>
  951. </div>
  952. </div>
  953. )
  954. }}
  955. </Draggable>
  956. </div>
  957. ))}
  958. {!forms.loadingStatus && (
  959. <NButton
  960. block
  961. type="primary"
  962. secondary
  963. class={styles.addKnowledgePoint}
  964. onClick={() => {
  965. forms.coursewareList.push({
  966. name: '',
  967. list: []
  968. });
  969. }}>
  970. <i class={styles.iconCAdd}></i>
  971. 添加知识点
  972. </NButton>
  973. )}
  974. </div>
  975. </NSpin>
  976. </NScrollbar>
  977. {/* 弹窗查看 */}
  978. <CardPreview
  979. size={
  980. [
  981. 'INSTRUMENT',
  982. 'THEORY',
  983. 'MUSIC_WIKI',
  984. 'MUSICIAN',
  985. 'RHYTHM'
  986. ].includes(forms.item.type)
  987. ? 'large'
  988. : ''
  989. }
  990. v-model:show={forms.show}
  991. item={forms.item}
  992. isDownload={false}
  993. />
  994. <NModal
  995. maskClosable={modalClickMask}
  996. v-model:show={forms.addCoursewareVisiable}
  997. preset="card"
  998. class={['modalTitle', styles.addCourseware]}
  999. title={'添加到知识点'}>
  1000. <AddItemModel
  1001. coursewareList={forms.coursewareList}
  1002. onClose={() => (forms.addCoursewareVisiable = false)}
  1003. onConfirm={(selects: number[]) => {
  1004. if (Array.isArray(selects) && selects.length > 0) {
  1005. selects.forEach(select => {
  1006. addCoursewareItem({
  1007. ...forms.addCoursewareItem,
  1008. index: select
  1009. });
  1010. });
  1011. forms.addCoursewareVisiable = false;
  1012. checkCurrentInstrumentTip(forms.addCoursewareItem.isError)
  1013. } else {
  1014. message.error('请选择需要添加的知识点');
  1015. }
  1016. }}
  1017. />
  1018. </NModal>
  1019. <NModal
  1020. maskClosable={modalClickMask}
  1021. v-model:show={forms.messageOperation.visiable}
  1022. preset="card"
  1023. class={['modalTitle', styles.removeVisiable1]}
  1024. title={forms.messageOperation.title}>
  1025. <TheMessageDialog
  1026. content={forms.messageOperation.content}
  1027. contentDirection={forms.messageOperation.contentDirection}
  1028. cancelButtonText={forms.messageOperation.cancelButtonText}
  1029. confirmButtonText={forms.messageOperation.confirmButtonText}
  1030. loading={forms.messageOperation.loading}
  1031. onClose={() => {
  1032. forms.messageOperation.visiable = false;
  1033. if (
  1034. forms.messageOperation.type === 'save' ||
  1035. forms.messageOperation.type === 'pageLive'
  1036. ) {
  1037. onCancelSave()
  1038. if (
  1039. forms.messageOperation.type === 'pageLive' &&
  1040. typeof forms.messageCallBack === 'function'
  1041. ) {
  1042. forms.messageCallBack();
  1043. }
  1044. }
  1045. forms.messageCallBack = null;
  1046. }}
  1047. onConfirm={() => onMessageConfirm()}
  1048. />
  1049. </NModal>
  1050. <PreviewWindow
  1051. v-model:show={forms.previewModal}
  1052. type="attend"
  1053. params={forms.previewParams}
  1054. />
  1055. {/* 添加其它类型的资源 */}
  1056. <NModal
  1057. maskClosable={modalClickMask}
  1058. v-model:show={forms.addOtherSource}
  1059. preset="card"
  1060. class={['modalTitle background', styles.addOtherSource]}
  1061. title={'添加资源'}>
  1062. <AddOtherSource
  1063. onClose={() => (forms.addOtherSource = false)}
  1064. onComfirm={item => {
  1065. if (Array.isArray(item)) {
  1066. let isTips = false
  1067. item.forEach(async (child: any) => {
  1068. child.isError = checkCurrentIsInstrument(child.instrumentIds, child.type)
  1069. forms.coursewareList[forms.addOtherIndex || 0].list.push(child);
  1070. if(child.isError) {
  1071. isTips = true
  1072. }
  1073. });
  1074. checkCurrentInstrumentTip(isTips);
  1075. } else {
  1076. item.isError = checkCurrentIsInstrument(item.instrumentIds, item.type)
  1077. item.index = forms.addOtherIndex;
  1078. forms.coursewareList[item.index || 0].list.push(item);
  1079. checkCurrentInstrumentTip(item.isError);
  1080. }
  1081. }}
  1082. />
  1083. </NModal>
  1084. <NModal
  1085. maskClosable={modalClickMask}
  1086. v-model:show={showModalMask.value}>
  1087. <AddCoursewareProtocol
  1088. onClose={() => (showModalMask.value = false)}
  1089. onConfirm={async () => {
  1090. try {
  1091. const resultStatus = await onSaveCourseWare();
  1092. if (resultStatus) {
  1093. onCancelSave()
  1094. }
  1095. } catch {
  1096. //
  1097. }
  1098. }}
  1099. />
  1100. </NModal>
  1101. </NForm>
  1102. );
  1103. }
  1104. });