346 lines
13 KiB
Vue
346 lines
13 KiB
Vue
<template>
|
||
<div id="global-uploader">
|
||
|
||
<!-- 上传 -->
|
||
<uploader
|
||
ref="uploader"
|
||
:options="options"
|
||
:autoStart="false"
|
||
@file-added="filesAdded"
|
||
@file-success="onFileSuccess"
|
||
@file-progress="onFileProgress"
|
||
@file-error="onFileError"
|
||
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">
|
||
<span class="title-span">上传列表</span>
|
||
<div class="operate">
|
||
<el-button @click="fileListShow" 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="iconfont el-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>
|
||
import SparkMD5 from 'spark-md5'
|
||
// import Cookies from 'js-cookie'
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
options: {
|
||
target: this.$http.defaults.baseURL+'filetransfer/uploadfile?projectSn='+this.$store.state.projectSn, // 目标上传 URL
|
||
chunkSize: 1024 * 1024,
|
||
fileParameterName: 'file',
|
||
maxChunkRetries: 3,
|
||
testChunks: false, //是否开启服务器分片校验
|
||
// 服务器分片校验函数,秒传及断点续传基础
|
||
// checkChunkUploadedByResponse: function (chunk, message) {
|
||
// console.log('message',chunk)
|
||
// let objMessage = JSON.parse(message);
|
||
// let data = objMessage.result;
|
||
// if (data.skipUpload) {
|
||
// return true;
|
||
// }else{
|
||
// return false
|
||
// }
|
||
// //
|
||
// // return (data.uploaded || []).indexOf(chunk.offset + 1) >= 0
|
||
// },
|
||
|
||
// headers: {
|
||
// Authorization:'Bearer'+' '+this.$store.state.userInfo.token,
|
||
// // token: Cookies.get('token', { domain: '.qiwenshare.com' }),
|
||
// 'Content-Type':'application/x-www-form-urlencoded'
|
||
// },
|
||
// method:'post',
|
||
// query: {
|
||
// projectSn:this.$store.state.projectSn
|
||
// }
|
||
},
|
||
attrs: {
|
||
accept: '*'
|
||
},
|
||
panelShow: false, //选择文件后,展示上传panel
|
||
collapse: false,
|
||
}
|
||
},
|
||
mounted() {
|
||
|
||
this.$EventBus.$on('openUploader', query => {
|
||
console.log('openUploader')
|
||
this.params = query || {};
|
||
// this.$refs.uploadBtn.click()
|
||
var e = document.createEvent('MouseEvent');
|
||
e.initEvent('click', false, false);
|
||
this.$refs.uploadBtn.$el.dispatchEvent(e);
|
||
});
|
||
},
|
||
computed: {
|
||
//Uploader实例
|
||
uploader() {
|
||
return this.$refs.uploader.uploader;
|
||
}
|
||
},
|
||
methods: {
|
||
// 添加文件的回调
|
||
filesAdded(file) {
|
||
console.log('filesAdded')
|
||
this.panelShow = true;
|
||
this.computeMD5(file);
|
||
},
|
||
onFileProgress(rootFile, file, chunk) {
|
||
console.log(`上传中 ${file.name},chunk:${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
|
||
},
|
||
// 文件上传成功的回调
|
||
onFileSuccess(rootFile, file, response, chunk){
|
||
if (response == "") {
|
||
this.statusSet(file.id, 'failed');
|
||
return
|
||
}
|
||
let data = response.data
|
||
|
||
let result = JSON.parse(response)
|
||
if (result.success) {
|
||
this.$message.success(`${file.name} - 上传完毕`)
|
||
|
||
this.statusRemove(file.id);
|
||
this.$EventBus.$emit('refreshList', "")
|
||
this.$EventBus.$emit('refreshStorage', "")
|
||
} else {
|
||
this.$message.error(result.errorMessage)
|
||
this.statusSet(file.id, 'failed');
|
||
}
|
||
console.log(chunk)
|
||
},
|
||
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();
|
||
let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
|
||
let currentChunk = 0;
|
||
const chunkSize = 1 * 1024 * 1024;
|
||
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) {
|
||
// 将自定义参数直接加载uploader实例的opts上
|
||
Object.assign(this.uploader.opts, {
|
||
query: {
|
||
...this.params,
|
||
}
|
||
})
|
||
file.uniqueIdentifier = md5;
|
||
file.resume();
|
||
this.statusRemove(file.id);
|
||
},
|
||
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'
|
||
},
|
||
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() {
|
||
this.$off('openUploader');
|
||
},
|
||
components: {}
|
||
}
|
||
</script>
|
||
|
||
<style lang="stylus" scoped>
|
||
#global-uploader {
|
||
position: fixed;
|
||
z-index: 20;
|
||
right: 15px;
|
||
bottom: 15px;
|
||
.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, .2);
|
||
.file-title {
|
||
display: flex;
|
||
height: 40px;
|
||
line-height: 40px;
|
||
padding: 0 15px;
|
||
border-bottom: 1px solid #ddd;
|
||
.title-span {
|
||
padding-left: 0;
|
||
margin-bottom: 0;
|
||
font-size: 17px;
|
||
color: #303133;
|
||
}
|
||
.operate {
|
||
flex: 1;
|
||
text-align: right;
|
||
}
|
||
}
|
||
.file-list {
|
||
position: relative;
|
||
height: 240px;
|
||
overflow-x: hidden;
|
||
overflow-y: auto;
|
||
background-color: #fff;
|
||
> li {
|
||
background-color: #fff;
|
||
}
|
||
}
|
||
&.collapse {
|
||
.file-title {
|
||
background-color: #E7ECF2;
|
||
}
|
||
}
|
||
}
|
||
.no-file {
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
font-size: 16px;
|
||
}
|
||
/deep/.uploader-file-icon {
|
||
&:before {
|
||
content: '' !important;
|
||
}
|
||
&[icon=image] {
|
||
background: url(/assets/images/file/file_pic.png);
|
||
}
|
||
&[icon=video] {
|
||
background: url(/assets/images/file/file_video.png);
|
||
}
|
||
&[icon=document] {
|
||
background: url(/assets/images/file/file_txt.png);
|
||
}
|
||
}
|
||
/deep/.uploader-file-actions > span {
|
||
margin-right: 6px;
|
||
}
|
||
}
|
||
/* 隐藏上传按钮 */
|
||
#global-uploader-btn {
|
||
position: absolute;
|
||
clip: rect(0, 0, 0, 0);
|
||
}
|
||
</style> |