前言

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

编辑器之间的简单比较

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

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

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

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

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

引入并初始化

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

<script src=./tinymce4.7.5/tinymce.min.js></script></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><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 = true          editor.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('上传出错')            })        }      })    }  }}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 = true          editor.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' // 无序列表 有序列表      }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;"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;"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”将不允许粘贴图像。// 设置为“true”将允许粘贴图像,而将其设置为“false”将不允许粘贴图像。

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

图片处理就告一段落~

关于预览

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

 plugin_preview_width: 375, // 预览宽度 plugin_preview_height: 668,// 预览宽度 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 = true            throw 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    },//设置白名单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 : capture          return 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 contentthis.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相关推荐

  1. vue项目中更换tinymce版本踩坑

    项目需求: vue项目中实现多图片批量上传功能 问题: tinymce富文本编辑器的多图片批量上传插件 支持版本:5.0.4+ 项目中现有的富文本编辑器版本:4.9.4 为实现这一功能选择更换tiny ...

  2. Vue项目中使用 tinymce 富文本编辑器的方法,附完整源码

    来源 | https://blog.csdn.net/xingmeiok/article/deta 1.tinymce相关参考资料 tinymce版资料: http://tinymce.ax-z.cn ...

  3. 在vue3.0项目中使用tinymce富文本编辑器

    目录 一.安装 二.完整代码 三.事项说明 四.参考文档   之前看了好几篇关于 vue项目中使用 tinymce的文章, import引入大量 tinymce插件, /node_modules/ti ...

  4. Vue项目中tinymce富文本的安装以及配置

    Vue项目中tinymce富文本的安装以及配置 对于目前网上存在的许多富文本插件中,个人还是觉得tinymce相对比较强大一些.在使用配置的过程中,可能会出现配置不完全,导致使用不了的情况,下面把我个 ...

  5. canvas java 上传截图_在Vue项目中使用html2canvas生成页面截图并上传

    使用方法 项目中引入 npm install html2canvas html代码 //html代码 js代码 // 引入html2canvas import html2canvas from 'ht ...

  6. 如何在Vue项目中使用vw实现移动端适配(转)

    有关于移动端的适配布局一直以来都是众说纷纭,对应的解决方案也是有很多种.在<使用Flexible实现手淘H5页面的终端适配>提出了Flexible的布局方案,随着viewport单位越来越 ...

  7. 实战:vue项目中导入swiper插件

    版本选择 swiper是个常用的插件,现在已经迭代到了第四代:swiper4. 常用的版本是swiper3和swiper4,我选择的是swiper3. 安装 安装swiper3的最新版本3.4.2: ...

  8. vue ajax highcharts,在vue项目中引入highcharts图表的方法(详解)

    npm进行highchars的导入,导入完成后就可以进行highchars的可视化组件开发了 npm install highcharts --save 1.components目录下新建一个char ...

  9. VUE项目中使用this.$forceUpdate();解决页面v-for中修改item属性值后页面v-if不改变的问题

    VUE项目中使用this.$forceUpdate();解决页面v-for中修改item属性值后页面v-if不改变的问题 参考文章: (1)VUE项目中使用this.$forceUpdate();解决 ...

最新文章

  1. 【JS基础】类型转换——不同数据类型比较
  2. Mac无损音乐播放器Audirvana plus
  3. 四川大学计算机学院优秀毕业论文,四川大学本科生毕业论文设计评分标准.docx...
  4. python在线教学-python在线教学
  5. salt-master
  6. 实验11.2 链表 6-4 链表拼接
  7. 【简便代码】1064 朋友数 (20分)_25行代码AC
  8. 纪中B组模拟赛总结(2020.2.3)
  9. spark第十篇:Spark与Kafka整合
  10. 计算机专业的书普遍都这么贵,你们都是怎么获取资源的?
  11. 发明与实用新型专利了解
  12. vs2015安装msdn
  13. 全卷积 FCN 数据标签制作
  14. bootstrap菜单html,Bootstrap实现下拉菜单效果
  15. tar:time stamp in the future
  16. LeetCode.714.买卖股票的最佳时机含手续费
  17. [全解] 刷机, BL 锁, Bootloader, Recovery, Magisk, Root, ADB, 线刷, 卡刷, 9008, 绕过 FRP
  18. 阿里巴巴社招笔试题——多线程打印
  19. php两个图片并排显示,wordpress文章图片怎么并排
  20. 浏览器打开网址https显示不能访问怎么办?

热门文章

  1. 基于Python的OpenCV+TensorFlow+Keras人脸识别实现
  2. 苯四乙酸 cas1820793-31-4 齐岳中间体|单体材料
  3. 综述笔记-多无人机多目标任务分配1
  4. 计算机毕业设计springboot+vue基本微信小程序的校园二手闲置物品交易小程序 uniapp
  5. typescript 的认识3
  6. 联想台式机计算机接口,接口篇:四款产品接口配置横向对比_联想ThinkCentre台式电脑_台式电脑评测-中关村在线...
  7. 实时监控Mysql等数据库变化_进行数据同步_了解Debezium_--Debezium工作笔记001
  8. php保存微信用户头像到本地或者服务器的完美方案!
  9. mysql 两表拼接_数据库将两张表进行横向连接(拼接成一张表的形式显示)
  10. yolov3原理+训练损失