addCourseware.tsx 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182
  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. // if(item.isError) {
  399. // message.error('您添加的资源与适用乐器不符')
  400. // } else {
  401. // message.success('添加成功');
  402. // }
  403. }
  404. timer = setTimeout(() => {
  405. // 内容有更新 - 相关资源会刷新
  406. eventGlobal.emit('onCoursewareUpdate');
  407. }, 100);
  408. });
  409. };
  410. // 拖拽添加数据
  411. const addDragCoursewareItem = async (item: any, newIndex: number) => {
  412. clearTimeout(timer);
  413. forms.dragLoadingStatus = true;
  414. const materialList: any[] = [];
  415. try {
  416. const { data } = await api_materialDetail(item.materialId);
  417. if (Array.isArray(data.materialRefs)) {
  418. data.materialRefs.forEach((item: any) => {
  419. if (item.refType === 'STRONG') {
  420. const relateMaterialInfo = item.relateMaterialInfo || {};
  421. materialList.push({
  422. content: relateMaterialInfo.content,
  423. coverImg: relateMaterialInfo.coverImg,
  424. isSelected:
  425. relateMaterialInfo.sourceFrom === 'PLATFORM' ? true : false,
  426. instrumentIds: relateMaterialInfo.instrumentIds,
  427. isError: checkCurrentIsInstrument(relateMaterialInfo.instrumentIds, relateMaterialInfo.type),
  428. materialId: relateMaterialInfo.id,
  429. title: relateMaterialInfo.name,
  430. type: relateMaterialInfo.type
  431. });
  432. }
  433. });
  434. }
  435. } catch {
  436. //
  437. }
  438. nextTick(() => {
  439. const array: any = forms.coursewareList[item.index || 0].list || [];
  440. array[newIndex] = item;
  441. materialList.forEach((m: any) => {
  442. array.splice(newIndex + 1, 0, m);
  443. });
  444. forms.coursewareList[item.index || 0].list = array;
  445. if(item.isError) {
  446. message.error('您添加的资源与适用乐器不符')
  447. }
  448. timer = setTimeout(() => {
  449. // 内容有更新 - 相关资源会刷新
  450. eventGlobal.emit('onCoursewareUpdate');
  451. }, 100);
  452. forms.dragLoadingStatus = false;
  453. });
  454. };
  455. /** 取消保存时处理 */
  456. const onCancelSave = () => {
  457. emit('change', {
  458. status: false,
  459. addParam: {
  460. isAdd: false,
  461. name: forms.name,
  462. id: forms.createId
  463. }
  464. });
  465. eventGlobal.emit('teacher-slideshow', false);
  466. }
  467. /** 提交数据前做拦截提示 */
  468. const checkInstrumentIdsSubmit = () => {
  469. let status = false
  470. // 修改声部后重新检测状态
  471. forms.coursewareList.forEach((item: any) => {
  472. const childList = item.list || []
  473. childList.forEach((child: any) => {
  474. if(child.isError) {
  475. status = true
  476. }
  477. })
  478. })
  479. if(status) {
  480. forms.messageOperation = {
  481. visiable: true,
  482. type: 'checkInstrument',
  483. loading: false,
  484. contentDirection: 'center',
  485. title: '温馨提示',
  486. content: '课件中含有不符合适用乐器的资源,是否继续保存?',
  487. cancelButtonText: '取消',
  488. confirmButtonText: '继续保存',
  489. index: 0
  490. };
  491. }
  492. return status
  493. }
  494. // 提交
  495. const onSubmit = async () => {
  496. try {
  497. coursewareListRef.value.validate();
  498. eventGlobal.emit('checkCoursewareForm');
  499. if (!forms.name) {
  500. message.error('请输入课件名称');
  501. return;
  502. }
  503. if (forms.subjects.length <= 0) {
  504. message.error('请选择适用乐器');
  505. return;
  506. }
  507. if (forms.coursewareList.length <= 0) {
  508. message.error('请至少添加一个知识点');
  509. return;
  510. }
  511. let isNotAdd = false;
  512. for (const item of forms.coursewareList) {
  513. if (!item.name) {
  514. message.error('请输入知识点名称');
  515. return;
  516. }
  517. if (Array.isArray(item.list) && item.list.length <= 0) {
  518. isNotAdd = true;
  519. }
  520. }
  521. if (isNotAdd) {
  522. message.error('请至少添加一个资源');
  523. return;
  524. }
  525. // 检测是否有异常资源
  526. if(checkInstrumentIdsSubmit()) return
  527. if (forms.openFlag && !userStore.getReadCoursewareOpenAgreement) {
  528. showModalMask.value = true;
  529. return;
  530. }
  531. const resultStatus = await onSaveCourseWare();
  532. if (resultStatus) {
  533. onCancelSave()
  534. }
  535. } catch {
  536. //
  537. }
  538. };
  539. const onSaveCourseWare = async () => {
  540. try {
  541. const params = {
  542. name: forms.name,
  543. instrumentIds: forms.subjects.join(','),
  544. openFlag: forms.openFlag,
  545. autoPlay: forms.autoPlay,
  546. coursewareDetailKnowledgeId: prepareStore.getSelectKey,
  547. chapterKnowledgeList: [] as any
  548. };
  549. forms.coursewareList.forEach((item: any) => {
  550. let tempItem: any = [];
  551. if (Array.isArray(item.list) && item.list.length > 0) {
  552. tempItem = item.list.map((child: any) => {
  553. return {
  554. bizId: child.materialId,
  555. type: child.type,
  556. dataJson: !['IMG', 'VIDEO', 'SONG', 'MUSIC', 'PPT'].includes(
  557. child.type
  558. )
  559. ? JSON.stringify({
  560. setting: child.dataJson,
  561. coverImg: child.coverImg,
  562. bizId: child.bizId,
  563. content: child.content,
  564. name: child.title
  565. })
  566. : ''
  567. };
  568. });
  569. }
  570. params.chapterKnowledgeList.push({
  571. name: item.name,
  572. chapterKnowledgeMaterialList: tempItem
  573. });
  574. });
  575. if (props.groupItem?.id) {
  576. await api_teacherChapterLessonCoursewareUpdate({
  577. id: props.groupItem.id,
  578. ...params
  579. });
  580. message.success('保存成功');
  581. } else {
  582. const { data } = await api_teacherChapterLessonCoursewareAdd(params);
  583. forms.createId = data.id;
  584. }
  585. return true;
  586. } catch {
  587. //
  588. return false;
  589. }
  590. };
  591. const addItem = (item: any, point?: any) => {
  592. if(forms.subjects.length <= 0) {
  593. message.error('请先选择适用乐器')
  594. eventGlobal.emit('checkCoursewareForm', 'subject')
  595. return
  596. }
  597. item.isError = checkCurrentIsInstrument(item.instrumentIds, item.type, item.hasNotCheck) // 是否异常
  598. if (forms.coursewareList.length <= 0) {
  599. // 添加到临时对象
  600. forms.addCoursewareItem = item;
  601. forms.messageOperation = {
  602. visiable: true,
  603. type: 'addItem',
  604. contentDirection: 'center',
  605. title: '添加到知识点',
  606. loading: false,
  607. content: '当前课件暂无知识点,请添加知识点后操作',
  608. cancelButtonText: '取消',
  609. confirmButtonText: '添加知识点',
  610. index: 0
  611. };
  612. } else if (forms.coursewareList.length > 1 && item.addType !== 'drag') {
  613. forms.addCoursewareVisiable = true;
  614. forms.addCoursewareItem = item;
  615. } else {
  616. addCoursewareItem(item, point);
  617. checkCurrentInstrumentTip(item.isError)
  618. }
  619. };
  620. // 当页面离开时
  621. const onPageBeforeLeave = (event: any) => {
  622. const objA = JSON.stringify(forms.coursewareList);
  623. const objB = JSON.stringify(forms.baseCoursewareList);
  624. const baseA = JSON.stringify({
  625. subjects: forms.subjects,
  626. autoPlay: forms.autoPlay,
  627. name: forms.name,
  628. openFlag: forms.openFlag
  629. });
  630. const baseB = JSON.stringify(forms.baseInfo);
  631. if (objA === objB && baseA === baseB) {
  632. if (typeof event == 'function') {
  633. event();
  634. onCancelSave()
  635. }
  636. } else {
  637. forms.messageCallBack = event;
  638. forms.messageOperation = {
  639. visiable: true,
  640. type: 'pageLive',
  641. loading: false,
  642. contentDirection: 'center',
  643. title: '保存课件',
  644. content: '当前课件暂未保存,是否保存?',
  645. cancelButtonText: '不保存',
  646. confirmButtonText: '保存',
  647. index: 0
  648. };
  649. }
  650. };
  651. const onCancelCourseware = (item: any) => {
  652. forms.subjects = item.subjects;
  653. forms.openFlagEnable = item.openFlagEnable;
  654. forms.autoPlay = item.autoPlay;
  655. forms.name = item.name;
  656. forms.openFlag = item.openFlag;
  657. const objA = JSON.stringify(forms.coursewareList);
  658. const objB = JSON.stringify(forms.baseCoursewareList);
  659. const baseA = JSON.stringify({
  660. subjects: forms.subjects,
  661. autoPlay: forms.autoPlay,
  662. name: forms.name,
  663. openFlag: forms.openFlag
  664. });
  665. const baseB = JSON.stringify(forms.baseInfo);
  666. if (objA === objB && baseA === baseB) {
  667. onCancelSave()
  668. } else {
  669. forms.messageOperation = {
  670. visiable: true,
  671. type: 'save',
  672. loading: false,
  673. contentDirection: 'center',
  674. title: '保存课件',
  675. content: '当前课件暂未保存,是否保存?',
  676. cancelButtonText: '不保存',
  677. confirmButtonText: '保存',
  678. index: 0
  679. };
  680. }
  681. };
  682. const onSubmitCourseware = (item: any) => {
  683. forms.subjects = item.subjects;
  684. forms.openFlagEnable = item.openFlagEnable;
  685. forms.autoPlay = item.autoPlay;
  686. forms.name = item.name;
  687. forms.openFlag = item.openFlag;
  688. onSubmit();
  689. };
  690. const syncLeftFormData = (item: any) => {
  691. forms.subjects = item.subjects;
  692. forms.openFlagEnable = item.openFlagEnable;
  693. forms.autoPlay = item.autoPlay;
  694. forms.name = item.name;
  695. forms.openFlag = item.openFlag;
  696. }
  697. // 声部变化时
  698. const onCourseWareSubjectChange = (subjects: any) => {
  699. forms.subjects = subjects
  700. let isTips = false
  701. // 修改声部后重新检测状态
  702. forms.coursewareList.forEach((item: any) => {
  703. const childList = item.list || []
  704. childList.forEach((child: any) => {
  705. child.isError = checkCurrentIsInstrument(child.instrumentIds, child.type, child.hasNotCheck)
  706. if(child.isError) {
  707. isTips = true
  708. }
  709. })
  710. })
  711. if(isTips) {
  712. message.error('您添加的资源与适用乐器不符')
  713. }
  714. }
  715. onMounted(async () => {
  716. // 修改时重置默认数据
  717. if (props.groupItem?.id) {
  718. forms.coursewareList = [];
  719. forms.baseCoursewareList = [];
  720. }
  721. await getList();
  722. // 动态添加数据
  723. eventGlobal.on('onPrepareAddItem', addItem);
  724. eventGlobal.on('pageBeforeLeave', onPageBeforeLeave);
  725. // 选择乐器改变时
  726. eventGlobal.on('coursewareSubjectChange', onCourseWareSubjectChange)
  727. // 取消
  728. eventGlobal.on('coursewareClosed', onCancelCourseware);
  729. // 保存
  730. eventGlobal.on('coursewareSave', onSubmitCourseware);
  731. eventGlobal.on('coursewareHeadSyncData', syncLeftFormData);
  732. });
  733. onUnmounted(() => {
  734. eventGlobal.off('onPrepareAddItem', addItem);
  735. eventGlobal.off('pageBeforeLeave', onPageBeforeLeave);
  736. eventGlobal.off('coursewareSubjectChange', onCourseWareSubjectChange)
  737. eventGlobal.off('coursewareClosed', onCancelCourseware);
  738. eventGlobal.off('coursewareSave', onSubmitCourseware);
  739. eventGlobal.off('coursewareHeadSyncData', syncLeftFormData);
  740. });
  741. // 当列表数据更新时同步缓存数据
  742. watch(
  743. () => forms.coursewareList,
  744. () => {
  745. prepareStore.setCoursewareList(forms.coursewareList);
  746. },
  747. {
  748. deep: true
  749. }
  750. );
  751. return () => (
  752. <NForm
  753. class={styles.coursewareModal}
  754. model={forms}
  755. ref={coursewareListRef}>
  756. <NScrollbar
  757. class={[styles.listContainer, 'listContainerWrap']}
  758. {...{ id: 'lessonsIn-1' }}>
  759. <NSpin show={forms.loadingStatus}>
  760. <div
  761. class={[styles.listSection, 'listSectionWrap']}
  762. id="listSectionWrap">
  763. {forms.coursewareList.map((item: any, index: number) => (
  764. <div
  765. class={[styles.listItems, 'row-group']}
  766. >
  767. <div class={styles.knowledgePoint}>
  768. <NFormItem
  769. class={styles.btnItem}
  770. label="知识点名称"
  771. showFeedback={false}
  772. requireMarkPlacement="left"
  773. path={`coursewareList.${index}.name`}
  774. rule={[
  775. {
  776. required: true,
  777. trigger: ['input', 'blur']
  778. }
  779. ]}>
  780. <NInput
  781. placeholder="未命名知识点"
  782. v-model:value={item.name}
  783. maxlength={15}
  784. clearable
  785. />
  786. </NFormItem>
  787. </div>
  788. <NSpace class={styles.operationGroup}>
  789. {index > 0 && (
  790. <NTooltip showArrow={false}>
  791. {{
  792. trigger: () => (
  793. <i
  794. class={styles.iconCUp}
  795. onClick={() => onChangePoint('up', index)}></i>
  796. ),
  797. default: () => '上移知识点'
  798. }}
  799. </NTooltip>
  800. )}
  801. {index < forms.coursewareList.length - 1 && (
  802. <NTooltip showArrow={false}>
  803. {{
  804. trigger: () => (
  805. <i
  806. class={styles.iconCDown}
  807. onClick={() => onChangePoint('down', index)}></i>
  808. ),
  809. default: () => '下移知识点'
  810. }}
  811. </NTooltip>
  812. )}
  813. <NTooltip showArrow={false}>
  814. {{
  815. trigger: () => (
  816. <i
  817. class={styles.iconCRemove}
  818. onClick={() =>
  819. onChangePoint('remove', index, item)
  820. }></i>
  821. ),
  822. default: () => '删除知识点'
  823. }}
  824. </NTooltip>
  825. </NSpace>
  826. {/* {item.list.length > 0 && ( */}
  827. <Draggable
  828. v-model:modelValue={item.list}
  829. itemKey="id"
  830. // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  831. // @ts-ignore
  832. group="description"
  833. scroll={true}
  834. animation={200}
  835. onAdd={async (evt: any) => {
  836. // 锁
  837. if(forms.dragLoadingStatus) return
  838. const list = forms.coursewareList[index].list;
  839. const dropItem = list[evt.newDraggableIndex];
  840. if (dropItem.sourceForm === 'resource-item') {
  841. if(forms.subjects.length <= 0) {
  842. list.splice(evt.newDraggableIndex, 1)
  843. message.error('请先选择适用乐器')
  844. eventGlobal.emit('checkCoursewareForm', 'subject')
  845. return
  846. }
  847. await addDragCoursewareItem(
  848. {
  849. materialId: dropItem.id,
  850. coverImg: dropItem.coverImg,
  851. type: dropItem.type,
  852. title: dropItem.title,
  853. refFlag: dropItem.refFlag,
  854. isCollect: dropItem.isCollect,
  855. isSelected: dropItem.isSelected,
  856. hasNotCheck: dropItem.hasNotCheck,
  857. isError: checkCurrentIsInstrument(dropItem.instrumentIds, dropItem.type, dropItem.hasNotCheck), // 是否异常
  858. content: dropItem.content,
  859. audioPlayTypeArray: dropItem.audioPlayTypeArray,
  860. removeFlag: false,
  861. index
  862. },
  863. evt.newDraggableIndex
  864. );
  865. }
  866. }}
  867. onDrag={(event: any) => {
  868. // 修复滚动超出范围
  869. const container: any = document.querySelector(
  870. '.listContainerWrap .n-scrollbar-container'
  871. );
  872. const sensitivity = 150; // 自定义灵敏度
  873. if (event.clientY < sensitivity) {
  874. container.scrollTop -= 8;
  875. } else if (
  876. window.innerHeight - event.clientY <
  877. sensitivity
  878. ) {
  879. container.scrollTop += 8;
  880. }
  881. }}
  882. componentData={{
  883. draggable: 'row-nav',
  884. // scroll: false,
  885. itemKey: 'id',
  886. tag: 'div',
  887. animation: 200,
  888. // pull: true,
  889. // put: true,
  890. group: 'description'
  891. // disabled: false
  892. }}
  893. class={styles.list}>
  894. {{
  895. item: (element: any) => {
  896. const item = element.element;
  897. return (
  898. <div
  899. data-id={item.id}
  900. class={[
  901. styles.itemWrap,
  902. styles.itemBlock,
  903. 'row-nav'
  904. ]}>
  905. <div class={styles.itemWrapBox}>
  906. <CardType
  907. class={[styles.itemContent]}
  908. isError={item.isError}
  909. isShowCollect={false}
  910. offShelf={item.removeFlag ? true : false}
  911. // onOffShelf={() => onRemove(item)}
  912. item={item}
  913. audioPlayTypeSize="small"
  914. disabledMouseHover={false}
  915. onClick={() => {
  916. if (item.type === 'IMG') return;
  917. forms.show = true;
  918. forms.item = item;
  919. }}
  920. />
  921. <div class={styles.itemOperation}>
  922. <img
  923. src={iconDelete}
  924. class={styles.iconDelete}
  925. onClick={(e: MouseEvent) => {
  926. e.stopPropagation();
  927. onDelete(element.index, index);
  928. }}
  929. />
  930. </div>
  931. </div>
  932. </div>
  933. );
  934. },
  935. footer: () => (
  936. <div class={styles.itemWrap}>
  937. <div class={styles.itemWrapBox}>
  938. <div
  939. class={[
  940. styles.itemContent,
  941. styles.addMusicItem,
  942. 'handle'
  943. ]}
  944. onClick={() => {
  945. if(forms.subjects.length <= 0) {
  946. message.error('请先选择适用乐器')
  947. eventGlobal.emit('checkCoursewareForm', 'subject')
  948. return
  949. }
  950. forms.addOtherSource = true;
  951. forms.addOtherIndex = index;
  952. }}>
  953. <img src={iconAddMusic} />
  954. <p class={styles.addMusicName}>添加资源</p>
  955. </div>
  956. </div>
  957. </div>
  958. )
  959. }}
  960. </Draggable>
  961. </div>
  962. ))}
  963. {!forms.loadingStatus && (
  964. <NButton
  965. block
  966. type="primary"
  967. secondary
  968. class={styles.addKnowledgePoint}
  969. onClick={() => {
  970. forms.coursewareList.push({
  971. name: '',
  972. list: []
  973. });
  974. }}>
  975. <i class={styles.iconCAdd}></i>
  976. 添加知识点
  977. </NButton>
  978. )}
  979. </div>
  980. </NSpin>
  981. </NScrollbar>
  982. {/* 弹窗查看 */}
  983. <CardPreview
  984. size={
  985. [
  986. 'INSTRUMENT',
  987. 'THEORY',
  988. 'MUSIC_WIKI',
  989. 'MUSICIAN',
  990. 'RHYTHM'
  991. ].includes(forms.item.type)
  992. ? 'large'
  993. : ''
  994. }
  995. v-model:show={forms.show}
  996. item={forms.item}
  997. isDownload={false}
  998. />
  999. <NModal
  1000. maskClosable={modalClickMask}
  1001. v-model:show={forms.addCoursewareVisiable}
  1002. preset="card"
  1003. class={['modalTitle', styles.addCourseware]}
  1004. title={'添加到知识点'}>
  1005. <AddItemModel
  1006. coursewareList={forms.coursewareList}
  1007. onClose={() => (forms.addCoursewareVisiable = false)}
  1008. onConfirm={(selects: number[]) => {
  1009. if (Array.isArray(selects) && selects.length > 0) {
  1010. selects.forEach(select => {
  1011. addCoursewareItem({
  1012. ...forms.addCoursewareItem,
  1013. index: select
  1014. });
  1015. });
  1016. forms.addCoursewareVisiable = false;
  1017. checkCurrentInstrumentTip(forms.addCoursewareItem.isError)
  1018. } else {
  1019. message.error('请选择需要添加的知识点');
  1020. }
  1021. }}
  1022. />
  1023. </NModal>
  1024. <NModal
  1025. maskClosable={modalClickMask}
  1026. v-model:show={forms.messageOperation.visiable}
  1027. preset="card"
  1028. class={['modalTitle', styles.removeVisiable1]}
  1029. title={forms.messageOperation.title}>
  1030. <TheMessageDialog
  1031. content={forms.messageOperation.content}
  1032. contentDirection={forms.messageOperation.contentDirection}
  1033. cancelButtonText={forms.messageOperation.cancelButtonText}
  1034. confirmButtonText={forms.messageOperation.confirmButtonText}
  1035. loading={forms.messageOperation.loading}
  1036. onClose={() => {
  1037. forms.messageOperation.visiable = false;
  1038. if (
  1039. forms.messageOperation.type === 'save' ||
  1040. forms.messageOperation.type === 'pageLive'
  1041. ) {
  1042. onCancelSave()
  1043. if (
  1044. forms.messageOperation.type === 'pageLive' &&
  1045. typeof forms.messageCallBack === 'function'
  1046. ) {
  1047. forms.messageCallBack();
  1048. }
  1049. }
  1050. forms.messageCallBack = null;
  1051. }}
  1052. onConfirm={() => onMessageConfirm()}
  1053. />
  1054. </NModal>
  1055. <PreviewWindow
  1056. v-model:show={forms.previewModal}
  1057. type="attend"
  1058. params={forms.previewParams}
  1059. />
  1060. {/* 添加其它类型的资源 */}
  1061. <NModal
  1062. maskClosable={modalClickMask}
  1063. v-model:show={forms.addOtherSource}
  1064. preset="card"
  1065. class={['modalTitle background', styles.addOtherSource]}
  1066. title={'添加资源'}>
  1067. <AddOtherSource
  1068. onClose={() => (forms.addOtherSource = false)}
  1069. onComfirm={item => {
  1070. if (Array.isArray(item)) {
  1071. let isTips = false
  1072. item.forEach(async (child: any) => {
  1073. child.isError = checkCurrentIsInstrument(child.instrumentIds, child.type)
  1074. forms.coursewareList[forms.addOtherIndex || 0].list.push(child);
  1075. if(child.isError) {
  1076. isTips = true
  1077. }
  1078. });
  1079. checkCurrentInstrumentTip(isTips)
  1080. } else {
  1081. item.isError = checkCurrentIsInstrument(item.instrumentIds, item.type)
  1082. addCoursewareItem(
  1083. { ...item, index: forms.addOtherIndex },
  1084. null,
  1085. true
  1086. );
  1087. checkCurrentInstrumentTip(item.isError)
  1088. }
  1089. }}
  1090. />
  1091. </NModal>
  1092. <NModal
  1093. maskClosable={modalClickMask}
  1094. v-model:show={showModalMask.value}>
  1095. <AddCoursewareProtocol
  1096. onClose={() => (showModalMask.value = false)}
  1097. onConfirm={async () => {
  1098. try {
  1099. const resultStatus = await onSaveCourseWare();
  1100. if (resultStatus) {
  1101. onCancelSave()
  1102. }
  1103. } catch {
  1104. //
  1105. }
  1106. }}
  1107. />
  1108. </NModal>
  1109. </NForm>
  1110. );
  1111. }
  1112. });