index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. <template>
  2. <div class="edit-task" :class="[type && type !== 'personal' ? 'edit-task_template' : '']">
  3. <MenuPage
  4. v-if="!type || type === 'personal'"
  5. :only-key="type ? '/personal_workbench/template_list' : '/personal_workbench/edit_task'"
  6. />
  7. <div class="edit-task__header" :style="{ top: type && type !== 'personal' ? '0' : '' }">
  8. <div class="menu-container">
  9. <span class="name">{{ courseware_info.book_name }}</span>
  10. </div>
  11. <div class="courseware">
  12. <span class="name-path">{{ courseware_info.name_path }}</span>
  13. <div class="switch">
  14. <span :class="['link', { active: isEdit }]" @click="toggleEditMode(true)">内容编辑</span>
  15. <span :class="['link', { active: !isEdit }]" @click="toggleEditMode(false)">位置调整</span>
  16. </div>
  17. <div class="operator">
  18. <el-switch
  19. v-show="!isEdit"
  20. v-model="chinese"
  21. active-value="zh-Hant"
  22. inactive-value="zh-Hans"
  23. active-color="#ff4949"
  24. inactive-color="#165dff"
  25. active-text="繁"
  26. inactive-text="简"
  27. />
  28. <span v-if="!isEdit" class="link">
  29. <el-select v-model="lang" placeholder="请选择语言" size="mini" class="lang-select">
  30. <el-option v-for="item in langList" :key="item.type" :label="item.name" :value="item.type" />
  31. </el-select>
  32. </span>
  33. <template v-if="!type">
  34. <span class="link" @click="copyFormat">复制格式</span>
  35. <span :class="['link', { disabled: !format.isCopy }]" @click="pasteFormat">粘贴格式</span>
  36. <span class="link" @click="pasteComponent('bottom')">粘贴组件</span>
  37. <el-popover placement="bottom" width="100" trigger="click">
  38. <el-link type="primary" @click="pasteComponent('bottom')">粘贴到下方</el-link>
  39. <el-link type="primary" @click="pasteComponent('top')">粘贴到上方</el-link>
  40. <el-link type="primary" @click="pasteComponent('left')">粘贴到左侧</el-link>
  41. <el-link type="primary" @click="pasteComponent('right')">粘贴到右侧</el-link>
  42. <i slot="reference" class="link el-icon-caret-bottom" :style="{ margin: '0 -10px 0 -4px' }"></i>
  43. </el-popover>
  44. <span class="link"></span>
  45. <span class="link" @click="useTemplate">使用模板</span>
  46. </template>
  47. <span class="link" @click="showSetBackground">背景图</span>
  48. <span class="link" @click="saveCoursewareContent('quit')">退出编辑</span>
  49. <span class="link" @click="saveCoursewareContent">保存</span>
  50. <span v-if="isEdit && !type" class="link" @click="showFullTextSettings">全文设置</span>
  51. <span v-if="type" class="link" @click="goBackTemplateList">返回模板列表</span>
  52. <span v-else class="link" @click="goBackBookList">返回教材列表</span>
  53. </div>
  54. </div>
  55. </div>
  56. <CreatePage ref="create" class="edit-task__content" @goBackPreview="goBackPreview" />
  57. <UseTemplate :visible.sync="visibleTemplate" @useTemplate="handleUseTemplate" />
  58. </div>
  59. </template>
  60. <script>
  61. import CreatePage from '@/views/book/courseware/create/index.vue';
  62. import MenuPage from '@/views/personal_workbench/common/menu.vue';
  63. import UseTemplate from './UseTemplate.vue';
  64. import tinymce from 'tinymce';
  65. import * as OpenCC from 'opencc-js';
  66. import { GetBookCoursewareInfo, GetMyBookCoursewareTaskList } from '@/api/project';
  67. import { GetLanguageTypeList } from '@/api/book';
  68. export default {
  69. name: 'EditTaskPage',
  70. components: {
  71. CreatePage,
  72. MenuPage,
  73. UseTemplate,
  74. },
  75. provide() {
  76. return {
  77. getLang: () => this.lang,
  78. getChinese: () => this.chinese,
  79. getLangList: () => this.langList,
  80. convertText: this.convertText,
  81. getTemporaryCoursewareID: () => this.temporaryCoursewareID,
  82. };
  83. },
  84. data() {
  85. return {
  86. id: this.$route.params.courseware_id,
  87. project_id: this.$route.query.project_id,
  88. type: this.$route.query.template_type,
  89. courseware_info: {},
  90. courseware_list: [],
  91. isEdit: true, // 是否编辑状态
  92. opencc: OpenCC.Converter({ from: 'cn', to: 'tw' }),
  93. langList: [],
  94. lang: 'ZH',
  95. chinese: 'zh-Hans',
  96. visibleTemplate: false,
  97. temporaryCoursewareID: '',
  98. format: {
  99. isCopy: false, // 是否已复制格式
  100. font_size: 12, // 字体大小
  101. font_family: 'Arial', // 字体
  102. weight: 400, // 字体粗细
  103. color: '#000000', // 文字颜色
  104. text_decoration: 'none', // 装饰线样式
  105. font_style: 'normal', // 字体样式
  106. },
  107. };
  108. },
  109. created() {
  110. this.getBookCoursewareInfo();
  111. this.getMyBookCoursewareTaskList();
  112. },
  113. mounted() {
  114. this.$nextTick(() => {
  115. this.$watch(
  116. () => this.$refs.create.isEdit,
  117. (newVal) => {
  118. this.isEdit = newVal;
  119. },
  120. { immediate: true },
  121. );
  122. });
  123. },
  124. methods: {
  125. // 复制格式
  126. copyFormat() {
  127. const selection = tinymce.activeEditor.selection.getNode();
  128. let content = tinymce.activeEditor.selection.getContent({ format: 'text' });
  129. if (!content || content.trim() === '') {
  130. this.$message.warning('请先选中需要复制格式的文本');
  131. return;
  132. }
  133. // 向下找找到第一个字的父节点,获取样式
  134. let node = selection;
  135. // 如果选中了文本节点,使用它的父元素;如果选中了元素,查找第一个非空文本子节点并使用它的父元素,
  136. // 以保证获取到应用样式的元素而不是直接的文本节点,避免丢失样式
  137. if (node && node.nodeType === 3) {
  138. node = node.parentElement || node.parentNode;
  139. } else if (node && node.nodeType === 1) {
  140. const walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, {
  141. acceptNode(n) {
  142. return n.nodeValue && n.nodeValue.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
  143. },
  144. });
  145. const textNode = walker.nextNode();
  146. if (textNode && textNode.parentElement) {
  147. node = textNode.parentElement;
  148. }
  149. }
  150. const style = window.getComputedStyle(node);
  151. // 将字体大小转为 pt
  152. const pxSize = style.fontSize ? parseFloat(style.fontSize) : 12;
  153. // px -> pt (1pt = 1.3333px => pt = px * 0.75)
  154. this.format.font_size = Math.round(pxSize * 0.75);
  155. this.format.font_family = style.fontFamily || 'Arial';
  156. this.format.weight = style.fontWeight ? parseInt(style.fontWeight) : 400;
  157. this.format.color = style.color || '#000000';
  158. this.format.text_decoration = style.textDecoration || 'none';
  159. this.format.font_style = style.fontStyle || 'normal';
  160. this.format.isCopy = true;
  161. },
  162. // 粘贴格式
  163. pasteFormat() {
  164. if (!this.format.isCopy) {
  165. return;
  166. }
  167. const activeEditor = tinymce.activeEditor;
  168. // 格式刷到tinymce 选中的文字上
  169. activeEditor.formatter.register('customFormat', {
  170. inline: 'span',
  171. styles: {
  172. 'font-size': `${this.format.font_size}pt`,
  173. 'font-family': this.format.font_family,
  174. 'font-weight': this.format.weight,
  175. color: this.format.color,
  176. 'text-decoration': this.format.text_decoration,
  177. 'font-style': this.format.font_style,
  178. },
  179. });
  180. activeEditor.formatter.apply('customFormat');
  181. },
  182. /**
  183. * @description 粘贴组件
  184. */
  185. pasteComponent(position = 'bottom') {
  186. this.$refs.create.pasteComponent(position);
  187. },
  188. /**
  189. * 得到教材课件信息
  190. */
  191. getBookCoursewareInfo() {
  192. GetBookCoursewareInfo({ id: this.id }).then(({ courseware_info }) => {
  193. this.courseware_info = courseware_info;
  194. this.getLangList();
  195. });
  196. },
  197. goBackBookList() {
  198. this.$router.push({ path: '/personal_workbench/edit_task', query: { book_id: this.courseware_info.book_id } });
  199. },
  200. showSetBackground() {
  201. this.$refs.create.showSetBackground();
  202. },
  203. toggleEditMode(isEdit) {
  204. if (this.isEdit !== isEdit) {
  205. this.saveCoursewareContent('edit');
  206. }
  207. },
  208. saveCoursewareContent(type = '') {
  209. this.temporaryCoursewareID = '';
  210. this.$refs.create.saveCoursewareContent(type);
  211. },
  212. showFullTextSettings() {
  213. this.$refs.create.showFullTextSettings();
  214. },
  215. goBackPreview() {
  216. /* eslint-disable */ console.log(...oo_oo(`2987550641_136_6_136_27_4`, 'tuichu'));
  217. if (this.$route.query.template_type) {
  218. this.$router.push({
  219. path: `/personal_workbench/template_list/preview/${this.id}`,
  220. query: { template_type: this.$route.query.template_type },
  221. });
  222. } else {
  223. this.$router.push({
  224. path: `/personal_workbench/edit_task/preview/${this.id}`,
  225. query: { project_id: this.project_id },
  226. });
  227. }
  228. },
  229. getLangList() {
  230. GetLanguageTypeList({ book_id: this.courseware_info.book_id, is_contain_zh: 'true' }).then(
  231. ({ language_type_list }) => {
  232. this.langList = language_type_list;
  233. },
  234. );
  235. },
  236. /**
  237. * 得到我的教材课件任务列表
  238. */
  239. getMyBookCoursewareTaskList() {
  240. GetMyBookCoursewareTaskList({ project_id: this.project_id }).then(({ courseware_list }) => {
  241. this.courseware_list = courseware_list;
  242. });
  243. },
  244. /**
  245. * 文本转换
  246. * @param {string} text - 要转换的文本
  247. * @returns {string} - 转换后的文本
  248. */
  249. convertText(text) {
  250. if (this.chinese === 'zh-Hant' && this.opencc) {
  251. return this.opencc(text);
  252. }
  253. return text;
  254. },
  255. /**
  256. * 使用模板
  257. */
  258. useTemplate() {
  259. this.visibleTemplate = true;
  260. },
  261. /**
  262. * 处理使用模板事件
  263. * @param {Object} param
  264. * @param {Object} param.data - 模板数据
  265. * @param {Array} param.content_group_row_list - 内容分组行列表
  266. * @param {string} param.temporaryCoursewareID - 临时课件ID
  267. */
  268. handleUseTemplate({ data, content_group_row_list, temporaryCoursewareID }) {
  269. this.$refs.create.loadTemplateData_Create({ data, content_group_row_list });
  270. this.temporaryCoursewareID = temporaryCoursewareID;
  271. this.visibleTemplate = false;
  272. },
  273. goBackTemplateList() {
  274. if (this.type === 'personal') {
  275. this.$router.push({ path: '/personal_workbench/template_list' });
  276. } else if (this.type === 'org') {
  277. this.$router.push({ path: '/personal_workbench/template_list_org' });
  278. } else {
  279. this.$router.push({ path: '/personal_workbench/template_list_manager' });
  280. }
  281. },
  282. },
  283. };
  284. </script>
  285. <style lang="scss" scoped>
  286. @use '@/styles/mixin.scss' as *;
  287. .edit-task {
  288. height: calc(100% - 52px);
  289. min-height: calc(100% - 52px) !important;
  290. @include page-content(true);
  291. &__header {
  292. display: flex;
  293. align-items: center;
  294. border-top: $border;
  295. border-bottom: $border;
  296. .menu {
  297. .name {
  298. max-width: 260px;
  299. font-size: 16px;
  300. font-weight: bold;
  301. }
  302. }
  303. .courseware {
  304. .name-path {
  305. font-size: 14px;
  306. }
  307. .switch {
  308. padding: 4px 16px 4px 0;
  309. margin-right: 300px;
  310. border-right: $border;
  311. .link {
  312. padding: 4px;
  313. border-radius: 4px;
  314. &.active {
  315. background-color: $main-active-color;
  316. }
  317. }
  318. }
  319. .operator {
  320. .lang-select {
  321. :deep .el-input {
  322. width: 100px;
  323. }
  324. :deep .el-input__inner {
  325. height: 24px;
  326. line-height: 24px;
  327. background-color: #fff;
  328. }
  329. :deep .el-input__icon {
  330. line-height: 24px;
  331. }
  332. }
  333. }
  334. }
  335. }
  336. &__content {
  337. flex: 1;
  338. flex-direction: row !important;
  339. }
  340. &_template {
  341. main {
  342. min-height: 100%;
  343. }
  344. }
  345. }
  346. </style>
  347. <style lang="scss">
  348. .el-popover.el-popper {
  349. min-width: 100px;
  350. }
  351. </style>