IDCDatasync-vue/src/views/fileManage/modules/SliceUpload.vue

443 lines
14 KiB
Vue
Raw Normal View History

2025-02-20 17:22:54 +08:00
<template>
<div>
<a-row type="flex">
<a-col flex="auto">
<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 { 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
},
dirId: { // 是否显示下载按钮
type: String,
default: ''
}
},
data () {
return {
fileMD5: {},
isStop: false,
fileList: [],
tableDate: [],
}
},
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 = []
},
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 }) {
// 开始执行上传逻辑
const currentRow = this.tableDate.find((row) => row.uid === file.uid)
if (currentRow) {
// 当前上传进度归0
currentRow.percentage = 0
const _20M = 20 * 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()
})
},
2025-02-20 17:54:47 +08:00
buildFileFormData (fileName, fileSize, md5Value, shareTotal, shareIndex, file, fileShare,currShareM5) {
2025-02-20 17:22:54 +08:00
const formData = new FormData()
2025-02-20 17:54:47 +08:00
formData.append('dirId', this.dirId)
formData.append('fileShare', fileShare)
2025-02-20 17:22:54 +08:00
formData.append('fileName', fileName)
2025-02-20 17:54:47 +08:00
formData.append('fileSuffix', fileName.substring(fileName.lastIndexOf('.')))
2025-02-20 17:22:54 +08:00
formData.append('fileSize', fileSize)
formData.append('md5Value', md5Value)
formData.append('shareTotal', shareTotal)
formData.append('shareIndex', shareIndex)
2025-02-20 17:54:47 +08:00
formData.append('currShareM5', currShareM5)
2025-02-20 17:22:54 +08:00
formData.append('file', file)
2025-02-20 17:54:47 +08:00
2025-02-20 17:22:54 +08:00
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 { // 未存在,走上传逻辑
2025-02-20 17:54:47 +08:00
const formData = this.buildFileFormData(file.name, file.size, fileMD5Value, 0, 0, file, false,fileMD5Value)
2025-02-20 17:22:54 +08:00
const url = '/file/uoloadFile'
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
*/
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() })
2025-02-20 17:54:47 +08:00
const formData = this.buildFileFormData(file.name, file.size, this.fileMD5[file.uid], sumSlicingCount, currIndex, result, true,this.fileMD5[file.uid])
2025-02-20 17:22:54 +08:00
// 开始上传
const url = '/file/uoloadFile'
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;
width: calc(100% - 10px);
/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>