Upload 拖拽上传组件
本身UI组件
el-upload
都是支持拖拽上传的,但是由于我们的上传文件增加了几个动画帧大致是以下:
- 默认是一个闭合的文件夹 (默认状态)
- 一个文件夹张开的动画 (表示鼠标放上去、或者拖拽文件到容器里了);
- 一个文件夹张开的图片(表示要上传文件)
- 一个文件夹投递的动画 (表示上传中);
- 一个是文件夹上传完成闭合的动画(表示上传完了);
以上几个动画帧组成一整个文件上传的动作。
现有的组件指定支持不了了,因为没有api暴漏上传的生命周期;
改造
也就是拿源码经行改造,支持点击上传、拖拽上传就好了。
定义了拖拽的生命周期:
js
export const DRAG_TYPE = {
DEFAULT: 'default', // 默认
BEFORE_DRAGOVER: 'before-dragover', // 拖拽/鼠标滑过前动画
DRAGOVER: 'dragover', // 拖拽/鼠标滑过
UNLOADING: 'uploading', // 上传中
BEFORE_OVER: 'before-over', // 结束前
};
其实就是再某个阶段展示某个生命周期的图片资源。
拖拽碰到里面的元素会重置拖拽事件
当拖拽的文件碰到upload里面的dom 就会重置拖拽事件
(drop、dragover、dragleave)
,导致界面发生闪动, 我再el-upload
里面也测试了 也有同样的问题。 只不过他没有我的那些状态动画帧展示效果没有那么明显。
js
@drop.prevent="onDrop" // drop 是拖拽的文件到松手的范围 松手了的事件
@dragover.prevent="onOpen" // dragover 是拖拽的文件到松手的范围的事件
@dragleave.prevent="onClose" // dragleave 拖拽文件离开事件
解决方法就是把拖拽的容器定位 让他的层级更高,里面不要嵌套dom;
原本是:
vue
<template>
<div
class="upload-dragger-container"
:class="{
'is-active': currentUploadType !== DRAG_TYPE.DEFAULT,
}"
@drop.prevent="onDrop"
@dragover.prevent="onOpen"
@dragleave.prevent="onClose"
@click="onClick"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
>
<input
ref="input"
class="upload-dragger__input"
type="file"
:multiple="multiple"
:accept="accept"
@change="handleChange"
>
<slot></slot>
</div>
</template>
经过改造:
vue
<template>
<div
class="upload-dragger-container"
:class="{
'is-active': currentUploadType !== DRAG_TYPE.DEFAULT,
}"
>
<!-- 不让拖拽放置容器里面存在dom -->
<div
class="upload-dragger__area"
@drop.prevent="onDrop"
@dragover.prevent="onOpen"
@dragleave.prevent="onClose"
@click="onClick"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
/>
<input
ref="input"
class="upload-dragger__input"
type="file"
:multiple="multiple"
:accept="accept"
@change="handleChange"
>
<slot></slot>
</div>
</template>
完整代码
完整代码:
vue
<template>
<div
class="upload-dragger-container"
:class="{
'is-active': currentUploadType !== DRAG_TYPE.DEFAULT,
}"
>
<div
class="upload-dragger__area"
@drop.prevent="onDrop"
@dragover.prevent="onOpen"
@dragleave.prevent="onClose"
@click="onClick"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
/>
<input
ref="input"
class="upload-dragger__input"
type="file"
:multiple="multiple"
:accept="accept"
@change="handleChange"
>
<slot></slot>
</div>
</template>
<script>
import { DRAG_TYPE, handlerAccept } from './options';
export default {
name: 'UploadDragger',
props: {
accept: {
type: String,
default: '',
required: false,
},
multiple: {
type: Boolean,
default: false,
required: false,
},
onUploadCallback: {
type: Function,
required: true,
default: () => {},
},
},
data() {
return {
currentUploadType: DRAG_TYPE.DEFAULT,
DRAG_TYPE,
dragover: false,
openTimer: null,
colseTimer: null,
};
},
beforeDestroy() {
this.clearTimeoutAll();
},
methods: {
onDrop(e) {
if (this.currentUploadType !== DRAG_TYPE.UNLOADING) {
const dropFiles = Array.from(e.dataTransfer.files);
const files = this.accept ? handlerAccept(dropFiles, this.accept) : dropFiles;
this.onUploadFile(files);
}
},
async onUploadFile(files) {
this.clearTimeoutAll();
this.currentUploadType = DRAG_TYPE.UNLOADING;
this.$emit('current-type', DRAG_TYPE.UNLOADING);
await this.onUploadCallback(files);
console.log('upload complate!!')
this.currentUploadType = DRAG_TYPE.BEFORE_OVER;
this.$emit('current-type', DRAG_TYPE.BEFORE_OVER);
this.onClose();
},
onClick() {
if (this.currentUploadType !== DRAG_TYPE.UNLOADING) {
this.$refs.input.value = null;
this.$refs.input.click();
}
},
handleChange(ev) {
const { files } = ev.target;
if (!files) return;
this.onUploadFile(Array.from(files));
},
onMouseleave() {
this.onClose();
},
onMouseover() {
this.onOpen();
},
onClose() {
if (this.currentUploadType !== DRAG_TYPE.UNLOADING) {
this.clearTimeoutAll();
this.currentUploadType = DRAG_TYPE.BEFORE_OVER;
this.$emit('current-type', DRAG_TYPE.BEFORE_OVER);
this.colseTimer = setTimeout(() => {
this.currentUploadType = DRAG_TYPE.DEFAULT;
this.$emit('current-type', DRAG_TYPE.DEFAULT);
this.dragover = false;
}, 150);
}
},
onOpen() {
if (!this.dragover && this.currentUploadType !== DRAG_TYPE.UNLOADING) {
this.clearTimeoutAll();
this.currentUploadType = DRAG_TYPE.BEFORE_DRAGOVER;
this.$emit('current-type', DRAG_TYPE.BEFORE_DRAGOVER);
this.dragover = true;
this.openTimer = setTimeout(() => {
this.currentUploadType = DRAG_TYPE.DRAGOVER;
this.$emit('current-type', DRAG_TYPE.DRAGOVER);
}, 150);
}
},
clearTimeoutAll() {
clearTimeout(this.colseTimer);
clearTimeout(this.openTimer);
},
},
};
</script>
<style lang="less" scoped>
.upload-dragger-container {
position: relative;
cursor: pointer;
width: 100%;
height: 100%;
border: 1px dotted #3F4664;
border-radius: 3px;
.upload-dragger__input {
display: none;
}
.upload-dragger__area {
z-index: 9;
position: absolute;
width: 100%;
height: 100%;
}
&.is-active {
background-color: #1B2D48;
border: 1px dotted #1B2D48;
}
}
</style>
- options.js
js
// options.js
export const DRAG_TYPE = {
DEFAULT: 'default', // 默认
BEFORE_DRAGOVER: 'before-dragover', // 拖拽/鼠标滑过前动画
DRAGOVER: 'dragover', // 拖拽/鼠标滑过
UNLOADING: 'uploading', // 上传中
BEFORE_OVER: 'before-over', // 结束前
};
export const handlerAccept = (files = [], accept) => {
return files.filter((file) => {
const { type, name } = file;
const extension = name.indexOf('.') > -1
? `.${name.split('.').pop()}`
: '';
const baseType = type.replace(/\/.*$/, '');
return accept.split(',')
.map(type => type.trim())
.filter(type => type)
.some((acceptedType) => {
if (/\..+$/.test(acceptedType)) {
return extension === acceptedType;
}
if (/\/\*$/.test(acceptedType)) {
return baseType === acceptedType.replace(/\/\*$/, '');
}
if (/^[^\/]+\/[^\/]+$/.test(acceptedType)) {
return type === acceptedType;
}
return false;
});
});
};
使用方法
使用方法介绍
vue
<template>
<upload-dragger
accept=".xlsx"
:on-upload-callback="onUpload"
@current-type="onCurrentType"
>
<!-- 这个 upload-type 就是需要展示对应的动画帧的type-->
<upload-type :upload-type="currentUploadType"/>
<div class="upload-dragger__text">
<div>仅支持上传.xlsx格式文件,文件大小:10M以内</div>
<el-button
type="primary"
size="small"
>
请选择文件
</el-button>
</div>
</upload-dragger>
</template>
<script>
import UploadDragger from '@/components/upload-dragger/index.vue';
import { DRAG_TYPE } from '@/components/upload-dragger/options';
import UploadType from '@/components/upload-dragger/upload-type.vue';
export default {
name: 'App',
components: {
UploadDragger,
UploadType,
},
data() {
return {
currentUploadType: DRAG_TYPE.DEFAULT,
};
},
methods: {
onCurrentType(val) {
this.currentUploadType = val;
},
async onUpload(files) {
const data = new FormData();
if (files?.length) {
files.forEach((element) => {
data.append('file', element);
});
} else {
this.$message.error('请确认文件格式!');
return;
}
// 调用上传接口
this.$message.success('上传成功');
},
},
};
</script>
- upload-type 组件代码也奉上 图片资源就不提供了
vue
<template>
<div class="upload-type-container">
<img
v-show="uploadType === DRAG_TYPE.DEFAULT"
src="./images/1.png"
>
<img
v-show="uploadType === DRAG_TYPE.BEFORE_DRAGOVER"
src="./images/2.png"
>
<img
v-show="uploadType === DRAG_TYPE.DRAGOVER"
src="./images/3.png"
>
<img
v-show="uploadType === DRAG_TYPE.UNLOADING"
src="./images/4.png"
>
<img
v-show="uploadType === DRAG_TYPE.BEFORE_OVER"
src="./images/5.png"
>
</div>
</template>
<script>
import { DRAG_TYPE } from './options';
export default {
name: 'UploadType',
props: {
uploadType: {
type: String,
default: DRAG_TYPE.DEFAULT,
required: true,
},
},
data() {
return {
DRAG_TYPE,
};
},
};
</script>
<style lang="less" scoped>
.upload-type-container {
position: absolute;
top: 0;
left: calc(50% - 172px);
width: 320px;
height: 240px;
}
</style>