实现一个File-Reader组件用来读取本地资源。
概述: 在用户手动上传一些资源的时候,需要分为两步,第一步是将其从本地读取出来,得到一个file
对象,然后再上传至服务器。该组件用于第一步,然后可通过后续进一步封装程Upload组件。
- 添加拖拽上传的功能;
- 本地文件的批量读取,以及相关预览;
- 相关浏览器的默认行为的
preventDefault
; - 相关文件的基本校验。
1. 实例
代码
<!-- 基础用法 -->
<fat-filereader
accept=".png, .jpg"
:size="500 * 1024"
@success="event => readHandler(event, 'file')"
@error="errorHandler"
/>
<!-- 自定义上传区域 -->
<fat-filereader @success="event => readHandler(event, 'otherFile')">
<div slot="clickarea" class="upload-area">
<fat-icon class="icon" name="cloud_upload" size="24"/>
</div>
</fat-filereader>
<!-- 拖拽上传 -->
<fat-filereader dragable @success="event => readHandler(event, 'anotherFile')">
<div slot="clickarea" class="upload-area">
<fat-icon class="icon" name="cloud_upload" size="24"/>
</div>
<span v-if="anotherFile.name">已上传:{{ anotherFile.name }}</span>
</fat-filereader>
<!-- 上传多个文件 -->
<fat-filereader multiple :limit="5" @success="multipleHandler" @error="errorHandler"/>
复制代码
实例地址:File-Reader 实例
代码地址:Github UI-Library
2. 原理
该组件的实现是基于原生的<input type="file" />
,再通过添加拖拽功能来以达到上图效果。
组件的基本结构如下,主要包含两个部分:
- 其一是
<slot name="clickarea"></slot>
自定义的点击区域; - 其二是原生的
< input type="file" />
用来完成文件上传工作。
<div
:class="['file-reader', { 'is-disabled': disabled }]"
>
<div
class="click-area"
@click.stop="eventHandler('readFile')"
@dragenter="prevent"
@dragover="prevent"
@drop="event => dragable && eventHandler('dropReadFile', event)"
>
<slot name="clickarea">
<fat-button :disabled="disabled" type="success">上传</fat-button>
</slot>
</div>
<slot></slot>
<input
ref="input"
type="file"
class="is-hide"
v-bind="$attrs"
@change="event => eventHandler('change', event)"
:disabled="disabled"
>
</div>
</template>
复制代码
在<slot name="clickarea"></slot>
外层包裹div
标签,用于监听该区域的点击事件。当它触发时,相关处理函数为
const handler = {
readFile: () => {
// fix change again
this.$refs.input.value = "";
this.$refs.input.click();
},
...
};
复制代码
先重置this.$refs.input.value
,不然会出现无法再上传的问题。然后,触发原生<input type="file" />
的点击事件,此时页面上会弹出原生的上传框。
监听该标签的change
事件, @change="event => eventHandler('change', event)
,如果发生上传,会触发相关处理函数。
eventHandler(type, event = {}) {
const handler = {
...
// read files
change: event => (this.sourceFiles = event.target.files)
};
handler[type] && handler[type](event);
}
复制代码
从event
对象中,读取选中待上传的文件对象
包含着name
文件名,size
大小,type
类型等属性。
由于部分文件需要进行预览,例如img、svg等,所以需要生成对应的URL,基本方法为:
window.URL.createObjectURL
:该方法会创建一个 DOMString,其中包含一个表示参数中给出的对象的URL;new FileReader
:该对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,再通过readAsDataURL
读取指定的 Blob 或 File 对象。
首先判断当前是否支持window.URL && window.URL.createObjectURL
,如果支持,只需将已上传的targetFiles
进行处理
Array.prototype.map.call(value, file => ({
// file => url
url: window.URL.createObjectURL(file),
file
}));
复制代码
如果不支持该方法,则需要利用File Reader
的readAsDataURL
,由于该方法是异步的,所以将其封装为Promise形式
const getReader = file => {
return new Promise(function(resolve, reject) {
const fileReader = new FileReader();
// 读取相关文件
fileReader.readAsDataURL(file);
// fileReader onload 时,返回其结果
fileReader.onload = () => resolve(fileReader.result);
fileReader.onerror = () => fileReader.abort();
});
};
const readers = Array.prototype.map.call(value, file => getReader(file));
复制代码
再利用Promise.all
统一进行处理,结合已上传的targetFiles
生成返回结果。
Promise.all(readers).then(results => {
this.targetFiles = results.map((url, i) => ({
url,
file: value[i]
}));
});
复制代码
以上完成基本的上传、预览功能。然后添加拖拽上传功能,在clickArea监听dragenter
、dragover
、drop
事件。
<div
class="click-area"
@click.stop="eventHandler('readFile')"
@dragenter="prevent"
@dragover="prevent"
@drop="event => dragable && eventHandler('dropReadFile', event)"
>
<slot name="clickarea">
<fat-button :disabled="disabled" type="success">上传</fat-button>
</slot>
</div>
复制代码
相关处理函数为
eventHandler(type, event = {}) {
const handler = {
...
dropReadFile: event => {
event.preventDefault();
this.sourceFiles = event.dataTransfer.files;
}
};
handler[type] && handler[type](event);
},
prevent(event) {
event.preventDefault();
}
复制代码
利用event.dataTransfer.files
获取相关上传文件,再通过event.preventDefault
阻止游览器的一些默认行为,例如直接预览该文件等。获取到sourceFiles
之后的操作等同于之前点击上传。
读取完成之后需要对文件进行校验,主要有上传文件的大小以及数量,相关代码如下
targetFiles(value) {
const { size, limit } = this;
if (value.length > limit) {
this.$emit("error", {
msg: "the quantity of files is too large its number cannot exceed"
});
} else {
if (
value.some(item => {
const {
file: { size: fileSize }
} = item;
return size && fileSize > size;
})
) {
this.$emit("error", {
msg: "file is too large its size cannot exceed"
});
} else {
this.$emit("success", value);
}
}
}
}
复制代码
3. 使用
相关的原生<input type="file" />
的accept
,multiple
等属性,利用v-bind=$attrs
传递给原生的<input type="file" />
。
之后, 可以结合相关上传方法将其封装程Upload组件,以Axios为例
uploadFile (fileData) {
const config = {
// 依据当前环境配置
baseURL: ...,
headers: { 'Content-type': 'multipart/form-data' }
}
let data = new FormData();
data.append('file', fileData);
data.append('name', fileData.name);
return Axios.post('upload url', data, config)
}
复制代码
4. 总结
封装一个File-reader组件,简化其内部逻辑,方便后续拓展。
往期文章:
- 从零实现Vue的组件库(零)-基本结构以及构建工具
- 从零实现Vue的组件库(一)-Toast
- 从零实现Vue的组件库(二)-Slider实现
- 从零实现Vue的组件库(三)- Tabs实现
- 从零实现Vue的组件库(四)- File-Reader实现
原创声明: 该文章为原创文章,转载请注明出处。