vue3 中使用 tinymce 记录 您所在的位置:网站首页 loadscript翻译 vue3 中使用 tinymce 记录

vue3 中使用 tinymce 记录

2023-07-31 17:47| 来源: 网络整理| 查看: 265

前言

最近项目中在做协议相关功能,要求引入富文本编辑器,有一些功能需求点,包括锚点、表格、预览、从 word 中复制到富文本编辑器中保留相关格式等。

综合业务需求,我这边对 ckEditor5 和 tinymce 进行了尝试。ckEditor5 在接入插件时相对较麻烦,我这边想要封装成一个通用功能星组件,就尝试采用 源码中引入ckEditor5插件,该方式需要去修改底层相关配置,但是我这边得项目底层配置是经过封装的,在接入过程中碰到了难以处理的问题,转而去尝试引入 tinymce,就一感觉:真香!!!

首先,奉上文档:

tinymce5 官方文档(英文): www.tiny.cloud/docs/quick-… tinymce5 中文翻译文档: tinymce.ax-z.cn/ 一、准备工作

安装

npm i tinymce 复制代码

我这里安装的版本是 5.10.2

安装 tinymce 时会安装所有开源的插件,在 node_modules/tinymce/plugins 下,在需要时直接引入就可以。

然后,node_modules 中找到 tinymce 目录,将目录中 skins 文件夹复制到新建的public/tinymce 文件夹中,然后去下载相关语言包,下载地址,放到 public/tinymce/language 中,后续需要引入。

image.png

tinymce 有三种模式:经典模式(classic,默认)、行内模式(inline)、清爽模式(Distraction-free),这里介绍最常用的经典模式,其它的模式可自行查看文档。

tinymce 的插件有开源插件和付费插件,目前开源插件能满足我的需求,我这边采用开源插件进行开发。

二、编辑器配置 1. 基本配置

添加最基本的配置:

import { defineComponent, computed, onMounted, onBeforeUnmount, unref } from 'vue' import type { Editor, RawEditorSettings } from 'tinymce' import tinymce from 'tinymce/tinymce' import 'tinymce/themes/silver' import 'tinymce/icons/default/icons' export default defineComponent({ setup(){ const tinymceId = ref(UUID()) const editorRef = ref() const initOptions = computed(():RawEditorSettings => { const publicPath = __webpack_public_path__ return { selector: `#${tinymceId.value}`, language_url: `${publicPath}tinymce/langs/zh_CN.js`, language: 'zh_CN', skin_url: `${publicPath}tinymce/skins/ui/oxide`, content_css: `${publicPath}tinymce/skins/ui/oxide/content.min.css`, } }) onMounted(() => { tinymce.init(initOptions.value) }) onBeforeUnmount(() => { destory() }) function destory() { if (tinymce !== null) { tinymce?.remove?.(unref(initOptions).selector!) } } return { tinymceId } } }) 复制代码

效果如下: image.png

2. 编辑器初始化

在初始化 setup 的钩子中可以进行初始化的操作:

向编辑器中填写初始化内容 设置编辑器的 只读/编辑状态 监听编辑器的相关操作 const initOptions = computed(() => { return { // ..... setup: (editor: Editor) => { editorRef.value = editor editor.on('init', initSetup) }, } }) // 编辑器初始化 function initSetup() { const editor = unref(editorRef) if (!editor) { return } const value = props.value || '' editor.setContent(value) bindModelHandlers(editor) } function setValue(editor, val: string, prevVal?: string) { if ( editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent() ) { editor.setContent(val) } } function bindModelHandlers(editor: any) { watch(() => props.value, (val: string, prevVal) => setValue(editor, val, prevVal), { immediate: true }, ) watch( () => props.disabled, val => { editor.setMode(val ? 'readonly' : 'design') }, { immediate: true }, ) editor.on('change keyup undo redo', () => { const content = editor.getContent() emit('update:value', content) emit('change', content) }) } 复制代码 3. 图片上传配置

使用 images_upload_handler 可自定义上传处理逻辑,该自定义函数需提供三个参数:blobInfo、成功回调、失败回调 和 上传进度。使用该配置,则无需使用其他上传配置选项

const initOptions = computed(() => { return { // ..... images_upload_handler: handleImgUpload } }) // 图片上传自定义逻辑 function handleImgUpload(blobInfo, success, failure, progress) { var xhr, formData; var file = blobInfo.blob();//转化为易于理解的file对象 xhr = new XMLHttpRequest(); xhr.withCredentials = false; xhr.open('POST', '/demo/upimg.php'); xhr.onload = function() { var json; if (xhr.status != 200) { failFun('HTTP Error: ' + xhr.status); return; } json = JSON.parse(xhr.responseText); if (!json || typeof json.location != 'string') { failFun('Invalid JSON: ' + xhr.responseText); return; } succFun(json.location); }; formData = new FormData(); formData.append('file', file, file.name ); xhr.send(formData); } 复制代码

最终效果图:

image.png

4. 完整版本代码

注意:paste_retain_style_properties 属性可以保留复制过来的相关样式,比如要保留字体大小、颜色、背景颜色,可以将其配置为 paste_retain_style_properties: 'font-size color background background-color',如果要保留所有样式可以设置为 all,但是这样会造成代码量很大,并且这个属性将在 6 版本中移除,谨慎使用。

import { defineComponent, computed, onMounted, ref, PropType, unref, watch, onBeforeUnmount, } from 'vue' import type { Editor, RawEditorSettings } from 'tinymce' import tinymce from 'tinymce/tinymce' import 'tinymce/themes/silver' import 'tinymce/icons/default/icons' import 'tinymce/plugins/advlist' import 'tinymce/plugins/anchor' import 'tinymce/plugins/autolink' import 'tinymce/plugins/autosave' import 'tinymce/plugins/code' import 'tinymce/plugins/codesample' import 'tinymce/plugins/directionality' import 'tinymce/plugins/fullscreen' import 'tinymce/plugins/hr' import 'tinymce/plugins/insertdatetime' import 'tinymce/plugins/link' import 'tinymce/plugins/lists' import 'tinymce/plugins/image' import 'tinymce/plugins/toc' import 'tinymce/plugins/nonbreaking' import 'tinymce/plugins/noneditable' import 'tinymce/plugins/pagebreak' import 'tinymce/plugins/paste' import 'tinymce/plugins/preview' import 'tinymce/plugins/print' import 'tinymce/plugins/save' import 'tinymce/plugins/searchreplace' import 'tinymce/plugins/spellchecker' import 'tinymce/plugins/tabfocus' import 'tinymce/plugins/table' import 'tinymce/plugins/template' import 'tinymce/plugins/textpattern' import 'tinymce/plugins/visualblocks' import 'tinymce/plugins/visualchars' import 'tinymce/plugins/wordcount' import { plugins as initialPlugins, toolbar as initialToolbar, fontFormats } from './tinymce' import { UUID } from 'uuid' type Recordable = Record export default defineComponent({ props: { value: { type: String, }, disabled: { type: Boolean, default: false }, options: { type: Object as PropType, default: () => ({}), }, toolbar: { type: String, default: initialToolbar, }, plugins: { type: Array as PropType, default: initialPlugins, }, height: { type: [Number, String] as PropType, required: false, default: 400, }, width: { type: [Number, String] as PropType, required: false, default: 'auto', }, }, emits: ['change', 'update:value'], setup(props, { emit }) { const tinymceId = ref(UUID()) const editorRef = ref() const initOptions = computed((): RawEditorSettings => { const publicPath = __webpack_public_path__ const { height, options, toolbar, plugins, } = props return { selector: `#${tinymceId.value}`, language_url: `${publicPath}tinymce/langs/zh_CN.js`, language: 'zh_CN', skin_url: `${publicPath}tinymce/skins/ui/oxide`, content_css: `${publicPath}tinymce/skins/ui/oxide/content.min.css`, images_upload_handler: handleImgUpload, images_file_types: 'jpeg,jpg,png,gif,bmp,webp', // 准许的图片格式 convert_urls: false, branding: false, // 隐藏品牌,隐藏状态栏中显示的“ Powered by Tiny ”链接 placeholder: '请输入内容', // 占位符 toolbar, plugins, height, toolbar_mode: 'sliding', toolbar_sticky: true, paste_block_drop: true, // 禁用将内容拖放到编辑器中 paste_data_images: false, // 粘贴data格式的图像 谷歌浏览器无法粘贴 font_formats: fontFormats, paste_retain_style_properties: 'color border border-left border-right border-bottom border-top', // MS Word 和类似 Office 套件产品保留样式 paste_webkit_styles: 'none', // 允许在 WebKit 中粘贴时要保留的样式 paste_tab_spaces: 2, // 将制表符转换成空格的个数 content_style: ` html, body { height:100%; } img { max-width:100%; display:block;height:auto; } a { text-decoration: none; } p { line-height:1.6; margin: 0px; } table { word-wrap:break-word; word-break:break-all;max-width:100%; border:none; border-color:#999; } .mce-object-iframe { width:100%; box-sizing:border-box; margin:0; padding:0; } ul,ol { list-style-position:inside; } `, ...options, setup: (editor: Editor) => { editorRef.value = editor editor.on('init', initSetup) }, } }) onMounted(() => { tinymce.init(initOptions.value) }) onBeforeUnmount(() => { destory() }) function destory() { if (tinymce !== null) { tinymce?.remove?.(unref(initOptions).selector!) } } // 图片上传自定义逻辑 function handleImgUpload(blobInfo, success, failure, progress) { console.log('blobInfo', blobInfo.blob(), blobInfo.filename()) const { type: fileType, name: fileName } = blobInfo.blob() // xxxx 自定义上传逻辑 } // 编辑器初始化 function initSetup() { const editor = unref(editorRef) if (!editor) { return } const value = props.value || '' editor.setContent(value) bindModelHandlers(editor) } function setValue(editor: Recordable, val: string, prevVal?: string) { if ( editor && typeof val === 'string' && val !== prevVal && val !== editor.getContent() ) { editor.setContent(val) } } function bindModelHandlers(editor: any) { watch( () => props.value, (val: string, prevVal) => setValue(editor, val, prevVal), { immediate: true }, ) watch( () => props.disabled, val => { editor.setMode(val ? 'readonly' : 'design') }, { immediate: true }, ) editor.on('change keyup undo redo', () => { const content = editor.getContent() emit('update:value', content) emit('change', content) }) } return { tinymceId, } }, }) 复制代码

tinymce.ts 文件里是 tinymce 的 plugins、toolbar、fontFormats 的配置,这里基本上使用了所有的开源插件,功能比较齐全

// tinymce.ts // imagetools export const plugins = [ 'advlist anchor autolink code codesample directionality fullscreen hr insertdatetime link lists nonbreaking noneditable pagebreak paste preview print save searchreplace tabfocus template textpattern visualblocks visualchars wordcount table image toc', ] export const toolbar = 'undo redo | bold italic underline strikethrough | fontselect fontsizeselect formatselect | toc alignleft aligncenter alignright alignjustify lineheight | outdent indent | numlist bullist | forecolor backcolor | pagebreak | charmap emoticons | fullscreen preview save print | hr link image | anchor pagebreak | insertdatetime | blockquote removeformat subscript superscript code codesample | searchreplace' export const fontFormats = '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif,Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats' 复制代码 三、 属性配置汇总 width: '100%', // 设置富文本编辑器宽度 height: '100%', // 设置富文本编辑器高度 menubar: false, // 设置富文本编辑器菜单, 默认true branding: false, // 关闭底部官网提示 默认true statusbar: true, // 显示底部状态栏 默认true readonly: false, // 设置只读属性 默认 false resize: false, // 调节编辑器大小 默认 true branding: false, // 隐藏状态栏右下角显示的品牌 placeholder: '请输入内容', // 占位符 theme: 'silver', // 主题 必须引入 skin_url: '/tinymce/skins/ui/oxide', // 主题路径 icons: 'custom', // 自定义图标名称 icons_url: '/tinymce/icons/icons.js', // 自定义图标路径 language_url: '/tinymce/langs/zh_CN.js', // 中文化 默认为英文 language: 'zh_CN', // 设置富文本编辑器语言 content_css: `/tinymce/skins/content/default`, // 富文本编辑器内容区域样式 content_style: 'body, p{font-size: 12px}', // 为内容区编辑自定义css样式 plugins: ['autosave help textpattern lineheight'], // 插件配置 toolbar: 'fontselect styleselect fontsizeselect restoredraft undo redo | bold italic underline strikethrough subscript superscript removeformat forecolor backcolor lineheight align outdent indent help', // 工具栏配置 toolbar_mode: 'sliding', // sliding生效条件toolbar必须为字符串,且有'|'区分,不能为数组 toolbar_sticky: true, // 粘性工具栏 默认false (在向下滚动网页直到不再可见编辑器时,将工具栏和菜单停靠在屏幕顶部) // 快速工具栏配置,需引入插件 quickbars quickbars_selection_toolbar: 'bold italic underline strikethrough | link h2 h3 h4 blockquote', // 设置 快速选择 触发提供的工具栏 需引入插件 默认 'alignleft aligncenter alignright' 设置为false禁用 quickbars_insert_toolbar: 'quickimage quicktable', // 设置 快速插入 触发提供的工具栏 需引入插件quickbars 默认 quickimage quicktable 设置为false禁用 // font 相关配置 fontsize_formats: '12px 14px 16px 18px 20px 22px 24px 26px 36px 48px 56px', // 工具栏自定义字体大小选项 font_formats: "微软雅黑='微软雅黑'; 宋体='宋体'; 黑体='黑体'; 仿宋='仿宋'; 楷体='楷体'; 隶书='隶书'; 幼圆='幼圆'; 方正舒体='方正舒体'; 方正姚体='方正姚体'; 等线='等线'; 华文彩云='华文彩云'; 华文仿宋='华文仿宋'; 华文行楷='华文行楷'; 华文楷体='华文楷体'; 华文隶书='华文隶书'; Andale Mono=andale mono,times; Arial=arial; Arial Black=arial black;avant garde; Book Antiqua=book antiqua;palatino; Comic Sans MS=comic sans ms; Courier New=courier new;courier; Georgia=georgia; Helvetica=helvetica; Impact=impact;chicago; Symbol=symbol; Tahoma=tahoma;arial; sans-serif; Terminal=terminal,monaco; Times New Roman=times new roman,times; Trebuchet MS=trebuchet ms; Verdana=verdana;geneva; Webdings=webdings; Wingdings=wingdings", // 工具栏自定义字体选项 // autosave 插件配置,需引入插件 autosave autosave_ask_before_unload: true, // 阻止有内容时浏览器阻塞行为, 默认 true autosave_interval: '3s', // 设置自动保存为草稿时间 单位只能为s autosave_prefix: `editor_${route.path}`, // 设置自动保存为草稿时前缀 本地localStorage中存储 autosave_retention: '300m', // 自动草稿的有效期 单位只能为m(分钟) // image 相关配置,需引入插件image images_upload_handler: (blobInfo, success, failure) => { // 发送请求, 获取图片路径后, 将路径传给success success('xxxx') }, // 图片上传函数 image_advtab: true, // 为上传图片窗口添加高级属性 // paste 相关配置,需引入插件paste paste_data_images: true, // 粘贴data格式的图像 paste_block_drop: true, // 禁用将内容拖放到编辑器中 paste_as_text: true, // 默认粘贴为文本 paste_retain_style_properties: 'color border', // MS Word 和类似 Office 套件产品保留样式 // template 内容模板配置,需引入插件template templates: [{ title: '标题', description: '描述', content: '内容' }], // 内容模板 // 快速排版配置,需引入插件 textpattern textpattern_patterns: [ { start: '*', end: '*', format: 'italic' }, { start: '**', end: '**', format: 'bold' }, { start: '#', format: 'h1' }, { start: '##', format: 'h2' }, { start: '###', format: 'h3' }, { start: '####', format: 'h4' }, { start: '#####', format: 'h5' }, { start: '######', format: 'h6' }, { start: '1. ', cmd: 'InsertOrderedList' }, { start: '* ', cmd: 'InsertUnorderedList' }, { start: '- ', cmd: 'InsertUnorderedList' } ], // 快速排版 类似于markdown init_instance_callback: editor => { // 初始化结束后执行, 里面实现双向数据绑定功能 editor.on('Input undo redo Change execCommand SetContent', (e) => { // editor.getContent({ format: ''text }) // 获取纯文本 $emit('change', editor.getContent()) }) }, setup: (editor) => { // 初始化前执行 // xxxx } 复制代码


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有