vue中使用tinymce,富文本编辑器组件封装。

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

npm install @tinymce/tinymce-vue -S

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

本着节约是第一准则的想法,只能去下载tinymce资源了.详细步骤可以查看tinymce中文文档

第一步:下载tinymce资源,

使用cdn代理,这里有两种方式:
(1)在index.html中引入tinymce.min.js文件

<script src="https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js"></script>

(2)在富文本编辑器初始化之前加载tinymce.min.js文件
我采用的第二种方式(下方会有引用位置提示)
把组件放到components文件夹中,方便调用。文件结构如图:
在这里插入图片描述

第二步:新建tinymce组件
在index.vue中初始化tinymce,是组件最主要的部分代码如下:
<template>
  <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
    <textarea :id="tinymceId" class="tinymce-textarea" />
  </div>
</template>

<script>

import plugins from './plugins'
import toolbar from './toolbar'
import load from './dynamicLoadScript'
// tinymceCDN代理地址
const tinymceCDN = 'https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js'

export default {
  name: 'Tinymce',
  components: {  },
  props: {
    id: {
      type: String,
      default: function() {
        return 'vue-tinymce-' + +new Date() + ((Math.random() * 1000).toFixed(0) + '')
      }
    },
    value: {
      type: String,
      default: ''
    },
    toolbar: {
      type: Array,
      required: false,
      default() {
        return []
      }
    },
    menubar: {
      type: String,
      default: 'file edit insert view format table'
    },
    height: {
      type: [Number, String],
      required: false,
      default: 350
    },
    width: {
      type: [Number, String],
      required: false,
      default: 'auto'
    }
  },
  data() {
    return {
      hasChange: false,
      hasInit: false,
      tinymceId: this.id,
      fullscreen: false,
      languageTypeList: {
        'en': 'en',
        'zh': 'zh_CN',
      }
    }
  },
  computed: {
    language() {
      return this.languageTypeList[this.$store.getters.language]
    },
    containerWidth() {
      const width = this.width
      if (/^[\d]+(\.[\d]+)?$/.test(width)) { // matches `100`, `'100'`
        return `${width}px`
      }
      return width
    }
  },
  watch: {
    value(val) {
      if (!this.hasChange && this.hasInit) {
        this.$nextTick(() =>
          window.tinymce.get(this.tinymceId).setContent(val || ''))
      }
    },
    language() {
      this.destroyTinymce()
      this.$nextTick(() => this.initTinymce())
    }
  },
  mounted() {
    this.init()
  },
  activated() {
    if (window.tinymce) {
      this.initTinymce()
    }
  },
  deactivated() {
    this.destroyTinymce()
  },
  destroyed() {
    this.destroyTinymce()
  },
  methods: {
    init() {
      // 引用tinymceCDN,并调用初始化Tinymce方法
      load(tinymceCDN, (err) => {
        if (err) {
          this.$message.error(err.message)
          return
        }
        this.initTinymce()
      })
    },
    initTinymce() {
      const _this = this
      window.tinymce.init({
        language: this.language,                    //中英文切换
        selector: `#${this.tinymceId}`,             //容器,可使用css选择器
        height: this.height,                        
        body_class: 'panel-body ',
        object_resizing: false,                                      // 是否禁用表格图片大小调整
        toolbar: this.toolbar.length > 0 ? this.toolbar : toolbar,    //工具栏
        menubar: this.menubar,                                        //菜单栏
        plugins: plugins,                                             //选择需加载的插件
        end_container_on_empty_block: true,                          // enter键 分块      
        powerpaste_word_import: 'clean',                            // 是否保留word粘贴样式  clean | merge  
        code_dialog_height: 450,                                    // 代码框高度 、宽度 
        code_dialog_width: 1000,
        advlist_bullet_styles: 'square',                            // 无序列表 有序列表
        advlist_number_styles: 'default',
        imagetools_cors_hosts: ['www.tinymce.com', 'codepen.io'],
        default_link_target: '_blank',
        branding:false,                   //是否显示POWERED BY TINYMCE
        link_title: false,
        nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
        // paste_data_images: true, // 设置为“true”将允许粘贴图像,而将其设置为“false”将不允许粘贴图像。
        init_instance_callback: editor => {
          if (_this.value) {
            editor.setContent(_this.value)
          }
          _this.hasInit = true
          editor.on('NodeChange Change KeyUp SetContent', () => {
            this.hasChange = true
            this.$emit('input', editor.getContent())
          })
        },
        setup(editor) {
          editor.on('FullscreenStateChanged', (e) => {
            _this.fullscreen = e.state
          })
        },
        // it will try to keep these URLs intact
        // https://www.tiny.cloud/docs-3x/reference/configuration/Configuration3x@convert_urls/
        // https://stackoverflow.com/questions/5196205/disable-tinymce-absolute-to-relative-url-conversions
        convert_urls: false
        // 整合七牛上传
        // images_dataimg_filter(img) {
        //   setTimeout(() => {
        //     const $image = $(img);
        //     $image.removeAttr('width');
        //     $image.removeAttr('height');
        //     if ($image[0].height && $image[0].width) {
        //       $image.attr('data-wscntype', 'image');
        //       $image.attr('data-wscnh', $image[0].height);
        //       $image.attr('data-wscnw', $image[0].width);
        //       $image.addClass('wscnph');
        //     }
        //   }, 0);
        //   return img
        // },
        // images_upload_handler(blobInfo, success, failure, progress) {
        //   progress(0);
        //   const token = _this.$store.getters.token;
        //   getToken(token).then(response => {
        //     const url = response.data.qiniu_url;
        //     const formData = new FormData();
        //     formData.append('token', response.data.qiniu_token);
        //     formData.append('key', response.data.qiniu_key);
        //     formData.append('file', blobInfo.blob(), url);
        //     upload(formData).then(() => {
        //       success(url);
        //       progress(100);
        //     })
        //   }).catch(err => {
        //     failure('出现未知问题,刷新页面,或者联系程序员')
        //     console.log(err);
        //   });
        // },
      })
    },
    destroyTinymce() {
      const tinymce = window.tinymce.get(this.tinymceId)
      if (this.fullscreen) {
        tinymce.execCommand('mceFullScreen')
      }

      if (tinymce) {
        tinymce.destroy()
      }
    },
    setContent(value) {
      window.tinymce.get(this.tinymceId).setContent(value)
    },
    getContent() {
      window.tinymce.get(this.tinymceId).getContent()
    },
    imageSuccessCBK(arr) {
      const _this = this
      arr.forEach(v => {
        window.tinymce.get(_this.tinymceId).insertContent(`<img class="wscnph" src="${v.url}" >`)
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.tinymce-container {
  position: relative;
  line-height: normal;
}

.tinymce-container {
  ::v-deep {
    .mce-fullscreen {
      z-index: 10000;
    }
  }
}

.tinymce-textarea {
  visibility: hidden;
  z-index: -1;
}

.editor-custom-btn-container {
  position: absolute;
  right: 4px;
  top: 4px;
  /*z-index: 2005;*/
}

.fullscreen .editor-custom-btn-container {
  z-index: 10000;
  position: fixed;
}

.editor-upload-btn {
  display: inline-block;
}
</style>

初始化的时候包含很多配置,在网上查询的富文本插件tinymce初始化配置参数说明tinymce中文文档中也有详细说明。

实现图片上传

要使TinyMCE能够上传图片,需要如下几步:

第1步:上传图片,首先要启用图片插件
在plugins参数中把image加进去。
第2步:在工具栏显示图片工具按钮
在toolbar参数中把image加进去。

此时,点图片按钮是没有上传选项的,只能添加图片地址。

第3步:加入配置参数images_upload_handler

此选项允许你使用自定义函数代替TinyMCE来处理上传操作。该自定义函数需提供三个参数:blobInfo、成功回调和失败回调。
在这里插入图片描述
这个方法会提供三个参数:blobInfo, success, failure

其中 blobinfo 是一个对象,包含上传文件的信息:
在这里插入图片描述
success 和 failure 是函数,上传成功的时候向 success 传入一个图片地址,失败的时候向 failure 传入报错信息,代码中还有一个progress参数没有查到其含义,待以后更新。

在plugins.js中是插件的配置,代码如下
const plugins = ['advlist anchor autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image imagetools insertdatetime link lists media nonbreaking noneditable pagebreak paste preview print save searchreplace spellchecker tabfocus table template textcolor textpattern visualblocks visualchars wordcount']

export default plugins
在toolbar.js中是工具栏的配置,代码如下
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
在dynamicLoadScript.js封装了index.vue用到的方法,代码如下
let callbacks = []

function loadedTinymce() {
  // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2144
  // check is successfully downloaded script
  return window.tinymce
}

const dynamicLoadScript = (src, callback) => {
  const existingScript = document.getElementById(src)
  const cb = callback || function() {}

  if (!existingScript) {
    const script = document.createElement('script')
    script.src = src // src url for the third-party library being loaded.
    script.id = src
    document.body.appendChild(script)
    callbacks.push(cb)
    const onEnd = 'onload' in script ? stdOnEnd : ieOnEnd
    onEnd(script)
  }

  if (existingScript && cb) {
    if (loadedTinymce()) {
      cb(null, existingScript)
    } else {
      callbacks.push(cb)
    }
  }

  function stdOnEnd(script) {
    script.onload = function() {
      // this.onload = null here is necessary
      // because even IE9 works not like others
      this.onerror = this.onload = null
      for (const cb of callbacks) {
        cb(null, script)
      }
      callbacks = null
    }
    script.onerror = function() {
      this.onerror = this.onload = null
      cb(new Error('Failed to load ' + src), script)
    }
  }

  function ieOnEnd(script) {
    script.onreadystatechange = function() {
      if (this.readyState !== 'complete' && this.readyState !== 'loaded') return
      this.onreadystatechange = null
      for (const cb of callbacks) {
        cb(null, script) // there is no way to catch loading errors in IE8
      }
      callbacks = null
    }
  }
}

export default dynamicLoadScript

第三步:引用tinymce组件

在父组件中引入tinymce组件

import tinymce from '@/components/Tinymce/index'
<tinymce  v-model="content"></tinymce>

希望此篇文章对你使用富文本编辑器有帮助,也可以留言一起讨论!

Logo

前往低代码交流专区

更多推荐