mirror of
https://gitee.com/myxzgzs/boyue-ui-admin-vue3
synced 2025-08-09 08:52:41 +08:00
commit
e8e357b8a2
@ -36,7 +36,7 @@ export type ApprovalTaskInfo = {
|
|||||||
assigneeUser: User
|
assigneeUser: User
|
||||||
status: number
|
status: number
|
||||||
reason: string
|
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 { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
|
||||||
const routerGroups = ref<RouterCondition[]>([])
|
const routerGroups = ref<RouterCondition[]>([])
|
||||||
const nodeOptions = ref()
|
const nodeOptions = ref<any>([])
|
||||||
const conditionRef = ref([])
|
const conditionRef = ref([])
|
||||||
|
|
||||||
/** 保存配置 */
|
/** 保存配置 */
|
||||||
@ -94,7 +94,7 @@ const saveConfig = async () => {
|
|||||||
// 校验表单
|
// 校验表单
|
||||||
let valid = true
|
let valid = true
|
||||||
for (const item of conditionRef.value) {
|
for (const item of conditionRef.value) {
|
||||||
if (!(await item.validate())) {
|
if (item && !(await item.validate())) {
|
||||||
valid = false
|
valid = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,7 +109,7 @@ const saveConfig = async () => {
|
|||||||
}
|
}
|
||||||
// 显示路由分支节点配置, 由父组件传过来
|
// 显示路由分支节点配置, 由父组件传过来
|
||||||
const showRouteNodeConfig = (node: SimpleFlowNode) => {
|
const showRouteNodeConfig = (node: SimpleFlowNode) => {
|
||||||
getRouterNode()
|
getRouterNode(processNodeTree?.value)
|
||||||
routerGroups.value = []
|
routerGroups.value = []
|
||||||
nodeName.value = node.name
|
nodeName.value = node.name
|
||||||
if (node.routerGroups) {
|
if (node.routerGroups) {
|
||||||
@ -172,15 +172,14 @@ const deleteRouterGroup = (index: number) => {
|
|||||||
routerGroups.value.splice(index, 1)
|
routerGroups.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRouterNode = () => {
|
// 递归获取所有节点
|
||||||
// TODO @lesan 还需要满足以下要求
|
const getRouterNode = (node) => {
|
||||||
|
// TODO 最好还需要满足以下要求
|
||||||
// 并行分支、包容分支内部节点不能跳转到外部节点
|
// 并行分支、包容分支内部节点不能跳转到外部节点
|
||||||
// 条件分支节点可以向上跳转到外部节点
|
// 条件分支节点可以向上跳转到外部节点
|
||||||
let node = processNodeTree?.value
|
|
||||||
nodeOptions.value = []
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!node) break
|
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({
|
nodeOptions.value.push({
|
||||||
label: node.name,
|
label: node.name,
|
||||||
value: node.id
|
value: node.id
|
||||||
@ -189,6 +188,11 @@ const getRouterNode = () => {
|
|||||||
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
|
if (!node.childNode || node.type === NodeType.END_EVENT_NODE) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if (node.conditionNodes && node.conditionNodes.length) {
|
||||||
|
node.conditionNodes.forEach((item) => {
|
||||||
|
getRouterNode(item)
|
||||||
|
})
|
||||||
|
}
|
||||||
node = node.childNode
|
node = node.childNode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -440,217 +440,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<!-- TODO @lesan:要不抽成 Listener 小组件?类似 Condition.vue -->
|
|
||||||
<el-tab-pane label="监听器" name="listener">
|
<el-tab-pane label="监听器" name="listener">
|
||||||
<el-form ref="listenerFormRef" :model="configForm" label-position="top">
|
<UserTaskListener ref="userTaskListenerRef" v-model="configForm" :form-field-options="formFieldOptions" />
|
||||||
<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>
|
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@ -687,9 +478,7 @@ import {
|
|||||||
ASSIGN_EMPTY_HANDLER_TYPES,
|
ASSIGN_EMPTY_HANDLER_TYPES,
|
||||||
AssignEmptyHandlerType,
|
AssignEmptyHandlerType,
|
||||||
FieldPermissionType,
|
FieldPermissionType,
|
||||||
ProcessVariableEnum,
|
ProcessVariableEnum
|
||||||
LISTENER_MAP_TYPES,
|
|
||||||
ListenerParamTypeEnum
|
|
||||||
} from '../consts'
|
} from '../consts'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -703,6 +492,7 @@ import {
|
|||||||
import { defaultProps } from '@/utils/tree'
|
import { defaultProps } from '@/utils/tree'
|
||||||
import { cloneDeep } from 'lodash-es'
|
import { cloneDeep } from 'lodash-es'
|
||||||
import { convertTimeUnit, getApproveTypeText } from '../utils'
|
import { convertTimeUnit, getApproveTypeText } from '../utils'
|
||||||
|
import UserTaskListener from './components/UserTaskListener.vue'
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'UserTaskNodeConfig'
|
name: 'UserTaskNodeConfig'
|
||||||
})
|
})
|
||||||
@ -780,21 +570,6 @@ const formRules = reactive({
|
|||||||
assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
|
assignEmptyHandlerUserIds: [{ required: true, message: '用户不能为空', trigger: 'change' }],
|
||||||
assignStartUserHandlerType: [{ required: true }]
|
assignStartUserHandlerType: [{ required: true }]
|
||||||
})
|
})
|
||||||
// 监听器数组
|
|
||||||
const taskListener = ref([
|
|
||||||
{
|
|
||||||
name: '创建任务',
|
|
||||||
type: 'Create'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '指派任务执行人员',
|
|
||||||
type: 'Assign'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: '完成任务',
|
|
||||||
type: 'Complete'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
configForm: tempConfigForm,
|
configForm: tempConfigForm,
|
||||||
@ -843,7 +618,7 @@ const {
|
|||||||
cTimeoutMaxRemindCount
|
cTimeoutMaxRemindCount
|
||||||
} = useTimeoutHandler()
|
} = useTimeoutHandler()
|
||||||
|
|
||||||
const listenerFormRef = ref()
|
const userTaskListenerRef = ref()
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
const saveConfig = async () => {
|
const saveConfig = async () => {
|
||||||
@ -860,8 +635,8 @@ const saveConfig = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!formRef) return false
|
if (!formRef) return false
|
||||||
if (!listenerFormRef) return false
|
if (!userTaskListenerRef) return false
|
||||||
const valid = (await formRef.value.validate()) && (await listenerFormRef.value.validate())
|
const valid = (await formRef.value.validate()) && (await userTaskListenerRef.value.validate())
|
||||||
if (!valid) return false
|
if (!valid) return false
|
||||||
const showText = getShowText()
|
const showText = getShowText()
|
||||||
if (!showText) return false
|
if (!showText) return false
|
||||||
@ -1104,17 +879,6 @@ function useTimeoutHandler() {
|
|||||||
cTimeoutMaxRemindCount
|
cTimeoutMaxRemindCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addTaskListenerParam = (arr) => {
|
|
||||||
arr.push({
|
|
||||||
key: '',
|
|
||||||
type: 1,
|
|
||||||
value: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const deleteTaskListenerParam = (arr, index) => {
|
|
||||||
arr.splice(index, 1)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<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
|
"isBody": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "SignEnable",
|
||||||
|
"superClass": ["Element"],
|
||||||
|
"meta": {
|
||||||
|
"allowedIn": ["bpmn:UserTask"]
|
||||||
|
},
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"name": "value",
|
||||||
|
"type": "Boolean",
|
||||||
|
"isBody": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"emumerations": []
|
"emumerations": []
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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 v-model="activeTab" v-if="isReady">
|
||||||
<el-collapse-item name="base">
|
<el-collapse-item name="base">
|
||||||
<!-- class="panel-tab__title" -->
|
<!-- class="panel-tab__title" -->
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
4. 操作按钮
|
4. 操作按钮
|
||||||
5. 字段权限
|
5. 字段权限
|
||||||
6. 审批类型
|
6. 审批类型
|
||||||
|
7. 是否需要签名
|
||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
@ -161,6 +162,11 @@
|
|||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -218,6 +224,9 @@ const { formType, fieldsPermissionConfig, getNodeConfigFormFields } = useFormFie
|
|||||||
// 审批类型
|
// 审批类型
|
||||||
const approveType = ref({ value: ApproveType.USER })
|
const approveType = ref({ value: ApproveType.USER })
|
||||||
|
|
||||||
|
// 是否需要签名
|
||||||
|
const signEnable = ref({ value: false })
|
||||||
|
|
||||||
const elExtensionElements = ref()
|
const elExtensionElements = ref()
|
||||||
const otherExtensions = ref()
|
const otherExtensions = ref()
|
||||||
const bpmnElement = ref()
|
const bpmnElement = ref()
|
||||||
@ -325,6 +334,11 @@ const resetCustomConfigList = () => {
|
|||||||
ex.$type !== `${prefix}:ApproveType`
|
ex.$type !== `${prefix}:ApproveType`
|
||||||
) ?? []
|
) ?? []
|
||||||
|
|
||||||
|
// 是否需要签名
|
||||||
|
signEnable.value =
|
||||||
|
elExtensionElements.value.values?.filter((ex) => ex.$type === `${prefix}:SignEnable`)?.[0] ||
|
||||||
|
bpmnInstances().moddle.create(`${prefix}:SignEnable`, { value: false })
|
||||||
|
|
||||||
// 更新元素扩展属性,避免后续报错
|
// 更新元素扩展属性,避免后续报错
|
||||||
updateElementExtensions()
|
updateElementExtensions()
|
||||||
}
|
}
|
||||||
@ -373,7 +387,8 @@ const updateElementExtensions = () => {
|
|||||||
assignEmptyUserIdsEl.value,
|
assignEmptyUserIdsEl.value,
|
||||||
approveType.value,
|
approveType.value,
|
||||||
...buttonsSettingEl.value,
|
...buttonsSettingEl.value,
|
||||||
...fieldsPermissionEl.value
|
...fieldsPermissionEl.value,
|
||||||
|
signEnable.value
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
bpmnInstances().modeling.updateProperties(toRaw(bpmnElement.value), {
|
||||||
|
@ -65,6 +65,33 @@ const download = {
|
|||||||
a.download = 'image.png'
|
a.download = 'image.png'
|
||||||
a.click()
|
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
|
<el-form-item
|
||||||
v-if="runningTask.signEnable"
|
v-if="runningTask.signEnable"
|
||||||
label="签名"
|
label="签名"
|
||||||
prop="sign"
|
prop="signPicUrl"
|
||||||
ref="approveSignFormRef"
|
ref="approveSignFormRef"
|
||||||
>
|
>
|
||||||
<el-button @click="signRef.open()">点击签名</el-button>
|
<el-button @click="signRef.open()">点击签名</el-button>
|
||||||
<el-image
|
<el-image
|
||||||
class="w-90px h-40px ml-5px"
|
class="w-90px h-40px ml-5px"
|
||||||
v-if="approveReasonForm.sign"
|
v-if="approveReasonForm.signPicUrl"
|
||||||
:src="approveReasonForm.sign"
|
:src="approveReasonForm.signPicUrl"
|
||||||
:preview-src-list="[approveReasonForm.sign]"
|
:preview-src-list="[approveReasonForm.signPicUrl]"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
@ -553,11 +553,11 @@ const signRef = ref()
|
|||||||
const approveSignFormRef = ref()
|
const approveSignFormRef = ref()
|
||||||
const approveReasonForm = reactive({
|
const approveReasonForm = reactive({
|
||||||
reason: '',
|
reason: '',
|
||||||
sign: ''
|
signPicUrl: ''
|
||||||
})
|
})
|
||||||
const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
|
const approveReasonRule = reactive<FormRules<typeof approveReasonForm>>({
|
||||||
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
reason: [{ required: true, message: '审批意见不能为空', trigger: 'blur' }],
|
||||||
sign: [{ required: true, message: '签名不能为空', trigger: 'change' }]
|
signPicUrl: [{ required: true, message: '签名不能为空', trigger: 'change' }]
|
||||||
})
|
})
|
||||||
// 拒绝表单
|
// 拒绝表单
|
||||||
const rejectFormRef = ref<FormInstance>()
|
const rejectFormRef = ref<FormInstance>()
|
||||||
@ -705,7 +705,7 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
|
|||||||
}
|
}
|
||||||
// 签名
|
// 签名
|
||||||
if (runningTask.value.signEnable) {
|
if (runningTask.value.signEnable) {
|
||||||
data.sign = approveReasonForm.sign
|
data.signPicUrl = approveReasonForm.signPicUrl
|
||||||
}
|
}
|
||||||
// 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
// 多表单处理,并且有额外的 approveForm 表单,需要校验 + 拼接到 data 表单里提交
|
||||||
// TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
|
// TODO 芋艿 任务有多表单这里要如何处理,会和可编辑的字段冲突
|
||||||
@ -1002,7 +1002,7 @@ const getUpdatedProcessInstanceVariables = () => {
|
|||||||
|
|
||||||
/** 处理签名完成 */
|
/** 处理签名完成 */
|
||||||
const handleSignFinish = (url: string) => {
|
const handleSignFinish = (url: string) => {
|
||||||
approveReasonForm.sign = url
|
approveReasonForm.signPicUrl = url
|
||||||
approveSignFormRef.value.validate('change')
|
approveSignFormRef.value.validate('change')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,14 +124,14 @@
|
|||||||
审批意见:{{ task.reason }}
|
审批意见:{{ task.reason }}
|
||||||
</div>
|
</div>
|
||||||
<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"
|
class="text-#a5a5a5 text-13px mt-1 w-full bg-#f8f8fa p2 rounded-md"
|
||||||
>
|
>
|
||||||
签名:
|
签名:
|
||||||
<el-image
|
<el-image
|
||||||
class="w-90px h-40px ml-5px"
|
class="w-90px h-40px ml-5px"
|
||||||
:src="task.sign"
|
:src="task.signPicUrl"
|
||||||
:preview-src-list="[task.sign]"
|
:preview-src-list="[task.signPicUrl]"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</teleport>
|
</teleport>
|
||||||
|
@ -2,9 +2,8 @@
|
|||||||
<el-dialog v-model="signDialogVisible" title="签名" width="935">
|
<el-dialog v-model="signDialogVisible" title="签名" width="935">
|
||||||
<div class="position-relative">
|
<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
|
<el-button
|
||||||
style="position: absolute; bottom: 20px; right: 10px"
|
class="pos-absolute bottom-20px right-10px"
|
||||||
type="primary"
|
type="primary"
|
||||||
text
|
text
|
||||||
size="small"
|
size="small"
|
||||||
@ -26,6 +25,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Vue3Signature from 'vue3-signature'
|
import Vue3Signature from 'vue3-signature'
|
||||||
import * as FileApi from '@/api/infra/file'
|
import * as FileApi from '@/api/infra/file'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const signDialogVisible = ref(false)
|
const signDialogVisible = ref(false)
|
||||||
@ -40,40 +40,11 @@ const emits = defineEmits(['success'])
|
|||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
message.success('签名上传中请稍等。。。')
|
message.success('签名上传中请稍等。。。')
|
||||||
const res = await FileApi.updateFile({
|
const res = await FileApi.updateFile({
|
||||||
file: base64ToFile(signature.value.save('image/png'), '签名')
|
file: download.base64ToFile(signature.value.save('image/png'), '签名')
|
||||||
})
|
})
|
||||||
emits('success', res.data)
|
emits('success', res.data)
|
||||||
signDialogVisible.value = false
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user