赞
踩
首先为什么要分片上传?
大部分小白使用element-ui中上传组件,但是直接用它上传大文件会 超时 或者Request Entity Too Large(请求实体太大)这种问题。
我的这个可以自定义样式(没懂的留言给我)
上代码:
<template> <div id="global-uploader" :class="{'global-uploader-single': !global}"> <uploader ref="uploader" :options="initOptions" :fileStatusText="fileStatusText" :autoStart="false" @file-added="onFileAdded" @file-success="onUploadSuccess" @file-progress="onFileProgress" @file-error="onFileError" class="uploader-app"> <uploader-unsupport></uploader-unsupport> <div @click="clickUploader" :style="{width}"> <uploader-drop class="custom_uploader_drop" :style="{width}" v-if="!isUpload" v-loading="isMd5Upload" element-loading-text="正在读取中"> <slot name="customContent"></slot> <uploader-btn ref="uploadBtn" style="display: none" /> </uploader-drop> <div v-if="isUpload" class="upload_process_box"> <div>文件名:{{fileName}}</div> <el-progress :percentage="uploadProcessNum"></el-progress> <!-- <el-progress v-else :percentage="syncUploadProcessNum"></el-progress>--> <div v-if="isMd5Upload"> 正在读取文件中 - {{md5ProgressText}} </div> <div v-if="!isSyncUpload&&!isMd5Upload"> 正在上传至服务器 - <span>{{uploadSpeed}} M/s</span> </div> <div v-if="isSyncUpload"> 正在上传至华为云,稍等会儿 (*^<i class="el-icon-loading" />^*) </div> </div> </div> <!-- <uploader-list v-show="panelShow">--> <!-- <div class="file-panel" slot-scope="props" :class="{ collapse: collapse }">--> <!-- <div class="file-title">--> <!-- <div class="title">文件列表</div>--> <!-- <div class="operate">--> <!-- <el-button @click="collapse = !collapse" type="text" :title="collapse ? '展开' : '折叠'">--> <!-- <i class="iconfont" :class="collapse ? 'el-icon-full-screen' : 'el-icon-minus'"></i>--> <!-- </el-button>--> <!-- <el-button @click="close" type="text" title="关闭">--> <!-- <i class="el-icon-close"></i>--> <!-- </el-button>--> <!-- </div>--> <!-- </div>--> <!-- <ul class="file-list">--> <!-- <li--> <!-- class="file-item"--> <!-- v-for="file in props.fileList"--> <!-- :key="file.id">--> <!-- <uploader-file--> <!-- :class="['file_' + file.id, customStatus]"--> <!-- ref="files"--> <!-- :file="file"--> <!-- :list="true"--> <!-- ></uploader-file>--> <!-- </li>--> <!-- <div class="no-file" v-if="!props.fileList.length">--> <!-- <i class="iconfont icon-empty-file"></i> 暂无待上传文件--> <!-- </div>--> <!-- </ul>--> <!-- </div>--> <!-- </uploader-list>--> </uploader> </div> </template> <script> import { ACCEPT_CONFIG } from './js/config' import Bus from './js/bus' import SparkMD5 from 'spark-md5' // import { mergeSimpleUpload } from '@/api' // 封装的网络请求promise import { uploadFile,startOriginUploadFile, queryUploadFileProgress,startMergeFile } from './js/service.js' import { getToken } from "@/utils/auth"; // let urll = process.env.VUE_APP_BASE_API + '/wk-upload/upload/upload'; // let urll = 'http://192.168.1.111:9999/wk-upload/upload/upload'; export default { props: { global: { type: Boolean, default: false }, // 发送给服务器的额外参数 params: { type: Object }, options: { type: Object }, // 宽度 width: { type: [Number,String], default: 100, validator(value) { return typeof value === 'String' ? value : (value + 'px') } } }, data() { return { // actionResourceUrl: process.env.VUE_APP_BASE_API + '/upload/upload', initOptions: { target: this.$api.actionBigFileUrl, headers: { Authorization: "Bearer " + getToken(), // 'Content-Type': 'application/json;charset=UTF-8', }, chunkSize: 1024*1024*10,//10485760 //10000000, //1024 * 1024 * 3, //3MB 10000000 // chunkSize: '2048000', fileParameterName: 'file', //上传文件时文件的参数名,默认file singleFile: true, // 启用单个文件上传。上传一个文件后,第二个文件将超过现有文件,第一个文件将被取消。 maxChunkRetries: 3, //最大自动失败重试上传次数 testChunks: false, //是否开启服务器分片校验 // simultaneousUploads: 3, //并发上传数 // 服务器分片校验函数,秒传及断点续传基础 // checkChunkUploadedByResponse: (chunk, message) => { // let skip = false // // // // try { // // let objMessage = JSON.parse(message) // // if (objMessage.skipUpload) { // // skip = true // // } else { // // skip = (objMessage.uploaded || []).indexOf(chunk.offset + 1) >= 0 // // } // // } catch (e) {} // // return skip // }, query: (file, chunk) => { return { ...file.params } } }, fileStatusText: { success: '上传成功', error: '上传失败', uploading: '上传中', paused: '已暂停', waiting: '等待上传' }, panelShow: false, //选择文件后,展示上传panel collapse: false, customParams: {}, customStatus: '', isUploadOk: false, isUploadErr: false, isStartUpload: false, // 开始上传 md5ProgressText: 0, isMd5Upload: false, // 计算md5状态 isUpload: false, // 正在上传 uploadProcessNum: 0, // 上传进度 uploadSpeed: 0, // 上传速度 fileName: '', // 文件名 isSyncUpload: false, // 是否在同步远程数据 syncUploadProcessNum: 0, // 同步远程数据 response: null, // 上传成功 queryTimer: null, // 轮询计时器 socket: null, } }, computed: { // Uploader实例 uploader() { return this.$refs.uploader.uploader } }, methods: { // 自定义options customizeOptions(opts) { // 自定义上传url if (opts.target) { this.uploader.opts.target = opts.target } // 是否可以秒传、断点续传 if (opts.testChunks !== undefined) { this.uploader.opts.testChunks = opts.testChunks } // merge 的方法,类型为Function,返回Promise this.mergeFn = opts.mergeFn || uploadFile // 自定义文件上传类型 let input = document.querySelector('#global-uploader-btn input') let accept = opts.accept || ACCEPT_CONFIG.getAll() input.setAttribute('accept', accept.join()) }, clickUploader(e) { // console.log(e) this.$refs.uploadBtn.$el.click() }, // 上传前 onFileAdded(file) { // this.panelShow = true // this.emit('fileAdded') // 将额外的参数赋值到每个文件上,以不同文件使用不同params的需求 // file.params = this.customParams // 计算MD5 this.computeMD5(file).then((result) => this.startUpload(result)) }, /** * 计算md5值,以实现断点续传及秒传 * @param file * @returns Promise */ computeMD5(file) { let fileReader = new FileReader() let time = new Date().getTime() let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice let currentChunk = 0 const chunkSize = this.initOptions.chunkSize; let chunks = Math.ceil(file.size / chunkSize) let spark = new SparkMD5.ArrayBuffer() // 获取文件名 const fileInfo = file.uploader.fileList[0] this.fileName = fileInfo.name // 文件状态设为"计算MD5" // this.statusSet(file.id, 'md5') this.isMd5Upload = true; this.isUpload = true; file.pause() loadNext() return new Promise((resolve, reject) => { fileReader.onload = (e) => { spark.append(e.target.result) if (currentChunk < chunks) { currentChunk++ loadNext() // 实时展示MD5的计算进度 this.$nextTick(() => { this.md5ProgressText = ((currentChunk/chunks)*100).toFixed(0)+'%' }) } else { let md5 = spark.end() // md5计算完毕 resolve({md5, file}) // console.log(file); // console.log( // `MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${ // new Date().getTime() - time // } ms` // ) } } fileReader.onerror = function () { this.error(`文件${file.name}读取出错,请检查该文件`) file.cancel() reject() } }) function loadNext() { let start = currentChunk * chunkSize let end = start + chunkSize >= file.size ? file.size : start + chunkSize fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end)) } }, // md5计算完毕,开始上传 startUpload({md5, file}) { file.uniqueIdentifier = md5 file.resume(); this.isMd5Upload = false; this.isStartUpload = true; // this.statusRemove(file.id) }, // 上传中 onFileProgress(rootFile, file, chunk) { // console.log( // `上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${ // chunk.endByte / 1024 / 1024 // }` // ) // let index = this.findFileById(file.uniqueIdentifier),//通过index来获取对应的文件progress // p = Math.round(file.progress()*100); // if(index > -1){ // if(p < 100){ // console.log(p) // } // this.fileList[index].status = file.status; // } // console.log(rootFile) // let p = this.$refs.uploader.progress() // console.log(p) let uploader = this.$refs.uploader.uploader; this.uploadProcessNum = Math.floor(uploader.progress() * 100) this.emit('onUploadProcess',uploader.progress()); // let averageSpeed = uploader.averageSpeed let averageSpeed = uploader.averageSpeed // let timeRemaining = uploader.timeRemaining() // let uploadedSize = uploader.sizeUploaded() let speed = averageSpeed / 1000 / 10; this.uploadSpeed = speed.toFixed(2); // console.log(speed.toFixed(2) + 'M/s') }, // 上传中转站成功 onUploadSuccess(rootFile, file, response, chunk) { let res1 = JSON.parse(response); if(res1.code===200) { // 开始merge console.log(rootFile.uniqueIdentifier); const body = { totalChunks: rootFile.chunks.length, md5File: rootFile.uniqueIdentifier, fileName: rootFile.name } console.log(file) startMergeFile(body).then(res=>{ // 上传到华为云 this.onUploadSuccessFinally({rootFile, file, res, chunk}) }) }else{ this.error('上传失败') this.$emit('onUploadError',res1) } // 服务端自定义的错误(即http状态码为200,但是是错误的情况),这种错误是Uploader无法拦截的 // if (!res.result) { // this.error(res.message) // // 文件状态设为“失败” // this.statusSet(file.id, 'failed') // return // } // return; // 如果服务端返回了需要合并的参数 // if(res.needMerge) { // // 文件状态设为“合并中” // this.statusSet(file.id, 'merging') // // this.mergeFn({ // tempName: res.tempName, // fileName: file.name, // ...file.params // }) // .then((res) => { // // 文件合并成功 // this.emit('fileSuccess') // // this.statusRemove(file.id) // }) // .catch((e) => {}) // // // 不需要合并 // } else { // this.emit('fileSuccess') // console.log('上传成功') // } }, // 开始上传至华为云 onUploadSuccessFinally(data) { // console.log(data) const {rootFile, file, res, chunk} = data let body = { id: res.data.id } // try { // 请求上传远程开始 this.isSyncUpload = true; startOriginUploadFile(body).then(res1=>{ this.isUpload = false; this.isSyncUpload = false; this.$emit('onUploadSuccess',res1) }).catch(err=>{ this.isUpload = false; this.$emit('onUploadError',e) this.error('上传华为云失败') clearInterval(this.queryTimer) }) // this.uploadProcessNum = 0; // this.queryTimer = setInterval(()=>{ // this.queryProcess(body).then(res=>{ // console.log(res); // this.isSyncUpload = true; // if(res.data.process&&res.data.process<100) { // this.syncUploadProcessNum = res.data.process // }else{ // this.syncUploadProcessNum = 100; // this.isUploadOk = true; // this.isUpload = false; // this.$emit('onUploadSuccess',res) // clearInterval(this.queryTimer) // } // }).catch(err=>{ // this.isUpload = false; // this.$emit('onUploadError',err) // this.error('上传华为云失败') // clearInterval(this.queryTimer) // }) // },3000) // } catch (e) { // this.isUpload = false; // this.$emit('onUploadError',e) // this.error('上传华为云失败') // clearInterval(this.queryTimer) // } // console.log('上传成功') // this.$emit('onUploadSuccess',res); // setInterval(()=>{ // // },1000) // queryUploadFileProgress().then(res=>{}) }, // 获取上传华为云进度 queryProcess(body) { // return new Promise((resolve,reject)=>{ // queryUploadFileProgress(body).then(res=>{ // if(res.code===200) { // resolve(res) // }else{ // reject(res) // } // }).catch(err=>{ // reject(err) // }) // }) }, // 上传失败 onFileError(rootFile, file, response, chunk) { this.error('上传失败'); this.$emit('onUploadError',response); this.isUpload = false; }, // 取消 close() { this.uploader.cancel() this.panelShow = false }, /** * 新增的自定义的状态: 'md5'、'merging'、'transcoding'、'failed' * @param id * @param status */ statusSet(id, status) { let statusMap = { md5: { text: '读取文件中', bgc: '#fff' }, merging: { text: '合并中', bgc: '#e2eeff' }, transcoding: { text: '转码中', bgc: '#e2eeff' }, failed: { text: '上传失败', bgc: '#e2eeff' } } this.customStatus = status // this.$nextTick(() => { // const statusTag = document.createElement('p') // statusTag.className = `custom-status-${id} custom-status` // statusTag.innerText = statusMap[status].text // statusTag.style.backgroundColor = statusMap[status].bgc // // const statusWrap = document.querySelector(`.file_${id} .uploader-file-status`) // statusWrap.appendChild(statusTag) // }) }, // 移除状态 statusRemove(id) { this.customStatus = '' // this.$nextTick(() => { // const statusTag = document.querySelector(`.custom-status-${id}`) // statusTag.remove() // }) }, emit(e) { // Bus.$emit(e) // this.$emit(e) }, error(msg) { this.$notify({ title: '错误', message: msg, type: 'error', duration: 2000 }) } }, beforeDestroy() { clearInterval(this.queryTimer) // this.socket.onclose = (ee)=>{ // // } } } </script> <style lang="scss"> #global-uploader { &:not(.global-uploader-single) { position: fixed; z-index: 20; right: 15px; bottom: 15px; box-sizing: border-box; } .uploader-app { width: 520px; } .file-panel { background-color: #fff; border: 1px solid #e2e2e2; border-radius: 7px 7px 0 0; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); .file-title { display: flex; height: 40px; line-height: 40px; padding: 0 15px; border-bottom: 1px solid #ddd; .operate { flex: 1; text-align: right; i { font-size: 18px; } } } .file-list { position: relative; height: 240px; overflow-x: hidden; overflow-y: auto; background-color: #fff; transition: all 0.3s; .file-item { background-color: #fff; } } &.collapse { .file-title { background-color: #e7ecf2; } .file-list { height: 0; } } } .no-file { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 16px; } .uploader-file { &.md5 { .uploader-file-resume { display: none; } } } .uploader-file-icon { &:before { content: '' !important; } //&[icon='image'] { // background: url(./images/image-icon.png); //} //&[icon=audio] { // background: url(./images/audio-icon.png); // background-size: contain; //} //&[icon='video'] { // background: url(./images/video-icon.png); //} //&[icon='document'] { // background: url(./images/text-icon.png); //} //&[icon=unknown] { // background: url(./images/zip.png) no-repeat center; // background-size: contain; //} } .uploader-file-actions > span { margin-right: 6px; } .custom-status { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: 1; } } .custom_uploader_drop { background-color: rgba(176, 172, 172, 0.1) !important; cursor: pointer; border-radius: 5px; &:hover { border-color: #409eff !important; } } .upload_process_box { border-radius: 5px; border: 1px dashed #409eff; & > div { padding: 5px 0; } } /* 隐藏上传按钮 */ #global-uploader-btn { position: absolute; clip: rect(0, 0, 0, 0); } .global-uploader-single { #global-uploader-btn { position: relative; } } //.dot_tran { // animation-name: dot_tran_keyframe; // animation-duration: 0.1s; // animation-iteration-count: infinite; // .dot_tran_{ // &:after { // content: ''; // } // } // //} //@keyframes dot_tran_keyframe { // 0% { // .dot_tran_ { // &:after { // content: '.'; // } // } // } // 50% { // .dot_tran_ { // &:after { // content: '..'; // } // } // // } // 100% { // .dot_tran_ { // &:after { // content: '...'; // } // } // } //} </style>
<customStardingUploader v-if="!form.resourceUrl" width="400px" @onUploadSuccess="handleAvatarSuccessResource" @onUploadError="handleVideoErrorResource"> <!-- 自定义内容 --> <template #customContent> <div style="margin: 0 auto;width: fit-content;text-align: center"> <div>将文件拖拽到此处</div> <div>或点击上传</div> </div> </template> </customStardingUploader> // 上传视频成功 handleAvatarSuccessResource(res, file) { this.form.resourceUrl = res.data; this.uploadLoading = false; }, // 上传视频失败 handleVideoErrorResource(err) { this.uploadLoading = false; },
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。