BookEep.vue 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057
  1. <template>
  2. <div class="common-preview">
  3. <div class="audit-content">
  4. <div ref="previewMain" class="main-container">
  5. <main :class="['preview-main', 'no-audit']" :style="computedCommonPreviewStyle">
  6. <div class="preview-left" :style="{ backgroundColor: background.background?.is_global ? '' : '#fff' }"></div>
  7. <CoursewarePreview
  8. ref="courseware"
  9. :is-show-group="false"
  10. :group-show-all="true"
  11. :group-row-list="groupRowList"
  12. :data="data"
  13. :courseware-id="curSelectId"
  14. :component-list="cList"
  15. :background="background"
  16. :can-remark="false"
  17. :show-remark="false"
  18. :component-remark-obj="{}"
  19. :project="project"
  20. @editNote="handEditNote"
  21. @saveCollect="saveCollect"
  22. @getTranslate="getTranslate"
  23. />
  24. <div class="preview-right" :style="{ backgroundColor: background.background?.is_global ? '' : '#fff' }"></div>
  25. </main>
  26. </div>
  27. </div>
  28. <ExplanatoryNoteDialog
  29. ref="explanatoryNote"
  30. :open.sync="editDialogOpen"
  31. :init-data="oldRichData"
  32. :title-text="dialogTitleText"
  33. :modal="false"
  34. @confirm="dialogSave"
  35. @cancel="dialogCancel"
  36. />
  37. <TranslateDialog
  38. :open.sync="showTranslate"
  39. :modal="false"
  40. :init-text="translateText"
  41. :book-id="id"
  42. title-text="翻译 (机器翻译)"
  43. />
  44. </div>
  45. </template>
  46. <script>
  47. import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview.vue';
  48. import ExplanatoryNoteDialog from '@/components/ExplanatoryNoteDialog.vue';
  49. import TranslateDialog from '@/components/TranslateDialog.vue';
  50. import { isTrue } from '@/utils/validate';
  51. import { buildCoursewareStyle } from '@/views/book/courseware/preview/common/utils/coursewareStyle';
  52. import { updateBaseURL } from '@/utils/http';
  53. import {
  54. GetMyNoteList,
  55. AddMyCollect,
  56. GetMyCollectList,
  57. DeleteMyNote,
  58. UpdateMyNote,
  59. AddMyNote,
  60. AddCoursewareFeedback,
  61. DeleteCoursewareFeedback,
  62. GetCoursewareFeedbackList,
  63. } from '@/api/book';
  64. export default {
  65. name: 'BookEep',
  66. components: {
  67. CoursewarePreview,
  68. ExplanatoryNoteDialog,
  69. TranslateDialog,
  70. },
  71. provide() {
  72. return {
  73. getLang: () => this.lang,
  74. getChinese: () => this.chinese,
  75. getShowPinYin: () => this.showPinYin,
  76. getLangList: () => this.langList,
  77. convertText: this.convertText,
  78. getTitleList: () => this.title_list,
  79. getPermissionControl: () => this.permissionControl,
  80. };
  81. },
  82. props: {
  83. id: {
  84. type: String,
  85. default: '',
  86. },
  87. // 组件内容
  88. content: {
  89. type: String,
  90. default: '',
  91. },
  92. // 内容分组行列表
  93. contentGroupRowList: {
  94. type: String,
  95. default: '',
  96. },
  97. // 组件列表
  98. componentList: {
  99. type: Array,
  100. default: () => [],
  101. },
  102. // 教材背景
  103. bookBackground: {
  104. type: String,
  105. default: '',
  106. },
  107. permissControl: {
  108. type: Object,
  109. default: () => ({}),
  110. },
  111. // 外部传入组件用户答案,JSON 格式字符串
  112. componentUserAnswer: {
  113. type: String,
  114. default: '',
  115. },
  116. },
  117. data() {
  118. return {
  119. background: this.bookBackground.length > 0 ? JSON.parse(this.bookBackground) : {},
  120. groupRowList: this.contentGroupRowList.length > 0 ? JSON.parse(this.contentGroupRowList) : [],
  121. data: this.content.length > 0 ? JSON.parse(this.content) : { row_list: [] },
  122. cList: this.handleComponentList(),
  123. curSelectId: this.id,
  124. isTrue,
  125. project: {
  126. editor: '', // 作者
  127. cover_image_file_id: null, // 封面图片ID
  128. cover_image_file_url: '', // 封面图片URL
  129. },
  130. title_list: [],
  131. node_list: [],
  132. permissionControl: {
  133. can_answer: true, // 可作答
  134. can_judge_correct: false, // 可判断对错(客观题)
  135. can_show_answer: false, // 可查看答案
  136. can_correct: false, // 可批改
  137. can_check_correct: false, // 可查看批改
  138. },
  139. popoverShow: false,
  140. is_can_edit: 'false', // 是否可以编辑
  141. allCollectList: [],
  142. newSelectedInfo: {}, // 新的选中信息
  143. allNoteList: [],
  144. oldRichData: {},
  145. editDialogOpen: false,
  146. dialogType: 0,
  147. lang: 'ZH',
  148. chinese: 'zh-Hans',
  149. showPinYin: 'false',
  150. langList: [],
  151. showTranslate: false,
  152. translateText: '',
  153. selectedInfo: null,
  154. allFeedbackList: [],
  155. drawerType: 1,
  156. };
  157. },
  158. computed: {
  159. computedCommonPreviewStyle() {
  160. return buildCoursewareStyle(this.background, 'commonPreview');
  161. },
  162. dialogTitleText() {
  163. if (this.dialogType === 2) return '反馈';
  164. return '笔记';
  165. },
  166. },
  167. watch: {
  168. componentList: {
  169. handler(newVal) {
  170. if (newVal && newVal.length > 0) {
  171. this.cList = this.handleComponentList();
  172. this.applyComponentUserAnswer();
  173. }
  174. },
  175. deep: true,
  176. },
  177. bookBackground: {
  178. handler(newVal) {
  179. if (newVal && newVal.length > 0) {
  180. this.background = JSON.parse(newVal);
  181. }
  182. },
  183. },
  184. content: {
  185. handler(newVal) {
  186. if (newVal && newVal.length > 0) {
  187. this.data = JSON.parse(newVal);
  188. }
  189. },
  190. },
  191. contentGroupRowList: {
  192. handler(newVal) {
  193. if (newVal && newVal.length > 0) {
  194. this.groupRowList = JSON.parse(newVal);
  195. }
  196. },
  197. },
  198. id: {
  199. handler(newVal) {
  200. if (newVal && newVal.length > 0) {
  201. this.curSelectId = newVal;
  202. }
  203. },
  204. },
  205. permissControl: {
  206. handler(newVal) {
  207. if (newVal && Object.keys(newVal).length > 0) {
  208. this.permissionControl = newVal;
  209. }
  210. },
  211. deep: true,
  212. immediate: true,
  213. },
  214. componentUserAnswer: {
  215. handler() {
  216. this.applyComponentUserAnswer();
  217. },
  218. deep: true,
  219. immediate: true,
  220. },
  221. },
  222. created() {
  223. const origin = window.location.origin;
  224. if (origin && origin.indexOf('localhost') === -1) {
  225. updateBaseURL(`${origin}/`);
  226. }
  227. },
  228. mounted() {
  229. this.applyComponentUserAnswer();
  230. },
  231. methods: {
  232. backTop() {
  233. this.$refs.previewMain.scrollTo({
  234. top: 0,
  235. left: 0,
  236. behavior: 'smooth',
  237. });
  238. },
  239. convertText(text) {
  240. return text;
  241. },
  242. handleComponentList() {
  243. const processHtmlString = this.processHtmlString;
  244. const cList = this.componentList || [];
  245. cList.forEach((x) => {
  246. if (x.component_type === 'audio') {
  247. let _c = JSON.parse(x.content);
  248. let p = _c.property || {};
  249. if (!p.file_name_display_mode) p.file_name_display_mode = 'true';
  250. if (p.view_method === 'independent' && !p.style_mode) {
  251. p.style_mode = 'middle';
  252. }
  253. if (!p.style_mode) p.style_mode = 'big';
  254. if (p.view_method === 'icon') {
  255. p.file_name_display_mode = 'false';
  256. p.view_method = 'independent';
  257. p.style_mode = 'small';
  258. }
  259. x.content = JSON.stringify(_c);
  260. } else if (x.component_type === 'richtext') {
  261. if (!processHtmlString) return;
  262. let _c = JSON.parse(x.content);
  263. let p = _c.property || {};
  264. let lev = Number(p.title_style_level);
  265. if (p.is_title !== 'true' || lev < 1 || !_c.content) return;
  266. let style = this.title_list.find((y) => y.level === lev) || {};
  267. if (style && style.style) {
  268. style = JSON.parse(style.style);
  269. let c_text = _c.content;
  270. const parser = new DOMParser();
  271. const doc = parser.parseFromString(c_text, 'text/html');
  272. const body = doc.body;
  273. const pElements = body.querySelectorAll('p');
  274. processHtmlString(pElements, style);
  275. _c.content = body.innerHTML;
  276. x.content = JSON.stringify(_c);
  277. }
  278. }
  279. });
  280. return cList;
  281. },
  282. /**
  283. * 获取所有组件的用户答案,供 UI 包使用者通过 ref 调用
  284. * @returns {Object} 以组件 id 为键的答案映射
  285. */
  286. getAllComponentUserAnswer() {
  287. const previewList = this.$refs?.courseware?.$refs?.preview || [];
  288. const answerMap = {};
  289. previewList.forEach((item) => {
  290. const id = item?.$el?.dataset?.id;
  291. if (!id || typeof item.getAnswer !== 'function') return;
  292. answerMap[id] = item.getAnswer();
  293. });
  294. const obj = {
  295. duration: 0,
  296. count_not_done: 0,
  297. count_right: 0,
  298. count_error: 0,
  299. content: JSON.stringify(answerMap),
  300. };
  301. return JSON.stringify(obj);
  302. },
  303. /**
  304. * 将外部传入的用户答案回填到组件
  305. */
  306. applyComponentUserAnswer() {
  307. if (!this.componentUserAnswer || this.componentUserAnswer.length === 0) return;
  308. this.$nextTick(() => {
  309. const previewList = this.$refs?.courseware?.$refs?.preview || [];
  310. if (!previewList.length) return;
  311. const answerMap = JSON.parse(this.componentUserAnswer || '{}');
  312. if (!answerMap || Object.keys(answerMap).length === 0) return;
  313. previewList.forEach((item) => {
  314. const id = item?.$el?.dataset?.id;
  315. if (!id || typeof item.showAnswer !== 'function') return;
  316. if (!Object.hasOwn(answerMap, id)) return;
  317. item.showAnswer(true, true, answerMap[id], true);
  318. });
  319. });
  320. },
  321. processHtmlString(pElements, styleConfig, isClear = false) {
  322. const _pElements = pElements || [];
  323. _pElements.forEach((pElement) => {
  324. if (pElement.innerHTML !== '<br data-mce-bogus="1">') {
  325. pElement.innerHTML = pElement.innerText;
  326. }
  327. if (isClear) {
  328. pElement.removeAttribute('style');
  329. pElement.removeAttribute('data-mce-style');
  330. } else {
  331. const style = pElement.style;
  332. if (styleConfig.font) style.fontFamily = styleConfig.font;
  333. if (styleConfig.font_size) style.fontSize = styleConfig.font_size;
  334. if (styleConfig.text_color) style.color = styleConfig.text_color;
  335. style.fontWeight = styleConfig.bold === 'true' ? 'bold' : 'normal';
  336. style.fontStyle = styleConfig.italic === 'true' ? 'italic' : 'normal';
  337. if (styleConfig.indent || styleConfig.indent === 0) style.marginLeft = `${styleConfig.indent}px`;
  338. }
  339. });
  340. },
  341. /**
  342. * 获取收藏列表
  343. * @param {Object} params - 参数对象
  344. * @param {number} params.type - 收藏类型(可选)
  345. */
  346. async getCollect(params) {
  347. if (params && params.type) this.drawerType = Number(params.type);
  348. this.allCollectList = [];
  349. await GetMyCollectList({ courseware_id: this.curSelectId }).then((res) => {
  350. if (res.status === 1) {
  351. res.collect_list.forEach((x) => {
  352. if (x.collect_desc) {
  353. let n = JSON.parse(x.collect_desc);
  354. let obj = {
  355. coursewareId: x.courseware_id,
  356. id: x.id,
  357. blockId: n.blockId,
  358. startIndex: n.startIndex,
  359. endIndex: n.endIndex,
  360. text: n.text,
  361. };
  362. this.allCollectList.push(obj);
  363. }
  364. });
  365. }
  366. });
  367. },
  368. /**
  369. * 保存收藏
  370. * @param {Object} collect - 收藏信息对象
  371. * @param {string} collect.coursewareId - 课件 ID
  372. * @param {string} collect.blockId - 区块 ID
  373. * @param {number} collect.startIndex - 起始索引
  374. * @param {number} collect.endIndex - 结束索引
  375. * @param {string} collect.text - 收藏文本
  376. */
  377. async saveCollect(collect) {
  378. if (this.allCollectList.length === 0) {
  379. await this.getCollect();
  380. }
  381. let old = this.allCollectList.find(
  382. (x) =>
  383. x.coursewareId === collect.coursewareId &&
  384. x.blockId === collect.blockId &&
  385. x.startIndex === collect.startIndex &&
  386. x.endIndex === collect.endIndex,
  387. );
  388. if (old) {
  389. this.$message({
  390. dangerouslyUseHTMLString: true,
  391. message: "<i class='el-icon-check' />已收藏",
  392. });
  393. return;
  394. }
  395. this.newSelectedInfo = collect;
  396. let collectInfo = {
  397. blockId: this.newSelectedInfo.blockId,
  398. startIndex: this.newSelectedInfo.startIndex,
  399. endIndex: this.newSelectedInfo.endIndex,
  400. text: this.newSelectedInfo.text,
  401. };
  402. let reqData = {
  403. courseware_id: this.newSelectedInfo.coursewareId, // 课件 ID
  404. component_id: 'WHOLE',
  405. collect_desc: JSON.stringify(collectInfo), // 位置描述
  406. };
  407. AddMyCollect(reqData)
  408. .then(() => {
  409. this.getCollect();
  410. })
  411. .then(() => {
  412. this.$message({
  413. dangerouslyUseHTMLString: true,
  414. message: "<i class='el-icon-check' />已收藏",
  415. });
  416. });
  417. },
  418. /**
  419. * 获取笔记列表
  420. * @param {Object} params - 参数对象
  421. */
  422. async getNote(params) {
  423. if (params && params.type) this.drawerType = Number(params.type);
  424. this.allNoteList = [];
  425. await GetMyNoteList({ courseware_id: this.curSelectId }).then((res) => {
  426. if (res.status === 1) {
  427. res.note_list.forEach((x) => {
  428. if (x.note_desc) {
  429. let n = JSON.parse(x.note_desc);
  430. let obj = {
  431. coursewareId: x.courseware_id,
  432. id: x.id,
  433. blockId: n.blockId,
  434. startIndex: n.startIndex,
  435. endIndex: n.endIndex,
  436. text: n.text,
  437. note: this.addImageMaxWidth(n.note),
  438. };
  439. this.allNoteList.push(obj);
  440. }
  441. });
  442. }
  443. });
  444. },
  445. async handEditNote(note) {
  446. this.oldRichData = {};
  447. if (this.allNoteList.length === 0) {
  448. await this.getNote();
  449. }
  450. let old = this.allNoteList.find(
  451. (x) =>
  452. x.coursewareId === note.coursewareId &&
  453. x.blockId === note.blockId &&
  454. x.startIndex === note.startIndex &&
  455. x.endIndex === note.endIndex,
  456. );
  457. if (old) {
  458. this.oldRichData = old;
  459. this.oldRichData.dataStr = old.note;
  460. }
  461. this.newSelectedInfo = note;
  462. this.editDialogOpen = true;
  463. this.dialogType = 1;
  464. },
  465. /**
  466. * 获取翻译
  467. * @param {Object} info - 翻译信息对象
  468. * @param {string} info.text - 需要翻译的文本
  469. */
  470. async getTranslate(info) {
  471. this.showTranslate = true;
  472. this.translateText = info.text;
  473. },
  474. dialogSave(obj) {
  475. if (this.dialogType === 1) return this.saveNote(obj);
  476. if (this.dialogType === 2) return this.saveFeedback(obj);
  477. },
  478. dialogCancel(id) {
  479. if (this.dialogType === 1) return this.delNote(id);
  480. if (this.dialogType === 2) return this.delFeedback(id);
  481. },
  482. /**
  483. * 保存笔记
  484. * @param {Object} note - 笔记信息对象
  485. * @param {string} note.dataStr - 笔记内容
  486. */
  487. saveNote(note) {
  488. let noteInfo = {
  489. blockId: this.newSelectedInfo.blockId,
  490. startIndex: this.newSelectedInfo.startIndex,
  491. endIndex: this.newSelectedInfo.endIndex,
  492. note: note.dataStr,
  493. text: this.newSelectedInfo.text,
  494. };
  495. let reqData = {
  496. courseware_id: this.newSelectedInfo.coursewareId, // 课件 ID
  497. component_id: 'WHOLE',
  498. note_desc: JSON.stringify(noteInfo), // 位置描述
  499. };
  500. if (note.id) {
  501. if (!noteInfo.note) {
  502. this.delNote(note.id);
  503. return;
  504. }
  505. let upDate = {
  506. id: note.id,
  507. note_desc: reqData.note_desc,
  508. };
  509. UpdateMyNote(upDate).then(() => {
  510. this.getNote();
  511. });
  512. } else {
  513. AddMyNote(reqData).then(() => {
  514. this.getNote();
  515. });
  516. }
  517. this.editDialogOpen = false;
  518. this.newSelectedInfo = null;
  519. this.selectedInfo = null;
  520. },
  521. delNote(id) {
  522. const noteId = id || (this.oldRichData && this.oldRichData.id);
  523. if (!noteId) return;
  524. DeleteMyNote({ id: noteId }).then(() => {
  525. this.allNoteList = this.allNoteList.filter((x) => x.id !== noteId);
  526. });
  527. },
  528. saveFeedback(feedBack) {
  529. if (feedBack.id) {
  530. this.delFeedback(feedBack.id);
  531. }
  532. if (feedBack.dataStr) {
  533. let feedBackInfo = {
  534. blockId: this.newSelectedInfo.blockId,
  535. startIndex: this.newSelectedInfo.startIndex,
  536. endIndex: this.newSelectedInfo.endIndex,
  537. feedBack: feedBack.dataStr,
  538. text: this.newSelectedInfo.text,
  539. };
  540. let reqData = {
  541. courseware_id: this.newSelectedInfo.coursewareId, // 课件 ID
  542. component_id: 'WHOLE',
  543. content: JSON.stringify(feedBackInfo),
  544. content_select: this.newSelectedInfo.text,
  545. };
  546. AddCoursewareFeedback(reqData).then(() => {
  547. this.getFeedback();
  548. });
  549. }
  550. this.editDialogOpen = false;
  551. this.newSelectedInfo = null;
  552. this.selectedInfo = null;
  553. },
  554. delFeedback(id) {
  555. const feedBackId = id || (this.oldRichData && this.oldRichData.id);
  556. if (!feedBackId) return;
  557. DeleteCoursewareFeedback({ id: feedBackId })
  558. .then(() => {
  559. this.allFeedbackList = this.allFeedbackList.filter((x) => x.id !== feedBackId);
  560. })
  561. .catch(() => {});
  562. },
  563. /**
  564. * 为HTML内容中的图片添加最大宽度限制
  565. * @param {string} html - HTML字符串
  566. * @returns {string} - 处理后的HTML字符串
  567. */
  568. addImageMaxWidth(html) {
  569. if (!html) return html;
  570. const regex = /<img([^>]*)>/gi;
  571. return html.replace(regex, (match, attributes) => {
  572. if (attributes.includes('style=')) {
  573. return match.replace(/style=["']([^"']*)["']/i, (styleMatch, styleValue) => {
  574. if (styleValue.includes('max-width')) {
  575. return styleMatch;
  576. }
  577. return `style="${styleValue}; max-width: 200px;"`;
  578. });
  579. }
  580. return `<img${attributes} style="max-width: 200px;">`;
  581. });
  582. },
  583. async getFeedback(params) {
  584. if (params && params.type) this.drawerType = Number(params.type);
  585. this.allFeedbackList = [];
  586. await GetCoursewareFeedbackList({ courseware_id: this.curSelectId }).then((res) => {
  587. if (res.status === 1 && res.feedback_list) {
  588. res.feedback_list.forEach((x) => {
  589. if (x.content) {
  590. let n = JSON.parse(x.content);
  591. let obj = {
  592. coursewareId: x.courseware_id,
  593. id: x.id,
  594. blockId: n.blockId,
  595. startIndex: n.startIndex,
  596. endIndex: n.endIndex,
  597. text: n.text,
  598. feedBack: this.addImageMaxWidth(n.feedBack),
  599. };
  600. this.allFeedbackList.push(obj);
  601. }
  602. });
  603. }
  604. });
  605. },
  606. },
  607. };
  608. </script>
  609. <style lang="scss" scoped>
  610. @use '@/styles/mixin.scss' as *;
  611. $total-width: $courseware-width + $courseware-left-margin + $courseware-right-margin;
  612. .common-preview {
  613. min-width: calc($total-width);
  614. .main-container {
  615. position: relative;
  616. flex: 1;
  617. min-width: 1110px;
  618. padding: 15px;
  619. overflow: auto;
  620. background-color: #ececec;
  621. .catalogue-bar {
  622. position: absolute;
  623. top: 15px;
  624. left: 0;
  625. display: flex;
  626. align-items: center;
  627. justify-content: center;
  628. width: 54px;
  629. height: 54px;
  630. margin: -9px 6px 0 240px;
  631. cursor: pointer;
  632. background-color: #fff;
  633. border-radius: 2px;
  634. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 40%);
  635. }
  636. .sidebar-bar {
  637. position: absolute;
  638. top: 0;
  639. right: 240px;
  640. display: flex;
  641. width: 60px;
  642. height: calc(100vh - 166px);
  643. .toolbar {
  644. display: flex;
  645. flex-direction: column;
  646. align-items: center;
  647. width: 60px;
  648. height: 100%;
  649. img {
  650. cursor: pointer;
  651. }
  652. &-special {
  653. display: flex;
  654. flex-direction: column;
  655. row-gap: 16px;
  656. align-items: center;
  657. width: 100%;
  658. margin-bottom: 24px;
  659. background-color: #fff;
  660. img {
  661. width: 36px;
  662. height: 36px;
  663. }
  664. }
  665. }
  666. }
  667. }
  668. .back-top {
  669. position: absolute;
  670. right: 240px;
  671. bottom: 0;
  672. display: flex;
  673. place-content: center center;
  674. align-items: center;
  675. width: 60px;
  676. height: 60px;
  677. cursor: pointer;
  678. background-color: #fff;
  679. }
  680. main.preview-main {
  681. display: flex;
  682. flex: 1;
  683. width: calc($total-width);
  684. min-width: calc($total-width);
  685. max-width: calc($total-width);
  686. min-height: 100%;
  687. margin: 0 auto;
  688. background-color: #fff;
  689. border-radius: 4px;
  690. box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 40%);
  691. .preview-left {
  692. width: $courseware-left-margin;
  693. min-width: $courseware-left-margin;
  694. max-width: $courseware-left-margin;
  695. background-color: $courseware-bgColor;
  696. }
  697. .preview-right {
  698. width: $courseware-right-margin;
  699. min-width: $courseware-right-margin;
  700. max-width: $courseware-right-margin;
  701. background-color: $courseware-bgColor;
  702. }
  703. &.no-audit {
  704. margin: 0 auto;
  705. }
  706. }
  707. .audit-content {
  708. display: flex;
  709. min-width: calc($total-width);
  710. .left-menu {
  711. display: flex;
  712. flex-direction: column;
  713. width: $catalogue-width;
  714. font-family: 'Microsoft YaHei', 'Arial', sans-serif;
  715. background-color: #fff;
  716. .courseware-info {
  717. display: flex;
  718. column-gap: 18px;
  719. width: 100%;
  720. height: 186px;
  721. padding: 6px 6px 24px;
  722. border-bottom: $border;
  723. .cover-image {
  724. display: flex;
  725. align-items: center;
  726. justify-content: center;
  727. width: 111px;
  728. height: 157px;
  729. background-color: rgba(229, 229, 229, 100%);
  730. img {
  731. max-width: 111px;
  732. max-height: 157px;
  733. }
  734. }
  735. .info-content {
  736. display: flex;
  737. flex-direction: column;
  738. justify-content: space-between;
  739. .catalogue-icon {
  740. text-align: right;
  741. .svg-icon {
  742. cursor: pointer;
  743. }
  744. }
  745. .courseware {
  746. width: 159px;
  747. height: 64px;
  748. font-size: 16px;
  749. .name {
  750. font-weight: bold;
  751. }
  752. .editor {
  753. display: -webkit-box;
  754. overflow: hidden;
  755. text-overflow: ellipsis;
  756. word-break: break-word;
  757. white-space: normal;
  758. -webkit-line-clamp: 2; /* 多行省略行数,按需调整 */
  759. -webkit-box-orient: vertical;
  760. }
  761. }
  762. }
  763. }
  764. }
  765. .sidebar {
  766. position: relative;
  767. display: flex;
  768. width: $sidebar-width;
  769. .toolbar {
  770. display: flex;
  771. flex-direction: column;
  772. align-items: center;
  773. width: 60px;
  774. height: 100%;
  775. background-color: rgba(247, 248, 250, 100%);
  776. img {
  777. cursor: pointer;
  778. }
  779. &-special {
  780. display: flex;
  781. flex-direction: column;
  782. row-gap: 16px;
  783. margin-bottom: 24px;
  784. }
  785. &-list {
  786. display: flex;
  787. flex-direction: column;
  788. row-gap: 16px;
  789. align-items: center;
  790. width: 100%;
  791. .sidebar-item {
  792. width: 100%;
  793. text-align: center;
  794. .sidebar-icon {
  795. width: 36px;
  796. height: 36px;
  797. cursor: pointer;
  798. }
  799. &.active {
  800. background-color: #4095e5;
  801. }
  802. }
  803. }
  804. }
  805. .content {
  806. flex: 1;
  807. background-color: #fff;
  808. .resource_box {
  809. height: 100%;
  810. overflow-y: auto;
  811. border: 1px solid #e5e5e5;
  812. h5 {
  813. position: fixed;
  814. z-index: 999;
  815. width: 240px;
  816. padding: 0 5px;
  817. margin: 0;
  818. font-size: 18px;
  819. line-height: 40px;
  820. background: #f2f3f5;
  821. }
  822. .scroll-container {
  823. display: flex;
  824. flex-direction: column;
  825. row-gap: 8px;
  826. margin: 6px;
  827. .list-item {
  828. display: flex;
  829. align-items: center;
  830. cursor: pointer;
  831. border: 1px solid #ccc;
  832. border-radius: 8px;
  833. :deep .el-slider {
  834. .el-slider__runway {
  835. background-color: #eee;
  836. }
  837. }
  838. .el-image {
  839. display: flex;
  840. width: 100%;
  841. min-width: 100%;
  842. height: 90px;
  843. background-color: #ccc;
  844. border-radius: 8px;
  845. }
  846. .video-play {
  847. width: 100%;
  848. min-width: 100%;
  849. }
  850. .text-box {
  851. word-break: break-word;
  852. }
  853. }
  854. }
  855. p {
  856. color: #999;
  857. text-align: center;
  858. }
  859. .card-box li {
  860. padding: 10px;
  861. border-bottom: 1px solid #ccc;
  862. .el-icon-notebook-2 {
  863. display: block;
  864. margin-bottom: 4px;
  865. font-size: 12px;
  866. color: grey;
  867. }
  868. }
  869. }
  870. }
  871. .back-top {
  872. position: absolute;
  873. bottom: 0;
  874. left: 0;
  875. display: flex;
  876. place-content: center center;
  877. align-items: center;
  878. width: 60px;
  879. height: 60px;
  880. cursor: pointer;
  881. }
  882. }
  883. }
  884. }
  885. :deep .scroll-container .audio-wrapper {
  886. width: 100% !important;
  887. .audio-middle {
  888. width: 100% !important;
  889. padding: 6px 8px !important;
  890. border: none;
  891. border-radius: 8px;
  892. .audio-name {
  893. text-align: left;
  894. }
  895. .slider-area {
  896. column-gap: 8px !important;
  897. }
  898. :deep .remark-dialog {
  899. .el-dialog__body {
  900. padding: 5px 20px;
  901. }
  902. }
  903. :deep .audit-dialog {
  904. .el-dialog__body {
  905. height: calc(100vh - 260px);
  906. padding: 5px 20px;
  907. }
  908. .mind-map-container .mind-map {
  909. height: calc(100vh - 310px);
  910. }
  911. }
  912. }
  913. }
  914. .mt10 {
  915. margin: 10px 0 0 !important;
  916. background-color: #eee;
  917. }
  918. .courseware-tree {
  919. display: flex;
  920. flex: 1;
  921. flex-direction: column;
  922. row-gap: 8px;
  923. max-height: calc(100vh - 200px);
  924. overflow: auto;
  925. .menu-item {
  926. display: flex;
  927. align-items: center;
  928. &:not(.courseware) {
  929. font-weight: bold;
  930. }
  931. &.courseware {
  932. &:hover {
  933. .name {
  934. background-color: #f3f3f3;
  935. }
  936. }
  937. }
  938. .svg-icon {
  939. margin-left: 4px;
  940. &.my-edit-task {
  941. color: $right-color;
  942. }
  943. }
  944. .name {
  945. flex: 1;
  946. padding: 4px 8px 4px 4px;
  947. border-radius: 4px;
  948. }
  949. &.active {
  950. .name {
  951. font-weight: bold;
  952. color: #4095e5;
  953. }
  954. }
  955. }
  956. }
  957. </style>
  958. <style lang="scss">
  959. .tox-tinymce-aux {
  960. z-index: 9999 !important;
  961. }
  962. </style>