背景:随着业务的复杂性提高,前端上传文件变得越来越常见,那么深入了解上传这一行为很有必要;
一、input标签 type="file"
1.1 基本用法:
html
<input type="file" />
思考🤔,我们是否可以使用 v-model来获取选择的文件呢,hhh, 答案是不行;(注意下面的写法是不允许的)
html
// v-model cannot be used on file inputs since they are read-only. Use a v-on:change listener instead.vue(59)
<input v-model="filePath" type="file" />
在Vue中,使用v-model
指令可以将表单输入和应用程序状态进行双向绑定。然而,<input type="file" />
元素是一个特殊情况,因为它的值是只读的,无法直接通过v-model
来绑定。如果你想要获取文件路径,你可以使用@change
事件监听器来捕获文件选择的变化,然后在事件处理程序中更新Vue实例的数据。
js
<input type="file" @change="handleFileChange" />
const handleFileChange = (event) => {
console.log(event.target?.files); // FileList {0: File, length: 1}
console.log(event.target?.value); // 'C:\\fakepath\\test.pdf'
};
备注: 为了阻止恶意软件猜测文件路径,该值的字符串表示总是以
C:\fakepath
为前缀的文件名,而并不是文件的真实路径
1.2 其他参数
accept
定义input能选择的文件类型,例如:accept=".pdf,.zip,.ofd"
, 属性接受的是一个字符串(唯一文件类型说明符号)可以是 1、一个以英文句号(".")开头的合法的不区分大小写的文件名扩展名。例如:.jpg
、.pdf
或 .doc
; 2、一个不带扩展名的 MIME 类型字符串
multiple
布尔类型,用于表示是否多选
...
1.3 上传文件夹
input 中还存在一个不标准的属性: webkitdirectory ,表示存在兼容性,不过大部分都兼容
js
<input
ref="uploadDirRef"
type="file"
id="file"
accept=".pdf,.ofd"
:webkitdirectory="true"
:mozdirectory="true"
:odirectory="true"
@change="handleDirChange"
/>
const handleDirChange = (event) => {
console.log(event.target?.files); // FileList {0: File, length: 1}
};
在选择文件目录后,该目录及其整个内容层次结构将包含在所选项目集内。可以使用 webkitEntries
(en-US) 属性获取选定的文件系统条目,意思是:文件夹中还有文件夹的多级结构,也能成功选到其中的文件;
使用这种方式存在的问题:
- 会显示浏览器的上传提示弹窗,(比较丑)
- 无法判断空文件夹,上传了空文件夹不会触发change事件,所以响应不好
二、upload组件
基础用法
html
<template>
<el-upload
v-model:file-list="fileList"
class="upload-demo"
action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
multiple
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:limit="3"
:on-exceed="handleExceed"
>
<el-button type="primary">Click to upload</el-button>
<template #tip>
<div class="el-upload__tip">
jpg/png files with a size less than 500KB.
</div>
</template>
</el-upload>
</template>
拖拽上传
upload组件里面支持拖拽上传,源码 封装了一个 upload-dragger
子组件,主要利用了DragEvent.dataTransfer
这个只读属性;在进行拖放操作时,传输的数据。核心代码如下:
js
<template>
<div
:class="[ns.b('dragger'), ns.is('dragover', dragover)]"
@drop.prevent="onDrop"
@dragover.prevent="onDragover"
@dragleave.prevent="dragover = false"
>
<slot />
</div>
</template>
const onDrop = (e: DragEvent) => {
if (disabled.value) return
dragover.value = false
e.stopPropagation()
const files = Array.from(e.dataTransfer!.files)
emit('file', files)
}
源码解析
看了一下源码,主要核心代码还是使用的 input标签 type="file",主要加上了一些其他的样式,还有文件列表;
html
<input
ref="inputRef"
:class="ns.e('input')"
:name="name"
:multiple="multiple"
:accept="accept"
type="file"
@change="handleChange"
@click.stop
/>
三、useFileDialog
在vueUse库中,存在一个处理文件上传的 hooks: useFileDialog
js
<script setup lang="ts">
import { useFileDialog } from "@vueuse/core";
const { files, open, reset, onChange } = useFileDialog({
accept: "image/*", // 设置类型
directory: true, // 设置 - 选择文件夹
});
onChange((files) => {
/** do something with files */
});
</script>
<template>
<div>
<button type="button" @click="() => open()">Choose file</button>
</div>
</template>
查看源码发现与 核心就是 input标签 type="file",与前面第一条很类似,不过封装思路值得学习;
四、js新api,window.showOpenFilePicker()
js新增了 window.showOpenFilePicker()
、 window.showDirectoryPicker()
、window.showSaveFilePicker()
等api,兼容性查,慎用;
五、其他细节
1. 中断终止
因为上传是个耗时的任务,用户很可能中途停止,如关闭弹窗、关闭抽屉等,这时候我们需要终止上传;
使用action方式的中断方法,调用组件暴露给外部的api
js
uploadRef.value?.abort();
使用:http-request=
自定义上传请求的,可以使用AbortController中断
js
const controller = new AbortController()
try {
const r = await fetch('/json', { signal: controller.signal });
} catch (e) {}
// 取消上传
controller.abort()
2. 大文件上传
一般大文件上传需要切片 还要考虑断点重传,笔者还未研究这块,暂时给自己留个作业