基于Vue + Axios封装上传组件,并支持拖拽文件

一、Input File

使用  type='file'  的 <input> 元素可以选择文件,基于此我们可以封装自定义的上传组件,<input type="file"> 可以接收四个附加属性:

accept: 字符串,允许选择的文件类型,如: ".jpg,.jpeg,.png,.gif" 

multiple: 布尔值,是否允许多选

capture: 字符串,可以调用设备默认的相机或录音机(移动端有效)

<input type="file" accept="image/*" capture="camera">

files: 已选择文件对象列表,通过 htmlInputElement.files 获取和赋值

但 <input type="file"> 包含按钮和文件列表两部分,如果给它添加宽高和背景色...

<input type="file" class="upload-inner">

<style type="text/css">

.upload-inner {

width: 200px;

height: 80px;

background-color: #e3e3e3;

}

</style>

基于Vue + Axios封装上传组件,并支持拖拽文件

这个标签的样式基本没救了...

所以为了能用上更美观的上传控件,通常会选择隐藏真正的文件上传控件

然后用其他标签来代替上传按钮,在点击事件中触发上传事件


二、控件设计

基于Vue + Axios封装上传组件,并支持拖拽文件

这是一个常见的上传控件样式,它的 html 可以这么设计:

<!-- wrapper 组件 -->

<template>

<div class="upload-wrapper">

<div class="upload-inputs">

<div class="upload-bg"></div>

<upload

:accept="uploadConfig.accept"

></upload>

<div class="tip">点击上传按钮,或拖拽文件到框内上传</div>

<div class="tiny-tip">请选择不大于 10m 的文件</div>

</div>

</div>

</template>

上面是外层的 <wrapper /> 组件,关于上传控件的具体逻辑可以单独封装到 <upload /> 组件中

<!-- upload 组件 -->

<template>

<div class="file-selector">

<button class="selector-btn" @click="handleUpClick">

<slot>选择文件</slot>

</button>

<input

ref="input"

class="file-selector-input"

type="file"

:multiple="multiple"

:accept="accept"

@change="handleFiles"

/>

</div>

</template>

在 <upload /> 组件中,需要将 <input /> 控件隐藏掉,并用 <button> 模拟一个上传按钮样式

<style lang="scss">

.file-selector {

margin-bottom: 16px;

.selector-btn {

background-color: #2976e6;

color: #fff;

padding: 6px 18px;

border-radius: 4px;

cursor: pointer;

&:hover {

background-color: rgba($color: #2976e6, $alpha: 0.8);

transition: background 180ms;

}

}

&-input {

display: none;

}

}

</style>

然后通过 <button> 的上传事件来触发 <input> 的上传事件 (上面的 handleUpClick)

并监听 <input> 的 change 事件,处理所选文件 (上面的 handleFiles)

// 调用上传功能

handleUpClick() {

this.uploadFinished // 维护一个上传状态,上传过程中禁用上传按钮

? this.$refs.input.click()

: console.warn('请等待当前文件全部上传完成');

},

handleFiles(e) {

const files = e?.target?.files;

this.readFiles(files);

},

 

三、处理文件

上面的 hanldeFiles 调用了 readFiles 方法,这是处理文件的主要逻辑

// 上传之前将文件处理为对象

readFiles(files) {

if (!files || files.length <= 0) {

return;

}

for (const file of files) {

const url = window.URL.createObjectURL(file);

const obj = {

title: file.name.replace(/(\.[^\.]*$)|[\_]/gi, ''), // 去掉文件后缀

url,

file,

fileType: file.fileType,

status: 0, // 状态 -> 0 等待中,1 完成, 2 正在上传,3 上传失败

percent: 0, // 上传进度

};

// 提前在 data 中定义 list,用来保存需要上传的文件

this.list.push(obj);

}

// 在 data 中定义 startIndex 初始值为 0,上传完成后更新,用于追加上传文件

this.startUpload(this.startIndex);

},

然后是上传文件

// 上传前需要校验文件

checkFile(index) {

const file = this.list[index];

// 如果文件不存在,即全部文件上传完成

if (!file) {

this.uploadFinished = true;
   // 上传完成,向父组件抛出 success 事件

this.$emit('success', this.list);

// 清空上传控件中的值,保证 change 事件能正常触发

this.$refs.input.value = null;
this.startIndex = index > 1 ? index - 1 : 0;

return false;

}

// 校验是否已上传

if (`${file.status}` === "1") {

this.startUpload(++index);

return false;

}

// 校验文件大小

if (this.maxSize && file.file && file.file.size >= this.maxSize) {

this.startUpload(++index);

return false;

}

return true;

},


// 上传单个文件

startUpload(index) {

if (!this.checkFile(index)) { return; }

// 开始上传,维护上传状态

this.uploadFinished = false;

const file = this.list[index].file;

const fileObj = this.list[index];

// 创建 formData 用于提交

const data = new FormData();

data.append('userfile', file);

axios({

url: this.url, // 上传接口,由 props 传入

method: 'post',

data,

withCredentials: true,

cancelToken: this.source.token, // 用于取消接口请求

// 进度条

onUploadProgress: e => {

if (fileObj.status === 1) { return; } // 已上传

// 限定最大值为 99%

const p = parseInt((e.loaded / e.total) * 99);

if (e.total) {

fileObj.status = 2; // 正在上传

fileObj.percent = p; // 更新上传进度

} else {

fileObj.status = 3; // 上传失败

}

},

})

.then(response => {

if (`${response.code}` === "200") {

fileObj.status = 1;

fileObj.percent = 100;

} else {

fileObj.status = 3;

}

})

.catch(e => {

fileObj.status = 3;

})

.finally(e => {

this.startUpload(++index);

});

// 上传完成

},

上传的时候用到了 axios 的 onUploadProgress 来维护上传进度和当前文件的上传状态,用于开发上传中的样式

还使用了 cancelToken 来取消上传,不过取消上传还需要提前在 data 中定义一个 source 变量

source: axios.CancelToken.source(), // axios 取消请求

所以最终 <upload /> 组件中的 data 为:

data: () => ({

list: [], // 已选择的文件对象

uploadFinished: true, // 上传状态

startIndex: 0, // 开始上传的下标,用于追加文件

source: axios.CancelToken.source(), // axios 取消请求

}),

最后在组件中添加一个重置方法

reset() { // 重置

this.list = [];

this.source.cancel();

this.startIndex = 0;

this.uploadFinished = true;

this.$refs.input && (this.$refs.input.value = null);

},

上传组件的核心逻辑就完成了

 

四、拖拽上传

除了基本的点击按钮上传之外,还需要支持拖拽上传,这部分逻辑可以在 <wrapper /> 组件中完成

先回顾一下组件的 HTML 结构

<!-- wrapper 组件 -->

<template>

<div class="upload-wrapper">

<div

:class="['upload-inputs', dragging ? 'dragging' : '']"

ref="pickerArea"

>

<div class="upload-bg"></div>

<upload
     ref="uploadBtn"

:accept="uploadConfig.accept"

></upload>

<div class="tip">点击上传按钮,或拖拽文件到框内上传</div>

<div class="tiny-tip">请选择不大于 10m 的文件</div>

</div>

</div>

</template>

上面增加了一个 dragging 变量,用来控制拖拽文件时的样式,需要在 data 中定义

当组件初始化的时候,需要对 ref="pickerArea" 绑定拖拽事件

在拖拽过程中,通过维护 dragging 状态来更新热区样式,当拖拽结束后,调用 <upload /> 组件的上传功能

bindEvents() {

const dropbox = this.$refs.pickerArea;

// 防止重复绑定事件,需要在 data 中初始化 bindDrop 为 false

if (!dropbox || this.bindDrop) { return }

// 绑定拖拽事件,在组件销毁时解绑

dropbox.addEventListener("drop", this.handleDrop, false);

dropbox.addEventListener("dragleave", this.handleDragLeave);

dropbox.addEventListener("dragover", this.handleDragOver);

this.bindDrop = true;

},

// 拖拽到上传区域

handleDragOver(e) {

e.stopPropagation();

e.preventDefault();

this.dragging = true;

},

// 离开上传区域

handleDragLeave(e) {

e.stopPropagation();

e.preventDefault();

this.dragging = false;

},

// 拖拽结束

handleDrop(e) {

e.stopPropagation();

e.preventDefault();

this.dragging = false;

const files = e.dataTransfer.files;
 // 调用 <upload/> 组件的上传功能

this.$refs.uploadBtn && this.$refs.uploadBtn.readFiles(files);

},

上面通过 addEventLister 添加了事件监听,所以需要在 beforeDestroy 生命周期中注销

beforeDestroy() {

// 组件销毁前解绑拖拽事件

try {

const dropbox = this.$refs.pickerArea;

dropbox.removeEventListener("drop", this.handleDrop);

dropbox.removeEventListener("dragleave", this.handleDragLeave);

dropbox.removeEventListener("dragover", this.handleDragOver);

this.bindDrop = false;

} catch (e) {}

},

到这里一个上传组件就完成了,不过只有上传功能,在样式和交互上还有未尽之处

比如拖拽的时候热区的样式,上传过程中拖拽按钮的禁用样式等。不过这些都维护了相应状态,通过这些状态来添加样式即可

真正没有涉及到的是已选中的文件样式,包括上传的进度条、文件操作等等

但在上面 <upload /> 组件中抛出了 this.list,其中的每个元素也维护了上传状态和上传进度,可以基于此来开发对应的交互

来自:https://www.cnblogs.com/wisewrong/archive/2020/09/01/13591315.html

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9612

以上是 基于Vue + Axios封装上传组件,并支持拖拽文件 的全部内容, 来源链接: utcz.com/a/63128.html

回到顶部