说在前面的
在日常的开发工作中,文件上传是一个不可避免的需求,通常我们会使用诸如 element
、antd
之类组件库自带的上传组件来实现功能。但若止步于此的话,一旦场景开始变得复杂起来,我们很容易就丧失了进一步解决问题的能力。
而本系列文章的主旨就帮助我们理解从最基础的文件上传开始,到拖拽上传,添加进度条,大文件上传以及断点续传等等背后的原理,以及如何实现。
相信读完本系列文章之后,你会对文件上传有更加深刻的认识和理解,那么废话不多说,我们直接开始吧。
环境搭建
前端:vue3
这里没什么需要特别注意的,直接使用 vue-cli 创建项目即可,选项如下:
▶ vue create file-upload
Vue CLI v4.5.10
? Please pick a preset: Manually select features
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to inve
rt selection)
❯◉ Choose Vue version
◉ Babel
◯ TypeScript
◯ Progressive Web App (PWA) Support
◯ Router
◯ Vuex
◉ CSS Pre-processors
◉ Linter / Formatter
◯ Unit Testing
◯ E2E Testing
依赖如下:
后端:Koa
这里除了 Koa 之外,还需要:
koa-router
:设置路由koa-body
:解析 formdata,需要设置multipart: true
koa-static
:设置静态目录fs-extra
:操作文件(用原生的 fs 也行)
依赖如下:
V1.0:基础文件上传
在 components
目录下创建 Upload.vue
组件,然后我们就可以开始实现文件上传了。
首先实现 html 结构,这里需要一个 file
类型的 input
,并且要监听它的 change
事件,这样才能获取到文件数据:
<template>
<div class="upload">
<input type="file" name="file" @change="handleFileChange" />
</div>
</template>
<script>
export default {
name: "Upload",
setup() {
const handleFileChange = e => {
console.log("[+] data", e.target.files);
};
return {
handleFileChange
};
}
};
</script>
效果如下:
可以看到,e.target.files 是一个 FileList,这里我们只做基础的单文件上传,所以取其首位即可:
const [file] = e.target.files;
在选择文件之后,要上传到后端,这里就需要用到网络请求,我们可以使用 axios 来完成这个功能,需要注意的是,如果遇到跨域问题,可以在 vue.config.js
中配置代理:
devServer: {
proxy: {
[process.env.VUE_APP_BASE_API]: {
target: `${process.env.VUE_APP_SERVER_URL}/api/v1`,
changeOrigin: true,
pathRewrite: {
["^" + process.env.VUE_APP_BASE_API]: ""
}
}
}
}
接下来可以做一个简单的测试:
const res = await axios.get("/dev-api/test");
console.log("[+] res", res);
结果如下:
说明网络请求已经没有问题了,那么接下来就可以将文件数据装进 formData 中,发送给后端:
<template>
<div class="upload">
<input type="file" name="file" @change="handleFileChange" />
<button @click="hanleFileUpload">upload</button>
</div>
</template>
<script>
import axios from "axios";
import { ref } from "vue";
export default {
name: "Upload",
setup() {
const file = ref(null);
const handleFileChange = e => {
const [_file] = e.target.files;
file.value = _file;
};
const hanleFileUpload = async () => {
const formData = new FormData();
formData.append("name", "file");
formData.append("file", file.value);
await axios.post("/dev-api/upload", formData);
};
return {
handleFileChange,
hanleFileUpload
};
}
};
</script>
当然现在后端功能还没实现,但依然可以测试一下从控制台上看看文件是否真的发送出去了:
可见文件已经发送出去了,那么在后端创建路由接收即可:
router.post("/api/v1/upload", async ctx => {
const file = ctx.request.files.file;
console.log("[+] file: ", file);
});
结果如下:
接下来就只需要将文件移动到公共目录即可:
router.post("/api/v1/upload", async ctx => {
const file = ctx.request.files.file;
+ const { name: filename, path: cache } = file;
+ const basename = path.basename(file.path);
+ await fse.move(cache, path.resolve(__dirname, "public/uploads/" + filename));
+ ctx.body = { url: `${ctx.origin}/uploads/${basename}` };
});
最终结果如下:
V1.1:拖拽上传
在基础的铺垫完成之后,我们可以为这个上传新增一些体验向的功能,比如拖拽上传。
首先,可以给文件上传增加一个边框:
<template>
<div class="upload">
<div class="drag-wrap">
<input type="file" name="file" @change="handleFileChange" />
</div>
<button @click="hanleFileUpload">upload</button>
</div>
</template>
<style lang="scss" scoped>
.drag-wrap {
display: flex;
justify-content: center;
align-items: center;
height: 100px;
border: 2px dashed #eee;
}
</style>
效果如下:
接下来,可以简单分析一下:
- 当文件拖进区域内的时候,给出提示
- 当文件离开区域的时候,恢复状态
- 当文件在区域内松开的时候,触发存储文件
那么这里需要监听三个事件:
const useDrage = elRef => {
const el = elRef.value;
el.addEventListener("dragover", e => {
console.log("dragover");
e.preventDefault();
});
el.addEventListener("dragleave", e => {
console.log("dragleave");
e.preventDefault();
});
el.addEventListener("drop", e => {
console.log("drop");
e.preventDefault();
});
};
而 el
则由 ref
指定即可:
<template>
<div class="upload">
<div class="drag-wrap" ref="dragEle">
<input type="file" name="file" @change="handleFileChange" />
</div>
</div>
</template>
<script>
import { ref, onMounted } from "vue";
export default {
name: "Upload",
setup() {
const fileData = ref(null);
const dragEle = ref();
onMounted(() => {
useDrage(dragEle.value);
});
return {
dragEle
};
}
};
</script>
接下来,只需要做三件事即可:
dragover
:改变边框颜色dragleave
:恢复边框颜色drop
:存储文件,并且恢复边框颜色
el.addEventListener("dragover", e => {
el.style.borderColor = "red";
e.preventDefault();
});
el.addEventListener("dragleave", e => {
el.style.borderColor = "#eee";
e.preventDefault();
});
el.addEventListener("drop", e => {
const [file] = e.dataTransfer.files;
fileData.value = file;
el.style.borderColor = "#eee";
e.preventDefault();
});
结果如下:
V1.2:上传进度条
关于上传进度条,这里我们选择借助 element
的 progress
和 axios
的 onUploadProgress
来实现,当然,由于只使用了 progress
,element
可以按需引入:
<template>
<div class="upload">
<el-progress :percentage="uploadProgress"></el-progress>
</div>
</template>
<script>
import axios from "axios";
import { ref, onMounted } from "vue";
export default {
name: "Upload",
setup() {
const uploadProgress = ref(0);
const hanleFileUpload = async () => {
await axios.post("/dev-api/upload", formData, {
onUploadProgress: progress => {
const { loaded, total } = progress;
uploadProgress.value = Number(((loaded / total) * 100).toFixed(2));
}
});
};
return {
uploadProgress
};
}
};
</script>
结果如下:
结束语
今天的讲解就到这里,下一篇我们将实现二进制层面对于文件类型的校验。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!