<template>
<div
@dragover.prevent
@dragenter.prevent="drag = true"
@dragleave.prevent="drag = false"
@drop.prevent="onDrop"
@click="input.click"
v-show="status === 'upload'"
:class="drag ? 'upload-active' : ''"
class="box upload">
<input @change="selectChange" ref="input" type="file" />
</div>
<div v-show="status === 'uploading'" class="box uploading">
<img :src="imgUrl" />
<p>{{ progress }}%</p>
<progress id="file" max="100" :value="progress"></progress>
<p>文件上传中...</p>
<button @click="cancel">取消</button>
</div>
<div v-show="status === 'finished'" class="box finished">
<div @click="cancel" class="cancel"><i-material-symbols-close /></div>
<img :src="imgUrl" />
</div>
</template>
<script setup>
import axios from 'axios'
const status = ref('upload') // upload、uploading、finished
const input = ref()
const progress = ref(0)
const imgUrl = ref('')
const drag = ref(false)
let cancelUpload = null
const selectChange = e => {
if (!e.target.files.length) return
const file = e.target.files[0]
if (!validateFile(file)) return
status.value = 'uploading'
imgUrl.value = URL.createObjectURL(file) // blob
cancelUpload = upload(
file,
val => (progress.value = val),
resp => {
status.value = 'finished'
imgUrl.value = resp.data.url
}
)
}
const validateFile = file => {
const suffixArr = ['jpg', 'jpeg', '.png']
const maxSize = 1024 * 1024 * 5
if (file.name && file.size) {
const suffix = file.name.split('.').pop()
if (suffixArr.includes(suffix) && file.size <= maxSize) return true
}
return false
}
const upload = (file, onProgress, onFinish) => {
// 获取上传进度,取消请求:https://juejin.cn/post/7127547763679559693
const controller = new AbortController()
const data = new FormData()
data.append('file', file)
data.append('bar', 'foo')
axios
.post('/upload', data, {
headers: { 'Content-Type': 'multipart/form-data;charset=utf-8' },
onUploadProgress(e) {
// 获取上传进度
if (e.lengthComputable) {
onProgress(Math.round((e.loaded * 100) / e.total))
}
}
})
.then(resp => onFinish(resp))
// 取消上传
return () => controller.abort()
}
const cancel = () => {
cancelUpload && cancelUpload()
status.value = 'upload'
progress.value = 0
drag.value = false
}
const onDrop = e => {
selectChange({ target: { files: e.dataTransfer.files } })
}
</script>
<style scoped lang="less">
.box {
position: relative;
margin: 100px auto;
width: 250px;
height: 250px;
box-sizing: border-box;
border-radius: 5px;
}
img {
position: absolute;
left: 0;
top: 0;
z-index: -1;
width: 100%;
object-fit: cover;
}
.upload {
border: 2px dashed #ccc;
&:hover {
border-color: #409eff;
cursor: pointer;
}
input {
display: none;
}
&::after {
content: '+';
position: absolute;
font-size: 100px;
color: #aaa;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
}
.upload-active {
background-color: rgba(64, 158, 255, 0.3);
border-color: #409eff;
}
.uploading {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
border: 2px solid #ccc;
background-color: rgba(0, 0, 0, 0.5);
p {
color: #fff;
font-size: 18px;
margin: 10px;
}
progress {
width: 200px;
height: 8px;
border-radius: 85px;
background-color: #fff;
&::-moz-progress-bar {
background-color: #409eff;
}
}
button {
all: initial;
color: #fff;
cursor: pointer;
}
}
.finished {
border: 2px solid #ccc;
cursor: pointer;
.cancel {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 25px;
height: 25px;
background-color: rgba(0, 0, 0, 0.6);
right: 0;
top: 0;
border-radius: 3px 3px 0 3px;
font-size: 22px;
color: #fff;
}
}
</style>
标签:vue,const,color,border,upload,value,file,组件,上传
From: https://www.cnblogs.com/19BigData/p/17550519.html