Parcourir la source

perf: review tinymce code

vben il y a 4 ans
Parent
commit
f75425d13b

+ 129 - 19
src/components/Tinymce/src/Editor.vue

@@ -1,59 +1,169 @@
 <template>
   <div class="tinymce-container" :style="{ width: containerWidth }">
-    <tinymce-editor
-      :id="id"
-      :init="initOptions"
-      :modelValue="tinymceContent"
-      @update:modelValue="handleChange"
-      :tinymceScriptSrc="tinymceScriptSrc"
-    ></tinymce-editor>
+    <textarea :id="tinymceId" visibility="hidden" ref="elRef"></textarea>
   </div>
 </template>
 
 <script lang="ts">
-  import TinymceEditor from './lib'; // TinyMCE vue wrapper
-  import { defineComponent, computed } from 'vue';
+  import {
+    defineComponent,
+    computed,
+    onMounted,
+    nextTick,
+    ref,
+    unref,
+    watch,
+    onUnmounted,
+    onDeactivated,
+  } from 'vue';
   import { basicProps } from './props';
   import toolbar from './toolbar';
   import plugins from './plugins';
+  import { getTinymce } from './getTinymce';
+  import { useScript } from '/@/hooks/web/useScript';
+  import { snowUuid } from '/@/utils/uuid';
+  import { bindHandlers } from './helper';
 
   const CDN_URL = 'https://cdn.bootcdn.net/ajax/libs/tinymce/5.5.1';
+
   const tinymceScriptSrc = `${CDN_URL}/tinymce.min.js`;
 
   export default defineComponent({
     name: 'Tinymce',
-    components: { TinymceEditor },
     props: basicProps,
-    setup(props, { emit }) {
+    emits: ['change', 'update:modelValue'],
+    setup(props, { emit, attrs }) {
+      const editorRef = ref<any>(null);
+      const elRef = ref<Nullable<HTMLElement>>(null);
+
+      const tinymceId = computed(() => {
+        return snowUuid('tiny-vue');
+      });
+
       const tinymceContent = computed(() => {
-        return props.value;
+        return props.modelValue;
       });
-      function handleChange(value: string) {
-        emit('change', value);
-      }
+
       const containerWidth = computed(() => {
         const width = props.width;
-        // Test matches `100`, `'100'`
         if (/^[\d]+(\.[\d]+)?$/.test(width.toString())) {
           return `${width}px`;
         }
         return width;
       });
+
       const initOptions = computed(() => {
-        const { id, height, menubar } = props;
+        const { height, menubar } = props;
         return {
-          selector: `#${id}`,
+          selector: `#${unref(tinymceId)}`,
           height: height,
           toolbar: toolbar,
+          theme: 'silver',
           menubar: menubar,
           plugins: plugins,
           // 语言包
           language_url: 'resource/tinymce/langs/zh_CN.js',
           // 中文
           language: 'zh_CN',
+          default_link_target: '_blank',
+          link_title: false,
+          advlist_bullet_styles: 'square',
+          advlist_number_styles: 'default',
+          object_resizing: false,
+          setup: (editor: any) => {
+            editorRef.value = editor;
+            editor.on('init', (e: Event) => initSetup(e));
+          },
         };
       });
-      return { containerWidth, initOptions, tinymceContent, handleChange, tinymceScriptSrc };
+
+      const { toPromise } = useScript({
+        src: tinymceScriptSrc,
+      });
+
+      watch(
+        () => attrs.disabled,
+        () => {
+          const editor = unref(editorRef);
+          if (!editor) return;
+          editor.setMode(attrs.disabled ? 'readonly' : 'design');
+        }
+      );
+
+      onMounted(() => {
+        nextTick(() => {
+          init();
+        });
+      });
+
+      onUnmounted(() => {
+        destory();
+      });
+
+      onDeactivated(() => {
+        destory();
+      });
+
+      function destory() {
+        if (getTinymce() !== null) {
+          getTinymce().remove(unref(editorRef));
+        }
+      }
+
+      function init() {
+        toPromise().then(() => {
+          initEditor();
+        });
+      }
+
+      function initEditor() {
+        getTinymce().init(unref(initOptions));
+      }
+
+      function initSetup(e: Event) {
+        const editor = unref(editorRef);
+        if (!editor) return;
+        const value = props.modelValue || '';
+
+        editor.setContent(value);
+        bindModelHandlers(editor);
+        bindHandlers(e, attrs, unref(editorRef));
+      }
+
+      function bindModelHandlers(editor: any) {
+        const modelEvents = attrs.modelEvents ? attrs.modelEvents : null;
+        const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
+        watch(
+          () => props.modelValue,
+          (val: string, prevVal: string) => {
+            if (
+              editor &&
+              typeof val === 'string' &&
+              val !== prevVal &&
+              val !== editor.getContent({ format: attrs.outputFormat })
+            ) {
+              editor.setContent(val);
+            }
+          }
+        );
+
+        editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
+          emit('update:modelValue', editor.getContent({ format: attrs.outputFormat }));
+        });
+      }
+
+      function handleChange(value: string) {
+        emit('change', value);
+      }
+      return {
+        containerWidth,
+        initOptions,
+        tinymceContent,
+        handleChange,
+        tinymceScriptSrc,
+        elRef,
+        tinymceId,
+      };
     },
   });
 </script>

+ 1 - 4
src/components/Tinymce/src/lib/TinyMCE.ts → src/components/Tinymce/src/getTinymce.ts

@@ -1,9 +1,6 @@
 const getGlobal = (): any => (typeof window !== 'undefined' ? window : global);
 
-const getTinymce = () => {
+export const getTinymce = () => {
   const global = getGlobal();
-
   return global && global.tinymce ? global.tinymce : null;
 };
-
-export { getTinymce };

+ 81 - 0
src/components/Tinymce/src/helper.ts

@@ -0,0 +1,81 @@
+const validEvents = [
+  'onActivate',
+  'onAddUndo',
+  'onBeforeAddUndo',
+  'onBeforeExecCommand',
+  'onBeforeGetContent',
+  'onBeforeRenderUI',
+  'onBeforeSetContent',
+  'onBeforePaste',
+  'onBlur',
+  'onChange',
+  'onClearUndos',
+  'onClick',
+  'onContextMenu',
+  'onCopy',
+  'onCut',
+  'onDblclick',
+  'onDeactivate',
+  'onDirty',
+  'onDrag',
+  'onDragDrop',
+  'onDragEnd',
+  'onDragGesture',
+  'onDragOver',
+  'onDrop',
+  'onExecCommand',
+  'onFocus',
+  'onFocusIn',
+  'onFocusOut',
+  'onGetContent',
+  'onHide',
+  'onInit',
+  'onKeyDown',
+  'onKeyPress',
+  'onKeyUp',
+  'onLoadContent',
+  'onMouseDown',
+  'onMouseEnter',
+  'onMouseLeave',
+  'onMouseMove',
+  'onMouseOut',
+  'onMouseOver',
+  'onMouseUp',
+  'onNodeChange',
+  'onObjectResizeStart',
+  'onObjectResized',
+  'onObjectSelected',
+  'onPaste',
+  'onPostProcess',
+  'onPostRender',
+  'onPreProcess',
+  'onProgressState',
+  'onRedo',
+  'onRemove',
+  'onReset',
+  'onSaveContent',
+  'onSelectionChange',
+  'onSetAttrib',
+  'onSetContent',
+  'onShow',
+  'onSubmit',
+  'onUndo',
+  'onVisualAid',
+];
+
+const isValidKey = (key: string) => validEvents.indexOf(key) !== -1;
+
+export const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => {
+  Object.keys(listeners)
+    .filter(isValidKey)
+    .forEach((key: string) => {
+      const handler = listeners[key];
+      if (typeof handler === 'function') {
+        if (key === 'onInit') {
+          handler(initEvent, editor);
+        } else {
+          editor.on(key.substring(2), (e: any) => handler(e, editor));
+        }
+      }
+    });
+};

+ 0 - 72
src/components/Tinymce/src/lib/ScriptLoader.ts

@@ -1,72 +0,0 @@
-import { uuid } from './Utils';
-
-export type callbackFn = () => void;
-export interface IStateObj {
-  listeners: callbackFn[];
-  scriptId: string;
-  scriptLoaded: boolean;
-}
-
-const createState = (): IStateObj => {
-  return {
-    listeners: [],
-    scriptId: uuid('tiny-script'),
-    scriptLoaded: false
-  };
-};
-
-interface ScriptLoader {
-  load: (doc: Document, url: string, callback: callbackFn) => void;
-  reinitialize: () => void;
-}
-
-const CreateScriptLoader = (): ScriptLoader => {
-  let state: IStateObj = createState();
-
-  const injectScriptTag = (scriptId: string, doc: Document, url: string, callback: callbackFn) => {
-    const scriptTag = doc.createElement('script');
-    scriptTag.referrerPolicy = 'origin';
-    scriptTag.type = 'application/javascript';
-    scriptTag.id = scriptId;
-    scriptTag.src = url;
-
-    const handler = () => {
-      scriptTag.removeEventListener('load', handler);
-      callback();
-    };
-    scriptTag.addEventListener('load', handler);
-    if (doc.head) {
-      doc.head.appendChild(scriptTag);
-    }
-  };
-
-  const load = (doc: Document, url: string, callback: callbackFn) => {
-    if (state.scriptLoaded) {
-      callback();
-    } else {
-      state.listeners.push(callback);
-      if (!doc.getElementById(state.scriptId)) {
-        injectScriptTag(state.scriptId, doc, url, () => {
-          state.listeners.forEach((fn) => fn());
-          state.scriptLoaded = true;
-        });
-      }
-    }
-  };
-
-  // Only to be used by tests.
-  const reinitialize = () => {
-    state = createState();
-  };
-
-  return {
-    load,
-    reinitialize
-  };
-};
-
-const ScriptLoader = CreateScriptLoader();
-
-export {
-  ScriptLoader
-};

+ 0 - 151
src/components/Tinymce/src/lib/Utils.ts

@@ -1,151 +0,0 @@
-import { ComponentPublicInstance } from 'vue';
-
-const validEvents = [
-  'onActivate',
-  'onAddUndo',
-  'onBeforeAddUndo',
-  'onBeforeExecCommand',
-  'onBeforeGetContent',
-  'onBeforeRenderUI',
-  'onBeforeSetContent',
-  'onBeforePaste',
-  'onBlur',
-  'onChange',
-  'onClearUndos',
-  'onClick',
-  'onContextMenu',
-  'onCopy',
-  'onCut',
-  'onDblclick',
-  'onDeactivate',
-  'onDirty',
-  'onDrag',
-  'onDragDrop',
-  'onDragEnd',
-  'onDragGesture',
-  'onDragOver',
-  'onDrop',
-  'onExecCommand',
-  'onFocus',
-  'onFocusIn',
-  'onFocusOut',
-  'onGetContent',
-  'onHide',
-  'onInit',
-  'onKeyDown',
-  'onKeyPress',
-  'onKeyUp',
-  'onLoadContent',
-  'onMouseDown',
-  'onMouseEnter',
-  'onMouseLeave',
-  'onMouseMove',
-  'onMouseOut',
-  'onMouseOver',
-  'onMouseUp',
-  'onNodeChange',
-  'onObjectResizeStart',
-  'onObjectResized',
-  'onObjectSelected',
-  'onPaste',
-  'onPostProcess',
-  'onPostRender',
-  'onPreProcess',
-  'onProgressState',
-  'onRedo',
-  'onRemove',
-  'onReset',
-  'onSaveContent',
-  'onSelectionChange',
-  'onSetAttrib',
-  'onSetContent',
-  'onShow',
-  'onSubmit',
-  'onUndo',
-  'onVisualAid'
-];
-
-const isValidKey = (key: string) => validEvents.indexOf(key) !== -1;
-
-const bindHandlers = (initEvent: Event, listeners: any, editor: any): void => {
-  Object.keys(listeners)
-    .filter(isValidKey)
-    .forEach((key: string) => {
-      const handler = listeners[key];
-      if (typeof handler === 'function') {
-        if (key === 'onInit') {
-          handler(initEvent, editor);
-        } else {
-          editor.on(key.substring(2), (e: any) => handler(e, editor));
-        }
-      }
-    });
-};
-
-const bindModelHandlers = (ctx: ComponentPublicInstance, editor: any) => {
-  const modelEvents = ctx.$props.modelEvents ? ctx.$props.modelEvents : null;
-  const normalizedEvents = Array.isArray(modelEvents) ? modelEvents.join(' ') : modelEvents;
-  // @ts-ignore
-  ctx.$watch('modelValue', (val: string, prevVal: string) => {
-    if (editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent({ format: ctx.$props.outputFormat })) {
-      editor.setContent(val);
-    }
-  });
-
-  editor.on(normalizedEvents ? normalizedEvents : 'change keyup undo redo', () => {
-    ctx.$emit('update:modelValue', editor.getContent({ format: ctx.$props.outputFormat }));
-  });
-};
-
-const initEditor = (initEvent: Event, ctx: ComponentPublicInstance, editor: any) => {
-  const value = ctx.$props.modelValue ? ctx.$props.modelValue : '';
-  const initialValue = ctx.$props.initialValue ? ctx.$props.initialValue : '';
-
-  editor.setContent(value || initialValue);
-
-  // checks if the v-model shorthand is used (which sets an v-on:input listener) and then binds either
-  // specified the events or defaults to "change keyup" event and emits the editor content on that event
-  if (ctx.$attrs['onUpdate:modelValue']) {
-    bindModelHandlers(ctx, editor);
-  }
-
-  bindHandlers(initEvent, ctx.$attrs, editor);
-};
-
-let unique = 0;
-
-const uuid = (prefix: string): string => {
-  const time = Date.now();
-  const random = Math.floor(Math.random() * 1000000000);
-
-  unique++;
-
-  return prefix + '_' + random + unique + String(time);
-};
-
-const isTextarea = (element: Element | null): element is HTMLTextAreaElement => {
-  return element !== null && element.tagName.toLowerCase() === 'textarea';
-};
-
-const normalizePluginArray = (plugins?: string | string[]): string[] => {
-  if (typeof plugins === 'undefined' || plugins === '') {
-    return [];
-  }
-
-  return Array.isArray(plugins) ? plugins : plugins.split(' ');
-};
-
-const mergePlugins = (initPlugins: string | string[], inputPlugins?: string | string[]) =>
-  normalizePluginArray(initPlugins).concat(normalizePluginArray(inputPlugins));
-
-const isNullOrUndefined = (value: any): value is null | undefined => value === null || value === undefined;
-
-export {
-  bindHandlers,
-  bindModelHandlers,
-  initEditor,
-  uuid,
-  isTextarea,
-  mergePlugins,
-  isNullOrUndefined
-};

+ 0 - 111
src/components/Tinymce/src/lib/components/Editor.ts

@@ -1,111 +0,0 @@
-/**
- * Copyright (c) 2018-present, Ephox, Inc.
- *
- * This source code is licensed under the Apache 2 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-// import { ThisTypedComponentOptionsWithRecordProps } from 'vue/types/options';
-// import { CreateElement, Vue } from 'vue/types/vue';
-
-import { ScriptLoader } from '../ScriptLoader';
-import { getTinymce } from '../TinyMCE';
-import { initEditor, isTextarea, mergePlugins, uuid, isNullOrUndefined } from '../Utils';
-import { editorProps, IPropTypes } from './EditorPropTypes';
-import { h, defineComponent, ComponentPublicInstance } from 'vue'
-
-
-export interface IEditor {
-  $props: Partial<IPropTypes>
-}
-
-declare module '@vue/runtime-core' {
-  interface ComponentCustomProperties {
-    elementId: string;
-    element: Element | null;
-    editor: any;
-    inlineEditor: boolean;
-    $props: Partial<IPropTypes>;
-  }
-}
-
-
-const renderInline = (id: string, tagName?: string) => {
-  return h(tagName ? tagName : 'div', {
-    id
-  });
-};
-
-const renderIframe = (id: string) => {
-  return h('textarea', {
-    id,
-    visibility: 'hidden'
-  });
-};
-
-const initialise = (ctx: ComponentPublicInstance) => () => {
-  const finalInit = {
-    ...ctx.$props.init,
-    readonly: ctx.$props.disabled,
-    selector: `#${ctx.elementId}`,
-    plugins: mergePlugins(ctx.$props.init && ctx.$props.init.plugins, ctx.$props.plugins),
-    toolbar: ctx.$props.toolbar || (ctx.$props.init && ctx.$props.init.toolbar),
-    inline: ctx.inlineEditor,
-    setup: (editor: any) => {
-      ctx.editor = editor;
-      editor.on('init', (e: Event) => initEditor(e, ctx, editor));
-
-      if (ctx.$props.init && typeof ctx.$props.init.setup === 'function') {
-        ctx.$props.init.setup(editor);
-      }
-    }
-  };
-
-  if (isTextarea(ctx.element)) {
-    ctx.element.style.visibility = '';
-  }
-
-  getTinymce().init(finalInit);
-};
-
-export const Editor = defineComponent({
-  props: editorProps,
-  created() {
-    this.elementId = this.$props.id || uuid('tiny-vue');
-    this.inlineEditor = (this.$props.init && this.$props.init.inline) || this.$props.inline;
-  },
-  watch: {
-    disabled() {
-      (this as any).editor.setMode(this.disabled ? 'readonly' : 'design');
-    }
-  },
-  mounted() {
-    this.element = this.$el;
-
-    if (getTinymce() !== null) {
-      initialise(this)();
-    } else if (this.element && this.element.ownerDocument) {
-      const channel = this.$props.cloudChannel ? this.$props.cloudChannel : '5';
-      const apiKey = this.$props.apiKey ? this.$props.apiKey : 'no-api-key';
-
-      const scriptSrc = isNullOrUndefined(this.$props.tinymceScriptSrc) ?
-        `https://cdn.tiny.cloud/1/${apiKey}/tinymce/${channel}/tinymce.min.js` :
-        this.$props.tinymceScriptSrc;
-
-      ScriptLoader.load(
-        this.element.ownerDocument,
-        scriptSrc,
-        initialise(this)
-      );
-    }
-  },
-  beforeUnmount() {
-    if (getTinymce() !== null) {
-      getTinymce().remove(this.editor);
-    }
-  },
-  render() {
-    return this.inlineEditor ? renderInline(this.elementId, this.$props.tagName) : renderIframe(this.elementId);
-  }
-})

+ 0 - 46
src/components/Tinymce/src/lib/components/EditorPropTypes.ts

@@ -1,46 +0,0 @@
-/**
- * Copyright (c) 2018-present, Ephox, Inc.
- *
- * This source code is licensed under the Apache 2 license found in the
- * LICENSE file in the root directory of this source tree.
- *
- */
-
-export type CopyProps<T> = { [P in keyof T]: any };
-
-export interface IPropTypes {
-  apiKey: string;
-  cloudChannel: string;
-  id: string;
-  init: any;
-  initialValue: string;
-  outputFormat: 'html' | 'text';
-  inline: boolean;
-  modelEvents: string[] | string;
-  plugins: string[] | string;
-  tagName: string;
-  toolbar: string[] | string;
-  modelValue: string;
-  disabled: boolean;
-  tinymceScriptSrc: string;
-}
-
-export const editorProps: CopyProps<IPropTypes> = {
-  apiKey: String,
-  cloudChannel: String,
-  id: String,
-  init: Object,
-  initialValue: String,
-  inline: Boolean,
-  modelEvents: [String, Array],
-  plugins: [String, Array],
-  tagName: String,
-  toolbar: [String, Array],
-  modelValue: String,
-  disabled: Boolean,
-  tinymceScriptSrc: String,
-  outputFormat: {
-    type: String,
-    validator: (prop: string) => prop === 'html' || prop === 'text'
-  },
-};

+ 0 - 4
src/components/Tinymce/src/lib/global.d.ts

@@ -1,4 +0,0 @@
-// Global compile-time constants
-declare var __DEV__: boolean
-declare var __BROWSER__: boolean
-declare var __CI__: boolean

+ 0 - 3
src/components/Tinymce/src/lib/index.ts

@@ -1,3 +0,0 @@
-import { Editor } from './components/Editor';
-
-export default Editor;

+ 4 - 6
src/components/Tinymce/src/props.ts

@@ -1,12 +1,6 @@
 import { PropType } from 'vue';
 
 export const basicProps = {
-  id: {
-    type: String as PropType<string>,
-    default: () => {
-      return `tinymce-${new Date().getTime()}${(Math.random() * 1000).toFixed(0)}`;
-    },
-  },
   menubar: {
     type: String as PropType<string>,
     default: 'file edit insert view format table',
@@ -15,6 +9,10 @@ export const basicProps = {
     type: String as PropType<string>,
     // default: ''
   },
+  modelValue: {
+    type: String as PropType<string>,
+    // default: ''
+  },
   // 高度
   height: {
     type: [Number, String] as PropType<string | number>,

+ 5 - 5
src/router/menus/modules/demo/charts.ts

@@ -6,23 +6,23 @@ const menu: MenuModule = {
     path: '/charts',
     children: [
       {
-        path: '/apexChart',
+        path: 'apexChart',
         name: 'ApexChart',
       },
       {
-        path: '/echarts',
+        path: 'echarts',
         name: 'Echarts',
         children: [
           {
-            path: '/map',
+            path: 'map',
             name: '地图',
           },
           {
-            path: '/line',
+            path: 'line',
             name: '折线图',
           },
           {
-            path: '/pie',
+            path: 'pie',
             name: '饼图',
           },
         ],

+ 11 - 25
src/router/menus/modules/demo/comp.ts

@@ -6,16 +6,16 @@ const menu: MenuModule = {
     path: '/comp',
     children: [
       {
-        path: '/basic',
+        path: 'basic',
         name: '基础组件',
       },
       {
-        path: '/countTo',
+        path: 'countTo',
         name: '数字动画',
       },
 
       {
-        path: '/scroll',
+        path: 'scroll',
         name: '滚动组件',
         children: [
           {
@@ -33,53 +33,39 @@ const menu: MenuModule = {
         ],
       },
       {
-        path: '/modal',
+        path: 'modal',
         name: '弹窗扩展',
       },
       {
-        path: '/drawer',
+        path: 'drawer',
         name: '抽屉扩展',
       },
       {
-        path: '/desc',
+        path: 'desc',
         name: '详情组件',
       },
       {
-        path: '/verify',
+        path: 'verify',
         name: '验证组件',
         children: [
           {
-            path: '/drag',
+            path: 'drag',
             name: '拖拽校验',
           },
           {
-            path: '/rotate',
+            path: 'rotate',
             name: '图片还原校验',
           },
         ],
       },
       {
-        path: '/qrcode',
+        path: 'qrcode',
         name: '二维码组件',
       },
       {
-        path: '/strength-meter',
+        path: 'strength-meter',
         name: '密码强度组件',
       },
-      {
-        path: '/tinymce',
-        name: '富文本',
-        children: [
-          {
-            path: '/index',
-            name: '基础使用',
-          },
-          {
-            path: '/editor',
-            name: '嵌入form使用',
-          },
-        ],
-      },
     ],
   },
 };

+ 15 - 1
src/router/menus/modules/demo/editor.ts

@@ -6,9 +6,23 @@ const menu: MenuModule = {
     path: '/editor',
     children: [
       {
-        path: '/markdown',
+        path: 'markdown',
         name: 'markdown编辑器',
       },
+      {
+        path: 'tinymce',
+        name: '富文本',
+        children: [
+          {
+            path: 'index',
+            name: '基础使用',
+          },
+          // {
+          //   path: 'editor',
+          //   name: '嵌入form使用',
+          // },
+        ],
+      },
     ],
   },
 };

+ 4 - 6
src/router/menus/modules/demo/excel.ts

@@ -6,23 +6,21 @@ const menu: MenuModule = {
     path: '/excel',
     children: [
       {
-        path: '/customExport',
+        path: 'customExport',
         name: '选择导出格式',
       },
       {
-        path: '/jsonExport',
+        path: 'jsonExport',
         name: 'JSON数据导出',
       },
       {
-        path: '/arrayExport',
+        path: 'arrayExport',
         name: 'Array数据导出',
       },
       {
-        path: '/importExcel',
+        path: 'importExcel',
         name: '导入',
       },
-      //     ],
-      //   },
     ],
   },
 };

+ 6 - 6
src/router/menus/modules/demo/exception.ts

@@ -6,27 +6,27 @@ const menu: MenuModule = {
     path: '/exception',
     children: [
       {
-        path: '/404',
+        path: '404',
         name: '404',
       },
       {
-        path: '/500',
+        path: '500',
         name: '500',
       },
       {
-        path: '/net-work-error',
+        path: 'net-work-error',
         name: '网络错误',
       },
       {
-        path: '/page-time-out',
+        path: 'page-time-out',
         name: '页面超时',
       },
       {
-        path: '/not-data',
+        path: 'not-data',
         name: '无数据',
       },
       {
-        path: '/error-log',
+        path: 'error-log',
         name: '错误日志',
       },
     ],

+ 13 - 13
src/router/menus/modules/demo/feat.ts

@@ -6,55 +6,55 @@ const menu: MenuModule = {
     path: '/feat',
     children: [
       {
-        path: '/icon',
+        path: 'icon',
         name: '图标',
       },
       {
-        path: '/tabs',
+        path: 'tabs',
         name: '标签页操作',
       },
       {
-        path: '/context-menu',
+        path: 'context-menu',
         name: '右键菜单',
       },
       {
-        path: '/click-out-side',
+        path: 'click-out-side',
         name: 'ClickOutSide',
       },
       {
-        path: '/img-preview',
+        path: 'img-preview',
         name: '图片预览',
       },
       {
-        path: '/i18n',
+        path: 'i18n',
         name: '国际化',
       },
       {
-        path: '/copy',
+        path: 'copy',
         name: '剪切板',
       },
       {
-        path: '/msg',
+        path: 'msg',
         name: '消息提示',
       },
       {
-        path: '/watermark',
+        path: 'watermark',
         name: '水印',
       },
       {
-        path: '/full-screen',
+        path: 'full-screen',
         name: '全屏',
       },
       {
-        path: '/testTab',
+        path: 'testTab',
         name: '带参Tab',
         children: [
           {
-            path: '/id1',
+            path: 'id1',
             name: '带参tab1',
           },
           {
-            path: '/id2',
+            path: 'id2',
             name: '带参tab2',
           },
         ],

+ 7 - 7
src/router/menus/modules/demo/form.ts

@@ -6,31 +6,31 @@ const menu: MenuModule = {
     name: 'Form',
     children: [
       {
-        path: '/basic',
+        path: 'basic',
         name: '基础表单',
       },
       {
-        path: '/useForm',
+        path: 'useForm',
         name: 'useForm',
       },
       {
-        path: '/refForm',
+        path: 'refForm',
         name: 'RefForm',
       },
       {
-        path: '/advancedForm',
+        path: 'advancedForm',
         name: '可收缩表单',
       },
       {
-        path: '/ruleForm',
+        path: 'ruleForm',
         name: '表单校验',
       },
       {
-        path: '/dynamicForm',
+        path: 'dynamicForm',
         name: '动态表单',
       },
       {
-        path: '/customerForm',
+        path: 'customerForm',
         name: '自定义组件',
       },
     ],

+ 3 - 3
src/router/menus/modules/demo/iframe.ts

@@ -6,15 +6,15 @@ const menu: MenuModule = {
     path: '/frame',
     children: [
       {
-        path: '/antv',
+        path: 'antv',
         name: 'antVue文档(内嵌)',
       },
       {
-        path: '/doc',
+        path: 'doc',
         name: '项目文档(内嵌)',
       },
       {
-        path: '/docExternal',
+        path: 'docExternal',
         name: '项目文档(外链)',
       },
     ],

+ 8 - 8
src/router/menus/modules/demo/permission.ts

@@ -6,37 +6,37 @@ const menu: MenuModule = {
     path: '/permission',
     children: [
       {
-        path: '/front',
+        path: 'front',
         name: '基于前端',
         children: [
           {
-            path: '/page',
+            path: 'page',
             name: '页面权限',
           },
           {
-            path: '/btn',
+            path: 'btn',
             name: '按钮权限',
           },
           {
-            path: '/auth-pageA',
+            path: 'auth-pageA',
             name: '权限测试页A',
           },
           {
-            path: '/auth-pageB',
+            path: 'auth-pageB',
             name: '权限测试页B',
           },
         ],
       },
       {
-        path: '/back',
+        path: 'back',
         name: '基于后台',
         children: [
           {
-            path: '/page',
+            path: 'page',
             name: '页面权限',
           },
           {
-            path: '/btn',
+            path: 'btn',
             name: '按钮权限',
           },
         ],

+ 14 - 14
src/router/menus/modules/demo/table.ts

@@ -6,59 +6,59 @@ const menu: MenuModule = {
     name: 'Table',
     children: [
       {
-        path: '/basic',
+        path: 'basic',
         name: '基础表格',
       },
       {
-        path: '/treeTable',
+        path: 'treeTable',
         name: '树形表格',
       },
       {
-        path: '/fetchTable',
+        path: 'fetchTable',
         name: '远程加载',
       },
       {
-        path: '/fixedColumn',
+        path: 'fixedColumn',
         name: '固定列',
       },
       {
-        path: '/customerCell',
+        path: 'customerCell',
         name: '自定义列',
       },
       {
-        path: '/formTable',
+        path: 'formTable',
         name: '开启搜索区域',
       },
       {
-        path: '/useTable',
+        path: 'useTable',
         name: 'UseTable',
       },
       {
-        path: '/refTable',
+        path: 'refTable',
         name: 'RefTable',
       },
       {
-        path: '/multipleHeader',
+        path: 'multipleHeader',
         name: '多级表头',
       },
       {
-        path: '/mergeHeader',
+        path: 'mergeHeader',
         name: '合并单元格',
       },
       {
-        path: '/expandTable',
+        path: 'expandTable',
         name: '可展开表格',
       },
       {
-        path: '/fixedHeight',
+        path: 'fixedHeight',
         name: '定高/头部自定义',
       },
       {
-        path: '/footerTable',
+        path: 'footerTable',
         name: '表尾行合计',
       },
       {
-        path: '/editCellTable',
+        path: 'editCellTable',
         name: '可编辑单元格',
       },
     ],

+ 0 - 26
src/router/routes/modules/demo/comp.ts

@@ -136,31 +136,5 @@ export default {
         title: '密码强度组件',
       },
     },
-    {
-      path: '/tinymce',
-      name: 'TinymceDemo',
-      meta: {
-        title: '富文本',
-      },
-      redirect: '/comp/tinymce/index',
-      children: [
-        {
-          path: 'index',
-          name: 'Tinymce',
-          component: () => import('/@/views/demo/comp/tinymce/index.vue'),
-          meta: {
-            title: '基础使用',
-          },
-        },
-        {
-          path: 'editor',
-          name: 'TinymceEditor',
-          component: () => import('/@/views/demo/comp/tinymce/Editor.vue'),
-          meta: {
-            title: '嵌入form使用',
-          },
-        },
-      ],
-    },
   ],
 } as AppRouteModule;

+ 27 - 0
src/router/routes/modules/demo/editor.ts

@@ -23,5 +23,32 @@ export default {
         title: 'markdown编辑器',
       },
     },
+    {
+      path: '/tinymce',
+      name: 'TinymceDemo',
+      meta: {
+        title: '富文本',
+      },
+      redirect: '/editor/tinymce/index',
+      children: [
+        {
+          path: 'index',
+          name: 'TinymceBasicDemo',
+          component: () => import('/@/views/demo/editor/tinymce/index.vue'),
+          meta: {
+            title: '基础使用',
+          },
+        },
+        // TODO
+        // {
+        //   path: 'editor',
+        //   name: 'TinymceFormDemo',
+        //   component: () => import('/@/views/demo/comp/tinymce/Editor.vue'),
+        //   meta: {
+        //     title: '嵌入form使用',
+        //   },
+        // },
+      ],
+    },
   ],
 } as AppRouteModule;

+ 4 - 0
src/utils/is.ts

@@ -67,3 +67,7 @@ export const isServer = typeof window === 'undefined';
 export function isImageDom(o: Element) {
   return o && ['IMAGE', 'IMG'].includes(o.tagName);
 }
+
+export const isTextarea = (element: Element | null): element is HTMLTextAreaElement => {
+  return element !== null && element.tagName.toLowerCase() === 'textarea';
+};

+ 8 - 0
src/utils/uuid.ts

@@ -17,3 +17,11 @@ export function buildUUID(): string {
   }
   return uuid.replace(/-/g, '');
 }
+
+let unique = 0;
+export function snowUuid(prefix: string): string {
+  const time = Date.now();
+  const random = Math.floor(Math.random() * 1000000000);
+  unique++;
+  return prefix + '_' + random + unique + String(time);
+}

+ 1 - 1
src/views/demo/comp/tinymce/Editor.vue → src/views/demo/editor/tinymce/Editor.vue

@@ -43,7 +43,7 @@
     },
   ];
   export default defineComponent({
-    components: { BasicForm, CollapseContainer, Tinymce },
+    components: { BasicForm, CollapseContainer },
     setup() {
       const { createMessage } = useMessage();
 

+ 8 - 3
src/views/demo/comp/tinymce/index.vue → src/views/demo/editor/tinymce/index.vue

@@ -1,19 +1,24 @@
 <template>
   <div class="flex p-4">
-    <Tinymce value="Hello, World!" @change="handleChange" width="100%" />
+    {{ value }}
+    <Tinymce v-model="value" @change="handleChange" width="100%" />
   </div>
 </template>
 <script lang="ts">
-  import { defineComponent } from 'vue';
+  import { defineComponent, ref } from 'vue';
   import { Tinymce } from '/@/components/Tinymce/index';
 
   export default defineComponent({
     components: { Tinymce },
     setup() {
+      const value = ref('hello world!');
       function handleChange(value: string) {
         console.log(value);
       }
-      return { handleChange };
+      // setTimeout(() => {
+      //   value.value = '1233';
+      // }, 5000);
+      return { handleChange, value };
     },
   });
 </script>