目录
定时模拟进度条
方法
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].fileUrldocument.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= falseonBeforeUpload(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>