commit
e8e357b8a2
|
@ -36,7 +36,7 @@ export type ApprovalTaskInfo = {
|
|||
assigneeUser: User
|
||||
status: number
|
||||
reason: string
|
||||
sign: string // TODO @lesan:字段改成 signPicUrl 签名照片。只有 sign 感觉是签名文本哈。
|
||||
signPicUrl: string
|
||||
}
|
||||
|
||||
// 审批节点信息
|
||||
|
|
|
@ -86,7 +86,7 @@ const currentNode = useWatchNode(props)
|
|||
// 节点名称
|
||||
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
|
||||
const routerGroups = ref<RouterCondition[]>([])
|
||||
const nodeOptions = ref()
|
||||
const nodeOptions = ref<any>([])
|
||||
const conditionRef = ref([])
|
||||
|
||||
/** 保存配置 */
|
||||
|
@ -94,7 +94,7 @@ const saveConfig = async () => {
|
|||
// 校验表单
|
||||
let valid = true
|
||||
for (const item of conditionRef.value) {
|
||||
if (!(await item.validate())) {
|
||||
if (item && !(await item.validate())) {
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ const saveConfig = async () => {
|
|||
}
|
||||
// 显示路由分支节点配置, 由父组件传过来
|
||||
const showRouteNodeConfig = (node: SimpleFlowNode) => {
|
||||
getRouterNode()
|
||||
getRouterNode(processNodeTree?.value)
|
||||
routerGroups.value = []
|
||||
nodeName.value = node.name
|
||||
if (node.routerGroups) {
|
||||
|
@ -172,15 +172,14 @@ const deleteRouterGroup = (index: number) => {
|
|||
routerGroups.value.splice(index, 1)
|
||||
}
|
||||
|
||||
const getRouterNode = () => {
|
||||
// TODO @lesan 还需要满足以下要求
|
||||
// 递归获取所有节点
|
||||
const getRouterNode = (node) => {
|
||||
// TODO 最好还需要满足以下要求
|
||||
// 并行分支、包容分支内部节点不能跳转到外部节点
|
||||
// 条件分支节点可以向上跳转到外部节点
|
||||
let node = processNodeTree?.value
|
||||
nodeOptions.value = []
|
||||
while (true) {
|
||||
if (!node) break
|
||||
if (node.type !== NodeType.ROUTER_BRANCH_NODE) {
|
||||
if (node.type !== NodeType.ROUTER_BRANCH_NODE && node.type !== NodeType.CONDITION_NODE) {
|
||||
nodeOptions.value.push({
|
||||
label: node.name,
|
||||
value: node.id
|
||||
|
@ -189,6 +188,11 @@ const getRouterNode = () => {
|
|||
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
|
||||
break
|
||||
}
|
||||
if (node.conditionNodes && node.conditionNodes.length) {
|
||||
node.conditionNodes.forEach((item) => {
|
||||
getRouterNode(item)
|
||||
})
|
||||
}
|
||||
node = node.childNode
|
||||
}
|
||||
}
|
||||
|
|
|
@ -440,217 +440,8 @@
|
|||
</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">
|
||||
<el-divider content-position="left">
|
||||
<el-text tag="b" size="large">{{ listener.name }}</el-text>
|
||||
</el-divider>
|
||||
<el-form-item>
|
||||
<el-switch
|
||||
v-model="configForm[`task${listener.type}ListenerEnable`]"
|
||||
active-text="开启"
|
||||
inactive-text="关闭"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
|
||||
<el-form-item>
|
||||
<el-alert
|
||||
title="仅支持 POST 请求,以请求体方式接收参数"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="请求地址"
|
||||
:prop="`task${listener.type}ListenerPath`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请求地址不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求头">
|
||||
<div
|
||||
class="flex pt-2"
|
||||
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
|
||||
:key="index"
|
||||
>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerHeader.${index}.key`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数名不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input class="w-160px" v-model="item.key" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-select class="w-100px!" v-model="item.type">
|
||||
<el-option
|
||||
v-for="types in LISTENER_MAP_TYPES"
|
||||
:key="types.value"
|
||||
:label="types.label"
|
||||
:value="types.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerHeader.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
|
||||
class="w-160px"
|
||||
v-model="item.value"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerHeader.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'change'
|
||||
}"
|
||||
>
|
||||
<el-select
|
||||
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
|
||||
class="w-160px!"
|
||||
v-model="item.value"
|
||||
>
|
||||
<el-option
|
||||
v-for="(field, fIdx) in formFieldOptions"
|
||||
:key="fIdx"
|
||||
:label="field.title"
|
||||
:value="field.field"
|
||||
:disabled="!field.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-1 flex items-center">
|
||||
<Icon
|
||||
icon="ep:delete"
|
||||
:size="18"
|
||||
@click="
|
||||
deleteTaskListenerParam(
|
||||
configForm[`task${listener.type}ListenerHeader`],
|
||||
index
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求体">
|
||||
<div
|
||||
class="flex pt-2"
|
||||
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
|
||||
:key="index"
|
||||
>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerBody.${index}.key`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数名不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input class="w-160px" v-model="item.key" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-select class="w-100px!" v-model="item.type">
|
||||
<el-option
|
||||
v-for="types in LISTENER_MAP_TYPES"
|
||||
:key="types.value"
|
||||
:label="types.label"
|
||||
:value="types.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerBody.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
|
||||
class="w-160px"
|
||||
v-model="item.value"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerBody.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'change'
|
||||
}"
|
||||
>
|
||||
<el-select
|
||||
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
|
||||
class="w-160px!"
|
||||
v-model="item.value"
|
||||
>
|
||||
<el-option
|
||||
v-for="(field, fIdx) in formFieldOptions"
|
||||
:key="fIdx"
|
||||
:label="field.title"
|
||||
:value="field.field"
|
||||
:disabled="!field.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-1 flex items-center">
|
||||
<Icon
|
||||
icon="ep:delete"
|
||||
:size="18"
|
||||
@click="
|
||||
deleteTaskListenerParam(
|
||||
configForm[`task${listener.type}ListenerBody`],
|
||||
index
|
||||
)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
<UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<template #footer>
|
||||
|
@ -687,9 +478,7 @@ import {
|
|||
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||
AssignEmptyHandlerType,
|
||||
FieldPermissionType,
|
||||
ProcessVariableEnum,
|
||||
LISTENER_MAP_TYPES,
|
||||
ListenerParamTypeEnum
|
||||
ProcessVariableEnum
|
||||
} from '../consts'
|
||||
|
||||
import {
|
||||
|
@ -703,6 +492,7 @@ import {
|
|||
import { defaultProps } from '@/utils/tree'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { convertTimeUnit, getApproveTypeText } from '../utils'
|
||||
import UserTaskListener from './components/UserTaskListener.vue'
|
||||
defineOptions({
|
||||
name: 'UserTaskNodeConfig'
|
||||
})
|
||||
|
@ -780,21 +570,6 @@ const formRules = reactive({
|
|||
assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
|
||||
assignStartUserHandlerType: [{ required: true }]
|
||||
})
|
||||
// 监听器数组
|
||||
const taskListener = ref([
|
||||
{
|
||||
name: '创建任务',
|
||||
type: 'Create'
|
||||
},
|
||||
{
|
||||
name: '指派任务执行人员',
|
||||
type: 'Assign'
|
||||
},
|
||||
{
|
||||
name: '完成任务',
|
||||
type: 'Complete'
|
||||
}
|
||||
])
|
||||
|
||||
const {
|
||||
configForm: tempConfigForm,
|
||||
|
@ -843,7 +618,7 @@ const {
|
|||
cTimeoutMaxRemindCount
|
||||
} = useTimeoutHandler()
|
||||
|
||||
const listenerFormRef = ref()
|
||||
const userTaskListenerRef = ref()
|
||||
|
||||
// 保存配置
|
||||
const saveConfig = async () => {
|
||||
|
@ -860,8 +635,8 @@ const saveConfig = async () => {
|
|||
}
|
||||
|
||||
if (!formRef) return false
|
||||
if (!listenerFormRef) return false
|
||||
const valid = (await formRef.value.validate()) && (await listenerFormRef.value.validate())
|
||||
if (!userTaskListenerRef) return false
|
||||
const valid = (await formRef.value.validate()) && (await userTaskListenerRef.value.validate())
|
||||
if (!valid) return false
|
||||
const showText = getShowText()
|
||||
if (!showText) return false
|
||||
|
@ -1104,17 +879,6 @@ function useTimeoutHandler() {
|
|||
cTimeoutMaxRemindCount
|
||||
}
|
||||
}
|
||||
|
||||
const addTaskListenerParam = (arr) => {
|
||||
arr.push({
|
||||
key: '',
|
||||
type: 1,
|
||||
value: ''
|
||||
})
|
||||
}
|
||||
const deleteTaskListenerParam = (arr, index) => {
|
||||
arr.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
<template>
|
||||
<el-form ref="listenerFormRef" :model="configForm" label-position="top">
|
||||
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
|
||||
<el-divider content-position="left">
|
||||
<el-text tag="b" size="large">{{ listener.name }}</el-text>
|
||||
</el-divider>
|
||||
<el-form-item>
|
||||
<el-switch
|
||||
v-model="configForm[`task${listener.type}ListenerEnable`]"
|
||||
active-text="开启"
|
||||
inactive-text="关闭"
|
||||
/>
|
||||
</el-form-item>
|
||||
<div v-if="configForm[`task${listener.type}ListenerEnable`]">
|
||||
<el-form-item>
|
||||
<el-alert
|
||||
title="仅支持 POST 请求,以请求体方式接收参数"
|
||||
type="warning"
|
||||
show-icon
|
||||
:closable="false"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="请求地址"
|
||||
:prop="`task${listener.type}ListenerPath`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '请求地址不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input v-model="configForm[`task${listener.type}ListenerPath`]" />
|
||||
</el-form-item>
|
||||
<el-form-item label="请求头">
|
||||
<div
|
||||
class="flex pt-2"
|
||||
v-for="(item, index) in configForm[`task${listener.type}ListenerHeader`]"
|
||||
:key="index"
|
||||
>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerHeader.${index}.key`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数名不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input class="w-160px" v-model="item.key" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-select class="w-100px!" v-model="item.type">
|
||||
<el-option
|
||||
v-for="types in LISTENER_MAP_TYPES"
|
||||
:key="types.value"
|
||||
:label="types.label"
|
||||
:value="types.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerHeader.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
|
||||
class="w-160px"
|
||||
v-model="item.value"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerHeader.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'change'
|
||||
}"
|
||||
>
|
||||
<el-select
|
||||
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
|
||||
class="w-160px!"
|
||||
v-model="item.value"
|
||||
>
|
||||
<el-option
|
||||
v-for="(field, fIdx) in formFieldOptions"
|
||||
:key="fIdx"
|
||||
:label="field.title"
|
||||
:value="field.field"
|
||||
:disabled="!field.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-1 flex items-center">
|
||||
<Icon
|
||||
icon="ep:delete"
|
||||
:size="18"
|
||||
@click="
|
||||
deleteTaskListenerParam(configForm[`task${listener.type}ListenerHeader`], index)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerHeader`])"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求体">
|
||||
<div
|
||||
class="flex pt-2"
|
||||
v-for="(item, index) in configForm[`task${listener.type}ListenerBody`]"
|
||||
:key="index"
|
||||
>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerBody.${index}.key`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数名不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input class="w-160px" v-model="item.key" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-select class="w-100px!" v-model="item.type">
|
||||
<el-option
|
||||
v-for="types in LISTENER_MAP_TYPES"
|
||||
:key="types.value"
|
||||
:label="types.label"
|
||||
:value="types.value"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="mr-2">
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerBody.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'blur'
|
||||
}"
|
||||
>
|
||||
<el-input
|
||||
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
|
||||
class="w-160px"
|
||||
v-model="item.value"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:prop="`task${listener.type}ListenerBody.${index}.value`"
|
||||
:rules="{
|
||||
required: true,
|
||||
message: '参数值不能为空',
|
||||
trigger: 'change'
|
||||
}"
|
||||
>
|
||||
<el-select
|
||||
v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
|
||||
class="w-160px!"
|
||||
v-model="item.value"
|
||||
>
|
||||
<el-option
|
||||
v-for="(field, fIdx) in formFieldOptions"
|
||||
:key="fIdx"
|
||||
:label="field.title"
|
||||
:value="field.field"
|
||||
:disabled="!field.required"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="mr-1 flex items-center">
|
||||
<Icon
|
||||
icon="ep:delete"
|
||||
:size="18"
|
||||
@click="
|
||||
deleteTaskListenerParam(configForm[`task${listener.type}ListenerBody`], index)
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
@click="addTaskListenerParam(configForm[`task${listener.type}ListenerBody`])"
|
||||
>
|
||||
<Icon icon="ep:plus" class="mr-5px" />添加一行
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { LISTENER_MAP_TYPES, ListenerParamTypeEnum } from '../../consts'
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
formFieldOptions: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
const listenerFormRef = ref()
|
||||
const configForm = computed({
|
||||
get() {
|
||||
return props.modelValue
|
||||
},
|
||||
set(newValue) {
|
||||
emit('update:modelValue', newValue)
|
||||
}
|
||||
})
|
||||
const taskListener = ref([
|
||||
{
|
||||
name: '创建任务',
|
||||
type: 'Create'
|
||||
},
|
||||
{
|
||||
name: '指派任务执行人员',
|
||||
type: 'Assign'
|
||||
},
|
||||
{
|
||||
name: '完成任务',
|
||||
type: 'Complete'
|
||||
}
|
||||
])
|
||||
|
||||
const addTaskListenerParam = (arr) => {
|
||||
arr.push({
|
||||
key: '',
|
||||
type: 1,
|
||||
value: ''
|
||||
})
|
||||
}
|
||||
const deleteTaskListenerParam = (arr, index) => {
|
||||
arr.splice(index, 1)
|
||||
}
|
||||
|
||||
const validate = async () => {
|
||||
if (!listenerFormRef) return false
|
||||
return await listenerFormRef.value.validate()
|
||||
}
|
||||
|
||||
defineExpose({ validate })
|
||||
</script>
|
|
@ -1438,6 +1438,20 @@
|
|||
"isBody": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "SignEnable",
|
||||
"superClass": ["Element"],
|
||||
"meta": {
|
||||
"allowedIn": ["bpmn:UserTask"]
|
||||
},
|
||||
"properties": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "Boolean",
|
||||
"isBody": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"emumerations": []
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="process-panel__container" :style="{ width: `${width}px` }">
|
||||
<div class="process-panel__container" :style="{ width: `${width}px`, maxHeight: '600px' }">
|
||||
<el-collapse v-model="activeTab" v-if="isReady">
|
||||
<el-collapse-item name="base">
|
||||
<!-- class="panel-tab__title" -->
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
4. 操作按钮
|
||||
5. 字段权限
|
||||
6. 审批类型
|
||||
7. 是否需要签名
|
||||
-->
|
||||
<template>
|
||||
<div>
|
||||
|
@ -161,6 +162,11 @@
|
|||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">是否需要签名</el-divider>
|
||||
<el-form-item prop="signEnable">
|
||||
<el-switch v-model="signEnable.value" active-text="是" inactive-text="否" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -218,6 +224,9 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
|
|||
// 审批类型
|
||||
const approveType = ref({ value: ApproveType.USER })
|
||||
|
||||
// 是否需要签名
|
||||
const signEnable = ref({ value: false })
|
||||
|
||||
const elExtensionElements = ref()
|
||||
const otherExtensions = ref()
|
||||
const bpmnElement = ref()
|
||||
|
@ -325,6 +334,11 @@ const resetCustomConfigList = () => {
|
|||
ex.$type !== `${prefix}:ApproveType`
|
||||
) ?? []
|
||||
|
||||
// 是否需要签名
|
||||
signEnable.value =
|
||||
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
|
||||
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
|
||||
|
||||
// 更新元素扩展属性,避免后续报错
|
||||
updateElementExtensions()
|
||||
}
|
||||
|
@ -373,7 +387,8 @@ const updateElementExtensions = () => {
|
|||
assignEmptyUserIdsEl.value,
|
||||
approveType.value,
|
||||
...buttonsSettingEl.value,
|
||||
...fieldsPermissionEl.value
|
||||
...fieldsPermissionEl.value,
|
||||
signEnable.value
|
||||
]
|
||||
})
|
||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||
|
|
|
@ -65,6 +65,33 @@ const download = {
|
|||
a.download = 'image.png'
|
||||
a.click()
|
||||
}
|
||||
},
|
||||
base64ToFile: (base64, fileName) => {
|
||||
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
|
||||
const data = base64.split(',')
|
||||
// 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
|
||||
const type = data[0].match(/:(.*?);/)[1]
|
||||
// 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
|
||||
const suffix = type.split('/')[1]
|
||||
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
|
||||
const bstr = window.atob(data[1])
|
||||
// 获取解码结果字符串的长度
|
||||
let n = bstr.length
|
||||
// 根据解码结果字符串的长度创建一个等长的整形数字数组
|
||||
// 但在创建时 所有元素初始值都为 0
|
||||
const u8arr = new Uint8Array(n)
|
||||
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
|
||||
while (n--) {
|
||||
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
|
||||
u8arr[n] = bstr.charCodeAt(n)
|
||||
}
|
||||
// 利用构造函数创建File文件对象
|
||||
// new File(bits, name, options)
|
||||
const file = new File([u8arr], `${fileName}.${suffix}`, {
|
||||
type: type
|
||||
})
|
||||
// 将File文件对象返回给方法的调用者
|
||||
return file
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,15 +47,15 @@
|
|||
<el-form-item
|
||||
v-if="runningTask.signEnable"
|
||||
label="签名"
|
||||
prop="sign"
|
||||
prop="signPicUrl"
|
||||
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]"
|
||||
v-if="approveReasonForm.signPicUrl"
|
||||
:src="approveReasonForm.signPicUrl"
|
||||
:preview-src-list="[approveReasonForm.signPicUrl]"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
|
@ -553,11 +553,11 @@ const signRef = ref()
|
|||
const approveSignFormRef = ref()
|
||||
const approveReasonForm = reactive({
|
||||
reason: '',
|
||||
sign: ''
|
||||
signPicUrl: ''
|
||||
})
|
||||
const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
|
||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||
sign: [{ required: true, message: '签名不能为空', trigger: 'change' }]
|
||||
signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }]
|
||||
})
|
||||
// 拒绝表单
|
||||
const rejectFormRef = ref<FormInstance>()
|
||||
|
@ -705,7 +705,7 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||
}
|
||||
// 签名
|
||||
if (runningTask.value.signEnable) {
|
||||
data.sign = approveReasonForm.sign
|
||||
data.signPicUrl = approveReasonForm.signPicUrl
|
||||
}
|
||||
// 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||
// TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
|
||||
|
@ -1002,7 +1002,7 @@ const getUpdatedProcessInstanceVariables = () => {
|
|||
|
||||
/** 处理签名完成 */
|
||||
const handleSignFinish = (url: string) => {
|
||||
approveReasonForm.sign = url
|
||||
approveReasonForm.signPicUrl = url
|
||||
approveSignFormRef.value.validate('change')
|
||||
}
|
||||
|
||||
|
|
|
@ -124,14 +124,14 @@
|
|||
审批意见:{{ task.reason }}
|
||||
</div>
|
||||
<div
|
||||
v-if="task.sign && activity.nodeType === NodeType.USER_TASK_NODE"
|
||||
v-if="task.signPicUrl && activity.nodeType === NodeType.USER_TASK_NODE"
|
||||
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]"
|
||||
:src="task.signPicUrl"
|
||||
:preview-src-list="[task.signPicUrl]"
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
<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" />
|
||||
<!-- @lesan:建议改成 unocss 哈 -->
|
||||
<el-button
|
||||
style="position: absolute; bottom: 20px; right: 10px"
|
||||
class="pos-absolute bottom-20px right-10px"
|
||||
type="primary"
|
||||
text
|
||||
size="small"
|
||||
|
@ -26,6 +25,7 @@
|
|||
<script setup lang="ts">
|
||||
import Vue3Signature from 'vue3-signature'
|
||||
import * as FileApi from '@/api/infra/file'
|
||||
import download from '@/utils/download'
|
||||
|
||||
const message = useMessage() // 消息弹窗
|
||||
const signDialogVisible = ref(false)
|
||||
|
@ -40,40 +40,11 @@ const emits = defineEmits(['success'])
|
|||
const submit = async () => {
|
||||
message.success('签名上传中请稍等。。。')
|
||||
const res = await FileApi.updateFile({
|
||||
file: base64ToFile(signature.value.save('image/png'), '签名')
|
||||
file: download.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(',')
|
||||
// 利用正则表达式 从前缀中获取图片的类型信息(image/png、image/jpeg、image/webp等)
|
||||
let type = data[0].match(/:(.*?);/)[1]
|
||||
// 从图片的类型信息中 获取具体的文件格式后缀(png、jpeg、webp)
|
||||
let suffix = type.split('/')[1]
|
||||
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
|
||||
const bstr = window.atob(data[1])
|
||||
// 获取解码结果字符串的长度
|
||||
let n = bstr.length
|
||||
// 根据解码结果字符串的长度创建一个等长的整形数字数组
|
||||
// 但在创建时 所有元素初始值都为 0
|
||||
const u8arr = new Uint8Array(n)
|
||||
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
|
||||
while (n--) {
|
||||
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
|
||||
u8arr[n] = bstr.charCodeAt(n)
|
||||
}
|
||||
// 利用构造函数创建File文件对象
|
||||
// new File(bits, name, options)
|
||||
const file = new File([u8arr], `${fileName}.${suffix}`, {
|
||||
type: type
|
||||
})
|
||||
// 将File文件对象返回给方法的调用者
|
||||
return file
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
Loading…
Reference in New Issue