基于vue-simplemde实现图片拖拽、粘贴功能
项目使用的是vue框架,需要一个markdown的编辑框,就在npm上找了一下,发现simplemde挺不错的,由于我比较懒,就顺便在npm又搜了一下,找到了vue-simplemde这个package,那就开始使用它吧。
但是这个vue-simplemde不支持图片拖拽上传、粘贴上传,也不能说是因为这个vue-simplemde,因为vue-simplemde只是对simplemde的基础上封装成一个Vue插件。所以最后还是由于simplemde没有提供相关的功能,但是为了用户体验考虑,这个功能时必要的,除非不使用markdown编辑器。而去使用富文本编辑器,那样的话,项目很多的代码都要进行更改。所以就在网上查了文章,及在github上查了一些代码。下面将进行分析
拖拽
拖拽的API核心是drop这个事件,就是当我们从桌面拖动一个文件到浏览器里时,松开的时候,而触发的事件名。
我们都知道,你随便拖动一个图片到浏览器里,会直接打开这个图片,这是因为浏览器默认你拖动文件到浏览器里时,将打开这个文件,所以,我们需要阻止原生的操作。
我们现在先写一段代码,让其屏蔽掉默认事件
window.addEventListener("drop",e=>{ e=e||event if(e.target.className==='CodeMirror-scroll'){//如果进入到编辑器的话,将阻止默认事件 e.preventDefault() } },false)
CodeMirror-scroll这个Class就是simplemde编辑框的Class名称。
现在我们拖拽文件到这个编辑框,然后松掉,不会出现任何反应。如果在编辑框之外的地方,还是会继续触发默认事件。
下面就是获取simplemde方法,给他drop事件处理方法。
//假设页面一共有三个编辑窗口,所以需要循环监听事件 [this.$refs.simplemde1, this.$refs.simplemde2, this.$refs.simplemde3 ].map(({simplemde})=>{ simplemde.codemirror.on('drop',(editor,e)=>{ if(!(e.dataTransfer&&e.dataTransfer.files)){ //弹窗说明,此浏览器不支持此操作 return } letdataList=e.dataTransfer.files letimageFiles=[]//要上传的文件实例数组 //循环,是因为可能会同时拖动几个图片文件 for(leti=0;i诈一看,代码好像有点多,那是因为注释的原因,下面是没有注释的代码。你可以根据下面的代码,有自己的见解和理解:
[this.$refs.simplemde1, this.$refs.simplemde2, this.$refs.simplemde3 ].map(({simplemde})=>{ simplemde.codemirror.on('drop',(editor,e)=>{ if(!(e.dataTransfer&&e.dataTransfer.files)){ return } letdataList=e.dataTransfer.files letimageFiles=[] for(leti=0;i粘贴
粘贴的API是paste方法,这个不像上面一样,粘贴不需要禁止默认事件,因为我们可以看到,你复制一个图片,到浏览器里按下ctrl+v的时候,是不会发生任何变化的,所以没用必要禁止默认事件。
下面是代码:
simplemde.codemirror.on('paste',(editor,e)=>{//粘贴图片的触发函数 if(!(e.clipboardData&&e.clipboardData.items)){ //弹窗说明,此浏览器不支持此操作 return } try{ letdataList=e.clipboardData.items if(dataList[0].kind==='file'&&dataList[0].getAsFile().type.indexOf('image')!==-1){ this.uploadImagesFile(simplemde.codemirror,[dataList[0].getAsFile()]) } }catch(e){ //弹窗说明,只能粘贴图片 } })之所以这里写上try...catch方法,是因为如果你粘贴的时候,如果是一个文件,items将是空的,而在下面的if循环里,使用dataList[0].kind。也就是e.clipboardData.items[0].kind。当item为空时,还去访问一个不存的kind属性时,就会报错了。所以这里需要使用try...catch方法进行判断。
dataList[0].getAsFile().type.indexOf('image')!==-1这个句话是判断,粘贴的东西确认是图片,而不是其他东西。
if里的上传图片,不一样的地方是[dataList[0].getAsFile()],因为为了统一格式,方便uploadImagesFile函数进行处理,我加上了[],使之成为数组。dataList[0].getAsFile()就是获取文件实例了。
上传
上传就有一点麻烦了:
uploadImagesFile(simplemde,files){ //把每个文件实例使用FormData进行包装一下,然后返回一个数组 letparams=files.map(file=>{ letparam=newFormData() param.append('file',file,file.name) returnparam }) letmakeRequest=params=>{ returnthis.$http.post('/Api/upload',params) } letrequests=params.map(makeRequest) this.$http.spread=callback=>{ returnarr=>{ returncallback.apply(null,arr) } } //服务端返回的格式是{state:Boolean,data:String} //state为false时,data就是返回的错误信息 //state为true时,data是图片上传后url地址,这个地址是针对网站的绝对路径。如下: ///static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png Promise.all(requests) .then(this.$http.spread((...resps)=>{ for(leti=0;i因为我是把axiox封装成vue插件来使用,这样会导致,this.$http是实例化后的,而不是他本身。axios维护者说的解决方案是,重新引入axios包,来使用。但是我觉得没有必要。axios.all内部是Promise.all。axios.spread实现代码比较少,就直接拿过来,重新赋值给axios就好了
所以上面有段代码是
Promise.all(requests) .then(this.$http.spread((...resps)=>{ //code })
把这段代码翻译一下就是
axios.all(requests) .then(axios.spread((...resps)=>{ //code })关于这个问题,请看下官方的解释:axios-all-is-not-a-function-inside-vue-component。也可以看下axios的代码:axios.js#L45-L48
这个问题,暂时就不深究了,我们回到刚刚的话题上。
上面我说到当state为true时,data是文件相对于网站的绝对路径,如:/static/upload/2cfd6a50-3d30-11e8-b351-0d25ce9162a3.png
如果我们需要进行拼接一下,所以就有了这段代码进行拼接。最后的两行是获取指的获取之前的内容,然后在追加url地址。