vue中引入Tinymce富文本编辑器

Posted fqh123

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue中引入Tinymce富文本编辑器相关的知识,希望对你有一定的参考价值。

最近想在项目上引入一个富文本编辑器,之前引入过summernote,感觉并不太适合vue使用,

然后在网上查了查,vue中使用Tinymce比较适合,

首先,我们在vue项目的components文件夹中加入如下几个文件

技术图片

 

 首先看一下Tinymce/dynamicLoadScript.js的内容:

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/components/EditorImage.vue中的内容

<template>
  <div class="upload-container">
    <el-button :style="background:color,borderColor:color" icon="el-icon-upload" size="mini" type="primary" @click=" dialogVisible=true">
      upload
    </el-button>
    <el-dialog :visible.sync="dialogVisible">
      <el-upload
        :multiple="true"
        :file-list="fileList"
        :show-file-list="true"
        :on-remove="handleRemove"
        :on-success="handleSuccess"
        :before-upload="beforeUpload"
        class="editor-slide-upload"
        action="https://httpbin.org/post"
        list-type="picture-card"
      >
        <el-button size="small" type="primary">
          Click upload
        </el-button>
      </el-upload>
      <el-button @click="dialogVisible = false">
        Cancel
      </el-button>
      <el-button type="primary" @click="handleSubmit">
        Confirm
      </el-button>
    </el-dialog>
  </div>
</template>

<script>
// import  getToken  from ‘api/qiniu‘

export default 
  name: EditorSlideUpload,
  props: 
    color: 
      type: String,
      default: #1890ff
    
  ,
  data() 
    return 
      dialogVisible: false,
      listObj: ,
      fileList: []
    
  ,
  methods: 
    checkAllSuccess() 
      return Object.keys(this.listObj).every(item => this.listObj[item].hasSuccess)
    ,
    handleSubmit() 
      const arr = Object.keys(this.listObj).map(v => this.listObj[v])
      if (!this.checkAllSuccess()) 
        this.$message(Please wait for all images to be uploaded successfully. If there is a network problem, please refresh the page and upload again!)
        return
      
      this.$emit(successCBK, arr)
      this.listObj = 
      this.fileList = []
      this.dialogVisible = false
    ,
    handleSuccess(response, file) 
      const uid = file.uid
      const objKeyArr = Object.keys(this.listObj)
      for (let i = 0, len = objKeyArr.length; i < len; i++) 
        if (this.listObj[objKeyArr[i]].uid === uid) 
          this.listObj[objKeyArr[i]].url = response.files.file
          this.listObj[objKeyArr[i]].hasSuccess = true
          return
        
      
    ,
    handleRemove(file) 
      const uid = file.uid
      const objKeyArr = Object.keys(this.listObj)
      for (let i = 0, len = objKeyArr.length; i < len; i++) 
        if (this.listObj[objKeyArr[i]].uid === uid) 
          delete this.listObj[objKeyArr[i]]
          return
        
      
    ,
    beforeUpload(file) 
      const _self = this
      const _URL = window.URL || window.webkitURL
      const fileName = file.uid
      this.listObj[fileName] = 
      return new Promise((resolve, reject) => 
        const img = new Image()
        img.src = _URL.createObjectURL(file)
        img.onload = function() 
          _self.listObj[fileName] =  hasSuccess: false, uid: file.uid, width: this.width, height: this.height 
        
        resolve(true)
      )
    
  

</script>

<style lang="scss" scoped>
.editor-slide-upload 
  margin-bottom: 20px;
  /deep/ .el-upload--picture-card 
    width: 100%;
  

</style>

Tinymce/plugins.js中的内容:

// Any plugins you want to use has to be imported
// Detail plugins list see https://www.tinymce.com/docs/plugins/
// Custom builds see https://www.tinymce.com/download/custom-builds/

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

Tinymce/toolbar.js的内容:

// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols

const toolbar = [‘searchreplace bold fontsizeselect 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

在看看Tinymce/index.vue中的内容:

<template>
  <div :class="fullscreen:fullscreen" class="tinymce-container" :style="width:containerWidth">
    <textarea :id="tinymceId" class="tinymce-textarea" />
    <!-- <div class="editor-custom-btn-container">
      <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
    </div> -->
  </div>
</template>

<script>
/**
 * docs:
 * https://panjiachen.github.io/vue-element-admin-site/feature/component/rich-editor.html#tinymce
 */
import editorImage from ./components/EditorImage
import plugins from ./plugins
import toolbar from ./toolbar
import load from ./dynamicLoadScript

// why use this cdn, detail see https://github.com/PanJiaChen/tinymce-all-in-one
const tinymceCDN = https://cdn.jsdelivr.net/npm/tinymce-all-in-one@4.9.3/tinymce.min.js

export default 
  name: Tinymce,
  components:  editorImage ,
  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: 360
    ,
    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,
        es: es_MX,
        ja: ja
      
    
  ,
  computed: 
    containerWidth() 
      const width = this.width
      if (/^[\\d]+(\\.[\\d]+)?$/.test(width))  // matches `100`, `‘100‘`
        return `$widthpx`
      
      return width
    
  ,
  watch: 
    value(val) 
      if (!this.hasChange && this.hasInit) 
        this.$nextTick(() =>
          window.tinymce.get(this.tinymceId).setContent(val || ‘‘))
      
    
  ,
  mounted() 
    this.init()
  ,
  activated() 
    if (window.tinymce) 
      this.initTinymce()
    
  ,
  deactivated() 
    this.destroyTinymce()
  ,
  destroyed() 
    this.destroyTinymce()
  ,
  methods: 
    init() 
      // dynamic load tinymce from cdn
      load(tinymceCDN, (err) => 
        if (err) 
          this.$message.error(err.message)
          return
        
        this.initTinymce()
      )
    ,
    initTinymce() 
      const _this = this
      window.tinymce.init(
        selector: `#$this.tinymceId`,
        language: this.languageTypeList[zh],
        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,
        powerpaste_word_import: clean,
        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,
        link_title: false,
        nonbreaking_force_tab: true, // inserting nonbreaking space &nbsp; need Nonbreaking Space Plugin
        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
          )
        
        // 整合七牛上传
        // 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 scoped>
.tinymce-container 
  position: relative;
  line-height: normal;

.tinymce-container>>>.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>

最后,在需要引入的页面内引入;

<template>
  <div >
    <tinymce v-model="content" :height="300" />
  
  </div>
  
</template>

<script>
import Tinymce from @/components/Tinymce

export default 
  components:
    Tinymce
  
</script>

效果:

技术图片

 

以上是关于vue中引入Tinymce富文本编辑器的主要内容,如果未能解决你的问题,请参考以下文章

富文本编辑器tinymce在vue中的使用

在vue cli 3脚手架里引入tinymce 5富文本编辑器

vue项目中使用tinymce富文本的踩坑经历

粗暴将tinymce富文本编辑器整合到vue项目

vue tinymce 控制图片大小上传,以及富文本编辑框在dialog的问题

tinymce富文本编辑器