Browse Source

上传优化

master
xuhuajiao 2 months ago
parent
commit
e371826774
  1. 28
      src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload-old.vue
  2. 1140
      src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload.vue
  3. 245
      src/views/components/category/PreviewForm.vue
  4. 692
      src/views/components/category/preUpload-time.vue
  5. 1076
      src/views/components/category/preUpload.vue
  6. 654
      src/views/components/category/preUpload2.vue
  7. 838
      src/views/components/category/preUpload3.vue
  8. 670
      src/views/components/category/preUpload4.vue
  9. 279
      src/views/components/category/preUpload5.vue
  10. 50
      src/views/prearchiveLibrary/file/index.vue
  11. 23
      src/views/prearchiveLibrary/index.vue

28
src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload22.vue → src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload-old.vue

@ -78,6 +78,10 @@
<p style="margin-right: 20px;">成功 <span style="color:#1AAE93; font-weight: bold;">{{ resultCatalog && resultCatalog.mountFile.successNum }} </span> </p> <p style="margin-right: 20px;">成功 <span style="color:#1AAE93; font-weight: bold;">{{ resultCatalog && resultCatalog.mountFile.successNum }} </span> </p>
<p>失败 <span style="color:#f00; font-weight: bold; ">{{ resultCatalog && resultCatalog.mountFile.failNum }}</span> </p> <p>失败 <span style="color:#f00; font-weight: bold; ">{{ resultCatalog && resultCatalog.mountFile.failNum }}</span> </p>
</div> </div>
<!-- <div v-for="(item,index) in resultCatalog && resultCatalog.failArchives" :key="index" class="file-list" style="margin-bottom: 10px;">
<i class="iconfont icon-xiaowenjian" />
{{ item }}
</div> -->
<el-table class="archives-table" :data="catalogInfoData" style="min-width: 100%" height="calc(100vh - 676px)"> <el-table class="archives-table" :data="catalogInfoData" style="min-width: 100%" height="calc(100vh - 676px)">
<el-table-column type="expand"> <el-table-column type="expand">
<template #default="{ row }"> <template #default="{ row }">
@ -90,6 +94,12 @@
</el-table-column> </el-table-column>
<el-table-column prop="archives" label="档案/电子原文" /> <el-table-column prop="archives" label="档案/电子原文" />
<el-table-column prop="description" label="失败原因" /> <el-table-column prop="description" label="失败原因" />
<!-- <el-table-column prop="update_by" label="电子原文" min-width="60" />
<el-table-column prop="update_time" label="状态" width="180">
<template slot-scope="scope">
<div>{{ scope.row.update_time | parseTime }}</div>
</template>
</el-table-column> -->
</el-table> </el-table>
</div> </div>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
@ -155,11 +165,6 @@ export default {
}, },
headers: { headers: {
'Authorization': getToken() 'Authorization': getToken()
},
processParams: (params, file, chunk) => {
params.fileMd5 = chunk.file.uniqueIdentifier
params.chunkIndex = chunk.offset
return params
} }
}, },
attrs: { attrs: {
@ -197,7 +202,7 @@ export default {
]) ])
}, },
mounted() { mounted() {
this.options.target = this.baseApi + '/api/collect/chunk'
this.options.target = this.baseApi + '/api/collect/upload'
}, },
methods: { methods: {
opened() { opened() {
@ -247,6 +252,11 @@ export default {
this.chunkOffset = [] this.chunkOffset = []
const result = JSON.parse(response) const result = JSON.parse(response)
console.log('result', result) console.log('result', result)
//
// if (result.data.needMerge && !this.skip) {
// } else {
// console.log('')
// }
this.fileList.push(file) this.fileList.push(file)
if (result.code === 200 && this.fileList.length !== 0) { if (result.code === 200 && this.fileList.length !== 0) {
this.submitted = true this.submitted = true
@ -410,6 +420,7 @@ export default {
this.handleCloseDialog() this.handleCloseDialog()
} else { } else {
// //
// this.$refs.uploader.fileList
const uploaderInstance = this.$refs.uploader const uploaderInstance = this.$refs.uploader
uploaderInstance.fileList = uploaderInstance.fileList.filter(file => nonRepeatFileData.some(nonRepeatFile => nonRepeatFile.uniqueIdentifier === file.uniqueIdentifier)) uploaderInstance.fileList = uploaderInstance.fileList.filter(file => nonRepeatFileData.some(nonRepeatFile => nonRepeatFile.uniqueIdentifier === file.uniqueIdentifier))
this.$refs.uploader.files = this.$refs.uploader.files.filter(file => nonRepeatFileData.some(nonRepeatFile => nonRepeatFile.uniqueIdentifier === file.uniqueIdentifier)) this.$refs.uploader.files = this.$refs.uploader.files.filter(file => nonRepeatFileData.some(nonRepeatFile => nonRepeatFile.uniqueIdentifier === file.uniqueIdentifier))
@ -663,4 +674,9 @@ export default {
} }
} }
// .catalog-dialog{
// ::v-deep .el-dialog{
// width: 560px !important;
// }
// }
</style> </style>

1140
src/views/collectReorganizi/collectionLibrary/module/uploadOriginal/bigUpload.vue
File diff suppressed because it is too large
View File

245
src/views/components/category/PreviewForm.vue

@ -116,7 +116,16 @@
<el-row v-if="isDesFormType === 'prearchiveLibrary'" class="preview-form-bottom prearch-bottom-new"> <el-row v-if="isDesFormType === 'prearchiveLibrary'" class="preview-form-bottom prearch-bottom-new">
<el-col> <el-col>
<el-form-item label="电子原件" prop="fileOriginal" class="prearch-upload"> <el-form-item label="电子原件" prop="fileOriginal" class="prearch-upload">
<p class="input-style">{{ fileOriginal }}</p>
<!-- {{ fileJsonString && JSON.parse(fileJsonString).length }} -->
<!-- archivesType === 'edit' -->
<p v-if="fileJsonString && JSON.parse(fileJsonString).length > 0">一共 {{ JSON.parse(fileJsonString).length }} 个原件新增 {{ newFileCount }} 个文件</p>
<ul v-if="fileJsonString && JSON.parse(fileJsonString).length > 0" style="width:100%; height: 84px; overflow: hidden; overflow-y: scroll;">
<li v-for="item in JSON.parse(fileJsonString)" :key="item.id" style="display: flex; justify-content: space-between; align-items: center; padding-right: 20px;">
<p><i class="iconfont icon-attachment" />{{ item.file_name }}</p>
<i class="iconfont icon-shanchu" style="cursor: pointer;" @click="deleteFile(item)" />
</li>
</ul>
<p style="display: none;" class="input-style">{{ fileOriginal }}</p>
<PreUpload ref="preUploadRefs" :selected-document="selectedDocument" :arc-id="arcId" @onUploadSuccess="handleSuccessResource" @onUploadError="handleErrorResource" /> <PreUpload ref="preUploadRefs" :selected-document="selectedDocument" :arc-id="arcId" @onUploadSuccess="handleSuccessResource" @onUploadError="handleErrorResource" />
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -187,9 +196,9 @@ import { FetchSonDictionaryList, FetchDictionaryTree } from '@/api/system/dict'
import { FetchInitSearchFonds } from '@/api/archiveUtilize/archiveUtilize' import { FetchInitSearchFonds } from '@/api/archiveUtilize/archiveUtilize'
import { FetchArchivesClassTree, FetchSonArchivesClass } from '@/api/system/archivesClass' import { FetchArchivesClassTree, FetchSonArchivesClass } from '@/api/system/archivesClass'
// parseTime // parseTime
import { getCurrentTime } from '@/utils/index'
import { reDocumentUpload } from '@/utils/upload'
import PreUpload from './preUpload4'
// import { getCurrentTime } from '@/utils/index'
// import { reDocumentUpload } from '@/utils/upload'
import PreUpload from './preUpload'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Vue from 'vue' import Vue from 'vue'
@ -302,7 +311,8 @@ export default {
'4-3': 'item_no' '4-3': 'item_no'
}, },
isMaxLoading: false, isMaxLoading: false,
loadingStatus: {}
loadingStatus: {},
newFileCount: 0
} }
}, },
computed: { computed: {
@ -381,7 +391,8 @@ export default {
this.jsonArrayStore = [] this.jsonArrayStore = []
this.fileNameStore = [] this.fileNameStore = []
this.fileOriginal = '' this.fileOriginal = ''
this.fileJsonString = null // null
this.fileJsonString = null
this.newFileCount = 0
}, },
// handleSuccessResource(data, fileName, jsonArrayToSend) { // handleSuccessResource(data, fileName, jsonArrayToSend) {
// this.minioPreResult = data // this.minioPreResult = data
@ -401,6 +412,7 @@ export default {
// fileJson[0].signature = this.minioPreResult.signature // fileJson[0].signature = this.minioPreResult.signature
// this.fileJsonString = JSON.stringify(fileJson) // this.fileJsonString = JSON.stringify(fileJson)
// }, // },
// 1.
handleSuccessResource(data, fileName, jsonArrayToSend) { handleSuccessResource(data, fileName, jsonArrayToSend) {
console.log('原始fileJsonString:', this.fileJsonString) console.log('原始fileJsonString:', this.fileJsonString)
@ -414,6 +426,9 @@ export default {
const newFileNames = Array.isArray(fileName) ? fileName : [fileName] const newFileNames = Array.isArray(fileName) ? fileName : [fileName]
const newJsonData = Array.isArray(jsonArrayToSend) ? jsonArrayToSend : [jsonArrayToSend] const newJsonData = Array.isArray(jsonArrayToSend) ? jsonArrayToSend : [jsonArrayToSend]
//
let currentNewCount = 0
// fileNameStore // fileNameStore
newMinioData.forEach((minioItem, index) => { newMinioData.forEach((minioItem, index) => {
const currentFileName = newFileNames[index]?.trim() || `未知文件_${index}` const currentFileName = newFileNames[index]?.trim() || `未知文件_${index}`
@ -429,13 +444,17 @@ export default {
return return
} }
//
this.minioPreResult.push(minioItem)
this.jsonArrayStore.push(currentJsonItem)
this.fileNameStore.push(currentFileName)
//
this.minioPreResult.unshift(minioItem) //
this.jsonArrayStore.unshift(currentJsonItem) //
this.fileNameStore.unshift(currentFileName) //
currentNewCount++ // +1
console.log(`文件名【${currentFileName}】合并到全局存储成功`) console.log(`文件名【${currentFileName}】合并到全局存储成功`)
}) })
//
this.newFileCount += currentNewCount
// fileJsonList // fileJsonList
let newFileJsonList = [] let newFileJsonList = []
this.minioPreResult.forEach((minioItem, index) => { this.minioPreResult.forEach((minioItem, index) => {
@ -483,7 +502,7 @@ export default {
console.log('解析后的历史数据:', historyFileJsonList) console.log('解析后的历史数据:', historyFileJsonList)
// 2. + // 2. +
const mergeAllList = [...historyFileJsonList, ...newFileJsonList]
const mergeAllList = [...newFileJsonList, ...historyFileJsonList] //
const uniqueMap = new Map() const uniqueMap = new Map()
mergeAllList.forEach(item => { mergeAllList.forEach(item => {
// file_namefile_path // file_namefile_path
@ -509,9 +528,72 @@ export default {
this.fileJsonString = JSON.stringify(finalFileJsonList) this.fileJsonString = JSON.stringify(finalFileJsonList)
console.log('最终更新后的fileJsonString:', this.fileJsonString) console.log('最终更新后的fileJsonString:', this.fileJsonString)
}, },
handleErrorResource(res) { handleErrorResource(res) {
console.log('handleErrorResource', res) console.log('handleErrorResource', res)
}, },
// 2.
deleteFile(fileItem) {
let fileList = []
if (this.fileJsonString && this.fileJsonString !== 'null' && this.fileJsonString !== '[]') {
try {
fileList = JSON.parse(this.fileJsonString)
fileList = Array.isArray(fileList) ? fileList : []
} catch (error) {
console.error('解析文件列表失败:', error)
fileList = []
}
}
// 2.
const uniqueKey = fileItem.file_name || fileItem.file_path
const newFileList = fileList.filter(item => {
const itemKey = item.file_name || item.file_path
return itemKey !== uniqueKey
})
// 3.
const isNewFile = this.fileNameStore.some(name => name === fileItem.file_name)
if (isNewFile) {
this.newFileCount = Math.max(0, this.newFileCount - 1)
//
const deleteIndex = this.fileNameStore.findIndex(name => name === fileItem.file_name)
if (deleteIndex > -1) {
this.fileNameStore.splice(deleteIndex, 1)
this.minioPreResult.splice(deleteIndex, 1)
this.jsonArrayStore.splice(deleteIndex, 1)
}
}
// 4.
this.fileJsonString = JSON.stringify(newFileList)
// 5. fileOriginal
const finalFileNames = newFileList
.map(item => item.file_name?.trim())
.filter(name => name && name !== '未知文件')
const uniqueFinalFileNames = [...new Set(finalFileNames)]
this.fileOriginal = uniqueFinalFileNames.join(',') || ''
this.$message({
type: 'success',
message: '文件删除成功!'
})
// //
// this.$confirm('', '', {
// confirmButtonText: '',
// cancelButtonText: '',
// type: 'warning'
// }).then(() => {
// // 1.
// }).catch(() => {
// this.$message({
// type: 'info',
// message: ''
// })
// })
},
checkboxT(row, rowIndex) { checkboxT(row, rowIndex) {
return row.level ? row.level === 3 : true return row.level ? row.level === 3 : true
}, },
@ -1225,6 +1307,7 @@ export default {
'fondsAffiliation': this.selectedDocument.fondsId 'fondsAffiliation': this.selectedDocument.fondsId
} }
console.log('this.fileJsonString', JSON.parse(this.fileJsonString))
console.log('params', params) console.log('params', params)
prearchEdit(params).then(res => { prearchEdit(params).then(res => {
if (res) { if (res) {
@ -1370,76 +1453,76 @@ export default {
done() done()
}, },
// //
async changeFile(e) {
this.file = e.target.files[0]
this.fileSize = this.file.size
this.formatType = this.file.type.substring(0, this.file.type.indexOf('/'))
this.fileNames = this.file.name
this.postfix = this.file.name.substring(
this.fileNames.lastIndexOf('.') + 1,
this.fileNames.length
)
if (this.formatType === 'image') {
const fileBase64 = await this.getBase64(this.file)
const res = await this.getImgPx(fileBase64)
this.px = res.width + 'px*' + res.height + 'px'
} else {
this.px = ''
}
//
reDocumentUpload(this.baseApi + '/api/re-document/uploadFile', this.file, this.selectedDocument.id).then(res => {
if (res.data.code === 200) {
this.filePath = res.data.data
this.uploadSave()
}
})
},
// -
uploadSave() {
this.nowDate = getCurrentTime()
const json = {
'file_name': this.fileNames,
'file_size': this.fileSize,
'file_type': this.postfix,
'file_path': this.filePath,
'sequence': null,
'archive_id': this.arcId,
'file_dpi': this.px,
'file_thumbnail': '',
'create_time': this.nowDate,
'id': null,
'is_quote': null,
'last_modified': this.file.lastModified
}
const arrayUpload = []
arrayUpload.push(json)
this.fileOriginal = this.fileNames
// this.$set(this.addOrUpdateForm, 'fileOriginal', this.fileNames)
this.fileJsonString = JSON.stringify(arrayUpload)
},
// base64
getBase64(file) {
const reader = new FileReader()
reader.readAsDataURL(file)
return new Promise((resolve) => {
reader.onload = () => {
resolve(reader.result)
}
})
},
//
getImgPx(img) {
const image = new Image()
image.src = img
return new Promise((resolve) => {
image.onload = () => {
const width = image.width
const height = image.height
resolve({ width, height })
}
})
},
// async changeFile(e) {
// this.file = e.target.files[0]
// this.fileSize = this.file.size
// this.formatType = this.file.type.substring(0, this.file.type.indexOf('/'))
// this.fileNames = this.file.name
// this.postfix = this.file.name.substring(
// this.fileNames.lastIndexOf('.') + 1,
// this.fileNames.length
// )
// if (this.formatType === 'image') {
// const fileBase64 = await this.getBase64(this.file)
// const res = await this.getImgPx(fileBase64)
// this.px = res.width + 'px*' + res.height + 'px'
// } else {
// this.px = ''
// }
// //
// reDocumentUpload(this.baseApi + '/api/re-document/uploadFile', this.file, this.selectedDocument.id).then(res => {
// if (res.data.code === 200) {
// this.filePath = res.data.data
// this.uploadSave()
// }
// })
// },
// // -
// uploadSave() {
// this.nowDate = getCurrentTime()
// const json = {
// 'file_name': this.fileNames,
// 'file_size': this.fileSize,
// 'file_type': this.postfix,
// 'file_path': this.filePath,
// 'sequence': null,
// 'archive_id': this.arcId,
// 'file_dpi': this.px,
// 'file_thumbnail': '',
// 'create_time': this.nowDate,
// 'id': null,
// 'is_quote': null,
// 'last_modified': this.file.lastModified
// }
// const arrayUpload = []
// arrayUpload.push(json)
// this.fileOriginal = this.fileNames
// // this.$set(this.addOrUpdateForm, 'fileOriginal', this.fileNames)
// this.fileJsonString = JSON.stringify(arrayUpload)
// },
// // base64
// getBase64(file) {
// const reader = new FileReader()
// reader.readAsDataURL(file)
// return new Promise((resolve) => {
// reader.onload = () => {
// resolve(reader.result)
// }
// })
// },
// //
// getImgPx(img) {
// const image = new Image()
// image.src = img
// return new Promise((resolve) => {
// image.onload = () => {
// const width = image.width
// const height = image.height
// resolve({ width, height })
// }
// })
// },
// - // -
getDictsList(item) { getDictsList(item) {
FetchDictionaryTree().then((res) => { FetchDictionaryTree().then((res) => {

692
src/views/components/category/preUpload-time.vue

@ -1,692 +0,0 @@
<template>
<div class="uploader-big">
<uploader
ref="uploader"
:options="initOptions"
:file-status-text="fileStatusText"
:auto-start="false"
class="uploader-app"
@file-added="onFileAdded"
@file-success="onUploadSuccess"
@file-progress="onFileProgress"
@file-error="onFileError"
>
<uploader-unsupport />
<!-- @click="clickUploader" -->
<uploader-drop class="custom_uploader_drop" element-loading-text="正在读取中">
<uploader-btn ref="uploadBtn" :attrs="attrs">
<slot>
<i class="iconfont icon-shangchuan" />
上传
</slot>
</uploader-btn>
</uploader-drop>
<div v-if="isUpload" class="upload_process_box">
<div>文件名{{ fileName }}</div>
<el-progress :percentage="uploadProcessNum" />
<div v-if="isMd5Upload">
正在读取文件中 - {{ md5ProgressText }}
</div>
<div v-if="!isSyncUpload&&!isMd5Upload">
上传至服务器 - <span>{{ uploadSpeed }} M/s</span>
</div>
<div v-if="isSyncUpload">
上传中请稍后
</div>
</div>
</uploader>
</div>
</template>
<script>
import SparkMD5 from 'spark-md5'
import { mapGetters } from 'vuex'
import axios from 'axios'
import { getToken } from '@/utils/auth'
import { getCurrentTime } from '@/utils/index'
export default {
props: {
selectedDocument: {
type: Object,
default: function() {
return {}
}
},
arcId: {
type: String,
default: function() {
return ''
}
},
selectedCategory: {
type: Object,
default: function() {
return {}
}
},
isBatchMount: {
type: String,
default: function() {
return ''
}
}
},
data() {
return {
initOptions: {
target: null,
headers: {
Authorization: getToken()
},
singleFile: false, //
uploadMethod: 'post', // 使 HTTP , POST
maxChunkRetries: 3, //
testChunks: true, //
parseTimeRemaining: function(timeRemaining, parsedTimeRemaining) {
return parsedTimeRemaining
.replace(/\syears?/, '年')
.replace(/\days?/, '天')
.replace(/\shours?/, '小时')
.replace(/\sminutes?/, '分钟')
.replace(/\sseconds?/, '秒')
},
checkChunkUploadedByResponse: (chunk, message) => {
const result = JSON.parse(message)
if (result.data.skipUpload) {
this.skip = true
return true
}
return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0
}
},
attrs: {
accept: ''
},
fileStatusText: {
success: '上传成功',
error: '上传失败',
uploading: '上传中',
paused: '已暂停',
waiting: '等待上传'
},
isStartUpload: false, //
md5ProgressText: 0,
isMd5Upload: false, // md5
isUpload: false, //
uploadProcessNum: 0, //
uploadSpeed: 0, //
fileName: '', //
isSyncUpload: false, //
syncUploadProcessNum: 0, //
response: null, //
queryTimer: null, //
socket: null
}
},
computed: {
...mapGetters([
'baseApi'
]),
// Uploader
uploader() {
return this.$refs.uploader.uploader
}
},
created() {
this.initOptions.target = this.baseApi + '/api/collect/upload'
if (this.isBatchMount === 'true') {
this.attrs.accept = '.zip'
} else {
this.attrs.accept = ''
}
},
beforeDestroy() {
clearInterval(this.queryTimer)
},
methods: {
clickUploader(e) {
this.$refs.uploadBtn.$el.click()
},
//
onFileAdded(file) {
this.uploadProcessNum = 0
// MD5
this.computeMD5(file).then((result) => this.startUpload(result))
},
/**
* 计算md5值以实现断点续传及秒传
*/
computeMD5(file) {
const maxMessage = '上传文件大小不能超过 10GB!'
const maxSize = 10 * 1024 * 1024 * 1024
if (file && file.size > maxSize) {
this.$message.warning(maxMessage)
return false
}
const fileReader = new FileReader()
const time = new Date().getTime()
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
let currentChunk = 0
// const chunkSize = this.initOptions.chunkSize
const chunkSize = 10 * 1024 * 1024
// 使 Math.ceil
const chunksWithCeil = Math.ceil(file.size / chunkSize)
console.log('使用 Math.ceil 得到的 chunks 数量:', chunksWithCeil)
//
let chunks = Math.floor(file.size / chunkSize)
if (file.size % chunkSize !== 0) {
chunks++
}
console.log('chunksManual', chunks)
const spark = new SparkMD5.ArrayBuffer()
this.fileName = file.name
// "MD5"
this.isMd5Upload = true
this.isUpload = true
file.pause()
loadNext()
return new Promise((resolve, reject) => {
fileReader.onload = (e) => {
spark.append(e.target.result)
if (currentChunk < chunks) {
currentChunk++
loadNext()
// MD5
this.$nextTick(() => {
this.md5ProgressText = ((currentChunk / chunks) * 100).toFixed(0) + '%'
})
} else {
console.log('spark', spark)
const md5 = spark.end()
console.log('md5', md5)
// md5
resolve({ md5, file })
console.log(file)
console.log(
`MD5计算完毕:${file.name} \nMD5:${md5} \n分片:${chunks} 大小:${file.size} 用时:${
new Date().getTime() - time
} ms`
)
}
}
fileReader.onerror = function() {
this.$message({ message: `文件${file.name}读取出错,请检查该文件`, type: 'error', offset: 8 })
file.cancel()
reject()
}
})
function loadNext() {
const start = currentChunk * chunkSize
const end = start + chunkSize >= file.size ? file.size : start + chunkSize
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
},
// md5
startUpload({ md5, file }) {
file.uniqueIdentifier = md5
file.resume()
this.isMd5Upload = false
this.isStartUpload = true
},
//
onFileProgress(rootFile, file, chunk) {
const uploader = this.$refs.uploader.uploader
this.uploadProcessNum = Math.floor(uploader.progress() * 100)
// this.emit('onUploadProcess', uploader.progress())
const averageSpeed = uploader.averageSpeed
const speed = averageSpeed / 1000 / 10
this.uploadSpeed = speed.toFixed(2)
},
//
onUploadSuccess(rootFile, file, response, chunk) {
const res1 = JSON.parse(response)
if (res1.code === 200) {
// merge
console.log('rootFile.uniqueIdentifier', rootFile.uniqueIdentifier)
const body = {
totalChunks: rootFile.chunks.length,
md5File: rootFile.uniqueIdentifier,
fileName: rootFile.name
}
console.log('body', body)
console.log(file)
this.handleUploadConfirm()
} else {
this.$message({ message: '上传失败!', type: 'error', offset: 8 })
this.$emit('onUploadError', res1)
}
if (this.skip) {
this.skip = false
}
},
// handleUploadConfirm() {
// if (this.$refs.uploader.fileList.length === 0) {
// this.$message({ message: '!', type: 'error', offset: 8 })
// return false
// }
// this.nowDate = getCurrentTime()
// const jsonArrayToSend = []
// this.isSyncUpload = true
// // 使 Promise.all
// Promise.all(this.$refs.uploader.fileList.map(async(item) => {
// console.log('item', item)
// const json = {}
// const jsonArray = []
// const jsonString = {}
// if (item.file.type.substring(0, item.file.type.indexOf('/')) === 'image') {
// const fileBase64 = await this.getBase64(item.file)
// const imgRes = await this.getImgPx(fileBase64)
// item.file.px = imgRes.width + 'px*' + imgRes.height + 'px'
// } else {
// item.file.px = ''
// }
// jsonString.file_name = item.file.name
// jsonString.file_size = item.file.size
// jsonString.file_type = item.file.name.substring(item.name.lastIndexOf('.') + 1, item.file.name.length)
// json.last_modified = item.file.lastModified
// jsonString.file_path = ''
// jsonString.sequence = null
// jsonString.archive_id = this.arcId
// jsonString.file_dpi = item.file.px
// jsonString.file_thumbnail = ''
// jsonString.create_time = this.nowDate
// jsonString.id = null
// jsonArray.push(jsonString)
// if (this.isBatchMount === 'true') {
// json.categoryId = this.selectedCategory.id
// } else {
// json.documentId = this.selectedDocument.id
// }
// json.archivesId = this.arcId
// json.identifier = item.uniqueIdentifier
// json.filename = item.name
// json.totalChunks = item.chunks.length - 1
// json.totalSize = item.size
// json.fileJsonString = JSON.stringify(jsonArray)
// jsonArrayToSend.push(json)
// })).then(() => {
// console.log('jsonArrayToSend', jsonArrayToSend)
// if (this.$refs.uploader.fileList.every(item => item.completed) && this.isUpload) {
// if (this.isBatchMount === 'true') {
// //
// axios.post(this.baseApi + '/api/collect/merge', jsonArrayToSend, {
// headers: {
// 'Authorization': getToken()
// }
// }).then((res) => {
// console.log(res)
// if (res.data.code === 200 && res.data.data.length === 1 && res.data.data[0] !== '') {
// this.$message({ message: '', type: 'success', offset: 8 })
// this.$emit('onUploadSuccess', res.data.data[0], this.fileName, jsonArrayToSend)
// } else {
// this.$message({ message: '', type: 'error', offset: 8 })
// }
// this.isSyncUpload = false
// setTimeout(() => {
// this.isUpload = false
// }, 2000)
// }).catch(err => {
// this.isSyncUpload = false
// setTimeout(() => {
// this.isUpload = false
// }, 2000)
// this.$emit('onUploadError', err)
// this.$message({ message: '', type: 'error', offset: 8 })
// clearInterval(this.queryTimer)
// })
// } else {
// //
// axios.post(this.baseApi + '/api/re-document/merge', jsonArrayToSend, {
// headers: {
// 'Authorization': getToken()
// }
// }).then((res) => {
// console.log(res)
// if (res.data.code === 200 && res.data.data.length === 1 && res.data.data[0] !== '') {
// this.$message({ message: '', type: 'success', offset: 8 })
// this.$emit('onUploadSuccess', res.data.data[0], this.fileName, jsonArrayToSend)
// } else {
// this.$message({ message: '', type: 'error', offset: 8 })
// }
// this.isSyncUpload = false
// }).catch(err => {
// this.isSyncUpload = false
// setTimeout(() => {
// this.isUpload = false
// }, 2000)
// this.$emit('onUploadError', err)
// this.$message({ message: '', type: 'error', offset: 8 })
// clearInterval(this.queryTimer)
// })
// }
// } else {
// this.isSyncUpload = false
// setTimeout(() => {
// this.isUpload = false
// }, 2000)
// this.$message({ message: '!', type: 'error', offset: 8 })
// }
// })
// },
handleUploadConfirm() {
//
if (this.$refs.uploader.fileList.length === 0) {
this.showMessage('请选择要上传的文件!', 'error')
return false
}
this.nowDate = getCurrentTime()
const jsonArrayToSend = []
this.isSyncUpload = true
//
const processFile = async(item) => {
const json = {}
const jsonArray = []
const jsonString = {}
//
if (item.file.type.startsWith('image')) {
const fileBase64 = await this.getBase64(item.file)
const imgRes = await this.getImgPx(fileBase64)
item.file.px = `${imgRes.width}px*${imgRes.height}px`
} else {
item.file.px = ''
}
// jsonString
jsonString.file_name = item.file.name
jsonString.file_size = item.file.size
jsonString.file_type = item.file.name.split('.').pop()
json.last_modified = item.file.lastModified
jsonString.file_path = ''
jsonString.sequence = null
jsonString.archive_id = this.arcId
jsonString.file_dpi = item.file.px
jsonString.file_thumbnail = ''
jsonString.create_time = this.nowDate
jsonString.id = null
jsonArray.push(jsonString)
// id
if (this.isBatchMount === 'true') {
json.categoryId = this.selectedCategory.id
} else {
json.documentId = this.selectedDocument.id
}
// json
json.archivesId = this.arcId
json.identifier = item.uniqueIdentifier
json.filename = item.name
json.totalChunks = item.chunks.length - 1
json.totalSize = item.size
json.fileJsonString = JSON.stringify(jsonArray)
jsonArrayToSend.push(json)
}
// 使 Promise.all
Promise.all(this.$refs.uploader.fileList.map(processFile))
.then(() => {
console.log('jsonArrayToSend', jsonArrayToSend)
//
if (this.$refs.uploader.fileList.every(item => item.completed) && this.isUpload) {
const apiUrl = this.isBatchMount === 'true'
? `${this.baseApi}/api/collect/merge`
: `${this.baseApi}/api/re-document/merge`
//
this.sendRequest(apiUrl, jsonArrayToSend)
.then((res) => {
if (res.data.code === 200 && res.data.data.length === 1 && res.data.data[0] !== '') {
this.showMessage('文件上传成功', 'success')
this.$emit('onUploadSuccess', res.data.data[0], this.fileName, jsonArrayToSend)
} else {
this.showMessage('文件上传失败', 'error')
}
this.isSyncUpload = false
if (this.isBatchMount === 'true') {
setTimeout(() => {
this.isUpload = false
}, 2000)
}
})
.catch((err) => {
this.handleRequestError(err)
})
} else {
this.isSyncUpload = false
setTimeout(() => {
this.isUpload = false
}, 2000)
this.showMessage('请耐心等待文件上传完成后再保存!', 'error')
}
})
.catch((err) => {
this.handleRequestError(err)
})
},
//
showMessage(message, type) {
this.$message({ message, type, offset: 8 })
},
//
sendRequest(url, data) {
return axios.post(url, data, {
headers: {
'Authorization': getToken()
}
})
},
//
handleRequestError(err) {
this.isSyncUpload = false
setTimeout(() => {
this.isUpload = false
}, 2000)
this.$emit('onUploadError', err)
this.showMessage('上传服务器失败', 'error')
clearInterval(this.queryTimer)
},
//
onFileError(rootFile, file, response, chunk) {
this.$message({ message: '上传失败', type: 'error', offset: 8 })
this.$emit('onUploadError', response)
this.isUpload = false
},
// base64
getBase64(file) {
const reader = new FileReader()
reader.readAsDataURL(file)
return new Promise((resolve) => {
reader.onload = () => {
resolve(reader.result)
}
})
},
//
getImgPx(img) {
const image = new Image()
image.src = img
return new Promise((resolve) => {
image.onload = () => {
const width = image.width
const height = image.height
resolve({ width, height })
}
})
},
handleClearData() {
this.isStartUpload = false
this.md5ProgressText = 0
this.isMd5Upload = false
this.isUpload = false
this.uploadProcessNum = 0
this.uploadSpeed = 0
this.fileName = ''
this.isSyncUpload = false
const uploaderInstance = this.$refs.uploader.uploader
console.log('uploaderInstance.fileList222', uploaderInstance.fileList)
uploaderInstance.fileList.forEach(file => file.cancel())
uploaderInstance.fileList = []
uploaderInstance.files = []
this.$refs.uploader.files = []
this.$refs.uploader.fileList = []
}
}
}
</script>
<style lang="scss" scoped>
.uploader-big{
position: relative;
// width: 100%;
margin-left: 10px;
.uploader{
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
width: 100%;
text-align: center;
margin-bottom: 8px;
.uploader-drop{
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
width: 100px;
height: 34px;
padding: 0;
border: 1px solid #0348f3;
background-color: transparent;
border-radius: 4px;
.uploader-btn{
width: 100%;
margin: 0;
padding: 0;
border: none;
color: #0348f3;
line-height: 36px;
i{
font-size: 20px;
color: #1F55EB;
}
&:hover{
background-color: transparent;
}
}
.el-upload__tip{
font-size: 12px;
color: #A6ADB6;
}
}
}
}
.upload_process_box{
position: absolute;
right: 0;
top: 44px;
width: 540px;
border-radius: 5px;
border: 1px dashed #409eff;
padding: 10px 0;
}
.uploader-file[status="success"] .uploader-file-remove {
display:block
}
::v-deep .uploader-list{
max-height: 344px;
overflow-y: scroll;
.uploader-file-size{
text-align: right !important;
}
.uploader-file-icon:before{
content: "";
}
.uploader-file-name{
text-align: left !important;
}
.uploader-file{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/attachment.png") no-repeat;
background-size: 100% 100%;
}
&.icon-image{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/image.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-excel,&.icon-xlsx,&.icon-xls{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/excel.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-pdf{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/pdf.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-ppt, &.icon-pptx{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/ppt.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-word,&.icon-docx,&.icon-doc{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/word.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-zip,&.icon-rar{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/zip.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-txt{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/txt.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-ofd{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/OFD.png") no-repeat;
background-size: 100% 100%;
}
}
}
}
</style>

1076
src/views/components/category/preUpload.vue
File diff suppressed because it is too large
View File

654
src/views/components/category/preUpload2.vue

@ -1,654 +0,0 @@
<template>
<div class="uploader-big">
<uploader
ref="uploader"
:options="initOptions"
:file-status-text="fileStatusText"
:auto-start="false"
class="uploader-app"
@file-added="onFileAdded"
@file-success="onUploadSuccess"
@file-progress="onFileProgress"
@file-error="onFileError"
>
<uploader-unsupport />
<uploader-drop class="custom_uploader_drop" element-loading-text="正在读取中">
<uploader-btn ref="uploadBtn" :attrs="attrs" :multiple="true">
<slot>
<i class="iconfont icon-shangchuan" />
上传
</slot>
</uploader-btn>
</uploader-drop>
<!-- 多文件上传进度展示 -->
<div v-if="isUpload" class="upload_process_box">
<div v-for="(file, index) in uploadFiles" :key="index" class="file-progress-item">
<div>文件名{{ file.name }}</div>
<el-progress :percentage="file.progress" />
<div v-if="file.isMd5Processing">
正在读取文件中 - {{ file.md5Progress }}
</div>
<div v-if="!file.isMd5Processing && !file.isSyncing">
上传至服务器 - <span>{{ file.speed }} M/s</span>
</div>
<div v-if="file.isSyncing">
上传中请稍后
</div>
</div>
<!-- 整体进度 -->
<div v-if="uploadFiles.length > 1" class="total-progress">
<div>整体进度</div>
<el-progress :percentage="totalProgress" />
</div>
</div>
</uploader>
</div>
</template>
<script>
import SparkMD5 from 'spark-md5'
import { mapGetters } from 'vuex'
import axios from 'axios'
import { getToken } from '@/utils/auth'
import { getCurrentTime } from '@/utils/index'
export default {
props: {
selectedDocument: {
type: Object,
default: () => ({})
},
arcId: {
type: String,
default: ''
},
selectedCategory: {
type: Object,
default: () => ({})
},
isBatchMount: {
type: String,
default: ''
}
},
data() {
return {
initOptions: {
target: null,
headers: {
Authorization: getToken()
},
singleFile: false, //
uploadMethod: 'post',
maxChunkRetries: 3,
testChunks: true,
parseTimeRemaining: function(timeRemaining, parsedTimeRemaining) {
return parsedTimeRemaining
.replace(/\syears?/, '年')
.replace(/\days?/, '天')
.replace(/\shours?/, '小时')
.replace(/\sminutes?/, '分钟')
.replace(/\sseconds?/, '秒')
},
checkChunkUploadedByResponse: (chunk, message) => {
const result = JSON.parse(message)
if (result.data.skipUpload) {
this.skip = true
return true
}
return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0
}
},
attrs: {
accept: ''
},
fileStatusText: {
success: '上传成功',
error: '上传失败',
uploading: '上传中',
paused: '已暂停',
waiting: '等待上传'
},
uploadFiles: [], //
totalProgress: 0, //
isUpload: false, //
queryTimer: null,
socket: null,
completedFiles: 0, //
totalFiles: 0, //
totalMergeStartTime: null, //
totalMergeEndTime: null //
}
},
computed: {
...mapGetters(['baseApi']),
uploader() {
return this.$refs.uploader?.uploader
}
},
created() {
this.initOptions.target = this.baseApi + '/api/collect/upload'
this.attrs.accept = this.isBatchMount === 'true' ? '.zip' : ''
},
beforeDestroy() {
clearInterval(this.queryTimer)
},
methods: {
clickUploader() {
this.$refs.uploadBtn.$el.click()
},
// YYYY-MM-DD HH:mm:ss
formatTime(time) {
if (!time) return ''
const date = new Date(time)
return `${date.getFullYear()}-${this.padZero(date.getMonth() + 1)}-${this.padZero(date.getDate())} ${this.padZero(date.getHours())}:${this.padZero(date.getMinutes())}:${this.padZero(date.getSeconds())}`
},
//
padZero(num) {
return num.toString().padStart(2, '0')
},
// xxxx
calculateDuration(startTime, endTime) {
if (!startTime || !endTime) return ''
const duration = (endTime - startTime) / 1000
const minutes = Math.floor(duration / 60)
const seconds = Math.floor(duration % 60)
const milliseconds = Math.floor((duration - Math.floor(duration)) * 1000)
return `${minutes > 0 ? minutes + '分' : ''}${seconds}${milliseconds > 0 ? milliseconds + '毫秒' : ''}`
},
//
async onFileAdded(file) {
//
const maxSize = 10 * 1024 * 1024 * 1024 // 10GB
if (file.size > maxSize) {
this.$message.warning(`文件 ${file.name} 大小不能超过 10GB!`)
return false
}
//
this.uploadFiles.push({
id: file.id,
name: file.name,
progress: 0,
speed: 0,
isMd5Processing: true,
md5Progress: '0%',
isSyncing: false,
md5: null,
file: file,
completed: false,
chunkUploadStartTime: null, //
chunkUploadEndTime: null //
})
this.isUpload = true
this.totalFiles = this.uploadFiles.length
// MD5
try {
const md5 = await this.computeMD5(file)
const fileInfo = this.uploadFiles.find(f => f.id === file.id)
if (fileInfo) {
fileInfo.md5 = md5
fileInfo.isMd5Processing = false
file.uniqueIdentifier = md5
//
fileInfo.chunkUploadStartTime = new Date().getTime()
console.log(`【文件${file.name}】分片上传开始时间:${this.formatTime(fileInfo.chunkUploadStartTime)}`)
file.resume() //
}
} catch (error) {
this.$message.error(`文件 ${file.name} 处理失败: ${error.message}`)
this.removeFile(file.id)
}
},
/**
* 计算文件MD5
*/
computeMD5(file) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
const chunkSize = 10 * 1024 * 1024 // 10MB
const chunks = Math.ceil(file.size / chunkSize)
let currentChunk = 0
const spark = new SparkMD5.ArrayBuffer()
file.pause()
const loadNext = () => {
const start = currentChunk * chunkSize
const end = Math.min(start + chunkSize, file.size)
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
fileReader.onload = (e) => {
spark.append(e.target.result)
currentChunk++
// MD5
const progress = Math.floor((currentChunk / chunks) * 100)
const fileInfo = this.uploadFiles.find(f => f.id === file.id)
if (fileInfo) {
fileInfo.md5Progress = `${progress}%`
}
if (currentChunk < chunks) {
loadNext()
} else {
const md5 = spark.end()
resolve(md5)
}
}
fileReader.onerror = () => {
reject(new Error(`文件读取出错: ${fileReader.error.message}`))
}
loadNext()
})
},
//
onFileProgress(rootFile, file, chunk) {
const fileInfo = this.uploadFiles.find(f => f.id === rootFile.id)
if (fileInfo) {
const progress = Math.floor(this.uploader.progress() * 100)
fileInfo.progress = progress
//
const averageSpeed = this.uploader.averageSpeed
const speed = averageSpeed / 1000 / 1024 // MB/s
fileInfo.speed = speed.toFixed(2)
}
//
this.calculateTotalProgress()
},
//
onUploadSuccess(rootFile, file, response) {
const res1 = JSON.parse(response)
if (res1.code === 200) {
const fileInfo = this.uploadFiles.find(f => f.id === rootFile.id)
if (fileInfo) {
//
fileInfo.chunkUploadEndTime = new Date().getTime()
const chunkDuration = this.calculateDuration(fileInfo.chunkUploadStartTime, fileInfo.chunkUploadEndTime)
//
console.log(`【文件${rootFile.name}】分片上传结束时间:${this.formatTime(fileInfo.chunkUploadEndTime)},分片上传耗时:${chunkDuration}`)
fileInfo.completed = true
fileInfo.progress = 100
this.completedFiles++
}
//
if (this.completedFiles === this.totalFiles) {
this.handleUploadConfirm()
}
} else {
this.$message.error(`文件 ${rootFile.name} 上传失败: ${res1.msg || '未知错误'}`)
this.removeFile(rootFile.id)
}
},
// merge
handleUploadConfirm() {
if (this.uploadFiles.length === 0) {
this.showMessage('没有可处理的文件!', 'error')
return
}
//
this.totalMergeStartTime = new Date().getTime()
console.log(`【整体合并】所有文件分片上传完成,开始合并,合并开始时间:${this.formatTime(this.totalMergeStartTime)}`)
this.nowDate = getCurrentTime()
const jsonArrayToSend = []
//
this.uploadFiles.forEach(file => {
file.isSyncing = true
file.mergeUploadStartTime = new Date().getTime()
console.log(`【文件${file.name}】合并上传开始时间:${this.formatTime(file.mergeUploadStartTime)}`)
})
//
const processFiles = this.uploadFiles.map(async(fileInfo) => {
const item = fileInfo.file
const json = {}
const jsonArray = []
const jsonString = {}
//
if (item.file.type.startsWith('image')) {
const fileBase64 = await this.getBase64(item.file)
const imgRes = await this.getImgPx(fileBase64)
item.file.px = `${imgRes.width}px*${imgRes.height}px`
} else {
item.file.px = ''
}
//
jsonString.file_name = item.file.name
jsonString.file_size = item.file.size
jsonString.file_type = item.file.name.split('.').pop() || ''
json.last_modified = item.file.lastModified
jsonString.file_path = ''
jsonString.sequence = null
jsonString.archive_id = this.arcId
jsonString.file_dpi = item.file.px
jsonString.file_thumbnail = ''
jsonString.create_time = this.nowDate
jsonString.id = null
jsonArray.push(jsonString)
//
if (this.isBatchMount === 'true') {
json.categoryId = this.selectedCategory.id
} else {
json.documentId = this.selectedDocument.id
}
//
json.archivesId = this.arcId
json.identifier = fileInfo.md5
json.filename = item.name
json.totalChunks = item.chunks.length - 1
json.totalSize = item.size
json.fileJsonString = JSON.stringify(jsonArray)
jsonArrayToSend.push(json)
return json
})
//
Promise.all(processFiles)
.then((jsonArray) => {
const apiUrl = this.isBatchMount === 'true'
? `${this.baseApi}/api/collect/merge`
: `${this.baseApi}/api/re-document/merge`
//
return this.sendRequest(apiUrl, jsonArray)
})
.then((res) => {
//
this.totalMergeEndTime = new Date().getTime()
const totalMergeDuration = this.calculateDuration(this.totalMergeStartTime, this.totalMergeEndTime)
if (res.data.code === 200) {
this.showMessage('所有文件上传成功', 'success')
this.uploadFiles.forEach(file => {
file.mergeUploadEndTime = new Date().getTime()
const mergeDuration = this.calculateDuration(file.mergeUploadStartTime, file.mergeUploadEndTime)
console.log(`【文件${file.name}】合并上传结束时间:${this.formatTime(file.mergeUploadEndTime)},合并上传耗时:${mergeDuration}`)
})
console.log(`【整体合并】所有文件合并完成,合并结束时间:${this.formatTime(this.totalMergeEndTime)},整体合并耗时:${totalMergeDuration}`)
this.$emit('onUploadSuccess', res.data.data, this.uploadFiles.map(f => f.name), jsonArrayToSend)
} else {
this.showMessage('文件合并失败', 'error')
console.log(`【整体合并】合并失败,失败时间:${this.formatTime(this.totalMergeEndTime)},合并耗时:${totalMergeDuration}`)
}
this.resetUploadState()
})
.catch((err) => {
//
this.totalMergeEndTime = new Date().getTime()
const totalMergeDuration = this.calculateDuration(this.totalMergeStartTime, this.totalMergeEndTime)
console.log(`【整体合并】合并请求异常,异常时间:${this.formatTime(this.totalMergeEndTime)},合并耗时:${totalMergeDuration},异常信息:`, err)
this.showMessage('上传服务器失败', 'error')
this.$emit('onUploadError', err)
this.resetUploadState()
})
},
//
onFileError(rootFile, file, response) {
this.showMessage(`文件 ${rootFile.name} 上传失败`, 'error')
console.log(`【文件${rootFile.name}】分片上传失败,失败响应:`, response)
this.$emit('onUploadError', response)
this.removeFile(rootFile.id)
},
//
calculateTotalProgress() {
if (this.uploadFiles.length === 0) {
this.totalProgress = 0
return
}
const total = this.uploadFiles.reduce((sum, file) => sum + file.progress, 0)
this.totalProgress = Math.floor(total / this.uploadFiles.length)
},
//
removeFile(fileId) {
const removeFile = this.uploadFiles.find(f => f.id === fileId)
if (removeFile) {
console.log(`【文件${removeFile.name}】已从上传列表移除`)
}
this.uploadFiles = this.uploadFiles.filter(f => f.id !== fileId)
this.totalFiles = this.uploadFiles.length
this.completedFiles = this.uploadFiles.filter(f => f.completed).length
if (this.uploadFiles.length === 0) {
this.isUpload = false
this.totalProgress = 0
this.totalMergeStartTime = null
this.totalMergeEndTime = null
} else {
this.calculateTotalProgress()
}
},
//
resetUploadState() {
this.uploadFiles.forEach(file => {
file.isSyncing = false
})
//
setTimeout(() => {
this.uploadFiles = []
this.isUpload = false
this.totalProgress = 0
this.completedFiles = 0
this.totalFiles = 0
this.totalMergeStartTime = null
this.totalMergeEndTime = null
//
if (this.uploader) {
this.uploader.fileList.forEach(file => file.cancel())
this.uploader.fileList = []
this.uploader.files = []
}
if (this.$refs.uploader) {
this.$refs.uploader.files = []
this.$refs.uploader.fileList = []
}
}, 2000)
},
//
showMessage(message, type) {
this.$message({ message, type, offset: 8 })
},
sendRequest(url, data) {
return axios.post(url, data, {
headers: { 'Authorization': getToken() }
})
},
getBase64(file) {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
})
},
getImgPx(img) {
return new Promise((resolve) => {
const image = new Image()
image.src = img
image.onload = () => resolve({ width: image.width, height: image.height })
})
},
handleClearData() {
console.log('【上传状态重置】手动清空所有上传数据')
this.uploadFiles = []
this.isUpload = false
this.totalProgress = 0
this.completedFiles = 0
this.totalFiles = 0
this.totalMergeStartTime = null
this.totalMergeEndTime = null
if (this.uploader) {
this.uploader.fileList.forEach(file => file.cancel())
this.uploader.fileList = []
this.uploader.files = []
}
if (this.$refs.uploader) {
this.$refs.uploader.files = []
this.$refs.uploader.fileList = []
}
}
}
}
</script>
<style lang="scss" scoped>
.uploader-big{
position: relative;
margin-left: 10px;
.uploader-drop {
.uploader-btn {
i {
font-size: 20px;
color: #1F55EB;
}
}
}
}
.upload_process_box{
position: absolute;
right: 0;
top: 44px;
width: 540px;
border-radius: 5px;
border: 1px dashed #409eff;
padding: 10px;
background: #fff; //
z-index: 10;
.file-progress-item {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
}
.total-progress {
margin-top: 15px;
padding-top: 10px;
border-top: 1px solid #e0e0e0;
}
}
//
::v-deep .uploader-list{
max-height: 344px;
overflow-y: scroll;
.uploader-file-size{
text-align: right !important;
}
.uploader-file-name{
text-align: left !important;
}
.uploader-file{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/attachment.png") no-repeat;
background-size: 100% 100%;
}
&.icon-image{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/image.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-excel,&.icon-xlsx,&.icon-xls{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/excel.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-pdf{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/pdf.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-ppt, &.icon-pptx{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/ppt.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-word,&.icon-docx,&.icon-doc{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/word.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-zip,&.icon-rar{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/zip.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-txt{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/txt.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-ofd{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/OFD.png") no-repeat;
background-size: 100% 100%;
}
}
}
}
</style>

838
src/views/components/category/preUpload3.vue

@ -1,838 +0,0 @@
<template>
<div class="uploader-big">
<uploader
ref="uploader"
:options="initOptions"
:file-status-text="fileStatusText"
:auto-start="false"
class="uploader-app"
@file-added="onFileAdded"
@file-success="onUploadSuccess"
@file-progress="onFileProgress"
@file-error="onFileError"
>
<uploader-unsupport />
<uploader-drop class="custom_uploader_drop" element-loading-text="正在读取中">
<uploader-btn ref="uploadBtn" :attrs="attrs" :multiple="true">
<slot>
<i class="iconfont icon-shangchuan" />
上传
</slot>
</uploader-btn>
</uploader-drop>
<!-- 多文件上传进度展示 -->
<div v-if="isUpload" class="upload_process_box">
<!-- 操作栏清空所有 -->
<div class="upload-operate-bar">
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleClearAll">
清空所有
</el-button>
</div>
<div v-for="(file, index) in uploadFiles" :key="index" class="file-progress-item">
<!-- 文件基本信息名称+大小 -->
<div class="file-base-info">
<span>文件名{{ file.name }}</span>
<span class="file-size">({{ formatFileSize(file.file.size) }})</span>
</div>
<!-- 进度条 -->
<el-progress
:percentage="file.progress"
:status="file.errorMsg ? 'exception' : (file.completed ? 'success' : '')"
/>
<!-- 状态提示 -->
<div class="file-status-tip">
<span v-if="file.isMd5Processing">正在读取文件中 - {{ file.md5Progress }}</span>
<span v-else-if="!file.isSyncing && !file.errorMsg">
上传至服务器 - <span class="upload-speed">{{ file.speed }} MB/s</span>
</span>
<span v-else-if="file.isSyncing">上传中请稍后合并分片</span>
<span v-else-if="file.errorMsg" class="error-tip"> {{ file.errorMsg }}</span>
<span v-else-if="file.completed" class="success-tip"> 上传成功</span>
</div>
<!-- 操作按钮取消/重新上传 -->
<div class="file-operate-btn">
<el-button
v-if="!file.completed && !file.errorMsg"
size="mini"
type="text"
icon="el-icon-circle-close"
@click="handleCancelUpload(file)"
>
取消
</el-button>
<el-button
v-if="file.errorMsg || file.isCancelled"
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleReUpload(file)"
>
重新上传
</el-button>
</div>
</div>
<!-- 整体进度 -->
<div v-if="uploadFiles.length > 1" class="total-progress">
<div>整体进度 ({{ completedFiles }}/{{ totalFiles }})</div>
<el-progress :percentage="totalProgress" />
</div>
</div>
</uploader>
</div>
</template>
<script>
import SparkMD5 from 'spark-md5'
import { mapGetters } from 'vuex'
import axios from 'axios'
import { getToken } from '@/utils/auth'
import { getCurrentTime } from '@/utils/index'
export default {
props: {
selectedDocument: {
type: Object,
default: () => ({})
},
arcId: {
type: String,
default: ''
},
selectedCategory: {
type: Object,
default: () => ({})
},
isBatchMount: {
type: String,
default: ''
},
// ['zip', 'pdf', 'jpg']
allowFileTypes: {
type: Array,
default: () => []
},
// (GB)10GB
maxFileSizeGB: {
type: Number,
default: 10
}
},
data() {
return {
initOptions: {
target: null,
headers: {
Authorization: getToken()
},
singleFile: false, //
uploadMethod: 'post',
maxChunkRetries: 3,
testChunks: true,
parseTimeRemaining: function(timeRemaining, parsedTimeRemaining) {
return parsedTimeRemaining
.replace(/\syears?/, '年')
.replace(/\days?/, '天')
.replace(/\shours?/, '小时')
.replace(/\sminutes?/, '分钟')
.replace(/\sseconds?/, '秒')
},
checkChunkUploadedByResponse: (chunk, message) => {
const result = JSON.parse(message)
if (result.data.skipUpload) {
this.skip = true
return true
}
return (result.data.uploaded || []).indexOf(chunk.offset + 1) >= 0
}
},
attrs: {
accept: ''
},
fileStatusText: {
success: '上传成功',
error: '上传失败',
uploading: '上传中',
paused: '已暂停',
waiting: '等待上传'
},
uploadFiles: [], //
totalProgress: 0, //
isUpload: false, //
queryTimer: null,
socket: null,
completedFiles: 0, //
totalFiles: 0, //
// MD5FileReader
md5Readers: {}
}
},
computed: {
...mapGetters(['baseApi']),
uploader() {
return this.$refs.uploader?.uploader
},
//
maxFileSize() {
return this.maxFileSizeGB * 1024 * 1024 * 1024
}
},
created() {
this.initOptions.target = this.baseApi + '/api/minioUpload/chunk'
// allowFileTypesaccept
if (this.isBatchMount === 'true') {
this.attrs.accept = '.zip'
} else if (this.allowFileTypes.length > 0) {
this.attrs.accept = this.allowFileTypes.map(type => `.${type}`).join(',')
}
},
beforeDestroy() {
clearInterval(this.queryTimer)
// MD5
Object.values(this.md5Readers).forEach(reader => {
if (reader.abort) reader.abort()
})
},
methods: {
clickUploader() {
this.$refs.uploadBtn.$el.click()
},
// GB/MB/KB
formatFileSize(bytes) {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return (bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i]
},
//
isFileDuplicate(file) {
return this.uploadFiles.some(item => {
return item.name === file.name && item.file.size === file.size
})
},
//
checkFileType(file) {
if (this.allowFileTypes.length === 0) return true
const fileExt = file.name.split('.').pop().toLowerCase()
return this.allowFileTypes.includes(fileExt)
},
//
async onFileAdded(file) {
// 1.
if (file.size > this.maxFileSize) {
this.$message.warning(`文件 ${file.name} 大小不能超过 ${this.maxFileSizeGB}GB!`)
return false
}
// 2.
if (!this.checkFileType(file)) {
const allowTypes = this.allowFileTypes.join('、')
this.$message.warning(`文件 ${file.name} 类型不支持,仅允许${allowTypes}格式!`)
return false
}
// 3.
if (this.isFileDuplicate(file)) {
this.$message.info(`文件 ${file.name} 已在上传列表中,请勿重复添加!`)
return false
}
//
const fileId = file.id || Date.now() + Math.random().toString(36).substr(2, 9)
this.uploadFiles.push({
id: fileId,
name: file.name,
file: file,
progress: 0,
speed: 0,
isMd5Processing: true,
md5Progress: '0%',
isSyncing: false,
md5: null,
completed: false,
errorMsg: '', //
isCancelled: false //
})
this.isUpload = true
this.totalFiles = this.uploadFiles.length
// MD5
try {
const md5 = await this.computeMD5(file, fileId)
const fileInfo = this.uploadFiles.find(f => f.id === fileId)
if (fileInfo) {
fileInfo.md5 = md5
fileInfo.isMd5Processing = false
file.uniqueIdentifier = md5
//
if (fileInfo.isCancelled) {
fileInfo.isCancelled = false
fileInfo.errorMsg = ''
}
file.resume() //
}
} catch (error) {
const fileInfo = this.uploadFiles.find(f => f.id === fileId)
if (fileInfo) {
fileInfo.errorMsg = error.message
fileInfo.isMd5Processing = false
}
this.$message.error(`文件 ${file.name} 处理失败: ${error.message}`)
}
},
/**
* 优化计算文件MD5支持取消
*/
computeMD5(file, fileId) {
return new Promise((resolve, reject) => {
const fileReader = new FileReader()
// reader
this.md5Readers[fileId] = fileReader
const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
const chunkSize = 10 * 1024 * 1024 // 10MB
const chunks = Math.ceil(file.size / chunkSize)
let currentChunk = 0
const spark = new SparkMD5.ArrayBuffer()
file.pause()
const loadNext = () => {
//
const fileInfo = this.uploadFiles.find(f => f.id === fileId)
if (fileInfo?.isCancelled) {
reject(new Error('用户取消了MD5计算'))
fileReader.abort()
delete this.md5Readers[fileId]
return
}
const start = currentChunk * chunkSize
const end = Math.min(start + chunkSize, file.size)
fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
}
fileReader.onload = (e) => {
spark.append(e.target.result)
currentChunk++
// MD5
const progress = Math.floor((currentChunk / chunks) * 100)
const fileInfo = this.uploadFiles.find(f => f.id === fileId)
if (fileInfo) {
fileInfo.md5Progress = `${progress}%`
}
if (currentChunk < chunks) {
loadNext()
} else {
const md5 = spark.end()
delete this.md5Readers[fileId] // reader
resolve(md5)
}
}
fileReader.onerror = () => {
delete this.md5Readers[fileId]
reject(new Error(`文件读取出错: ${fileReader.error.message}`))
}
loadNext()
})
},
//
onFileProgress(rootFile, file, chunk) {
const fileInfo = this.uploadFiles.find(f => f.id === rootFile.id)
if (fileInfo && !fileInfo.isCancelled && !fileInfo.errorMsg) {
// 使
const fileProgress = Math.floor((rootFile.progress() * 100))
fileInfo.progress = fileProgress
//
const averageSpeed = rootFile.averageSpeed || this.uploader?.averageSpeed || 0
// 00.00
const speed = averageSpeed > 0 ? (averageSpeed / 1024 / 1024) : 0
fileInfo.speed = speed.toFixed(2)
}
//
this.calculateTotalProgress()
},
//
onUploadSuccess(rootFile, file, response) {
const res1 = JSON.parse(response)
if (res1.code === 200) {
const fileInfo = this.uploadFiles.find(f => f.id === rootFile.id)
if (fileInfo) {
fileInfo.completed = true
fileInfo.progress = 100
fileInfo.speed = 0
this.completedFiles++
}
//
if (this.completedFiles === this.totalFiles) {
this.handleUploadConfirm()
}
} else {
const errorMsg = res1.msg || '未知错误'
const fileInfo = this.uploadFiles.find(f => f.id === rootFile.id)
if (fileInfo) {
fileInfo.errorMsg = errorMsg
}
this.$message.error(`文件 ${rootFile.name} 上传失败: ${errorMsg}`)
this.removeFile(rootFile.id, false) //
}
},
//
onFileError(rootFile, file, response) {
const errorMsg = response ? (JSON.parse(response)?.msg || '上传失败') : '网络异常'
const fileInfo = this.uploadFiles.find(f => f.id === rootFile.id)
if (fileInfo) {
fileInfo.errorMsg = errorMsg
fileInfo.isMd5Processing = false
}
this.showMessage(`文件 ${rootFile.name} 上传失败`, 'error')
this.$emit('onUploadError', response)
this.removeFile(rootFile.id, false) //
},
// merge
handleUploadConfirm() {
if (this.uploadFiles.length === 0) {
this.showMessage('没有可处理的文件!', 'error')
return
}
this.nowDate = getCurrentTime()
const jsonArrayToSend = []
//
this.uploadFiles.forEach(file => {
if (!file.isCancelled && !file.errorMsg) {
file.isSyncing = true
}
})
//
const processFiles = this.uploadFiles
.filter(file => !file.isCancelled && !file.errorMsg) // /
.map(async(fileInfo) => {
const item = fileInfo.file
const json = {}
const jsonArray = []
const jsonString = {}
//
if (item.file.type.startsWith('image')) {
const fileBase64 = await this.getBase64(item.file)
const imgRes = await this.getImgPx(fileBase64)
item.file.px = `${imgRes.width}px*${imgRes.height}px`
} else {
item.file.px = ''
}
//
jsonString.file_name = item.file.name
jsonString.file_size = item.file.size
jsonString.file_type = item.file.name.split('.').pop() || ''
json.last_modified = item.file.lastModified
jsonString.file_path = ''
jsonString.sequence = null
jsonString.archive_id = this.arcId
jsonString.file_dpi = item.file.px
jsonString.file_thumbnail = ''
jsonString.create_time = this.nowDate
jsonString.id = null
jsonArray.push(jsonString)
//
if (this.isBatchMount === 'true') {
json.categoryId = this.selectedCategory.id
} else {
json.documentId = this.selectedDocument.id
}
//
json.archivesId = this.arcId
json.identifier = fileInfo.md5
json.filename = item.name
json.totalChunks = item.chunks.length - 1
json.totalSize = item.size
json.fileJsonString = JSON.stringify(jsonArray)
jsonArrayToSend.push(json)
return json
})
//
Promise.all(processFiles)
.then((jsonArray) => {
if (jsonArray.length === 0) {
this.showMessage('无有效文件进行合并', 'warning')
this.resetUploadState()
return
}
const apiUrl = this.isBatchMount === 'true'
? `${this.baseApi}/api/collect/merge`
: `${this.baseApi}/api/minioUpload/merge`
//
return this.sendRequest(apiUrl, jsonArray)
})
.then((res) => {
if (res.data.code === 200) {
this.showMessage('所有文件上传成功', 'success')
this.$emit('onUploadSuccess', res.data.data, this.uploadFiles.map(f => f.name), jsonArrayToSend)
} else {
this.showMessage('文件合并失败', 'error')
}
this.resetUploadState()
})
.catch((err) => {
this.showMessage('上传服务器失败', 'error')
this.$emit('onUploadError', err)
this.resetUploadState()
})
},
//
calculateTotalProgress() {
if (this.uploadFiles.length === 0) {
this.totalProgress = 0
return
}
//
const validFiles = this.uploadFiles.filter(f => !f.isCancelled)
if (validFiles.length === 0) {
this.totalProgress = 0
return
}
const total = validFiles.reduce((sum, file) => sum + file.progress, 0)
this.totalProgress = Math.floor(total / validFiles.length)
},
//
removeFile(fileId, isRemoveFromList = true) {
if (isRemoveFromList) {
this.uploadFiles = this.uploadFiles.filter(f => f.id !== fileId)
}
this.totalFiles = this.uploadFiles.length
this.completedFiles = this.uploadFiles.filter(f => f.completed).length
if (this.uploadFiles.length === 0) {
this.isUpload = false
this.totalProgress = 0
} else {
this.calculateTotalProgress()
}
},
//
handleCancelUpload(fileInfo) {
const file = fileInfo.file
//
if (file.pause) file.pause()
// MD5
if (this.md5Readers[fileInfo.id]) {
this.md5Readers[fileInfo.id].abort()
delete this.md5Readers[fileInfo.id]
}
//
fileInfo.isCancelled = true
fileInfo.errorMsg = '用户取消上传'
fileInfo.isMd5Processing = false
//
this.calculateTotalProgress()
this.$message.info(`文件 ${fileInfo.name} 已取消上传`)
},
//
handleReUpload(fileInfo) {
const file = fileInfo.file
//
fileInfo.isCancelled = false
fileInfo.errorMsg = ''
fileInfo.progress = 0
fileInfo.isMd5Processing = true
fileInfo.md5Progress = '0%'
fileInfo.completed = false
// MD5
this.computeMD5(file, fileInfo.id)
.then(md5 => {
fileInfo.md5 = md5
fileInfo.isMd5Processing = false
file.uniqueIdentifier = md5
file.resume()
})
.catch(error => {
fileInfo.errorMsg = error.message
fileInfo.isMd5Processing = false
this.$message.error(`文件 ${fileInfo.name} 重新处理失败: ${error.message}`)
})
},
//
handleClearAll() {
//
this.uploadFiles.forEach(fileInfo => {
if (fileInfo.file.pause) fileInfo.file.pause()
// MD5
if (this.md5Readers[fileInfo.id]) {
this.md5Readers[fileInfo.id].abort()
}
})
//
this.uploadFiles = []
this.isUpload = false
this.totalProgress = 0
this.completedFiles = 0
this.totalFiles = 0
this.md5Readers = {}
//
if (this.uploader) {
this.uploader.fileList.forEach(file => file.cancel())
this.uploader.fileList = []
this.uploader.files = []
}
if (this.$refs.uploader) {
this.$refs.uploader.files = []
this.$refs.uploader.fileList = []
}
},
//
resetUploadState() {
this.uploadFiles.forEach(file => {
file.isSyncing = false
})
//
setTimeout(() => {
this.handleClearAll() //
}, 2000)
},
//
showMessage(message, type) {
this.$message({ message, type, offset: 8 })
},
sendRequest(url, data) {
return axios.post(url, data, {
headers: { 'Authorization': getToken() }
})
},
getBase64(file) {
return new Promise((resolve) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
})
},
getImgPx(img) {
return new Promise((resolve) => {
const image = new Image()
image.src = img
image.onload = () => resolve({ width: image.width, height: image.height })
})
},
handleClearData() {
this.handleClearAll() //
}
}
}
</script>
<style lang="scss" scoped>
.uploader-big{
position: relative;
margin-left: 10px;
.uploader-drop {
.uploader-btn {
i {
font-size: 20px;
color: #1F55EB;
}
}
}
}
.upload_process_box{
position: absolute;
right: 0;
top: 44px;
width: 540px;
border-radius: 5px;
border: 1px dashed #409eff;
padding: 10px;
background: #fff;
z-index: 100;
//
.upload-operate-bar {
margin-bottom: 10px;
text-align: right;
}
.file-progress-item {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
//
.file-base-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
.file-size {
font-size: 12px;
color: #999;
}
}
//
.file-status-tip {
margin: 5px 0;
font-size: 12px;
.upload-speed {
color: #1F55EB;
font-weight: 500;
}
.error-tip {
color: #f56c6c;
}
.success-tip {
color: #67c23a;
}
}
//
.file-operate-btn {
margin-top: 5px;
text-align: right;
.el-button {
padding: 0;
font-size: 12px;
}
}
}
.total-progress {
margin-top: 15px;
padding-top: 10px;
border-top: 1px solid #e0e0e0;
}
}
//
::v-deep .uploader-list{
max-height: 344px;
overflow-y: scroll;
.uploader-file-size{
text-align: right !important;
}
.uploader-file-name{
text-align: left !important;
}
.uploader-file{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/attachment.png") no-repeat;
background-size: 100% 100%;
}
&.icon-image{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/image.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-excel,&.icon-xlsx,&.icon-xls{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/excel.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-pdf{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/pdf.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-ppt, &.icon-pptx{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/ppt.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-word,&.icon-docx,&.icon-doc{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/word.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-zip,&.icon-rar{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/zip.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-txt{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/txt.png") no-repeat;
background-size: 100% 100%;
}
}
&.icon-ofd{
.uploader-file-icon:before{
background: url("~@/assets/images/fileIcon/OFD.png") no-repeat;
background-size: 100% 100%;
}
}
}
}
</style>

670
src/views/components/category/preUpload4.vue

@ -1,670 +0,0 @@
<template>
<div class="upload-minio">
<!-- 文件选择按钮与隐藏的input -->
<el-button
type="primary"
:disabled="!isCaValid || isCheckingCa"
icon="el-icon-upload"
>
选择文件
</el-button>
<input
type="file"
multiple
:disabled="!isCaValid || isCheckingCa"
class="file-input"
@change="handleFileSelect"
>
<!-- 文件上传弹框选择文件后自动打开 -->
<el-dialog
title="文件上传列表"
class="minio-file"
:close-on-click-modal="false"
:modal-append-to-body="false"
append-to-body
:visible.sync="uploadMinioVisible"
:before-close="handleCloseDialog"
width="600px"
>
<el-button
type="success"
style="margin-bottom: 20px"
:disabled="fileList.some(item => item.uploading || item.merging) || fileList.length === 0"
@click="handleUploadConfirm"
>
开始上传
</el-button>
<!-- 文件列表 -->
<div v-if="fileList.length === 0" class="empty-tip">
暂无选择的文件
</div>
<div
v-for="(fileItem, index) in fileList"
:key="index"
class="file-item"
>
<div class="file-name">{{ fileItem.file.name }} <span class="file-size">{{ formatFileSize(fileItem.file.size) }}</span></div>
<!-- 上传进度条 -->
<div v-if="fileItem.uploading" class="progress-wrapper">
<span class="progress-text">上传进度: {{ fileItem.progress }}%</span>
<div class="progress-bar" :style="{ width: fileItem.progress + '%' }" />
</div>
<!-- 合并中状态 -->
<div v-if="fileItem.merging" class="merge-loading ">
<span>合并中...</span>
</div>
<!-- 上传错误信息 -->
<p v-if="fileItem.errorMsg" class="error">{{ fileItem.errorMsg }}</p>
<!-- 上传成功 -->
<p v-if="fileItem.successMsg" class="success">{{ fileItem.successMsg }}</p>
</div>
</el-dialog>
</div>
</template>
<script>
import { FetchCheckCaValidity } from '@/api/system/auth2'
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import { getToken } from '@/utils/auth'
import { getCurrentTime } from '@/utils/index'
export default {
name: 'MinioMultiChunkUpload',
props: {
selectedDocument: {
type: Object,
default: () => ({})
},
arcId: {
type: String,
default: ''
},
selectedCategory: {
type: Object,
default: () => ({})
},
isBatchMount: {
type: String,
default: ''
}
},
data() {
return {
uploadMinioVisible: false, //
fileList: [], //
CHUNK_SIZE: 5 * 1024 * 1024, // (5MB)
totalMergeStartTime: null, //
totalMergeEndTime: null, //
allChunksUploaded: false, //
isCaValid: false, // CA
isCheckingCa: false // CA
}
},
mounted() {
this.getCheckCaValidity() // CA
},
methods: {
formatFileSize(bytes) {
if (bytes === 0) return '0.00MB'
const mb = bytes / (1024 * 1024) // 1MB = 1024KB = 1024*1024B
return mb.toFixed(2) + 'MB'
},
/**
* 工具方法格式化时间戳为易读的本地时间带毫秒
* @param {number} timestamp - 时间戳
* @returns {string} 格式化后的时间字符串
*/
formatTime(timestamp) {
if (!timestamp) return '无'
return new Date(timestamp).toLocaleString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
millisecond: '3-digit'
})
},
/**
* 工具方法计算两个时间戳的差值并格式化
* @param {number} start - 开始时间戳
* @param {number} end - 结束时间戳
* @returns {string} 格式化的耗时字符串
*/
getTimeDiff(start, end) {
if (!start || !end) return '0ms'
const diff = end - start
if (diff < 1000) return `${diff}ms`
if (diff < 60000) return `${(diff / 1000).toFixed(2)}s`
return `${(diff / 60000).toFixed(2)}min`
},
/**
* 工具方法获取图片分辨率
* @param {string} base64 - 图片base64编码
* @returns {Promise<{width: number, height: number}>} 图片宽高
*/
getImgPx(base64) {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
resolve({ width: img.width, height: img.height })
}
img.src = base64
})
},
/**
* 工具方法将文件转为base64
* @param {File} file - 文件对象
* @returns {Promise<string>} base64编码
*/
getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = () => resolve(reader.result)
reader.onerror = (err) => reject(err)
})
},
/**
* 工具方法显示提示信息
* @param {string} msg - 提示内容
* @param {string} type - 类型success/error
*/
showMessage(message, type) {
this.$message({ message, type, offset: 8 })
},
/**
* 重置上传状态
*/
resetUploadState() {
this.allChunksUploaded = false
},
/**
* 校验CA证书有效性
*/
async getCheckCaValidity() {
try {
this.isCheckingCa = true
const res = await FetchCheckCaValidity()
this.isCaValid = !!res //
if (!this.isCaValid) {
this.showMessage('CA证书不在有效期内,无法进行分片上传', 'error')
}
} catch (err) {
this.isCaValid = false
this.showMessage('CA证书校验失败:' + err.message, 'error')
console.error('CA校验接口异常:', err)
} finally {
this.isCheckingCa = false
}
},
/**
* 手动重新校验CA证书
*/
recheckCa() {
this.getCheckCaValidity()
},
/**
* 多文件选择处理函数选择后打开弹框仅初始化文件列表不上传
* @param {Event} e - 选择文件的事件对象
*/
async handleFileSelect(e) {
// 1. CA
if (this.isCheckingCa) {
this.showMessage('正在校验CA证书,请稍候...', 'warning')
e.target.value = ''
return
}
if (!this.isCaValid) {
this.showMessage('CA证书不在有效期内,无法上传文件', 'error')
e.target.value = ''
return
}
// 2.
const selectedFiles = Array.from(e.target.files)
if (selectedFiles.length === 0) return
//
const newFileList = selectedFiles.map(file => ({
file,
uploading: false,
merging: false,
progress: 0,
errorMsg: '',
successMsg: '',
md5: '',
md5StartTime: null,
md5EndTime: null,
chunkUploadStartTime: null,
chunkUploadEndTime: null,
mergeStartTime: null,
mergeEndTime: null
}))
this.fileList = [...this.fileList, ...newFileList]
// 3.
this.uploadMinioVisible = true
// inputchange
e.target.value = ''
},
/**
* 单个文件的分片上传流程
* @param {Object} fileItem - 文件状态对象
*/
async uploadFileChunks(fileItem) {
const file = fileItem.file
fileItem.uploading = true
fileItem.progress = 0
fileItem.errorMsg = ''
fileItem.successMsg = ''
try {
// MD5
const fileMd5 = await this.calculateFileMd5(file, fileItem)
fileItem.md5 = fileMd5
console.log(`${file.name}】文件MD5:`, fileMd5)
//
const totalChunks = Math.ceil(file.size / this.CHUNK_SIZE)
console.log(`${file.name}】总分片数:`, totalChunks)
//
fileItem.chunkUploadStartTime = new Date().getTime()
console.log(`${file.name}】分片上传开始时间:${this.formatTime(fileItem.chunkUploadStartTime)}`)
const uploadedChunks = []
for (let i = 0; i < totalChunks; i++) {
//
const checkResult = await this.checkChunkExists(fileMd5, i)
if (!checkResult.exists) {
//
await this.uploadSingleChunk(file, fileMd5, i)
}
uploadedChunks.push(i)
fileItem.progress = Math.round((uploadedChunks.length / totalChunks) * 100)
}
//
fileItem.chunkUploadEndTime = new Date().getTime()
console.log(`${file.name}】分片上传结束时间:${this.formatTime(fileItem.chunkUploadEndTime)},耗时:${this.getTimeDiff(fileItem.chunkUploadStartTime, fileItem.chunkUploadEndTime)}`)
fileItem.progress = 100
} catch (err) {
const fileName = file.name
if (fileItem.chunkUploadStartTime && !fileItem.chunkUploadEndTime) {
fileItem.chunkUploadEndTime = new Date().getTime()
console.log(`${fileName}】分片上传异常结束时间:${this.formatTime(fileItem.chunkUploadEndTime)},耗时:${this.getTimeDiff(fileItem.chunkUploadStartTime, fileItem.chunkUploadEndTime)}`)
}
fileItem.errorMsg = '分片上传失败: ' + (err.message || '未知错误')
console.error(`${fileName}】分片上传流程异常:`, err.message)
this.allChunksUploaded = false
} finally {
fileItem.uploading = false
}
},
/**
* 计算文件MD5
* @param {File} file - 待上传的文件
* @param {Object} fileItem - 文件状态对象
* @returns {Promise<string>} 文件的MD5值
*/
calculateFileMd5(file, fileItem) {
return new Promise((resolve, reject) => {
fileItem.md5StartTime = new Date().getTime()
console.log(`${file.name}】MD5计算开始时间:${this.formatTime(fileItem.md5StartTime)}`)
const spark = new SparkMD5.ArrayBuffer()
const fileReader = new FileReader()
const chunkSize = 2 * 1024 * 1024
let offset = 0
const loadNextChunk = () => {
const blob = file.slice(offset, offset + chunkSize)
fileReader.readAsArrayBuffer(blob)
}
fileReader.onload = (e) => {
spark.append(e.target.result)
offset += chunkSize
if (offset < file.size) {
loadNextChunk()
} else {
fileItem.md5EndTime = new Date().getTime()
console.log(`${file.name}】MD5计算结束时间:${this.formatTime(fileItem.md5EndTime)},耗时:${this.getTimeDiff(fileItem.md5StartTime, fileItem.md5EndTime)}`)
resolve(spark.end())
}
}
fileReader.onerror = (err) => reject(err)
loadNextChunk()
})
},
/**
* 校验所有分片是否存在
* @param {string} fileMd5 - 文件MD5
* @param {number} totalChunks - 总分片数
* @returns {Promise<boolean>} 所有分片是否都存在
*/
async checkAllChunksExist(fileMd5, totalChunks) {
for (let i = 0; i < totalChunks; i++) {
const result = await this.checkChunkExists(fileMd5, i)
if (!result.exists) {
console.warn(`${fileMd5}】分片${i}未上传`)
return false
}
}
return true
},
/**
* 检查分片是否已存在
* @param {string} fileMd5 - 文件MD5
* @param {number} chunkIndex - 分片索引
* @returns {Promise<Object>} 分片存在状态
*/
async checkChunkExists(fileMd5, chunkIndex) {
const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API
const response = await axios.get(linkSrc + '/api/minioUpload/chunk', {
params: { fileMd5, chunkIndex },
headers: { 'Authorization': getToken() }
})
if (response.data.code !== 200) {
throw new Error('检查分片失败: ' + response.data.msg)
}
return response.data.data
},
/**
* 上传单个分片
* @param {File} file - 待上传的文件
* @param {string} fileMd5 - 文件MD5
* @param {number} chunkIndex - 分片索引
*/
async uploadSingleChunk(file, fileMd5, chunkIndex) {
const start = chunkIndex * this.CHUNK_SIZE
const end = Math.min(start + this.CHUNK_SIZE, file.size)
const chunkBlob = file.slice(start, end)
const formData = new FormData()
formData.append('file', chunkBlob, `${fileMd5}_${chunkIndex}`)
formData.append('fileMd5', fileMd5)
formData.append('chunkIndex', chunkIndex)
const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API
const response = await axios.post(linkSrc + '/api/minioUpload/chunk', formData, {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': getToken()
}
})
if (response.data.code !== 200) {
throw new Error(`分片${chunkIndex}上传失败: ` + response.data.msg)
}
},
/**
* 批量上传并合并弹框内点击按钮触发核心修改点新增串行上传逻辑
*/
async handleUploadConfirm() {
if (this.fileList.length === 0) {
this.showMessage('请先选择要上传的文件!', 'warning')
return
}
// /
const pendingFiles = this.fileList.filter(item => !item.successMsg && !item.errorMsg)
if (pendingFiles.length === 0) {
this.showMessage('暂无待上传的文件!', 'warning')
return
}
// 1.
this.allChunksUploaded = true
for (const fileItem of pendingFiles) {
await this.uploadFileChunks(fileItem)
//
if (fileItem.errorMsg) {
this.allChunksUploaded = false
}
}
// 2.
if (!this.allChunksUploaded) {
this.showMessage('部分文件分片上传失败,无法执行合并', 'error')
return
}
const validFiles = this.fileList.filter(item => !item.errorMsg && item.progress === 100)
if (validFiles.length === 0) {
this.showMessage('无有效分片上传完成的文件!', 'error')
return
}
this.totalMergeStartTime = new Date().getTime()
console.log(`【整体合并】所有文件分片上传完成,开始合并,合并开始时间:${this.formatTime(this.totalMergeStartTime)}`)
const nowDate = getCurrentTime()
validFiles.forEach(fileItem => {
fileItem.merging = true
fileItem.mergeStartTime = new Date().getTime()
console.log(`【文件${fileItem.file.name}】合并开始时间:${this.formatTime(fileItem.mergeStartTime)}`)
})
try {
const processFiles = validFiles.map(async(fileItem) => {
const file = fileItem.file
const json = {}
const jsonArray = []
const jsonString = {}
if (file.type.startsWith('image')) {
const fileBase64 = await this.getBase64(file)
const imgRes = await this.getImgPx(fileBase64)
jsonString.file_dpi = `${imgRes.width}px*${imgRes.height}px`
} else {
jsonString.file_dpi = ''
}
jsonString.file_name = file.name
jsonString.file_size = file.size
jsonString.file_type = file.name.split('.').pop() || ''
jsonString.last_modified = file.lastModified
jsonString.file_path = ''
jsonString.sequence = null
jsonString.archive_id = this.arcId
jsonString.create_time = nowDate
jsonString.id = null
jsonString.file_thumbnail = ''
jsonArray.push(jsonString)
console.log('file.lastModified', file.lastModified)
if (this.isBatchMount === 'true') {
json.categoryId = this.selectedCategory.id
} else {
json.documentId = this.selectedDocument.id
}
const totalChunks = Math.ceil(file.size / this.CHUNK_SIZE)
const chunksExist = await this.checkAllChunksExist(fileItem.md5, totalChunks)
if (!chunksExist) {
throw new Error(`${file.name}】部分分片未上传完成,无法合并`)
}
json.archivesId = this.arcId
json.identifier = fileItem.md5
json.filename = file.name
json.totalChunks = totalChunks
json.totalSize = file.size
json.fileJsonString = JSON.stringify(jsonArray)
return json
})
const jsonArray = await Promise.all(processFiles)
//
const linkSrc = process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API
const response = await axios.post(linkSrc + '/api/minioUpload/merge', jsonArray, {
headers: {
'Authorization': getToken(),
'Content-Type': 'application/json'
}
})
this.totalMergeEndTime = new Date().getTime()
const totalMergeDuration = this.getTimeDiff(this.totalMergeStartTime, this.totalMergeEndTime)
if (response.data.code === 200) {
this.showMessage('所有文件上传并合并成功', 'success')
validFiles.forEach((fileItem) => {
fileItem.mergeEndTime = new Date().getTime()
const mergeDuration = this.getTimeDiff(fileItem.mergeStartTime, fileItem.mergeEndTime)
console.log(`【文件${fileItem.file.name}】合并结束时间:${this.formatTime(fileItem.mergeEndTime)},合并耗时:${mergeDuration}`)
fileItem.successMsg = '上传成功!'
fileItem.merging = false
})
console.log(`【整体合并】所有文件合并完成,整体合并耗时:${totalMergeDuration}`)
this.$emit('onUploadSuccess', response.data.data, validFiles.map(f => f.file.name), jsonArray)
this.uploadMinioVisible = false
this.fileList = [] //
} else {
throw new Error(response.data.msg || '合并失败')
}
} catch (err) {
this.totalMergeEndTime = new Date().getTime()
const totalMergeDuration = this.getTimeDiff(this.totalMergeStartTime, this.totalMergeEndTime)
console.log(`【整体合并】合并失败,耗时:${totalMergeDuration},异常信息:`, err)
this.showMessage(`文件合并失败: ${err.message}`, 'error')
validFiles.forEach(fileItem => {
fileItem.merging = false
fileItem.errorMsg = fileItem.errorMsg || `合并失败: ${err.message}`
})
this.$emit('onUploadError', err)
} finally {
this.resetUploadState()
}
},
/**
* 关闭弹框时清空文件列表
*/
handleCloseDialog() {
this.uploadMinioVisible = false
this.fileList = []
this.resetUploadState()
}
}
}
</script>
<style scoped>
.upload-minio {
position: relative;
margin-top: 10px;
}
/* 隐藏的文件选择input,覆盖在按钮上 */
.file-input {
position: absolute;
left: 0;
top: 0;
width: 100px;
height: 36px;
padding: 5px;
opacity: 0;
cursor: pointer;
z-index: 10;
}
/* 弹框内空提示 */
.empty-tip {
text-align: center;
color: #999;
padding: 20px 0;
}
.file-item {
width: 100%;
border: 1px dashed #409eff;
padding: 10px;
border-radius: 4px;
margin-bottom: 10px;
}
.file-name {
font-weight: 500;
color: #333;
margin-bottom: 8px;
}
.progress-wrapper {
display: flex;
flex-direction: column;
gap: 5px;
}
.progress-bar {
height: 10px;
background-color: #42b983;
transition: width 0.3s ease;
border-radius: 10px;
}
.progress-text {
font-size: 14px;
color: #666;
}
.merge-loading {
color: #1890ff;
font-size: 14px;
}
.success {
color: #00C851;
font-size: 14px;
margin: 5px 0 0 0;
}
.error {
color: #ff4444;
font-size: 14px;
margin: 5px 0 0 0;
}
.minio-file{
.el-dialog{
.el-dialog__body{
padding: 15px 0 30px 0 !important;
}
}
}
.file-size {
color: #666;
font-weight: normal;
font-size: 12px;
margin-left: 8px;
}
</style>

279
src/views/components/category/preUpload5.vue

@ -1,279 +0,0 @@
<template>
<div class="upload-container">
<!-- 多文件选择框添加multiple属性 -->
<input type="file" multiple @change="handleFileSelect">
<!-- 遍历展示每个文件的上传状态 -->
<div v-for="(fileItem, index) in fileList" :key="index" class="file-item">
<div class="file-name">{{ fileItem.file.name }}</div>
<!-- 上传进度条 -->
<div v-if="fileItem.uploading" class="progress-wrapper">
<div class="progress-bar" :style="{ width: fileItem.progress + '%' }" />
<span class="progress-text">上传进度: {{ fileItem.progress }}%</span>
</div>
<!-- 错误信息 -->
<p v-if="fileItem.errorMsg" class="error">{{ fileItem.errorMsg }}</p>
<!-- 成功信息 -->
<p v-if="fileItem.successMsg" class="success">{{ fileItem.successMsg }}</p>
</div>
</div>
</template>
<script>
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import { getToken } from '@/utils/auth'
// import { getCurrentTime } from '@/utils/index'
export default {
name: 'MinioMultiChunkUpload',
data() {
return {
fileList: [], //
CHUNK_SIZE: 5 * 1024 * 1024 // (5MB)
}
},
methods: {
/**
* 多文件选择处理函数
* @param {Event} e - 选择文件的事件对象
*/
async handleFileSelect(e) {
const selectedFiles = Array.from(e.target.files) //
if (selectedFiles.length === 0) return
//
const newFileList = selectedFiles.map(file => ({
file, //
uploading: false, //
progress: 0, //
errorMsg: '', //
successMsg: '' //
}))
this.fileList = [...this.fileList, ...newFileList]
//
for (const fileItem of newFileList) {
await this.uploadFile(fileItem)
}
},
/**
* 计算文件MD5用于分片唯一标识
* @param {File} file - 待上传的文件
* @returns {Promise<string>} 文件的MD5值
*/
calculateFileMd5(file) {
return new Promise((resolve, reject) => {
const spark = new SparkMD5.ArrayBuffer()
const fileReader = new FileReader()
const chunkSize = 10 * 1024 * 1024 // MD5
let offset = 0
const loadNextChunk = () => {
const blob = file.slice(offset, offset + chunkSize)
fileReader.readAsArrayBuffer(blob)
}
fileReader.onload = (e) => {
spark.append(e.target.result)
offset += chunkSize
if (offset < file.size) {
loadNextChunk()
} else {
resolve(spark.end())
}
}
fileReader.onerror = reject
loadNextChunk()
})
},
/**
* 单个文件的上传主流程分片检查上传合并
* @param {Object} fileItem - 文件状态对象
*/
async uploadFile(fileItem) {
const file = fileItem.file
fileItem.uploading = true
fileItem.progress = 0
fileItem.errorMsg = ''
fileItem.successMsg = ''
try {
// 1. MD5
const fileMd5 = await this.calculateFileMd5(file)
console.log(`文件${file.name}的MD5:`, fileMd5)
// 2.
const totalChunks = Math.ceil(file.size / this.CHUNK_SIZE)
console.log(`文件${file.name}的总分片数:`, totalChunks)
// 3.
const uploadedChunks = []
for (let i = 0; i < totalChunks; i++) {
//
const checkResult = await this.checkChunkExists(fileMd5, i)
if (!checkResult.exists) {
//
await this.uploadSingleChunk(file, fileMd5, i)
}
uploadedChunks.push(i)
fileItem.progress = Math.round((uploadedChunks.length / totalChunks) * 100)
}
// 4.
const mergeResult = await this.mergeChunks(fileMd5, file.name, totalChunks)
console.log(`文件${file.name}的合并结果:`, mergeResult)
fileItem.successMsg = '上传成功! 路径: ' + mergeResult.filePath
fileItem.progress = 100
} catch (err) {
fileItem.errorMsg = '上传失败: ' + (err.message || '未知错误')
} finally {
fileItem.uploading = false
}
},
/**
* 检查分片是否已存在断点续传核心
* @param {string} fileMd5 - 文件MD5
* @param {number} chunkIndex - 分片索引
* @returns {Promise<Object>} 分片存在状态
*/
async checkChunkExists(fileMd5, chunkIndex) {
const response = await axios.get('/api/minioUpload/chunk', {
params: {
fileMd5,
chunkIndex
},
headers: { 'Authorization': getToken() }
})
if (response.data.code !== 200) {
throw new Error('检查分片失败: ' + response.data.msg)
}
return response.data.data
},
/**
* 上传单个分片
* @param {File} file - 待上传的文件
* @param {string} fileMd5 - 文件MD5
* @param {number} chunkIndex - 分片索引
*/
async uploadSingleChunk(file, fileMd5, chunkIndex) {
//
const start = chunkIndex * this.CHUNK_SIZE
const end = Math.min(start + this.CHUNK_SIZE, file.size)
const chunkBlob = file.slice(start, end)
// FormData
const formData = new FormData()
formData.append('file', chunkBlob, `${fileMd5}_${chunkIndex}`)
formData.append('fileMd5', fileMd5)
formData.append('chunkIndex', chunkIndex)
const response = await axios.post('/api/minioUpload/chunk', formData, {
headers: {
'Content-Type': 'multipart/form-data',
Authorization: getToken()
}
})
if (response.data.code !== 200) {
throw new Error('分片上传失败: ' + response.data.msg)
}
},
/**
* 合并所有分片
* @param {string} fileMd5 - 文件MD5
* @param {string} fileName - 原文件名
* @param {number} totalChunks - 总分片数
* @returns {Promise<Object>} 合并结果
*/
async mergeChunks(fileMd5, fileName, totalChunks) {
try {
const response = await axios.post('/api/minioUpload/merge', null, {
params: {
fileMd5,
fileName,
totalChunks
},
headers: { 'Authorization': getToken() }
})
console.log('合并分片请求响应:', JSON.stringify(response.data)) //
if (response.data.code !== 200) {
throw new Error(`合并失败: ${response.data.msg || '无错误信息'}`)
}
return response.data.data
} catch (err) {
console.error(`文件${fileName}合并请求异常:`, err.message, err.response?.data) //
throw err
}
}
}
}
</script>
<style scoped>
.upload-container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
/* 单个文件项样式 */
.file-item {
margin-top: 20px;
padding: 15px;
border: 1px solid #f0f0f0;
border-radius: 6px;
}
.file-name {
font-weight: 500;
margin-bottom: 10px;
color: #333;
}
/* 进度条容器 */
.progress-wrapper {
display: flex;
flex-direction: column;
gap: 5px;
}
.progress-bar {
height: 20px;
background-color: #42b983;
transition: width 0.3s ease;
border-radius: 10px;
}
.progress-text {
font-size: 14px;
color: #666;
}
/* 提示文字样式 */
.error {
color: #ff4444;
margin-top: 10px;
font-size: 14px;
}
.success {
color: #00C851;
margin-top: 10px;
font-size: 14px;
}
</style>

50
src/views/prearchiveLibrary/file/index.vue

@ -29,7 +29,7 @@
<template slot="title">原文上传</template> <template slot="title">原文上传</template>
<!-- <el-menu-item index="1-5" @click="fileUpload(0)">普通上传</el-menu-item> --> <!-- <el-menu-item index="1-5" @click="fileUpload(0)">普通上传</el-menu-item> -->
<!-- @click="fileUpload(1)" --> <!-- @click="fileUpload(1)" -->
<el-menu-item index="1-6">文件上传</el-menu-item>
<el-menu-item index="1-6" @click="fileUpload">文件上传</el-menu-item>
</el-menu-item-group> </el-menu-item-group>
</el-submenu> </el-submenu>
<el-submenu index="3"> <el-submenu index="3">
@ -103,7 +103,7 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<!--分页组件--> <!--分页组件-->
<el-pagination
<!-- <el-pagination
v-if="fileData.length !== 0" v-if="fileData.length !== 0"
:current-page="currentPage" :current-page="currentPage"
:total="page.total" :total="page.total"
@ -112,8 +112,9 @@
layout="total, prev, pager, next, sizes" layout="total, prev, pager, next, sizes"
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentPage" @current-change="handleCurrentPage"
/>
/> -->
</div> </div>
<PreUpload ref="uploadBigRef" is-pre-file="true" :arc-id="parentInfo &&parentInfo.id" :selected-document="selectedDocument" @onUploadSuccess="handleSuccessResource" @onUploadError="handleErrorResource" />
</el-drawer> </el-drawer>
<div v-if="fileDrawer" class="mask-modal level1-modal" @click="closeDrawer" /> <div v-if="fileDrawer" class="mask-modal level1-modal" @click="closeDrawer" />
@ -126,16 +127,17 @@ import { mapGetters } from 'vuex'
import { downloadFile } from '@/utils/index' import { downloadFile } from '@/utils/index'
import { FetchFileListByDocumentId } from '@/api/prearchiveLibrary/prearchiveLibrary' import { FetchFileListByDocumentId } from '@/api/prearchiveLibrary/prearchiveLibrary'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
import PreUpload from '@/views/components/category/preUpload'
export default { export default {
name: 'File', name: 'File',
components: { },
components: { PreUpload },
mixins: [ mixins: [
header(), header(),
form({}) form({})
], ],
props: { props: {
selectedCategory: {
selectedDocument: {
type: Object, type: Object,
default: function() { default: function() {
return {} return {}
@ -166,7 +168,7 @@ export default {
]) ])
}, },
watch: { watch: {
selectedCategory: function(newValue, oldValue) {
selectedDocument: function(newValue, oldValue) {
} }
}, },
created() { created() {
@ -174,6 +176,26 @@ export default {
mounted() { mounted() {
}, },
methods: { methods: {
fileUpload() {
this.$refs.uploadBigRef.uploadMinioVisible = true
},
handleSuccessResource(filePath, fileName, jsonArrayToSend) {
// console.log('handleSuccessResource', filePath)
// console.log('handleSuccessResource', fileName)
// console.log('handleSuccessResource', jsonArrayToSend)
// console.log('handleSuccessResource', jsonArrayToSend[0].fileJsonString)
// const fileJson = JSON.parse(jsonArrayToSend[0].fileJsonString)
// fileJson[0].file_path = filePath
// fileJson[0].is_quote = null
// fileJson[0].last_modified = jsonArrayToSend[0].last_modified
// this.fileList = fileJson
// this.upfilePath = filePath
this.getFile()
},
handleErrorResource(res) {
console.log('handleErrorResource', res)
},
getFileIconClass(fileType) { getFileIconClass(fileType) {
if (!fileType) return 'icon-other' if (!fileType) return 'icon-other'
@ -218,7 +240,7 @@ export default {
this.getTableDisplayFieldsLoading = true this.getTableDisplayFieldsLoading = true
this.fileData = [] this.fileData = []
const params = { const params = {
documentId: this.selectedCategory.id,
documentId: this.selectedDocument.id,
archivesId: this.parentInfo.id archivesId: this.parentInfo.id
} }
FetchFileListByDocumentId(params).then(data => { FetchFileListByDocumentId(params).then(data => {
@ -228,7 +250,7 @@ export default {
}, },
closeDrawer() { closeDrawer() {
this.fileDrawer = false this.fileDrawer = false
// if (this.selectedCategory.arrangeType === 1) {
// if (this.selectedDocument.arrangeType === 1) {
// this.$parent.parentsAnjuanId = null // this.$parent.parentsAnjuanId = null
// } else { // } else {
// this.$parent.parentsJuanneiId = null // this.$parent.parentsJuanneiId = null
@ -265,20 +287,10 @@ export default {
this.currentPage = 1 this.currentPage = 1
this.page.size = size this.page.size = size
this.page.page = 0 this.page.page = 0
// if (this.selectedCategory.arrangeType === 1) {
// this.getViewTable(4, this.parentsData.parentsAnjuanId)
// } else {
// this.getViewTable(4, this.parentsData.parentsJuanneiId)
// }
}, },
handleCurrentPage(pageVal) { handleCurrentPage(pageVal) {
this.currentPage = pageVal this.currentPage = pageVal
this.page.page = pageVal - 1 this.page.page = pageVal - 1
// if (this.selectedCategory.arrangeType === 1) {
// this.getViewTable(4, this.parentsData.parentsAnjuanId)
// } else {
// this.getViewTable(4, this.parentsData.parentsJuanneiId)
// }
}, },
handleClose(done) { handleClose(done) {
done() done()
@ -290,7 +302,7 @@ export default {
'archiveNo': this.parentInfo.maintitle 'archiveNo': this.parentInfo.maintitle
}}) }})
window.open(routeData.href, '_blank') window.open(routeData.href, '_blank')
localStorage.setItem('documentId', JSON.stringify(this.selectedCategory.id))
localStorage.setItem('documentId', JSON.stringify(this.selectedDocument.id))
localStorage.setItem('fileParentInfo', JSON.stringify(this.parentInfo)) localStorage.setItem('fileParentInfo', JSON.stringify(this.parentInfo))
localStorage.setItem('fileTables', JSON.stringify(this.fileData)) localStorage.setItem('fileTables', JSON.stringify(this.fileData))
localStorage.setItem('fileCurrent', JSON.stringify(row)) localStorage.setItem('fileCurrent', JSON.stringify(row))

23
src/views/prearchiveLibrary/index.vue

@ -86,7 +86,7 @@
</div> </div>
<File ref="fileEle" :selected-category="selectedDocument" />
<File ref="fileEle" :selected-document="selectedDocument" />
<detail ref="archivesInfo" :selected-document="selectedDocument" /> <detail ref="archivesInfo" :selected-document="selectedDocument" />
<!-- 批量成件 --> <!-- 批量成件 -->
@ -146,24 +146,6 @@
</el-dialog> </el-dialog>
</div> </div>
</div> </div>
<!-- @close-dialog="closeDialog" -->
<!-- <UploadOriginal ref="uploadOriginalRef" :selected-category="selectedCategory" :arc-id="arcId" /> -->
<!-- <el-dialog class="preview-dialog" :modal-append-to-body="false" :close-on-click-modal="false" append-to-body :before-close="closeDialog" :visible="fileOneVisible" title="单文件上传">
<span class="dialog-right-top" />
<span class="dialog-left-bottom" />
<div class="setting-dialog">
<div style="height: 300px;">
<p class="input-style">{{ fileOriginal }}</p>
<PreUpload ref="preUploadRefs" :selected-document="selectedDocument" :arc-id="arcId" @onUploadSuccess="handleSuccessResource" @onUploadError="handleErrorResource" />
</div>
<div slot="footer" class="dialog-footer" style="margin-top: 20px !important;">
<el-button type="text" @click="closeDialog">取消</el-button>
<el-button :loading="archivesBtnLoading" type="primary" @click="handlerArchivesSubmit">确定</el-button>
</div>
</div>
</el-dialog> -->
</div> </div>
</template> </template>
@ -182,7 +164,6 @@ import PreviewForm from '@/views/components/category/PreviewForm'
import batchFile from './module/batchFile' import batchFile from './module/batchFile'
import moveFile from './module/moveFile' import moveFile from './module/moveFile'
import detail from './module/detail' import detail from './module/detail'
// import PreUpload from '@/views/components/category/preUpload'
import File from './file/index' import File from './file/index'
import { exportFile } from '@/utils/index' import { exportFile } from '@/utils/index'
@ -356,6 +337,8 @@ export default {
// //
this.$refs.previewForm.fileOriginal = fileOriginal this.$refs.previewForm.fileOriginal = fileOriginal
this.$refs.previewForm.fileJsonString = fileJsonString this.$refs.previewForm.fileJsonString = fileJsonString
console.log('fileJsonString', JSON.parse(fileJsonString))
console.log('fileJsonString', JSON.parse(fileJsonString).length)
} else { } else {
this.$refs.previewForm.archivesType = 'add' this.$refs.previewForm.archivesType = 'add'
} }

Loading…
Cancel
Save