TinyMCE@5.x + Vue3 + Ts 使用
-
安装依赖
npm install tinymce -S npm install @tinymce/tinymce-vue -S
-
汉化
去官网 下载汉化包 选择zh_CN
-
把皮肤和汉化包放到项目public中(public里面文件引入时文件名称保持原文件名,不做改变)
3.1 在项目public文件夹下新建tinymce文件夹,
3.2 将下载的汉化包解压到此文件夹
3.3 然后在node_modules/tinymce中找到skins文件夹,也复制到public/tinymce里 -
组件封装
``` <template> <div w-full> <editor-dom v-model="myValue" :init="init" :disabled="disabled" /> <input :id="id" type="file" :accept="imgAccept" hidden /> </div> </template> <script lang="ts" setup> import { useStore } from '@/stores' import logo from '@/assets/image/logo.png' import type { Editor } from 'tinymce' import tinymce from 'tinymce/tinymce' import EditorDom from '@tinymce/tinymce-vue' import 'tinymce/themes/silver/theme' import 'tinymce/icons/default' import 'tinymce/models/dom' import 'tinymce/plugins/advlist' import 'tinymce/plugins/autolink' import 'tinymce/plugins/lists' import 'tinymce/plugins/link' import 'tinymce/plugins/image' import 'tinymce/plugins/charmap' import 'tinymce/plugins/preview' import 'tinymce/plugins/anchor' import 'tinymce/plugins/searchreplace' import 'tinymce/plugins/visualblocks' import 'tinymce/plugins/code' import 'tinymce/plugins/fullscreen' import 'tinymce/plugins/insertdatetime' import 'tinymce/plugins/media' import 'tinymce/plugins/table' import 'tinymce/plugins/template' // import 'tinymce/plugins/help' import 'tinymce/plugins/wordcount' import { ElMessage } from 'element-plus' const store = useStore() interface Props { id?: string modelValue?: string height?: string | number width?: string | number disabled?: boolean showDel?: boolean } const props = withDefaults(defineProps<Props>(), { modelValue: '', id: 'vue-tinymce-' + new Date() + ((Math.random() * 1000).toFixed(0) + ''), height: 200, width: 'auto', disabled: false, showDel: false }) const emits = defineEmits<{ (e: 'update:modelValue', value: string): void }>() const myValue = computed({ get() { return props.modelValue }, set(value) { emits('update:modelValue', value) } }) const oldValue = ref<string>() const imgAccept = ref<string>( '.bmp,.jpg,.png,.tif,.gif,.pcx,.tga,.exif,.fpx,.svg,.psd,.cdr,.pcd,.dxf,.ufo,.eps,.ai,.raw,.WMF,.webp,.avif,.apng' ) const exampleImageUploadHandler = (blobInfo: Record<string, unknown>): Promise<string> => new Promise((resolve, reject) => { const formData = new FormData() formData.append('file', (blobInfo.blob as () => Blob)(), (blobInfo.filename as () => string)()) store .upFile(formData) .then(res => { if (res.data) { resolve(res.data) } else { resolve('') } }) .catch(err => reject(err)) }) const setup = (editor: typeof Editor): void => { editor.ui.registry.addButton('imageUpload', { tooltip: '图片', icon: 'image', onAction: () => { //点击按钮后执行 oldValue.value = props.modelValue const input = document.getElementById(props.id) as HTMLInputElement | null if (!input) { ElMessage.warning('上传失败,请刷新当前页面。') return } input.click() input.onchange = function () { if (!input.files) { ElMessage.warning('请选择图片上传') input.value = '' return } const file = input.files[0] const formData = new FormData() formData.append('file', file) if (!imgAccept.value.includes(file.name.substring(file.name.indexOf('.')))) { ElMessage.warning('请选择图片上传') input.value = '' return } store.upFile(formData).then(res => { if (res.data) { editor.insertContent("<img src='" + res.data.file + "' alt=''/>") input.value = '' } else { editor.setContent(oldValue.value as string) } }) } } }) } const init = reactive({ selector: `#${props.id}`, content_style: 'p {margin: 0; border:0; padding: 0;}', content_css: '/tinymce/skins/content/default/content.css', language_url: '/tinymce/langs/zh-Hans.js', // https://www.tiny.cloud/get-tiny/language-packages/ language: 'zh-Hans', skin_url: '/tinymce/skins/ui/oxide', height: props.height, promotion: false, //隐藏右上角upgrade按钮 branding: false, //隐藏右下角由TINY驱动 menubar: false, // 是否隐藏顶部菜单 contextmenu_never_use_native: true, //防止浏览器上下文菜单出现在编辑器中 elementpath: false, //隐藏底栏的元素路径(隐藏右下角元素显示) object_resizing: true, //是否允许调整图像大小. toolbar: 'undo redo | blocks fontfamily | forecolor bold italic backcolor |' + 'align bullist numlist | outdent indent | table | imageUpload |' + 'removeformat | hr | template | preview fullscreen', plugins: [ 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview', 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', 'insertdatetime', 'media', 'table', 'wordcount', 'template' ], // paste_data_images: false, //此选项指定是否应从粘贴的内容中删除图像 paste_webkit_styles: 'all', //此选项允许您指定粘贴到 WebKit 中时要保留的样式 'none' 或者 'all' // paste_merge_formats: true, //此选项在粘贴内容时启用合并格式功能。这将合并相同的文本格式元素,以减少生成的 HTML 元素的数量 advlist_bullet_styles: 'default,circle,disc,square', // advlist_number_styles: 'default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman', link_default_target: '_blank', link_title: false, //此选项允许您禁用对话框中的链接输入字段 nonbreaking_force_tab: true, //tab键插入三个 images_upload_handler: exampleImageUploadHandler, setup: setup, template_replace_values: { logoSrc: logo }, template_preview_replace_values: { logoSrc: logo }, templates: [ { title: '制度', description: '用于插入制度相关内容。', content: '<p style="text-align: center;"><img style="float: left;" src="{$logoSrc}" alt="" width="120" height="37.5" />祝工作愉快,身体健康!</p>' }, { title: '通报', description: '用于插入通报相关内容。', content: '<p style="text-align: center;"><img style="float: left;" src="{$logoSrc}" alt="" width="120" height="37.5">感谢大家的理解和支持!</p>\n<p> </p>\n<p style="text-align: left;"> </p>\n<p style="text-align: right;">XXX科技有限公司</p>\n<p style="text-align: right;">2023 年 9 月 6日</p>' }, { title: '通知', description: '用于插入通知相关内容。', content: '<p style="text-align: center;"><img style="float: left;" src="{$logoSrc}" alt="" width="120" height="37.5"></p>\n<p style="text-align: right; line-height: 1;"> </p>' } ] }) onMounted(() => { tinymce.init({}) }) </script> <style scoped></style> <style> .tox-tinymce-aux { z-index: 3035 !important; } .tox .tox-toolbar__group { padding: 0 3px 0 5px !important; } </style> ```
-
TS 配置 (Vite)
types文件夹下 新增文件 tinymce.d.ts// TinyMce 富文本 declare module 'tinymce' declare module 'tinymce/tinymce' declare module '@tinymce/tinymce-vue'