vue 项目中集成使用 tinymce 富文本编辑器实现图片上传

本文主要是介绍vue 项目中集成使用 tinymce 富文本编辑器实现图片上传,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

最近因为公司项目的后台管理端需要实现编辑器功能, 一方面满足编辑各类文章内容需求,另一方面要自己编辑一些课程相关的介绍,于是就花了一些时间对比体验现有的一些开源的编辑器。

 

编辑器之间的简单比较

  • UEditor:基本满足各种需求,依赖于jquery但是已经不再维护了,实现上传图片等需要修改源码,界面不太美观,对于老浏览器兼容还不错,但是我这里采用的是VueJS来开发,所以放弃

  • wangEditor:比较轻量级,最最最重要的是有中文文档上手快,UI 也比较漂亮,而且还是国产的, 对于编辑器功能需求少的兄 die 可以考虑,但是考虑到我这项目业务比较重,所以只好放弃

  • Bootstrap-wysiwyg:简洁好看,依赖于Bootstrap, jquery,选择的Element-ui弃之

  • TinyMCE: 支持图片在线处理,插件多,功能强。 嗯,就选它啦(虽然文档是英文,但是谷歌翻译也不错 )

我们项目要解决的需求说复杂也不复杂,但是却很烦人, 比如:

1. 实现图片上传(基础功能)

2. 模拟手机预览功能(基础功能)

3. 编辑的内容在 app 中显示要适配

4. 从 135 编辑器, 秀米等等编辑器拷贝过来的内容要正常显示并且排版还要保持,还要将这些第三方图片上传到自己服务(怕第三方下架图片)

 

引入并初始化

  • 引入 tinymace 文件

项目采用 vue-cli@3.x 构建的, 将 TinyMCE 下载放在 index.html 同级目录下, 并在index.html中引入 TinyMCE

<script src=./tinymce4.7.5/tinymce.min.js></script>
  • 初始化

引入文件后,在 html 元素上初始化 TinyMCE, 由于 TinyMCE 允许通过 CSS 选择器来标识可替换的元素,所以我们只需要将包含选择器的对象传递给 TinyMCE.init(),代码如下:

<template>  
<div class="tinymce-container editor-container">    
<textarea :id="tinymceId" class="tinymce-textarea" />  
</div>
</template>
<script>
export default {name: 'Tinymce', data() {  return {   tinymceId: this.id   } }, mounted(){  	this.initTinymce()},methods: {   initTinymce() {  window.tinymce.init({      selector: `#${this.tinymceId}`    })   } }}
</script>

 这样就将textarea替换为 TinyMCE 编辑器实例, 做完了最简单的初始化。

 

配置项

接下来就是添加配置项, 让 TinyMCE 编辑器功能丰富起来

  • 基础配置

关于基础配置, 我就不一一介绍,文档中都有详细的说明,如果英语和我一样弱鸡,可以借助 chrome 的翻译,大概能看明白。

下面是封装的组件的script内容, 关于一些配置直接在代码中说明:

 

import plugins from '@/components/Tinymce/plugins'
import toolbar from '@/components/Tinymce/toolbar'
import {uploadFile
} from '@/api/file/upload'
export default {name: 'Tinymce',props: {tid: {type: String,default: 'my-tinymce-' + new Date().getTime() + parseInt(Math.random(100))},content: {type: String,default: ''},menubar: { // 菜单栏      type: String,default: 'file edit insert view format table'},toolbar: { // 工具栏     type: Array,required: false,default () {return []}},height: {type: Number,required: false,default: 360}},data() {return {tinymceId: this.tid,finishInit: false,hasChanged: false,config: {}}},mounted() {this.initTinymce()},methods: {initTinymce() {window.tinymce.init({selector: `#${this.tinymceId}`,...this.config,content_style: 'img {max-width:100% !important }', // 初始化赋值        init_instance_callback: editor => {if (this.content) {editor.setContent(this.content)}this.finishInit = trueeditor.on('NodeChange Change SetContent KeyUp', () => {this.hasChanged = true})}, // 上传图片images_upload_handler: (blobInfo, success, failure) => {const formData = new FormData();formData.append('file', blobInfo.blob());uploadFile(formData).then(res => {if (res.data.code == 0) {let file = res.data.data;success(file.filePath);return}failure('上传失败')}).catch(() => {failure('上传出错')})}})}}
}

 

组件初始化完成,编辑框如图所示: 

 

config 内容

为了方便阅读, 这里将 config 内容抽取出来单独展示, 我也对部分配置项进行了注释, 方便理解:

 

config: {language: 'zh_CN',height: this.height,menubar: this.menubar, //菜单:指定应该出现哪些菜单        toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar, // 分组工具栏控件        plugins: plugins, // 插件(比如: advlist | link | image | preview等)        object_resizing: false, // 是否禁用表格图片大小调整        end_container_on_empty_block: true, // enter键 分块        powerpaste_word_import: 'merge', // 是否保留word粘贴样式  clean | merge        code_dialog_height: 450, // 代码框高度 、宽度        code_dialog_width: 1000,advlist_bullet_styles: 'square' // 无序列表 有序列表      
}

toolbar.js

组件中引入的toolbar.js文件存的是 TinyMCE 初始化时加载的工具栏控件, 设置的顺序即代表着在编辑器工具栏上出现的顺序

 

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;

 

plugin.js

组件中引入的plugin.js文件是设置 TinyMCE 初始化时加载哪些插件,默认情况下,TinyMCE 不会加载任何插件:

const plugins = ["advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools importcss insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount"];
export default plugins;

 

上传图片

TinyMCE 提供了图片上传处理函数images_upload_handler, 该函数有三个参数:blobInfo,success callback,failure callback, 分别是图片内容, 一个成功的回调函数以及一个失败的回调函数,具体上传图片代码在上面已经写,这里就不赘述; 需要注意的是,**当向后台上传完图片, 我们要调用success函数来用服务器地址替换<image>标签的 src 属性**。

  succuss(服务图片地址);

 本来以为上传图片就完成了, 图片上传就算完事了, 结果产品小伙伴说啦: “你这图片不可以直接复制粘贴吗?每次点上传好伐呀!!”, 那继续加复制粘贴功能呗!

拖入/粘贴图片

其实实现图片粘贴并不难, 之前已经加载了paste插件, 接下来只需要在初始化中插入配置项:

   paste_data_images: true, // 设置为“true”将允许粘贴图像,而将其设置为“false”将不允许粘贴图像。

但是我却花费了一个小时来搞这个, 因为我咋也粘贴不上, 所以不得不提一下这个坑:就因为我用的chrome开发, chrome 浏览器直接在文件中复制粘贴图片是无法粘贴上的, 但是可以从微信输入框等地方粘贴上,也能拖入, 我暂时还没有进一步去做 chrome 浏览器粘贴的兼容,后续有时间回去做.

关于预览

TinyMCE 配置了预览插件preview, 前面在plugin.js中也加入了, 但是我们的需求是实现手机模式下的预览, 所以还需要设置一下预览内容的宽度以及高度

 plugin_preview_width: 375, // 预览宽度 plugin_preview_height: 668,

 设置完预览之后发现图片大于预览宽度, 出现了滚动,于是找到了一个content_style属性, 可以设置 css 样式,将样式注入到编辑器中, 在初始化中设置下面的属性:

 

  content_style: ` * { padding:0; margin:0; }	img {max-width:100% !important }`,

 

于是模拟手机端预览也完事了, 但内容提交后, 手机上查看图片仍然很大, 原因是我忽略了官方文档说的:这些样式不会与内容一起保存的

所以我在提交代码时将这个 style 字符串拼接到内容上

 

content += `<style>* { padding:0; margin:0; }	img {max-width:100% !important }</style>`

第三方编辑器内容拷贝

上面我也说到了第三方编辑器内容拷贝的需求, 要让内容拷贝过来排版不变, 并且图片内容要上传到我们自己服务器上。

1. 对于 135 编辑器

135 编辑器支持拷贝的是 html 代码,通过直接粘贴在 code 中即可保持排版样式不变,对于图片地址处理思路如下:

1. 为自己的服务器设置一个白名单

2. 将页面中非白名单内的图片链接地址传给后台,让后台去把这些图片放到自己服务器并返回给我新图片链接

3. 然后我再更新对应的图片链接;

这里面主要涉及到:

  • 找到所有图片链接

  • 更新对应的图片链接

本来是打算使用正则来找到图片, 获得服务器返回的内容,再使用正则匹配替换, 后来发现 TinyMCE 提供了urlconverter_callback用于处理 url 替换, 它有四个参数:url,node,an_save,name,主要使用到的是要替换的 url 地址, 这个方法返回的是替换后的 url 地址;

我是这样做的:

urlconverter_callback: (url, node, on_save, name) => { //设置白名单      const assignUrl = ['http://192.168.1.49', 'https://lms0-1251107588']let isInnerUrl = false //默认不是内部链接      try {assignUrl.forEach(item => {if (url.indexOf(item) > -1) {isInnerUrl = truethrow new Error('EndIterate')}})} catch (e) {if (e.message != 'EndIterate') throw e}if (!isInnerUrl) {replaceUrl(url).then(result => {if (result.data.code == 0) {this.newImgUrl.push(result.data.data)}})}return url
},

这一步只是实现了将白名单外的图片地址发给服务器,接收并保存返回的地址,大家可能会好奇为什么不在这里直接利用返回值替换图片地址呢?

由于这个函数没有没有提供回调函数,当异步从服务器取回新地址时,renturn 回去的 url 是不等人的, 我试了使用await来解决,但是发现它不支持异步来处理,所有只好放弃,采用这种方式变向处理,让用户点击保存时再去匹配并替换内容。

 

if (!this.newImgUrl.length) return this.content // 匹配并替换 img中src图片路径      
this.content = this.content.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/gi, (mactch, capture) => {let current = ''this.newImgUrl.forEach(item => {if (capture == item.oriUrl) {current = item.filePath}}) current = current ? current : capturereturn mactch.replace(/src=[\'\"]?([^\'\"]*)[\'\"]?/i, 'src=' + current)
}) // 匹配并替换 任意html元素中 url 路径 
this.content = this.content.replace(/url\(['"](.+)['"]\)/gi, (mactch, capture) => {let current = ''this.newImgUrl.forEach(item => {if (capture == item.oriUrl) {current = item.filePath}}) current = current ? current : capture;return mactch.replace(/url\((['"])(.+)(['"])\)/i, `url($1${current}$3) `)
}) 
return content

最后再将替换完成后的内容发送给后台,这里对于 TinyMce 编辑器的使用就告一段落了,谢谢你的认真阅读,希望对你有所帮助,后期有新的功能添加或是新内容我会再更新的。

这篇关于vue 项目中集成使用 tinymce 富文本编辑器实现图片上传的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/264130

相关文章

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

深度解析Java项目中包和包之间的联系

《深度解析Java项目中包和包之间的联系》文章浏览阅读850次,点赞13次,收藏8次。本文详细介绍了Java分层架构中的几个关键包:DTO、Controller、Service和Mapper。_jav... 目录前言一、各大包1.DTO1.1、DTO的核心用途1.2. DTO与实体类(Entity)的区别1

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Python中注释使用方法举例详解

《Python中注释使用方法举例详解》在Python编程语言中注释是必不可少的一部分,它有助于提高代码的可读性和维护性,:本文主要介绍Python中注释使用方法的相关资料,需要的朋友可以参考下... 目录一、前言二、什么是注释?示例:三、单行注释语法:以 China编程# 开头,后面的内容为注释内容示例:示例:四

java实现docker镜像上传到harbor仓库的方式

《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Java easyExcel实现导入多sheet的Excel

《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁

python实现对数据公钥加密与私钥解密

《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

ModelMapper基本使用和常见场景示例详解

《ModelMapper基本使用和常见场景示例详解》ModelMapper是Java对象映射库,支持自动映射、自定义规则、集合转换及高级配置(如匹配策略、转换器),可集成SpringBoot,减少样板... 目录1. 添加依赖2. 基本用法示例:简单对象映射3. 自定义映射规则4. 集合映射5. 高级配置匹