在Vue3中实现Upload组件怎样做,过程是什么
Admin 2022-07-19 群英技术资讯 1698 次浏览
在这篇文章中,我们来学习一下“在Vue3中实现Upload组件怎样做,过程是什么”的相关知识,下文有详细的讲解,易于大家学习和理解,有需要的朋友可以借鉴参考,下面就请大家跟着小编的思路一起来学习一下吧。开发上传组件前我们需要了解:
首先实现一个最基本的上传流程:
基本上传流程,点击按钮选择,完成上传
代码如下:
<template>
<div class="app-container">
<!--使用change事件-->
<input type="file" @change="handleFileChange">
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
export default defineComponent({
name: 'App',
setup() {
const handleFileChange = (e: Event) => {
// 断言为HTMLInputElement
const target = e.target as HTMLInputElement
const files = target.files
if(files) {
const uploadedFile = files[0]
const formData = new FormData()
formData.append('file', uploadedFile)
// 使用node模拟上传接口
axios.post('http://localhost:3001/upload', formData, {
headers: {
"Content-Type": 'multipart/form-data'
}
}).then(resp=> {
console.log('resp', resp)
}).catch(error=> {
})
}
}
return {
handleFileChange
}
}
})
</script>
<style>
.page-title {
color: #fff;
}
</style>
结果如下:

到这里我们基本的上传已经处理完成了,相对来说还是比较简单的,接下来我们创建Uploader.vue文件用来封装Upload组件。
我们知道使用<input type="file">系统自带样式比较难看所用我们需要如下处理:
代码如下:
<template>
<div class="file-upload">
<div class="upload-area" @click="triggerUpload"></div>
<span v-if="fileStatus==='loading'">正在上传</span>
<span v-else-if="fileStatus==='success'">上传成功</span>
<span v-else-if="fileStatus==='error'">上传失败</span>
<span v-else>点击上传</span>
<input ref="fileInput" type="file" name="file" style="display: none" />
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType, reactive, ref } from 'vue'
import axios from 'axios'
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
export default defineComponent({
props: {
action: { // url地址
type: String,
required: true
}
},
setup(props) {
// input实例对象,通过与ref="fileInput"的关联获取到input实例对象
const fileInput = ref<null | HTMLInputElement>(null)
const fileStatus = ref<UploadStatus>('ready')
// 1.div点击事件
const triggerUpload = () => {
if(fileInput.value) {
fileInput.value.click()
}
}
// 通过div来触发input的change事件
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement
const files = target.files
if(files) {
const uploadedFile = files[0]
const formData = new FormData()
formData.append('file', uploadedFile)
readyFile.status = 'loading' // 上传之前将状态设置为loading
axios.post(props.actions, formData, {
headers: {
"Content-Type": 'multipart/form-data'
}
}).then(resp=> {
console.log('resp', resp)
readyFile.status = 'success' // 上传成功将状态设置为success
}).catch(error=> {
readyFile.status = 'error' // // 上传失败将状态设置为error
})
}
}
return {
fileInput,
triggerUpload,
handleFileChange,
fileStatus
}
}
})
</script>
现在我们已经完成了对上传组件样式的优化,和上传状态的处理,接下来我们需要处理文件上传列表
处理文件上传列表我们需要有如下实现:
在上一步代码的基础上做一些修改:
<template>
<div class="file-upload">
<div class="upload-area" @click="triggerUpload"></div>
<!-- 点击上传态 -->
<slot v-if="isUploading" name='loading'>
<button disabled>正在上传</button>
</slot>
<!-- 上传完毕态 -->
<slot name="uploaded" v-else-if="lastFileData && lastFileData.loaded" :uploadData="lastFileData.data">
<button disabled>点击上传</button>
</slot>
<!-- 默认态 -->
<slot v-else name='default'>
<!-- <button disabled>点击上传</button> -->
<span>点击上传</span>
</slot>
<input ref="fileInput" type="file" name="file" style="display: none" />
<!--展示fileList-->
<ul>
<li
v-for="file in filesList"
:key="file.uid"
:class="`uploaded-file upload-${file.status}`"
>
<span class="filname">
{{ file.name }}
</span>
<button class="delete-icon" @click="reomveFile(file.uid)">Del</button>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { last } from 'lodash-es'
import { v4 as uuidv4 } from 'uuid';
// 定义上传状态
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
// step1 定义上传文件对象接口类
export interface UploadFile {
uid: string; // 文件唯一id
size: number; // 文件大小
name: string; // 文件名称
status: UploadStatus; // 上传状态
raw: File; // 文件
progress?: string; // 文件上传进度
resp?: any; // 服务端返回数据
url?: string // 对应展示的url
}
export default defineComponent({
props: {
action: { // url地址
type: String,
required: true
}
},
setup(props) {
// input实例对象,通过与ref="fileInput"的关联获取到input实例对象
const fileInput = ref<null | HTMLInputElement>(null)
// step2 上传文件列表
const filesList = ref<UploadFile[]>([])
// step4-1 判断是否正在上传中
const isUploading = computed(()=> {
return filesList.value.some((file)=>file.status==='loading')
})
//step4-2 获取上传文件的最后一项
const lastFileData = computed(()=>{
const lastFile = last(filesList.value)
if(!lastFile) return false
return {
loaded: lastFile?.status === 'success',
data: lastFile?.resp
}
})
// 1.div点击事件
const triggerUpload = () => {
if(fileInput.value) {
fileInput.value.click()
}
}
// 通过div来触发input的change事件
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement
const files = target.files
if(files) {
const uploadedFile = files[0]
const formData = new FormData()
formData.append('file', uploadedFile)
// step3 设置响应式对象,存储到filesList中,以便在页面中展示
const fileObj = reactive<UploadFile>({
uid: uuid(); // 文件唯一id
size: uploadedFile.size,
name: uploadedFile.name,
status: 'loading',
raw: uploadedFile
})
filesList.value.push(fileObj)
axios.post(props.actions, formData, {
headers: {
"Content-Type": 'multipart/form-data'
},
//step6 处理上传进度
onUploadProgress: (progressEvent)=> {
const complete = (progressEvent.loaded / progressEvent.total * 100 | 0) + '%'
fileObj.progress = complete
}
}).then(resp=> {
console.log('resp', resp)
fileObj.status = 'success'
}).catch(error=> {
fileObj.status = 'error'
}).finally(()=> {
// 一张图片重复上传时无法继续上传bug
if(fileInput.value) {
fileInput.value.value = ''
}
})
}
}
// step7 处理删除
const reomveFile = (uid: string) => {
filesList.value = filesList.value.filter(file=>file.uid!==uid)
}
return {
fileInput,
triggerUpload,
handleFileChange,
fileStatus,
isUploading,
filesList,
lastFileData
}
}
})
</script>
注意点:如果选择相同的图片不会进行上传操作,因为我们使用的是input的change事件,所以我们需要在上传后将input的value设置为null
beforeUpload
<template>
...
</template>
<script lang="ts">
import { last } from 'lodash-es'
import { v4 as uuidv4 } from 'uuid';
// 定义上传状态
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
// 定义上传文件为boolean或promsie且接受一个File
type CheckUpload = ()=> boolean | Promise<File>
export interface UploadFile {
...
}
export default defineComponent({
props: {
action: { // url地址
type: String,
required: true
},
beforeUpload: {
type: Function as PropType<CheckUpload>
}
},
setup(props) {
const fileInput = ref<null | HTMLInputElement>(null)
const filesList = ref<UploadFile[]>([])
// step4-1 判断是否正在上传中
const isUploading = computed(()=> {
return filesList.value.some((file)=>file.status==='loading')
})
const lastFileData = computed(()=>{
const lastFile = last(filesList.value)
if(!lastFile) return false
return {
loaded: lastFile?.status === 'success',
data: lastFile?.resp
}
})
const triggerUpload = () => {
if(fileInput.value) {
fileInput.value.click()
}
}
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement
const files = target.files
const uploadedFile = files[0]
if(props.beforeUpload) {
const result = props.beforeUpload(uploadedFile)
if(result && result instanceof Promise) {
result.then((processFile)=> {
if(uploadedFile instanceof File) {
postFile(uploadedFile)
}
}).catch(error=>{
console.error(error)
})
} esle if(result===true) {
postFile(uploadedFile)
}
}else{
postFile(uploadedFile)
}
}
const reomveFile = (uid: string) => {
filesList.value = filesList.value.filter(file=>file.uid!==uid)
}
// 处理文件上传
const postFile = (readyFile:UploadFile)=> {
const formData = new FormData()
formData.append('file', readyFile.raw)
readyFile.status = 'loading'
axios.post(props.action, formData, {
headers: {
"Content-Type": 'multipart/form-data'
},
onUploadProgress: (progressEvent)=> {
const complete = (progressEvent.loaded / progressEvent.total * 100 | 0) + '%'
// console.log('上传 ' + complete)
readyFile.progress = complete
}
}, ).then(resp=> {
console.log('resp', resp)
// fileStatus.value = 'success'
readyFile.status = 'success'
readyFile.resp = resp.data
}).catch(error=> {
// fileStatus.value = 'error'
readyFile.status = 'error'
})
.finally(()=> {
// 一张图片重复上传时无法继续上传bug
if(fileInput.value) {
fileInput.value.value = ''
}
})
}
return {
fileInput,
triggerUpload,
handleFileChange,
fileStatus,
isUploading,
filesList,
lastFileData
}
}
})
</script>
实现步骤:
onProgress,onSuccess,onError,onChange与之类似
大致流程如下图:

需要注意v-on的使用具体可参考vue官方文档
上代码:
<template>
<div class="file-upload">
<div
:class="['upload-area', drag && isDragOver ? 'is-dragover': '' ]"
v-on="events"
>
<!-- 点击上传态 -->
<slot v-if="isUploading" name='loading'>
<button disabled>正在上传</button>
</slot>
<!-- 上传完毕态 -->
<slot name="uploaded" v-else-if="lastFileData && lastFileData.loaded" :uploadData="lastFileData.data">
<button disabled>点击上传</button>
</slot>
<!-- 默认态 -->
<slot v-else name='default'>
<!-- <button disabled>点击上传</button> -->
<span>点击上传</span>
</slot>
</div>
<input ref="fileInput" type="file" name="file" style="display: none" @change="handleFileChange" />
<ul>
<li
v-for="file in filesList"
:key="file.uid"
:class="`uploaded-file upload-${file.status}`"
>
<img
v-if="file.url && listType === 'picture'"
class="upload-list-thumbnail"
:src="file.url"
:alt="file.name"
>
<span class="filname">
{{ file.name }}
</span>
<span class="progress">
{{ file.progress }}
</span>
<button class="delete-icon" @click="reomveFile(file.uid)">Del</button>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, PropType, reactive, ref } from 'vue'
import axios from 'axios'
import { v4 as uuidv4 } from 'uuid';
import { last } from 'lodash-es'
type UploadStatus = 'ready' | 'loading' | 'success' | 'error'
type fileListType = 'text' | 'picture'
type CheckUpload = ()=> boolean | Promise<File>
export interface UploadFile {
uid: string; // 文件唯一id
size: number; // 文件大小
name: string; // 文件名称
status: UploadStatus; // 上传状态
raw: File; // 文件
progress?: string;
resp?: any; // 服务端返回数据
url?: string // 对应展示的url
}
// dragOver 文件拖拽到区域时触发
// dragLeave 文件离开拖动区域
// drop 事件拿到正在被拖拽的文件删除class并且触发上传
// 只有设置drag的时候才有效
export default defineComponent({
name: 'Uploader',
props: {
action: { // url地址
type: String,
required: true
},
beofreUpload:{ // 上传之前的处理
type: Function as PropType<CheckUpload>
},
drag: { // 是否拖拽
type: Boolean,
default: false
},
autoUpload: { // 自动上传
type: Boolean,
default: true
},
listType: {
type: String as PropType<fileListType>,
default: 'text'
}
},
setup(props) {
const fileInput = ref<null | HTMLInputElement>(null)
const fileStatus = ref<UploadStatus>('ready')
// 存储上传的文件
const filesList = ref<UploadFile[]>([])
// 定义了一个isDragOver标识,处理拖动后样式.upload-area显示
const isDragOver = ref<boolean>(false)
const triggerUpload = () => {
if(fileInput.value) {
fileInput.value.click()
}
}
let events: {[key:string]: (e: any)=>void} = {
'click': triggerUpload,
}
// 只要有一个文件状态是loading态就说明是正在上传
const isUploading = computed(()=>{
return filesList.value.some((file)=> file.status==='loading')
})
// 获取上传文件的最后一项
const lastFileData = computed(()=>{
const lastFile = last(filesList.value)
if(!lastFile) return false
return {
loaded: lastFile?.status === 'success',
data: lastFile?.resp
}
})
// 处理dragover,dragleave
const handleDrag = (e: DragEvent,over: boolean)=> {
// 阻止默认事件
e.preventDefault()
// dragover为true,dragleave为fasle
isDragOver.value = over
}
// 处理drop
const handleDrop = (e: DragEvent) => {
e.preventDefault()
isDragOver.value = false
if(e.dataTransfer) {
beforeUploadCheck(e.dataTransfer.files)
}
}
if(props.drag){
events = {
...events,
'dragover': (e: DragEvent) => { handleDrag(e, true)},
'dragleave': (e: DragEvent) => { handleDrag(e, false)},
'drop': handleDrop
}
// console.log(events)
}
// 删除文件
const reomveFile = (uid: string)=> {
filesList.value = filesList.value.filter(file=>file.uid!==uid)
}
const postFile = (readyFile:UploadFile) => {
const formData = new FormData()
formData.append('file', readyFile.raw)
readyFile.status = 'loading'
// 选择文件后push到存储对象里面
axios.post(props.action, formData, {
headers: {
"Content-Type": 'multipart/form-data'
},
onUploadProgress: (progressEvent)=> {
const complete = (progressEvent.loaded / progressEvent.total * 100 | 0) + '%'
// console.log('上传 ' + complete)
readyFile.progress = complete
}
}, ).then(resp=> {
console.log('resp', resp)
// fileStatus.value = 'success'
readyFile.status = 'success'
readyFile.resp = resp.data
}).catch(error=> {
// fileStatus.value = 'error'
readyFile.status = 'error'
})
.finally(()=> {
// 一张图片重复上传时无法继续上传bug
if(fileInput.value) {
fileInput.value.value = ''
}
})
}
const addFileToList = (uploadedFile: File) => {
const fileObj = reactive<UploadFile>({
uid: uuidv4(),
size: uploadedFile.size,
name: uploadedFile.name,
status: 'ready',
raw: uploadedFile
})
// 处理图片格式并显示
if(props.listType==='picture') {
// try {
// fileObj.url = URL.createObjectURL(uploadedFile)
// }catch(err) {
// console.error('upload file err', err)
// }
const fileReader = new FileReader()
fileReader.readAsDataURL(uploadedFile)
fileReader.addEventListener('load', ()=> {
fileObj.url = fileReader.result as string
})
}
filesList.value.push(fileObj)
if(props.autoUpload) {
postFile(fileObj)
}
}
const uploadFiles = ()=> {
// filesList.value.filter(file => file.status === 'ready').forEach(readyFile => postFile(readyFile))
filesList.value.filter(file => file.status === 'ready').forEach(readyFile=>postFile(readyFile))
}
const beforeUploadCheck = (files: null | FileList ) => {
if(files) {
fileStatus.value = 'loading'
const uploadedFile = files[0]
if(props.beofreUpload) {
const result = props.beofreUpload()
// 如果有返回值
if(result && result instanceof Promise) {
result.then(processedFile=> {
if(processedFile instanceof File) {
addFileToList(processedFile)
} else {
throw new Error('beforeUpload promise should return file object')
}
}).catch(err=> {
console.log(err)
})
} else if(result === true) {
addFileToList(uploadedFile)
}
} else {
addFileToList(uploadedFile)
}
}
}
const handleFileChange = (e: Event) => {
const target = e.target as HTMLInputElement
const files = target.files
beforeUploadCheck(files)
}
return {
fileInput,
triggerUpload,
handleFileChange,
isUploading,
filesList,
reomveFile,
lastFileData,
beforeUploadCheck,
isDragOver,
events
}
}
})
</script>
以上就是基本的基于vue3实现的上传通用组件
另附上上传接口代码:
// 引入模块
const Koa = require('koa');
const fs = require('fs');
const path = require('path');
const router = require('koa-router')();
const koaBody = require('koa-body');
const static = require('koa-static');
const cors = require('koa2-cors')
// 实例化
const app = new Koa();
app.use(koaBody({
multipart: true, // 支持文件上传
formidable: {
maxFieldsSize: 2 * 1024 * 1024, // 最大文件为2兆
multipart: true // 是否支持 multipart-formdate 的表单
}
}));
const uploadUrl = "http://localhost:3001/static/upload";
// 配置路由
router.get('/', (ctx) => {
// 设置头类型, 如果不设置,会直接下载该页面
ctx.type = 'html';
// 读取文件
const pathUrl = path.join(__dirname, '/static/upload.html');
ctx.body = fs.createReadStream(pathUrl);
});
// 上传文件
router.post('/upload', (ctx) => {
// 获取上传文件
const file = ctx.request.files.file;
console.log(file);
// 读取文件流
const fileReader = fs.createReadStream(file.path);
// 设置文件保存路径
const filePath = path.join(__dirname, '/static/upload/');
// 组装成绝对路径
const fileResource = filePath + `/${file.name}`;
/**
* 使用 createWriteStream 写入数据,然后使用管道流pipe拼接
*/
const writeStream = fs.createWriteStream(fileResource);
// 判断 /static/upload 文件夹是否存在,如果不在的话就创建一个
if (!fs.existsSync(filePath)) {
fs.mkdir(filePath, (err) => {
if (err) {
throw new Error(err);
} else {
fileReader.pipe(writeStream);
ctx.body = {
url: uploadUrl + `/${file.name}`,
code: 0,
message: '上传成功1'
};
}
});
} else {
fileReader.pipe(writeStream);
ctx.body = {
url: uploadUrl + `/${file.name}`,
code: 0,
message: '上传成功1'
};
}
});
// 配置静态资源路径
app.use(static(path.join(__dirname)));
// 开启跨域
app.use(cors())
// 启动路由
app.use(router.routes()).use(router.allowedMethods());
// 监听端口号
app.listen(3001, () => {
console.log('server is listen in 3001');
});
已上只是一个简单的实现,当然了我们也可以添加自定义headers,自定义传递数据data,accecpt等等
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍了详解CocosCreator消息分发机制,详细介绍了各模块的设计,同学们一定要自己看下
Node如何实现数据传输加密解密?下面本篇文章给大家介绍一下Node.js实现前后端数据传输加密解密的方法,希望对大家有所帮助!
这个只是一个分页的demo,主要是思路整理(很久之前项目用的东西)分页实现的效果 主要是 左侧上一页 右侧是下一页 中间显示主要是超过5个显示 省略号 然后是可配置选项实现之后的效果首先需要初始化该对象的一些基本属性,显示总页码数,中间显示的页面数, 添加一个回调函数,在页面变化激活回调函数并返回当前页面和一些需要的其
ThreeJS从创建场景到使用功能实例详解 目录 前言 创建场景以及相机 创建一个平面 添加图片 创建线 添加轴线 缩放.定位.以及旋转 添加文字 正交摄像机和透视摄像机的区别 总结 前言 最近公司要做一个2.5D插件,然后自己学旋转角度不太好,然后就使用了THREEJS, 用起来还是比较繁琐的,整体支持不太好,整体都是自己研究,看到写的不好地方勿怪 创建场景以及相机 首先,要创建一个场景,以及一个相机(相机分为透视相机和正交摄像机,区别在后面会解释),代码如下 export default class ThreeComponent ex
这篇文章我们来了解jQuery中如何实现修改style属性。这里我们可以使用attr()方法来修改style属性,具体的实现方法及代码如下,有需要的朋友可以参考,接下来就跟随小编来一起学习一下吧!
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 ICP核准(ICP备案)粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008