466 lines
15 KiB
Vue
466 lines
15 KiB
Vue
<template>
|
||
<div>
|
||
<a-row type="flex">
|
||
<a-col flex="auto">
|
||
<a-select placeholder="选择模式" option-filter-prop="children" size="large" v-model="schemaName" style="width: 200px;">
|
||
<a-select-option v-for="d in allSchemaName" :key="d">
|
||
{{ d }}
|
||
</a-select-option>
|
||
</a-select>
|
||
<a-upload
|
||
class="upload-wrapper"
|
||
:showUploadList="false"
|
||
:disabled="maxFile && tableDate.length >= maxFile"
|
||
:accept="accept"
|
||
:before-upload="beforeUpload"
|
||
:customRequest="customRequestUpload"
|
||
:multiple="true"
|
||
@change="handleChange">
|
||
<!-- <a-input style="width:100%" :value="fileList.map(item=> item.name).join('、')" /> -->
|
||
<a-button :disabled="maxFile && tableDate.length >= maxFile">
|
||
<a-icon type="upload" /> 文档上传
|
||
</a-button>
|
||
</a-upload>
|
||
</a-col>
|
||
<a-col flex="100px" v-if="false" >
|
||
<a-button @click="customRequestUpload">
|
||
<a-icon type="upload" /> 文档上传
|
||
</a-button>
|
||
</a-col>
|
||
</a-row>
|
||
<div class="result-wrapper" :style="{minHeight: boxHeight + 'px'}" >
|
||
<div class="item" v-for="(file,idx) in tableDate" :key="file.uid">
|
||
<div class="content">
|
||
<div class="body">
|
||
<div class="fileName"> {{ file.fileName }}</div>
|
||
<div>
|
||
<a-space>
|
||
<a-button v-if="canDownload" type="link" style="padding: 0" @click="handleDownload(file)">下载</a-button>
|
||
<a-popconfirm
|
||
title="是否确认删除该文件?"
|
||
@confirm="handleDelete(file, idx)"
|
||
>
|
||
<a-icon style="margin-left:10px; cursor: pointer;" type="delete" />
|
||
</a-popconfirm>
|
||
</a-space>
|
||
</div>
|
||
<a-progress
|
||
class="progress"
|
||
v-if="autoUpload"
|
||
:percent="file.percentage"
|
||
:strokeWidth="3"
|
||
:showInfo="false" />
|
||
</div>
|
||
</div>
|
||
<slot name="extra" :idx="idx" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import SparkMD5 from 'spark-md5'
|
||
import { getAction,postAction,downloadFile2 } from '@/api/manage'
|
||
import { verifyFileExist } from '@/api/fileapi'
|
||
|
||
|
||
export default {
|
||
name: 'SliceUpload',
|
||
props: {
|
||
boxHeight: {
|
||
type: Number,
|
||
default: 300
|
||
},
|
||
// eslint-disable-next-line vue/require-default-prop
|
||
maxFile: Number,
|
||
accept: {
|
||
type: String,
|
||
default: '.jpg,.png,.doc,.docx,.pdf,.txt,.jpeg'
|
||
},
|
||
autoUpload: { // 自动上传
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
canRepeat: { // 是否可上传重复的文件
|
||
type: Boolean,
|
||
default: true
|
||
},
|
||
canDownload: { // 是否显示下载按钮
|
||
type: Boolean,
|
||
default: false
|
||
},
|
||
dataLinkType: {
|
||
type: String,
|
||
default: '1'
|
||
},
|
||
},
|
||
data () {
|
||
return {
|
||
fileMD5: {},
|
||
isStop: false,
|
||
fileList: [],
|
||
tableDate: [],
|
||
schemaName:"",
|
||
allSchemaName:[]
|
||
}
|
||
},
|
||
created () {
|
||
this.getAllSchemaName();
|
||
this.deleteFile();
|
||
},
|
||
methods: {
|
||
stop (record) {
|
||
this.isStop = true
|
||
record.uploadStatus = 0
|
||
},
|
||
start (record, index) {
|
||
const file = this.fileList[index].originFileObj
|
||
const currentRow = this.tableDate.find((row) => row.uid === file.uid)
|
||
this.isStop = false
|
||
record.uploadStatus = 1
|
||
this.uploadByPieces({
|
||
file, // 文件信息
|
||
currentRow,
|
||
success: (data) => {
|
||
record.percentage = 100
|
||
},
|
||
error: (e) => {
|
||
record.percentage = 0
|
||
}
|
||
})
|
||
},
|
||
// 从外部设置已有文件列表
|
||
setFileList (fileList) {
|
||
this.tableDate = fileList
|
||
},
|
||
deleteFile () {
|
||
this.fileList = []
|
||
this.tableDate = []
|
||
this.schemaName =""
|
||
},
|
||
getAllSchemaName(){
|
||
getAction("/fileDataLink/getAllSchemaName").then((res) => {
|
||
if (res.success) {
|
||
console.log(res)
|
||
this.allSchemaName = res.result;
|
||
}else{
|
||
this.$message.warning(res.message);
|
||
}
|
||
});
|
||
},
|
||
getFileList () {
|
||
return this.tableDate
|
||
},
|
||
bytesToSize (bytes) {
|
||
if (bytes === 0) return '0 B'
|
||
const k = 1024
|
||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||
return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]
|
||
},
|
||
handleDelete (file, idx) {
|
||
this.tableDate.splice(idx, 1)
|
||
delete this.fileMD5[file.uid]
|
||
},
|
||
async beforeUpload (file) {
|
||
if (!this.canRepeat) {
|
||
const md5 = await this.getMd5(file, file.size)
|
||
const find = Object.values(this.fileMD5).find(v => {
|
||
return v === md5
|
||
})
|
||
if (!this.canRepeat && find) {
|
||
this.$message.warning(`文件重复,重复的文件为:${file.name}`)
|
||
throw new Error('file repeat')
|
||
} else {
|
||
this.fileMD5[file.uid] = md5
|
||
}
|
||
}
|
||
|
||
this.fileList.push(file)
|
||
// uploadStatus 上传状态 1开始 0暂停 2完成
|
||
this.tableDate.push({
|
||
fileData: file,
|
||
uid: file.uid,
|
||
fileName: file.name,
|
||
size: file.size,
|
||
type: file.type,
|
||
percentage: 0,
|
||
uploadStatus: 1,
|
||
remarks: ''
|
||
})
|
||
if (!this.autoUpload) {
|
||
throw new Error('not auto upload')
|
||
}
|
||
},
|
||
/**
|
||
* 自定义上传事件
|
||
*/
|
||
customRequestUpload ({ file }) {
|
||
if(this.schemaName ==""){
|
||
this.$message.warning("请先选择上传的模式");
|
||
return
|
||
}
|
||
// 开始执行上传逻辑
|
||
const currentRow = this.tableDate.find((row) => row.uid === file.uid)
|
||
if (currentRow) {
|
||
// 当前上传进度归0
|
||
currentRow.percentage = 0
|
||
const _20M = 20000000000 * 1024 * 1024 //设置超大文件判断关闭分片
|
||
if (file.size > _20M) { // 20M 以上分片上传
|
||
this.uploadByPieces({ // 这里走分片上传逻辑
|
||
file, // 文件信息
|
||
currentRow,
|
||
success: (data) => {
|
||
currentRow.percentage = 100
|
||
},
|
||
error: (e) => {
|
||
currentRow.percentage = 0
|
||
}
|
||
})
|
||
} else { // 20M 以下直接上传
|
||
this.uploadDirectly(currentRow, file)
|
||
}
|
||
}
|
||
},
|
||
getMd5 (file, chunkSize) {
|
||
return new Promise((resolve, reject) => {
|
||
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
|
||
const chunks = Math.ceil(file.size / chunkSize)
|
||
let currentChunk = 0
|
||
const spark = new SparkMD5.ArrayBuffer() // 追加数组缓冲区。
|
||
const fileReader = new FileReader() // 读取文件
|
||
fileReader.onload = function (e) {
|
||
spark.append(e.target.result)
|
||
currentChunk++
|
||
if (currentChunk < chunks) {
|
||
loadNext()
|
||
} else {
|
||
const md5 = spark.end() // 完成md5的计算,返回十六进制结果。
|
||
resolve(md5)
|
||
}
|
||
}
|
||
|
||
fileReader.onerror = function (e) {
|
||
reject(e)
|
||
}
|
||
|
||
function loadNext () {
|
||
const start = currentChunk * chunkSize
|
||
let end = start + chunkSize
|
||
end > file.size && (end = file.size)
|
||
fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
|
||
}
|
||
loadNext()
|
||
})
|
||
},
|
||
|
||
buildFileFormData (fileName, fileSize, md5Value, shareTotal, shareIndex, file, fileShare,currShareM5) {
|
||
const formData = new FormData()
|
||
formData.append('fileShare', fileShare)
|
||
formData.append('fileName', fileName)
|
||
formData.append('fileSuffix', fileName.substring(fileName.lastIndexOf('.')))
|
||
formData.append('fileSize', fileSize)
|
||
formData.append('md5Value', md5Value)
|
||
formData.append('shareTotal', shareTotal)
|
||
formData.append('shareIndex', shareIndex)
|
||
formData.append('currShareM5', currShareM5)
|
||
formData.append('file', file)
|
||
formData.append("dataLinkType",this.dataLinkType);
|
||
formData.append("schemaName",this.schemaName);
|
||
return formData
|
||
},
|
||
|
||
/**
|
||
* 直接上传
|
||
* @param {File} file file文件
|
||
*/
|
||
async uploadDirectly (currentRow, file) {
|
||
let fileMD5Value = this.fileMD5[file.uid]
|
||
if (!fileMD5Value) {
|
||
fileMD5Value = await this.getMd5(file, file.size)
|
||
}
|
||
try {
|
||
const res = await verifyFileExist({ fileMD5Value })
|
||
if (res.exist) { // 跳过文件验证逻辑
|
||
currentRow.percentage = 100
|
||
currentRow.uploadStatus = 2
|
||
currentRow.result = res
|
||
} else { // 未存在,走上传逻辑
|
||
const formData = this.buildFileFormData(file.name, file.size, fileMD5Value, 1, 1, file, false,fileMD5Value)
|
||
const url = '/fileDataLink/uploadFile'
|
||
try {
|
||
const res = await postAction(url, formData)
|
||
if (res) {
|
||
currentRow.percentage = 100
|
||
currentRow.uploadStatus = 2
|
||
currentRow.result = {
|
||
fileId: res.id
|
||
}
|
||
} else {
|
||
currentRow.percentage = 0
|
||
currentRow.uploadStatus = 1
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error(error)
|
||
this.$message.error('获取文件是否已上传状态失败')
|
||
}
|
||
},
|
||
|
||
// 断点分片上传
|
||
uploadByPieces ({ file, currentRow, success, error }) {
|
||
// const that = this
|
||
// 上传过程中用到的变量
|
||
var slicingSize = null
|
||
if (file.size <= 20971520) {
|
||
// 20M以内,单个切片大小设置为2MB
|
||
slicingSize = 2 * 1024 * 1024 // 切片大小 单位MB
|
||
} else if (file.size <= 524288000) {
|
||
// 500M以内,单个切片大小设置为5MB
|
||
slicingSize = 5 * 1024 * 1024 // 切片大小 单位MB
|
||
} else {
|
||
// 500M以外,单个切片大小设置为10MB
|
||
slicingSize = 10 * 1024 * 1024 // 切片大小 单位MB
|
||
}
|
||
const sumSlicingCount = Math.ceil(file.size / slicingSize) // 总片数
|
||
|
||
currentRow.remarks = '正在获取hash值...'
|
||
this.getMd5(file, slicingSize)
|
||
.then((res) => {
|
||
this.fileMD5[file.uid] = res
|
||
|
||
currentRow.remarks = ''
|
||
this.readFileMD5(file, currentRow, slicingSize, sumSlicingCount, success, error)
|
||
})
|
||
.catch((e) => {
|
||
console.error('MD5计算错误', e)
|
||
})
|
||
},
|
||
// 得到某一片的分片 file 总文件; currentIndex 当前切片数,按索引计算; slicingSize 切片大小
|
||
getSlicingInfo (file, currentIndex, slicingSize) {
|
||
const start = currentIndex * slicingSize
|
||
const end = Math.min(file.size, start + slicingSize)
|
||
const slicingInfo = file.slice(start, end)
|
||
return slicingInfo
|
||
},
|
||
// 开始执行切片上传
|
||
readFileMD5 (file, currentRow, slicingSize, sumSlicingCount, success, error) {
|
||
// 检查文件有没有上传过的状态
|
||
verifyFileExist({ fileMD5Value: this.fileMD5[file.uid] })
|
||
.then((res) => {
|
||
const { exist, shareTotal, shareIndex } = res
|
||
if (exist) {
|
||
if (shareIndex === (shareTotal - 1)) { // 相等说之前已经上传完整
|
||
currentRow.percentage = 100
|
||
currentRow.uploadStatus = 2
|
||
currentRow.result = res
|
||
} else { // 不相等说明之前上传中断了,接着再上传
|
||
const pross = (shareIndex + 1 / sumSlicingCount) * 100 // 计算之前上传的进度
|
||
currentRow.percentage = Number(pross.toFixed(2))
|
||
this.uploadSliceFile(file, currentRow, slicingSize, sumSlicingCount, shareIndex + 1, success, error)
|
||
}
|
||
} else { // 没有传过就从第0个分片开始上传
|
||
this.uploadSliceFile(file, currentRow, slicingSize, sumSlicingCount, 0, success, error)
|
||
}
|
||
})
|
||
.catch((e) => {
|
||
error && error(e)
|
||
})
|
||
},
|
||
/**
|
||
* 对切片文件进行上传
|
||
* @param {File} file
|
||
*/
|
||
async uploadSliceFile (file, currentRow, slicingSize, sumSlicingCount, currIndex, success, error) {
|
||
if (currIndex < sumSlicingCount && !this.isStop) {
|
||
// 得到当前需要上传的分片文件
|
||
const currentInfo = this.getSlicingInfo(file, currIndex, slicingSize)
|
||
const result = new File([currentInfo], currIndex, { type: file.type, lastModified: Date.now() })
|
||
let fileMD5Value = await this.getMd5(result, result.size)
|
||
const formData = this.buildFileFormData(file.name, file.size, this.fileMD5[file.uid], sumSlicingCount, (currIndex + 1), result, true,fileMD5Value)
|
||
|
||
// 开始上传
|
||
const url = '/fileDataLink/uploadFile'
|
||
postAction(url, formData).then((res) => {
|
||
const { completed, id } = res
|
||
if (completed) { // 已上传完毕
|
||
currentRow.percentage = 100
|
||
currentRow.uploadStatus = 2
|
||
currentRow.result = {
|
||
fileId: id
|
||
}
|
||
} else {
|
||
// 每上传一个就在进度条上加数据
|
||
const pross = ((currIndex + 1) / sumSlicingCount) * 100
|
||
currentRow.percentage = Number(pross.toFixed(2))
|
||
this.uploadSliceFile(file, currentRow, slicingSize, sumSlicingCount, currIndex + 1, success, error)
|
||
}
|
||
})
|
||
}
|
||
},
|
||
handleChange (info) {
|
||
this.fileList = [...info.fileList]
|
||
},
|
||
|
||
/**
|
||
* 下载文件
|
||
*/
|
||
handleDownload ({ fileName: filterWordName, filePath: filterWordPath }) {
|
||
downloadFile2(filterWordName, { filterWordName, filterWordPath }, '/file/verifyFileExist')
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.upload-wrapper{
|
||
display: inline-block;
|
||
/deep/.ant-upload{
|
||
width: 100%;
|
||
}
|
||
}
|
||
|
||
.result-wrapper {
|
||
margin-top: 10px;
|
||
width: 100%;
|
||
background: #FFFFFF;
|
||
border: 1px solid #D9DADB;
|
||
padding: 10px;
|
||
|
||
.item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
font-size: 14px;
|
||
font-weight: 400;
|
||
color: #666666;
|
||
line-height: 36px;
|
||
|
||
.content {
|
||
flex: 1;
|
||
.body {
|
||
position: relative;
|
||
justify-content: space-between;
|
||
padding-bottom: 4px;
|
||
display: flex;
|
||
|
||
.fileName {
|
||
max-width: 400px;
|
||
text-overflow: ellipsis;
|
||
overflow: hidden;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.progress {
|
||
position: absolute;
|
||
left: 0;
|
||
bottom: 0px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|