index.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211
  1. <!-- 富文本编辑器 -->
  2. <template>
  3. <component v-if="inlineEditor" :is="tagName" :id="elementId" />
  4. <textarea v-else :id="elementId"></textarea>
  5. </template>
  6. <script>
  7. import tinymce from 'tinymce/tinymce';
  8. import 'tinymce/themes/silver';
  9. import 'tinymce/icons/default';
  10. import 'tinymce/plugins/code';
  11. import 'tinymce/plugins/preview';
  12. import 'tinymce/plugins/fullscreen';
  13. import 'tinymce/plugins/paste';
  14. import 'tinymce/plugins/searchreplace';
  15. import 'tinymce/plugins/save';
  16. import 'tinymce/plugins/autosave';
  17. import 'tinymce/plugins/link';
  18. import 'tinymce/plugins/autolink';
  19. import 'tinymce/plugins/image';
  20. import 'tinymce/plugins/media';
  21. import 'tinymce/plugins/table';
  22. import 'tinymce/plugins/codesample';
  23. import 'tinymce/plugins/lists';
  24. import 'tinymce/plugins/advlist';
  25. import 'tinymce/plugins/hr';
  26. import 'tinymce/plugins/charmap';
  27. import 'tinymce/plugins/emoticons';
  28. import 'tinymce/plugins/anchor';
  29. import 'tinymce/plugins/directionality';
  30. import 'tinymce/plugins/pagebreak';
  31. import 'tinymce/plugins/quickbars';
  32. import 'tinymce/plugins/nonbreaking';
  33. import 'tinymce/plugins/visualblocks';
  34. import 'tinymce/plugins/visualchars';
  35. import 'tinymce/plugins/wordcount';
  36. import 'tinymce/plugins/emoticons/js/emojis';
  37. import {
  38. DEFAULT_CONFIG,
  39. DARK_CONFIG,
  40. uuid,
  41. bindHandlers,
  42. openAlert
  43. } from './util';
  44. export default {
  45. name: 'TinymceEditor',
  46. props: {
  47. // 编辑器唯一 id
  48. id: String,
  49. // v-model
  50. value: String,
  51. // 编辑器配置
  52. init: Object,
  53. // 是否内联模式
  54. inline: {
  55. type: Boolean,
  56. default: false
  57. },
  58. // model events
  59. modelEvents: {
  60. type: String,
  61. default: 'change input undo redo'
  62. },
  63. // 内联模式标签名
  64. tagName: {
  65. type: String,
  66. default: 'div'
  67. },
  68. // 是否禁用
  69. disabled: Boolean,
  70. // 自动跟随框架主题
  71. autoTheme: {
  72. type: Boolean,
  73. default: true
  74. },
  75. // 是否使用暗黑主题
  76. darkTheme: Boolean
  77. },
  78. data() {
  79. return {
  80. // 编辑器唯一 id
  81. elementId: this.id || uuid('tiny-vue'),
  82. // 编辑器实例
  83. editorIns: null,
  84. // 是否内联模式
  85. inlineEditor: this.init?.inline || this.inline
  86. };
  87. },
  88. computed: {
  89. // 是否是暗黑模式
  90. darkMode() {
  91. return this.$store?.state?.theme?.darkMode;
  92. }
  93. },
  94. methods: {
  95. /* 更新 value */
  96. updateValue(value) {
  97. this.$emit('input', value);
  98. },
  99. /* 修改内容 */
  100. setContent(value) {
  101. if (
  102. this.editorIns &&
  103. typeof value === 'string' &&
  104. value !== this.editorIns.getContent()
  105. ) {
  106. this.editorIns.setContent(value);
  107. }
  108. },
  109. /* 渲染编辑器 */
  110. render() {
  111. const isDark = this.autoTheme ? this.darkMode : this.darkTheme;
  112. tinymce.init({
  113. ...DEFAULT_CONFIG,
  114. ...(isDark ? DARK_CONFIG : {}),
  115. ...this.init,
  116. selector: `#${this.elementId}`,
  117. readonly: this.disabled,
  118. inline: this.inlineEditor,
  119. setup: (editor) => {
  120. this.editorIns = editor;
  121. editor.on('init', (e) => {
  122. // 回显初始值
  123. if (this.value) {
  124. this.setContent(this.value);
  125. }
  126. // v-model
  127. editor.on(this.modelEvents, () => {
  128. this.updateValue(editor.getContent());
  129. });
  130. // valid events
  131. bindHandlers(e, this.$attrs, editor);
  132. });
  133. if (typeof this.init?.setup === 'function') {
  134. this.init.setup(editor);
  135. }
  136. }
  137. });
  138. },
  139. /* 销毁编辑器 */
  140. destory() {
  141. if (tinymce != null && this.editorIns != null) {
  142. tinymce.remove(this.editorIns);
  143. this.editorIns = null;
  144. }
  145. },
  146. /* 弹出提示框 */
  147. alert(option) {
  148. openAlert(this.editorIns, option);
  149. }
  150. },
  151. watch: {
  152. value(val, prevVal) {
  153. if (val !== prevVal) {
  154. this.setContent(val);
  155. }
  156. },
  157. disabled(disable) {
  158. if (this.editorIns !== null) {
  159. if (typeof this.editorIns.mode?.set === 'function') {
  160. this.editorIns.mode.set(disable ? 'readonly' : 'design');
  161. } else {
  162. this.editorIns.setMode(disable ? 'readonly' : 'design');
  163. }
  164. }
  165. },
  166. tagName() {
  167. this.destory();
  168. this.$nextTick(() => {
  169. this.render();
  170. });
  171. },
  172. darkMode() {
  173. if (this.autoTheme) {
  174. this.destory();
  175. this.$nextTick(() => {
  176. this.render();
  177. });
  178. }
  179. }
  180. },
  181. mounted() {
  182. this.render();
  183. },
  184. beforeDestroy() {
  185. this.destory();
  186. },
  187. activated() {
  188. this.render();
  189. },
  190. deactivated() {
  191. this.destory();
  192. }
  193. };
  194. </script>
  195. <style>
  196. body .tox-tinymce-aux {
  197. z-index: 19990000;
  198. }
  199. textarea[id^='tiny-vue'] {
  200. width: 0;
  201. height: 0;
  202. margin: 0;
  203. padding: 0;
  204. opacity: 0;
  205. box-sizing: border-box;
  206. }
  207. </style>