index.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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="courseware"
  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(
  221. ({ content, component_list, content_group_row_list, background }) => {
  222. if (background) {
  223. this.background = JSON.parse(background);
  224. }
  225. if (content) {
  226. const _content = JSON.parse(content);
  227. this.data = _content;
  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. },
  253. getLangList() {
  254. GetLanguageTypeList({
  255. book_id: this.courseware_info.book_id,
  256. is_contain_zh: 'true',
  257. is_only_can_translation: 'true',
  258. }).then(({ language_type_list }) => {
  259. this.langList = language_type_list;
  260. });
  261. },
  262. /**
  263. * 得到教材章节结构展开列表
  264. */
  265. getBookChapterStructExpandList() {
  266. ChapterGetBookChapterStructExpandList({
  267. book_id: this.book_id,
  268. node_deep_mode: 0,
  269. is_contain_producer: 'true',
  270. is_contain_auditor: 'true',
  271. }).then(({ node_list }) => {
  272. this.node_list = node_list;
  273. });
  274. },
  275. getBookUnifiedAttr() {
  276. GetBookUnifiedAttrib({ book_id: this.book_id }).then(({ content }) => {
  277. if (content) {
  278. this.unified_attrib = JSON.parse(content);
  279. }
  280. });
  281. },
  282. /**
  283. * 选择节点
  284. * @param {string} nodeId - 节点ID
  285. */
  286. selectNode(nodeId) {
  287. this.getCoursewareComponentContent_View(nodeId);
  288. this.getBookCoursewareInfo(nodeId);
  289. this.getCoursewareAuditRemarkList(nodeId);
  290. this.select_node = nodeId;
  291. },
  292. // 审校批注列表
  293. getCoursewareAuditRemarkList(id) {
  294. this.remark_list = [];
  295. let remarkListObj = {};
  296. GetCoursewareAuditRemarkList({
  297. courseware_id: id,
  298. }).then(({ remark_list }) => {
  299. this.remark_list = remark_list;
  300. if (!remark_list) return;
  301. remarkListObj = remark_list.reduce((acc, item) => {
  302. if (!acc[item.component_id]) {
  303. acc[item.component_id] = [];
  304. }
  305. acc[item.component_id].push(item);
  306. return acc;
  307. }, {});
  308. this.remark_list_obj = remarkListObj;
  309. });
  310. },
  311. dialogClose(type) {
  312. this[`visible${type}`] = false;
  313. },
  314. // 计算previewMain滑动距离
  315. computeScroll() {
  316. this.$refs.courseware.handleResult(
  317. this.$refs.previewMain.scrollTop,
  318. this.$refs.previewMain.scrollLeft,
  319. this.select_node,
  320. );
  321. },
  322. /**
  323. * 文本转换
  324. * @param {string} text - 要转换的文本
  325. * @returns {string} - 转换后的文本
  326. */
  327. convertText(text) {
  328. if (this.chinese === 'zh-Hant' && this.opencc) {
  329. return this.opencc(text);
  330. }
  331. return text;
  332. },
  333. simulateAnswer(disabled = true) {
  334. this.$refs.courseware.simulateAnswer(this.isJudgeCorrect, this.isShowAnswer, disabled);
  335. },
  336. /**
  337. * 切换右侧工具栏显示与隐藏
  338. */
  339. toggleSidebarShow() {
  340. this.sidebarShow = !this.sidebarShow;
  341. },
  342. },
  343. };
  344. </script>
  345. <style lang="scss" scoped>
  346. @use '@/styles/mixin.scss' as *;
  347. .common-preview {
  348. &__header {
  349. position: sticky;
  350. top: 0;
  351. left: 0;
  352. z-index: 9;
  353. display: flex;
  354. align-items: center;
  355. height: 40px;
  356. padding: 6px 4px;
  357. margin-bottom: 5px;
  358. background-color: #fff;
  359. border-top: $border;
  360. border-bottom: $border;
  361. > .menu-container {
  362. display: flex;
  363. justify-content: space-between;
  364. width: 360px;
  365. padding: 4px 8px;
  366. border-right: $border;
  367. }
  368. > .courseware {
  369. display: flex;
  370. flex-grow: 1;
  371. column-gap: 16px;
  372. align-items: center;
  373. justify-content: space-between;
  374. height: 40px;
  375. .name-path {
  376. min-width: 200px;
  377. height: 40px;
  378. padding: 4px 8px;
  379. font-size: 14px;
  380. line-height: 32px;
  381. border-right: $border;
  382. }
  383. .lang-select {
  384. :deep .el-input {
  385. width: 100px;
  386. }
  387. :deep .el-input__inner {
  388. height: 24px;
  389. line-height: 24px;
  390. background-color: #fff;
  391. }
  392. :deep .el-input__icon {
  393. line-height: 24px;
  394. }
  395. }
  396. .flow-nodename {
  397. flex: 1;
  398. }
  399. .group {
  400. display: flex;
  401. align-items: center;
  402. }
  403. .operator {
  404. display: flex;
  405. column-gap: 8px;
  406. align-items: center;
  407. .link {
  408. + .link {
  409. margin-left: 0;
  410. &::before {
  411. margin-right: 8px;
  412. color: #999;
  413. content: '|';
  414. }
  415. }
  416. }
  417. }
  418. }
  419. }
  420. .main-container {
  421. flex: 1;
  422. width: 100%;
  423. overflow: auto;
  424. :deep img {
  425. max-width: 100% !important;
  426. }
  427. }
  428. main.preview-main {
  429. display: flex;
  430. flex: 1;
  431. flex-direction: column;
  432. row-gap: 5px;
  433. width: 100%;
  434. min-height: 100%;
  435. padding: 5px;
  436. margin: 0 auto;
  437. background-color: #fff;
  438. border-radius: 4px;
  439. box-shadow: 0 2px 4px rgba(0, 0, 0, 10%);
  440. }
  441. .audit-content {
  442. display: flex;
  443. width: 100%;
  444. height: calc(100vh - 56px);
  445. .content-left,
  446. .sidebar {
  447. width: 20px;
  448. }
  449. .content-left {
  450. display: flex;
  451. align-items: center;
  452. justify-content: center;
  453. height: 20px;
  454. margin-right: 4px;
  455. background-color: #fff;
  456. }
  457. .sidebar {
  458. padding: 4px;
  459. margin-left: 4px;
  460. background-color: #fff;
  461. .toolbar {
  462. display: flex;
  463. flex-direction: column;
  464. align-items: center;
  465. height: 100%;
  466. img {
  467. cursor: pointer;
  468. }
  469. &-special {
  470. display: flex;
  471. flex-direction: column;
  472. row-gap: 6px;
  473. margin-bottom: 8px;
  474. img {
  475. width: 12px;
  476. height: 12px;
  477. }
  478. }
  479. &-list {
  480. display: flex;
  481. flex-direction: column;
  482. row-gap: 6px;
  483. align-items: center;
  484. width: 100%;
  485. .sidebar-item {
  486. width: 100%;
  487. text-align: center;
  488. .sidebar-icon {
  489. width: 12px;
  490. height: 12px;
  491. cursor: pointer;
  492. }
  493. &.active {
  494. background-color: #4095e5;
  495. }
  496. }
  497. }
  498. .adjustable {
  499. img {
  500. width: 12px;
  501. height: 12px;
  502. }
  503. }
  504. }
  505. .content {
  506. flex: 1;
  507. background-color: #fff;
  508. }
  509. }
  510. }
  511. }
  512. </style>
  513. <style lang="scss">
  514. .tox-tinymce-aux {
  515. z-index: 9999 !important;
  516. }
  517. </style>