vben
2020-11-09 1093ec3e6e4fe1f49b7458c29e518744fe56532f
提交 | 用户 | age
c0e4c9 1 <template>
J 2   <div class="tinymce-container" :style="{ width: containerWidth }">
1093ec 3     <textarea :id="tinymceId" ref="elRef" :style="{ visibility: 'hidden' }"></textarea>
c0e4c9 4   </div>
J 5 </template>
6
7 <script lang="ts">
f75425 8   import {
V 9     defineComponent,
10     computed,
11     onMounted,
12     nextTick,
13     ref,
14     unref,
15     watch,
16     onUnmounted,
17     onDeactivated,
18   } from 'vue';
c0e4c9 19   import { basicProps } from './props';
J 20   import toolbar from './toolbar';
21   import plugins from './plugins';
f75425 22   import { getTinymce } from './getTinymce';
V 23   import { useScript } from '/@/hooks/web/useScript';
24   import { snowUuid } from '/@/utils/uuid';
25   import { bindHandlers } from './helper';
7658f4 26   import lineHeight from './lineHeight';
c0e4c9 27
J 28   const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';
f75425 29
c0e4c9 30   const tinymceScriptSrc = `${CDN_URL}/tinymce.min.js`;
J 31
32   export default defineComponent({
33     name: 'Tinymce',
34     props: basicProps,
f75425 35     emits: ['change', 'update:modelValue'],
V 36     setup(props, { emit, attrs }) {
37       const editorRef = ref<any>(null);
1093ec 38       const tinymceId = ref<string>(snowUuid('tiny-vue'));
f75425 39       const elRef = ref<Nullable<HTMLElement>>(null);
V 40
41       const tinymceContent = computed(() => {
42         return props.modelValue;
43       });
44
c0e4c9 45       const containerWidth = computed(() => {
J 46         const width = props.width;
47         if (/^[\d]+(\.[\d]+)?$/.test(width.toString())) {
48           return `${width}px`;
49         }
50         return width;
51       });
f75425 52
c0e4c9 53       const initOptions = computed(() => {
e034d1 54         const { height, options } = props;
c0e4c9 55         return {
f75425 56           selector: `#${unref(tinymceId)}`,
58f988 57           base_url: CDN_URL,
V 58           suffix: '.min',
c0e4c9 59           height: height,
J 60           toolbar: toolbar,
e034d1 61           menubar: 'file edit insert view format table',
c0e4c9 62           plugins: plugins,
J 63           // 语言包
64           language_url: 'resource/tinymce/langs/zh_CN.js',
65           // 中文
66           language: 'zh_CN',
f75425 67           default_link_target: '_blank',
V 68           link_title: false,
69           advlist_bullet_styles: 'square',
70           advlist_number_styles: 'default',
71           object_resizing: false,
adffef 72           fontsize_formats: '10px 11px 12px 14px 16px 18px 20px 24px 36px 48px',
8a9822 73           lineheight_formats: '1 1.5 1.75 2.0 3.0 4.0 5.0',
e034d1 74           ...options,
f75425 75           setup: (editor: any) => {
V 76             editorRef.value = editor;
77             editor.on('init', (e: Event) => initSetup(e));
78           },
c0e4c9 79         };
J 80       });
f75425 81
V 82       const { toPromise } = useScript({
83         src: tinymceScriptSrc,
84       });
85
86       watch(
87         () => attrs.disabled,
88         () => {
89           const editor = unref(editorRef);
90           if (!editor) return;
91           editor.setMode(attrs.disabled ? 'readonly' : 'design');
92         }
93       );
94
95       onMounted(() => {
96         nextTick(() => {
97           init();
98         });
99       });
100
101       onUnmounted(() => {
102         destory();
103       });
104
105       onDeactivated(() => {
106         destory();
107       });
108
109       function destory() {
110         if (getTinymce() !== null) {
111           getTinymce().remove(unref(editorRef));
112         }
113       }
114
115       function init() {
116         toPromise().then(() => {
1093ec 117           setTimeout(() => {
V 118             initEditor();
119           }, 0);
f75425 120         });
V 121       }
122
123       function initEditor() {
7658f4 124         getTinymce().PluginManager.add('lineHeight', lineHeight(getTinymce()));
1093ec 125         const el = unref(elRef);
V 126         if (el) {
127           el.style.visibility = '';
128         }
f75425 129         getTinymce().init(unref(initOptions));
V 130       }
131
132       function initSetup(e: Event) {
133         const editor = unref(editorRef);
134         if (!editor) return;
135         const value = props.modelValue || '';
136
137         editor.setContent(value);
138         bindModelHandlers(editor);
139         bindHandlers(e, attrs, unref(editorRef));
140       }
141
58f988 142       function setValue(editor: any, val: string, prevVal: string) {
V 143         if (
144           editor &&
145           typeof val === 'string' &&
146           val !== prevVal &&
147           val !== editor.getContent({ format: attrs.outputFormat })
148         ) {
149           editor.setContent(val);
150         }
151       }
152
f75425 153       function bindModelHandlers(editor: any) {
V 154         const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
155         const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
156         watch(
157           () => props.modelValue,
158           (val: string, prevVal: string) => {
58f988 159             setValue(editor, val, prevVal);
V 160           }
161         );
162
163         watch(
164           () => props.value,
165           (val: string, prevVal: string) => {
166             setValue(editor, val, prevVal);
167           },
168           {
169             immediate: true,
f75425 170           }
V 171         );
172
173         editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
58f988 174           const content = editor.getContent({ format: attrs.outputFormat });
V 175           emit('update:modelValue', content);
176           emit('change', content);
f75425 177         });
V 178       }
179
180       return {
181         containerWidth,
182         initOptions,
183         tinymceContent,
184         tinymceScriptSrc,
185         elRef,
186         tinymceId,
187       };
c0e4c9 188     },
J 189   });
190 </script>
191
192 <style lang="less" scoped>
193   .tinymce-container {
194     position: relative;
195     line-height: normal;
196
197     .mce-fullscreen {
198       z-index: 10000;
199     }
200   }
201
202   .editor-custom-btn-container {
203     position: absolute;
204     top: 6px;
205     right: 6px;
206
207     &.fullscreen {
208       position: fixed;
209       z-index: 10000;
210     }
211   }
212
213   .editor-upload-btn {
214     display: inline-block;
215   }
216
217   textarea {
218     z-index: -1;
219     visibility: hidden;
220   }
221 </style>