【功能评审】Bpm:审批签名

This commit is contained in:
YunaiV
2025-01-16 13:01:06 +08:00
parent 753e44ccd0
commit 8e5271a6d6
7 changed files with 126 additions and 144 deletions

View File

@ -36,7 +36,7 @@ export type ApprovalTaskInfo = {
assigneeUser: User
status: number
reason: string
sign: string
sign: string // TODO @lesan字段改成 signPicUrl 签名照片。只有 sign 感觉是签名文本哈。
}
// 审批节点信息
@ -90,7 +90,7 @@ export const getProcessInstanceCopyPage = async (params: any) => {
// 获取审批详情
export const getApprovalDetail = async (params: any) => {
return await request.get({ url: 'bpm/process-instance/get-approval-detail' , params })
return await request.get({ url: 'bpm/process-instance/get-approval-detail', params })
}
// 获取表单字段权限

View File

@ -37,9 +37,9 @@
:value="node.value"
/>
</el-select>
<el-button class="mla" type="danger" link @click="deleteRouterGroup(index)"
>删除</el-button
>
<el-button class="mla" type="danger" link @click="deleteRouterGroup(index)">
删除
</el-button>
</div>
</template>
<Condition
@ -67,6 +67,7 @@ import { Plus } from '@element-plus/icons-vue'
import { SimpleFlowNode, NodeType, ConditionType, RouterCondition } from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node'
import Condition from './components/Condition.vue'
defineOptions({
name: 'RouterNodeConfig'
})
@ -86,9 +87,9 @@ const currentNode = useWatchNode(props)
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
const routerGroups = ref<RouterCondition[]>([])
const nodeOptions = ref()
const conditionRef = ref([])
// 保存配置
/** 保存配置 */
const saveConfig = async () => {
// 校验表单
let valid = true

View File

@ -440,6 +440,7 @@
</div>
</div>
</el-tab-pane>
<!-- TODO @lesan要不抽成 Listener 小组件?类似 Condition.vue -->
<el-tab-pane label="监听器" name="listener">
<el-form ref="listenerFormRef" :model="configForm" label-position="top">
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">

View File

@ -44,14 +44,26 @@
:rows="4"
/>
</el-form-item>
<el-form-item v-if="runningTask.signEnable" label="签名" prop="sign" ref="approveSignFormRef">
<el-form-item
v-if="runningTask.signEnable"
label="签名"
prop="sign"
ref="approveSignFormRef"
>
<el-button @click="signRef.open()">点击签名</el-button>
<el-image class="w-90px h-40px ml-5px" v-if="approveReasonForm.sign"
:src="approveReasonForm.sign"
:preview-src-list="[approveReasonForm.sign]"/>
<el-image
class="w-90px h-40px ml-5px"
v-if="approveReasonForm.sign"
:src="approveReasonForm.sign"
:preview-src-list="[approveReasonForm.sign]"
/>
</el-form-item>
<el-form-item>
<el-button :disabled="formLoading" type="success" @click="handleAudit(true, approveFormRef)">
<el-button
:disabled="formLoading"
type="success"
@click="handleAudit(true, approveFormRef)"
>
{{ getButtonDisplayName(OperationButtonType.APPROVE) }}
</el-button>
<el-button @click="closePropover('approve', approveFormRef)"> 取消 </el-button>
@ -92,7 +104,11 @@
/>
</el-form-item>
<el-form-item>
<el-button :disabled="formLoading" type="danger" @click="handleAudit(false,rejectFormRef)">
<el-button
:disabled="formLoading"
type="danger"
@click="handleAudit(false, rejectFormRef)"
>
{{ getButtonDisplayName(OperationButtonType.REJECT) }}
</el-button>
<el-button @click="closePropover('reject', rejectFormRef)"> 取消 </el-button>
@ -478,7 +494,8 @@
</div>
</div>
<SignDialog ref="signRef" @success="handleSignFinish"/>
<!-- 签名弹窗 -->
<SignDialog ref="signRef" @success="handleSignFinish" />
</template>
<script lang="ts" setup>
import { useUserStoreWithOut } from '@/store/modules/user'
@ -487,12 +504,13 @@ import * as TaskApi from '@/api/bpm/task'
import * as ProcessInstanceApi from '@/api/bpm/processInstance'
import * as UserApi from '@/api/system/user'
import {
OperationButtonType,
OPERATION_BUTTON_NAME
OPERATION_BUTTON_NAME,
OperationButtonType
} from '@/components/SimpleProcessDesignerV2/src/consts'
import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants'
import { BpmModelFormType, BpmProcessInstanceStatus } from '@/utils/constants'
import type { FormInstance, FormRules } from 'element-plus'
import SignDialog from "./SignDialog.vue";
import SignDialog from './SignDialog.vue'
defineOptions({ name: 'ProcessInstanceBtnContainer' })
const router = useRouter() // 路由
@ -501,12 +519,12 @@ const message = useMessage() // 消息弹窗
const userId = useUserStoreWithOut().getUser.id // 当前登录的编号
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const props = defineProps< {
processInstance: any, // 流程实例信息
processDefinition: any, // 流程定义信息
userOptions: UserApi.UserVO[],
normalForm: any, // 流程表单 formCreate
normalFormApi: any, // 流程表单 formCreate Api
const props = defineProps<{
processInstance: any // 流程实例信息
processDefinition: any // 流程定义信息
userOptions: UserApi.UserVO[]
normalForm: any // 流程表单 formCreate
normalFormApi: any // 流程表单 formCreate Api
writableFields: string[] // 流程表单可以编辑的字段
}>()
@ -547,7 +565,7 @@ const rejectReasonForm = reactive({
reason: ''
})
const rejectReasonRule = reactive<FormRules<typeof rejectReasonForm>>({
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
})
// 抄送表单
@ -568,7 +586,7 @@ const transferForm = reactive({
})
const transferFormRule = reactive<FormRules<typeof transferForm>>({
assigneeUserId: [{ required: true, message: '新审批人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
})
// 委派表单
@ -579,7 +597,7 @@ const delegateForm = reactive({
})
const delegateFormRule = reactive<FormRules<typeof delegateForm>>({
delegateUserId: [{ required: true, message: '接收人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
})
// 加签表单
@ -590,7 +608,7 @@ const addSignForm = reactive({
})
const addSignFormRule = reactive<FormRules<typeof addSignForm>>({
addSignUserIds: [{ required: true, message: '加签处理人不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
})
// 减签表单
@ -601,7 +619,7 @@ const deleteSignForm = reactive({
})
const deleteSignFormRule = reactive<FormRules<typeof deleteSignForm>>({
deleteSignTaskId: [{ required: true, message: '减签人员不能为空', trigger: 'change' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }]
})
// 退回表单
@ -621,7 +639,7 @@ const cancelForm = reactive({
cancelReason: ''
})
const cancelFormRule = reactive<FormRules<typeof cancelForm>>({
cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }],
cancelReason: [{ required: true, message: '取消理由不能为空', trigger: 'blur' }]
})
/** 监听 approveFormFApis实现它对应的 form-create 初始化后,隐藏掉对应的表单提交按钮 */
@ -640,11 +658,11 @@ watch(
const openPopover = async (type: string) => {
if (type === 'approve') {
// 校验流程表单
const valid = await validateNormalForm();
if (!valid) {
const valid = await validateNormalForm()
if (!valid) {
message.warning('表单校验不通过,请先完善表单!!')
return;
}
return
}
}
if (type === 'return') {
// 获取退回节点
@ -665,7 +683,7 @@ const openPopover = async (type: string) => {
const closePropover = (type: string, formRef: FormInstance | undefined) => {
if (formRef) {
formRef.resetFields()
}
}
popOverVisible.value[type] = false
}
@ -677,8 +695,8 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
if (!formRef) return
await formRef.validate()
if (pass) {
// 获取修改的流程变量, 暂时只支持流程表单
const variables = getUpdatedProcessInstanceVaiables();
// 获取修改的流程变量, 暂时只支持流程表单
const variables = getUpdatedProcessInstanceVariables()
// 审批通过数据
const data = {
id: runningTask.value.id,
@ -701,10 +719,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
popOverVisible.value.approve = false
message.success('审批通过成功')
} else {
// 审批不通过数据
const data = {
// 审批不通过数据
const data = {
id: runningTask.value.id,
reason: rejectReasonForm.reason,
reason: rejectReasonForm.reason
}
await TaskApi.rejectTask(data)
popOverVisible.value.reject = false
@ -730,7 +748,7 @@ const handleCopy = async () => {
const data = {
id: runningTask.value.id,
reason: copyForm.copyReason,
copyUserIds:copyForm.copyUserIds
copyUserIds: copyForm.copyUserIds
}
await TaskApi.copyTask(data)
copyFormRef.value.resetFields()
@ -769,7 +787,6 @@ const handleTransfer = async () => {
const handleDelegate = async () => {
formLoading.value = true
try {
// 1.1 校验表单
if (!delegateFormRef.value) return
await delegateFormRef.value.validate()
@ -966,24 +983,25 @@ const validateNormalForm = async () => {
try {
await props.normalFormApi?.validate()
} catch {
valid = false;
valid = false
}
return valid;
return valid
} else {
return true;
return true
}
}
/** 从可以编辑的流程表单字段,获取需要修改的流程实例的变量 */
const getUpdatedProcessInstanceVaiables = ()=> {
const getUpdatedProcessInstanceVariables = () => {
const variables = {}
props.writableFields.forEach( (field) => {
const fieldValue = props.normalFormApi.getValue(field)
variables[field] = fieldValue;
props.writableFields.forEach((field) => {
variables[field] = props.normalFormApi.getValue(field)
})
return variables
}
const handleSignFinish = (url) => {
/** 处理签名完成 */
const handleSignFinish = (url: string) => {
approveReasonForm.sign = url
approveSignFormRef.value.validate('change')
}

View File

@ -128,9 +128,11 @@
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
>
签名:
<el-image class="w-90px h-40px ml-5px"
:src="task.sign"
:preview-src-list="[task.sign]"/>
<el-image
class="w-90px h-40px ml-5px"
:src="task.sign"
:preview-src-list="[task.sign]"
/>
</div>
</teleport>
</div>

View File

@ -1,11 +1,8 @@
<template>
<el-dialog
v-model="signDialogVisible"
title="签名"
width="935"
>
<el-dialog v-model="signDialogVisible" title="签名" width="935">
<div class="position-relative">
<Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px"/>
<Vue3Signature class="b b-solid b-gray" ref="signature" w="900px" h="400px" />
<!-- @lesan建议改成 unocss -->
<el-button
style="position: absolute; bottom: 20px; right: 10px"
type="primary"
@ -13,23 +10,21 @@
size="small"
@click="signature.clear()"
>
<Icon icon="ep:delete" class="mr-5px"/>
<Icon icon="ep:delete" class="mr-5px" />
清除
</el-button>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="signDialogVisible = false">取消</el-button>
<el-button type="primary" @click="submit">
提交
</el-button>
<el-button type="primary" @click="submit"> 提交 </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import Vue3Signature from "vue3-signature"
import Vue3Signature from 'vue3-signature'
import * as FileApi from '@/api/infra/file'
const message = useMessage() // 消息弹窗
@ -39,25 +34,28 @@ const signature = ref()
const open = async () => {
signDialogVisible.value = true
}
defineExpose({open})
defineExpose({ open })
const emits = defineEmits(['success'])
const submit = async () => {
message.success('签名上传中请稍等。。。')
const res = await FileApi.updateFile({file: base64ToFile(signature.value.save('image/png'), '签名')})
const res = await FileApi.updateFile({
file: base64ToFile(signature.value.save('image/png'), '签名')
})
emits('success', res.data)
signDialogVisible.value = false
}
// TODO @lesan这个要不抽到 download.js 里,让这个组件更简洁干净?
const base64ToFile = (base64, fileName) => {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
let data = base64.split(',');
let data = base64.split(',')
// 利用正则表达式 从前缀中获取图片的类型信息image/png、image/jpeg、image/webp等
let type = data[0].match(/:(.*?);/)[1];
let type = data[0].match(/:(.*?);/)[1]
// 从图片的类型信息中 获取具体的文件格式后缀png、jpeg、webp
let suffix = type.split('/')[1];
let suffix = type.split('/')[1]
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
const bstr = window.atob(data[1]);
const bstr = window.atob(data[1])
// 获取解码结果字符串的长度
let n = bstr.length
// 根据解码结果字符串的长度创建一个等长的整形数字数组
@ -74,11 +72,8 @@ const base64ToFile = (base64, fileName) => {
type: type
})
// 将File文件对象返回给方法的调用者
return file;
return file
}
</script>
<style scoped>
</style>
<style scoped></style>