CommonPreview.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. <template>
  2. <div class="common-preview">
  3. <div class="common-preview__header">
  4. <div class="menu-container">
  5. <MenuPopover :id="id" :node-list="node_list" :book-name="courseware_info.book_name" @selectNode="selectNode" />
  6. </div>
  7. <div class="courseware">
  8. <span class="name-path">{{ courseware_info.name_path }}</span>
  9. <span class="flow-nodename">{{ courseware_info.cur_audit_flow_node_name }}</span>
  10. <slot name="middle" :courseware="courseware_info"></slot>
  11. <div class="operator">
  12. <slot name="operator" :courseware="courseware_info"></slot>
  13. </div>
  14. </div>
  15. </div>
  16. <div class="audit-content">
  17. <div class="main-container" ref="previewMain">
  18. <main :class="['preview-main', { 'no-audit': !isShowAudit }]">
  19. <span class="title">
  20. <SvgIcon icon-class="menu-2" size="24" />
  21. <span>{{ courseware_info.name_path }}</span>
  22. </span>
  23. <CoursewarePreview
  24. :data="data"
  25. :component-list="component_list"
  26. :background="background"
  27. :can-remark="isTrue(courseware_info.is_my_audit_task) && isTrue(courseware_info.is_can_add_audit_remark)"
  28. :show-remark="isShowAudit"
  29. :component-remark-obj="remark_list_obj"
  30. @computeScroll="computeScroll"
  31. @addRemark="addRemark"
  32. ref="courserware"
  33. v-if="courseware_info.book_name"
  34. />
  35. </main>
  36. </div>
  37. <div v-if="isShowAudit" class="remark-list">
  38. <h5>审校批注</h5>
  39. <ul v-if="remark_list.length > 0">
  40. <li v-for="{ id: remarkId, content, remark_person_name, remark_time } in remark_list" :key="remarkId">
  41. <!-- eslint-disable-next-line vue/no-v-html -->
  42. <p v-html="content"></p>
  43. <div v-if="isAudit" class="remark-bottom">
  44. <span>{{ remark_person_name + ':' + remark_time }}</span>
  45. <el-button type="text" class="delete-btn" @click="deleteRemarks(remarkId)">删除</el-button>
  46. </div>
  47. </li>
  48. </ul>
  49. <p v-else style="text-align: center">暂无批注</p>
  50. </div>
  51. </div>
  52. <el-dialog
  53. title="添加课件审校批注"
  54. :visible="visible"
  55. width="680px"
  56. :close-on-click-modal="false"
  57. class="audit-dialog"
  58. @close="dialogClose"
  59. >
  60. <RichText
  61. v-model="remark_content"
  62. toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
  63. :wordlimit-num="false"
  64. :height="240"
  65. page-from="audit"
  66. />
  67. <div slot="footer">
  68. <el-button @click="dialogClose">取消</el-button>
  69. <el-button type="primary" :loading="submit_loading" @click="addCoursewareAuditRemark(select_node)">
  70. 确定
  71. </el-button>
  72. </div>
  73. </el-dialog>
  74. </div>
  75. </template>
  76. <script>
  77. import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview.vue';
  78. import MenuPopover from '@/views/personal_workbench/common/MenuPopover.vue';
  79. import RichText from '@/components/RichText.vue';
  80. import { isTrue } from '@/utils/common';
  81. import {
  82. GetBookCoursewareInfo,
  83. GetProjectBaseInfo,
  84. GetCoursewareAuditRemarkList,
  85. AddCoursewareAuditRemark,
  86. DeleteCoursewareAuditRemarkList,
  87. } from '@/api/project';
  88. import { ContentGetCoursewareContent_View, ChapterGetBookChapterStructExpandList, GetBookBaseInfo } from '@/api/book';
  89. export default {
  90. name: 'CommonPreview',
  91. components: {
  92. CoursewarePreview,
  93. MenuPopover,
  94. RichText,
  95. },
  96. props: {
  97. projectId: {
  98. type: String,
  99. required: true,
  100. },
  101. id: {
  102. type: String,
  103. default: '',
  104. },
  105. // 是否是审校页面
  106. isAudit: {
  107. type: Boolean,
  108. default: false,
  109. },
  110. isShowAudit: {
  111. type: Boolean,
  112. default: true,
  113. },
  114. isBook: {
  115. type: Boolean,
  116. default: false,
  117. },
  118. },
  119. data() {
  120. return {
  121. select_node: this.id,
  122. courseware_info: {
  123. book_name: '',
  124. is_can_start_edit: 'false',
  125. is_can_submit_audit: 'false',
  126. is_can_audit_pass: 'false',
  127. is_can_audit_reject: 'false',
  128. is_can_add_audit_remark: 'false',
  129. is_can_finish_audit: 'false',
  130. is_can_request_shangjia_book: 'false',
  131. is_can_request_rollback_project: 'false',
  132. is_can_shangjia_book: 'false',
  133. is_can_rollback_project: 'false',
  134. },
  135. background: {
  136. background_image_url: '',
  137. background_position: {
  138. left: 0,
  139. top: 0,
  140. },
  141. },
  142. node_list: [],
  143. data: { row_list: [] },
  144. component_list: [],
  145. remark_list: [],
  146. remark_list_obj: {}, // 存放转为以组件为对象的数组
  147. visible: false,
  148. remark_content: '',
  149. submit_loading: false,
  150. isTrue,
  151. menuPosition: {
  152. x: -1,
  153. y: -1,
  154. componentId: 'WHOLE',
  155. },
  156. };
  157. },
  158. created() {
  159. if (this.id) {
  160. this.getBookCoursewareInfo(this.id);
  161. this.getCoursewareComponentContent_View(this.id);
  162. this.getCoursewareAuditRemarkList(this.id);
  163. } else {
  164. this.isBook ? this.getBookBaseInfo() : this.getProjectBaseInfo();
  165. }
  166. this.getBookChapterStructExpandList();
  167. },
  168. methods: {
  169. getProjectBaseInfo() {
  170. GetProjectBaseInfo({ id: this.projectId }).then(({ project_info }) => {
  171. this.courseware_info = { ...project_info, book_name: project_info.name };
  172. });
  173. },
  174. getBookBaseInfo() {
  175. GetBookBaseInfo({ id: this.projectId }).then(({ book_info }) => {
  176. this.courseware_info = { ...this.courseware_info, ...book_info, book_name: book_info.name };
  177. });
  178. },
  179. /**
  180. * 得到教材课件信息
  181. * @param {string} id - 课件ID
  182. */
  183. getBookCoursewareInfo(id) {
  184. GetBookCoursewareInfo({ id, is_contain_producer: 'true', is_contain_auditor: 'true' }).then(
  185. ({ courseware_info }) => {
  186. this.courseware_info = { ...this.courseware_info, ...courseware_info };
  187. },
  188. );
  189. },
  190. /**
  191. * 得到课件内容(展示内容)
  192. * @param {string} id - 课件ID
  193. */
  194. getCoursewareComponentContent_View(id) {
  195. ContentGetCoursewareContent_View({ id }).then(({ content, component_list }) => {
  196. if (content) {
  197. this.data = JSON.parse(content);
  198. } else {
  199. this.data = { row_list: [] };
  200. }
  201. if (component_list) this.component_list = component_list;
  202. });
  203. },
  204. /**
  205. * 得到教材章节结构展开列表
  206. */
  207. getBookChapterStructExpandList() {
  208. ChapterGetBookChapterStructExpandList({
  209. book_id: this.projectId,
  210. node_deep_mode: 0,
  211. is_contain_producer: 'true',
  212. is_contain_auditor: 'true',
  213. }).then(({ node_list }) => {
  214. this.node_list = node_list;
  215. });
  216. },
  217. /**
  218. * 选择节点
  219. * @param {string} nodeId - 节点ID
  220. */
  221. selectNode(nodeId) {
  222. this.getCoursewareComponentContent_View(nodeId);
  223. this.getBookCoursewareInfo(nodeId);
  224. this.getCoursewareAuditRemarkList(nodeId);
  225. this.select_node = nodeId;
  226. },
  227. // 审校批注列表
  228. getCoursewareAuditRemarkList(id) {
  229. this.remark_list = [];
  230. this.remark_list_obj = {};
  231. GetCoursewareAuditRemarkList({
  232. courseware_id: id,
  233. }).then(({ remark_list }) => {
  234. this.remark_list = remark_list;
  235. remark_list.forEach((item) => {
  236. // 组件的审批
  237. if (item.component_id !== 'WHOLE') {
  238. if (!this.remark_list_obj[item.component_id]) {
  239. this.remark_list_obj[item.component_id] = [];
  240. }
  241. this.remark_list_obj[item.component_id].push(item);
  242. }
  243. });
  244. });
  245. },
  246. addRemark(selectNode, x, y, componentId) {
  247. this.remark_content = '';
  248. this.visible = true;
  249. if (selectNode) {
  250. this.menuPosition = {
  251. x: x,
  252. y: y,
  253. componentId: componentId,
  254. };
  255. } else {
  256. this.menuPosition = {
  257. x: -1,
  258. y: -1,
  259. componentId: 'WHOLE',
  260. };
  261. }
  262. },
  263. dialogClose() {
  264. this.visible = false;
  265. },
  266. // 添加审校批注
  267. addCoursewareAuditRemark(id) {
  268. this.submit_loading = true;
  269. AddCoursewareAuditRemark({
  270. courseware_id: id || this.id,
  271. content: this.remark_content,
  272. component_id: this.menuPosition.componentId,
  273. position_x: this.menuPosition.x,
  274. position_y: this.menuPosition.y,
  275. })
  276. .then(() => {
  277. this.submit_loading = false;
  278. this.visible = false;
  279. this.getCoursewareAuditRemarkList(id || this.id);
  280. })
  281. .catch(() => {
  282. this.submit_loading = false;
  283. });
  284. },
  285. // 删除批注
  286. deleteRemarks(id) {
  287. this.$confirm('确定要删除此条批注吗?', '提示', {
  288. confirmButtonText: '确定',
  289. cancelButtonText: '取消',
  290. type: 'warning',
  291. })
  292. .then(() => {
  293. DeleteCoursewareAuditRemarkList({ id }).then(() => {
  294. this.getCoursewareAuditRemarkList(this.select_node ? this.select_node : this.id);
  295. this.$message.success('删除成功!');
  296. });
  297. })
  298. .catch(() => {});
  299. },
  300. // 计算previewMain滑动距离
  301. computeScroll() {
  302. this.$refs.courserware.handleResult(
  303. this.$refs.previewMain.scrollTop,
  304. this.$refs.previewMain.scrollLeft,
  305. this.select_node,
  306. );
  307. },
  308. },
  309. };
  310. </script>
  311. <style lang="scss" scoped>
  312. @use '@/styles/mixin.scss' as *;
  313. .common-preview {
  314. &__header {
  315. position: sticky;
  316. top: 56px;
  317. left: 0;
  318. z-index: 9;
  319. display: flex;
  320. align-items: center;
  321. height: 40px;
  322. padding: 6px 4px;
  323. margin-bottom: 5px;
  324. background-color: #fff;
  325. border-top: $border;
  326. border-bottom: $border;
  327. > .menu-container {
  328. display: flex;
  329. justify-content: space-between;
  330. width: 360px;
  331. padding: 4px 8px;
  332. border-right: $border;
  333. }
  334. > .courseware {
  335. display: flex;
  336. flex-grow: 1;
  337. column-gap: 16px;
  338. align-items: center;
  339. justify-content: space-between;
  340. height: 40px;
  341. .name-path {
  342. min-width: 200px;
  343. height: 40px;
  344. padding: 4px 8px;
  345. font-size: 14px;
  346. line-height: 32px;
  347. border-right: $border;
  348. }
  349. .flow-nodename {
  350. flex: 1;
  351. }
  352. .operator {
  353. display: flex;
  354. column-gap: 8px;
  355. align-items: center;
  356. .link {
  357. + .link {
  358. margin-left: 0;
  359. &::before {
  360. margin-right: 8px;
  361. color: #999;
  362. content: '|';
  363. }
  364. }
  365. }
  366. }
  367. }
  368. }
  369. .main-container {
  370. flex: 1;
  371. min-width: 1110px;
  372. overflow: auto;
  373. }
  374. main.preview-main {
  375. display: flex;
  376. flex: 1;
  377. flex-direction: column;
  378. row-gap: 5px;
  379. width: 1100px;
  380. min-width: 1100px;
  381. min-height: 100%;
  382. padding: 5px;
  383. margin: 0 5px;
  384. background-color: #fff;
  385. border: 3px solid #f44444;
  386. border-radius: 4px;
  387. box-shadow: 0 2px 4px rgba(0, 0, 0, 10%);
  388. &.no-audit {
  389. margin: 0 auto;
  390. }
  391. .title {
  392. display: inline-flex;
  393. column-gap: 24px;
  394. align-items: center;
  395. width: 100%;
  396. min-width: 280px;
  397. height: 64px;
  398. padding: 18px 24px;
  399. font-size: 20px;
  400. color: #fff;
  401. background-color: #f44444;
  402. border-top-left-radius: 12px;
  403. border-bottom-right-radius: 16px;
  404. }
  405. }
  406. .audit-content {
  407. display: flex;
  408. column-gap: 20px;
  409. min-width: 1400px;
  410. height: calc(100vh - 175px);
  411. .remark-list {
  412. width: 300px;
  413. overflow: auto;
  414. border: 1px solid #e5e5e5;
  415. h5 {
  416. padding: 0 5px;
  417. margin: 0;
  418. font-size: 18px;
  419. line-height: 40px;
  420. background: #f2f3f5;
  421. }
  422. .delete-btn {
  423. padding-left: 10px;
  424. color: #f44444;
  425. border-left: 1px solid #e5e5e5;
  426. }
  427. ul {
  428. height: calc(100% - 40px);
  429. overflow: auto;
  430. li {
  431. border-bottom: 1px solid #e5e5e5;
  432. > p {
  433. padding: 5px;
  434. }
  435. :deep p {
  436. margin: 0;
  437. }
  438. .remark-bottom {
  439. display: flex;
  440. align-items: center;
  441. justify-content: space-between;
  442. padding: 0 5px;
  443. font-size: 14px;
  444. color: #555;
  445. border-top: 1px solid #e5e5e5;
  446. }
  447. }
  448. }
  449. }
  450. }
  451. }
  452. :deep .audit-dialog {
  453. .el-dialog__body {
  454. padding: 5px 20px;
  455. }
  456. }
  457. </style>
  458. <style lang="scss">
  459. .tox-tinymce-aux {
  460. z-index: 9999 !important;
  461. }
  462. </style>