index.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. import { computed, defineComponent, nextTick, onMounted, reactive, ref } from 'vue';
  2. import styles from './index.module.less';
  3. import {
  4. NButton,
  5. NCheckbox,
  6. NCheckboxGroup,
  7. // NBreadcrumb,
  8. // NBreadcrumbItem,
  9. // NScrollbar,
  10. NSlider,
  11. NSpace,
  12. NSpin
  13. } from 'naive-ui';
  14. import iconT from '/src/views/content-information/images/icon-t.png';
  15. import iconAddT from '/src/views/content-information/images/icon-add-t.png';
  16. import iconPlusT from '/src/views/content-information/images/icon-plus-t.png';
  17. import {
  18. api_lessonCoursewareDetail_listKnowledge,
  19. api_lessonCoursewareKnowledgeDetail
  20. } from '/src/views/content-information/api';
  21. import TheEmpty from '/src/components/TheEmpty';
  22. import { PageEnum } from '/src/enums/pageEnum';
  23. import { useSpeak } from '/src/views/content-information/useSpeak';
  24. export default defineComponent({
  25. name: 'cotnent-knowledge',
  26. emits: ['close', 'confirm'],
  27. setup(props, { emit }) {
  28. const show = ref(false);
  29. const content = ref(false);
  30. const musicContentRef = ref();
  31. const speakMusicContent =
  32. 'musicContent' + new Date().getTime() + Math.floor(Math.random() * 100);
  33. const speak = useSpeak(speakMusicContent);
  34. const state = reactive({
  35. fontSize: 18,
  36. tableList: [] as any,
  37. selectKey: null,
  38. details: {} as any,
  39. selectCheckboxs: [] as any
  40. });
  41. const isDisabled = computed(() => {
  42. const boxs = state.selectCheckboxs;
  43. let flag = false;
  44. for (const i in boxs) {
  45. if (boxs[i].length > 0) {
  46. flag = true;
  47. break;
  48. }
  49. }
  50. return !flag;
  51. });
  52. const getDetails = async () => {
  53. show.value = true;
  54. content.value = true;
  55. try {
  56. const { data } = await api_lessonCoursewareDetail_listKnowledge({
  57. type: 'COURSEWARE'
  58. });
  59. state.tableList = data || [];
  60. if (state.tableList.length > 0) {
  61. const item =
  62. state.tableList[0].lessonCoursewareDetailKnowledgeDetailList;
  63. state.tableList[0].selected = true;
  64. if (item && item.length) {
  65. const child = item[0];
  66. state.selectKey = child.id;
  67. await getDetail();
  68. }
  69. state.tableList.forEach((item: any) => {
  70. item.checked = false;
  71. item.indeterminate = false;
  72. });
  73. }
  74. } catch {
  75. //
  76. }
  77. content.value = false;
  78. show.value = false;
  79. };
  80. const getDetail = async () => {
  81. content.value = true;
  82. try {
  83. const { data } = await api_lessonCoursewareKnowledgeDetail({
  84. id: state.selectKey
  85. });
  86. content.value = false;
  87. state.details = data;
  88. nextTick(() => {
  89. // 使用 DOMParser 解析 HTML 字符串
  90. const parser = new DOMParser();
  91. const doc = parser.parseFromString(data.desc, 'text/html');
  92. const hasChilds = document.querySelectorAll('.only-child-select');
  93. if (hasChilds.length > 0) {
  94. hasChilds.forEach(child => {
  95. child.remove();
  96. });
  97. }
  98. const childNodes = doc.body.childNodes;
  99. childNodes?.forEach((node: any) => {
  100. node?.classList.add('only-child-select');
  101. });
  102. // 提取并分割 HTML 文档中的内容
  103. const result = document.createElement("div")
  104. result.classList.add("html-to-dom")
  105. result.appendChild(speak.processNode(doc.body))
  106. document
  107. .querySelector('#' + speakMusicContent)
  108. ?.appendChild(result);
  109. });
  110. } catch {
  111. //
  112. }
  113. content.value = false;
  114. };
  115. const onSubmit = () => {
  116. const items: any[] = [];
  117. for (const i in state.selectCheckboxs) {
  118. const ids = state.selectCheckboxs[i];
  119. const item = state.tableList[i];
  120. if (Array.isArray(item.lessonCoursewareDetailKnowledgeDetailList)) {
  121. item.lessonCoursewareDetailKnowledgeDetailList.forEach(
  122. (child: any) => {
  123. if (ids.includes(child.id)) {
  124. items.push(child);
  125. }
  126. }
  127. );
  128. }
  129. }
  130. const result: any[] = [];
  131. items.forEach(item => {
  132. result.push({
  133. coverImg: PageEnum.THEORY_DEFAULT_COVER,
  134. title: '乐理知识-' + item.name,
  135. materialId: item.id,
  136. content: item.id
  137. });
  138. });
  139. emit('confirm', result);
  140. };
  141. onMounted(() => {
  142. getDetails();
  143. });
  144. return () => (
  145. <div class={styles.container}>
  146. <div class={[styles.wrap]}>
  147. <div class={styles.content}>
  148. <div class={styles.contentWrap}>
  149. <div class={styles.directoryList}>
  150. <div
  151. class={[
  152. styles.scrollBar,
  153. !show.value && state.tableList.length <= 0
  154. ? styles.empty
  155. : ''
  156. ]}
  157. style={{ height: '100%' }}>
  158. <NSpin show={show.value} style={{ height: '100%' }}>
  159. <div class={[styles.listSection]}>
  160. {state.tableList.map((item: any, index: number) => (
  161. <div
  162. class={[
  163. styles.treeParent,
  164. item.selected && styles.treeParentSelected
  165. ]}
  166. key={'parent' + index}>
  167. <div
  168. class={[styles.treeItem, styles.parentItem]}
  169. onClick={() => {
  170. state.tableList.forEach((child: any) => {
  171. if (item.id !== child.id) {
  172. child.selected = false;
  173. }
  174. });
  175. item.selected = item.selected ? false : true;
  176. }}>
  177. {item.lessonCoursewareDetailKnowledgeDetailList &&
  178. item.lessonCoursewareDetailKnowledgeDetailList
  179. .length > 0 && (
  180. <span
  181. class={[
  182. styles.arrow,
  183. item.selected ? styles.arrowSelect : ''
  184. ]}></span>
  185. )}
  186. <p
  187. class={[
  188. styles.title,
  189. item.selected ? styles.titleSelect : ''
  190. ]}>
  191. <span
  192. class={[
  193. styles.dir,
  194. item.selected ? styles.dirSelect : ''
  195. ]}></span>
  196. <p>{item.name}</p>
  197. </p>
  198. <div
  199. class={styles.checkbox}
  200. onClick={(e: any) => {
  201. e.stopPropagation();
  202. }}>
  203. <NCheckbox
  204. checked={item.checked}
  205. indeterminate={item.indeterminate}
  206. onUpdate:checked={(val: boolean) => {
  207. item.checked = val;
  208. const child =
  209. item.lessonCoursewareDetailKnowledgeDetailList ||
  210. [];
  211. if (val) {
  212. const ids: any = [];
  213. child.forEach((c: any) => {
  214. ids.push(c.id);
  215. });
  216. state.selectCheckboxs[index] = ids;
  217. } else {
  218. state.selectCheckboxs[index] = [];
  219. }
  220. item.indeterminate = false;
  221. }}></NCheckbox>
  222. </div>
  223. </div>
  224. <NCheckboxGroup
  225. value={state.selectCheckboxs[index]}
  226. onUpdate:value={val => {
  227. state.selectCheckboxs[index] = val;
  228. const child =
  229. item.lessonCoursewareDetailKnowledgeDetailList ||
  230. [];
  231. if (val.length <= 0) {
  232. item.checked = false;
  233. item.indeterminate = false;
  234. } else if (val.length === child.length) {
  235. item.checked = true;
  236. item.indeterminate = false;
  237. } else {
  238. item.checked = false;
  239. item.indeterminate = true;
  240. }
  241. }}>
  242. {item.selected &&
  243. item.lessonCoursewareDetailKnowledgeDetailList &&
  244. item.lessonCoursewareDetailKnowledgeDetailList.map(
  245. (child: any, j: number) => (
  246. <div
  247. key={'child' + j}
  248. class={[
  249. styles.treeItem,
  250. styles.childItem,
  251. styles.animation,
  252. state.selectKey === child.id
  253. ? styles.childSelect
  254. : ''
  255. ]}
  256. onClick={() => {
  257. if (state.selectKey === child.id) return;
  258. state.selectKey = child.id;
  259. getDetail();
  260. speak.onCloseSpeak();
  261. musicContentRef.value.$el.scrollTo(0, 0);
  262. }}>
  263. <span class={styles.childArrow}></span>
  264. <p class={styles.title}>{child.name}</p>
  265. <div
  266. class={styles.checkbox}
  267. onClick={(e: any) => e.stopPropagation()}>
  268. <NCheckbox value={child.id}></NCheckbox>
  269. </div>
  270. </div>
  271. )
  272. )}
  273. </NCheckboxGroup>
  274. </div>
  275. ))}
  276. </div>
  277. </NSpin>
  278. {!show.value && state.tableList.length <= 0 && (
  279. <TheEmpty style={{ height: '100%' }} />
  280. )}
  281. </div>
  282. </div>
  283. <div class={styles.musicStaff}>
  284. <div class={styles.musicTitleRight}>
  285. {speak.isSpeak.value ? (
  286. <span class={styles.textClose} onClick={speak.onCloseSpeak}>
  287. <i class={styles.icon}></i>关闭朗读
  288. </span>
  289. ) : (
  290. <span class={styles.textRead} onClick={speak.onAllSpeak}>
  291. <i class={styles.icon}></i>全文朗读
  292. </span>
  293. )}
  294. </div>
  295. <NSpin
  296. show={content.value}
  297. ref={musicContentRef}
  298. class={
  299. !content.value && !state.details?.desc ? styles.empty : ''
  300. }>
  301. {state.details?.desc ? (
  302. <div
  303. class={styles.musicContent}
  304. id={speakMusicContent}
  305. style={{ fontSize: state.fontSize + 'px' }}>
  306. {/* 选中的内容 */}
  307. <div
  308. id="selectionCouser"
  309. class={[
  310. styles.selectionCouser,
  311. !speak.showDom.value && styles.hide
  312. ]}>
  313. <span
  314. class={styles.textStart}
  315. onClick={speak.onTextStart}>
  316. 开始朗读<i class={styles.icon}></i>
  317. </span>
  318. <span
  319. class={styles.textReadOnly}
  320. onClick={speak.onTextReadOnly}>
  321. 只读这段<i class={styles.icon}></i>
  322. </span>
  323. </div>
  324. </div>
  325. ) : (
  326. ''
  327. )}
  328. {!content.value && !state.details?.desc && <TheEmpty />}
  329. </NSpin>
  330. </div>
  331. <div class={styles.changeSizeSection}>
  332. <img src={iconT} class={styles.iconT} />
  333. <img
  334. src={iconAddT}
  335. class={styles.iconAddT}
  336. onClick={() => {
  337. if (state.fontSize >= 32) return;
  338. state.fontSize += 1;
  339. }}
  340. />
  341. <NSlider
  342. v-model:value={state.fontSize}
  343. vertical
  344. min={12}
  345. max={32}
  346. />
  347. <img
  348. src={iconPlusT}
  349. class={styles.iconPlusT}
  350. onClick={() => {
  351. if (state.fontSize <= 12) return;
  352. state.fontSize -= 1;
  353. }}
  354. />
  355. </div>
  356. </div>
  357. </div>
  358. </div>
  359. <NSpace class={styles.btnGroup} justify="center">
  360. <NButton round onClick={() => emit('close')}>
  361. 取消
  362. </NButton>
  363. <NButton round type="primary" onClick={onSubmit} disabled={isDisabled.value}>
  364. 确认添加
  365. </NButton>
  366. </NSpace>
  367. </div>
  368. );
  369. }
  370. });