(一)首先实现html、css布局
代码:
<template><div class="image-uploader"><img :src="uploadIcon" class="icon"/> <p>Drag your images here</p><p>OR</p><div class="real-btn">SELECT A FiLE<input type="file" @change="inputChange" class="hide-btn"></div></div>
</template><script>
import uploadIcon from '../assets/upload.png'
export default {name: 'imageUpload',data () {return {uploadIcon: uploadIcon}}
}
</script><style scoped>
.image-uploader {margin: 20px;border: 3px dashed #fff;background: #2196F3;padding: 10px;text-align: center;color: #fff;border-radius: 10px;font-weight: 500;
}
.real-btn {cursor: pointer;position: relative;padding: 10px 20px;background: #fff;border-radius: 10px;color: #2196F3;
}
.hide-btn {width: 100%;height: 100%;top: 0;left: 0;position: absolute;opacity: 0;
}
.icon {width: 50px;height: 50px;
}
</style>
到这一步,样式就算是写完了
注意!!
由于只有 input 的 type 等于 file 的时候才能调起电脑文件夹,所以 SELECT A FiLE 这个按钮是与 <input type="file"> 这个元素重叠的,点击 SELECT A FiLE 这个按钮其实是 点击 <input type="file">,然后将 input 这个标签隐藏
最后通过 opacity: 0;将真实的input 隐藏
(二)实现拖拽上传
(1)实现拖拽的效果需要 使用两个方法:dragEnter、dragLeave
这时候又遇到坑了,我在最外层父元素上绑定 dragEnter、dragLeave
当将图片在父元素区域内拖动时,会不断触发 dragEnter、dragLeave,而不是 只有进来和出去触发,经过了解解释如下:
它是每进入一个新元素的同时就退出上一个元素,当退出父元素时,就不会有下一个进入的元素了, 所以进入的元素与退出的的元素相等,所以我们需要记录上次移入的节点
// 拖拽进入上传文件区dragEnter (e) {this.currentTarget = e.target // 当前进入的元素this.isDragging = true},// 拖拽离开上传文件区dragLeave (e) {if (e.target === this.currentTarget) {this.isDragging = false}}
(2)拖拽进去松手之后
触发 drop 事件
drop (e) {this.isDragging = false// 解决 e.dataTransfer.files 经常为空的问题var file = []file.forEach.call(e.dataTransfer.files, function (item) {file.push(item)}, false)},
拖进去的文件 在 e.dataTransfer.files 这个字段里,我们需要转成数组
(3)通过 input 上传
<input type="file" @change="inputChange" class="hide-btn">
async inputChange (e) {const base64 = await this.getBase64(e.target.files[0])this.getImageUrl(base64, e.target.files[0].name)},
上传结果图:
完整代码:
<template><div class="image-uploader"@dragenter="dragEnter"@dragleave="dragLeave"@drop.prevent="drop"@dragover.prevent:class="[ isDragging ? 'isDraging' : '' ]"> <img :src="uploadIcon" class="icon"/><p>Drag your images here</p><p>OR</p><divclass="real-btn":class="[ isDragging ? 'isDraging-btn' : '' ]">SELECT A FILE<input type="file" @change="inputChange" class="hide-btn"></div><!-- 上传图片展示列表 --><div v-show="files.length > 0" class="file-list"><divv-for="(fileItem) in files":key="fileItem.index"class="list-item"><img :src="fileItem.url" alt=""><div>{{ fileItem.name }}</div></div></div></div>
</template><script>
import uploadIcon from '../assets/upload.png'
import axios from 'axios'
export default {name: 'imageUpload',data () {return {uploadIcon: uploadIcon,isDragging: false, // 是否正在拖拽currentTarget: null, // 上一个dragEnter的元素files: [] // 文件上传列表}},methods: {// 拖拽进入上传文件区dragEnter (e) {this.currentTarget = e.targetthis.isDragging = true},// 拖拽离开上传文件区dragLeave (e) {if (e.target === this.currentTarget) {this.isDragging = false}},// drop 是指将文件拖入父元素 松手触发的操作// 必须阻止 dragover 的默认行为 不然 drop 不生效// drop 也要阻止默认行为 不然拖进去后 浏览器自动打开该文件drop (e) {this.isDragging = false// 解决 e.dataTransfer.files 经常为空的问题// 把累数组转化成数组var file = []file.forEach.call(e.dataTransfer.files, function (item) {file.push(item)}, false)// Array.from(e.dataTransfer.files).forEach((item) => { file.push(item) })// 遍历对象 存入文件数组file.forEach(async (item) => {const base64 = await this.getBase64(item)this.getImageUrl(base64, item.name)})},async inputChange (e) {const base64 = await this.getBase64(e.target.files[0])this.getImageUrl(base64, e.target.files[0].name)},// 图片上传cdn 生成链接getImageUrl (base64, fileName) {const url = 'https://bird.ioliu.cn/v1?url=http://hn216.api.yesapi.cn'axios.post(url, {file: base64,s: 'App.CDN.UploadImgByBase64',app_key: '228290AC6E185D2121CD5878EDC4D010',file_name: fileName}).then((res) => {this.files.push({url: res.url,name: fileName,index: Math.random()})}).catch(() => {// 使用默认的const res = {url: 'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=191936283,2923048863&fm=26&gp=0.jpg'}this.files.push({url: res.url,name: fileName,index: Math.random()})})},// 图片转化成base64 便于上传cdn转成链接getBase64 (file) {return new Promise((resolve, reject) => {let reader = new FileReader()if (file) {// 将文件以Data URL形式读入页面let imgUrlBase64 = reader.readAsDataURL(file)reader.onload = function (e) {resolve(reader.result)}}})}}
}
</script><style scoped>
.image-uploader {margin: 20px;border: 3px dashed #fff;background: #2196F3;padding: 10px;text-align: center;color: #fff;border-radius: 10px;font-weight: 500;
}
.image-uploader.isDraging {background: #fff;border: 3px dashed #2196F3;color: #2196F3;
}
.real-btn {cursor: pointer;position: relative;padding: 10px 20px;background: #fff;border-radius: 10px;color: #2196F3;
}
.real-btn.isDraging-btn {background: #2196F3;color: #fff;
}
.hide-btn {width: 100%;height: 100%;top: 0;left: 0;position: absolute;opacity: 0;
}
.icon {width: 50px;height: 50px;
}
.file-list {display: flex;flex-wrap: wrap;margin-top: 10px;
}
.file-list img {width: 100%;
}
.list-item {width: 50%;
}
</style>
(1)可以判断一下上传的文件类型,通过type字段,然后做相应的处理
比如类型不正确,然后进行报错提示
(2)还可以拿到上传文件的大小,通过size字段