IDCDatasync-vue/src/views/datalink/modules/SliceUpload.vue
2025-03-04 18:05:29 +08:00

466 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>