前端文件上传的几种交互造轮子
来源 | OSCHINA 社区
作者 | 京东云开发者-京东物流 刘海鼎
原文链接:https://my.oschina.net/u/4090830/blog/10084832
背景
介绍
几种交互
<div class="uploader-content" @click="handleClick">
<input ref="inputRef"
class="uploader-target"
:name="name" :multiple="multiple"
:accept="accept" type="file"
@change="handleChange" />
</div>
<script setup>
const inputRef = shallowRef(null)
const handleClick = () => {
inputRef.value.value = ''
inputRef.value?.click()
}
const handleChange = (e) => {
const files = e.target.files
if (!files) return
// 获取到文件后调用附件上传方法
uploadFiles(files)
}
</script>
<style lang='less' scoped>
.uploader-target {
display: none;
}
</style>
<div class="uploader-drag" v-if="props.uploadMode == 'drag'" :class="['dragger', dragover ? 'dragover' : '']" @drop.prevent="onDrop" @dragover.prevent="onDragover"
@dragleave.prevent="dragover = false">
<div class="dragicon-box">
<span>+</span>
</div>
</div>
<script setup>
const dragover = ref(false)
const onDrop = (e) => {
const files = Array.from(e.dataTransfer?.files)
dragover.value = false
uploadFiles(files);
}
const onDragover = () => {
dragover.value = true
}
</script>
<div class="uploader-paste"
v-if="props.uploadMode == 'paste'"
:class="['dragger', dragover ? '' : '']"
@mouseover.stop="clipboardover = true"
@mouseleave.stop="clipboardover = false"
@drop.prevent="onDrop"
@dragover.prevent="onDragover"
@dragleave.prevent="dragover = false"
@paste="pasteFun"
>
<!--默认插槽内容-->
<template v-if="$slots.default == null">
<div class="dragicon-box">
<span>+</span>
</div>
</template>
<slot />
</div>
<script setup>
const clipboardover = ref(false)
const pasteFun = (e) => {
if(!clipboardover.value) return
const clipboardFile = e.clipboardData.files;
uploadFiles(clipboardFile)
}
</script>
上传模式
根据以上三种交互,大家可自由组合上传形式,比如点击和拖拽,拖拽和粘贴组合等等,我这边目前按点击,拖拽,粘贴叠加组合,设置为:
・点击上传,click
・拖拽上传 drag(包括点击上传和拖拽上传)
・粘贴上传 paste (包括点击,拖拽和复制上传)
通过传参 uploadeMode 设置 (click, drag, paste)
组件设置:
<div class="uploader-content" @click="handleClick">
<input
ref="inputRef"
class="uploader-target"
:name="name"
:multiple="multiple"
:accept="props.accept"
type="file"
@change="handleChange"
v-if="props.uploadMode != 'click'"
/>
<!-- click -->
<div class="uploader-click" v-if="props.uploadMode == 'click'">
<slot />
<input
ref="inputRef"
class="uploader-target"
:name="name"
:multiple="multiple"
:accept="accept"
type="file"
@change="handleChange"
@click.stop />
</div>
<!-- drag -->
<div class="uploader-drag"
v-if="props.uploadMode == 'drag'"
:class="['dragger', dragover ? 'dragover' : '']"
@drop.prevent="onDrop"
@dragover.prevent="onDragover"
@dragleave.prevent="dragover = false">
<template v-if="$slots.default == null">
<div class="dragicon-box">
<span>+</span>
</div>
</template>
<slot />
</div>
<!-- copy -->
<div class="uploader-paste"
v-if="props.uploadMode == 'paste'"
:class="['dragger', dragover ? '' : '']"
@mouseover.stop="clipboardover = true"
@mouseleave.stop="clipboardover = false"
@drop.prevent="onDrop"
@dragover.prevent="onDragover"
@dragleave.prevent="dragover = false"
@paste="pasteFun"
>
<template v-if="$slots.default == null">
<div class="dragicon-box">
<span>+</span>
</div>
</template>
<slot />
</div>
</div>
</template>
组件应用
<Upload action="https://jsonplaceholder.typicode.com/posts/" uploadMode="click">
<div>点击上传</div>
</Upload>
<script lang="ts">
import Upload from '@/components/uploader';
</script>
文件限制
<div class="uploader-content" @click="handleClick">
<input ref="inputRef"
class="uploader-target"
:name="name" :multiple="multiple" :accept="props.accept" type="file"
@change="handleChange" v-if="props.uploadMode != 'click'" @click.stop />
<div class="uploader-paste" v-if="props.uploadMode == 'paste'" :class="['dragger', dragover ? '' : '']"
@mouseover.stop="clipboardover = true"
@mouseleave.stop="clipboardover = false"
@drop.prevent="onDrop"
@dragover.prevent="onDragover"
@dragleave.prevent="dragover = false"
@paste="pasteFun"
>
<template v-if="$slots.default == null">
<div class="dragicon-box">
<span>+</span>
</div>
</template>
<slot />
</div>
</div>
<script setup>
import { shallowRef, ref } from 'vue';
const inputRef = shallowRef(null)
// 上传文件
const uploadFiles = (files) => {
if (files.length === 0) return
const { limit, multiple, accept } = props
// 是否多文件限制,主要用于拖拽和粘贴上传中
if (!multiple) {
files = Array.from(files).slice(0, 1)
}
// 文件数量
if (limit && files.length > limit) {
/*具体大家需要的逻辑可自行定义*/
return
}
// 文件类型限制
if (accept) {
files = filesFiltered(Array.from(files), accept)
}
//在文件符合条件后执行上传方法
}
// 文件过滤
const filesFiltered = (files, accept) => {
return files.filter((file) => {
const { type, name } = file
const extension = name.includes('.') ? `.${name.split('.').pop()}` : ''
const baseType = type.replace(//.*$/, '')
return accept
.split(',')
.map((type) => type.trim())
.filter((type) => type)
.some((acceptedType) => {
if (acceptedType.startsWith('.')) {
return extension === acceptedType
}
if (//*$/.test(acceptedType)) {
return baseType === acceptedType.replace(//*$/, '')
}
if (/^[^/]+/[^/]+$/.test(acceptedType)) {
type === acceptedType
}
return false
})
})
}
</script>
上传进度设置
// 上传方法调用
ajaxUpload({...props, file})
// 上传方法实现
ajaxUpload = (options) => {
const xhr = new XMLHttpRequest()
const action = option.action
console.log(xhr, xhr.upload)
if (xhr.upload) {
// 建立progress监听
xhr.upload.addEventListener('progress', (evt:any) => {
const progressEvt = evt
progressEvt.percent = evt.total > 0 ? (evt.loaded / evt.total) * 100 : 0
// 回传进度数据
option.onProgress(progressEvt)
})
}
}
同样文件上传成功,异常等方法也可以通过监听 load 并且判断 xhr.status 来实现,
xhr.addEventListener('load', () => {
if (xhr.status < 200 || xhr.status >= 300) {
return option.onError(getError(action, option, xhr))
}
option.onSuccess(getBody(xhr))
})
<Upload action="https://jsonplaceholder.typicode.com/posts/" :limit="3" uploadMode="click" :onProgress="progress">
<div class="button">点击上传</div>
<template v-slot:progress>
<!-自定义的进度条样式,大家可以根据自己的想象,自行设置进度条样式-->
<div class="progress-box">
<div class="progress">
<span class="line" :style="{'width': progressval + '%'}"></span>
</div>
<span class="val">{{progressval}} %</span>
</div>
</template>
</Upload>
<script setup>
import {ref} from 'vue'
import Upload from '@/components/uploader';
const progressval = ref(0)
const progress = (evt)=>{
progressval.value = evt.percent.toFixed(2)
},
// 上传成功
const uploadSucess = (e)=>{
console.log('sucess', e)
}
// 上传异常
const uploadError= (e)=> {
console.log('sucess', e)
}
</script>
// 上传文件
const uploadFiles = (files) => {
if (files.length === 0) return
const { limit, multiple, accept, httpRequest } = props
// 是否多文件限制,主要用于拖拽和粘贴上传中
if (!multiple) {
files = Array.from(files).slice(0, 1)
}
// 文件数量
if (limit && files.length > limit) {
/*具体大家需要的逻辑可自行定义*/
return
}
// 文件类型限制
if (accept) {
files = filesFiltered(Array.from(files), accept)
}
//在文件符合条件后执行上传方法
// 自定义上传方法调用
if(httpRequest) {
return httpRequest(files)
}
}
<Upload :limit="3" uploadMode="click" :onProgress="progress" :onSuccess="uploadSucess" :onError="uploadError" :httpRequest="httpRequest">
<div class="button">点击上传</div>
<template v-slot:progress>
<div class="progress-box">
<div class="progress">
<span class="line" :style="{'width': progressval + '%'}"></span>
</div>
<span class="val">{{progressval}} %</span>
</div>
</template>
</Upload>
<script setup>
const httpRequest = (files)=> {
// 获取到文件 ,自定已上传方法
}
</script>
总结
往期推荐
点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦
微信扫码关注该文公众号作者
戳这里提交新闻线索和高质量文章给我们。
来源: qq
点击查看作者最近其他文章