tinymce剪切板粘贴上传阿里oss问题

439 阅读3分钟

需求:

富文本编辑器用的是tinymce,需要上传和粘贴图片自动上传到阿里oss

问题:

上传图片调用images_upload_handler方法时,没有问题,直接用blobInfo参数就可以实现上传,但是剪切板粘贴图片后,上传到阿里oss提示Must provide Buffer/Blob for put,没有办法拿到阿里云返回的正确地址

方案:

先将images_upload_handler里的blobInfo获取的二进制文件流转化成base64,再在上传方法中将base64转化为blob文件流,就可以实现blob格式

tinymce.js 显示富文本的js

...

// 定义2个全局变量
let uploadTimeOut = null
let removeFn = null


  // Initialize TinyMCE
  tinymce.init({
    selector: '#mytextarea',
    plugins:
      'advlist  autolink autosave code codesample colorpicker colorpicker contextmenu directionality emoticons fullscreen hr image  insertdatetime link lists media nonbreaking noneditable  paste preview print save searchreplace tabfocus table template textcolor textpattern visualblocks visualchars wordcount autoresize  ',
    toolbar:
      'code undo redo   |  fontselect fontsizeselect styleselect   | forecolor backcolor bold italic underline strikethrough hr link blockquote lineheight removeformat |cut copy  paste  | alignleft aligncenter alignright alignjustify outdent indent \
     | table image media | bullist numlist  | \
     subscript superscript   emoticons   insertdatetime print preview | fullscreen ',
    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;',
    height: 650, //编辑器高度
    min_height: 400,
    language: 'zh_CN', // Set language pack to Chinese
    toolbar_sticky: true, //导航栏高度自定义时候,固定工具栏
    autosave_ask_before_unload: false,
    //   powerpaste_word_import: 'propmt', // 参数可以是propmt, merge, clear,效果自行切换对比
    //   powerpaste_html_import: 'propmt', // propmt, merge, clear
    powerpaste_allow_local_images: true,
    paste_data_images: true, // 默认是false的,记得要改为true才能粘贴
    // 上传函数
    images_upload_handler: function (blobInfo, success, failure, progress) {
      if (uploadTimeOut) {
        removeFn && removeFn()
        clearTimeout(uploadTimeOut)
      }

      let fileInfo = blobInfo.blob()

      //关键代码,先转化将blob二进制流转化为base64
      function blobToBase64(blob) {
        return new Promise((resolve, _) => {
          const reader = new FileReader()
          reader.onloadend = () => resolve(reader.result)
          reader.readAsDataURL(blob)
        })
      }

      var base64 = null
      //由于粘贴板问题,因此需要重新将图片转成base64
      blobToBase64(fileInfo).then((res) => {
        base64 = res
      })

      let fileName = fileInfo.name

      var upload = () => {
        putImageUploadSpecial(fileName, base64, 'content')
          .then((res) => {
            let result = res.res
            if (result.status == 200) {
            //获取正确地址
              let imgUrl = res.url
              success(imgUrl)
            }
          })
          .catch((err) => {
            success('uploadFail')
            setTimeout(() => {
              let errorImg = document.querySelectorAll('img[src="uploadFail"]')
              errorImg.forEach((item) => {
                item.parentNode.removeChild(item)
              })
            }, 100)
          })
      }

      if (fileInfo.name == 'image.png') {
        uploadTimeOut = setTimeout(() => upload(), 30)

        // 这里要用一下闭包把当前的success函数保存起来,具体为啥一下子说不清,存起来就是了
        // 图片复制进来后唯一的标识就是 blobUri 了,到时候删除还得靠他
        // removeFn 与第9行代码呼应
        removeFn = (function (url, cb) {
          return function () {
            cb && cb(url)
            let imgNode = document.querySelector('img[src="' + url + '"]')
            imgNode && imgNode.parentNode && imgNode.parentNode.removeChild(imgNode)
            removeFn = null
          }
        })(blobInfo.blobUri(), success)
      } else {
        upload()
      }
    },
    // 文件
    file_picker_callback: function (callback, value, meta) {
      //文件分类
      var filetype = '.pdf, .txt, .zip, .rar, .7z, .doc, .docx, .xls, .xlsx, .ppt, .pptx, .mp3, .mp4'
      //后端接收上传文件的路径
      var dir = ''
      //为不同插件指定文件类型及后端地址
      switch (meta.filetype) {
        case 'image':
          filetype = '.jpg, .jpeg, .png, .gif'
          dir = 'img'
          break
        case 'media':
          filetype = '.mp3, .mp4'
          dir = 'media'
          break
        case 'file':
        default:
      }
      //模拟出一个input用于添加本地文件
      let input = document.createElement('input')
      input.setAttribute('type', 'file')
      input.setAttribute('accept', filetype)
      input.click()
      input.onchange = function () {
        let file = this.files[0]
        let fileName = file.name
        // let formData = new FormData()

        // formData.append('file', file, file.name)
        putImageUpload(fileName, file, 'content')
          .then((res) => {
            let result = res.res

            if (result.status == 200) {
              callback(res.url, { text: fileName })
            }
          })
          .catch((err) => {
            console.log(err)
          })
      }
    },
    //初始化回显
    init_instance_callback: (editor) => {
      if (val) {
        tinymce.get('mytextarea').setContent(val)
      }
    },
  })

...

all-oss.js 上传阿里oss封装方法

let client = new OSS({
  region: 'xxxxx',
  //云账号AccessKey有所有API访问权限,建议遵循阿里云安全最佳实践,部署在服务端使用RAM子账号或STS,部署在客户端使用STS。
  accessKeyId: 'xxxxxx',
  accessKeySecret: 'xxxx',
  bucket: 'xxx',
})



//上传图片到阿里云oss,处理剪切板问题blob流浏览器不识别问题
async function putImageUploadSpecial(fileName, base64, name) {
  let _file = dataURLtoBlob(base64)
  try {
    var urls = window.location.href
    urlarr = urls.split('/')
    var environment = urlarr[2]
    var path_img = 'on-line_js_img'
    if (environment.indexOf('local') >= 0) {
      path_img = 'local_js_img'
    } else if (environment.indexOf('test') >= 0) {
      path_img = 'test_js_img'
    } else {
      path_img = 'on-line_js_img'
    }
    //唯一随机命名
    let newFileName = new Date().getTime() + Math.random().toString(36).substring(3, 20) + _getSuffix(fileName)

    let result = await client.put(name + '/' + path_img + '/' + newFileName, _file)

    return result
  } catch (e) {
    console.log(e)
  }
}

// 获取文件名后缀
function _getSuffix(filename) {
  let pos = filename.lastIndexOf('.')
  let suffix = ''
  if (pos != -1) {
    suffix = filename.substring(pos)
  }
  return suffix
}

//base64转blob
function dataURLtoBlob(dataurl) {
  var arr = dataurl.split(','),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n)
  while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
  }
  return new Blob([u8arr], { type: mime })
}

juejin.cn/post/698739…