封装进度条onUploadProgress+axios取消请求的上传组件
目录
定时模拟进度条
方法
A.axios
B.xhr
取消请求
完整代码
A.自定义上传组件
B.二次封装组件
情况
增加cancelToken不生效,刷新页面
进度条太快->设置浏览器网速
定时模拟进度条
startUpload() {
if (!this.file) return;
const totalSize = this.file.size;
let uploadedSize = 0;
const interval = setInterval(() => {
if (uploadedSize >= totalSize) {
clearInterval(interval);
// this.state_tip = STATE_TIPS.get('上传成功');
} else {
uploadedSize += 1024;
this.progress = Math.round((uploadedSize / totalSize) * 100);
}
}, 200);
}
方法
A.axios
uploadQuery() {
if (!this.file) return;
this.state_tip = STATE_TIPS.get('上传中');
this.progress = 0;
// headers = {'Content-Type': 'multipart/form-data'}
const formData = new FormData()
formData.append('file', this.file)
axios.post(this.uploadPath, formData, {
headers: {
"X-Requested-With": "XMLHttpRequest",
},
onUploadProgress: (progressEvent: ProgressEvent) => {
console.log("onUploadProgress");
if (progressEvent.lengthComputable) {
this.progress = Math.round(
(progressEvent.loaded / progressEvent.total) * 100
);
console.log(this.progress);
}
},
}).then((res: any) => {
if (res && res.code == 200) {
this.uploadExel = res.data;
this.state_tip = STATE_TIPS.get('上传成功');
console.log(this.uploadExel);
this.$emit("update:uploadExel", this.uploadExel);
} else {
this.state_tip = STATE_TIPS.get('其他');
this.state_tip.tip = res.msg || '请取消上传,更换符合模板要求的文件';
}
}).catch((error: any) => {
this.state_tip = STATE_TIPS.get('上传失败');
}).finally(() => {
this.uploaded = true;
this.$emit("update:uploaded", this.uploaded);
});
}
B.xhr
uploadQuery(file: File) {
// headers = {'Content-Type': 'multipart/form-data'}
const formData = new FormData()
formData.append('file', file)
const xhr = new XMLHttpRequest();
xhr.open("POST", this.uploadPath, true);
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
this.uprogress = Math.round((event.loaded / event.total) * 100);
}
};
xhr.onload = () => {
console.log(xhr);
if (xhr.status === 200) {
const res = JSON.parse(xhr.responseText);
console.log(res);
console.log(res.code);
if (res.code === 200) {
this.uploadExel = res.data;
this.state_tip = "上传成功";
this.uploaded = true;
console.log(this.uploadExel);
this.$emit("update:uploaded", this.uploaded);
this.$emit("update:uploadExel", this.uploadExel);
} else {
// 处理上传失败情况
this.state_tip = "上传失败";
}
}
};
xhr.onerror = () => {
// 处理上传出错情况
this.state_tip = "上传出错";
};
xhr.send(formData);
// request.post(this.uploadPath, formData).then((res: any) => {
// if (res.code == 200) {
// this.uploadExel = res.data;
// this.state_tip = STATE_TIPS.get('上传成功');
// this.uploaded = true;
// this.$emit("update:uploaded", this.uploaded);
// this.$emit("update:uploadExel", this.uploadExel);
// } else {
// }
// })
}
取消请求
完整代码
<UploadComp :uploadPath="PATH" :fileLogPath="PATH.replace('uploadExcel?wpReleId=','getOtherIndexFileLog/')" :uploaded.sync="uploaded" :uploadExel.sync="uploadExel" @cancelUpload="cancelUpload" />
<!-- <SingleUploadComp :uploadPath="PATH" :uploaded.sync="uploaded" :uploadExel.sync="uploadExel" @cancelUpload="cancelUpload" /> -->
A.自定义上传组件
<template>
<div class="upload-list-dragger" :uploadPath="uploadPath" :fileLogPath="fileLogPath">
<div v-if="!file" @click="openFileInput" @dragenter="onDragEnter" @dragover="onDragOver" @drop="onDrop"
:class="{ 'drag-over': isDragOver }">
<input type="file" ref="fileInput" style="display: none;" @change="onFileChange" :accept="format" />
<div class="custom-drag-style">
<img src="@/assets/img/upload.png" class="upload-icon" />
<div class="upload-click-drag">点击或拖拽上传文件</div>
<!-- 使用正则表达式替换所有点号 -->
<div class="upload-tip">请上传{{ format.replace(/\./g, "") }}格式文件,上传多份文件时以最后一次为准</div>
</div>
</div>
<div v-else class="custom-upload-card">
<img class="upload-card-icon" src="@/assets/img/excel.png" />
<div class="upload-card-state">
<div>
<span class="file-name">{{ file.name }}</span>
<span class="cancel-upload" @click="cancelUpload"><mds-icon type="line-close" /></span>
</div>
<div class="progress-bar" :style="{ width: progress + '%', backgroundColor: state_tip.color }"></div>
<div class="span-container">
<span :style="{ color: state_tip.state === '上传中' ? '#A8ACB3' : state_tip.color }">{{
state_tip.tip
}}</span>
<span v-if="state_tip.state === '上传中'">{{ progress + '%' }}</span>
<span v-if="state_tip.state === '上传失败'" class="span-operate" underline
@click="restartUpload">重新上传</span>
<span v-if="state_tip.state === '上传成功'" class="span-operate" underline
@click="downloadQuery">下载结果明细</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator';
import request from '@/utils/request'
import axios, { Canceler } from 'axios';
const STATE_TIPS = new Map([['其他', { state: '其他', color: 'orange', tip: '' }], ['上传中', { state: '上传中', color: '#1564FF', tip: '文件上传解析中…' }], ['上传失败', { state: '上传失败', color: '#DD2100', tip: '上传失败,请重新上传' }], ['上传成功', { state: '上传成功', color: '#00AF5B', tip: '上传成功!' }]])
@Component({
components: {}
})
export default class UploadComp extends Vue {
@Prop({ required: true }) private uploadPath!: string
@Prop({ required: true }) private fileLogPath!: string
@Prop({ default: '.xls' }) private format!: string //形如".xls,.csv,.xlsx"
uploadExel: any = {
succList: []
}
uploaded: boolean = false;
file: File | null = null;
source = axios.CancelToken.source();
progress = 0;
isDragOver = false;
data() {
return {
state_tip: {},
}
}
created() {
console.log(this.fileLogPath);
}
onFileChange(event: Event) {
const target = event.target as HTMLInputElement;
this.fileValidator(target.files);//可能为null
}
fileValidator(files: FileList | undefined | null) {
if (files && files.length > 0) {
// 上传多份文件时以最后一次为准
const file = files[0];
if (this.isValidFormat(file)) {
this.file = file;
console.log(this.file);
this.uploadQuery();
} else {
alert(`请上传${this.format.replace(/\./g, "")}格式文件。`);
}
} else {
alert(`请上传文件!`);
}
}
uploadQuery() {
if (!this.file) return;
this.state_tip = STATE_TIPS.get('上传中');
this.progress = 0;
// headers = {'Content-Type': 'multipart/form-data'}
const formData = new FormData()
formData.append('file', this.file)
// 在合适的地方定义取消令牌和取消函数
const CancelToken = axios.CancelToken;
// 判断上一次的请求是否还在继续,如果还在继续,则取消上一次的请求
if(this.source.token._listeners!=undefined )
{
this.source.cancel("取消请求")
this.source = axios.CancelToken.source()
}
request.post(this.uploadPath, formData, {
onUploadProgress: (progressEvent: ProgressEvent) => {
console.log("Upload progress:", progressEvent);
this.progress = Math.round((progressEvent.loaded / progressEvent.total) * 100);
console.log("进度:", this.progress);
},
cancelToken:this.source.token,
}).then((res: any) => {
if (res && res.code == 200) {
this.uploadExel = res.data;
this.state_tip = STATE_TIPS.get('上传成功');
console.log(this.uploadExel);
this.$emit("update:uploadExel", this.uploadExel);
this.uploaded = true;
this.$emit("update:uploaded", this.uploaded);
} else {
this.state_tip = STATE_TIPS.get('其他');
this.state_tip.tip = res.msg || '请取消上传,更换符合模板要求的文件';
}
}).catch((error: any) => {
this.state_tip = STATE_TIPS.get('上传失败');
})
}
downloadQuery() {
request.get(this.fileLogPath).then((res: any) => {
var aLink = document.createElement("a");
aLink.style.display = "none";
aLink.href = res.data[0].fileUrl
document.body.appendChild(aLink);
aLink.click();
document.body.removeChild(aLink);
})
}
cancelUpload() {
console.log("取消上传")
this.state_tip = STATE_TIPS.get('其他');
this.progress = 0;
this.file = null;
if (this.uploaded) {
this.$emit('cancelUpload', this.uploadExel.fileLogId)
}else{
this.source.cancel("请求已被取消")
this.source = axios.CancelToken.source()
}
}
restartUpload() {
this.uploadQuery();
}
openFileInput() {
const fileInput = this.$refs.fileInput as HTMLInputElement;
fileInput.click();
}
// 拖动文件进入上传区域
onDragEnter(event: DragEvent) {
// 防止浏览器默认的拖放行为
event.preventDefault();
this.isDragOver = true;
}
// 拖动文件在上传区域中移动
onDragOver(event: DragEvent) {
//防止浏览器默认的拖放行为
event.preventDefault();
}
// 放置拖动的文件
onDrop(event: DragEvent) {
event.preventDefault();
this.isDragOver = false;
this.fileValidator(event.dataTransfer?.files)//可能为undefined
}
isValidFormat(file: File) {
const supportedFormats: string[] = this.format.split(','); // 将 format 字符串拆分成数组
const fileExtension = '.' + file.name.split('.').pop(); // 获取文件名的扩展名
return supportedFormats.some((supportedFormat: string) => {
return fileExtension === supportedFormat;
});
}
}
</script>
<style>
.upload-list-dragger {
width: 800px;
height: 160px;
border: 1px dashed rgba(206, 212, 224, 1);
border-radius: 4px;
display: flex;
align-items: center;
}
.upload-list-dragger:hover {
background-color: #eef8ff;
}
.custom-drag-style {
height: 140px;
width: 780px;
background-color: #fff;
flex-wrap: wrap;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
.upload-icon {
width: 24px;
height: 24px;
}
.upload-click-drag {
width: 144px;
height: 24px;
font-family: PingFangSC-Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: rgba(69, 71, 77, 1);
text-align: left;
display: block;
}
.upload-tip {
height: 24px;
font-family: PingFangSC-Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: rgba(168, 172, 179, 1);
text-align: left;
display: block;
}
}
.custom-upload-card {
display: flex;
align-items: center;
height: 71px;
.upload-card-icon {
width: 71px;
}
.upload-card-state {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
.file-name {
font-family: PingFangSC-Regular;
font-size: 16px;
font-weight: 400;
line-height: 16px;
color: rgba(69, 71, 77, 1);
text-align: left;
margin-right: 12px;
}
.cancel-upload {
cursor: pointer;
}
.progress-bar {
height: 8px;
border-radius: 8px;
}
/* 进度条看作是由两个嵌套的<div>元素构成,外部的.progress-bar元素和内部的<div> */
.progress-bar div {
width: 638px;
height: 8px;
background-color: rgba(228, 231, 237, 1);
border-radius: 8px;
}
.span-container {
width: 690px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: PingFangSC-Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
.span-operate {
color: #1564FF;
cursor: pointer;
}
}
}
}
</style>
B.二次封装组件
mds-upload内部取消上传,但组件会阻止Lits的改变,并呈现上传失败的样式,再次点击才能返回到上传界面
<template>
<mds-upload ref="uploadRef" :path="uploadPath" name="file" :beforeUpload="onBeforeUpload"
:getUploadParams="getUploadParams" :disabled="false" :multiple="false" :accept="format" :onComplete="onUploadComplete"
:onError="onUploadError" :onChange="onListChange" listType="imgCard" :limit="1" :dragable="true">
<template v-slot:dragStyle>
<div class="custom-drag-style">
<img src="@/assets/img/upload.png" class="upload-icon" />
<div class="upload-click-drag">点击或拖拽上传文件</div>
<!-- 使用正则表达式替换所有点号 -->
<div class="upload-tip" slot="tip">请上传{{ format.replace(/\./g, "") }}格式文件,上传多份文件时以最后一次为准</div>
</div>
</template>
</mds-upload>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'vue-property-decorator'
@Component({
components: {}
})
export default class SingleUploadComp extends Vue {
@Prop({ required: true }) private uploadPath!: boolean
@Prop({ default: '.xls' }) private format!: string //形如".xls,.csv,.xlsx"
uploadExel: any = {
succList: []
}
uploaded:boolean= false
onBeforeUpload(files: File[], callback: (files: File[]) => void) {
callback(files)
}
getUploadParams(file: File, callback: (data: any) => void) {
const formData = new FormData()
formData.append('file', file)
const cbData = {
data: formData,
withCredentials: true
}
callback(cbData)
this.$refs.uploadRef.$el.querySelector('.upload-list-dragger').style.display = "none";
}
/**
* @param res 响应结果
* @param oriFile 原始文件
*/
onUploadComplete(res: any, oriFile: File) {
const errEle = this.$refs.uploadRef.$el.querySelector('.mds-upload-card-data-error')
if (res.data.code == 200) {
this.uploadExel = res.data.data;
this.$emit("update:uploadExel", this.uploadExel);
errEle.innerHTML = "上传成功!";
this.uploaded = true;
this.$emit("update:uploaded", this.uploaded);
} else {
errEle.innerHTML = res.data.msg;
errEle.style.color = "orange";
}
}
onUploadError(err: any, oriFile: File) {
const errEle = this.$refs.uploadRef.$el.querySelector('.mds-upload-card-data-erro')
errEle.innerHTML = "上传失败,请重新上传";
}
onListChange(uploadList: any[]) {
console.log('on list change')
if (uploadList.length == 0) {
if (this.uploaded) {
console.log("取消上传")
this.$emit('cancelUpload', this.uploadExel.fileLogId)
}
this.$refs.uploadRef.$el.querySelector('.upload-list-dragger').style.display = "block";
}
}
}
</script>
<style lang="scss" scoped>
::v-deep .upload-list-dragger {
// position: relative;
width: 800px;
height: 160px;
border: 1px dashed rgba(206, 212, 224, 1);
border-radius: 4px;
.custom-drag-style:hover {
background-color: #eef8ff;
}
.custom-drag-style {
height: 140px;
background-color: #fff;
flex-wrap: wrap;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
cursor: pointer;
.upload-icon {
width: 24px;
height: 24px;
}
.upload-click-drag {
width: 144px;
height: 24px;
font-family: PingFangSC-Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: rgba(69, 71, 77, 1);
text-align: left;
display: block;
}
.upload-tip {
width: 326px;
height: 24px;
font-family: PingFangSC-Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
color: rgba(168, 172, 179, 1);
text-align: left;
display: block;
}
}
}
::v-deep .mds-upload-card {
position: relative;
width: 800px;
height: 160px;
border: 1px dashed rgba(206, 212, 224, 1) !important;
border-radius: 4px;
}
::v-deep .mds-upload-card:hover .mds-upload-card-eyes {
display: none;
}
::v-deep .mds-upload-card-icon {
width: 71px;
height: 71px;
display: block;
&::before {
content: '';
display: block;
width: 71px;
height: 71px;
background: url('../../../assets/img/excel.png');
background-size: 71px 71px;
z-index: 9999;
}
}
::v-deep .mds-upload-card-data-name {
width: 114px;
height: 24px;
font-family: PingFangSC-Regular;
font-size: 16px;
font-weight: 400;
line-height: 24px;
color: rgba(69, 71, 77, 1);
text-align: left;
}
::v-deep .mds-upload-card-data {
.mds-upload-card-data-error {
color: #00AF5B;
height: 24px;
font-family: PingFangSC-Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
text-align: left;
}
.mds-upload-card-data-size {
height: 24px;
font-family: PingFangSC-Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
text-align: left;
}
}
</style>