Browse Source

20251126-2

master
xuhuajiao 3 weeks ago
parent
commit
4d6e2cff39
  1. 11
      src/api/prearchiveLibrary/prearchiveLibrary.js
  2. 11
      src/api/system/auth2.js
  3. 25
      src/utils/index.js
  4. 5
      src/views/collectReorganizi/collectionLibrary/anjuan/tableList.vue
  5. 5
      src/views/collectReorganizi/collectionLibrary/juannei/index.vue
  6. 4
      src/views/collectReorganizi/collectionLibrary/module/collectHeader.vue
  7. 13
      src/views/components/category/PreviewForm.vue
  8. 645
      src/views/components/category/preUpload4 -数组.vue
  9. 591
      src/views/components/category/preUpload4.vue
  10. 35
      src/views/prearchiveLibrary/module/detail.vue
  11. 4
      src/views/system/archivesCategory/form.vue

11
src/api/prearchiveLibrary/prearchiveLibrary.js

@ -117,4 +117,13 @@ export function FetchReDocumentBase64ByFileId(params) {
})
}
export default { add, prearchEdit, del, FetchInitPreDocument, FetchInitDocumentsViewTable, FetchDoeditDocument, FetchBatchToFile, FetchMergeToFile, FetchMove, FetchArchivesDetails, FetchFileListByDocumentId, FetchArchivesMetadata, FetchReDocumentBase64ByFileId }
// downloadFile
export function FetchDownload(params) {
return request({
url: 'api/minioUpload/download',
method: 'get',
params
})
}
export default { add, prearchEdit, del, FetchInitPreDocument, FetchInitDocumentsViewTable, FetchDoeditDocument, FetchBatchToFile, FetchMergeToFile, FetchMove, FetchArchivesDetails, FetchFileListByDocumentId, FetchArchivesMetadata, FetchReDocumentBase64ByFileId, FetchDownload }

11
src/api/system/auth2.js

@ -52,4 +52,13 @@ export function FetchCaPasswordById(params) {
})
}
export default { add, edit, del, FetchAppSecret, FetchCaPfxCertificatesById, FetchCaPasswordById }
// 判断ca证书是否在有效期内
export function FetchCheckCaValidity(params) {
return request({
url: 'api/oauthClient/checkCaValidity',
method: 'get',
params
})
}
export default { add, edit, del, FetchAppSecret, FetchCaPfxCertificatesById, FetchCaPasswordById, FetchCheckCaValidity }

25
src/utils/index.js

@ -475,3 +475,28 @@ export function saveByteArray(fileName, byte) {
link.click()
}
// 下载文件
export function downloadMinioFile(obj, name, suffix) {
// 校验参数,避免空值
if (!obj || !name || !suffix) {
console.error('下载参数缺失:obj/name/suffix不能为空')
return
}
try {
const blob = new Blob([obj])
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
const fileName = `${name}.${suffix}`
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
setTimeout(() => {
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
}, 100)
} catch (err) {
console.error('下载工具函数执行失败:', err)
}
}

5
src/views/collectReorganizi/collectionLibrary/anjuan/tableList.vue

@ -47,12 +47,11 @@
{{ scope.row[field.fieldName] }}
</template>
</el-table-column>
<el-table-column v-if="!isRecycle" label="装盒" width="88" align="center" :fixed="parentsData.fixedStatusBar ? false : 'right' ">
<!-- <el-table-column v-if="!isRecycle" label="装盒" width="88" align="center" :fixed="parentsData.fixedStatusBar ? false : 'right' ">
<template slot-scope="scope">
<!-- 未装 / 已装 -->
<span :class="['row-state', 'row-packing', scope.row.case_no ? 'state-active' : '' ]">{{ scope.row.case_no ? '已装': '未装' }}</span>
</template>
</el-table-column>
</el-table-column> -->
<el-table-column v-if="!isRecycle && !(isTitleType === 3 && activeIndex === 1 && selectedCategory.arrangeType !== 1 )" label="审批锁定" width="88" align="center" :fixed="parentsData.fixedStatusBar ? false : 'right' ">
<template slot-scope="scope">
<span :class="['row-state', 'row-warehousing', scope.row.collect_formal===2 ? 'state-active' : '' ]">{{ scope.row.collect_formal===2 ? '归档': '空闲' }}</span>

5
src/views/collectReorganizi/collectionLibrary/juannei/index.vue

@ -57,12 +57,11 @@
{{ scope.row[field.fieldName] }}
</template>
</el-table-column>
<el-table-column v-if="!isRecycle" label="装盒" width="88" align="center" :fixed="parentsData.fixedStatusBar ? false : 'right' ">
<!-- <el-table-column v-if="!isRecycle" label="装盒" width="88" align="center" :fixed="parentsData.fixedStatusBar ? false : 'right' ">
<template slot-scope="scope">
<!-- 未装 / 已装 -->
<span :class="['row-state', 'row-packing', scope.row.case_no ? 'state-active' : '' ]">{{ scope.row.case_no ? '已装': '未装' }}</span>
</template>
</el-table-column>
</el-table-column> -->
<el-table-column v-if="!isRecycle" label="审批锁定" width="88" align="center" :fixed="parentsData.fixedStatusBar ? false : 'right' ">
<template slot-scope="scope">
<span :class="['row-state', 'row-warehousing', scope.row.collect_formal===2 ? 'state-active' : '' ]">{{ scope.row.collect_formal===2 ? '归档': '空闲' }}</span>

4
src/views/collectReorganizi/collectionLibrary/module/collectHeader.vue

@ -106,11 +106,11 @@
<el-menu-item index="2-12" @click="handleQuickPaper">快速组卷</el-menu-item>
<el-menu-item index="2-13" @click="handlePaper">手工组卷</el-menu-item>
</el-menu-item-group>
<el-menu-item-group v-if="isTitleType !== 4" class="collect-submenu-group submenu-tree">
<!-- <el-menu-item-group v-if="isTitleType !== 4" class="collect-submenu-group submenu-tree">
<template slot="title">档案装盒</template>
<el-menu-item index="2-2" @click="handlePackingBox(0)">装盒</el-menu-item>
<el-menu-item v-if="selectedCategory.arrangeType !== 1 " index="2-3" @click="handlePackingBox(1)">分卷装盒</el-menu-item>
</el-menu-item-group>
</el-menu-item-group> -->
</el-submenu>
<el-submenu index="3">
<template slot="title">

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

@ -285,7 +285,8 @@ export default {
entityIndex: null,
fileList: [],
archivesSummaryResponse: {},
pickerDateMap: {}
pickerDateMap: {},
minioPreResult: {}
}
},
computed: {
@ -321,6 +322,7 @@ export default {
this.$refs.preUploadRefs.handleClearData()
},
handleSuccessResource(filePath, fileName, jsonArrayToSend) {
this.minioPreResult = filePath
console.log('handleSuccessResource', filePath)
console.log('handleSuccessResource', fileName)
console.log('handleSuccessResource', jsonArrayToSend)
@ -329,9 +331,12 @@ export default {
this.fileOriginal = fileName
const fileJson = JSON.parse(jsonArrayToSend[0].fileJsonString)
fileJson[0].file_path = '/' + filePath
fileJson[0].file_path = '/' + filePath.filePath
fileJson[0].is_quote = null
fileJson[0].last_modified = jsonArrayToSend[0].last_modified
fileJson[0].ca_id = this.minioPreResult.caId
fileJson[0].encryption_time = this.minioPreResult.timestamp
fileJson[0].signature = this.minioPreResult.signature
this.fileJsonString = JSON.stringify(fileJson)
},
// handleSuccessResource(filePath, fileName, jsonArrayToSend) {
@ -1075,6 +1080,7 @@ export default {
'jsonString': JSON.stringify(this.addOrUpdateForm),
'fileJsonString': this.fileJsonString,
'fondsAffiliation': this.selectedDocument.fondsId
}
console.log('params', params)
prearchEdit(params).then(res => {
@ -1473,7 +1479,8 @@ export default {
position: relative;
width: 540px !important;
display: flex;
justify-content: space-between;
justify-content: flex-start;
flex-wrap: wrap;
.input-style{
width: 560px;
height: 34px;

645
src/views/components/category/preUpload4 -数组.vue

@ -0,0 +1,645 @@
<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>
<!-- 合并中状态 -->
<div v-if="fileItem.merging" class="merge-loading">
<span>合并中...</span>
</div>
<!-- 验签中状态 -->
<div v-if="fileItem.verifying" class="verify-loading">
<span>验签中...</span>
</div>
<!-- 上传错误信息 -->
<p v-if="fileItem.errorMsg" class="error">{{ fileItem.errorMsg }}</p>
<!-- 上传成功+验签结果 -->
<div v-if="fileItem.successMsg" class="success-section">
<p class="success">{{ fileItem.successMsg }}</p>
<!-- 验签成功 -->
<p v-if="fileItem.verifySuccess" class="verify-success"> 验签成功</p>
<!-- 验签失败 -->
<p v-if="fileItem.verifyError" class="verify-error"> 验签失败: {{ fileItem.verifyError }}</p>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import { getToken } from '@/utils/auth'
// getCurrentTime
const getCurrentTime = () => {
return new Date().toISOString().replace(/T/, ' ').replace(/\..+/, '')
}
export default {
name: 'MinioMultiChunkUpload',
props: {
selectedDocument: {
type: Object,
default: () => ({})
},
arcId: {
type: String,
default: ''
},
selectedCategory: {
type: Object,
default: () => ({})
},
isBatchMount: {
type: String,
default: ''
}
},
data() {
return {
fileList: [], //
CHUNK_SIZE: 5 * 1024 * 1024, // (5MB)
totalMergeStartTime: null, //
totalMergeEndTime: null, //
baseApi: '', //
allChunksUploaded: false //
}
},
methods: {
/**
* 工具方法格式化时间戳为易读的本地时间带毫秒
* @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)
})
},
/**
* 工具方法显示提示信息可替换为项目的Message组件
* @param {string} msg - 提示内容
* @param {string} type - 类型success/error
*/
showMessage(msg, type) {
if (type === 'error') {
console.error(msg)
} else {
console.log(msg)
}
// ElementUI/NaiveUI
// this.$message[type](msg)
},
/**
* 重置上传状态
*/
resetUploadState() {
this.allChunksUploaded = false
//
},
/**
* 多文件选择处理函数
* @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, //
merging: false, //
progress: 0, //
errorMsg: '', // /
successMsg: '', //
verifying: false, //
verifySuccess: false, //
verifyError: '', //
md5: '', // MD5
//
md5StartTime: null, // MD5
md5EndTime: null, // MD5
chunkUploadStartTime: null, //
chunkUploadEndTime: null, //
mergeStartTime: null, //
mergeEndTime: null, //
verifyStartTime: null, //
verifyEndTime: null //
}))
this.fileList = [...this.fileList, ...newFileList]
//
for (const fileItem of newFileList) {
await this.uploadFileChunks(fileItem)
}
//
this.allChunksUploaded = true
this.handleUploadConfirm()
},
/**
* 单个文件的分片上传流程仅上传分片不立即合并
* @param {Object} fileItem - 文件状态对象
*/
async uploadFileChunks(fileItem) {
const file = fileItem.file
fileItem.uploading = true
fileItem.progress = 0
fileItem.errorMsg = ''
fileItem.successMsg = ''
try {
// 1. MD5
const fileMd5 = await this.calculateFileMd5(file, fileItem)
fileItem.md5 = fileMd5 // MD5
console.log(`${file.name}】文件MD5:`, fileMd5)
// 2.
const totalChunks = Math.ceil(file.size / this.CHUNK_SIZE)
console.log(`${file.name}】总分片数:`, totalChunks)
// 3.
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 // 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) => {
// MD5
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 // 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 {
// MD5
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 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(`分片${chunkIndex}上传失败: ` + response.data.msg)
}
},
/**
* 所有文件分片上传完成后自动执行批量合并核心改造方法
*/
async handleUploadConfirm() {
if (this.fileList.length === 0 || !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()
const jsonArrayToSend = []
//
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 = ''
}
// handleUploadConfirm
jsonString.file_name = file.name
jsonString.file_size = file.size
jsonString.file_type = file.name.split('.').pop() || ''
json.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)
//
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)
jsonArrayToSend.push(json)
return json
})
//
const jsonArray = await Promise.all(processFiles)
// /api/minioUpload/merge
const response = await axios.post('/api/minioUpload/merge', jsonArray, {
headers: {
'Authorization': getToken(),
'Content-Type': 'application/json' // 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, index) => {
fileItem.mergeEndTime = new Date().getTime()
const mergeDuration = this.getTimeDiff(fileItem.mergeStartTime, fileItem.mergeEndTime)
console.log(`【文件${fileItem.file.name}】合并结束时间:${this.formatTime(fileItem.mergeEndTime)},合并耗时:${mergeDuration}`)
//
const mergeResult = response.data.data[index] || {}
fileItem.successMsg = '上传成功! 路径: ' + (mergeResult.filePath || '未知路径')
fileItem.merging = false
//
this.verifySignature(fileItem, mergeResult)
})
console.log(`【整体合并】所有文件合并完成,合并结束时间:${this.formatTime(this.totalMergeEndTime)},整体合并耗时:${totalMergeDuration}`)
this.$emit('onUploadSuccess', response.data.data, validFiles.map(f => f.file.name), jsonArrayToSend)
} else {
throw new Error(response.data.msg || '合并失败')
}
} catch (err) {
//
this.totalMergeEndTime = new Date().getTime()
const totalMergeDuration = this.getTimeDiff(this.totalMergeStartTime, this.totalMergeEndTime)
console.log(`【整体合并】合并失败,失败时间:${this.formatTime(this.totalMergeEndTime)},合并耗时:${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()
}
},
/**
* 调用验签接口验证签名
* @param {Object} fileItem - 文件状态对象
* @param {Object} mergeResult - 合并成功后的返回数据
*/
async verifySignature(fileItem, mergeResult) {
const file = fileItem.file
const verifyParams = {
certFingerprint: mergeResult.certFingerprint,
filePath: mergeResult.filePath,
signature: mergeResult.signature,
timestamp: mergeResult.timestamp
}
console.log(`${file.name}】验签参数:`, verifyParams)
fileItem.verifying = true
try {
const response = await axios.post(
'/api/minioUpload/verify-signature',
null,
{
params: verifyParams,
headers: { 'Authorization': getToken() }
}
)
console.log(`${file.name}】验签接口响应:`, response.data)
if (response.data.code === 200) { //
fileItem.verifySuccess = true
} else {
fileItem.verifyError = response.data.error || '验签失败,原因未知'
}
} catch (err) {
fileItem.verifyError = `请求异常: ${err.message || '网络错误'}`
console.error(`${file.name}】验签请求异常:`, err)
} finally {
//
fileItem.verifyEndTime = new Date().getTime()
fileItem.verifying = false
//
const totalTime = this.getTimeDiff(fileItem.md5StartTime, fileItem.verifyEndTime)
console.log(`${file.name}】上传+合并+验签全流程总耗时:${totalTime}`)
}
}
}
}
</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;
}
/* 合并中状态 */
.merge-loading {
margin: 10px 0;
color: #1890ff;
font-size: 14px;
}
/* 验签中状态 */
.verify-loading {
margin: 10px 0;
color: #42b983;
font-size: 14px;
}
/* 上传和验签提示样式 */
.success-section {
display: flex;
flex-direction: column;
gap: 5px;
}
.success {
color: #00C851;
margin: 0;
font-size: 14px;
}
.error {
color: #ff4444;
margin: 10px 0 0 0;
font-size: 14px;
}
.verify-success {
color: #00C851;
font-size: 14px;
margin: 0;
}
.verify-error {
color: #ff4444;
font-size: 14px;
margin: 0;
}
</style>

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

@ -1,8 +1,15 @@
<template>
<div class="upload-container">
<!-- 多文件选择框添加multiple属性 -->
<input type="file" multiple @change="handleFileSelect">
<!-- 遍历展示每个文件的上传状态 -->
<div class="upload-minio">
<input
type="file"
multiple
:disabled="!isCaValid || isCheckingCa"
class="file-input"
@change="handleFileSelect"
>
<el-button>上传文件</el-button>
<!-- 遍历展示每个文件的上传状态移除验签相关渲染 -->
<div v-for="(fileItem, index) in fileList" :key="index" class="file-item">
<div class="file-name">{{ fileItem.file.name }}</div>
<!-- 上传进度条 -->
@ -10,37 +17,62 @@
<div class="progress-bar" :style="{ width: fileItem.progress + '%' }" />
<span class="progress-text">上传进度: {{ fileItem.progress }}%</span>
</div>
<!-- 验签中状态 -->
<div v-if="fileItem.verifying" class="verify-loading">
<span>验签...</span>
<!-- 合并中状态 -->
<div v-if="fileItem.merging" class="merge-loading">
<span>合并...</span>
</div>
<!-- 上传错误信息 -->
<p v-if="fileItem.errorMsg" class="error">{{ fileItem.errorMsg }}</p>
<!-- 上传成功+验签结果 -->
<div v-if="fileItem.successMsg" class="success-section">
<p class="success">{{ fileItem.successMsg }}</p>
<!-- 验签成功 -->
<p v-if="fileItem.verifySuccess" class="verify-success"> 验签成功</p>
<!-- 验签失败 -->
<p v-if="fileItem.verifyError" class="verify-error"> 验签失败: {{ fileItem.verifyError }}</p>
</div>
<!-- 上传成功移除验签结果展示 -->
<p v-if="fileItem.successMsg" class="success">{{ fileItem.successMsg }}</p>
</div>
</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 {
fileList: [], //
CHUNK_SIZE: 5 * 1024 * 1024 // (5MB)
CHUNK_SIZE: 5 * 1024 * 1024, // (5MB)
totalMergeStartTime: null, //
totalMergeEndTime: null, //
allChunksUploaded: false, //
isCaValid: false, // CA
isCheckingCa: false // CA
}
},
mounted() {
this.getCheckCaValidity() // CA
},
methods: {
/**
* 工具方法格式化时间戳为易读的本地时间带毫秒
@ -75,183 +107,222 @@ export default {
},
/**
* 多文件选择处理函数
* @param {Event} e - 选择文件的事件对象
* 工具方法获取图片分辨率
* @param {string} base64 - 图片base64编码
* @returns {Promise<{width: number, height: number}>} 图片宽高
*/
async handleFileSelect(e) {
const selectedFiles = Array.from(e.target.files) //
if (selectedFiles.length === 0) return
getImgPx(base64) {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => {
resolve({ width: img.width, height: img.height })
}
img.src = base64
})
},
//
const newFileList = selectedFiles.map(file => ({
file, //
uploading: false, //
progress: 0, //
errorMsg: '', //
successMsg: '', //
verifying: false, //
verifySuccess: false, //
verifyError: '', //
//
md5StartTime: null, // MD5
md5EndTime: null, // MD5
chunkUploadStartTime: null, //
chunkUploadEndTime: null, //
mergeStartTime: null, //
mergeEndTime: null, //
verifyStartTime: null, //
verifyEndTime: null //
}))
this.fileList = [...this.fileList, ...newFileList]
/**
* 工具方法将文件转为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)
})
},
//
for (const fileItem of newFileList) {
await this.uploadFile(fileItem)
/**
* 工具方法显示提示信息可替换为项目的Message组件
* @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 // true/false
if (this.isCaValid) {
// this.showMessage('CA', 'success')
} else {
this.showMessage('CA证书不在有效期内,无法进行分片上传', 'error')
}
} catch (err) {
this.isCaValid = false
this.showMessage('CA证书校验失败:' + err.message, 'error')
console.error('CA校验接口异常:', err)
} finally {
this.isCheckingCa = false
}
},
/**
* 计算文件MD5用于分片唯一标识
* @param {File} file - 待上传的文件
* @param {Object} fileItem - 文件状态对象
* @returns {Promise<string>} 文件的MD5值
* 手动重新校验CA证书
*/
calculateFileMd5(file, fileItem) {
return new Promise((resolve, reject) => {
// MD5
fileItem.md5StartTime = new Date().getTime()
console.log(`${file.name}】MD5计算开始时间:${this.formatTime(fileItem.md5StartTime)}`)
recheckCa() {
this.getCheckCaValidity()
},
const spark = new SparkMD5.ArrayBuffer()
const fileReader = new FileReader()
const chunkSize = 2 * 1024 * 1024 // MD5
let offset = 0
/**
* 多文件选择处理函数
* @param {Event} e - 选择文件的事件对象
*/
async handleFileSelect(e) {
// 1. CA
if (this.isCheckingCa) {
this.showMessage('正在校验CA证书,请稍候...', 'error')
return
}
if (!this.isCaValid) {
this.showMessage('CA证书不在有效期内,无法上传文件', 'error')
e.target.value = '' //
return
}
const loadNextChunk = () => {
const blob = file.slice(offset, offset + chunkSize)
fileReader.readAsArrayBuffer(blob)
}
// 2.
const selectedFiles = Array.from(e.target.files)
if (selectedFiles.length === 0) return
fileReader.onload = (e) => {
spark.append(e.target.result)
offset += chunkSize
//
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]
if (offset < file.size) {
loadNextChunk()
} else {
// MD5
fileItem.md5EndTime = new Date().getTime()
console.log(`${file.name}】MD5计算结束时间:${this.formatTime(fileItem.md5EndTime)},耗时:${this.getTimeDiff(fileItem.md5StartTime, fileItem.md5EndTime)}`)
resolve(spark.end())
}
}
//
for (const fileItem of newFileList) {
await this.uploadFileChunks(fileItem)
}
fileReader.onerror = (err) => {
reject(err)
}
loadNextChunk()
})
//
this.allChunksUploaded = true
this.handleUploadConfirm()
},
/**
* 单个文件的上传主流程分片检查上传合并验签
* 单个文件的分片上传流程
* @param {Object} fileItem - 文件状态对象
*/
async uploadFile(fileItem) {
async uploadFileChunks(fileItem) {
const file = fileItem.file
fileItem.uploading = true
fileItem.progress = 0
fileItem.errorMsg = ''
fileItem.successMsg = ''
fileItem.verifying = false
fileItem.verifySuccess = false
fileItem.verifyError = ''
try {
// 1. MD5
// MD5
const fileMd5 = await this.calculateFileMd5(file, fileItem)
fileItem.md5 = fileMd5
console.log(`${file.name}】文件MD5:`, fileMd5)
// 2.
//
const totalChunks = Math.ceil(file.size / this.CHUNK_SIZE)
console.log(`${file.name}】总分片数:`, totalChunks)
// 3.
//
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)}`)
//
const allChunksExist = await this.checkAllChunksExist(fileMd5, totalChunks)
if (!allChunksExist) {
throw new Error('部分分片未上传完成,无法合并')
}
// 4.
fileItem.mergeStartTime = new Date().getTime()
console.log(`${file.name}】分片合并开始时间:${this.formatTime(fileItem.mergeStartTime)}`)
const mergeResult = await this.mergeChunks(fileMd5, file.name, totalChunks)
//
fileItem.mergeEndTime = new Date().getTime()
console.log(`${file.name}】分片合并结束时间:${this.formatTime(fileItem.mergeEndTime)},耗时:${this.getTimeDiff(fileItem.mergeStartTime, fileItem.mergeEndTime)}`)
console.log(`${file.name}】合并结果:`, mergeResult)
fileItem.successMsg = '上传成功! 路径: ' + mergeResult.filePath
fileItem.progress = 100
// 5.
fileItem.verifyStartTime = new Date().getTime()
console.log(`${file.name}】验签开始时间:${this.formatTime(fileItem.verifyStartTime)}`)
await this.verifySignature(fileItem, mergeResult)
// finally
fileItem.verifyEndTime = fileItem.verifyEndTime || new Date().getTime()
console.log(`${file.name}】验签结束时间:${this.formatTime(fileItem.verifyEndTime)},耗时:${this.getTimeDiff(fileItem.verifyStartTime, fileItem.verifyEndTime)}`)
//
const totalTime = this.getTimeDiff(fileItem.md5StartTime, fileItem.verifyEndTime)
console.log(`${file.name}】上传+验签全流程总耗时:${totalTime}`)
} 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)}`)
}
if (fileItem.mergeStartTime && !fileItem.mergeEndTime) {
fileItem.mergeEndTime = new Date().getTime()
console.log(`${fileName}】分片合并异常结束时间:${this.formatTime(fileItem.mergeEndTime)},耗时:${this.getTimeDiff(fileItem.mergeStartTime, fileItem.mergeEndTime)}`)
}
if (fileItem.verifyStartTime && !fileItem.verifyEndTime) {
fileItem.verifyEndTime = new Date().getTime()
console.log(`${fileName}】验签异常结束时间:${this.formatTime(fileItem.verifyEndTime)},耗时:${this.getTimeDiff(fileItem.verifyStartTime, fileItem.verifyEndTime)}`)
}
fileItem.errorMsg = '上传失败: ' + (err.message || '未知错误')
console.error(`${fileName}】上传流程异常:`, err.message)
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>} 所有分片是否都存在
@ -268,17 +339,14 @@ export default {
},
/**
* 检查分片是否已存在断点续传核心
* 检查分片是否已存在
* @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
},
params: { fileMd5, chunkIndex },
headers: { 'Authorization': getToken() }
})
@ -296,12 +364,10 @@ export default {
* @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)
@ -320,96 +386,177 @@ export default {
},
/**
* 合并所有分片
* @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(`${fileName}:`, JSON.stringify(response.data.data))
// console.log(`${fileName}:`, response.data.data.success)
// console.log(`${fileName}:`, response.data.data.error)
if (!response.data.data.success) {
throw new Error(`合并失败: ${response.data.data.error || '无错误信息'}`)
}
return response.data.data
} catch (err) {
console.error(`${fileName}】合并请求异常:`, err.message, err.response?.data.data)
throw err
async handleUploadConfirm() {
if (this.fileList.length === 0 || !this.allChunksUploaded) {
this.showMessage('没有可处理的文件或分片上传未完成!', 'error')
return
}
},
/**
* 调用验签接口验证签名
* @param {Object} fileItem - 文件状态对象
* @param {Object} mergeResult - 合并成功后的返回数据
*/
async verifySignature(fileItem, mergeResult) {
const file = fileItem.file
const verifyParams = {
certFingerprint: mergeResult.certFingerprint,
filePath: mergeResult.filePath,
signature: mergeResult.signature,
timestamp: mergeResult.timestamp
const validFiles = this.fileList.filter(item => !item.errorMsg && item.progress === 100)
if (validFiles.length === 0) {
this.showMessage('无有效分片上传完成的文件!', 'error')
return
}
// console.log(`${file.name}:`, verifyParams)
fileItem.verifying = true
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 response = await axios.post(
'/api/minioUpload/verify-signature',
null,
{
params: verifyParams,
headers: { 'Authorization': getToken() }
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() || ''
json.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)
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}】部分分片未上传完成,无法合并`)
}
)
console.log(`${file.name}】验签接口响应:`, response.data)
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)
// jsonArray
const response = await axios.post('/api/minioUpload/merge', jsonArray[0], {
headers: {
'Authorization': getToken(),
'Content-Type': 'application/json'
}
})
if (response.data.code !== 500) {
fileItem.verifySuccess = true
this.totalMergeEndTime = new Date().getTime()
const totalMergeDuration = this.getTimeDiff(this.totalMergeStartTime, this.totalMergeEndTime)
if (response.data.code === 200) {
this.showMessage('所有文件合并成功', 'success')
validFiles.forEach((fileItem, index) => {
fileItem.mergeEndTime = new Date().getTime()
const mergeDuration = this.getTimeDiff(fileItem.mergeStartTime, fileItem.mergeEndTime)
console.log(`【文件${fileItem.file.name}】合并结束时间:${this.formatTime(fileItem.mergeEndTime)},合并耗时:${mergeDuration}`)
// const mergeResult = response.data.data || {}
// fileItem.successMsg = '! : ' + (mergeResult.filePath || '')
fileItem.successMsg = '上传成功!'
fileItem.merging = false
})
console.log(`【整体合并】所有文件合并完成,合并结束时间:${this.formatTime(this.totalMergeEndTime)},整体合并耗时:${totalMergeDuration}`)
this.$emit('onUploadSuccess', response.data.data, validFiles.map(f => f.file.name), jsonArray)
} else {
fileItem.verifyError = response.data.error || '验签失败,原因未知'
throw new Error(response.data.msg || '合并失败')
}
} catch (err) {
fileItem.verifyError = `请求异常: ${err.message || '网络错误'}`
console.error(`${file.name}】验签请求异常:`, err)
this.totalMergeEndTime = new Date().getTime()
const totalMergeDuration = this.getTimeDiff(this.totalMergeStartTime, this.totalMergeEndTime)
console.log(`【整体合并】合并失败,失败时间:${this.formatTime(this.totalMergeEndTime)},合并耗时:${totalMergeDuration},异常信息:`, err)
this.showMessage(`合并失败: ${err.message}`, 'error')
validFiles.forEach(fileItem => {
fileItem.merging = false
fileItem.errorMsg = fileItem.errorMsg || `合并失败: ${err.message}`
})
this.$emit('onUploadError', err)
} finally {
//
fileItem.verifyEndTime = new Date().getTime()
fileItem.verifying = false
this.resetUploadState()
}
},
handleClearData() {
this.fileList = []
}
}
}
</script>
<style scoped>
.upload-container {
max-width: 800px;
margin: 20px auto;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
.upload-minio {
position: relative;
/* max-width: 800px; */
margin-top: 20px;
/* background-color: red; */
.el-button{
cursor: pointer;
}
}
.file-input {
position: absolute;
left: 0;
top: 0;
padding: 5px;
opacity: 0;
cursor: pointer;
}
.ca-checking {
color: #1890ff;
font-size: 14px;
margin: 5px 0;
}
.ca-invalid {
color: #ff4444;
font-size: 14px;
margin: 5px 0;
}
.recheck-btn {
margin: 5px 0 20px;
padding: 5px 10px;
cursor: pointer;
background: #42b983;
color: #fff;
border: none;
border-radius: 4px;
}
.recheck-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
/* 单个文件项样式 */
.file-item {
margin-top: 20px;
padding: 15px;
border: 1px solid #f0f0f0;
border-radius: 6px;
}
.file-name {
@ -418,7 +565,6 @@ export default {
color: #333;
}
/* 进度条容器 */
.progress-wrapper {
display: flex;
flex-direction: column;
@ -426,7 +572,8 @@ export default {
}
.progress-bar {
height: 20px;
width: 100%;
height: 10px;
background-color: #42b983;
transition: width 0.3s ease;
border-radius: 10px;
@ -437,23 +584,15 @@ export default {
color: #666;
}
/* 验签中状态 */
.verify-loading {
.merge-loading {
margin: 10px 0;
color: #42b983;
color: #1890ff;
font-size: 14px;
}
/* 上传和验签提示样式 */
.success-section {
display: flex;
flex-direction: column;
gap: 5px;
}
.success {
color: #00C851;
margin: 0;
margin: 10px 0 0 0;
font-size: 14px;
}
@ -462,16 +601,4 @@ export default {
margin: 10px 0 0 0;
font-size: 14px;
}
.verify-success {
color: #00C851;
font-size: 14px;
margin: 0;
}
.verify-error {
color: #ff4444;
font-size: 14px;
margin: 0;
}
</style>

35
src/views/prearchiveLibrary/module/detail.vue

@ -77,8 +77,8 @@
<template slot-scope="scope">
<div class="handle-btn">
<el-button class="iconfont icon-sulan" @click="toPreview(scope.row)" />
<!-- <el-button class="iconfont icon-xiazai" />
<el-button class="iconfont icon-dayin" /> -->
<el-button class="iconfont icon-xiazai" @click="downloadFile(scope.row)" />
<!-- <el-button class="iconfont icon-dayin" /> -->
</div>
</template>
</el-table-column>
@ -109,6 +109,8 @@
import { form } from '@crud/crud'
import { FetchArchivesDetails, FetchFileListByDocumentId, FetchArchivesMetadata } from '@/api/prearchiveLibrary/prearchiveLibrary'
import { mapGetters } from 'vuex'
import { downloadMinioFile } from '@/utils/index'
import { getToken } from '@/utils/auth'
export default {
name: 'PrearchiveLibraryDetail',
components: { },
@ -148,6 +150,35 @@ export default {
mounted() {
},
methods: {
//
downloadFile(row) {
const url = this.baseApi + '/api/minioUpload/download' + row.file_path
const fetchOptions = {
method: 'GET',
headers: {
'Authorization': getToken()
}
}
fetch(url, fetchOptions)
.then(res => {
if (!res.ok) {
throw new Error(`请求失败:${res.status} ${res.statusText}`)
}
return res.blob()
})
.then(blob => {
downloadMinioFile(blob, row.file_name.split('.')[0], row.file_type)
})
.catch((err) => {
console.error('下载失败详情:', err)
this.$message({
message: '下载文件失败,请检查权限或网络',
type: 'error',
offset: 8
})
})
},
toPreview(row) {
const routeData = this.$router.resolve({
path: '/preview',

4
src/views/system/archivesCategory/form.vue

@ -20,14 +20,14 @@
<el-form-item label="编码" prop="code">
<el-input v-model="form.code" />
</el-form-item>
<el-row v-if="isAdd">
<!-- <el-row v-if="isAdd">
<el-form-item label="是否装盒" prop="isCase">
<el-radio-group v-model="form.isCase" style="width: 580px;">
<el-radio :label="true">装盒</el-radio>
<el-radio :label="false">不装盒</el-radio>
</el-radio-group>
</el-form-item>
</el-row>
</el-row> -->
<el-row v-if="isAdd">
<el-form-item label="整理方式" prop="arrangeType">
<el-radio-group v-model="form.arrangeType" style="width: 580px;">

Loading…
Cancel
Save