Commit f75425d13bc9f6003021fd4b5d6451ae096c09b7

Authored by vben
1 parent 9c02d8ec

perf: review tinymce code

src/components/Tinymce/src/Editor.vue
1 1 <template>
2 2 <div class="tinymce-container" :style="{ width: containerWidth }">
3   - <tinymce-editor
4   - :id="id"
5   - :init="initOptions"
6   - :modelValue="tinymceContent"
7   - @update:modelValue="handleChange"
8   - :tinymceScriptSrc="tinymceScriptSrc"
9   - ></tinymce-editor>
  3 + <textarea :id="tinymceId" visibility="hidden" ref="elRef"></textarea>
10 4 </div>
11 5 </template>
12 6  
13 7 <script lang="ts">
14   - import TinymceEditor from './lib'; // TinyMCE vue wrapper
15   - import { defineComponent, computed } from 'vue';
  8 + import {
  9 + defineComponent,
  10 + computed,
  11 + onMounted,
  12 + nextTick,
  13 + ref,
  14 + unref,
  15 + watch,
  16 + onUnmounted,
  17 + onDeactivated,
  18 + } from 'vue';
16 19 import { basicProps } from './props';
17 20 import toolbar from './toolbar';
18 21 import plugins from './plugins';
  22 + import { getTinymce } from './getTinymce';
  23 + import { useScript } from '/@/hooks/web/useScript';
  24 + import { snowUuid } from '/@/utils/uuid';
  25 + import { bindHandlers } from './helper';
19 26  
20 27 const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';
  28 +
21 29 const tinymceScriptSrc = `${CDN_URL}/tinymce.min.js`;
22 30  
23 31 export default defineComponent({
24 32 name: 'Tinymce',
25   - components: { TinymceEditor },
26 33 props: basicProps,
27   - setup(props, { emit }) {
  34 + emits: ['change', 'update:modelValue'],
  35 + setup(props, { emit, attrs }) {
  36 + const editorRef = ref<any>(null);
  37 + const elRef = ref<Nullable<HTMLElement>>(null);
  38 +
  39 + const tinymceId = computed(() => {
  40 + return snowUuid('tiny-vue');
  41 + });
  42 +
28 43 const tinymceContent = computed(() => {
29   - return props.value;
  44 + return props.modelValue;
30 45 });
31   - function handleChange(value: string) {
32   - emit('change', value);
33   - }
  46 +
34 47 const containerWidth = computed(() => {
35 48 const width = props.width;
36   - // Test matches `100`, `'100'`
37 49 if (/^[\d]+(\.[\d]+)?$/.test(width.toString())) {
38 50 return `${width}px`;
39 51 }
40 52 return width;
41 53 });
  54 +
42 55 const initOptions = computed(() => {
43   - const { id, height, menubar } = props;
  56 + const { height, menubar } = props;
44 57 return {
45   - selector: `#${id}`,
  58 + selector: `#${unref(tinymceId)}`,
46 59 height: height,
47 60 toolbar: toolbar,
  61 + theme: 'silver',
48 62 menubar: menubar,
49 63 plugins: plugins,
50 64 // 语言包
51 65 language_url: 'resource/tinymce/langs/zh_CN.js',
52 66 // 中文
53 67 language: 'zh_CN',
  68 + default_link_target: '_blank',
  69 + link_title: false,
  70 + advlist_bullet_styles: 'square',
  71 + advlist_number_styles: 'default',
  72 + object_resizing: false,
  73 + setup: (editor: any) => {
  74 + editorRef.value = editor;
  75 + editor.on('init', (e: Event) => initSetup(e));
  76 + },
54 77 };
55 78 });
56   - return { containerWidth, initOptions, tinymceContent, handleChange, tinymceScriptSrc };
  79 +
  80 + const { toPromise } = useScript({
  81 + src: tinymceScriptSrc,
  82 + });
  83 +
  84 + watch(
  85 + () => attrs.disabled,
  86 + () => {
  87 + const editor = unref(editorRef);
  88 + if (!editor) return;
  89 + editor.setMode(attrs.disabled ? 'readonly' : 'design');
  90 + }
  91 + );
  92 +
  93 + onMounted(() => {
  94 + nextTick(() => {
  95 + init();
  96 + });
  97 + });
  98 +
  99 + onUnmounted(() => {
  100 + destory();
  101 + });
  102 +
  103 + onDeactivated(() => {
  104 + destory();
  105 + });
  106 +
  107 + function destory() {
  108 + if (getTinymce() !== null) {
  109 + getTinymce().remove(unref(editorRef));
  110 + }
  111 + }
  112 +
  113 + function init() {
  114 + toPromise().then(() => {
  115 + initEditor();
  116 + });
  117 + }
  118 +
  119 + function initEditor() {
  120 + getTinymce().init(unref(initOptions));
  121 + }
  122 +
  123 + function initSetup(e: Event) {
  124 + const editor = unref(editorRef);
  125 + if (!editor) return;
  126 + const value = props.modelValue || '';
  127 +
  128 + editor.setContent(value);
  129 + bindModelHandlers(editor);
  130 + bindHandlers(e, attrs, unref(editorRef));
  131 + }
  132 +
  133 + function bindModelHandlers(editor: any) {
  134 + const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
  135 + const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
  136 + watch(
  137 + () => props.modelValue,
  138 + (val: string, prevVal: string) => {
  139 + if (
  140 + editor &&
  141 + typeof val === 'string' &&
  142 + val !== prevVal &&
  143 + val !== editor.getContent({ format: attrs.outputFormat })
  144 + ) {
  145 + editor.setContent(val);
  146 + }
  147 + }
  148 + );
  149 +
  150 + editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
  151 + emit('update:modelValue', editor.getContent({ format: attrs.outputFormat }));
  152 + });
  153 + }
  154 +
  155 + function handleChange(value: string) {
  156 + emit('change', value);
  157 + }
  158 + return {
  159 + containerWidth,
  160 + initOptions,
  161 + tinymceContent,
  162 + handleChange,
  163 + tinymceScriptSrc,
  164 + elRef,
  165 + tinymceId,
  166 + };
57 167 },
58 168 });
59 169 </script>
... ...
src/components/Tinymce/src/lib/TinyMCE.ts renamed to src/components/Tinymce/src/getTinymce.ts
1 1 const getGlobal = (): any => (typeof window !== 'undefined' ? window : global);
2 2  
3   -const getTinymce = () => {
  3 +export const getTinymce = () => {
4 4 const global = getGlobal();
5   -
6 5 return global && global.tinymce ? global.tinymce : null;
7 6 };
8   -
9   -export { getTinymce };
... ...
src/components/Tinymce/src/lib/Utils.ts renamed to src/components/Tinymce/src/helper.ts
1   -import { ComponentPublicInstance } from 'vue';
2   -
3 1 const validEvents = [
4 2 'onActivate',
5 3 'onAddUndo',
... ... @@ -62,12 +60,12 @@ const validEvents = [
62 60 'onShow',
63 61 'onSubmit',
64 62 'onUndo',
65   - 'onVisualAid'
  63 + 'onVisualAid',
66 64 ];
67 65  
68 66 const isValidKey = (key: string) => validEvents.indexOf(key) !== -1;
69 67  
70   -const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => {
  68 +export const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => {
71 69 Object.keys(listeners)
72 70 .filter(isValidKey)
73 71 .forEach((key: string) => {
... ... @@ -81,71 +79,3 @@ const bindHandlers = (initEvent: Event, listeners: any, editor: any): void =&gt; {
81 79 }
82 80 });
83 81 };
84   -
85   -const bindModelHandlers = (ctx: ComponentPublicInstance, editor: any) => {
86   - const modelEvents = ctx.$props.modelEvents ? ctx.$props.modelEvents : null;
87   - const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
88   - // @ts-ignore
89   - ctx.$watch('modelValue', (val: string, prevVal: string) => {
90   - if (editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent({ format: ctx.$props.outputFormat })) {
91   - editor.setContent(val);
92   - }
93   - });
94   -
95   - editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
96   - ctx.$emit('update:modelValue', editor.getContent({ format: ctx.$props.outputFormat }));
97   - });
98   -};
99   -
100   -const initEditor = (initEvent: Event, ctx: ComponentPublicInstance, editor: any) => {
101   - const value = ctx.$props.modelValue ? ctx.$props.modelValue : '';
102   - const initialValue = ctx.$props.initialValue ? ctx.$props.initialValue : '';
103   -
104   - editor.setContent(value || initialValue);
105   -
106   - // checks if the v-model shorthand is used (which sets an v-on:input listener) and then binds either
107   - // specified the events or defaults to "change keyup" event and emits the editor content on that event
108   - if (ctx.$attrs['onUpdate:modelValue']) {
109   - bindModelHandlers(ctx, editor);
110   - }
111   -
112   - bindHandlers(initEvent, ctx.$attrs, editor);
113   -};
114   -
115   -let unique = 0;
116   -
117   -const uuid = (prefix: string): string => {
118   - const time = Date.now();
119   - const random = Math.floor(Math.random() * 1000000000);
120   -
121   - unique++;
122   -
123   - return prefix + '_' + random + unique + String(time);
124   -};
125   -
126   -const isTextarea = (element: Element | null): element is HTMLTextAreaElement => {
127   - return element !== null && element.tagName.toLowerCase() === 'textarea';
128   -};
129   -
130   -const normalizePluginArray = (plugins?: string | string[]): string[] => {
131   - if (typeof plugins === 'undefined' || plugins === '') {
132   - return [];
133   - }
134   -
135   - return Array.isArray(plugins) ? plugins : plugins.split(' ');
136   -};
137   -
138   -const mergePlugins = (initPlugins: string | string[], inputPlugins?: string | string[]) =>
139   - normalizePluginArray(initPlugins).concat(normalizePluginArray(inputPlugins));
140   -
141   -const isNullOrUndefined = (value: any): value is null | undefined => value === null || value === undefined;
142   -
143   -export {
144   - bindHandlers,
145   - bindModelHandlers,
146   - initEditor,
147   - uuid,
148   - isTextarea,
149   - mergePlugins,
150   - isNullOrUndefined
151   -};
152 82 \ No newline at end of file
... ...
src/components/Tinymce/src/lib/ScriptLoader.ts deleted 100644 → 0
1   -import { uuid } from './Utils';
2   -
3   -export type callbackFn = () => void;
4   -export interface IStateObj {
5   - listeners: callbackFn[];
6   - scriptId: string;
7   - scriptLoaded: boolean;
8   -}
9   -
10   -const createState = (): IStateObj => {
11   - return {
12   - listeners: [],
13   - scriptId: uuid('tiny-script'),
14   - scriptLoaded: false
15   - };
16   -};
17   -
18   -interface ScriptLoader {
19   - load: (doc: Document, url: string, callback: callbackFn) => void;
20   - reinitialize: () => void;
21   -}
22   -
23   -const CreateScriptLoader = (): ScriptLoader => {
24   - let state: IStateObj = createState();
25   -
26   - const injectScriptTag = (scriptId: string, doc: Document, url: string, callback: callbackFn) => {
27   - const scriptTag = doc.createElement('script');
28   - scriptTag.referrerPolicy = 'origin';
29   - scriptTag.type = 'application/javascript';
30   - scriptTag.id = scriptId;
31   - scriptTag.src = url;
32   -
33   - const handler = () => {
34   - scriptTag.removeEventListener('load', handler);
35   - callback();
36   - };
37   - scriptTag.addEventListener('load', handler);
38   - if (doc.head) {
39   - doc.head.appendChild(scriptTag);
40   - }
41   - };
42   -
43   - const load = (doc: Document, url: string, callback: callbackFn) => {
44   - if (state.scriptLoaded) {
45   - callback();
46   - } else {
47   - state.listeners.push(callback);
48   - if (!doc.getElementById(state.scriptId)) {
49   - injectScriptTag(state.scriptId, doc, url, () => {
50   - state.listeners.forEach((fn) => fn());
51   - state.scriptLoaded = true;
52   - });
53   - }
54   - }
55   - };
56   -
57   - // Only to be used by tests.
58   - const reinitialize = () => {
59   - state = createState();
60   - };
61   -
62   - return {
63   - load,
64   - reinitialize
65   - };
66   -};
67   -
68   -const ScriptLoader = CreateScriptLoader();
69   -
70   -export {
71   - ScriptLoader
72   -};
73 0 \ No newline at end of file
src/components/Tinymce/src/lib/components/Editor.ts deleted 100644 → 0
1   -/**
2   - * Copyright (c) 2018-present, Ephox, Inc.
3   - *
4   - * This source code is licensed under the Apache 2 license found in the
5   - * LICENSE file in the root directory of this source tree.
6   - *
7   - */
8   -
9   -// import { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options';
10   -// import { CreateElement, Vue } from 'vue/types/vue';
11   -
12   -import { ScriptLoader } from '../ScriptLoader';
13   -import { getTinymce } from '../TinyMCE';
14   -import { initEditor, isTextarea, mergePlugins, uuid, isNullOrUndefined } from '../Utils';
15   -import { editorProps, IPropTypes } from './EditorPropTypes';
16   -import { h, defineComponent, ComponentPublicInstance } from 'vue'
17   -
18   -
19   -export interface IEditor {
20   - $props: Partial<IPropTypes>
21   -}
22   -
23   -declare module '@vue/runtime-core' {
24   - interface ComponentCustomProperties {
25   - elementId: string;
26   - element: Element | null;
27   - editor: any;
28   - inlineEditor: boolean;
29   - $props: Partial<IPropTypes>;
30   - }
31   -}
32   -
33   -
34   -const renderInline = (id: string, tagName?: string) => {
35   - return h(tagName ? tagName : 'div', {
36   - id
37   - });
38   -};
39   -
40   -const renderIframe = (id: string) => {
41   - return h('textarea', {
42   - id,
43   - visibility: 'hidden'
44   - });
45   -};
46   -
47   -const initialise = (ctx: ComponentPublicInstance) => () => {
48   - const finalInit = {
49   - ...ctx.$props.init,
50   - readonly: ctx.$props.disabled,
51   - selector: `#${ctx.elementId}`,
52   - plugins: mergePlugins(ctx.$props.init && ctx.$props.init.plugins, ctx.$props.plugins),
53   - toolbar: ctx.$props.toolbar || (ctx.$props.init && ctx.$props.init.toolbar),
54   - inline: ctx.inlineEditor,
55   - setup: (editor: any) => {
56   - ctx.editor = editor;
57   - editor.on('init', (e: Event) => initEditor(e, ctx, editor));
58   -
59   - if (ctx.$props.init && typeof ctx.$props.init.setup === 'function') {
60   - ctx.$props.init.setup(editor);
61   - }
62   - }
63   - };
64   -
65   - if (isTextarea(ctx.element)) {
66   - ctx.element.style.visibility = '';
67   - }
68   -
69   - getTinymce().init(finalInit);
70   -};
71   -
72   -export const Editor = defineComponent({
73   - props: editorProps,
74   - created() {
75   - this.elementId = this.$props.id || uuid('tiny-vue');
76   - this.inlineEditor = (this.$props.init && this.$props.init.inline) || this.$props.inline;
77   - },
78   - watch: {
79   - disabled() {
80   - (this as any).editor.setMode(this.disabled ? 'readonly' : 'design');
81   - }
82   - },
83   - mounted() {
84   - this.element = this.$el;
85   -
86   - if (getTinymce() !== null) {
87   - initialise(this)();
88   - } else if (this.element && this.element.ownerDocument) {
89   - const channel = this.$props.cloudChannel ? this.$props.cloudChannel : '5';
90   - const apiKey = this.$props.apiKey ? this.$props.apiKey : 'no-api-key';
91   -
92   - const scriptSrc = isNullOrUndefined(this.$props.tinymceScriptSrc) ?
93   - `https://cdn.tiny.cloud/1/${apiKey}/tinymce/${channel}/tinymce.min.js` :
94   - this.$props.tinymceScriptSrc;
95   -
96   - ScriptLoader.load(
97   - this.element.ownerDocument,
98   - scriptSrc,
99   - initialise(this)
100   - );
101   - }
102   - },
103   - beforeUnmount() {
104   - if (getTinymce() !== null) {
105   - getTinymce().remove(this.editor);
106   - }
107   - },
108   - render() {
109   - return this.inlineEditor ? renderInline(this.elementId, this.$props.tagName) : renderIframe(this.elementId);
110   - }
111   -})
src/components/Tinymce/src/lib/components/EditorPropTypes.ts deleted 100644 → 0
1   -/**
2   - * Copyright (c) 2018-present, Ephox, Inc.
3   - *
4   - * This source code is licensed under the Apache 2 license found in the
5   - * LICENSE file in the root directory of this source tree.
6   - *
7   - */
8   -
9   -export type CopyProps<T> = { [P in keyof T]: any };
10   -
11   -export interface IPropTypes {
12   - apiKey: string;
13   - cloudChannel: string;
14   - id: string;
15   - init: any;
16   - initialValue: string;
17   - outputFormat: 'html' | 'text';
18   - inline: boolean;
19   - modelEvents: string[] | string;
20   - plugins: string[] | string;
21   - tagName: string;
22   - toolbar: string[] | string;
23   - modelValue: string;
24   - disabled: boolean;
25   - tinymceScriptSrc: string;
26   -}
27   -
28   -export const editorProps: CopyProps<IPropTypes> = {
29   - apiKey: String,
30   - cloudChannel: String,
31   - id: String,
32   - init: Object,
33   - initialValue: String,
34   - inline: Boolean,
35   - modelEvents: [String, Array],
36   - plugins: [String, Array],
37   - tagName: String,
38   - toolbar: [String, Array],
39   - modelValue: String,
40   - disabled: Boolean,
41   - tinymceScriptSrc: String,
42   - outputFormat: {
43   - type: String,
44   - validator: (prop: string) => prop === 'html' || prop === 'text'
45   - },
46   -};
src/components/Tinymce/src/lib/global.d.ts deleted 100644 → 0
1   -// Global compile-time constants
2   -declare var __DEV__: boolean
3   -declare var __BROWSER__: boolean
4   -declare var __CI__: boolean
src/components/Tinymce/src/lib/index.ts deleted 100644 → 0
1   -import { Editor } from './components/Editor';
2   -
3   -export default Editor;
src/components/Tinymce/src/props.ts
1 1 import { PropType } from 'vue';
2 2  
3 3 export const basicProps = {
4   - id: {
5   - type: String as PropType<string>,
6   - default: () => {
7   - return `tinymce-${new Date().getTime()}${(Math.random() * 1000).toFixed(0)}`;
8   - },
9   - },
10 4 menubar: {
11 5 type: String as PropType<string>,
12 6 default: 'file edit insert view format table',
... ... @@ -15,6 +9,10 @@ export const basicProps = {
15 9 type: String as PropType<string>,
16 10 // default: ''
17 11 },
  12 + modelValue: {
  13 + type: String as PropType<string>,
  14 + // default: ''
  15 + },
18 16 // 高度
19 17 height: {
20 18 type: [Number, String] as PropType<string | number>,
... ...
src/router/menus/modules/demo/charts.ts
... ... @@ -6,23 +6,23 @@ const menu: MenuModule = {
6 6 path: '/charts',
7 7 children: [
8 8 {
9   - path: '/apexChart',
  9 + path: 'apexChart',
10 10 name: 'ApexChart',
11 11 },
12 12 {
13   - path: '/echarts',
  13 + path: 'echarts',
14 14 name: 'Echarts',
15 15 children: [
16 16 {
17   - path: '/map',
  17 + path: 'map',
18 18 name: '地图',
19 19 },
20 20 {
21   - path: '/line',
  21 + path: 'line',
22 22 name: '折线图',
23 23 },
24 24 {
25   - path: '/pie',
  25 + path: 'pie',
26 26 name: '饼图',
27 27 },
28 28 ],
... ...
src/router/menus/modules/demo/comp.ts
... ... @@ -6,16 +6,16 @@ const menu: MenuModule = {
6 6 path: '/comp',
7 7 children: [
8 8 {
9   - path: '/basic',
  9 + path: 'basic',
10 10 name: '基础组件',
11 11 },
12 12 {
13   - path: '/countTo',
  13 + path: 'countTo',
14 14 name: '数字动画',
15 15 },
16 16  
17 17 {
18   - path: '/scroll',
  18 + path: 'scroll',
19 19 name: '滚动组件',
20 20 children: [
21 21 {
... ... @@ -33,53 +33,39 @@ const menu: MenuModule = {
33 33 ],
34 34 },
35 35 {
36   - path: '/modal',
  36 + path: 'modal',
37 37 name: '弹窗扩展',
38 38 },
39 39 {
40   - path: '/drawer',
  40 + path: 'drawer',
41 41 name: '抽屉扩展',
42 42 },
43 43 {
44   - path: '/desc',
  44 + path: 'desc',
45 45 name: '详情组件',
46 46 },
47 47 {
48   - path: '/verify',
  48 + path: 'verify',
49 49 name: '验证组件',
50 50 children: [
51 51 {
52   - path: '/drag',
  52 + path: 'drag',
53 53 name: '拖拽校验',
54 54 },
55 55 {
56   - path: '/rotate',
  56 + path: 'rotate',
57 57 name: '图片还原校验',
58 58 },
59 59 ],
60 60 },
61 61 {
62   - path: '/qrcode',
  62 + path: 'qrcode',
63 63 name: '二维码组件',
64 64 },
65 65 {
66   - path: '/strength-meter',
  66 + path: 'strength-meter',
67 67 name: '密码强度组件',
68 68 },
69   - {
70   - path: '/tinymce',
71   - name: '富文本',
72   - children: [
73   - {
74   - path: '/index',
75   - name: '基础使用',
76   - },
77   - {
78   - path: '/editor',
79   - name: '嵌入form使用',
80   - },
81   - ],
82   - },
83 69 ],
84 70 },
85 71 };
... ...
src/router/menus/modules/demo/editor.ts
... ... @@ -6,9 +6,23 @@ const menu: MenuModule = {
6 6 path: '/editor',
7 7 children: [
8 8 {
9   - path: '/markdown',
  9 + path: 'markdown',
10 10 name: 'markdown编辑器',
11 11 },
  12 + {
  13 + path: 'tinymce',
  14 + name: '富文本',
  15 + children: [
  16 + {
  17 + path: 'index',
  18 + name: '基础使用',
  19 + },
  20 + // {
  21 + // path: 'editor',
  22 + // name: '嵌入form使用',
  23 + // },
  24 + ],
  25 + },
12 26 ],
13 27 },
14 28 };
... ...
src/router/menus/modules/demo/excel.ts
... ... @@ -6,23 +6,21 @@ const menu: MenuModule = {
6 6 path: '/excel',
7 7 children: [
8 8 {
9   - path: '/customExport',
  9 + path: 'customExport',
10 10 name: '选择导出格式',
11 11 },
12 12 {
13   - path: '/jsonExport',
  13 + path: 'jsonExport',
14 14 name: 'JSON数据导出',
15 15 },
16 16 {
17   - path: '/arrayExport',
  17 + path: 'arrayExport',
18 18 name: 'Array数据导出',
19 19 },
20 20 {
21   - path: '/importExcel',
  21 + path: 'importExcel',
22 22 name: '导入',
23 23 },
24   - // ],
25   - // },
26 24 ],
27 25 },
28 26 };
... ...
src/router/menus/modules/demo/exception.ts
... ... @@ -6,27 +6,27 @@ const menu: MenuModule = {
6 6 path: '/exception',
7 7 children: [
8 8 {
9   - path: '/404',
  9 + path: '404',
10 10 name: '404',
11 11 },
12 12 {
13   - path: '/500',
  13 + path: '500',
14 14 name: '500',
15 15 },
16 16 {
17   - path: '/net-work-error',
  17 + path: 'net-work-error',
18 18 name: '网络错误',
19 19 },
20 20 {
21   - path: '/page-time-out',
  21 + path: 'page-time-out',
22 22 name: '页面超时',
23 23 },
24 24 {
25   - path: '/not-data',
  25 + path: 'not-data',
26 26 name: '无数据',
27 27 },
28 28 {
29   - path: '/error-log',
  29 + path: 'error-log',
30 30 name: '错误日志',
31 31 },
32 32 ],
... ...
src/router/menus/modules/demo/feat.ts
... ... @@ -6,55 +6,55 @@ const menu: MenuModule = {
6 6 path: '/feat',
7 7 children: [
8 8 {
9   - path: '/icon',
  9 + path: 'icon',
10 10 name: '图标',
11 11 },
12 12 {
13   - path: '/tabs',
  13 + path: 'tabs',
14 14 name: '标签页操作',
15 15 },
16 16 {
17   - path: '/context-menu',
  17 + path: 'context-menu',
18 18 name: '右键菜单',
19 19 },
20 20 {
21   - path: '/click-out-side',
  21 + path: 'click-out-side',
22 22 name: 'ClickOutSide',
23 23 },
24 24 {
25   - path: '/img-preview',
  25 + path: 'img-preview',
26 26 name: '图片预览',
27 27 },
28 28 {
29   - path: '/i18n',
  29 + path: 'i18n',
30 30 name: '国际化',
31 31 },
32 32 {
33   - path: '/copy',
  33 + path: 'copy',
34 34 name: '剪切板',
35 35 },
36 36 {
37   - path: '/msg',
  37 + path: 'msg',
38 38 name: '消息提示',
39 39 },
40 40 {
41   - path: '/watermark',
  41 + path: 'watermark',
42 42 name: '水印',
43 43 },
44 44 {
45   - path: '/full-screen',
  45 + path: 'full-screen',
46 46 name: '全屏',
47 47 },
48 48 {
49   - path: '/testTab',
  49 + path: 'testTab',
50 50 name: '带参Tab',
51 51 children: [
52 52 {
53   - path: '/id1',
  53 + path: 'id1',
54 54 name: '带参tab1',
55 55 },
56 56 {
57   - path: '/id2',
  57 + path: 'id2',
58 58 name: '带参tab2',
59 59 },
60 60 ],
... ...
src/router/menus/modules/demo/form.ts
... ... @@ -6,31 +6,31 @@ const menu: MenuModule = {
6 6 name: 'Form',
7 7 children: [
8 8 {
9   - path: '/basic',
  9 + path: 'basic',
10 10 name: '基础表单',
11 11 },
12 12 {
13   - path: '/useForm',
  13 + path: 'useForm',
14 14 name: 'useForm',
15 15 },
16 16 {
17   - path: '/refForm',
  17 + path: 'refForm',
18 18 name: 'RefForm',
19 19 },
20 20 {
21   - path: '/advancedForm',
  21 + path: 'advancedForm',
22 22 name: '可收缩表单',
23 23 },
24 24 {
25   - path: '/ruleForm',
  25 + path: 'ruleForm',
26 26 name: '表单校验',
27 27 },
28 28 {
29   - path: '/dynamicForm',
  29 + path: 'dynamicForm',
30 30 name: '动态表单',
31 31 },
32 32 {
33   - path: '/customerForm',
  33 + path: 'customerForm',
34 34 name: '自定义组件',
35 35 },
36 36 ],
... ...
src/router/menus/modules/demo/iframe.ts
... ... @@ -6,15 +6,15 @@ const menu: MenuModule = {
6 6 path: '/frame',
7 7 children: [
8 8 {
9   - path: '/antv',
  9 + path: 'antv',
10 10 name: 'antVue文档(内嵌)',
11 11 },
12 12 {
13   - path: '/doc',
  13 + path: 'doc',
14 14 name: '项目文档(内嵌)',
15 15 },
16 16 {
17   - path: '/docExternal',
  17 + path: 'docExternal',
18 18 name: '项目文档(外链)',
19 19 },
20 20 ],
... ...
src/router/menus/modules/demo/permission.ts
... ... @@ -6,37 +6,37 @@ const menu: MenuModule = {
6 6 path: '/permission',
7 7 children: [
8 8 {
9   - path: '/front',
  9 + path: 'front',
10 10 name: '基于前端',
11 11 children: [
12 12 {
13   - path: '/page',
  13 + path: 'page',
14 14 name: '页面权限',
15 15 },
16 16 {
17   - path: '/btn',
  17 + path: 'btn',
18 18 name: '按钮权限',
19 19 },
20 20 {
21   - path: '/auth-pageA',
  21 + path: 'auth-pageA',
22 22 name: '权限测试页A',
23 23 },
24 24 {
25   - path: '/auth-pageB',
  25 + path: 'auth-pageB',
26 26 name: '权限测试页B',
27 27 },
28 28 ],
29 29 },
30 30 {
31   - path: '/back',
  31 + path: 'back',
32 32 name: '基于后台',
33 33 children: [
34 34 {
35   - path: '/page',
  35 + path: 'page',
36 36 name: '页面权限',
37 37 },
38 38 {
39   - path: '/btn',
  39 + path: 'btn',
40 40 name: '按钮权限',
41 41 },
42 42 ],
... ...
src/router/menus/modules/demo/table.ts
... ... @@ -6,59 +6,59 @@ const menu: MenuModule = {
6 6 name: 'Table',
7 7 children: [
8 8 {
9   - path: '/basic',
  9 + path: 'basic',
10 10 name: '基础表格',
11 11 },
12 12 {
13   - path: '/treeTable',
  13 + path: 'treeTable',
14 14 name: '树形表格',
15 15 },
16 16 {
17   - path: '/fetchTable',
  17 + path: 'fetchTable',
18 18 name: '远程加载',
19 19 },
20 20 {
21   - path: '/fixedColumn',
  21 + path: 'fixedColumn',
22 22 name: '固定列',
23 23 },
24 24 {
25   - path: '/customerCell',
  25 + path: 'customerCell',
26 26 name: '自定义列',
27 27 },
28 28 {
29   - path: '/formTable',
  29 + path: 'formTable',
30 30 name: '开启搜索区域',
31 31 },
32 32 {
33   - path: '/useTable',
  33 + path: 'useTable',
34 34 name: 'UseTable',
35 35 },
36 36 {
37   - path: '/refTable',
  37 + path: 'refTable',
38 38 name: 'RefTable',
39 39 },
40 40 {
41   - path: '/multipleHeader',
  41 + path: 'multipleHeader',
42 42 name: '多级表头',
43 43 },
44 44 {
45   - path: '/mergeHeader',
  45 + path: 'mergeHeader',
46 46 name: '合并单元格',
47 47 },
48 48 {
49   - path: '/expandTable',
  49 + path: 'expandTable',
50 50 name: '可展开表格',
51 51 },
52 52 {
53   - path: '/fixedHeight',
  53 + path: 'fixedHeight',
54 54 name: '定高/头部自定义',
55 55 },
56 56 {
57   - path: '/footerTable',
  57 + path: 'footerTable',
58 58 name: '表尾行合计',
59 59 },
60 60 {
61   - path: '/editCellTable',
  61 + path: 'editCellTable',
62 62 name: '可编辑单元格',
63 63 },
64 64 ],
... ...
src/router/routes/modules/demo/comp.ts
... ... @@ -136,31 +136,5 @@ export default {
136 136 title: '密码强度组件',
137 137 },
138 138 },
139   - {
140   - path: '/tinymce',
141   - name: 'TinymceDemo',
142   - meta: {
143   - title: '富文本',
144   - },
145   - redirect: '/comp/tinymce/index',
146   - children: [
147   - {
148   - path: 'index',
149   - name: 'Tinymce',
150   - component: () => import('/@/views/demo/comp/tinymce/index.vue'),
151   - meta: {
152   - title: '基础使用',
153   - },
154   - },
155   - {
156   - path: 'editor',
157   - name: 'TinymceEditor',
158   - component: () => import('/@/views/demo/comp/tinymce/Editor.vue'),
159   - meta: {
160   - title: '嵌入form使用',
161   - },
162   - },
163   - ],
164   - },
165 139 ],
166 140 } as AppRouteModule;
... ...
src/router/routes/modules/demo/editor.ts
... ... @@ -23,5 +23,32 @@ export default {
23 23 title: 'markdown编辑器',
24 24 },
25 25 },
  26 + {
  27 + path: '/tinymce',
  28 + name: 'TinymceDemo',
  29 + meta: {
  30 + title: '富文本',
  31 + },
  32 + redirect: '/editor/tinymce/index',
  33 + children: [
  34 + {
  35 + path: 'index',
  36 + name: 'TinymceBasicDemo',
  37 + component: () => import('/@/views/demo/editor/tinymce/index.vue'),
  38 + meta: {
  39 + title: '基础使用',
  40 + },
  41 + },
  42 + // TODO
  43 + // {
  44 + // path: 'editor',
  45 + // name: 'TinymceFormDemo',
  46 + // component: () => import('/@/views/demo/comp/tinymce/Editor.vue'),
  47 + // meta: {
  48 + // title: '嵌入form使用',
  49 + // },
  50 + // },
  51 + ],
  52 + },
26 53 ],
27 54 } as AppRouteModule;
... ...
src/utils/is.ts
... ... @@ -67,3 +67,7 @@ export const isServer = typeof window === &#39;undefined&#39;;
67 67 export function isImageDom(o: Element) {
68 68 return o && ['IMAGE', 'IMG'].includes(o.tagName);
69 69 }
  70 +
  71 +export const isTextarea = (element: Element | null): element is HTMLTextAreaElement => {
  72 + return element !== null && element.tagName.toLowerCase() === 'textarea';
  73 +};
... ...
src/utils/uuid.ts
... ... @@ -17,3 +17,11 @@ export function buildUUID(): string {
17 17 }
18 18 return uuid.replace(/-/g, '');
19 19 }
  20 +
  21 +let unique = 0;
  22 +export function snowUuid(prefix: string): string {
  23 + const time = Date.now();
  24 + const random = Math.floor(Math.random() * 1000000000);
  25 + unique++;
  26 + return prefix + '_' + random + unique + String(time);
  27 +}
... ...
src/views/demo/comp/tinymce/Editor.vue renamed to src/views/demo/editor/tinymce/Editor.vue
... ... @@ -43,7 +43,7 @@
43 43 },
44 44 ];
45 45 export default defineComponent({
46   - components: { BasicForm, CollapseContainer, Tinymce },
  46 + components: { BasicForm, CollapseContainer },
47 47 setup() {
48 48 const { createMessage } = useMessage();
49 49  
... ...
src/views/demo/comp/tinymce/index.vue renamed to src/views/demo/editor/tinymce/index.vue
1 1 <template>
2 2 <div class="flex p-4">
3   - <Tinymce value="Hello, World!" @change="handleChange" width="100%" />
  3 + {{ value }}
  4 + <Tinymce v-model="value" @change="handleChange" width="100%" />
4 5 </div>
5 6 </template>
6 7 <script lang="ts">
7   - import { defineComponent } from 'vue';
  8 + import { defineComponent, ref } from 'vue';
8 9 import { Tinymce } from '/@/components/Tinymce/index';
9 10  
10 11 export default defineComponent({
11 12 components: { Tinymce },
12 13 setup() {
  14 + const value = ref('hello world!');
13 15 function handleChange(value: string) {
14 16 console.log(value);
15 17 }
16   - return { handleChange };
  18 + // setTimeout(() => {
  19 + // value.value = '1233';
  20 + // }, 5000);
  21 + return { handleChange, value };
17 22 },
18 23 });
19 24 </script>
... ...