<template>
|
<div id="global-uploader">
|
<!-- 上传 -->
|
<uploader ref="uploader" :options="options" :autoStart="false" @file-added="onFileAdded"
|
@file-success="onFileSuccess" @file-progress="onFileProgress" @file-error="onFileError"
|
@file-complete="onFileComplete" class="uploader-app">
|
<uploader-unsupport></uploader-unsupport>
|
|
<uploader-btn id="global-uploader-btn" :attrs="attrs" ref="uploadBtn">选择文件</uploader-btn>
|
|
<uploader-list v-show="panelShow">
|
<div class="file-panel" slot-scope="props" :class="{'collapse': collapse}">
|
<div class="file-title">
|
<h2>文件列表</h2>
|
<div class="operate">
|
<el-button @click="fileListShow" type="text" :title="collapse ? '展开':'折叠' ">
|
<i class="iconfont" :class="collapse ? 'inuc-fullscreen': 'inuc-minus-round'"></i>
|
</el-button>
|
<el-button @click="close" type="text" title="关闭">
|
<i class="iconfont icon-close"></i>
|
</el-button>
|
</div>
|
</div>
|
|
<ul class="file-list">
|
<li v-for="file in props.fileList" :key="file.id">
|
<uploader-file :class="'file_' + file.id" 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>
|
/**
|
* 全局上传插件
|
* 调用方法:Bus.$emit('openUploader', {}) 打开文件选择框,参数为需要传递的额外参数
|
* 监听函数:Bus.$on('fileAdded', fn); 文件选择后的回调
|
* Bus.$on('fileSuccess', fn); 文件上传成功的回调
|
*/
|
|
import {
|
ACCEPT_CONFIG
|
} from '@/js/config';
|
import Bus from '@/js/bus';
|
import SparkMD5 from 'spark-md5';
|
import $ from 'jquery';
|
import Element from 'element-ui';
|
|
// 这两个是我自己项目中用的,请忽略
|
// import {Ticket} from '@/assets/js/utils';
|
// import api from '@/api';
|
|
export default {
|
data() {
|
return {
|
store: {
|
|
},
|
retrySet: {
|
|
},
|
paramSet: {},
|
options: {
|
//target: "http://192.168.40.222:8089/aa",
|
chunkSize: 6 * 1024 * 1024,
|
fileParameterName: 'upFile',
|
forceChunkSize: true,
|
maxChunkRetries: 3,
|
testChunks: true, //是否开启服务器分片校验
|
// 服务器分片校验函数,秒传及断点续传基础
|
checkChunkUploadedByResponse: function(chunk, message) {
|
let resp = JSON.parse(message);
|
if (!resp.success) {
|
console.error("校验分片异常")
|
return false;
|
}
|
|
//已经秒传
|
let indexArray=resp.result.chunkIndex;
|
if (resp.result.chunkIndex != null && indexArray[chunk.offset] > 0) {
|
console.log(chunk.offset + 1 + "已经秒传..")
|
return true;
|
}
|
console.log(chunk.offset + 1 + "正常上传..")
|
|
return false;
|
},
|
headers: {
|
// Authorization: Ticket.get() && "Bearer " + Ticket.get().access_token
|
},
|
query() {
|
//aa: "bbb";
|
}
|
},
|
attrs: {
|
accept: ACCEPT_CONFIG.getAll()
|
},
|
panelShow: true, //选择文件后,展示上传panel
|
collapse: true,
|
}
|
},
|
mounted() {
|
Bus.$on('openUploader', query => {
|
this.params = query || {};
|
|
if (this.$refs.uploadBtn) {
|
$('#global-uploader-btn').click();
|
}
|
});
|
},
|
computed: {
|
//Uploader实例
|
uploader() {
|
return this.$refs.uploader.uploader;
|
}
|
},
|
methods: {
|
onFileAdded(file) {
|
this.panelShow = true;
|
this.computeMD5(file);
|
|
Bus.$emit('fileAdded');
|
},
|
|
onFileComplete(rootFile) {
|
// console.log(11111)
|
// console.log(rootFile)
|
},
|
onFileProgress(rootFile, file, chunk) {
|
|
// console.log("onFileProgress")
|
// console.log(rootFile)
|
// console.log(chunk)
|
console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
|
},
|
onFileSuccess(rootFile, file, response, chunk) {
|
let res = JSON.parse(response);
|
console.log("上传完成回调" + response)
|
// 服务器自定义的错误(即虽返回200,但是是错误的情况),这种错误是Uploader无法拦截的
|
if (!res.success) {
|
this.$message({
|
message: res.errmsg,
|
type: 'error'
|
});
|
// 文件状态设为“失败”
|
this.statusSet(file.id, 'failed');
|
return
|
}
|
var $this = this;
|
|
// 如果服务端返回需要合并 todo
|
|
|
// 文件状态设为“合并中”
|
$this.statusRemove(file.id);
|
$this.statusSet(file.id, 'merging');
|
|
//请求合并
|
this.$axios.get('http://localhost:8001/commonFileUpload/doMerge?mainMD5=' + res.result.mainMD5)
|
.then(function(response) {
|
|
if (response.data.success) {
|
// 文件合并成功
|
Bus.$emit('fileSuccess');
|
$this.statusRemove(file.id);
|
|
console.log('文件合并成功');
|
console.log('上传成功');
|
console.log("结束" + new Date().getTime())
|
} else {
|
//请求合并异常
|
$this.statusRemove(file.id);
|
$this.statusSet(file.id, 'mergingExcetion');
|
}
|
|
})
|
.catch(function(error) {
|
console.log(error);
|
$this.$message({
|
message: "合并异常",
|
type: 'error'
|
});
|
$this.statusRemove(file.id);
|
$this.statusSet(file.id, 'mergingExcetion');
|
});
|
|
|
},
|
onFileError(rootFile, file, response, chunk) {
|
this.$message({
|
message: response,
|
type: 'error'
|
})
|
},
|
|
/**
|
* 计算md5,实现断点续传及秒传
|
* @param file
|
*/
|
computeMD5(file) {
|
let fileReader = new FileReader();
|
let time = new Date().getTime();
|
console.log("开始" + new Date().getTime())
|
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
|
let currentChunk = 0;
|
const chunkSize = 6 * 1024 * 1024; //md5计算时切片大小
|
let chunks = Math.ceil(file.size / chunkSize);
|
|
let spark = new SparkMD5.ArrayBuffer();
|
|
// 文件状态设为"计算MD5"
|
this.statusSet(file.id, 'md5');
|
// file.pause();
|
|
loadNext();
|
|
fileReader.onload = (e => {
|
spark.append(e.target.result);
|
|
if (currentChunk < chunks) {
|
currentChunk++;
|
loadNext();
|
|
// 实时展示MD5的计算进度
|
this.$nextTick(() => {
|
$(`.myStatus_${file.id}`).text('校验MD5 ' + ((currentChunk / chunks) * 100)
|
.toFixed(0) + '%')
|
})
|
} else {
|
let md5 = spark.end();
|
this.computeMD5Success(md5, 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();
|
};
|
|
function loadNext() {
|
let start = currentChunk * chunkSize;
|
let end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;
|
|
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end));
|
}
|
},
|
|
computeMD5Success(md5, file) {
|
//1.初始化分片上传请求,获取分片参数
|
|
//计算内网ip是否联通,选择上传接口url,如果是
|
|
let $this = this;
|
this.$axios.post('http://localhost:8001/commonFileUpload/createShardPost', {
|
mainMD5: md5,
|
size: file.size,
|
fileName: file.name,
|
ext: file.name.substring(file.name.lastIndexOf(".") + 1, file.name.length), //文件名没有点号会异常
|
type: file.type,
|
})
|
.then(function(response) {
|
console.log(response);
|
if (response.data.success) {
|
let uploadOptions = response.data.result;
|
|
// 将自定义参数直接加载uploader实例的opts上
|
|
let opts = $this.uploader.opts;
|
opts.target = "http://localhost:8001/commonFileUpload/doUpload"; //上传接口
|
// opts.target = function(file, chunk, isTest, obj) {
|
// if (isTest) {
|
// return "http://localhost:8001/commonFileUpload/doUpload?mainMD5=" + file
|
// .uniqueIdentifier; //上传接口
|
// }
|
// return uploadOptions.uploadUrlList[chunk.offset];
|
|
// }
|
|
// opts.processResponse = function(response, cb, file, chunk) {
|
// $this.$axios.get(
|
// 'http://localhost:8001/commonFileUpload/updateProgress?mainMD5=' +
|
// file.uniqueIdentifier + "&chunk=" + (chunk.offset + 1) + "&chunkSize=" +
|
// chunk.loaded)
|
// .then(function(response) {
|
// cb(null, response.data);
|
// })
|
// .catch(function(error) {
|
// cb(null, error);
|
// });
|
// }
|
|
opts.processParams = function(params,file,chunk,isTest) {
|
let temp= $this.paramSet[file.uniqueIdentifier];
|
// chunkMD5:opts.chunkNumber,
|
// chunkNumber:opts.chunkNumber
|
temp.chunkMD5=chunk.offset+1;
|
temp.chunkNumber=chunk.offset+1;
|
return temp;
|
}
|
// console.log("ddd")
|
|
// if (chunk.offset == 0) {
|
// return "http://192.168.40.149:9000/test2//temp/70d52b8181e78089797a1bd0986bbf98/70d52b8181e78089797a1bd0986bbf98-1.chuk?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20211203%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20211203T094116Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7bc2d34e1ea53d7b2c8b2954fe8b72a4715dd54f097c29f02d4f9eba2462ec96"
|
// }
|
|
// if (chunk.offset == 1) {
|
// return "http://192.168.40.149:9000/test2//temp/70d52b8181e78089797a1bd0986bbf98/70d52b8181e78089797a1bd0986bbf98-2.chuk?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=minioadmin%2F20211203%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20211203T094128Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=51cffbc815dc80d09b938fd460dfe4b54fbeddde50806ef45bc0f5665edc77c5"
|
// }
|
|
// return "http://localhost:8001/commonFileUpload/doUpload?off=" + (chunk
|
// .offset + 1);
|
// }
|
// opts.chunkSize = uploadOptions.chunkSize; //切片大小
|
|
// opts.method = "octet";
|
opts.method = "multipart";
|
opts.testMethod = "GET";
|
opts.uploadMethod = "POST";
|
|
$this.params.mainMD5 = md5; //mainMD5参数
|
//$this.params.chunkNumber = opts.chunkNumber; //分片索引参数
|
Object.assign(opts, {
|
query: {
|
...$this.params,
|
}
|
})
|
//文件重新分块
|
file.uploader.opts.chunkSize = uploadOptions.chunkSize;
|
file.bootstrap();
|
$this.paramSet[md5] = {
|
mainMD5: md5,
|
chunkSize: uploadOptions.chunkSize,
|
// chunkMD5:opts.chunkNumber,
|
// chunkNumber:opts.chunkNumber
|
}
|
|
file.uniqueIdentifier = md5;
|
file.resume();
|
$this.statusRemove(file.id);
|
} else {
|
//中断上传
|
file.cancel();
|
}
|
})
|
.catch(function(error) {
|
console.log(error);
|
//中断上传
|
file.cancel();
|
return;
|
});
|
|
|
},
|
|
fileListShow() {
|
let $list = $('#global-uploader .file-list');
|
|
if ($list.is(':visible')) {
|
$list.slideUp();
|
this.collapse = true;
|
} else {
|
$list.slideDown();
|
this.collapse = false;
|
}
|
},
|
close() {
|
this.uploader.cancel();
|
|
this.panelShow = false;
|
},
|
|
/**
|
* 新增的自定义的状态: 'md5'、'transcoding'、'failed'
|
* @param id
|
* @param status
|
*/
|
statusSet(id, status) {
|
let statusMap = {
|
md5: {
|
text: '校验MD5',
|
bgc: '#fff'
|
},
|
merging: {
|
text: '合并中',
|
bgc: '#e2eeff'
|
},
|
mergingExcetion: {
|
text: '合并异常',
|
bgc: '#e2eeff'
|
},
|
transcoding: {
|
text: '转码中',
|
bgc: '#e2eeff'
|
},
|
failed: {
|
text: '上传失败',
|
bgc: '#e2eeff'
|
}
|
}
|
|
this.$nextTick(() => {
|
$(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
|
'position': 'absolute',
|
'top': '0',
|
'left': '0',
|
'right': '0',
|
'bottom': '0',
|
'zIndex': '1',
|
'backgroundColor': statusMap[status].bgc
|
}).text(statusMap[status].text);
|
})
|
},
|
statusRemove(id) {
|
this.$nextTick(() => {
|
$(`.myStatus_${id}`).remove();
|
})
|
},
|
|
error(msg) {
|
this.$notify({
|
title: '错误',
|
message: msg,
|
type: 'error',
|
duration: 2000
|
})
|
}
|
},
|
watch: {},
|
destroyed() {
|
Bus.$off('openUploader');
|
},
|
components: {}
|
}
|
</script>
|
|
<style>
|
#global-uploader {
|
position: fixed;
|
z-index: 20;
|
right: 15px;
|
bottom: 15px;
|
}
|
|
#global-uploader .uploader-app {
|
width: 1820px;
|
}
|
|
#global-uploader .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);
|
}
|
|
#global-uploader .file-panel .file-title {
|
display: flex;
|
height: 40px;
|
line-height: 40px;
|
padding: 0 15px;
|
border-bottom: 1px solid #ddd;
|
}
|
|
#global-uploader .file-panel .file-title .operate {
|
flex: 1;
|
text-align: right;
|
}
|
|
#global-uploader .file-panel .file-list {
|
position: relative;
|
height: 240px;
|
overflow-x: hidden;
|
overflow-y: auto;
|
background-color: #fff;
|
}
|
|
#global-uploader .file-panel .file-list>li {
|
background-color: #fff;
|
}
|
|
#global-uploader .file-panel.collapse .file-title {
|
background-color: #E7ECF2;
|
}
|
|
#global-uploader .no-file {
|
position: absolute;
|
top: 50%;
|
left: 50%;
|
transform: translate(-50%, -50%);
|
font-size: 16px;
|
}
|
|
#global-uploader .uploader-file-icon:before {
|
content: "" !important;
|
}
|
|
#global-uploader .uploader-file-actions>span {
|
margin-right: 6px;
|
}
|
|
#global-uploader .uploader-file-icon[icon=image] {
|
background: url(../images/image-icon.png);
|
}
|
|
#global-uploader .uploader-file-icon[icon=video] {
|
background: url(../images/video-icon.png);
|
}
|
|
#global-uploader .uploader-file-icon[icon=document] {
|
background: url(../images/text-icon.png);
|
}
|
|
/* 隐藏上传按钮 */
|
#global-uploader-btn {
|
position: absolute;
|
clip: rect(0, 0, 0, 0);
|
}
|
</style>
|