diff --git a/apps/web-antd/src/adapter/component/index.ts b/apps/web-antd/src/adapter/component/index.ts index 9aa0738f8..698f5c786 100644 --- a/apps/web-antd/src/adapter/component/index.ts +++ b/apps/web-antd/src/adapter/component/index.ts @@ -21,6 +21,7 @@ import { $t } from '@vben/locales'; import { notification } from 'ant-design-vue'; +import { Tinymce as RichTextarea } from '#/components/tinymce'; import { FileUpload, ImageUpload } from '#/components/upload'; const AutoComplete = defineAsyncComponent( @@ -128,6 +129,7 @@ export type ComponentType = | 'Space' | 'Switch' | 'Textarea' + | 'RichTextarea' | 'TimePicker' | 'TreeSelect' | 'Upload' @@ -184,6 +186,7 @@ async function initComponentAdapter() { Space, Switch, Textarea: withDefaultPlaceholder(Textarea, 'input'), + RichTextarea, TimePicker, TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'), Upload, diff --git a/apps/web-antd/src/adapter/form.ts b/apps/web-antd/src/adapter/form.ts index 65ff793b6..0e6c688d4 100644 --- a/apps/web-antd/src/adapter/form.ts +++ b/apps/web-antd/src/adapter/form.ts @@ -17,6 +17,7 @@ setupVbenForm({ modelPropNameMap: { Checkbox: 'checked', Radio: 'checked', + RichTextarea: 'modelValue', Switch: 'checked', Upload: 'fileList', }, diff --git a/apps/web-antd/src/components/tinymce/editor.vue b/apps/web-antd/src/components/tinymce/editor.vue new file mode 100644 index 000000000..9223c2ccc --- /dev/null +++ b/apps/web-antd/src/components/tinymce/editor.vue @@ -0,0 +1,352 @@ + + + + + + \ No newline at end of file diff --git a/apps/web-antd/src/components/tinymce/helper.ts b/apps/web-antd/src/components/tinymce/helper.ts new file mode 100644 index 000000000..1f98dda46 --- /dev/null +++ b/apps/web-antd/src/components/tinymce/helper.ts @@ -0,0 +1,85 @@ +const validEvents = new Set([ + 'onActivate', + 'onAddUndo', + 'onBeforeAddUndo', + 'onBeforeExecCommand', + 'onBeforeGetContent', + 'onBeforePaste', + 'onBeforeRenderUI', + 'onBeforeSetContent', + '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', + 'onObjectResized', + 'onObjectResizeStart', + 'onObjectSelected', + 'onPaste', + 'onPostProcess', + 'onPostRender', + 'onPreProcess', + 'onProgressState', + 'onRedo', + 'onRemove', + 'onReset', + 'onSaveContent', + 'onSelectionChange', + 'onSetAttrib', + 'onSetContent', + 'onShow', + 'onSubmit', + 'onUndo', + 'onVisualAid', +]); + +const isValidKey = (key: string) => validEvents.has(key); + +export const bindHandlers = ( + initEvent: Event, + listeners: any, + editor: any, +): void => { + Object.keys(listeners) + .filter((element) => isValidKey(element)) + .forEach((key: string) => { + const handler = listeners[key]; + if (typeof handler === 'function') { + if (key === 'onInit') { + handler(initEvent, editor); + } else { + editor.on(key.slice(2), (e: any) => handler(e, editor)); + } + } + }); +}; diff --git a/apps/web-antd/src/components/tinymce/img-upload.vue b/apps/web-antd/src/components/tinymce/img-upload.vue new file mode 100644 index 000000000..95eb602f3 --- /dev/null +++ b/apps/web-antd/src/components/tinymce/img-upload.vue @@ -0,0 +1,82 @@ + + + + diff --git a/apps/web-antd/src/components/tinymce/index.ts b/apps/web-antd/src/components/tinymce/index.ts new file mode 100644 index 000000000..c277d781d --- /dev/null +++ b/apps/web-antd/src/components/tinymce/index.ts @@ -0,0 +1 @@ +export { default as Tinymce } from './editor.vue'; diff --git a/apps/web-antd/src/components/tinymce/tinymce.ts b/apps/web-antd/src/components/tinymce/tinymce.ts new file mode 100644 index 000000000..45a867b61 --- /dev/null +++ b/apps/web-antd/src/components/tinymce/tinymce.ts @@ -0,0 +1,17 @@ +// Any plugins you want to setting has to be imported +// Detail plugins list see https://www.tiny.cloud/docs/plugins/ +// Custom builds see https://www.tiny.cloud/download/custom-builds/ +// colorpicker/contextmenu/textcolor plugin is now built in to the core editor, please remove it from your editor configuration + +export const plugins = + 'preview importcss searchreplace autolink autosave save directionality code visualblocks visualchars fullscreen image link media codesample table charmap pagebreak nonbreaking anchor insertdatetime advlist lists wordcount help emoticons accordion'; + +// 和 vben2.0 不同,从 https://www.tiny.cloud/ 拷贝 Vue 部分,然后去掉 importword exportword exportpdf | math 部分,并额外增加最后一行(来自 vben2.0 差异的部分) +export const toolbar = + 'undo redo | accordion accordionremove | \\\n' + + ' blocks fontfamily fontsize | bold italic underline strikethrough | \\\n' + + ' align numlist bullist | link image | table media | \\\n' + + ' lineheight outdent indent | forecolor backcolor removeformat | \\\n' + + ' charmap emoticons | code fullscreen preview | save print | \\\n' + + ' pagebreak anchor codesample | ltr rtl | \\\n' + + ' hr searchreplace alignleft aligncenter alignright blockquote subscript superscript'; diff --git a/packages/@core/base/shared/src/utils/index.ts b/packages/@core/base/shared/src/utils/index.ts index 925af1c12..2ee0b489c 100644 --- a/packages/@core/base/shared/src/utils/index.ts +++ b/packages/@core/base/shared/src/utils/index.ts @@ -13,6 +13,7 @@ export * from './tree'; export * from './unique'; export * from './update-css-variables'; export * from './util'; +export * from './uuid'; // add by 芋艿:从 vben2.0 复制 export * from './window'; export { default as cloneDeep } from 'lodash.clonedeep'; export { default as get } from 'lodash.get'; diff --git a/packages/@core/base/shared/src/utils/uuid.ts b/packages/@core/base/shared/src/utils/uuid.ts new file mode 100644 index 000000000..548bcf398 --- /dev/null +++ b/packages/@core/base/shared/src/utils/uuid.ts @@ -0,0 +1,28 @@ +const hexList: string[] = []; +for (let i = 0; i <= 15; i++) { + hexList[i] = i.toString(16); +} + +export function buildUUID(): string { + let uuid = ''; + for (let i = 1; i <= 36; i++) { + if (i === 9 || i === 14 || i === 19 || i === 24) { + uuid += '-'; + } else if (i === 15) { + uuid += 4; + } else if (i === 20) { + uuid += hexList[(Math.random() * 4) | 8]; + } else { + uuid += hexList[(Math.random() * 16) | 0]; + } + } + return uuid.replace(/-/g, ''); +} + +let unique = 0; +export function buildShortUUID(prefix = ''): string { + const time = Date.now(); + const random = Math.floor(Math.random() * 1000000000); + unique++; + return prefix + '_' + random + unique + String(time); +}