likes
comments
collection
share

关于Vue内使用tinymce图片上传粘贴相关问题

作者站长头像
站长
· 阅读数 13

最近因为工作需要,用到了富文本编辑器让用户填写反馈,上传图片等功能,经过一些对比选择了tinymce,记录下图片相关问题。

完整版封装的组件代码,放到最后。

环境

  • vue2.x
  • tinymce 5.10.3
  • tinymce-vue 2.1.0

这里由于开发环境是vue2,所以目前这个时间点,需要选择版本的去安装,引用官方文档的一句话

Version 4 of the tinymce-vue package supports Vue.js 3.x, but does not support Vue.js 2.x. For Vue.js 2.x applications, use tinymce-vue version 3.

图片上传

这个比较简单,在init的配置中,配置images_upload_handler

...
data () {
    init: {
      images_upload_handler:this.handleImageUpload
    }
},
methods: {
    handleImageUpload (blobInfo, success, failure) {
      // 将图片上传到服务器.
      let formdata = new FormData()
      formdata.append('file', blobInfo.blob(), blobInfo.filename())
      this.uploadImage(formdata)
        .then(success)
        .catch(failure)
    },
    uploadImage (formdata) {
      return new Promise((resolve, reject) => {
        Axios({
          url: 'https://xxxx.xx.com/xxx/xxx',
          method: 'post',
          data: formdata,
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        }).then(result => {
          console.log(result)
          if (result.status !== 200 || result.data.code !== '200') {
            const msg = '上传失败'
            reject(msg)
          } else {
            resolve(result.data.data)
          }
        })
      })
    }
}

图片粘贴

刚开始是想到的直接监听documentpaste事件,去获取剪切板的图片,代码如下:

// 代码引用至张鑫旭的个人网站文章
// https://www.zhangxinxu.com/wordpress/2018/09/ajax-upload-image-from-clipboard/
document.addEventListener('paste', function (event) {
    var items = event.clipboardData && event.clipboardData.items;
    var file = null;
    if (items && items.length) {
        // 检索剪切板items
        for (var i = 0; i < items.length; i++) {
            if (items[i].type.indexOf('image') !== -1) {
                file = items[i].getAsFile();
                break;
            }
        }
    }
    // 此时file就是剪切板中的图片文件
});

后来发现不生效,检查一下元素得知,tinymce是通过iframe使用的,

关于Vue内使用tinymce图片上传粘贴相关问题 所以在tinymce输入框内,paste事件无法触发

后来找到了init_instance_callback配置项,返回的实例可以监听粘贴事件

init: {
    init_instance_callback: editor => {
        editor.on('paste', e => {})
    }
}

粘贴监听算是解决了,接下来要处理的是,粘贴后如何如何替换图片。

因为默认粘贴进去的图片,是以base64存在的,这样直接存进数据库不好,所以需要将base64替换。

刚开始想到的是,将base64删除,然后替换新的img标签进去,这样存在一个问题,就是如果上传图片的网络慢,用户多次粘贴,或者输入文字,图片的位置就会错位. 最后这里选择了在用户一粘贴,拿到base64,上传成功后替换即可。代码如下

methods: {
    listenImgPaste (event) {
      return new Promise(resolve => {
        const items = event.clipboardData && event.clipboardData.items
        let file = null
        if (items && items.length) {
          for (let i = 0; i < items.length; i++) {
            if (items[i].type.indexOf('image') !== -1) {
              file = items[i].getAsFile()
              break
            }
          }
        }
        if (file) {
          setTimeout(() => {
            // 获取当前图片的base64
            const base64 = this.myValue.match(/src="data:image.*?"/g)
            let formdata = new FormData()
            formdata.append('file', file)
            this.uploadImage(formdata).then(url => {
              // 成功后将base64替换
              this.myValue = this.myValue.replace(base64, `src="${url}"`)
              resolve()
            })
          })
        }
      })
    },
}

成功实现。

最后一个问题,替换修改值以后,tinymce会默认将光标定位到最前面,体验不是很好,最后的解决方法也是加配置

init: {
    init_instance_callback: editor => {
          // 初始化后移动光标到最后
          this.moveCursorToLast(editor)
          console.log(editor)
          editor.on('paste', async event => {
            await this.listenImgPaste(event)
            this.moveCursorToLast(editor)
          })
        }
}

methods: {
    // 移动光标到最后
    moveCursorToLast (editor) {
      editor.selection.select(editor.getBody(), true)
      editor.selection.collapse(false)
    }
}

最后的完整组件代码

// tinymce.vue
<template>
  <div>
    <editor
      ref="tinymce"
      v-model="myValue"
      :init="init"
    ></editor>
  </div>
</template>

<script>

// import xx form 'xxx'
import Editor from '@tinymce/tinymce-vue'
import Axios from 'axios'
export default {
  name: 'tinymce',
  props: {
    value: String
  },
  components: {
    Editor
  },
  watch: {
    myValue (val) {
      this.$emit('input', val)
    },
    value: {
      handler (v) {
        this.myValue = v
      },
      immediate: true
    }
  },
  data () {
    return {
      myValue: '',
      init: {
        // language_url: '/static/plugins/tinymce/zh_CN.js', // 如果语言包不存在,指定一个语言包路径
        language: 'zh_CN', // 语言
        // skin_url: '/static/plugins/tinymce/skins/ui/oxide', // 如果主题不存在,指定一个主题路径
        // content_css: '/static/plugins/tinymce/mycontent.css',
        height: '700px',
        width: '1200px',
        plugins: 'link image', // 插件
        toolbar: 'bold italic underline strikethrough link image', // 工具栏
        branding: false, // 技术支持(Powered by Tiny || 由Tiny驱动)
        menubar: false, // 菜单栏
        theme: 'silver', // 主题
        zIndex: 1101,
        paste_data_images: true,
        images_upload_handler: this.handleImageUpload,
        init_instance_callback: editor => {
          // 初始化后移动光标到最后
          this.moveCursorToLast(editor)
          editor.on('paste', async event => {
            await this.listenImgPaste(event)
            this.moveCursorToLast(editor)
          })
        }
      }
    }
  },
  methods: {
    listenImgPaste (event) {
      return new Promise(resolve => {
        const items = event.clipboardData && event.clipboardData.items
        let file = null
        if (items && items.length) {
          for (let i = 0; i < items.length; i++) {
            if (items[i].type.indexOf('image') !== -1) {
              file = items[i].getAsFile()
              break
            }
          }
        }
        if (file) {
          setTimeout(() => {
            // 获取当前图片的base64
            const base64 = this.myValue.match(/src="data:image.*?"/g)
            let formdata = new FormData()
            formdata.append('file', file)
            this.uploadImage(formdata).then(url => {
              // 成功后将base64替换
              this.myValue = this.myValue.replace(base64, `src="${url}"`)
              resolve()
            })
          })
        }
      })
    },
    handleImageUpload (blobInfo, success, failure) {
      console.log(blobInfo, blobInfo.filename())
      // 将图片上传到服务器.
      let formdata = new FormData()
      formdata.append('file', blobInfo.blob(), blobInfo.filename())
      this.uploadImage(formdata)
        .then(success)
        .catch(failure)
    },
    // 上传图片
    uploadImage (formdata) {
      return new Promise((resolve, reject) => {
        Axios({
          url: 'https://xxxx.xxx.xxx/xxx/xxx/xx',
          method: 'post',
          data: formdata,
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        }).then(result => {
          console.log(result)
          if (result.status !== 200 || result.data.code !== '200') {
            const msg = '上传失败'
            reject(msg)
          } else {
            resolve(result.data.data)
          }
        })
      })
    },
    // 移动光标到最后
    moveCursorToLast (editor) {
      editor.selection.select(editor.getBody(), true)
      editor.selection.collapse(false)
    }
  }
}
</script>

<style scoped>

</style>


引用

    <div>
        <tinymce v-model="form.des"/>
    </div>
    
    <script>
        import tinymce from '../../../common/tinymce'
        export default {
          name: 'createFeedback',
          components: {
            tinymce
          },
          data () {
              return {
                  form: {
                      des: ''
                  }
              }
          }
    </script>
转载自:https://juejin.cn/post/7148385803981291528
评论
请登录