mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 15:41:32 +08:00
feat(tinymce): add rich editor
This commit is contained in:
1
src/components/Tinymce/index.ts
Normal file
1
src/components/Tinymce/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as Tinymce } from './src/Editor.vue';
|
90
src/components/Tinymce/src/Editor.vue
Normal file
90
src/components/Tinymce/src/Editor.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="tinymce-container" :style="{ width: containerWidth }">
|
||||
<tinymce-editor
|
||||
:id="id"
|
||||
:init="initOptions"
|
||||
:modelValue="tinymceContent"
|
||||
@update:modelValue="handleChange"
|
||||
:tinymceScriptSrc="tinymceScriptSrc"
|
||||
></tinymce-editor>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import TinymceEditor from './lib'; // TinyMCE vue wrapper
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import { basicProps } from './props';
|
||||
import toolbar from './toolbar';
|
||||
import plugins from './plugins';
|
||||
|
||||
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 }) {
|
||||
const tinymceContent = computed(() => {
|
||||
return props.value;
|
||||
});
|
||||
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;
|
||||
return {
|
||||
selector: `#${id}`,
|
||||
height: height,
|
||||
toolbar: toolbar,
|
||||
menubar: menubar,
|
||||
plugins: plugins,
|
||||
// 语言包
|
||||
language_url: 'resource/tinymce/langs/zh_CN.js',
|
||||
// 中文
|
||||
language: 'zh_CN',
|
||||
};
|
||||
});
|
||||
return { containerWidth, initOptions, tinymceContent, handleChange, tinymceScriptSrc };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.tinymce-container {
|
||||
position: relative;
|
||||
line-height: normal;
|
||||
|
||||
.mce-fullscreen {
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-custom-btn-container {
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
|
||||
&.fullscreen {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-upload-btn {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
textarea {
|
||||
z-index: -1;
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
72
src/components/Tinymce/src/lib/ScriptLoader.ts
Normal file
72
src/components/Tinymce/src/lib/ScriptLoader.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
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
|
||||
};
|
9
src/components/Tinymce/src/lib/TinyMCE.ts
Normal file
9
src/components/Tinymce/src/lib/TinyMCE.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
const getGlobal = (): any => (typeof window !== 'undefined' ? window : global);
|
||||
|
||||
const getTinymce = () => {
|
||||
const global = getGlobal();
|
||||
|
||||
return global && global.tinymce ? global.tinymce : null;
|
||||
};
|
||||
|
||||
export { getTinymce };
|
151
src/components/Tinymce/src/lib/Utils.ts
Normal file
151
src/components/Tinymce/src/lib/Utils.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
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
|
||||
};
|
111
src/components/Tinymce/src/lib/components/Editor.ts
Normal file
111
src/components/Tinymce/src/lib/components/Editor.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
})
|
46
src/components/Tinymce/src/lib/components/EditorPropTypes.ts
Normal file
46
src/components/Tinymce/src/lib/components/EditorPropTypes.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 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'
|
||||
},
|
||||
};
|
4
src/components/Tinymce/src/lib/global.d.ts
vendored
Normal file
4
src/components/Tinymce/src/lib/global.d.ts
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
// Global compile-time constants
|
||||
declare var __DEV__: boolean
|
||||
declare var __BROWSER__: boolean
|
||||
declare var __CI__: boolean
|
3
src/components/Tinymce/src/lib/index.ts
Normal file
3
src/components/Tinymce/src/lib/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Editor } from './components/Editor';
|
||||
|
||||
export default Editor;
|
10
src/components/Tinymce/src/plugins.ts
Normal file
10
src/components/Tinymce/src/plugins.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
// Any plugins you want to use has to be imported
|
||||
// Detail plugins list see https://www.tinymce.com/docs/plugins/
|
||||
// Custom builds see https://www.tinymce.com/download/custom-builds/
|
||||
// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration
|
||||
|
||||
const plugins = [
|
||||
'advlist anchor autolink autosave code codesample directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textpattern visualblocks visualchars wordcount',
|
||||
];
|
||||
|
||||
export default plugins;
|
31
src/components/Tinymce/src/props.ts
Normal file
31
src/components/Tinymce/src/props.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
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',
|
||||
},
|
||||
value: {
|
||||
type: String as PropType<string>,
|
||||
// default: ''
|
||||
},
|
||||
// 高度
|
||||
height: {
|
||||
type: [Number, String] as PropType<string | number>,
|
||||
required: false,
|
||||
default: 400,
|
||||
},
|
||||
|
||||
// 宽度
|
||||
width: {
|
||||
type: [Number, String] as PropType<string | number>,
|
||||
required: false,
|
||||
default: 'auto',
|
||||
},
|
||||
};
|
9
src/components/Tinymce/src/toolbar.ts
Normal file
9
src/components/Tinymce/src/toolbar.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
// Here is a list of the toolbar
|
||||
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
|
||||
|
||||
const toolbar = [
|
||||
'searchreplace bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample',
|
||||
'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor fullscreen',
|
||||
];
|
||||
|
||||
export default toolbar;
|
@@ -66,6 +66,20 @@ const menu: MenuModule = {
|
||||
path: '/strength-meter',
|
||||
name: '密码强度组件',
|
||||
},
|
||||
{
|
||||
path: '/tinymce',
|
||||
name: '富文本',
|
||||
children: [
|
||||
{
|
||||
path: '/index',
|
||||
name: '基础使用',
|
||||
},
|
||||
{
|
||||
path: '/editor',
|
||||
name: '嵌入form使用',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@@ -136,5 +136,31 @@ 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;
|
||||
|
58
src/views/demo/comp/tinymce/Editor.vue
Normal file
58
src/views/demo/comp/tinymce/Editor.vue
Normal file
@@ -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, Tinymce },
|
||||
setup() {
|
||||
const { createMessage } = useMessage();
|
||||
|
||||
return {
|
||||
schemas,
|
||||
handleSubmit: (values: any) => {
|
||||
createMessage.success('click search,values:' + JSON.stringify(values));
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
19
src/views/demo/comp/tinymce/index.vue
Normal file
19
src/views/demo/comp/tinymce/index.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<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>
|
Reference in New Issue
Block a user