feat(bpm): 支持审批任务附件上传与展示
- 审批通过、审批拒绝弹窗新增附件/图片上传 - 审批提交时携带 attachments 字段 - 审批完成或关闭弹窗后清理附件表单状态 - 审批流时间线支持展示审批附件 - 图片附件支持预览,非图片附件支持链接打开 - 统一附件上传目录、文件类型白名单和 5MB 大小限制 - ApprovalTaskInfo 增加 attachments 字段master
parent
61c71b9a0e
commit
44136d310b
|
|
@ -36,6 +36,7 @@ export type ApprovalTaskInfo = {
|
|||
assigneeUser: User
|
||||
status: number
|
||||
reason: string
|
||||
attachments?: string[]
|
||||
signPicUrl: string
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,16 @@
|
|||
:rows="4"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传附件/图片" prop="attachments">
|
||||
<UploadFile
|
||||
v-model="approveReasonForm.attachments"
|
||||
:limit="10"
|
||||
:file-type="APPROVAL_ATTACHMENT_FILE_TYPES"
|
||||
:file-size="APPROVAL_ATTACHMENT_FILE_SIZE"
|
||||
directory="bpm/task-attachment"
|
||||
:is-show-tip="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="下一个节点的审批人"
|
||||
prop="nextAssignees"
|
||||
|
|
@ -118,6 +128,16 @@
|
|||
:rows="4"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传附件/图片" prop="attachments">
|
||||
<UploadFile
|
||||
v-model="rejectReasonForm.attachments"
|
||||
:limit="10"
|
||||
:file-type="APPROVAL_ATTACHMENT_FILE_TYPES"
|
||||
:file-size="APPROVAL_ATTACHMENT_FILE_SIZE"
|
||||
directory="bpm/task-attachment"
|
||||
:is-show-tip="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
:disabled="formLoading"
|
||||
|
|
@ -530,6 +550,7 @@ import { until, useDebounceFn } from '@vueuse/core'
|
|||
import SignDialog from './SignDialog.vue'
|
||||
import ProcessInstanceTimeline from '../detail/ProcessInstanceTimeline.vue'
|
||||
import { isEmpty } from '@/utils/is'
|
||||
import { UploadFile } from '@/components/UploadFile'
|
||||
|
||||
defineOptions({ name: 'ProcessInstanceBtnContainer' })
|
||||
|
||||
|
|
@ -561,6 +582,23 @@ const popOverVisible = ref({
|
|||
deleteSign: false
|
||||
}) // 气泡卡是否展示
|
||||
const returnList = ref([] as any) // 退回节点
|
||||
const APPROVAL_ATTACHMENT_FILE_TYPES = [
|
||||
'doc',
|
||||
'docx',
|
||||
'xls',
|
||||
'xlsx',
|
||||
'ppt',
|
||||
'pptx',
|
||||
'txt',
|
||||
'pdf',
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'bmp',
|
||||
'webp'
|
||||
]
|
||||
const APPROVAL_ATTACHMENT_FILE_SIZE = 5
|
||||
|
||||
// ========== 审批信息 ==========
|
||||
const runningTask = ref<any>() // 运行中的任务
|
||||
|
|
@ -580,6 +618,7 @@ let pendingNextNodesTask: Promise<unknown> | null = null // 跟踪 onChange 触
|
|||
const approveReasonForm = reactive({
|
||||
reason: '',
|
||||
signPicUrl: '',
|
||||
attachments: [] as string[],
|
||||
nextAssignees: {}
|
||||
})
|
||||
const approveReasonRule = computed(() => {
|
||||
|
|
@ -599,7 +638,8 @@ const approveReasonRule = computed(() => {
|
|||
// 拒绝表单
|
||||
const rejectFormRef = ref<FormInstance>()
|
||||
const rejectReasonForm = reactive({
|
||||
reason: ''
|
||||
reason: '',
|
||||
attachments: [] as string[]
|
||||
})
|
||||
const rejectReasonRule = computed(() => {
|
||||
return {
|
||||
|
|
@ -736,6 +776,12 @@ const closePopover = (type: string, formRef: FormInstance | undefined) => {
|
|||
if (formRef) {
|
||||
formRef.resetFields()
|
||||
}
|
||||
if (type === 'approve') {
|
||||
approveReasonForm.attachments = []
|
||||
}
|
||||
if (type === 'reject') {
|
||||
rejectReasonForm.attachments = []
|
||||
}
|
||||
popOverVisible.value[type] = false
|
||||
nextAssigneesActivityNode.value = []
|
||||
// 清理 Timeline 组件中的自定义审批人数据
|
||||
|
|
@ -837,6 +883,7 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: approveReasonForm.reason,
|
||||
attachments: approveReasonForm.attachments,
|
||||
variables, // 审批通过, 把修改的字段值赋于流程实例变量
|
||||
nextAssignees: approveReasonForm.nextAssignees // 下个自选节点选择的审批人信息
|
||||
} as any
|
||||
|
|
@ -861,7 +908,8 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||
// 审批不通过数据
|
||||
const data = {
|
||||
id: runningTask.value.id,
|
||||
reason: rejectReasonForm.reason
|
||||
reason: rejectReasonForm.reason,
|
||||
attachments: rejectReasonForm.attachments
|
||||
}
|
||||
await TaskApi.rejectTask(data)
|
||||
popOverVisible.value.reject = false
|
||||
|
|
@ -869,6 +917,8 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||
}
|
||||
// 重置表单
|
||||
formRef.resetFields()
|
||||
approveReasonForm.attachments = []
|
||||
rejectReasonForm.attachments = []
|
||||
// 加载最新数据
|
||||
reload()
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -129,14 +129,33 @@
|
|||
</div>
|
||||
<teleport defer :to="`#activity-task-${activity.id}-${index}`">
|
||||
<div
|
||||
v-if="
|
||||
task.reason &&
|
||||
[NodeType.USER_TASK_NODE, NodeType.END_EVENT_NODE].includes(activity.nodeType)
|
||||
"
|
||||
v-if="shouldShowReasonAndAttachment(task, activity.nodeType)"
|
||||
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
|
||||
>
|
||||
<!-- TODO lesan:这里如果是办理,需要是办理意见 -->
|
||||
审批意见:{{ task.reason }}
|
||||
<div v-if="task.reason">审批意见:{{ task.reason }}</div>
|
||||
<div v-if="task.attachments?.length" class="mt-2 flex flex-wrap gap-2">
|
||||
<template v-for="attachment in task.attachments" :key="attachment">
|
||||
<el-image
|
||||
v-if="isImageAttachment(attachment)"
|
||||
class="h-40px w-40px rounded"
|
||||
:src="attachment"
|
||||
:preview-src-list="[attachment]"
|
||||
fit="cover"
|
||||
preview-teleported
|
||||
/>
|
||||
<el-link
|
||||
v-else
|
||||
:href="attachment"
|
||||
:underline="false"
|
||||
target="_blank"
|
||||
type="primary"
|
||||
>
|
||||
<Icon class="mr-1" icon="ep:document" />
|
||||
{{ getAttachmentName(attachment) }}
|
||||
</el-link>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="task.signPicUrl && activity.nodeType === NodeType.USER_TASK_NODE"
|
||||
|
|
@ -316,6 +335,34 @@ const getApprovalNodeTime = (node: ProcessInstanceApi.ApprovalNodeInfo) => {
|
|||
}
|
||||
}
|
||||
|
||||
/** 是否展示审批意见和附件 */
|
||||
const shouldShowReasonAndAttachment = (
|
||||
task: ProcessInstanceApi.ApprovalTaskInfo,
|
||||
nodeType: NodeType
|
||||
) => {
|
||||
return (
|
||||
Boolean(task.reason || task.attachments?.length) &&
|
||||
[NodeType.START_USER_NODE, NodeType.USER_TASK_NODE, NodeType.END_EVENT_NODE].includes(nodeType)
|
||||
)
|
||||
}
|
||||
|
||||
/** 获取附件名 */
|
||||
const getAttachmentName = (url: string) => {
|
||||
const cleanUrl = url.split(/[?#]/)[0]
|
||||
const fileName = cleanUrl.slice(cleanUrl.lastIndexOf('/') + 1)
|
||||
try {
|
||||
return decodeURIComponent(fileName)
|
||||
} catch {
|
||||
return fileName
|
||||
}
|
||||
}
|
||||
|
||||
/** 是否图片附件 */
|
||||
const isImageAttachment = (url: string) => {
|
||||
const ext = url.split(/[?#]/)[0]?.split('.').pop()?.toLowerCase()
|
||||
return ['bmp', 'gif', 'jpeg', 'jpg', 'png', 'webp'].includes(ext || '')
|
||||
}
|
||||
|
||||
// 选择自定义审批人
|
||||
const userSelectFormRef = ref()
|
||||
const handleSelectUser = (activityId, selectedList) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue