Vue 项目中引入 tinymce 富文本编辑器

在 Vue 项目中引入 tinymce 富文本编辑器

项目中原本使用的富文本编辑器是 wangEditor,这是一个很轻量、简洁编辑器

但是公司的业务升级,想要一个功能更全面的编辑器,我找了好久,目前常见的编辑器有这些:

UEditor:百度前端的开源项目,功能强大,基于 jQuery,但已经没有再维护,而且限定了后端代码,修改起来比较费劲

bootstrap-wysiwyg:微型,易用,小而美,只是 Bootstrap + jQuery...

kindEditor:功能强大,代码简洁,需要配置后台,而且好久没见更新了

wangEditor:轻量、简洁、易用,但是升级到 3.x 之后,不便于定制化开发。不过作者很勤奋,广义上和我是一家人,打个call

quill:本身功能不多,不过可以自行扩展,api 也很好懂,如果能看懂英文的话...

summernote:没深入研究,UI挺漂亮,也是一款小而美的编辑器,可是我需要大的

在有这么参考的情况下,我最终还是选择了 tinymce 这个不搞黑科技连官网都打不开的编辑器(简直是自讨苦吃),主要因为两点:

1. GitHub 上星星很多,功能也齐全;

2. 唯一一个从 word 粘贴过来还能保持绝大部分格式的编辑器;

3. 不需要找后端人员扫码改接口,前后端分离;

4. 说好的两点呢!

注意:这篇博客仅适用于 Tinymce 4.x

除了 tinymce 之外,CKEditor 5 也是一个大而全的富文本编辑器

但 CK5 门槛较高,有兴趣的可以看这篇文章《CKEditor 5 摸爬滚打(一)—— 从零构建定制化工程项目》

一、资源下载

如果有购买 tinymce 的服务,可以参考 tinymce-vue 的说明,通过 api-key 直接使用 tinymce

像我这样没购买的,还是要老老实实下载 tinymce

npm install tinymce -S

tinymce 官方为 vue 项目提供了一个组件 tinymce-vue

npm install @tinymce/tinymce-vue -S

在 vscode、webstorm 的终端运行这段代码可能会报错,最好使用操作系统自带的命令行工具

如果你用是vue2.0版本的话不要安装这个,vue2中不能使用@tinymce/tinymce-vue为4以上版本;

安装低版本的:例如:npm install @tinymce/tinymce-vue@3.0.1 -S

而tinymce版本大小无所谓

安装之后,在 node_modules 中找到 tinymce/skins 目录,然后将 skins 目录拷贝到 static 目录下

// 如果是使用 vue-cli 3.x 构建的 typescript 项目,就放到 public 目录下,文中所有 static 目录相关都这样处理

tinymce 默认是英文界面,所以还需要下载一个中文语言包(记得用科学的办法来访问!)

然后将这个语言包放到 static 目录下,为了结构清晰,我包了一层 tinymce 目录

 二、初始化

在页面中引入以下文件

import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/modern/theme'
import Editor from '@tinymce/tinymce-vue'

经评论区提醒,如果找不到  import 'tinymce/themes/modern/theme' 

可以替换成 import 'tinymce/themes/silver/theme' 
 

tinymce-vue 是一个组件,需要在 components 中注册,然后直接使用

<Editor id="tinymce" v-model="tinymceHtml" :init="editorInit"></Editor>

这里的 init 是 tinymce 初始化配置项,后面会讲到一些关键的 api,完整 api 可以参考官方文档

编辑器需要一个 skin 才能正常工作,所以要设置一个 skin_url 指向之前复制出来的 skin 文件

editorInit: {
  language_url: '/static/tinymce/zh_CN.js',
  language: 'zh_CN',
  skin_url: '/static/tinymce/skins/lightgray',
  height: 300
}

// vue-cli 3.x 创建的 typescript 项目,将 url 中的 static 去掉,即 skin_url: '/tinymce/skins/lightgray'

同时在 mounted 中也需要初始化一次:

如果在这里传入上面的 init 对象,并不能生效,但什么参数都不传也会报错,所以这里传入一个空对象

有朋友反映这里有可能出现以下报错

这是因为 init 参数地址错误,请核对下 init 参数中的几个路径是否正确

如果参数无误,可以先删除 language_url 和 language 再试

 三、扩展插件

完成了上面的初始化之后,就已经能正常运行编辑器了,但只有一些基本功能

tinymce 通过添加插件 plugins 的方式来添加功能

比如要添加一个上传图片的功能,就需要用到 image 插件,添加超链接需要用到 link 插件

同时还需要在页面引入这些插件:

添加了插件之后,默认会在工具栏 toolbar 上添加对应的功能按钮,toolbar 也可以自定义

贴一下完整的组件代码:

<template>
  <div class="tinymce">
    <editor id="tinymce" v-model="Html" :init="tinymceInit" @input="echoEditor"></editor>
    <!-- <div v-html="Html"></div> -->
  </div>
</template>

<script>
import uploadApi from '@/api/uploadApi/upload'
import tinymce from 'tinymce/tinymce'
import 'tinymce/themes/silver/theme'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/icons/default/icons'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/code'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/contextmenu'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/colorpicker'
import 'tinymce/plugins/textcolor'
import 'tinymce/plugins/print'
import 'tinymce/plugins/preview'
import 'tinymce/plugins/searchreplace'
import 'tinymce/plugins/autolink'
import 'tinymce/plugins/directionality'
import 'tinymce/plugins/visualblocks'
import 'tinymce/plugins/visualchars'
import 'tinymce/plugins/media'
import 'tinymce/plugins/fullscreen'
import 'tinymce/plugins/template'
import 'tinymce/plugins/codesample'
import 'tinymce/plugins/charmap'
import 'tinymce/plugins/hr'
import 'tinymce/plugins/pagebreak'
import 'tinymce/plugins/nonbreaking'
import 'tinymce/plugins/anchor'
import 'tinymce/plugins/insertdatetime'
import 'tinymce/plugins/advlist'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/wordcount'
import 'tinymce/plugins/imagetools'
import 'tinymce/plugins/textpattern'
import 'tinymce/plugins/help'
import 'tinymce/plugins/emoticons'
import 'tinymce/plugins/autosave'
import 'tinymce/plugins/autoresize'
// import 'tinymce/baseURL/icons/custom'
// import '../../../public/tinymce/plugins/emoticons'
import '../../../public/tinymce/plugins/bdmap/plugin'
import '../../../public/tinymce/plugins/indent2em/plugin'
import '../../../public/tinymce/plugins/axupimgs/plugin'
// import 'tinymce/plugins/formatpainter'
export default {
  name: 'Tinymce',
  data() {
    return {
      Html: '',
      tinymceInit: {
        language_url: '../../../tinymce/zh_CN.js',
        language: 'zh_CN',
        skin_url: '../../../tinymce/skins/ui/oxide',
        emoticons_database_url: '../../../tinymce/plugins/emoticons/js/emojis.js',
        plugins:
          'print preview searchreplace autolink directionality visualblocks visualchars fullscreen image link media template code codesample table charmap hr pagebreak nonbreaking anchor insertdatetime advlist lists wordcount imagetools textpattern help emoticons autosave autoresize bdmap indent2em axupimgs',
        //     toolbar:
        //       'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
        // styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
        // table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight axupimgs',
        toolbar: [
          'code undo redo restoredraft | cut copy paste pastetext | forecolor backcolor bold italic underline strikethrough link anchor | alignleft aligncenter alignright alignjustify outdent indent | \
       styleselect formatselect fontselect fontsizeselect | bullist numlist | blockquote subscript superscript removeformat | \
         table image media charmap emoticons hr pagebreak insertdatetime print preview | fullscreen | bdmap indent2em lineheight axupimgs'
        ],
        height: 650, //编辑器高度
        min_height: 400,
        // icons: 'custom',
        // inline: true,
        // statusbar: false,
        /*content_css: [ //可设置编辑区内容展示的css,谨慎使用
        '/static/reset.css',
        '/static/ax.css',
        '/static/css.css',
    ],*/
        fontsize_formats: '12px 14px 16px 18px 24px 36px 48px 56px 72px',
        font_formats:
          '微软雅黑=Microsoft YaHei,Helvetica Neue,PingFang SC,sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun,serif;仿宋体=FangSong,serif;黑体=SimHei,sans-serif;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;',
        link_list: [
          { title: '预置链接1', value: 'http://www.tinymce.com' },
          { title: '预置链接2', value: 'http://tinymce.ax-z.cn' }
        ],
        image_list: [
          { title: '预置图片1', value: 'https://www.tiny.cloud/images/glyph-tinymce@2x.png' },
          { title: '预置图片2', value: 'https://www.baidu.com/img/bd_logo1.png' }
        ],
        image_class_list: [
          { title: 'None', value: '' }
          // { title: 'Some class', value: 'class-name' }
        ],
        importcss_append: true,
        //自定义文件选择器的回调内容
        file_picker_callback: function (callback, value, meta) {
          if (meta.filetype === 'file') {
            // // 要先模拟出一个input用于上传本地文件
            var input = document.createElement('input')
            input.setAttribute('type', 'file')
            // 你可以给input加accept属性来限制上传的文件类型
            // 例如:input.setAttribute('accept', '.jpg,.png')
            input.setAttribute('accept', '.doc,.docx,.ppt,.pptx,.pdf,.xlsx')
            input.click()
            input.onchange = function () {
              var file = this.files[0]

              uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
                console.log(res.data.downloadPath)
                uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
                  callback(res.data.downloadPath, { text: file.name })
                })
              })
            }
            // callback('https://www.baidu.com/img/bd_logo1.png', { text: 'My text' })
          }
          if (meta.filetype === 'image') {
            // callback('https://www.baidu.com/img/bd_logo1.png', { alt: 'My alt text' })
            // // 要先模拟出一个input用于上传本地文件
            var input = document.createElement('input')
            input.setAttribute('type', 'file')
            // 你可以给input加accept属性来限制上传的文件类型
            // 例如:input.setAttribute('accept', '.jpg,.png')
            input.setAttribute('accept', '.jpg,.png,.jpeg,.svg')
            input.click()
            input.onchange = function () {
              var file = this.files[0]

              uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
                console.log(res.data.downloadPath)
                uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
                  callback(res.data.downloadPath, { text: file.name })
                })
              })
            }
          }
          if (meta.filetype === 'media') {
            // callback('https://www.baidu.com/img/bd_logo1.png', { alt: 'My alt text' })
            // // 要先模拟出一个input用于上传本地文件
            var input = document.createElement('input')
            input.setAttribute('type', 'file')
            // 你可以给input加accept属性来限制上传的文件类型
            // 例如:input.setAttribute('accept', '.jpg,.png')
            input.setAttribute('accept', '.mp4,.mov,.wmv,.flv')
            input.click()
            input.onchange = function () {
              var file = this.files[0]
              uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
                console.log(res.data.downloadPath)
                uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
                  callback(res.data.downloadPath, { text: file.name })
                })
              })
            }
            // callback('movie.mp4', { source2: 'alt.ogg', poster: 'https://www.baidu.com/img/bd_logo1.png' })
          }
        },
        //多图片上传在富文本内展示的路径(全路径时为空)
        images_upload_base_path: '',
        images_upload_handler: function (blobInfo, succFun, failFun) {
          var file = blobInfo.blob() //转化为易于理解的file对象
          console.log(file)
          // // 要先模拟出一个input用于上传本地文件
          var input = document.createElement('input')
          input.setAttribute('type', 'file')
          // 你可以给input加accept属性来限制上传的文件类型
          // 例如:input.setAttribute('accept', '.jpg,.png')

          uploadApi.getUpload({ bucketName: 'notice', objectName: file.name }).then(res => {
            console.log(res.data.downloadPath)
            uploadApi.uploadFile({ url: res.data.uploadPath, file: file }).then(data => {
              succFun(res.data.downloadPath)
            })
          })

          // callback('http
        },
        toolbar_sticky: true,
        autosave_ask_before_unload: false
      }
    }
  },
  mounted() {
    tinymce.init({})
  },
  methods: {
    echoEditor() {
      this.$emit('echoEditor', this.Html)
    }
  },
  props: {
    tinymceHtml: {
      type: String,
      default: ''
    }
  },
  watch: {
    tinymceHtml: {
      handler(val) {
        this.Html = val
      },
      immediate: true,
      deep: true
    }
  },

  components: { Editor }
}
</script>
 

四、上传图片

tinymce 提供了 images_upload_url 等 api 让用户配置上传图片的相关参数

但为了在不麻烦后端的前提下适配自家的项目,还是得用 images_upload_handler 来自定义一个上传方法

这个方法会提供三个参数:blobInfo, success, failure

其中 blobinfo 是一个对象,包含上传文件的信息:

success 和 failure 是函数,上传成功的时候向 success 传入一个图片地址,失败的时候向 failure 传入报错信息

贴一下我自己的上传方法,使用了 axios 发送请求

handleImgUpload (blobInfo, success, failure) {
  let formdata = new FormData()
  formdata.set('upload_file', blobInfo.blob())
  axios.post('/api/upload', formdata).then(res => {
    success(res.data.data.src)
  }).catch(res => {
    failure('error')
  })
}

  • 7
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
对于 Vue 项目引入 tinymce 富文本编辑器,我们需要先在项目安装 tinymce 的依赖包,可以通过 npm 指令进行安装。安装成功后,我们需要在组件引入并初始化 tinymce 编辑器。 首先,我们需要在组件引入 tinymce,可以通过 `import 'tinymce/tinymce'` 和 `import 'tinymce/themes/silver'` 来引入 tinymce 核心文件和主题文件。然后,我们在组件的 `mounted()` 生命周期函数进行 tinymce 编辑器的初始化。如下所示: ``` <template> <div> <textarea id="editor"></textarea> </div> </template> <script> import 'tinymce/tinymce' import 'tinymce/themes/silver' export default { mounted() { tinymce.init({ selector: '#editor', height: 500, plugins: [ 'paste', 'table', 'advlist' ], toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright | bullist numlist outdent indent | table | code | preview' }) } } </script> ``` 在初始化过程,我们需要指定要初始化的 HTML 元素的选择器 `selector`,这里是 `'#editor'`,然后可以进行一系列的配置,包括高度、插件、工具栏等,具体可以参考官方文档。 需要注意的是,在组件销毁时我们需要进行 tinymce 编辑器的销毁,可以在 `beforeDestroy()` 生命周期函数进行编写。如下所示: ``` <script> import 'tinymce/tinymce' import 'tinymce/themes/silver' export default { data() { return { tinymceInstance: null } }, mounted() { this.tinymceInstance = tinymce.init({ selector: '#editor', ... }) }, beforeDestroy() { tinymce.remove(this.tinymceInstance) } } </script> ``` 以上就是 Vue 项目引入 tinymce 富文本编辑器的一个基本流程,可以根据自己的需求进行相应的配置调整。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值