9 changed files with 3266 additions and 335 deletions
-
36src/utils/upload.js
-
733src/views/collectReorganizi/batchConnection/module/form copy.vue
-
504src/views/collectReorganizi/batchConnection/module/form-zip.vue
-
921src/views/collectReorganizi/batchConnection/module/form.vue
-
850src/views/components/category/preUpload-非zip-test.vue
-
8src/views/features/ssoLogin.vue
-
8src/views/system/fileLibraryManage/index.vue
-
167src/views/system/fileLibraryManage/processManage/index.vue
-
374src/views/system/fileLibraryManage/processManage/module/form.vue
@ -0,0 +1,733 @@ |
|||
<template> |
|||
<div> |
|||
<!-- 本地挂接 / 远程挂接 主弹窗 --> |
|||
<el-dialog |
|||
:title="connectionTitle" |
|||
:close-on-click-modal="false" |
|||
:modal-append-to-body="false" |
|||
append-to-body |
|||
:visible.sync="localHitchVisible" |
|||
:before-close="handleCancel" |
|||
@opened="opened" |
|||
> |
|||
<el-form ref="form" :model="form" :rules="rules" label-width="120px"> |
|||
<el-form-item label="重复验证方式" prop="checkRepeatType"> |
|||
<el-select v-model="form.checkRepeatType" placeholder="请选择" style="width: 400px;"> |
|||
<el-option |
|||
v-for="item in typeOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="挂接字段" prop="hitchFiled"> |
|||
<el-select v-model="form.hitchFiled" placeholder="请选择" style="width: 400px;" @change="selectFiled"> |
|||
<el-option |
|||
v-for="item in fieldOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="挂接详情" prop="fields"> |
|||
<div class="tag-wrapper"> |
|||
<el-tag v-for="item in hitchRemarkArray" :key="item" :closable="item !== '*'" @close="removeTag(item)"> |
|||
{{ item }} |
|||
</el-tag> |
|||
</div> |
|||
<el-input v-model="form.fields" style="display: none;" type="hidden" /> |
|||
</el-form-item> |
|||
<el-form-item label="匹配模式" prop="matchingMode"> |
|||
<el-select v-model="form.matchingMode" placeholder="请选择" style="width: 400px;" @change="changeMatchedType"> |
|||
<el-option |
|||
v-for="item in matchedOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item v-if="!connectionType" label="上传附件" prop="file" class="upload-wm"> |
|||
<!-- 打开子选择弹窗 --> |
|||
<el-button size="small" @click="openFileSelectDialog">选择文件/文件夹</el-button> |
|||
|
|||
<!-- 主弹窗展示:展示【文件名】,不展示完整路径;提交接口传完整路径 uploadResultPaths --> |
|||
<div v-if="uploadResultPaths.length" class="file-list"> |
|||
<div v-for="(path, idx) in uploadResultPaths" :key="idx" class="file-item"> |
|||
<i class="iconfont icon-xiaowenjian" /> |
|||
{{ getFileNameFromPath(path) }} |
|||
</div> |
|||
</div> |
|||
<el-input v-show="false" v-model="form.file" /> |
|||
</el-form-item> |
|||
<el-form-item v-if="connectionType" label="挂接类型" prop="protocol"> |
|||
<el-select v-model="form.protocol" placeholder="请选择" style="width: 400px;" @change="changeProtocol"> |
|||
<el-option |
|||
v-for="item in batchTypeOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item v-if="connectionType" label="文件路径" prop="filePath"> |
|||
<el-select v-model="form.filePath" placeholder="请选择" value-key="id" style="width: 400px;" @change="changeFilePath"> |
|||
<el-option |
|||
v-for="(item,index) in filePathOptions" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item |
|||
v-if="connectionType" |
|||
label="" |
|||
prop="fileRemote" |
|||
class="file_remote-style" |
|||
:rules="form.filePath !== null ? rules.fileRemote : []" |
|||
:required="form.filePath !== null" |
|||
> |
|||
<el-button v-if="form.filePath && fileRemoteLoading" type="primary" :loading="fileRemoteLoading" style="border: none; display: block; padding: 0 0 4px 0;">文件列表加载中</el-button> |
|||
<el-radio-group v-model="form.fileRemote"> |
|||
<el-radio |
|||
v-for="item in fileRemoteOptions" |
|||
:key="item.value" |
|||
:value="item.value" |
|||
:label="item.name" |
|||
>{{ item.name }}</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
</el-form> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button type="text" @click="handleCancel">取消</el-button> |
|||
<el-button :loading="btnLoading" type="primary" @click.native="handleComfired">确定</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
|
|||
<!-- =========子弹窗:文件选择+上传弹窗========= --> |
|||
<el-dialog |
|||
title="选择文件/文件夹" |
|||
:visible.sync="fileSelectDialogVisible" |
|||
width="650px" |
|||
:close-on-click-modal="false" |
|||
:modal-append-to-body="false" |
|||
append-to-body |
|||
@closed="resetFileDialog" |
|||
> |
|||
<!-- 选择文件 input 普通多选,不带webkitdirectory --> |
|||
<input |
|||
ref="fileOnlyInput" |
|||
type="file" |
|||
multiple |
|||
style="display: none" |
|||
@change="onSelectFileOnly" |
|||
> |
|||
<!-- 选择文件夹 input --> |
|||
<input |
|||
ref="folderInput" |
|||
type="file" |
|||
webkitdirectory |
|||
directory |
|||
multiple |
|||
style="display: none" |
|||
@change="onSelectFolder" |
|||
> |
|||
|
|||
<div class="select-btn-group"> |
|||
<el-button type="primary" :disabled="fileDialogUploading" @click="$refs.fileOnlyInput.click()">选择文件</el-button> |
|||
<el-button type="primary" :disabled="fileDialogUploading" @click="$refs.folderInput.click()">选择文件夹</el-button> |
|||
</div> |
|||
|
|||
<!-- 子弹窗上传进度条 --> |
|||
<div v-if="fileDialogUploading" class="dialog-upload-tip"> |
|||
<el-progress :percentage="dialogUploadProgress" /> |
|||
<div>正在上传:{{ dialogUploadingFileName }}</div> |
|||
</div> |
|||
|
|||
<div class="select-file-list"> |
|||
<div v-if="tempSelectedFiles.length === 0" class="empty-tip">暂未选择文件</div> |
|||
<div v-for="(f, idx) in tempSelectedFiles" :key="idx" class="temp-file-item"> |
|||
<i class="iconfont icon-xiaowenjian" /> |
|||
<span class="file-name">{{ f.name }}</span> |
|||
<i class="iconfont icon-shanchu" :disabled="fileDialogUploading" @click="deleteTempFile(idx)" /> |
|||
</div> |
|||
</div> |
|||
|
|||
<div slot="footer"> |
|||
<el-button @click="fileSelectDialogVisible = false">取消</el-button> |
|||
<el-button type="primary" :loading="fileDialogUploading" :disabled="tempSelectedFiles.length === 0" @click="startUploadFiles">开始上传</el-button> |
|||
<el-button type="primary" :disabled="!dialogUploadSuccess" @click="confirmSelectFiles">确认</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { crud } from '@crud/crud' |
|||
import { FetchInitCategoryInputFieldByPid } from '@/api/system/category/category' |
|||
import { FetchFindDetailsByProtocol, FetchFileByRemoteConnection } from '@/api/system/remoteConnection' |
|||
import { batchMountUpload, remoteConnectiontUpload } from '@/utils/upload' |
|||
import { mapGetters } from 'vuex' |
|||
import axios from 'axios' |
|||
import { getToken } from '@/utils/auth' |
|||
|
|||
// 分片大小 5MB |
|||
const CHUNK_SIZE = 5 * 1024 * 1024 |
|||
|
|||
export default { |
|||
name: 'LocalHitch', |
|||
mixins: [crud()], |
|||
props: { |
|||
selectedCategory: { |
|||
type: Object, |
|||
default: function() { |
|||
return {} |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
btnLoading: false, |
|||
connectionTitle: '本地挂接', |
|||
localHitchVisible: false, |
|||
hitchRemarkArray: [], |
|||
connectionType: 0, |
|||
fileRemoteLoading: false, |
|||
fileRemoteOptions: [], |
|||
|
|||
// =========子弹窗变量========= |
|||
fileSelectDialogVisible: false, |
|||
tempSelectedFiles: [], // 子弹窗临时文件列表 |
|||
fileDialogUploading: false, // 子弹窗上传中 |
|||
dialogUploadProgress: 0, |
|||
dialogUploadingFileName: '', |
|||
dialogUploadSuccess: false, // 是否上传合并完成 |
|||
dialogResultPaths: [], // 子弹窗上传完成返回的filePaths(完整路径) |
|||
|
|||
// ✅新增:保存上一次合并成功快照 |
|||
lastMergeSnapshot: null, |
|||
|
|||
// 主弹窗:最终上传完成的文件路径数组(完整路径,提交接口使用) |
|||
uploadResultPaths: [], |
|||
|
|||
form: { |
|||
checkRepeatType: 1, |
|||
hitchFiled: null, |
|||
matchingMode: 2, |
|||
fields: null, |
|||
file: null, |
|||
protocol: null, |
|||
filePath: null, |
|||
fileRemote: null |
|||
}, |
|||
typeOptions: [ |
|||
{ value: 1, label: '跳过' }, |
|||
{ value: 2, label: '覆盖' }, |
|||
{ value: 3, label: '追加' } |
|||
], |
|||
matchedOptions: [ |
|||
{ value: 1, label: '全量匹配' }, |
|||
{ value: 2, label: '前缀模糊匹配' }, |
|||
{ value: 3, label: '后缀模糊匹配' } |
|||
], |
|||
batchTypeOptions: [ |
|||
{ value: 1, label: '服务端挂接' }, |
|||
{ value: 2, label: 'FTP挂接' }, |
|||
{ value: 3, label: 'SFTP挂接' } |
|||
], |
|||
fieldOptions: [], |
|||
filePathOptions: [], |
|||
rules: { |
|||
checkRepeatType: [{ required: true, message: '请选择', trigger: 'change' }], |
|||
hitchFiled: [{ required: true, message: '请选择', trigger: 'change' }], |
|||
fields: [{ required: true, message: '请选择上面挂接字段生成挂接详情', trigger: 'blur' }], |
|||
matchingMode: [{ required: true, message: '请选择', trigger: 'change' }], |
|||
protocol: [{ required: true, message: '请选择', trigger: 'change' }], |
|||
filePath: [{ required: true, message: '请选择', trigger: 'change' }], |
|||
fileRemote: [{ required: true, message: '请选择指定文件', trigger: 'change' }] |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters(['baseApi']), |
|||
collectLevel() { |
|||
if (this.selectedCategory.arrangeType === 1) return 3 |
|||
if (this.selectedCategory.arrangeType === 2) return 2 |
|||
if (this.selectedCategory.arrangeType === 3) return 1 |
|||
return null |
|||
} |
|||
}, |
|||
created() { }, |
|||
mounted() { }, |
|||
methods: { |
|||
opened() { |
|||
this.getFieldCommon() |
|||
}, |
|||
// 比对两个文件数组是否完全一致(文件名+大小) |
|||
isFileListEqual(filesA, filesB) { |
|||
if (filesA.length !== filesB.length) return false |
|||
const listA = filesA.map(f => ({ name: f.name, size: f.size })) |
|||
const listB = filesB.map(f => ({ name: f.name, size: f.size })) |
|||
for (let i = 0; i < listA.length; i++) { |
|||
if (listA[i].name !== listB[i].name || listA[i].size !== listB[i].size) { |
|||
return false |
|||
} |
|||
} |
|||
return true |
|||
}, |
|||
// 从完整路径截取文件名,兼容 / 和 \ 分隔符 |
|||
getFileNameFromPath(path) { |
|||
if (!path) return '' |
|||
const splitArr = path.split(/[\\/]/) |
|||
return splitArr[splitArr.length - 1] |
|||
}, |
|||
|
|||
// ==========子弹窗方法========== |
|||
openFileSelectDialog() { |
|||
this.resetFileDialog() |
|||
this.fileSelectDialogVisible = true |
|||
}, |
|||
|
|||
// 重置子弹窗全部状态 |
|||
resetFileDialog() { |
|||
this.tempSelectedFiles = [] |
|||
this.fileDialogUploading = false |
|||
this.dialogUploadProgress = 0 |
|||
this.dialogUploadingFileName = '' |
|||
this.dialogUploadSuccess = false |
|||
this.dialogResultPaths = [] |
|||
this.lastMergeSnapshot = null // ✅清空快照 |
|||
}, |
|||
|
|||
// 选择普通文件(多选) |
|||
onSelectFileOnly(e) { |
|||
const files = e.target.files |
|||
if (!files || files.length === 0) return |
|||
const arr = Array.from(files) |
|||
this.tempSelectedFiles = [...this.tempSelectedFiles, ...arr] |
|||
e.target.value = '' |
|||
// 新增文件,旧合并结果作废 |
|||
this.dialogUploadSuccess = false |
|||
this.dialogResultPaths = [] |
|||
}, |
|||
|
|||
// 选择文件夹 |
|||
onSelectFolder(e) { |
|||
const files = e.target.files |
|||
if (!files || files.length === 0) return |
|||
const arr = Array.from(files) |
|||
this.tempSelectedFiles = [...this.tempSelectedFiles, ...arr] |
|||
e.target.value = '' |
|||
// 新增文件,旧合并结果作废 |
|||
this.dialogUploadSuccess = false |
|||
this.dialogResultPaths = [] |
|||
}, |
|||
|
|||
// 删除子弹窗列表里的文件 |
|||
deleteTempFile(idx) { |
|||
// 先删除临时文件 |
|||
this.tempSelectedFiles.splice(idx, 1) |
|||
|
|||
// 如果有快照,并且当前文件列表和快照一致 → 恢复上次合并状态 |
|||
if (this.lastMergeSnapshot && this.isFileListEqual(this.tempSelectedFiles, this.lastMergeSnapshot.files)) { |
|||
this.dialogUploadSuccess = true |
|||
this.dialogResultPaths = [...this.lastMergeSnapshot.paths] |
|||
} else { |
|||
// 不相等,需要重传 |
|||
this.dialogUploadSuccess = false |
|||
this.dialogResultPaths = [] |
|||
} |
|||
}, |
|||
|
|||
// ==========分片上传接口函数(全部在子弹窗内执行)========== |
|||
async initUpload(fileName, totalChunks) { |
|||
const res = await axios.post('/api/uploadtemp/init', null, { |
|||
params: { fileName, totalChunks }, |
|||
headers: { 'Authorization': getToken() } |
|||
}) |
|||
return res.data.data |
|||
}, |
|||
|
|||
async uploadChunk(uploadId, chunkIndex, fileBlob) { |
|||
const formData = new FormData() |
|||
formData.append('file', fileBlob) |
|||
formData.append('uploadId', uploadId) |
|||
formData.append('chunkIndex', chunkIndex) |
|||
const res = await axios.post('/api/uploadtemp/chunk', formData, { |
|||
headers: { |
|||
'Content-Type': 'multipart/form-data', |
|||
'Authorization': getToken() |
|||
} |
|||
}) |
|||
return res.data |
|||
}, |
|||
|
|||
async batchMerge(uploadIdToFileNameMap) { |
|||
const res = await axios.post('/api/uploadtemp/batchMerge', uploadIdToFileNameMap, { |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
'Authorization': getToken() |
|||
} |
|||
}) |
|||
return res.data.data |
|||
}, |
|||
|
|||
// 子弹窗:开始上传全部文件 |
|||
// 子弹窗:开始上传全部文件 |
|||
async startUploadFiles() { |
|||
if (this.tempSelectedFiles.length === 0) { |
|||
this.$message.warning('请选择文件') |
|||
return |
|||
} |
|||
this.fileDialogUploading = true |
|||
this.dialogUploadProgress = 0 |
|||
this.dialogUploadSuccess = false |
|||
this.dialogResultPaths = [] |
|||
const uploadIdMap = {} |
|||
|
|||
try { |
|||
for (let i = 0; i < this.tempSelectedFiles.length; i++) { |
|||
const file = this.tempSelectedFiles[i] |
|||
this.dialogUploadingFileName = file.name |
|||
const fileSize = file.size |
|||
const totalChunks = Math.ceil(fileSize / CHUNK_SIZE) |
|||
const uploadId = await this.initUpload(file.name, totalChunks) |
|||
uploadIdMap[uploadId] = file.name |
|||
|
|||
for (let chunkIdx = 0; chunkIdx < totalChunks; chunkIdx++) { |
|||
const start = chunkIdx * CHUNK_SIZE |
|||
const end = Math.min(start + CHUNK_SIZE, fileSize) |
|||
const blob = file.slice(start, end) |
|||
await this.uploadChunk(uploadId, chunkIdx, blob) |
|||
|
|||
const totalAllChunks = this.tempSelectedFiles.reduce((sum, f) => sum + Math.ceil(f.size / CHUNK_SIZE), 0) |
|||
const doneAllChunks = this.tempSelectedFiles.slice(0, i).reduce((sum, f) => sum + Math.ceil(f.size / CHUNK_SIZE), 0) + chunkIdx + 1 |
|||
this.dialogUploadProgress = Math.round((doneAllChunks / totalAllChunks) * 100) |
|||
} |
|||
} |
|||
// 批量合并 |
|||
const pathList = await this.batchMerge(uploadIdMap) |
|||
this.dialogResultPaths = pathList |
|||
this.dialogUploadSuccess = true |
|||
// ✅保存合并快照 |
|||
this.lastMergeSnapshot = { |
|||
files: [...this.tempSelectedFiles], |
|||
paths: [...pathList] |
|||
} |
|||
this.$message.success('文件上传合并完成') |
|||
} catch (err) { |
|||
console.error('上传失败', err) |
|||
this.$message.error('文件上传失败,请重试') |
|||
this.dialogUploadSuccess = false |
|||
this.dialogResultPaths = [] |
|||
this.lastMergeSnapshot = null |
|||
} finally { |
|||
this.fileDialogUploading = false |
|||
this.dialogUploadingFileName = '' |
|||
} |
|||
}, |
|||
|
|||
// 子弹窗确认,把返回的filePaths给到主弹窗 |
|||
confirmSelectFiles() { |
|||
if (!this.dialogUploadSuccess) { |
|||
this.$message.warning('请先完成文件上传') |
|||
return |
|||
} |
|||
this.uploadResultPaths = [...this.dialogResultPaths] |
|||
this.fileSelectDialogVisible = false |
|||
}, |
|||
|
|||
// =========主弹窗提交========== |
|||
async handleComfired() { |
|||
if (!this.connectionType) { |
|||
if (this.uploadResultPaths.length === 0) { |
|||
this.$message({ message: '请选择并上传文件或者文件夹!', offset: 8 }) |
|||
return false |
|||
} |
|||
} |
|||
if (this.hitchRemarkArray.length === 1 && this.hitchRemarkArray[0] === '*') { |
|||
this.form.fields = '' |
|||
} else { |
|||
this.form.fields = this.hitchRemarkArray.join('') |
|||
} |
|||
this.btnLoading = true |
|||
this.$refs['form'].validate(async(valid) => { |
|||
if (!valid) { |
|||
this.btnLoading = false |
|||
return false |
|||
} |
|||
let newhitchRemark = this.hitchRemarkArray.map(item => item.replace(/^\$|\$$|\*$/g, '')) |
|||
newhitchRemark = newhitchRemark.filter(item => item !== '') |
|||
|
|||
if (!this.connectionType) { |
|||
try { |
|||
const params = { |
|||
categoryId: this.selectedCategory.id, |
|||
mountType: 1, // 挂接方式 1.本地挂接 2.远程挂接 3.上传目录 (目前只有1) |
|||
checkRepeatType: this.form.checkRepeatType, |
|||
matchingMode: this.form.matchingMode, |
|||
fields: newhitchRemark, |
|||
fondsId: this.selectedCategory.fondsId, |
|||
filePaths: this.uploadResultPaths // 提交接口仍然传完整路径数组 |
|||
} |
|||
console.log('批量挂接参数', params) |
|||
// 调用批量挂接接口 |
|||
batchMountUpload(this.baseApi + '/api/collect/batchMountFolder', params).then(res => { |
|||
if (res.data.code === 200) { |
|||
this.$message({ message: '操作成功', type: 'success', offset: 8 }) |
|||
} else { |
|||
this.$message({ message: '挂接失败', type: 'error', offset: 8 }) |
|||
} |
|||
this.handleCancel() |
|||
this.crud.refresh() |
|||
this.btnLoading = false |
|||
}).catch(err => { |
|||
console.error('请求异常', err) |
|||
this.$message.error('请求异常') |
|||
this.btnLoading = false |
|||
}) |
|||
} catch (err) { |
|||
console.error('提交异常', err) |
|||
this.$message.error('提交失败,请重试') |
|||
this.btnLoading = false |
|||
} |
|||
} else { |
|||
// 远程挂接逻辑不变 |
|||
const params = { |
|||
account: this.form.filePath.account, |
|||
caddress: this.form.filePath.caddress, |
|||
cpassword: this.form.filePath.cpassword, |
|||
cport: this.form.filePath.cport, |
|||
categoryId: this.selectedCategory.id, |
|||
fondsId: this.selectedCategory.fondsId, |
|||
checkRepeatType: this.form.checkRepeatType, |
|||
matchingMode: this.form.matchingMode, |
|||
fields: newhitchRemark, |
|||
protocol: this.form.protocol, |
|||
zipFilePath: this.form.filePath.rootDirectory + '/' + this.form.fileRemote |
|||
} |
|||
console.log(params) |
|||
remoteConnectiontUpload(this.baseApi + '/api/collect/remoteMount', params).then(res => { |
|||
if (res.data.code === 200) { |
|||
this.$message({ message: '操作成功', type: 'success', offset: 8 }) |
|||
} else { |
|||
this.$message({ message: '操作失败', type: 'error', offset: 8 }) |
|||
} |
|||
this.handleCancel() |
|||
this.crud.refresh() |
|||
this.btnLoading = false |
|||
}).catch(err => { |
|||
console.error('请求异常', err) |
|||
this.$message.error('请求异常') |
|||
this.btnLoading = false |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
getFieldCommon() { |
|||
const params = { |
|||
categoryId: this.selectedCategory.id, |
|||
categoryLevel: this.collectLevel |
|||
} |
|||
FetchInitCategoryInputFieldByPid(params).then(data => { |
|||
this.fieldOptions = data.map(item => { |
|||
return { |
|||
id: item.id, |
|||
label: item.fieldCnName, |
|||
value: item.fieldName |
|||
} |
|||
}) |
|||
const archiveNoOption = this.fieldOptions.find(item => item.value === 'archive_no') |
|||
if (archiveNoOption) { |
|||
this.form.hitchFiled = archiveNoOption.value |
|||
this.selectFiled(archiveNoOption.value) |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
selectFiled(val) { |
|||
if (!val) return |
|||
if (this.hitchRemarkArray.includes('$' + val + '$')) { |
|||
this.$message({ message: '请注意当前选择的挂接字段,在挂接详情内已存在!', offset: 8 }) |
|||
return |
|||
} |
|||
const lastItemIndex = this.hitchRemarkArray.length - 1 |
|||
const index = this.hitchRemarkArray.indexOf('*') |
|||
if (index !== -1) { |
|||
if (index === lastItemIndex) { |
|||
if (this.form.matchingMode === 2) { |
|||
this.hitchRemarkArray.unshift('$' + val + '$') |
|||
} else if (this.form.matchingMode === 3) { |
|||
this.hitchRemarkArray.push('$' + val + '$') |
|||
} else { |
|||
this.hitchRemarkArray.splice(index > 0 ? index : 0, 0, '$' + val + '$') |
|||
} |
|||
} else { |
|||
this.hitchRemarkArray.push('$' + val + '$') |
|||
} |
|||
} else { |
|||
this.hitchRemarkArray.push('$' + val + '$') |
|||
} |
|||
}, |
|||
|
|||
changeMatchedType(val) { |
|||
const symbol = '*' |
|||
const index = this.hitchRemarkArray.indexOf(symbol) |
|||
if (index !== -1) this.hitchRemarkArray.splice(index, 1) |
|||
if (val === 2) this.hitchRemarkArray.push(symbol) |
|||
if (val === 3) this.hitchRemarkArray.unshift(symbol) |
|||
}, |
|||
|
|||
changeProtocol(val) { |
|||
if (val) { |
|||
this.form.filePath = null |
|||
this.filePathOptions = [] |
|||
this.fileRemoteOptions = [] |
|||
FetchFindDetailsByProtocol({ protocol: val }).then(res => { |
|||
this.filePathOptions = res |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
changeFilePath(val) { |
|||
this.fileRemoteLoading = true |
|||
if (val) { |
|||
this.fileRemoteOptions = [] |
|||
FetchFileByRemoteConnection({ id: val.id }).then(res => { |
|||
if (res) { |
|||
this.fileRemoteOptions = res.map(item => ({ value: item, name: item })) |
|||
} else { |
|||
this.fileRemoteOptions = [] |
|||
} |
|||
this.fileRemoteLoading = false |
|||
}) |
|||
} |
|||
}, |
|||
|
|||
removeTag(tag) { |
|||
const index = this.hitchRemarkArray.indexOf(tag) |
|||
if (index !== -1) this.hitchRemarkArray.splice(index, 1) |
|||
}, |
|||
|
|||
handleCancel() { |
|||
this.$refs.form.resetFields() |
|||
this.hitchRemarkArray = [] |
|||
this.uploadResultPaths = [] |
|||
this.fileSelectDialogVisible = false |
|||
this.localHitchVisible = false |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss' scoped> |
|||
::v-deep .el-dialog { |
|||
width: 585px !important; |
|||
.el-dialog__body { |
|||
padding: 30px 0 !important; |
|||
} |
|||
} |
|||
.dialog-footer { |
|||
margin-top: 0; |
|||
} |
|||
.file-list { |
|||
width: 400px; |
|||
height: 110px; |
|||
font-size: 12px; |
|||
color: #A6ADB6; |
|||
margin-top: 8px; |
|||
overflow-y: auto; |
|||
} |
|||
.file-item { |
|||
line-height: 26px; |
|||
// margin: 2px 0; |
|||
} |
|||
.tag-wrapper { |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
flex-wrap: wrap; |
|||
width: 400px; |
|||
min-height: 32px; |
|||
padding: 0 10px; |
|||
border-radius: 3px; |
|||
border: 1px solid #e6e8ed; |
|||
.el-tag { |
|||
margin-right: 6px; |
|||
} |
|||
} |
|||
.file_remote-style { |
|||
::v-deep .el-form-item__content { |
|||
width: 400px !important; |
|||
.el-radio-group { |
|||
display: block; |
|||
max-height: 60px; |
|||
overflow: hidden; |
|||
overflow-y: scroll; |
|||
.el-radio { |
|||
display: block; |
|||
padding: 3px 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.upload-wm { |
|||
height: 100px; |
|||
} |
|||
|
|||
/* 子弹窗样式 */ |
|||
.select-btn-group { |
|||
margin-bottom: 16px; |
|||
display: flex; |
|||
gap: 12px; |
|||
} |
|||
.dialog-upload-tip { |
|||
margin-bottom: 12px; |
|||
} |
|||
.select-file-list { |
|||
max-height: 300px; |
|||
overflow-y: auto; |
|||
border: 1px solid #e6e6e6; |
|||
padding: 8px; |
|||
border-radius: 4px; |
|||
} |
|||
.temp-file-item { |
|||
padding: 4px 0; |
|||
font-size: 14px; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
.file-name { |
|||
flex: 1; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
white-space: nowrap; |
|||
} |
|||
// .temp-del-icon { |
|||
// color: #f56c6c; |
|||
// cursor: pointer; |
|||
// margin-left: 10px; |
|||
// } |
|||
.empty-tip { |
|||
text-align: center; |
|||
color: #999; |
|||
padding: 20px 0; |
|||
} |
|||
.icon-shanchu{ |
|||
// position: absolute; |
|||
// right: -7px; |
|||
// top: 0; |
|||
// font-size: 20px; |
|||
color: #1F55EB; |
|||
cursor: pointer; |
|||
&:hover { |
|||
color: #ff4444; |
|||
transform: scale(1.1); |
|||
transition: all 0.2s ease; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,504 @@ |
|||
<template> |
|||
<!-- 本地挂接 / 远程挂接 --> |
|||
<el-dialog :title="connectionTitle" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body :visible.sync="localHitchVisible" :before-close="handleCancel" @opened="opened"> |
|||
<el-form ref="form" :model="form" :rules="rules" label-width="120px"> |
|||
<el-form-item label="重复验证方式" prop="checkRepeatType"> |
|||
<el-select v-model="form.checkRepeatType" placeholder="请选择" style="width: 400px;"> |
|||
<el-option |
|||
v-for="item in typeOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="挂接字段" prop="hitchFiled"> |
|||
<el-select v-model="form.hitchFiled" placeholder="请选择" style="width: 400px;" @change="selectFiled"> |
|||
<el-option |
|||
v-for="item in fieldOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item label="挂接详情" prop="fields"> |
|||
<div class="tag-wrapper"> |
|||
<el-tag v-for="item in hitchRemarkArray" :key="item" :closable="item !== '*'" @close="removeTag(item)"> |
|||
{{ item }} |
|||
</el-tag> |
|||
</div> |
|||
<el-input v-model="form.fields" style="display: none;" type="hidden" /> |
|||
</el-form-item> |
|||
<el-form-item label="匹配模式" prop="matchingMode"> |
|||
<el-select v-model="form.matchingMode" placeholder="请选择" style="width: 400px;" @change="changeMatchedType"> |
|||
<el-option |
|||
v-for="item in matchedOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item v-if="!connectionType" label="上传附件" prop="file" class="upload-wm"> |
|||
<!-- <div class="upload-bgColor"> |
|||
<input class="upload-input" type="file" accept=".zip" style="width: 110px; height: 32px;" @change="handleFile"> |
|||
<el-button size="small" type="primary"><i class="iconfont icon-shangchuan" />点击上传</el-button> |
|||
</div> --> |
|||
<div v-for="item in fileList" :key="item.name" class="file-list"> |
|||
<i class="iconfont icon-xiaowenjian" /> |
|||
<!-- {{ item.file_name }} --> |
|||
{{ item }} |
|||
</div> |
|||
<el-input v-show="false" v-model="form.file" /> |
|||
<!-- 大文件上传 --> |
|||
<PreUpload ref="uploadBigRef" is-batch-mount="true" :selected-category="selectedCategory" @onUploadSuccess="handleSuccessResource" @onUploadError="handleErrorResource" /> |
|||
<!-- <div class="file-list"> |
|||
<i class="iconfont icon-xiaowenjian" /> |
|||
{{ form.file && form.file.name }} |
|||
</div> --> |
|||
</el-form-item> |
|||
<el-form-item v-if="connectionType" label="挂接类型" prop="protocol"> |
|||
<el-select v-model="form.protocol" placeholder="请选择" style="width: 400px;" @change="changeProtocol"> |
|||
<el-option |
|||
v-for="item in batchTypeOptions" |
|||
:key="item.value" |
|||
:label="item.label" |
|||
:value="item.value" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item v-if="connectionType" label="文件路径" prop="filePath"> |
|||
<el-select v-model="form.filePath" placeholder="请选择" value-key="id" style="width: 400px;" @change="changeFilePath"> |
|||
<el-option |
|||
v-for="(item,index) in filePathOptions" |
|||
:key="index" |
|||
:label="item.name" |
|||
:value="item" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<el-form-item |
|||
v-if="connectionType" |
|||
label="" |
|||
prop="fileRemote" |
|||
class="file_remote-style" |
|||
:rules="form.filePath !== null ? rules.fileRemote : []" |
|||
:required="form.filePath !== null" |
|||
> |
|||
<el-button v-if="form.filePath && fileRemoteLoading" type="primary" :loading="fileRemoteLoading" style="border: none; display: block; padding: 0 0 4px 0;">文件列表加载中</el-button> |
|||
<el-radio-group v-model="form.fileRemote"> |
|||
<el-radio |
|||
v-for="item in fileRemoteOptions" |
|||
:key="item.value" |
|||
:value="item.value" |
|||
:label="item.name" |
|||
>{{ item.name }}</el-radio> |
|||
</el-radio-group> |
|||
</el-form-item> |
|||
</el-form> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button type="text" @click="handleCancel">取消</el-button> |
|||
<el-button :loading="btnLoading" type="primary" @click.native="handleComfired">确定</el-button> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
import { crud } from '@crud/crud' |
|||
import { FetchInitCategoryInputFieldByPid } from '@/api/system/category/category' |
|||
import { FetchFindDetailsByProtocol, FetchFileByRemoteConnection } from '@/api/system/remoteConnection' |
|||
import { batchMountUpload, remoteConnectiontUpload } from '@/utils/upload' |
|||
import { mapGetters } from 'vuex' |
|||
import PreUpload from '../../../components/category/preUpload.vue' |
|||
|
|||
export default { |
|||
name: 'LocalHitch', |
|||
components: { PreUpload }, |
|||
mixins: [crud()], |
|||
props: { |
|||
selectedCategory: { |
|||
type: Object, |
|||
default: function() { |
|||
return {} |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
btnLoading: false, |
|||
connectionTitle: '本地挂接', |
|||
localHitchVisible: false, |
|||
hitchRemarkArray: [], |
|||
connectionType: 0, |
|||
form: { |
|||
checkRepeatType: 1, |
|||
hitchFiled: null, |
|||
matchingMode: 2, |
|||
fields: null, |
|||
file: null, |
|||
protocol: null, |
|||
filePath: null, |
|||
fileRemote: null |
|||
}, |
|||
upfilePath: null, |
|||
fileList: [], |
|||
typeOptions: [ |
|||
{ |
|||
value: 1, |
|||
label: '跳过' |
|||
}, |
|||
{ |
|||
value: 2, |
|||
label: '覆盖' |
|||
}, |
|||
{ |
|||
value: 3, |
|||
label: '追加' |
|||
} |
|||
], |
|||
matchedOptions: [ |
|||
{ |
|||
value: 1, |
|||
label: '全量匹配' |
|||
}, |
|||
{ |
|||
value: 2, |
|||
label: '前缀模糊匹配' |
|||
}, |
|||
{ |
|||
value: 3, |
|||
label: '后缀模糊匹配' |
|||
} |
|||
], |
|||
batchTypeOptions: [ |
|||
{ |
|||
value: 1, |
|||
label: '服务端挂接' |
|||
}, |
|||
{ |
|||
value: 2, |
|||
label: 'FTP挂接' |
|||
}, |
|||
{ |
|||
value: 3, |
|||
label: 'SFTP挂接' |
|||
} |
|||
], |
|||
fieldOptions: [], |
|||
filePathOptions: [], |
|||
fileRemoteLoading: false, |
|||
fileRemoteOptions: [], |
|||
rules: { |
|||
checkRepeatType: [ |
|||
{ required: true, message: '请选择', trigger: 'change' } |
|||
], |
|||
hitchFiled: [ |
|||
{ required: true, message: '请选择', trigger: 'change' } |
|||
], |
|||
fields: [ |
|||
{ required: true, message: '请选择上面挂接字段生成挂接详情', trigger: 'blur' } |
|||
], |
|||
matchingMode: [ |
|||
{ required: true, message: '请选择', trigger: 'change' } |
|||
], |
|||
protocol: [ |
|||
{ required: true, message: '请选择', trigger: 'change' } |
|||
], |
|||
filePath: [ |
|||
{ required: true, message: '请选择', trigger: 'change' } |
|||
], |
|||
fileRemote: [ |
|||
{ required: true, message: '请选择指定文件', trigger: 'change' } |
|||
] |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
...mapGetters([ |
|||
'baseApi' |
|||
]), |
|||
collectLevel() { |
|||
if (this.selectedCategory.arrangeType === 1) { |
|||
return 3 |
|||
} else if (this.selectedCategory.arrangeType === 2) { |
|||
return 2 |
|||
} else if (this.selectedCategory.arrangeType === 3) { |
|||
return 1 |
|||
} |
|||
return null |
|||
} |
|||
}, |
|||
created() { |
|||
}, |
|||
mounted() { |
|||
}, |
|||
methods: { |
|||
opened() { |
|||
this.getFieldCommon() |
|||
}, |
|||
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 = fileName |
|||
this.upfilePath = filePath |
|||
}, |
|||
handleErrorResource(res) { |
|||
console.log('handleErrorResource', res) |
|||
}, |
|||
handleFile(event) { |
|||
const files = event.target.files |
|||
for (let i = 0; i < files.length; i++) { |
|||
this.fileList = [] |
|||
this.fileList.push(files[i]) |
|||
} |
|||
}, |
|||
handleComfired() { |
|||
if (!this.connectionType) { |
|||
if (this.fileList.length === 0) { |
|||
this.$message({ message: '请先选择相关文件!', offset: 8 }) |
|||
return false |
|||
} |
|||
} |
|||
|
|||
if (this.hitchRemarkArray.length === 1 && this.hitchRemarkArray[0] === '*') { |
|||
this.form.fields = '' |
|||
} else { |
|||
this.form.fields = this.hitchRemarkArray.join('') |
|||
} |
|||
this.btnLoading = true |
|||
this.$refs['form'].validate((valid) => { |
|||
console.log('this.selectedCategory', this.selectedCategory) |
|||
if (valid) { |
|||
let newhitchRemark = this.hitchRemarkArray.map(item => item.replace(/^\$|\$$|\*$/g, '')) |
|||
newhitchRemark = newhitchRemark.filter(item => item !== '') |
|||
|
|||
if (!this.connectionType) { |
|||
const params = { |
|||
'categoryId': this.selectedCategory.id, |
|||
'mountType': 1, // 挂接方式 1.本地挂接 2.远程挂接 3.上传目录 (目前只有1) |
|||
'checkRepeatType': this.form.checkRepeatType, |
|||
'matchingMode': this.form.matchingMode, |
|||
'fields': newhitchRemark, |
|||
'fondsId': this.selectedCategory.fondsId, |
|||
'upfilePath': this.upfilePath |
|||
} |
|||
console.log(params) |
|||
batchMountUpload(this.baseApi + '/api/collect/batchMount', |
|||
params |
|||
).then(res => { |
|||
if (res.data.code === 200) { |
|||
this.$message({ message: '操作成功', type: 'success', offset: 8 }) |
|||
} else { |
|||
this.$message({ message: '上传附件失败', type: 'error', offset: 8 }) |
|||
} |
|||
this.handleCancel() |
|||
this.crud.refresh() |
|||
this.btnLoading = false |
|||
}) |
|||
} else { |
|||
const params = { |
|||
'account': this.form.filePath.account, |
|||
'caddress': this.form.filePath.caddress, |
|||
'cpassword': this.form.filePath.cpassword, |
|||
'cport': this.form.filePath.cport, |
|||
'categoryId': this.selectedCategory.id, |
|||
'fondsId': this.selectedCategory.fondsId, |
|||
'checkRepeatType': this.form.checkRepeatType, |
|||
'matchingMode': this.form.matchingMode, |
|||
'fields': newhitchRemark, |
|||
'protocol': this.form.protocol, |
|||
'zipFilePath': this.form.filePath.rootDirectory + '/' + this.form.fileRemote // rootDirectory+勾选的文件名称 组成一个路径 |
|||
} |
|||
console.log(params) |
|||
remoteConnectiontUpload(this.baseApi + '/api/collect/remoteMount', |
|||
params |
|||
).then(res => { |
|||
if (res.data.code === 200) { |
|||
this.$message({ message: '操作成功', type: 'success', offset: 8 }) |
|||
} else { |
|||
this.$message({ message: '操作失败', type: 'error', offset: 8 }) |
|||
} |
|||
this.handleCancel() |
|||
this.crud.refresh() |
|||
this.btnLoading = false |
|||
}) |
|||
} |
|||
} else { |
|||
this.btnLoading = false |
|||
return false |
|||
} |
|||
}) |
|||
}, |
|||
getFieldCommon() { |
|||
const params = { |
|||
'categoryId': this.selectedCategory.id, |
|||
'categoryLevel': this.collectLevel |
|||
} |
|||
FetchInitCategoryInputFieldByPid(params).then(data => { |
|||
this.fieldOptions = data.map((item, index) => { |
|||
const json = {} |
|||
json.id = item.id |
|||
json.label = item.fieldCnName |
|||
json.value = item.fieldName |
|||
return json |
|||
}) |
|||
// 查找档号(archive_no)选项并设置为默认值 |
|||
const archiveNoOption = this.fieldOptions.find(item => item.value === 'archive_no') |
|||
if (archiveNoOption) { |
|||
this.form.hitchFiled = archiveNoOption.value |
|||
this.selectFiled(archiveNoOption.value) |
|||
} |
|||
}) |
|||
}, |
|||
selectFiled(val) { |
|||
if (val) { |
|||
if (this.hitchRemarkArray && this.hitchRemarkArray.includes('$' + val + '$')) { |
|||
this.$message({ message: '请注意当前选择的挂接字段,在挂接详情内已存在!', offset: 8 }) |
|||
} else { |
|||
const lastItemIndex = this.hitchRemarkArray.length - 1 |
|||
const index = this.hitchRemarkArray.indexOf('*') |
|||
if (index !== -1) { |
|||
if (index === lastItemIndex) { |
|||
if (this.form.matchingMode === 2) { |
|||
this.hitchRemarkArray.unshift('$' + val + '$') |
|||
} else if (this.form.matchingMode === 3) { |
|||
this.hitchRemarkArray.push('$' + val + '$') |
|||
} else { |
|||
this.hitchRemarkArray.splice(index > 0 ? index : 0, 0, '$' + val + '$') |
|||
} |
|||
} else { |
|||
this.hitchRemarkArray.push('$' + val + '$') |
|||
} |
|||
} else { |
|||
this.hitchRemarkArray.push('$' + val + '$') |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
changeMatchedType(val) { |
|||
const symbol = '*' |
|||
const index = this.hitchRemarkArray.indexOf(symbol) |
|||
if (index !== -1) { |
|||
this.hitchRemarkArray.splice(index, 1) |
|||
} |
|||
if (val === 2) { |
|||
this.hitchRemarkArray.push(symbol) |
|||
} else if (val === 3) { |
|||
this.hitchRemarkArray.unshift(symbol) |
|||
} |
|||
}, |
|||
changeProtocol(val) { |
|||
if (val) { |
|||
this.form.filePath = null |
|||
this.filePathOptions = [] |
|||
this.fileRemoteOptions = [] |
|||
FetchFindDetailsByProtocol({ 'protocol': val }).then(res => { |
|||
this.filePathOptions = res |
|||
}) |
|||
} |
|||
}, |
|||
changeFilePath(val) { |
|||
this.fileRemoteLoading = true |
|||
if (val) { |
|||
this.fileRemoteOptions = [] |
|||
FetchFileByRemoteConnection({ 'id': val.id }).then(res => { |
|||
if (res) { |
|||
this.fileRemoteOptions = res.map(item => { |
|||
const json = {} |
|||
json.value = item |
|||
json.name = item |
|||
return json |
|||
}) |
|||
} else { |
|||
this.fileRemoteOptions = [] |
|||
} |
|||
|
|||
this.fileRemoteLoading = false |
|||
}) |
|||
} |
|||
}, |
|||
removeTag(tag) { |
|||
const index = this.hitchRemarkArray.indexOf(tag) |
|||
if (index !== -1) { |
|||
this.hitchRemarkArray.splice(index, 1) |
|||
} |
|||
}, |
|||
handleCancel() { |
|||
this.$refs.form.resetFields() |
|||
this.hitchRemarkArray = [] |
|||
this.fileList = [] |
|||
this.filePathOptions = [] |
|||
this.fileRemoteOptions = [] |
|||
this.localHitchVisible = false |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss' scoped> |
|||
::v-deep .el-dialog{ |
|||
width: 585px !important; |
|||
.el-dialog__body{ |
|||
padding: 30px 0 !important; |
|||
} |
|||
} |
|||
.dialog-footer{ |
|||
margin-top: 0; |
|||
} |
|||
|
|||
.file-list{ |
|||
width: 400px; |
|||
font-size: 12px; |
|||
color: #A6ADB6; |
|||
} |
|||
.tag-wrapper{ |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
flex-wrap: wrap; |
|||
width: 400px; |
|||
min-height: 32px; |
|||
padding: 0 10px; |
|||
border-radius: 3px; |
|||
border: 1px solid #e6e8ed; |
|||
.el-tag{ |
|||
margin-right: 6px; |
|||
} |
|||
} |
|||
|
|||
.file_remote-style{ |
|||
::v-deep .el-form-item__content{ |
|||
width:400px !important; |
|||
.el-radio-group{ |
|||
display: block; |
|||
max-height: 60px; |
|||
overflow: hidden; |
|||
overflow-y: scroll; |
|||
.el-radio{ |
|||
display: block; |
|||
padding: 3px 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
.upload-wm{ |
|||
height: 100px; |
|||
} |
|||
::v-deep .uploader-big{ |
|||
margin-left: 0 !important; |
|||
} |
|||
::v-deep .upload_process_box{ |
|||
right: 0 !important; |
|||
left: 120px !important; |
|||
top: 0 !important; |
|||
width: 300px !important; |
|||
} |
|||
|
|||
</style> |
|||
921
src/views/collectReorganizi/batchConnection/module/form.vue
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,850 @@ |
|||
<template> |
|||
<div class="upload-minio"> |
|||
<el-button |
|||
v-if="isPreFile !== 'true' && archivesType === 'add'" |
|||
type="primary" |
|||
:disabled="!isCaValid || isCheckingCa" |
|||
icon="el-icon-upload" |
|||
@click="uploadMinioVisible=true" |
|||
> |
|||
选择文件 |
|||
</el-button> |
|||
<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" |
|||
> |
|||
<div class="uploader-drop" style="margin-bottom: 20px;"> |
|||
<div class="uploader-btn"> |
|||
<!-- 批量挂载模式:显示两个按钮:选择多文件 / 选择文件夹 --> |
|||
<template v-if="isBatchMount === 'true'"> |
|||
<el-button size="small" @click="triggerFileInput">选择多文件</el-button> |
|||
<el-button size="small" @click="triggerFolderInput">选择文件夹</el-button> |
|||
<p style="font-size: 12px; margin-top: 10px;">支持多选文件 或 选择文件夹</p> |
|||
</template> |
|||
<template v-else> |
|||
<p>点击上传(可多文件上传)</p> |
|||
<div style="margin: 20px 0 0 0;"> |
|||
<i class="iconfont icon-tianjiawenjian upload-icon" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<!-- 多选文件input --> |
|||
<input |
|||
ref="fileInput" |
|||
type="file" |
|||
multiple |
|||
:disabled="!isCaValid || isCheckingCa" |
|||
:accept="fileAcceptType" |
|||
class="file-input" |
|||
style="display: none;" |
|||
@change="handleFileSelect" |
|||
> |
|||
<!-- 选择文件夹input webkitdirectory --> |
|||
<input |
|||
ref="folderInput" |
|||
type="file" |
|||
webkitdirectory |
|||
directory |
|||
multiple |
|||
:disabled="!isCaValid || isCheckingCa" |
|||
class="file-input" |
|||
style="display: none;" |
|||
@change="handleFolderSelect" |
|||
> |
|||
</div> |
|||
</div> |
|||
<div v-if="fileList.length !== 0" style="max-height: 400px; overflow: hidden; overflow-y: scroll;"> |
|||
<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> |
|||
<!-- 文件夹来源展示相对路径 --> |
|||
<span v-if="fileItem.relativePath" style="color:#999;font-size:12px;margin-left:6px"> |
|||
{{ fileItem.relativePath }} |
|||
</span> |
|||
<i class="iconfont icon-shanchu" @click="handleDeleteFile(index)" /> |
|||
</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> |
|||
</div> |
|||
<div slot="footer" class="dialog-footer" style="margin-top: 0;"> |
|||
<el-button type="text" @click="handleCloseDialog">取消</el-button> |
|||
<el-button :disabled="fileList.some(item => item.uploading || item.merging) || fileList.length === 0" :loading="btnLoading" type="primary" @click="handleUploadConfirm">保存</el-button> |
|||
</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: '' |
|||
}, |
|||
isPreFile: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
archivesType: { |
|||
type: String, |
|||
default: 'add' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
btnLoading: false, |
|||
uploadMinioVisible: false, // 弹框显示状态 |
|||
fileList: [], // 多文件上传列表 |
|||
CHUNK_SIZE: 5 * 1024 * 1024, // 分片大小 (5MB) |
|||
totalMergeStartTime: null, // 整体批量合并开始时间 |
|||
totalMergeEndTime: null, // 整体批量合并结束时间 |
|||
allChunksUploaded: false, // 所有文件分片是否上传完成 |
|||
isCaValid: false, // CA证书是否有效 |
|||
isCheckingCa: false, // 是否正在校验CA状态 |
|||
baseApi: process.env.NODE_ENV === 'production' ? window.g.ApiUrl : process.env.VUE_APP_BASE_API |
|||
} |
|||
}, |
|||
// 计算属性:动态生成文件类型限制 |
|||
computed: { |
|||
fileAcceptType() { |
|||
// 批量挂载不再限制只能zip |
|||
return this.isBatchMount === 'true' ? '' : '' |
|||
}, |
|||
// 新增计算属性:动态获取分片接口路径 |
|||
chunkApiPath() { |
|||
return this.isBatchMount === 'true' ? '/api/collect/chunkZip' : '/api/minioUpload/chunk' |
|||
} |
|||
}, |
|||
mounted() { |
|||
this.getCheckCaValidity() // 初始化时自动校验CA证书 |
|||
}, |
|||
methods: { |
|||
convertBytesToKB(bytes) { |
|||
// 1KB = 1024字节 |
|||
const kb = bytes / 1024 |
|||
// 四舍五入后如果小于1则返回1,否则返回四舍五入的值 |
|||
const result = Math.round(kb) |
|||
return result < 1 ? 1 : result |
|||
}, |
|||
triggerFileInput() { |
|||
this.$refs.fileInput.click() |
|||
}, |
|||
triggerFolderInput() { |
|||
this.$refs.folderInput.click() |
|||
}, |
|||
handleDeleteFile(index) { |
|||
// 校验是否正在上传/合并,避免删除操作冲突 |
|||
const fileItem = this.fileList[index] |
|||
if (fileItem.uploading || fileItem.merging) { |
|||
this.showMessage('当前文件正在上传/合并中,无法删除', 'warning') |
|||
return |
|||
} |
|||
// 删除对应索引的文件项 |
|||
this.fileList.splice(index, 1) |
|||
this.showMessage('文件已移除', 'success') |
|||
}, |
|||
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 |
|||
this.btnLoading = 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 |
|||
// 3. 非批量挂载时,禁止上传压缩包 |
|||
if (this.isBatchMount !== 'true') { |
|||
const compressedExtensions = ['.zip', '.rar', '.7z', '.tar', '.gz', '.bz2'] |
|||
const hasCompressedFile = selectedFiles.some(file => |
|||
compressedExtensions.some(ext => file.name.toLowerCase().endsWith(ext)) |
|||
) |
|||
if (hasCompressedFile) { |
|||
this.showMessage('不可上传压缩包文件', 'error') |
|||
e.target.value = '' |
|||
return |
|||
} |
|||
} |
|||
// 初始化文件上传状态(仅初始化,不上传) |
|||
const newFileList = selectedFiles.map(file => ({ |
|||
file, |
|||
relativePath: '', // 普通文件无相对路径 |
|||
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] |
|||
this.uploadMinioVisible = true |
|||
// 清空input的文件选择(避免重复选择同一文件不触发change) |
|||
e.target.value = '' |
|||
}, |
|||
/** |
|||
* 选择文件夹处理函数:读取文件夹内全部文件,带webkitRelativePath |
|||
* @param {Event} e |
|||
*/ |
|||
async handleFolderSelect(e) { |
|||
if (this.isCheckingCa) { |
|||
this.showMessage('正在校验CA证书,请稍候...', 'warning') |
|||
e.target.value = '' |
|||
return |
|||
} |
|||
if (!this.isCaValid) { |
|||
this.showMessage('CA证书不在有效期内,无法上传文件', 'error') |
|||
e.target.value = '' |
|||
return |
|||
} |
|||
const selectedFiles = Array.from(e.target.files) |
|||
if (selectedFiles.length === 0) return |
|||
|
|||
const newFileList = selectedFiles.map(file => ({ |
|||
file, |
|||
relativePath: file.webkitRelativePath, // 保存文件夹内相对路径,例:folder/a/b.txt |
|||
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] |
|||
this.uploadMinioVisible = true |
|||
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) { |
|||
// 修改点:使用计算属性chunkApiPath |
|||
const response = await axios.get(`${this.baseApi}${this.chunkApiPath}`, { |
|||
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) |
|||
// 修改点:使用计算属性chunkApiPath |
|||
const response = await axios.post(`${this.baseApi}${this.chunkApiPath}`, 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 |
|||
} |
|||
this.btnLoading = true |
|||
// 1. 串行上传所有文件的分片 |
|||
this.allChunksUploaded = true |
|||
for (const fileItem of pendingFiles) { |
|||
await this.uploadFileChunks(fileItem) |
|||
// 如果单个文件上传失败,标记整体上传状态为失败 |
|||
if (fileItem.errorMsg) { |
|||
this.allChunksUploaded = false |
|||
} |
|||
} |
|||
// 2. 分片上传完成后执行合并 |
|||
if (!this.allChunksUploaded) { |
|||
this.btnLoading = false |
|||
this.showMessage('部分文件分片上传失败,无法执行合并', 'error') |
|||
return |
|||
} |
|||
const validFiles = this.fileList.filter(item => !item.errorMsg && item.progress === 100) |
|||
if (validFiles.length === 0) { |
|||
this.btnLoading = false |
|||
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 { |
|||
let response |
|||
if (this.isBatchMount === 'true') { |
|||
// ==========批量挂载模式修改:不再只处理单个zip文件,处理全部validFiles数组,带上relativePath========== |
|||
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 = this.convertBytesToKB(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) |
|||
|
|||
json.categoryId = this.selectedCategory.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 = this.convertBytesToKB(file.size) |
|||
json.fileJsonString = JSON.stringify(jsonArray) |
|||
// 新增:文件夹相对路径,传给后端用于重建目录 |
|||
json.relativePath = fileItem.relativePath || '' |
|||
return json |
|||
}) |
|||
const jsonArray = await Promise.all(processFiles) |
|||
console.log('jsonArray', jsonArray) |
|||
// const mergeApiUrl = `${this.baseApi}/api/collect/mergeZip` |
|||
// response = await axios.post(mergeApiUrl, 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.btnLoading = false |
|||
// 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 { |
|||
// this.btnLoading = false |
|||
// throw new Error(response.data.msg || '合并失败') |
|||
// } |
|||
} else { |
|||
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 = this.convertBytesToKB(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 = this.convertBytesToKB(file.size) |
|||
json.fileJsonString = JSON.stringify(jsonArray) |
|||
return json |
|||
}) |
|||
const jsonArray = await Promise.all(processFiles) |
|||
const mergeApiUrl = `${this.baseApi}/api/minioUpload/merge` |
|||
response = await axios.post(mergeApiUrl, 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.btnLoading = false |
|||
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 { |
|||
this.btnLoading = false |
|||
throw new Error(response.data.msg || '合并失败') |
|||
} |
|||
} |
|||
} catch (err) { |
|||
this.btnLoading = false |
|||
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; |
|||
} |
|||
.uploader-drop{ |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
text-align: center; |
|||
height: 120px; |
|||
border: none; |
|||
background-color: #f5f5f5; |
|||
.uploader-btn{ |
|||
border: none; |
|||
i{ |
|||
font-size: 32px; |
|||
color: #1F55EB; |
|||
} |
|||
&:hover{ |
|||
background-color: transparent; |
|||
} |
|||
} |
|||
.el-upload__tip{ |
|||
font-size: 12px; |
|||
color: #A6ADB6; |
|||
} |
|||
} |
|||
.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 { |
|||
position: relative; |
|||
font-weight: 500; |
|||
color: #333; |
|||
margin-bottom: 8px; |
|||
} |
|||
.icon-shanchu{ |
|||
position: absolute; |
|||
right: -7px; |
|||
top: 0; |
|||
font-size: 20px; |
|||
color: #1F55EB; |
|||
cursor: pointer; |
|||
&:hover { |
|||
color: #ff4444; |
|||
transform: scale(1.1); |
|||
transition: all 0.2s ease; |
|||
} |
|||
} |
|||
.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> |
|||
@ -0,0 +1,167 @@ |
|||
<template> |
|||
<div class="process-container"> |
|||
<div v-for="item in filteredFlowList" :key="item.id" class="process-item"> |
|||
<span>{{ item.name }}</span> |
|||
<p>{{ item.flowName }}<i class="iconfont icon-shezhi" @click="toProcessSelected(item)" /></p> |
|||
<el-switch v-model="item.status" class="isEnable-chapter" @change="changeCategoryState(item)" /> |
|||
</div> |
|||
<eForm ref="cuform" :selected-category="selectedCategory" :edit-info-flow="editInfoFlow" @refresh="getInitCategoryFlowBase" /> |
|||
</div> |
|||
</template> |
|||
|
|||
<script> |
|||
import { FetchInitCategoryFlowBase, FetchInitFlowByCategoryId, FetchChangeCategoryState, FetchgetCategoryFlow } from '@/api/system/category/process' |
|||
import eForm from './module/form' |
|||
export default { |
|||
name: 'ProcessManage', |
|||
components: { eForm }, |
|||
props: { |
|||
selectedCategory: { |
|||
type: Object, |
|||
default: function() { |
|||
return {} |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
flowBaseList: [], |
|||
flowSetting: [], |
|||
editInfoFlow: null |
|||
} |
|||
}, |
|||
computed: { |
|||
filteredFlowList() { |
|||
if (this.selectedCategory.pid === '0' && this.selectedCategory.isType === 1) { |
|||
return this.flowBaseList.filter(item => item.id === '5') |
|||
} else { |
|||
return this.flowBaseList.filter(item => item.id !== '5') |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
}, |
|||
mounted() { |
|||
this.getInitCategoryFlowBase() |
|||
}, |
|||
methods: { |
|||
getInitCategoryFlowBase() { |
|||
FetchInitCategoryFlowBase().then((res) => { |
|||
this.flowBaseList = res.map(item => { |
|||
return { |
|||
...item, |
|||
status: false |
|||
} |
|||
}) |
|||
this.getInitFlowByCategoryId() |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
getInitFlowByCategoryId() { |
|||
console.log(this.selectedCategory) |
|||
FetchInitFlowByCategoryId({ 'categoryId': this.selectedCategory.pid !== '0' ? this.selectedCategory.id : null }).then((res) => { |
|||
this.flowSetting = res |
|||
this.flowBaseList.forEach(item => { |
|||
this.flowSetting.forEach(v => { |
|||
if (item.id === v.flowManageId) { |
|||
item.status = !!v.status |
|||
item.flowName = v.flowName |
|||
} |
|||
}) |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
toProcessSelected(item) { |
|||
const params = { |
|||
'categoryId': this.selectedCategory.pid !== '0' ? this.selectedCategory.id : null, |
|||
'manageId': item.id |
|||
} |
|||
FetchgetCategoryFlow(params).then((res) => { |
|||
if (res) { |
|||
// 判断所有值是否都为 null |
|||
if (this.allValuesNull(res)) { |
|||
// console.log('所有值都为 null') 新增 |
|||
this.editInfoFlow = null |
|||
} else { |
|||
// console.log('不是所有值都为 null') 编辑 |
|||
this.editInfoFlow = res |
|||
} |
|||
} else { |
|||
this.editInfoFlow = null |
|||
this.$message({ message: '数据请求失败', type: 'error', offset: 8 }) |
|||
} |
|||
console.log('this.editInfoFlow', this.editInfoFlow) |
|||
this.$nextTick(() => { |
|||
this.$refs.cuform.cuDialogVisible = true |
|||
this.$refs.cuform.setPocessCurrent = item |
|||
this.$refs.cuform.getEditInfoFlow = this.editInfoFlow ? this.editInfoFlow : {} |
|||
this.$refs.cuform.handleEditInfoFlow(this.editInfoFlow) |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
changeCategoryState(item) { |
|||
const filterItem = this.flowSetting.find(v => item.id === v.flowManageId) |
|||
const params = { |
|||
'categoryId': this.selectedCategory.id, |
|||
'id': filterItem.id, |
|||
'status': item.status ? 1 : 0 |
|||
} |
|||
FetchChangeCategoryState(params).then((res) => { |
|||
if (res === 'SUCCESS') { |
|||
this.$message({ message: '操作成功', type: 'success', offset: 8 }) |
|||
} else { |
|||
this.$message({ message: '操作失败', type: 'error', offset: 8 }) |
|||
} |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
allValuesNull(jsonObject) { |
|||
for (const key in jsonObject) { |
|||
if (jsonObject[key] !== null) { |
|||
return false |
|||
} |
|||
} |
|||
return true |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang='scss' scoped> |
|||
.process-item{ |
|||
display: flex; |
|||
justify-content: flex-start; |
|||
padding: 36px 40px 0 40px; |
|||
font-size: 14px; |
|||
height: 32px; |
|||
line-height: 32px; |
|||
margin-bottom: 20px; |
|||
span{ |
|||
display: block; |
|||
margin-right: 12px; |
|||
} |
|||
p{ |
|||
position: relative; |
|||
width: 324px; |
|||
height: 32px; |
|||
line-height: 32px; |
|||
padding: 0 13px; |
|||
margin-right: 16px; |
|||
color: #0C0E1E; |
|||
background-color: #F5F7FA; |
|||
i{ |
|||
position: absolute; |
|||
right: 10px; |
|||
top: 50%; |
|||
transform: translateY(-50%); |
|||
color: #0348F3; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,374 @@ |
|||
<template> |
|||
<el-dialog class="process-dialog" :visible.sync="cuDialogVisible" :close-on-click-modal="false" :modal-append-to-body="false" append-to-body title="工作流选择" :before-close="handleCloseDialog"> |
|||
<div class="setting-dialog"> |
|||
<el-form ref="form" class="process-form" inline :model="form" :rules="rules" size="small" label-width="80px"> |
|||
<div class="process-template"> |
|||
<el-form-item label="流程模板" prop="model"> |
|||
<el-select |
|||
v-model="form.model" |
|||
placeholder="请选择" |
|||
style="width: 580px;" |
|||
value-key="modelId" |
|||
@change="selectModel" |
|||
> |
|||
<el-option |
|||
v-for="item in modelOptions" |
|||
:key="item.modelId" |
|||
:label="item.deployName" |
|||
:value="item" |
|||
/> |
|||
</el-select> |
|||
</el-form-item> |
|||
<div v-loading="loadingImg" class="template-img"> |
|||
<img :src="srcImg" alt="" :onerror="defaultImg"> |
|||
</div> |
|||
</div> |
|||
<div class="process-task"> |
|||
<div class="task-tip">任务需指定执行岗位。当流程进行到该任务时,指定岗位下同一全宗的管理员可进入流程操作</div> |
|||
<div class="task-list"> |
|||
<div v-for="(task,index) in taskList" :key="index" class="task-item"> |
|||
<p>任务{{ index+1 }} :{{ task.nodeName }}</p> |
|||
<el-form-item label="执行岗位" :prop="`task${index+1}`" :rules="dynamicRules(index)"> |
|||
<el-select v-model="form[`task${index+1}`]" value-key="id" @change="selectJob($event,index)"> |
|||
<el-option v-for="item in jobOptions" :key="item.value" :label="item.label" :value="item.value" /> |
|||
</el-select> |
|||
</el-form-item> |
|||
<div v-if="form[`task${index+1}`]" class="detail-arrow" @click="task.isExpend = !task.isExpend"><i :class="['iconfont icon-xiala', task.isExpend ? 'arrow-up':'']" /></div> |
|||
<el-collapse-transition> |
|||
<el-table v-if="task.isExpend" :data="task.userTableData" border style="width: 100%"> |
|||
<el-table-column prop="fondsName" label="全宗" align="center" /> |
|||
<el-table-column prop="deptsName" label="部门" align="center" /> |
|||
<el-table-column prop="username" label="用户名" align="center" /> |
|||
</el-table> |
|||
</el-collapse-transition> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</el-form> |
|||
<div slot="footer" class="dialog-footer"> |
|||
<el-button @click="handleCloseDialog">取消</el-button> |
|||
<el-button type="primary" :loading="loading" @click="save">保存</el-button> |
|||
</div> |
|||
</div> |
|||
</el-dialog> |
|||
</template> |
|||
|
|||
<script> |
|||
import { FetchInitFlowAll, FetchFindFolwImgByModelId, FetchUserTaskAllByDeployId } from '@/api/system/flowable' |
|||
import { FetchPostAll, FetchFindUserByPost } from '@/api/system/job' |
|||
import { FetchFlowbinding } from '@/api/system/category/process' |
|||
|
|||
export default { |
|||
props: { |
|||
selectedCategory: { |
|||
type: Object, |
|||
default: function() { |
|||
return {} |
|||
} |
|||
}, |
|||
editInfoFlow: { |
|||
type: Object, |
|||
default: function() { |
|||
return {} |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
cuDialogVisible: false, |
|||
loading: false, |
|||
loadingImg: false, |
|||
form: { |
|||
model: null |
|||
}, |
|||
modelOptions: [], |
|||
srcImg: '', |
|||
defaultImg: 'this.src="' + require('@/assets/images/system/default-img2.png') + '"', |
|||
jobOptions: [ |
|||
{ |
|||
value: '', |
|||
label: '请选择' |
|||
} |
|||
], |
|||
rules: { |
|||
model: [ |
|||
{ required: true, message: '流程模板不可为空', trigger: 'change' } |
|||
] |
|||
}, |
|||
userTableData: [], |
|||
taskList: [], |
|||
selectFlowVal: null, |
|||
setPocessCurrent: null, |
|||
getEditInfoFlow: {} |
|||
} |
|||
}, |
|||
computed: { |
|||
dynamicRules() { |
|||
return (index) => { |
|||
return [{ |
|||
required: true, |
|||
message: '执行岗位不可为空', |
|||
trigger: 'change' |
|||
}] |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
// editInfoFlow: { |
|||
// handler(newVal, oldVal) { |
|||
// // this.handleEditInfoFlow(newVal) |
|||
// }, |
|||
// deep: true |
|||
// } |
|||
}, |
|||
mounted() { |
|||
this.getAllFlowable() |
|||
this.getAllJob() |
|||
}, |
|||
methods: { |
|||
// handleEditInfoFlow(newVal) { |
|||
// console.log('getEditInfoFlow', this.getEditInfoFlow) |
|||
// const findItem = this.findIdByModelId(newVal.flowId) |
|||
// this.form.model = findItem |
|||
// this.selectModel(findItem, newVal) |
|||
// }, |
|||
// findIdByModelId(modelId) { |
|||
// const item = this.modelOptions.find(data => data.modelId === modelId) |
|||
// if (item) { |
|||
// return item |
|||
// } else { |
|||
// return null |
|||
// } |
|||
// }, |
|||
handleEditInfoFlow(newVal) { |
|||
console.log('编辑回显数据:', newVal) |
|||
|
|||
// 确保 form 存在 model 属性 |
|||
if (!this.form.model) { |
|||
this.form.model = null |
|||
} |
|||
|
|||
// 无数据直接清空返回 |
|||
if (!newVal || !newVal.flowId) { |
|||
this.form.model = null |
|||
return |
|||
} |
|||
|
|||
const findItem = this.findIdByModelId(newVal.flowId) |
|||
this.form.model = findItem |
|||
|
|||
this.$nextTick(() => { |
|||
if (findItem) { |
|||
this.selectModel(findItem, newVal) |
|||
} |
|||
}) |
|||
}, |
|||
findIdByModelId(modelId) { |
|||
return this.modelOptions.find(data => data.modelId === modelId) || null |
|||
}, |
|||
getAllFlowable() { |
|||
FetchInitFlowAll().then((res) => { |
|||
this.modelOptions = res |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
selectModel(val, parentProcessVal) { |
|||
if (!parentProcessVal) { |
|||
this.getEditInfoFlow.categoryFlowDetails = [] |
|||
} |
|||
|
|||
this.selectFlowVal = val |
|||
this.loadingImg = true |
|||
FetchFindFolwImgByModelId({ 'processDefinitionId': val.procdefId }).then((res) => { |
|||
this.srcImg = 'data:image/jpeg;base64,' + res |
|||
this.loadingImg = false |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
FetchUserTaskAllByDeployId({ 'deployId': val.deployId }).then((res) => { |
|||
this.taskList = res.map((item, index) => { |
|||
this.$set(this.form, `task${index + 1}`, null) |
|||
return { |
|||
...item, |
|||
isExpend: false, |
|||
userTableData: [], |
|||
postId: null |
|||
} |
|||
}) |
|||
if (parentProcessVal) { |
|||
parentProcessVal.categoryFlowDetails.forEach((item, index) => { |
|||
this.$set(this.form, `task${index + 1}`, item.postId) |
|||
this.selectJob(item.postId, index) |
|||
}) |
|||
} |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
getAllJob() { |
|||
FetchPostAll().then((res) => { |
|||
this.jobOptions = res.map(item => { |
|||
const json = {} |
|||
json.value = item.id |
|||
json.label = item.postName |
|||
return json |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
selectJob(val, index) { |
|||
this.$forceUpdate() |
|||
FetchFindUserByPost({ 'postId': val }).then((res) => { |
|||
this.taskList.forEach((item, i) => { |
|||
if (index === i) { |
|||
item.postId = val |
|||
item.userTableData = res |
|||
} |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
save() { |
|||
this.loading = true |
|||
this.$refs['form'].validate((valid) => { |
|||
if (valid) { |
|||
const detailsDTOs = this.taskList.map((item, index) => { |
|||
const json = {} |
|||
json.id = this.getEditInfoFlow && this.getEditInfoFlow.categoryFlowDetails.length !== 0 ? this.getEditInfoFlow.categoryFlowDetails[index].id : null |
|||
json.postId = item.postId |
|||
json.taskId = item.nodeId |
|||
return json |
|||
}) |
|||
const params = { |
|||
// 'categoryId': this.selectedCategory.pid !== '0' ? this.selectedCategory.id : null, |
|||
'categoryId': this.selectedCategory.id, |
|||
'detailsDTOs': detailsDTOs, |
|||
'flowId': this.selectFlowVal.modelId, |
|||
'flowName': this.selectFlowVal.deployName, |
|||
'flowKey': this.selectFlowVal.modelKey, |
|||
'flowManageId': this.setPocessCurrent.id, |
|||
'id': this.getEditInfoFlow && this.getEditInfoFlow.id, |
|||
'status': this.getEditInfoFlow && this.getEditInfoFlow.status |
|||
} |
|||
console.log('params', params) |
|||
FetchFlowbinding(params).then((res) => { |
|||
if (res === 'SUCCESS') { |
|||
this.$message({ message: '操作成功', type: 'success', offset: 8 }) |
|||
} else { |
|||
this.$message({ message: '操作失败', type: 'error', offset: 8 }) |
|||
} |
|||
this.handleCloseDialog() |
|||
this.$emit('refresh') |
|||
this.loading = false |
|||
}).catch(err => { |
|||
console.log(err) |
|||
this.loading = false |
|||
}) |
|||
} else { |
|||
this.loading = false |
|||
return false |
|||
} |
|||
}) |
|||
}, |
|||
handleCloseDialog() { |
|||
// 重置为初始状态,而不是直接赋空对象 |
|||
this.form = { |
|||
model: null |
|||
} |
|||
this.srcImg = '' |
|||
this.taskList = [] |
|||
this.selectFlowVal = null |
|||
this.getEditInfoFlow = {} |
|||
this.cuDialogVisible = false |
|||
|
|||
// 清除校验状态(必须加) |
|||
if (this.$refs.form) { |
|||
this.$refs.form.clearValidate() |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
|
|||
.process-dialog{ |
|||
::v-deep .el-dialog{ |
|||
width: 1100px; |
|||
} |
|||
} |
|||
.process-form{ |
|||
display: flex; |
|||
justify-content: space-between; |
|||
} |
|||
.process-template{ |
|||
margin-right: 20px; |
|||
} |
|||
.template-img{ |
|||
display: flex; |
|||
align-items: center; |
|||
width: 690px; |
|||
height: 460px; |
|||
border: 1px solid #eee; |
|||
background-color: #EDEFF3; |
|||
overflow: hidden; |
|||
img{ |
|||
display: block; |
|||
margin: 0 auto; |
|||
// width: 100%; |
|||
} |
|||
} |
|||
.task-tip{ |
|||
margin-bottom: 20px; |
|||
padding-left: 24px; |
|||
font-size: 14px; |
|||
line-height: 18px; |
|||
color: #0c0e1e; |
|||
background: url('~@/assets/images/icon/tip-icon.png') no-repeat left top; |
|||
background-size: 18px 18px; |
|||
} |
|||
.task-list{ |
|||
height: 454px; |
|||
overflow: hidden; |
|||
overflow-y: scroll; |
|||
} |
|||
.task-item{ |
|||
p{ |
|||
line-height: 32px; |
|||
} |
|||
} |
|||
.detail-arrow{ |
|||
position: relative; |
|||
width: 64px; |
|||
height: 20px; |
|||
margin: -8px auto 10px auto; |
|||
background-color:#E8F2FF; |
|||
border-radius: 3px; |
|||
cursor: pointer; |
|||
i{ |
|||
position: absolute; |
|||
left: 50%; |
|||
top: 50%; |
|||
display: block; |
|||
width: 14px; |
|||
height: 14px; |
|||
margin-top: -7px; |
|||
margin-left: -7px; |
|||
text-align: center; |
|||
font-size: 12px; |
|||
color: #0348F3; |
|||
transition: all ease-in 0.4s; |
|||
transform: scale(0.7); |
|||
&.arrow-up { |
|||
transform: scale(0.7) rotate(180deg); |
|||
transform-origin: center; |
|||
} |
|||
} |
|||
} |
|||
::v-deep .el-table__body-wrapper{ |
|||
border-bottom: 1px solid #dfe6ec !important; |
|||
} |
|||
</style> |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue