Browse Source

批量挂接优化0901

master
xuhuajiao 2 weeks ago
parent
commit
9bfa080076
  1. 36
      src/utils/upload.js
  2. 733
      src/views/collectReorganizi/batchConnection/module/form copy.vue
  3. 504
      src/views/collectReorganizi/batchConnection/module/form-zip.vue
  4. 673
      src/views/collectReorganizi/batchConnection/module/form.vue
  5. 850
      src/views/components/category/preUpload-非zip-test.vue
  6. 8
      src/views/features/ssoLogin.vue
  7. 8
      src/views/system/fileLibraryManage/index.vue
  8. 167
      src/views/system/fileLibraryManage/processManage/index.vue
  9. 374
      src/views/system/fileLibraryManage/processManage/module/form.vue

36
src/utils/upload.js

@ -80,24 +80,34 @@ export function modelUpload(api, file, name) {
// 收集库 - 批量挂接
// export function batchMountUpload(api, file, params) {
// export function batchMountUpload(api, params) {
// // var data = new FormData()
// // data.append('categoryId', params.categoryId)
// // data.append('mountType', params.mountType)
// // data.append('checkRepeatType', params.checkRepeatType)
// // data.append('matchingMode', params.matchingMode)
// // data.append('fields', params.fields)
// // data.append('fondsId', params.fondsId)
// // data.append('upfilePath', params.upfilePath)
// const jsonData = JSON.stringify(params)
// const config = {
// headers: {
// // 'Content-Type': 'application/json;charset=UTF-8',
// // 'Content-Type': 'application/json',
// 'Authorization': getToken()
// },
// transformRequest: [(data) => data]
// }
// return axios.post(api, jsonData, config)
// }
export function batchMountUpload(api, params) {
// var data = new FormData()
// data.append('categoryId', params.categoryId)
// data.append('mountType', params.mountType)
// data.append('checkRepeatType', params.checkRepeatType)
// data.append('matchingMode', params.matchingMode)
// data.append('fields', params.fields)
// data.append('fondsId', params.fondsId)
// data.append('upfilePath', params.upfilePath)
const jsonData = JSON.stringify(params)
const config = {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'Authorization': getToken()
},
transformRequest: [(data) => data]
}
return axios.post(api, jsonData, config)
}
return axios.post(api, params, config)
}
// 收集库 - 远程挂接

733
src/views/collectReorganizi/batchConnection/module/form copy.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>

504
src/views/collectReorganizi/batchConnection/module/form-zip.vue

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

673
src/views/collectReorganizi/batchConnection/module/form.vue

@ -1,6 +1,15 @@
<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">
<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;">
@ -41,22 +50,17 @@
</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">
<!-- 打开子选择弹窗 -->
<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" />
<!-- {{ item.file_name }} -->
{{ item }}
{{ getFileNameFromPath(path) }}
</div>
</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">
@ -102,6 +106,69 @@
<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"
:class="{ 'is-merged': isFileMerged(f), 'is-unmerged': !isFileMerged(f) }"
>
<i class="iconfont icon-xiaowenjian" />
<span class="file-name">{{ f.name }}</span>
<span class="merge-status">{{ isFileMerged(f) ? '已上传' : '未上传' }}</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>
@ -110,11 +177,14 @@ 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'
import axios from 'axios'
import { getToken } from '@/utils/auth'
// 5MB
const CHUNK_SIZE = 5 * 1024 * 1024
export default {
name: 'LocalHitch',
components: { PreUpload },
mixins: [crud()],
props: {
selectedCategory: {
@ -131,6 +201,24 @@ export default {
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,
@ -141,186 +229,324 @@ export default {
filePath: null,
fileRemote: null
},
upfilePath: null,
fileList: [],
typeOptions: [
{
value: 1,
label: '跳过'
},
{
value: 2,
label: '覆盖'
},
{
value: 3,
label: '追加'
}
{ value: 1, label: '跳过' },
{ value: 2, label: '覆盖' },
{ value: 3, label: '追加' }
],
matchedOptions: [
{
value: 1,
label: '全量匹配'
},
{
value: 2,
label: '前缀模糊匹配'
},
{
value: 3,
label: '后缀模糊匹配'
}
{ value: 1, label: '全量匹配' },
{ value: 2, label: '前缀模糊匹配' },
{ value: 3, label: '后缀模糊匹配' }
],
batchTypeOptions: [
{
value: 1,
label: '服务端挂接'
},
{
value: 2,
label: 'FTP挂接'
},
{
value: 3,
label: 'SFTP挂接'
}
{ 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' }
]
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'
]),
...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
}
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() {
},
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
//
getFileKey(f) {
return `${f.webkitRelativePath || f.name}__${f.size}`
},
handleErrorResource(res) {
console.log('handleErrorResource', res)
//
isFileMerged(f) {
return !!(this.lastMergeSnapshot && this.lastMergeSnapshot.fileKeyToPath[this.getFileKey(f)])
},
handleFile(event) {
const files = event.target.files
for (let i = 0; i < files.length; i++) {
this.fileList = []
this.fileList.push(files[i])
//
checkMergeSnapshot() {
if (!this.lastMergeSnapshot || this.tempSelectedFiles.length === 0) {
this.dialogUploadSuccess = false
this.dialogResultPaths = []
return
}
const paths = []
let allMatched = true
for (const f of this.tempSelectedFiles) {
const path = this.lastMergeSnapshot.fileKeyToPath[this.getFileKey(f)]
if (path) {
paths.push(path)
} else {
allMatched = false
break
}
}
if (allMatched) {
this.dialogUploadSuccess = true
this.dialogResultPaths = paths
} else {
this.dialogUploadSuccess = false
this.dialogResultPaths = []
}
},
// / \
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
this.appendUniqueFiles(Array.from(files))
e.target.value = ''
this.checkMergeSnapshot()
},
//
onSelectFolder(e) {
const files = e.target.files
if (!files || files.length === 0) return
this.appendUniqueFiles(Array.from(files))
e.target.value = ''
this.checkMergeSnapshot()
},
// webkitRelativePath/name + size
appendUniqueFiles(newFiles) {
const existingKeys = new Set(this.tempSelectedFiles.map(f => this.getFileKey(f)))
const uniqueFiles = []
let dupCount = 0
newFiles.forEach(f => {
const key = this.getFileKey(f)
if (existingKeys.has(key)) {
dupCount++
} else {
existingKeys.add(key)
uniqueFiles.push(f)
}
})
if (dupCount > 0) {
this.$message.warning(`已忽略 ${dupCount} 个重复文件`)
}
this.tempSelectedFiles = [...this.tempSelectedFiles, ...uniqueFiles]
},
//
deleteTempFile(idx) {
this.tempSelectedFiles.splice(idx, 1)
//
this.checkMergeSnapshot()
},
// ====================
async initUpload(fileName, totalChunks) {
const res = await axios.post('/api/uploadtemp/init', null, {
params: { fileName, totalChunks },
headers: { 'Authorization': getToken() }
})
return res.data.data
},
handleComfired() {
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)
// file key path
const fileKeyToPath = {}
this.tempSelectedFiles.forEach((f, i) => {
fileKeyToPath[this.getFileKey(f)] = pathList[i]
})
this.lastMergeSnapshot = { fileKeyToPath }
this.dialogResultPaths = [...pathList]
this.dialogUploadSuccess = true
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.fileList.length === 0) {
this.$message({ message: '请先选择相关文件!', offset: 8 })
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((valid) => {
console.log('this.selectedCategory', this.selectedCategory)
if (valid) {
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,
'upfilePath': this.upfilePath
}
console.log(params)
batchMountUpload(this.baseApi + '/api/collect/batchMount',
params
).then(res => {
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.$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 // rootDirectory+
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 => {
remoteConnectiontUpload(this.baseApi + '/api/collect/remoteMount', params).then(res => {
if (res.data.code === 200) {
this.$message({ message: '操作成功', type: 'success', offset: 8 })
} else {
@ -329,28 +555,28 @@ export default {
this.handleCancel()
this.crud.refresh()
this.btnLoading = false
})
}
} else {
}).catch(err => {
console.error('请求异常', err)
this.$message.error('请求异常')
this.btnLoading = false
return false
})
}
})
},
getFieldCommon() {
const params = {
'categoryId': this.selectedCategory.id,
'categoryLevel': this.collectLevel
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
this.fieldOptions = data.map(item => {
return {
id: item.id,
label: item.fieldCnName,
value: item.fieldName
}
})
// archive_no
const archiveNoOption = this.fieldOptions.find(item => item.value === 'archive_no')
if (archiveNoOption) {
this.form.hitchFiled = archiveNoOption.value
@ -358,11 +584,13 @@ export default {
}
})
},
selectFiled(val) {
if (val) {
if (this.hitchRemarkArray && this.hitchRemarkArray.includes('$' + val + '$')) {
if (!val) return
if (this.hitchRemarkArray.includes('$' + val + '$')) {
this.$message({ message: '请注意当前选择的挂接字段,在挂接详情内已存在!', offset: 8 })
} else {
return
}
const lastItemIndex = this.hitchRemarkArray.length - 1
const index = this.hitchRemarkArray.indexOf('*')
if (index !== -1) {
@ -380,63 +608,52 @@ export default {
} 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)
}
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 => {
FetchFindDetailsByProtocol({ protocol: val }).then(res => {
this.filePathOptions = res
})
}
},
changeFilePath(val) {
this.fileRemoteLoading = true
if (val) {
this.fileRemoteOptions = []
FetchFileByRemoteConnection({ 'id': val.id }).then(res => {
FetchFileByRemoteConnection({ id: val.id }).then(res => {
if (res) {
this.fileRemoteOptions = res.map(item => {
const json = {}
json.value = item
json.name = item
return json
})
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)
}
if (index !== -1) this.hitchRemarkArray.splice(index, 1)
},
handleCancel() {
this.$refs.form.resetFields()
this.hitchRemarkArray = []
this.fileList = []
this.filePathOptions = []
this.fileRemoteOptions = []
this.uploadResultPaths = []
this.fileSelectDialogVisible = false
this.localHitchVisible = false
}
}
@ -453,11 +670,17 @@ export default {
.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;
@ -472,7 +695,6 @@ export default {
margin-right: 6px;
}
}
.file_remote-style {
::v-deep .el-form-item__content {
width: 400px !important;
@ -491,14 +713,73 @@ export default {
.upload-wm {
height: 100px;
}
::v-deep .uploader-big{
margin-left: 0 !important;
/* 子弹窗样式 */
.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 8px;
font-size: 14px;
display: flex;
align-items: center;
justify-content: space-between;
border-radius: 3px;
&.is-merged {
background: #f0f9eb;
.icon-xiaowenjian { color: #67c23a; }
.merge-status { color: #67c23a; }
}
&.is-unmerged {
background: #fdf6ec;
.icon-xiaowenjian { color: #e6a23c; }
.merge-status { color: #e6a23c; }
}
}
.merge-status {
font-size: 12px;
margin: 0 8px;
white-space: nowrap;
}
.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;
}
::v-deep .upload_process_box{
right: 0 !important;
left: 120px !important;
top: 0 !important;
width: 300px !important;
}
</style>

850
src/views/components/category/preUpload-非zip-test.vue

@ -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
// 11
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
// inputchange
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') {
// ==========zipvalidFilesrelativePath==========
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>

8
src/views/features/ssoLogin.vue

@ -27,6 +27,7 @@
<script>
import { setToken } from '@/utils/auth'
import { mapGetters } from 'vuex'
export default {
name: 'SSOLogin',
@ -37,6 +38,11 @@ export default {
message: ''
}
},
computed: {
...mapGetters([
'baseApi'
])
},
mounted() {
this.handleAutoLogin()
},
@ -58,7 +64,7 @@ export default {
console.log('flowableId', flowableId)
//
fetch('/api/sso/auto-login', {
fetch(this.baseApi + '/api/sso/auto-login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'

8
src/views/system/fileLibraryManage/index.vue

@ -58,6 +58,7 @@
<li v-if="selectedCategory.isType && selectedCategory.isType === 3 && selectedCategory.cnName !== '电子原文表'" :class="{'active-tab-nav': activeIndex == 2}" @click="changeActiveTab(2)">著录界面设置<i /></li>
<li v-if="selectedCategory.isType && selectedCategory.isType === 3 && selectedCategory.cnName !== '电子原文表'" :class="{'active-tab-nav': activeIndex == 3}" @click="changeActiveTab(3)">列表界面设置<i /></li>
<li v-if="selectedCategory.isType && selectedCategory.isType === 3 && selectedCategory.cnName !== '电子原文表'" :class="{'active-tab-nav': activeIndex == 4}" @click="changeActiveTab(4)">排序规则设置<i /></li>
<li v-if="selectedCategory.isType && selectedCategory.isType === 1 && selectedCategory.pid === '0'" :class="{'active-tab-nav': activeIndex == 5}" @click="changeActiveTab(5)">流程管理<i /></li>
<!-- 最右侧装饰img -->
<span class="tab-right-img" />
</ul>
@ -106,12 +107,13 @@ import fieldManage from './fieldManage/index'
import descriptionPreview from './descriptionPreview/index'
import listBrowsing from './listBrowsing/index'
import orderingRule from './orderingRule/index'
import processManage from './processManage/index'
import crudOperation from '@crud/CRUD.operation'
import Vue from 'vue'
export default {
name: 'FileLibraryManage',
components: { crudOperation, eForm, sortDialog, baseInfo, fieldManage, descriptionPreview, listBrowsing, orderingRule },
components: { crudOperation, eForm, sortDialog, baseInfo, fieldManage, descriptionPreview, listBrowsing, orderingRule, processManage },
cruds() {
return [
CRUD({
@ -162,6 +164,8 @@ export default {
return 'listBrowsing'
} else if (this.activeIndex === 4) {
return 'orderingRule'
} else if (this.activeIndex === 5) {
return 'processManage'
}
return 'baseInfo'
}
@ -186,6 +190,8 @@ export default {
}
},
[CRUD.HOOK.afterRefresh]() {
console.log(this.crud.data)
let currentKey
if (localStorage.getItem('documentCategoryKey')) {
currentKey = JSON.parse(localStorage.getItem('documentCategoryKey'))

167
src/views/system/fileLibraryManage/processManage/index.vue

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

374
src/views/system/fileLibraryManage/processManage/module/form.vue

@ -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>
Loading…
Cancel
Save