vue 项目使用 TinyMCE

这篇文章记录一下 TinyMCE 在 vue 项目的中的运用。其实早就改写了,但一直……忽略了。这次项目迭代又用到了 TinyMCE,所以补作此文。

为什么选择 TinyMCE

TinyMCE 是一个功能齐全、历史悠久、持续维护的富文本编辑器。该编辑器为美国 Tiny Technologies 开发。wordpress 在切换为古腾堡编辑器之前就是使用的 TinyMCE。因此,项目选择了这个经过长久实践的编辑器。

安装包

首先,需要安装 TinyMCE。

yarn add tinymce

因为是 vue 项目,为了更方便地使用,安装一下 vue 的封装包。

yarn add @tinymce/tinymce-vue

使用

为了项目里各处使用,封装一个 Tinymce 组件

<template>
  <div class="tinymce-container">
    <editor id="tinymce" v-model="value" :init="init" />
  </div>
</template>

<script>
import tinymce from 'tinymce'
import Editor from '@tinymce/tinymce-vue'
import 'tinymce/themes/silver/theme'
import 'tinymce/plugins/image'
import 'tinymce/plugins/link'
import 'tinymce/plugins/code'
import 'tinymce/plugins/table'
import 'tinymce/plugins/lists'
import 'tinymce/plugins/wordcount'
import 'tinymce/icons/default'
import Compressor from 'compressorjs'
import { uploadFile } from '@/api/common'
// 下面的插件是自带的,不需要引入
// import "tinymce/plugins/contextmenu";
// import "tinymce/plugins/colorpicker";
// import "tinymce/plugins/textcolor";
export default {
  name: 'MyTinymce',
  components: {
    Editor
  },
  props: {
    tinymceHtml: {
      type: String,
      default: ''
    },
    tinymceHeight: {
      type: Number,
      default: 500
    }
  },
  data() {
    return {
      value: this.tinymceHtml, // 父组件通过ref拿到该组件的值
      init: {
        selector: '#tinymce',
        // https://www.tiny.cloud/get-tiny/language-packages/
        language_url: '/tinymce/langs/zh_CN.js',
        language: 'zh_CN',
        skin_url: '/tinymce/skins/ui/oxide', // 编辑器需要一个skin才能正常工作,所以要设置一个skin_url指向之前复制出来的skin文件
        height: this.tinymceHeight,
        plugins: 'image link code table lists wordcount', // 引入插件
        toolbar:
          'styleselect fontselect fontsizeselect lineheight link | forecolor backcolor bold italic underline strikethrough | formatselect alignleft aligncenter alignright alignjustify | image quicklink blockquote table numlist bullist | preview fullscreen', // 工具栏
        browser_spellcheck: true, // 拼写检查
        branding: false, // 去水印
        elementpath: false, // 禁用编辑器底部的状态栏
        statusbar: false, // 隐藏编辑器底部的状态栏
        paste_data_images: true, // 允许粘贴图像
        menubar: false, // 隐藏最上方menu
        toolbar_mode: 'wrap',
        file_picker_types: 'image',
        images_upload_credentials: true,
        fontsize_formats: '14px 16px 18px 20px 24px 26px 28px 30px 32px 36px', // 字体大小
        font_formats:
          '微软雅黑=Microsoft YaHei,Helvetica Neue;PingFang SC;sans-serif;苹果苹方=PingFang SC,Microsoft YaHei,sans-serif;宋体=simsun;serifsans-serif;Terminal=terminal;monaco;Times New Roman=times new roman;times', // 字体
        lineheight_formats: '1 1.1 1.2 1.3 1.4 1.5 2', // 行高
        style_formats: [
          {
            title: '段落间距',
            // items: [
            //   {
            //     title: '1', block: 'p', styles: { 'margin-top': '1em', 'margin-bottom': '1em' }
            //   }
            // ]
            items: [0, 1, 2, 3, 4, 5, 10, 15, 20, 25, 30, 35, 40].map(item => {
              return {
                title: `${item}px`,
                block: 'p',
                styles: {
                  'margin-top': `${item}px`,
                  'margin-bottom': `${item}px`
                }
              }
            })
          }
        ],
        style_formats_merge: true, // 自定义样式追加到 styleselect
        style_formats_autohide: true, // 隐藏不可用样式
        /**
         * 下面方法是为tinymce添加自定义插入图片按钮
         */
        // 图片上传三个参数,图片数据,成功时的回调函数,失败时的回调函数
        images_upload_handler: function(blobInfo, success, failure) {
          // 图片压缩
          new Compressor(blobInfo.blob(), {
            quality: 0.75,
            maxWidth: 1920,
            success: (result) => {
              _uploadFile(new File([result], result.name, { type: result.type }))
            },
            error: () => {
              _uploadFile(blobInfo.blob())
            }
          })

          function _uploadFile(blobData) {
            const formdata = new FormData()
            formdata.append('file', blobData)
            formdata.append('type', 'product')

            uploadFile(formdata)
              .then(res => {
                console.log(res)
                success(res.data.url)
              })
              .catch(res => {
                failure('error')
              })
          }
        }
      }
    }
  },
  computed: {},
  watch: {
    tinymceHtml(newV, oldV) {
      this.value = newV
    }
  },
  created() {},
  mounted() {
    tinymce.init({})
  },
  methods: {}
}
</script>
<style lang="scss">
.tox-silver-sink,
.tox-tinymce-aux {
  z-index: 3000000 !important;
}
.tox .tox-dialog-wrap__backdrop--opaque {
  background-color: transparent !important;
}
</style>

(这里的大部分代码也是拷贝的其他人的)

说明一下。

tinymce 需要有主题才能显示,这里把主题拷贝到了项目静态资源目录引用。原因忘了,大概是避免打包遗漏吧。

语言文件到 tinymce 网站下载。

style_formats:可以进行样式自定义。我这里定义了段落间距。tinymce 没有提供相关功能,只能自己定义。可以用,但效果不太好。使用后会把选中的部分标签都变成 p 标签,并应用相关 margin 设置。

style_formats_merge:把自定义的样式追加到样式文件。

images_upload_handler:这里可以进行图片压缩的操作,避免上传图片太大。我使用的图片压缩包是 compressorjs

样式部分的 z-index 是为了避免 element-ui 弹窗 z-index 过高而导致 tinymce 显示问题。

总结

web 的富文本编辑体验并不是很好,与 word 相差甚远。将就用吧。