index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. <template>
  2. <div class="common-preview">
  3. <div class="common-preview__header">
  4. <div class="menu-container">
  5. {{ courseware_info.book_name }}
  6. </div>
  7. </div>
  8. <div class="audit-content">
  9. <div class="content-left">
  10. <MenuPopover :node-list="node_list" type="mobile" @selectNode="selectNode" />
  11. </div>
  12. <div ref="previewMain" class="main-container">
  13. <main :class="['preview-main']">
  14. <MobileCoursewarePreview
  15. v-if="courseware_info.book_name"
  16. ref="courserware"
  17. :is-show-group="isShowGroup"
  18. :group-show-all="groupShowAll"
  19. :group-row-list="content_group_row_list"
  20. :data="data"
  21. :component-list="component_list"
  22. :background="background"
  23. :can-remark="isTrue(courseware_info.is_my_audit_task) && isTrue(courseware_info.is_can_add_audit_remark)"
  24. :show-remark="false"
  25. :component-remark-obj="remark_list_obj"
  26. @computeScroll="computeScroll"
  27. />
  28. </main>
  29. </div>
  30. <aside ref="sidebarMenu" class="sidebar" :style="{ height: sidebarShow ? '300px' : '60px' }">
  31. <aside class="toolbar">
  32. <div class="toolbar-special">
  33. <img :src="require('@/assets/icon/sidebar-fullscreen.png')" alt="全屏" />
  34. <img :src="require('@/assets/icon/sidebar-toolkit.png')" alt="工具箱" />
  35. </div>
  36. <div v-if="sidebarShow" class="toolbar-list">
  37. <div
  38. v-for="{ icon, title, handle, param } in sidebarIconList"
  39. :key="icon"
  40. :class="['sidebar-item', { active: curToolbarIcon === icon }]"
  41. :title="title"
  42. @click="handleSidebarClick(handle, param, icon)"
  43. >
  44. <div
  45. class="sidebar-icon icon-mask"
  46. :style="{
  47. backgroundColor: curToolbarIcon === icon ? '#fff' : '#1E2129',
  48. maskImage: `url(${require(`@/assets/icon/sidebar-${icon}.png`)})`,
  49. }"
  50. ></div>
  51. </div>
  52. </div>
  53. <div class="adjustable" @click="toggleSidebarShow">
  54. <img :src="require(`@/assets/icon/arrow-up.png`)" alt="伸缩" />
  55. </div>
  56. </aside>
  57. <div class="content"></div>
  58. </aside>
  59. </div>
  60. </div>
  61. </template>
  62. <script>
  63. import MobileCoursewarePreview from '@/views/book/courseware/preview/MobileCoursewarePreview.vue';
  64. import MenuPopover from '@/views/personal_workbench/common/MenuPopover.vue';
  65. import { isTrue } from '@/utils/validate';
  66. import * as OpenCC from 'opencc-js';
  67. import { GetBookCoursewareInfo, GetCoursewareAuditRemarkList } from '@/api/project';
  68. import {
  69. ContentGetCoursewareContent_View,
  70. ChapterGetBookChapterStructExpandList,
  71. GetBookBaseInfo,
  72. GetLanguageTypeList,
  73. GetBookUnifiedAttrib,
  74. } from '@/api/book';
  75. import { getLocalStore } from '@/utils/auth';
  76. export default {
  77. name: 'CommonPreview',
  78. components: {
  79. MobileCoursewarePreview,
  80. MenuPopover,
  81. },
  82. provide() {
  83. return {
  84. getLang: () => this.lang,
  85. getChinese: () => this.chinese,
  86. getLangList: () => this.langList,
  87. convertText: this.convertText,
  88. getSelectId: () => this.select_node,
  89. };
  90. },
  91. data() {
  92. const sidebarIconList = [
  93. // { icon: 'search', title: '搜索', handle: '', param: {} },
  94. { icon: 'mindmap', title: '思维导图', handle: 'openMindMap', param: {} },
  95. { icon: 'knowledge', title: '知识图谱', handle: 'openVisNetwork', param: {} },
  96. { icon: 'search', title: '搜索', handle: 'getSearch', param: { type: '5' } },
  97. // { icon: 'totalResources', title: '总资源', handle: '', param: {} },
  98. { icon: 'collect', title: '收藏', handle: 'getCollect', param: { type: '3' } },
  99. { icon: 'audio', title: '音频', handle: 'openDrawer', param: { type: '1' } },
  100. { icon: 'image', title: '图片', handle: 'openDrawer', param: { type: '0' } },
  101. { icon: 'video', title: '视频', handle: 'openDrawer', param: { type: '2' } },
  102. { icon: 'note', title: '笔记', handle: 'getNote', param: { type: '4' } },
  103. // { icon: 'translate', title: '翻译', handle: '', param: {} },
  104. // { icon: 'setting', title: '设置', handle: '', param: {} },
  105. ];
  106. const book_id = getLocalStore('book_id') || '';
  107. return {
  108. book_id,
  109. select_node: '',
  110. courseware_info: {
  111. book_name: '',
  112. is_can_start_edit: 'false',
  113. is_can_submit_audit: 'false',
  114. is_can_audit_pass: 'false',
  115. is_can_audit_reject: 'false',
  116. is_can_add_audit_remark: 'false',
  117. is_can_finish_audit: 'false',
  118. is_can_request_shangjia_book: 'false',
  119. is_can_request_rollback_project: 'false',
  120. is_can_shangjia_book: 'false',
  121. is_can_rollback_project: 'false',
  122. },
  123. background: {
  124. background_image_url: '',
  125. background_position: {
  126. left: 0,
  127. top: 0,
  128. },
  129. },
  130. node_list: [],
  131. data: { row_list: [] },
  132. component_list: [],
  133. content_group_row_list: [],
  134. remark_list: [],
  135. remark_list_obj: {}, // 存放以组件为对象的数组
  136. visible: false,
  137. remark_content: '',
  138. submit_loading: false,
  139. isTrue,
  140. menuPosition: {
  141. x: -1,
  142. y: -1,
  143. componentId: 'WHOLE',
  144. },
  145. sidebarIconList,
  146. visibleMindMap: false,
  147. isChildDataLoad: false,
  148. mindMapJsonData: {}, // 思维导图json数据
  149. drawerType: '', // 抽屉类型
  150. drawerStyle: {
  151. top: '0',
  152. height: '0',
  153. right: '0',
  154. },
  155. page_capacity: 10,
  156. cur_page: 1,
  157. file_list: [],
  158. total_count: 0,
  159. loading: false,
  160. isShowGroup: false,
  161. groupShowAll: true,
  162. opencc: OpenCC.Converter({ from: 'cn', to: 'tw' }),
  163. langList: [],
  164. lang: 'ZH',
  165. chinese: 'zh-Hans',
  166. isJudgeCorrect: false,
  167. isShowAnswer: false,
  168. unified_attrib: {},
  169. sidebarShow: true,
  170. curToolbarIcon: '',
  171. };
  172. },
  173. computed: {
  174. disabled() {
  175. return this.loading || this.noMore;
  176. },
  177. noMore() {
  178. return this.file_list.length >= this.total_count && this.total_count > 0;
  179. },
  180. },
  181. watch: {
  182. isJudgeCorrect(newVal) {
  183. if (!newVal) {
  184. this.isShowAnswer = false;
  185. }
  186. this.simulateAnswer(newVal);
  187. },
  188. isShowAnswer() {
  189. this.simulateAnswer();
  190. },
  191. },
  192. created() {
  193. this.getBookBaseInfo();
  194. this.getBookChapterStructExpandList();
  195. this.getBookUnifiedAttr();
  196. },
  197. methods: {
  198. getBookBaseInfo() {
  199. GetBookBaseInfo({ id: this.book_id }).then(({ book_info }) => {
  200. this.courseware_info = { ...this.courseware_info, ...book_info, book_name: book_info.name };
  201. });
  202. },
  203. /**
  204. * 得到教材课件信息
  205. * @param {string} id - 课件ID
  206. */
  207. getBookCoursewareInfo(id) {
  208. GetBookCoursewareInfo({ id, is_contain_producer: 'true', is_contain_auditor: 'true' }).then(
  209. ({ courseware_info }) => {
  210. this.courseware_info = { ...this.courseware_info, ...courseware_info };
  211. this.getLangList();
  212. },
  213. );
  214. },
  215. /**
  216. * 得到课件内容(展示内容)
  217. * @param {string} id - 课件ID
  218. */
  219. getCoursewareComponentContent_View(id) {
  220. ContentGetCoursewareContent_View({ id }).then(({ content, component_list, content_group_row_list }) => {
  221. if (content) {
  222. const _content = JSON.parse(content);
  223. this.data = _content;
  224. this.background = {
  225. background_image_url: _content.background_image_url,
  226. background_position: _content.background_position,
  227. };
  228. } else {
  229. this.data = { row_list: [] };
  230. }
  231. if (component_list) this.component_list = component_list;
  232. this.component_list.forEach((x) => {
  233. if (x.component_type === 'audio') {
  234. let _c = JSON.parse(x.content);
  235. let p = _c.property || {};
  236. if (!p.file_name_display_mode) p.file_name_display_mode = 'true';
  237. if (p.view_method === 'independent' && !p.style_mode) {
  238. p.style_mode = 'middle';
  239. }
  240. if (!p.style_mode) p.style_mode = 'big';
  241. if (p.view_method === 'icon') {
  242. p.file_name_display_mode = 'false';
  243. p.view_method = 'independent';
  244. p.style_mode = 'small';
  245. }
  246. x.content = JSON.stringify(_c);
  247. }
  248. });
  249. if (content_group_row_list) this.content_group_row_list = JSON.parse(content_group_row_list) || [];
  250. });
  251. },
  252. getLangList() {
  253. GetLanguageTypeList({ book_id: this.courseware_info.book_id, is_contain_zh: 'true' }).then(
  254. ({ language_type_list }) => {
  255. this.langList = language_type_list;
  256. },
  257. );
  258. },
  259. /**
  260. * 得到教材章节结构展开列表
  261. */
  262. getBookChapterStructExpandList() {
  263. ChapterGetBookChapterStructExpandList({
  264. book_id: this.book_id,
  265. node_deep_mode: 0,
  266. is_contain_producer: 'true',
  267. is_contain_auditor: 'true',
  268. }).then(({ node_list }) => {
  269. this.node_list = node_list;
  270. });
  271. },
  272. getBookUnifiedAttr() {
  273. GetBookUnifiedAttrib({ book_id: this.book_id }).then(({ content }) => {
  274. if (content) {
  275. this.unified_attrib = JSON.parse(content);
  276. }
  277. });
  278. },
  279. /**
  280. * 选择节点
  281. * @param {string} nodeId - 节点ID
  282. */
  283. selectNode(nodeId) {
  284. this.getCoursewareComponentContent_View(nodeId);
  285. this.getBookCoursewareInfo(nodeId);
  286. this.getCoursewareAuditRemarkList(nodeId);
  287. this.select_node = nodeId;
  288. },
  289. // 审校批注列表
  290. getCoursewareAuditRemarkList(id) {
  291. this.remark_list = [];
  292. let remarkListObj = {};
  293. GetCoursewareAuditRemarkList({
  294. courseware_id: id,
  295. }).then(({ remark_list }) => {
  296. this.remark_list = remark_list;
  297. if (!remark_list) return;
  298. remarkListObj = remark_list.reduce((acc, item) => {
  299. if (!acc[item.component_id]) {
  300. acc[item.component_id] = [];
  301. }
  302. acc[item.component_id].push(item);
  303. return acc;
  304. }, {});
  305. this.remark_list_obj = remarkListObj;
  306. });
  307. },
  308. dialogClose(type) {
  309. this[`visible${type}`] = false;
  310. },
  311. // 计算previewMain滑动距离
  312. computeScroll() {
  313. this.$refs.courserware.handleResult(
  314. this.$refs.previewMain.scrollTop,
  315. this.$refs.previewMain.scrollLeft,
  316. this.select_node,
  317. );
  318. },
  319. /**
  320. * 文本转换
  321. * @param {string} text - 要转换的文本
  322. * @returns {string} - 转换后的文本
  323. */
  324. convertText(text) {
  325. if (this.chinese === 'zh-Hant' && this.opencc) {
  326. return this.opencc(text);
  327. }
  328. return text;
  329. },
  330. simulateAnswer(disabled = true) {
  331. this.$refs.courserware.simulateAnswer(this.isJudgeCorrect, this.isShowAnswer, disabled);
  332. },
  333. /**
  334. * 切换右侧工具栏显示与隐藏
  335. */
  336. toggleSidebarShow() {
  337. this.sidebarShow = !this.sidebarShow;
  338. },
  339. },
  340. };
  341. </script>
  342. <style lang="scss" scoped>
  343. @use '@/styles/mixin.scss' as *;
  344. .common-preview {
  345. &__header {
  346. position: sticky;
  347. top: 0;
  348. left: 0;
  349. z-index: 9;
  350. display: flex;
  351. align-items: center;
  352. height: 40px;
  353. padding: 6px 4px;
  354. margin-bottom: 5px;
  355. background-color: #fff;
  356. border-top: $border;
  357. border-bottom: $border;
  358. > .menu-container {
  359. display: flex;
  360. justify-content: space-between;
  361. width: 360px;
  362. padding: 4px 8px;
  363. border-right: $border;
  364. }
  365. > .courseware {
  366. display: flex;
  367. flex-grow: 1;
  368. column-gap: 16px;
  369. align-items: center;
  370. justify-content: space-between;
  371. height: 40px;
  372. .name-path {
  373. min-width: 200px;
  374. height: 40px;
  375. padding: 4px 8px;
  376. font-size: 14px;
  377. line-height: 32px;
  378. border-right: $border;
  379. }
  380. .lang-select {
  381. :deep .el-input {
  382. width: 100px;
  383. }
  384. :deep .el-input__inner {
  385. height: 24px;
  386. line-height: 24px;
  387. background-color: #fff;
  388. }
  389. :deep .el-input__icon {
  390. line-height: 24px;
  391. }
  392. }
  393. .flow-nodename {
  394. flex: 1;
  395. }
  396. .group {
  397. display: flex;
  398. align-items: center;
  399. }
  400. .operator {
  401. display: flex;
  402. column-gap: 8px;
  403. align-items: center;
  404. .link {
  405. + .link {
  406. margin-left: 0;
  407. &::before {
  408. margin-right: 8px;
  409. color: #999;
  410. content: '|';
  411. }
  412. }
  413. }
  414. }
  415. }
  416. }
  417. .main-container {
  418. flex: 1;
  419. width: 100%;
  420. overflow: auto;
  421. :deep img {
  422. max-width: 100% !important;
  423. }
  424. }
  425. main.preview-main {
  426. display: flex;
  427. flex: 1;
  428. flex-direction: column;
  429. row-gap: 5px;
  430. width: 100%;
  431. min-height: 100%;
  432. padding: 5px;
  433. margin: 0 auto;
  434. background-color: #fff;
  435. border-radius: 4px;
  436. box-shadow: 0 2px 4px rgba(0, 0, 0, 10%);
  437. }
  438. .audit-content {
  439. display: flex;
  440. width: 100%;
  441. height: calc(100vh - 56px);
  442. .content-left,
  443. .sidebar {
  444. width: 20px;
  445. }
  446. .content-left {
  447. display: flex;
  448. align-items: center;
  449. justify-content: center;
  450. height: 20px;
  451. margin-right: 4px;
  452. background-color: #fff;
  453. }
  454. .sidebar {
  455. padding: 4px;
  456. margin-left: 4px;
  457. background-color: #fff;
  458. .toolbar {
  459. display: flex;
  460. flex-direction: column;
  461. align-items: center;
  462. height: 100%;
  463. img {
  464. cursor: pointer;
  465. }
  466. &-special {
  467. display: flex;
  468. flex-direction: column;
  469. row-gap: 6px;
  470. margin-bottom: 8px;
  471. img {
  472. width: 12px;
  473. height: 12px;
  474. }
  475. }
  476. &-list {
  477. display: flex;
  478. flex-direction: column;
  479. row-gap: 6px;
  480. align-items: center;
  481. width: 100%;
  482. .sidebar-item {
  483. width: 100%;
  484. text-align: center;
  485. .sidebar-icon {
  486. width: 12px;
  487. height: 12px;
  488. cursor: pointer;
  489. }
  490. &.active {
  491. background-color: #4095e5;
  492. }
  493. }
  494. }
  495. .adjustable {
  496. img {
  497. width: 12px;
  498. height: 12px;
  499. }
  500. }
  501. }
  502. .content {
  503. flex: 1;
  504. background-color: #fff;
  505. }
  506. }
  507. }
  508. }
  509. </style>
  510. <style lang="scss">
  511. .tox-tinymce-aux {
  512. z-index: 9999 !important;
  513. }
  514. </style>