From f75425d13bc9f6003021fd4b5d6451ae096c09b7 Mon Sep 17 00:00:00 2001 From: vben <anncwb@126.com> Date: Thu, 22 Oct 2020 23:56:33 +0800 Subject: [PATCH] perf: review tinymce code --- src/components/Tinymce/src/Editor.vue | 148 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------- src/components/Tinymce/src/getTinymce.ts | 6 ++++++ src/components/Tinymce/src/helper.ts | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/components/Tinymce/src/lib/ScriptLoader.ts | 72 ------------------------------------------------------------------------ src/components/Tinymce/src/lib/TinyMCE.ts | 9 --------- src/components/Tinymce/src/lib/Utils.ts | 151 ------------------------------------------------------------------------------------------------------------------------------------------------------- src/components/Tinymce/src/lib/components/Editor.ts | 111 --------------------------------------------------------------------------------------------------------------- src/components/Tinymce/src/lib/components/EditorPropTypes.ts | 46 ---------------------------------------------- src/components/Tinymce/src/lib/global.d.ts | 4 ---- src/components/Tinymce/src/lib/index.ts | 3 --- src/components/Tinymce/src/props.ts | 10 ++++------ src/router/menus/modules/demo/charts.ts | 10 +++++----- src/router/menus/modules/demo/comp.ts | 36 +++++++++++------------------------- src/router/menus/modules/demo/editor.ts | 16 +++++++++++++++- src/router/menus/modules/demo/excel.ts | 10 ++++------ src/router/menus/modules/demo/exception.ts | 12 ++++++------ src/router/menus/modules/demo/feat.ts | 26 +++++++++++++------------- src/router/menus/modules/demo/form.ts | 14 +++++++------- src/router/menus/modules/demo/iframe.ts | 6 +++--- src/router/menus/modules/demo/permission.ts | 16 ++++++++-------- src/router/menus/modules/demo/table.ts | 28 ++++++++++++++-------------- src/router/routes/modules/demo/comp.ts | 26 -------------------------- src/router/routes/modules/demo/editor.ts | 27 +++++++++++++++++++++++++++ src/utils/is.ts | 4 ++++ src/utils/uuid.ts | 8 ++++++++ src/views/demo/comp/tinymce/Editor.vue | 58 ---------------------------------------------------------- src/views/demo/comp/tinymce/index.vue | 19 ------------------- src/views/demo/editor/tinymce/Editor.vue | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/views/demo/editor/tinymce/index.vue | 24 ++++++++++++++++++++++++ 29 files changed, 427 insertions(+), 612 deletions(-) create mode 100644 src/components/Tinymce/src/getTinymce.ts create mode 100644 src/components/Tinymce/src/helper.ts delete mode 100644 src/components/Tinymce/src/lib/ScriptLoader.ts delete mode 100644 src/components/Tinymce/src/lib/TinyMCE.ts delete mode 100644 src/components/Tinymce/src/lib/Utils.ts delete mode 100644 src/components/Tinymce/src/lib/components/Editor.ts delete mode 100644 src/components/Tinymce/src/lib/components/EditorPropTypes.ts delete mode 100644 src/components/Tinymce/src/lib/global.d.ts delete mode 100644 src/components/Tinymce/src/lib/index.ts delete mode 100644 src/views/demo/comp/tinymce/Editor.vue delete mode 100644 src/views/demo/comp/tinymce/index.vue create mode 100644 src/views/demo/editor/tinymce/Editor.vue create mode 100644 src/views/demo/editor/tinymce/index.vue diff --git a/src/components/Tinymce/src/Editor.vue b/src/components/Tinymce/src/Editor.vue index 3fa9726..e0b9b5f 100644 --- a/src/components/Tinymce/src/Editor.vue +++ b/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> diff --git a/src/components/Tinymce/src/getTinymce.ts b/src/components/Tinymce/src/getTinymce.ts new file mode 100644 index 0000000..6c4e7a3 --- /dev/null +++ b/src/components/Tinymce/src/getTinymce.ts @@ -0,0 +1,6 @@ +const getGlobal = (): any => (typeof window !== 'undefined' ? window : global); + +export const getTinymce = () => { + const global = getGlobal(); + return global && global.tinymce ? global.tinymce : null; +}; diff --git a/src/components/Tinymce/src/helper.ts b/src/components/Tinymce/src/helper.ts new file mode 100644 index 0000000..2526ae7 --- /dev/null +++ b/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)); + } + } + }); +}; diff --git a/src/components/Tinymce/src/lib/ScriptLoader.ts b/src/components/Tinymce/src/lib/ScriptLoader.ts deleted file mode 100644 index d747505..0000000 --- a/src/components/Tinymce/src/lib/ScriptLoader.ts +++ /dev/null @@ -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 -}; \ No newline at end of file diff --git a/src/components/Tinymce/src/lib/TinyMCE.ts b/src/components/Tinymce/src/lib/TinyMCE.ts deleted file mode 100644 index 37a7925..0000000 --- a/src/components/Tinymce/src/lib/TinyMCE.ts +++ /dev/null @@ -1,9 +0,0 @@ -const getGlobal = (): any => (typeof window !== 'undefined' ? window : global); - -const getTinymce = () => { - const global = getGlobal(); - - return global && global.tinymce ? global.tinymce : null; -}; - -export { getTinymce }; diff --git a/src/components/Tinymce/src/lib/Utils.ts b/src/components/Tinymce/src/lib/Utils.ts deleted file mode 100644 index 2685ee9..0000000 --- a/src/components/Tinymce/src/lib/Utils.ts +++ /dev/null @@ -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 -}; \ No newline at end of file diff --git a/src/components/Tinymce/src/lib/components/Editor.ts b/src/components/Tinymce/src/lib/components/Editor.ts deleted file mode 100644 index 1317515..0000000 --- a/src/components/Tinymce/src/lib/components/Editor.ts +++ /dev/null @@ -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); - } -}) diff --git a/src/components/Tinymce/src/lib/components/EditorPropTypes.ts b/src/components/Tinymce/src/lib/components/EditorPropTypes.ts deleted file mode 100644 index 23619fe..0000000 --- a/src/components/Tinymce/src/lib/components/EditorPropTypes.ts +++ /dev/null @@ -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' - }, -}; diff --git a/src/components/Tinymce/src/lib/global.d.ts b/src/components/Tinymce/src/lib/global.d.ts deleted file mode 100644 index d1785b3..0000000 --- a/src/components/Tinymce/src/lib/global.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -// Global compile-time constants -declare var __DEV__: boolean -declare var __BROWSER__: boolean -declare var __CI__: boolean diff --git a/src/components/Tinymce/src/lib/index.ts b/src/components/Tinymce/src/lib/index.ts deleted file mode 100644 index 7cf759e..0000000 --- a/src/components/Tinymce/src/lib/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Editor } from './components/Editor'; - -export default Editor; diff --git a/src/components/Tinymce/src/props.ts b/src/components/Tinymce/src/props.ts index 8f7e227..231ae19 100644 --- a/src/components/Tinymce/src/props.ts +++ b/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>, diff --git a/src/router/menus/modules/demo/charts.ts b/src/router/menus/modules/demo/charts.ts index 7796799..fd8c6db 100644 --- a/src/router/menus/modules/demo/charts.ts +++ b/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: '饼图', }, ], diff --git a/src/router/menus/modules/demo/comp.ts b/src/router/menus/modules/demo/comp.ts index c477de2..b8b47a7 100644 --- a/src/router/menus/modules/demo/comp.ts +++ b/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使用', - }, - ], - }, ], }, }; diff --git a/src/router/menus/modules/demo/editor.ts b/src/router/menus/modules/demo/editor.ts index 0fbdaa2..eb307dc 100644 --- a/src/router/menus/modules/demo/editor.ts +++ b/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使用', + // }, + ], + }, ], }, }; diff --git a/src/router/menus/modules/demo/excel.ts b/src/router/menus/modules/demo/excel.ts index affef81..53a619b 100644 --- a/src/router/menus/modules/demo/excel.ts +++ b/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: '导入', }, - // ], - // }, ], }, }; diff --git a/src/router/menus/modules/demo/exception.ts b/src/router/menus/modules/demo/exception.ts index 7e5bf4e..a3e1198 100644 --- a/src/router/menus/modules/demo/exception.ts +++ b/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: '错误日志', }, ], diff --git a/src/router/menus/modules/demo/feat.ts b/src/router/menus/modules/demo/feat.ts index 83f854e..603c43e 100644 --- a/src/router/menus/modules/demo/feat.ts +++ b/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', }, ], diff --git a/src/router/menus/modules/demo/form.ts b/src/router/menus/modules/demo/form.ts index d1c5c68..01a8e32 100644 --- a/src/router/menus/modules/demo/form.ts +++ b/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: '自定义组件', }, ], diff --git a/src/router/menus/modules/demo/iframe.ts b/src/router/menus/modules/demo/iframe.ts index cf34045..14b1024 100644 --- a/src/router/menus/modules/demo/iframe.ts +++ b/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: '项目文档(外链)', }, ], diff --git a/src/router/menus/modules/demo/permission.ts b/src/router/menus/modules/demo/permission.ts index 237d207..2fc6dac 100644 --- a/src/router/menus/modules/demo/permission.ts +++ b/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: '按钮权限', }, ], diff --git a/src/router/menus/modules/demo/table.ts b/src/router/menus/modules/demo/table.ts index 7bad75c..555d60e 100644 --- a/src/router/menus/modules/demo/table.ts +++ b/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: '可编辑单元格', }, ], diff --git a/src/router/routes/modules/demo/comp.ts b/src/router/routes/modules/demo/comp.ts index 934dd24..dc7c687 100644 --- a/src/router/routes/modules/demo/comp.ts +++ b/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; diff --git a/src/router/routes/modules/demo/editor.ts b/src/router/routes/modules/demo/editor.ts index 482a0bd..cf54ea3 100644 --- a/src/router/routes/modules/demo/editor.ts +++ b/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; diff --git a/src/utils/is.ts b/src/utils/is.ts index 99ef156..5657c1c 100644 --- a/src/utils/is.ts +++ b/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'; +}; diff --git a/src/utils/uuid.ts b/src/utils/uuid.ts index f9a24cf..6b66f65 100644 --- a/src/utils/uuid.ts +++ b/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); +} diff --git a/src/views/demo/comp/tinymce/Editor.vue b/src/views/demo/comp/tinymce/Editor.vue deleted file mode 100644 index 2b9de95..0000000 --- a/src/views/demo/comp/tinymce/Editor.vue +++ /dev/null @@ -1,58 +0,0 @@ -<template> - <div class="m-4"> - <CollapseContainer title="富文本表单"> - <BasicForm - :labelWidth="100" - :schemas="schemas" - :actionColOptions="{ span: 24 }" - @submit="handleSubmit" - > - </BasicForm> - </CollapseContainer> - </div> -</template> -<script lang="ts"> - import { defineComponent, h } from 'vue'; - import { BasicForm, FormSchema } from '/@/components/Form/index'; - import { CollapseContainer } from '/@/components/Container/index'; - import { useMessage } from '/@/hooks/web/useMessage'; - import { Tinymce } from '/@/components/Tinymce/index'; - - const schemas: FormSchema[] = [ - { - field: 'title', - component: 'Input', - label: 'title', - defaultValue: 'defaultValue', - rules: [{ required: true }], - }, - { - field: 'tinymce', - component: 'Input', - label: 'tinymce', - defaultValue: 'defaultValue', - rules: [{ required: true }], - render: ({ model, field }) => { - return h(Tinymce, { - value: model[field], - onChange: (value: string) => { - model[field] = value; - }, - }); - }, - }, - ]; - export default defineComponent({ - components: { BasicForm, CollapseContainer, Tinymce }, - setup() { - const { createMessage } = useMessage(); - - return { - schemas, - handleSubmit: (values: any) => { - createMessage.success('click search,values:' + JSON.stringify(values)); - }, - }; - }, - }); -</script> diff --git a/src/views/demo/comp/tinymce/index.vue b/src/views/demo/comp/tinymce/index.vue deleted file mode 100644 index 5392192..0000000 --- a/src/views/demo/comp/tinymce/index.vue +++ /dev/null @@ -1,19 +0,0 @@ -<template> - <div class="flex p-4"> - <Tinymce value="Hello, World!" @change="handleChange" width="100%" /> - </div> -</template> -<script lang="ts"> - import { defineComponent } from 'vue'; - import { Tinymce } from '/@/components/Tinymce/index'; - - export default defineComponent({ - components: { Tinymce }, - setup() { - function handleChange(value: string) { - console.log(value); - } - return { handleChange }; - }, - }); -</script> diff --git a/src/views/demo/editor/tinymce/Editor.vue b/src/views/demo/editor/tinymce/Editor.vue new file mode 100644 index 0000000..b5f9bb0 --- /dev/null +++ b/src/views/demo/editor/tinymce/Editor.vue @@ -0,0 +1,58 @@ +<template> + <div class="m-4"> + <CollapseContainer title="富文本表单"> + <BasicForm + :labelWidth="100" + :schemas="schemas" + :actionColOptions="{ span: 24 }" + @submit="handleSubmit" + > + </BasicForm> + </CollapseContainer> + </div> +</template> +<script lang="ts"> + import { defineComponent, h } from 'vue'; + import { BasicForm, FormSchema } from '/@/components/Form/index'; + import { CollapseContainer } from '/@/components/Container/index'; + import { useMessage } from '/@/hooks/web/useMessage'; + import { Tinymce } from '/@/components/Tinymce/index'; + + const schemas: FormSchema[] = [ + { + field: 'title', + component: 'Input', + label: 'title', + defaultValue: 'defaultValue', + rules: [{ required: true }], + }, + { + field: 'tinymce', + component: 'Input', + label: 'tinymce', + defaultValue: 'defaultValue', + rules: [{ required: true }], + render: ({ model, field }) => { + return h(Tinymce, { + value: model[field], + onChange: (value: string) => { + model[field] = value; + }, + }); + }, + }, + ]; + export default defineComponent({ + components: { BasicForm, CollapseContainer }, + setup() { + const { createMessage } = useMessage(); + + return { + schemas, + handleSubmit: (values: any) => { + createMessage.success('click search,values:' + JSON.stringify(values)); + }, + }; + }, + }); +</script> diff --git a/src/views/demo/editor/tinymce/index.vue b/src/views/demo/editor/tinymce/index.vue new file mode 100644 index 0000000..b3c2e5e --- /dev/null +++ b/src/views/demo/editor/tinymce/index.vue @@ -0,0 +1,24 @@ +<template> + <div class="flex p-4"> + {{ value }} + <Tinymce v-model="value" @change="handleChange" width="100%" /> + </div> +</template> +<script lang="ts"> + 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); + } + // setTimeout(() => { + // value.value = '1233'; + // }, 5000); + return { handleChange, value }; + }, + }); +</script> -- libgit2 0.23.3