!655 Simple设计器完善及优化

Merge pull request !655 from Lesan/feature/bpm-n
This commit is contained in:
芋道源码 2025-01-15 13:14:58 +00:00 committed by Gitee
commit 753e44ccd0
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
13 changed files with 338 additions and 315 deletions

View File

@ -36,6 +36,7 @@ export type ApprovalTaskInfo = {
assigneeUser: User assigneeUser: User
status: number status: number
reason: string reason: string
sign: string
} }
// 审批节点信息 // 审批节点信息

View File

@ -46,7 +46,7 @@
</div> </div>
<div class="handler-item-text">延迟器</div> <div class="handler-item-text">延迟器</div>
</div> </div>
<div class="handler-item" @click="addNode(NodeType.ROUTE_BRANCH_NODE)"> <div class="handler-item" @click="addNode(NodeType.ROUTER_BRANCH_NODE)">
<!-- TODO @芋艿 需要更换一下iconfont的图标 --> <!-- TODO @芋艿 需要更换一下iconfont的图标 -->
<div class="handler-item-icon copy"> <div class="handler-item-icon copy">
<span class="iconfont icon-size icon-copy"></span> <span class="iconfont icon-size icon-copy"></span>
@ -67,12 +67,13 @@ import {
ApproveMethodType, ApproveMethodType,
AssignEmptyHandlerType, AssignEmptyHandlerType,
AssignStartUserHandlerType, AssignStartUserHandlerType,
ConditionType,
NODE_DEFAULT_NAME, NODE_DEFAULT_NAME,
NodeType, NodeType,
RejectHandlerType, RejectHandlerType,
SimpleFlowNode SimpleFlowNode
} from './consts' } from './consts'
import { generateUUID } from '@/utils' import {generateUUID} from '@/utils'
defineOptions({ defineOptions({
name: 'NodeHandler' name: 'NodeHandler'
@ -163,7 +164,7 @@ const addNode = (type: number) => {
showText: '', showText: '',
type: NodeType.CONDITION_NODE, type: NodeType.CONDITION_NODE,
childNode: undefined, childNode: undefined,
conditionType: 1, conditionType: ConditionType.RULE,
defaultFlow: false defaultFlow: false
}, },
{ {
@ -241,14 +242,13 @@ const addNode = (type: number) => {
} }
emits('update:childNode', data) emits('update:childNode', data)
} }
if (type === NodeType.ROUTE_BRANCH_NODE) { if (type === NodeType.ROUTER_BRANCH_NODE) {
const data: SimpleFlowNode = { const data: SimpleFlowNode = {
id: 'GateWay_' + generateUUID(), id: 'GateWay_' + generateUUID(),
name: NODE_DEFAULT_NAME.get(NodeType.ROUTE_BRANCH_NODE) as string, name: NODE_DEFAULT_NAME.get(NodeType.ROUTER_BRANCH_NODE) as string,
showText: '', showText: '',
type: NodeType.ROUTE_BRANCH_NODE, type: NodeType.ROUTER_BRANCH_NODE,
childNode: props.childNode, childNode: props.childNode
defaultFlowId: 'Flow_' + generateUUID()
} }
emits('update:childNode', data) emits('update:childNode', data)
} }

View File

@ -45,8 +45,8 @@
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
/> />
<!-- 路由分支节点 --> <!-- 路由分支节点 -->
<RouteNode <RouterNode
v-if="currentNode && currentNode.type === NodeType.ROUTE_BRANCH_NODE" v-if="currentNode && currentNode.type === NodeType.ROUTER_BRANCH_NODE"
:flow-node="currentNode" :flow-node="currentNode"
@update:flow-node="handleModelValueUpdate" @update:flow-node="handleModelValueUpdate"
/> />
@ -73,7 +73,7 @@ import ExclusiveNode from './nodes/ExclusiveNode.vue'
import ParallelNode from './nodes/ParallelNode.vue' import ParallelNode from './nodes/ParallelNode.vue'
import InclusiveNode from './nodes/InclusiveNode.vue' import InclusiveNode from './nodes/InclusiveNode.vue'
import DelayTimerNode from './nodes/DelayTimerNode.vue' import DelayTimerNode from './nodes/DelayTimerNode.vue'
import RouteNode from './nodes/RouteNode.vue' import RouterNode from './nodes/RouterNode.vue'
import { SimpleFlowNode, NodeType } from './consts' import { SimpleFlowNode, NodeType } from './consts'
import { useWatchNode } from './node' import { useWatchNode } from './node'
defineOptions({ defineOptions({

View File

@ -48,7 +48,7 @@ export enum NodeType {
/** /**
* *
*/ */
ROUTE_BRANCH_NODE = 54 ROUTER_BRANCH_NODE = 54
} }
export enum NodeId { export enum NodeId {
@ -116,7 +116,7 @@ export interface SimpleFlowNode {
// 延迟设置 // 延迟设置
delaySetting?: DelaySetting delaySetting?: DelaySetting
// 路由分支 // 路由分支
routerGroups?: RouteCondition[] routerGroups?: RouterCondition[]
defaultFlowId?: string defaultFlowId?: string
// 签名 // 签名
signEnable?: boolean signEnable?: boolean
@ -439,8 +439,6 @@ export enum OperationButtonType {
* *
*/ */
export type ConditionRule = { export type ConditionRule = {
type: number
opName: string
opCode: string opCode: string
leftSide: string leftSide: string
rightSide: string rightSide: string
@ -471,7 +469,7 @@ NODE_DEFAULT_TEXT.set(NodeType.COPY_TASK_NODE, '请配置抄送人')
NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件') NODE_DEFAULT_TEXT.set(NodeType.CONDITION_NODE, '请设置条件')
NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人') NODE_DEFAULT_TEXT.set(NodeType.START_USER_NODE, '请设置发起人')
NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器') NODE_DEFAULT_TEXT.set(NodeType.DELAY_TIMER_NODE, '请设置延迟器')
NODE_DEFAULT_TEXT.set(NodeType.ROUTE_BRANCH_NODE, '请设置路由节点') NODE_DEFAULT_TEXT.set(NodeType.ROUTER_BRANCH_NODE, '请设置路由节点')
export const NODE_DEFAULT_NAME = new Map<number, string>() export const NODE_DEFAULT_NAME = new Map<number, string>()
NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人') NODE_DEFAULT_NAME.set(NodeType.USER_TASK_NODE, '审批人')
@ -479,7 +477,7 @@ NODE_DEFAULT_NAME.set(NodeType.COPY_TASK_NODE, '抄送人')
NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件') NODE_DEFAULT_NAME.set(NodeType.CONDITION_NODE, '条件')
NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人') NODE_DEFAULT_NAME.set(NodeType.START_USER_NODE, '发起人')
NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器') NODE_DEFAULT_NAME.set(NodeType.DELAY_TIMER_NODE, '延迟器')
NODE_DEFAULT_NAME.set(NodeType.ROUTE_BRANCH_NODE, '路由分支') NODE_DEFAULT_NAME.set(NodeType.ROUTER_BRANCH_NODE, '路由分支')
// 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序 // 候选人策略。暂时不从字典中取。 后续可能调整。控制显示顺序
export const CANDIDATE_STRATEGY: DictDataVO[] = [ export const CANDIDATE_STRATEGY: DictDataVO[] = [
@ -660,7 +658,7 @@ export const DELAY_TYPE = [
/** /**
* *
*/ */
export type RouteCondition = { export type RouterCondition = {
nodeId: string nodeId: string
conditionType: ConditionType conditionType: ConditionType
conditionExpression: string conditionExpression: string

View File

@ -30,117 +30,7 @@
>未满足其它条件时将进入此分支该分支不可编辑和删除</div >未满足其它条件时将进入此分支该分支不可编辑和删除</div
> >
<div v-else> <div v-else>
<el-form ref="formRef" :model="currentNode" :rules="formRules" label-position="top"> <Condition ref="conditionRef" v-model="condition" />
<el-form-item label="配置方式" prop="conditionType">
<el-radio-group v-model="currentNode.conditionType" @change="changeConditionType">
<el-radio
v-for="(dict, index) in conditionConfigTypes"
:key="index"
:value="dict.value"
:label="dict.value"
>
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item
v-if="currentNode.conditionType === 1"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="currentNode.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item v-if="currentNode.conditionType === 2" label="条件规则">
<div class="condition-group-tool">
<div class="flex items-center">
<div class="mr-4">条件组关系</div>
<el-switch
v-model="conditionGroups.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
<el-space direction="vertical" :spacer="conditionGroups.and ? '且' : '或'">
<el-card
class="condition-group"
style="width: 530px"
v-for="(condition, cIdx) in conditionGroups.conditions"
:key="cIdx"
>
<div class="condition-group-delete" v-if="conditionGroups.conditions.length > 1">
<Icon
color="#0089ff"
icon="ep:circle-close-filled"
:size="18"
@click="deleteConditionGroup(cIdx)"
/>
</div>
<template #header>
<div class="flex items-center justify-between">
<div>条件组</div>
<div class="flex">
<div class="mr-4">规则关系</div>
<el-switch
v-model="condition.and"
inline-prompt
active-text="且"
inactive-text="或"
/>
</div>
</div>
</template>
<div class="flex pt-2" v-for="(rule, rIdx) in condition.rules" :key="rIdx">
<div class="mr-2">
<el-select style="width: 160px" v-model="rule.leftSide">
<el-option
v-for="(item, index) in fieldOptions"
:key="index"
:label="item.title"
:value="item.field"
:disabled="!item.required"
/>
</el-select>
</div>
<div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px">
<el-option
v-for="item in COMPARISON_OPERATORS"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</div>
<div class="mr-2">
<el-input v-model="rule.rightSide" style="width: 160px" />
</div>
<div class="mr-1 flex items-center" v-if="condition.rules.length > 1">
<Icon
icon="ep:delete"
:size="18"
@click="deleteConditionRule(condition, rIdx)"
/>
</div>
<div class="flex items-center">
<Icon icon="ep:plus" :size="18" @click="addConditionRule(condition, rIdx)" />
</div>
</div>
</el-card>
</el-space>
<div title="添加条件组" class="mt-4 cursor-pointer">
<Icon color="#0089ff" icon="ep:plus" :size="24" @click="addConditionGroup" />
</div>
</el-form-item>
</el-form>
</div> </div>
</div> </div>
<template #footer> <template #footer>
@ -155,33 +45,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
SimpleFlowNode, SimpleFlowNode,
CONDITION_CONFIG_TYPES,
ConditionType, ConditionType,
COMPARISON_OPERATORS, COMPARISON_OPERATORS,
ConditionGroup,
Condition,
ConditionRule,
ProcessVariableEnum ProcessVariableEnum
} from '../consts' } from '../consts'
import { getDefaultConditionNodeName } from '../utils' import { getDefaultConditionNodeName } from '../utils'
import { useFormFields } from '../node' import { useFormFields } from '../node'
import { BpmModelFormType } from '@/utils/constants' import Condition from './components/Condition.vue'
const message = useMessage() // const message = useMessage() //
defineOptions({ defineOptions({
name: 'ConditionNodeConfig' name: 'ConditionNodeConfig'
}) })
const formType = inject<Ref<number>>('formType') //
const conditionConfigTypes = computed(() => {
return CONDITION_CONFIG_TYPES.filter((item) => {
//
if (formType?.value === BpmModelFormType.CUSTOM && item.value === ConditionType.RULE) {
return false
} else {
return true
}
})
})
const props = defineProps({ const props = defineProps({
conditionNode: { conditionNode: {
type: Object as () => SimpleFlowNode, type: Object as () => SimpleFlowNode,
@ -193,11 +67,26 @@ const props = defineProps({
} }
}) })
const settingVisible = ref(false) const settingVisible = ref(false)
const condition = ref<any>()
const open = () => { const open = () => {
if (currentNode.value.conditionType === ConditionType.RULE) { condition.value = {
if (currentNode.value.conditionGroups) { conditionType: currentNode.value.conditionType,
conditionGroups.value = currentNode.value.conditionGroups conditionExpression: currentNode.value.conditionExpression ?? '',
conditionGroups: currentNode.value.conditionGroups ?? {
and: true,
conditions: [
{
and: true,
rules: [
{
opCode: '==',
leftSide: '',
rightSide: ''
} }
]
}
]
},
} }
settingVisible.value = true settingVisible.value = true
} }
@ -239,31 +128,27 @@ const handleClose = async (done: (cancel?: boolean) => void) => {
done() done()
} }
} }
//
const formRules = reactive({
conditionType: [{ required: true, message: '配置方式不能为空', trigger: 'blur' }],
conditionExpression: [{ required: true, message: '条件表达式不能为空', trigger: 'blur' }]
})
const formRef = ref() // Ref
const conditionRef = ref()
// //
const saveConfig = async () => { const saveConfig = async () => {
if (!currentNode.value.defaultFlow) { if (!currentNode.value.defaultFlow) {
// //
if (!formRef) return false const valid = await conditionRef.value.validate()
const valid = await formRef.value.validate()
if (!valid) return false if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) { if (!showText) {
return false return false
} }
currentNode.value.showText = showText currentNode.value.showText = showText
currentNode.value.conditionType = condition.value.conditionType
if (currentNode.value.conditionType === ConditionType.EXPRESSION) { if (currentNode.value.conditionType === ConditionType.EXPRESSION) {
currentNode.value.conditionGroups = undefined currentNode.value.conditionGroups = undefined
currentNode.value.conditionExpression = condition.value.conditionExpression
} }
if (currentNode.value.conditionType === ConditionType.RULE) { if (currentNode.value.conditionType === ConditionType.RULE) {
currentNode.value.conditionExpression = undefined currentNode.value.conditionExpression = undefined
currentNode.value.conditionGroups = conditionGroups.value currentNode.value.conditionGroups = condition.value.conditionGroups
} }
} }
settingVisible.value = false settingVisible.value = false
@ -271,16 +156,16 @@ const saveConfig = async () => {
} }
const getShowText = (): string => { const getShowText = (): string => {
let showText = '' let showText = ''
if (currentNode.value.conditionType === ConditionType.EXPRESSION) { if (condition.value.conditionType === ConditionType.EXPRESSION) {
if (currentNode.value.conditionExpression) { if (condition.value.conditionExpression) {
showText = `表达式:${currentNode.value.conditionExpression}` showText = `表达式:${condition.value.conditionExpression}`
} }
} }
if (currentNode.value.conditionType === ConditionType.RULE) { if (condition.value.conditionType === ConditionType.RULE) {
// //
const groupAnd = conditionGroups.value.and const groupAnd = condition.value.conditionGroups.and
let warningMesg: undefined | string = undefined let warningMesg: undefined | string = undefined
const conditionGroup = conditionGroups.value.conditions.map((item) => { const conditionGroup = condition.value.conditionGroups.conditions.map((item) => {
return ( return (
'(' + '(' +
item.rules item.rules
@ -309,64 +194,7 @@ const getShowText = (): string => {
return showText return showText
} }
//
const changeConditionType = () => {}
const conditionGroups = ref<ConditionGroup>({
and: true,
conditions: [
{
and: true,
rules: [
{
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
]
})
//
const addConditionGroup = () => {
const condition = {
and: true,
rules: [
{
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
]
}
conditionGroups.value.conditions.push(condition)
}
//
const deleteConditionGroup = (idx: number) => {
conditionGroups.value.conditions.splice(idx, 1)
}
//
const addConditionRule = (condition: Condition, idx: number) => {
const rule: ConditionRule = {
type: 1,
opName: '等于',
opCode: '==',
leftSide: '',
rightSide: ''
}
condition.rules.splice(idx + 1, 0, rule)
}
const deleteConditionRule = (condition: Condition, idx: number) => {
condition.rules.splice(idx, 1)
}
const fieldsInfo = useFormFields() const fieldsInfo = useFormFields()
/** 条件规则可选择的表单字段 */ /** 条件规则可选择的表单字段 */
const fieldOptions = computed(() => { const fieldOptions = computed(() => {
const fieldsCopy = fieldsInfo.slice() const fieldsCopy = fieldsInfo.slice()

View File

@ -37,16 +37,19 @@
:value="node.value" :value="node.value"
/> />
</el-select> </el-select>
<el-button class="mla" type="danger" link @click="deleteRouteGroup(index)" <el-button class="mla" type="danger" link @click="deleteRouterGroup(index)"
>删除</el-button >删除</el-button
> >
</div> </div>
</template> </template>
<Condition v-model="routerGroups[index]" /> <Condition
:ref="($event) => (conditionRef[index] = $event)"
v-model="routerGroups[index]"
/>
</el-card> </el-card>
</el-form> </el-form>
<el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouteGroup"> <el-button class="w-1/1" type="primary" :icon="Plus" @click="addRouterGroup">
新增路由分支 新增路由分支
</el-button> </el-button>
</div> </div>
@ -61,11 +64,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Plus } from '@element-plus/icons-vue' import { Plus } from '@element-plus/icons-vue'
import { SimpleFlowNode, NodeType, ConditionType, RouteCondition } from '../consts' import { SimpleFlowNode, NodeType, ConditionType, RouterCondition } from '../consts'
import { useWatchNode, useDrawer, useNodeName } from '../node' import { useWatchNode, useDrawer, useNodeName } from '../node'
import Condition from './components/Condition.vue' import Condition from './components/Condition.vue'
defineOptions({ defineOptions({
name: 'RouteNodeConfig' name: 'RouterNodeConfig'
}) })
const message = useMessage() // const message = useMessage() //
const props = defineProps({ const props = defineProps({
@ -80,12 +83,21 @@ const { settingVisible, closeDrawer, openDrawer } = useDrawer()
// //
const currentNode = useWatchNode(props) const currentNode = useWatchNode(props)
// //
const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTE_BRANCH_NODE) const { nodeName, showInput, clickIcon, blurEvent } = useNodeName(NodeType.ROUTER_BRANCH_NODE)
const routerGroups = ref<RouteCondition[]>([]) const routerGroups = ref<RouterCondition[]>([])
const nodeOptions = ref() const nodeOptions = ref()
const conditionRef = ref([])
// //
const saveConfig = async () => { const saveConfig = async () => {
//
let valid = true
for (const item of conditionRef.value) {
if (!(await item.validate())) {
valid = false
}
}
if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) return false if (!showText) return false
currentNode.value.name = nodeName.value! currentNode.value.name = nodeName.value!
@ -96,7 +108,7 @@ const saveConfig = async () => {
} }
// //
const showRouteNodeConfig = (node: SimpleFlowNode) => { const showRouteNodeConfig = (node: SimpleFlowNode) => {
getRoutableNode() getRouterNode()
routerGroups.value = [] routerGroups.value = []
nodeName.value = node.name nodeName.value = node.name
if (node.routerGroups) { if (node.routerGroups) {
@ -132,7 +144,7 @@ const getShowText = () => {
return `${routerGroups.value.length}条路由分支` return `${routerGroups.value.length}条路由分支`
} }
const addRouteGroup = () => { const addRouterGroup = () => {
routerGroups.value.push({ routerGroups.value.push({
nodeId: '', nodeId: '',
conditionType: ConditionType.RULE, conditionType: ConditionType.RULE,
@ -144,8 +156,6 @@ const addRouteGroup = () => {
and: true, and: true,
rules: [ rules: [
{ {
type: 1,
opName: '等于',
opCode: '==', opCode: '==',
leftSide: '', leftSide: '',
rightSide: '' rightSide: ''
@ -157,12 +167,11 @@ const addRouteGroup = () => {
}) })
} }
const deleteRouteGroup = (index: number) => { const deleteRouterGroup = (index: number) => {
routerGroups.value.splice(index, 1) routerGroups.value.splice(index, 1)
} }
// TODO @lesan router const getRouterNode = () => {
const getRoutableNode = () => {
// TODO @lesan // TODO @lesan
// //
// //
@ -170,7 +179,7 @@ const getRoutableNode = () => {
nodeOptions.value = [] nodeOptions.value = []
while (true) { while (true) {
if (!node) break if (!node) break
if (node.type !== NodeType.ROUTE_BRANCH_NODE) { if (node.type !== NodeType.ROUTER_BRANCH_NODE) {
nodeOptions.value.push({ nodeOptions.value.push({
label: node.name, label: node.name,
value: node.id value: node.id

View File

@ -359,11 +359,7 @@
<el-divider content-position="left">是否需要签名</el-divider> <el-divider content-position="left">是否需要签名</el-divider>
<el-form-item prop="signEnable"> <el-form-item prop="signEnable">
<el-switch <el-switch v-model="configForm.signEnable" active-text="是" inactive-text="否" />
v-model="configForm.signEnable"
active-text="是"
inactive-text="否"
/>
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
@ -445,7 +441,7 @@
</div> </div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="监听器" name="listener"> <el-tab-pane label="监听器" name="listener">
<el-form :model="configForm" label-position="top"> <el-form ref="listenerFormRef" :model="configForm" label-position="top">
<div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx"> <div v-for="(listener, listenerIdx) in taskListener" :key="listenerIdx">
<el-divider content-position="left"> <el-divider content-position="left">
<el-text tag="b" size="large">{{ listener.name }}</el-text> <el-text tag="b" size="large">{{ listener.name }}</el-text>
@ -484,7 +480,16 @@
:key="index" :key="index"
> >
<div class="mr-2"> <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-input class="w-160px" v-model="item.key" />
</el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-select class="w-100px!" v-model="item.type"> <el-select class="w-100px!" v-model="item.type">
@ -497,11 +502,28 @@
</el-select> </el-select>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerHeader.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input <el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE" v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px" class="w-160px"
v-model="item.value" 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 <el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM" v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!" class="w-160px!"
@ -515,6 +537,7 @@
:disabled="!field.required" :disabled="!field.required"
/> />
</el-select> </el-select>
</el-form-item>
</div> </div>
<div class="mr-1 flex items-center"> <div class="mr-1 flex items-center">
<Icon <Icon
@ -544,7 +567,16 @@
:key="index" :key="index"
> >
<div class="mr-2"> <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-input class="w-160px" v-model="item.key" />
</el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-select class="w-100px!" v-model="item.type"> <el-select class="w-100px!" v-model="item.type">
@ -557,11 +589,28 @@
</el-select> </el-select>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-form-item
:prop="`task${listener.type}ListenerBody.${index}.value`"
:rules="{
required: true,
message: '参数值不能为空',
trigger: 'blur'
}"
>
<el-input <el-input
v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE" v-if="item.type === ListenerParamTypeEnum.FIXED_VALUE"
class="w-160px" class="w-160px"
v-model="item.value" 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 <el-select
v-if="item.type === ListenerParamTypeEnum.FROM_FORM" v-if="item.type === ListenerParamTypeEnum.FROM_FORM"
class="w-160px!" class="w-160px!"
@ -575,6 +624,7 @@
:disabled="!field.required" :disabled="!field.required"
/> />
</el-select> </el-select>
</el-form-item>
</div> </div>
<div class="mr-1 flex items-center"> <div class="mr-1 flex items-center">
<Icon <Icon
@ -792,6 +842,8 @@ const {
cTimeoutMaxRemindCount cTimeoutMaxRemindCount
} = useTimeoutHandler() } = useTimeoutHandler()
const listenerFormRef = ref()
// //
const saveConfig = async () => { const saveConfig = async () => {
activeTabName.value = 'user' activeTabName.value = 'user'
@ -807,7 +859,8 @@ const saveConfig = async () => {
} }
if (!formRef) return false if (!formRef) return false
const valid = await formRef.value.validate() if (!listenerFormRef) return false
const valid = (await formRef.value.validate()) && (await listenerFormRef.value.validate())
if (!valid) return false if (!valid) return false
const showText = getShowText() const showText = getShowText()
if (!showText) return false if (!showText) return false
@ -937,7 +990,7 @@ const showUserTaskNodeConfig = (node: SimpleFlowNode) => {
configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? [] configForm.value.taskCompleteListenerHeader = node.taskCompleteListener?.header ?? []
configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? [] configForm.value.taskCompleteListenerBody = node.taskCompleteListener?.body ?? []
// 6. // 6.
configForm.value.signEnable = node.signEnable ?? false configForm.value.signEnable = node?.signEnable ?? false
} }
defineExpose({ openDrawer, showUserTaskNodeConfig }) // defineExpose({ openDrawer, showUserTaskNodeConfig }) //

View File

@ -1,7 +1,5 @@
<!-- TODO @lesan其它路由条件可以使用这个哇 -->
<template> <template>
<el-form ref="formRef" :model="condition" :rules="formRules" label-position="top"> <el-form ref="formRef" :model="condition" :rules="formRules" label-position="top">
<!-- TODO @lesan1默认选中 条件规则2条件规则放前面因为更常用-->
<el-form-item label="配置方式" prop="conditionType"> <el-form-item label="配置方式" prop="conditionType">
<el-radio-group v-model="condition.conditionType"> <el-radio-group v-model="condition.conditionType">
<el-radio <el-radio
@ -14,18 +12,6 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
<el-form-item v-if="condition.conditionType === ConditionType.RULE" label="条件规则"> <el-form-item v-if="condition.conditionType === ConditionType.RULE" label="条件规则">
<div class="condition-group-tool"> <div class="condition-group-tool">
<div class="flex items-center"> <div class="flex items-center">
@ -73,6 +59,14 @@
<div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx"> <div class="flex pt-2" v-for="(rule, rIdx) in equation.rules" :key="rIdx">
<div class="mr-2"> <div class="mr-2">
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.leftSide`"
:rules="{
required: true,
message: '左值不能为空',
trigger: 'change'
}"
>
<el-select style="width: 160px" v-model="rule.leftSide"> <el-select style="width: 160px" v-model="rule.leftSide">
<el-option <el-option
v-for="(field, fIdx) in fieldOptions" v-for="(field, fIdx) in fieldOptions"
@ -82,6 +76,7 @@
:disabled="!field.required" :disabled="!field.required"
/> />
</el-select> </el-select>
</el-form-item>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-select v-model="rule.opCode" style="width: 100px"> <el-select v-model="rule.opCode" style="width: 100px">
@ -94,7 +89,16 @@
</el-select> </el-select>
</div> </div>
<div class="mr-2"> <div class="mr-2">
<el-form-item
:prop="`conditionGroups.conditions.${cIdx}.rules.${rIdx}.rightSide`"
:rules="{
required: true,
message: '右值不能为空',
trigger: 'blur'
}"
>
<el-input v-model="rule.rightSide" style="width: 160px" /> <el-input v-model="rule.rightSide" style="width: 160px" />
</el-form-item>
</div> </div>
<div class="mr-1 flex items-center" v-if="equation.rules.length > 1"> <div class="mr-1 flex items-center" v-if="equation.rules.length > 1">
<Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" /> <Icon icon="ep:delete" :size="18" @click="deleteConditionRule(equation, rIdx)" />
@ -114,13 +118,25 @@
/> />
</div> </div>
</el-form-item> </el-form-item>
<el-form-item
v-if="condition.conditionType === ConditionType.EXPRESSION"
label="条件表达式"
prop="conditionExpression"
>
<el-input
type="textarea"
v-model="condition.conditionExpression"
clearable
style="width: 100%"
/>
</el-form-item>
</el-form> </el-form>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { import {
CONDITION_CONFIG_TYPES,
COMPARISON_OPERATORS, COMPARISON_OPERATORS,
CONDITION_CONFIG_TYPES,
ConditionType, ConditionType,
ProcessVariableEnum ProcessVariableEnum
} from '../../consts' } from '../../consts'
@ -181,8 +197,6 @@ const deleteConditionRule = (condition, index) => {
const addConditionRule = (condition, index) => { const addConditionRule = (condition, index) => {
const rule = { const rule = {
type: 1,
opName: '等于',
opCode: '==', opCode: '==',
leftSide: '', leftSide: '',
rightSide: '' rightSide: ''
@ -195,8 +209,6 @@ const addConditionGroup = (conditions) => {
and: true, and: true,
rules: [ rules: [
{ {
type: 1, // TODO @lesan~
opName: '等于',
opCode: '==', opCode: '==',
leftSide: '', leftSide: '',
rightSide: '' rightSide: ''
@ -205,6 +217,13 @@ const addConditionGroup = (conditions) => {
} }
conditions.push(condition) conditions.push(condition)
} }
const validate = async () => {
if (!formRef) return false
return await formRef.value.validate()
}
defineExpose({ validate })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -31,7 +31,7 @@
{{ currentNode.showText }} {{ currentNode.showText }}
</div> </div>
<div class="node-text" v-else> <div class="node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(NodeType.ROUTE_BRANCH_NODE) }} {{ NODE_DEFAULT_TEXT.get(NodeType.ROUTER_BRANCH_NODE) }}
</div> </div>
<Icon v-if="!readonly" icon="ep:arrow-right-bold" /> <Icon v-if="!readonly" icon="ep:arrow-right-bold" />
</div> </div>
@ -49,17 +49,17 @@
:current-node="currentNode" :current-node="currentNode"
/> />
</div> </div>
<RouteNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" /> <RouterNodeConfig v-if="!readonly && currentNode" ref="nodeSetting" :flow-node="currentNode" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts' import { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
import NodeHandler from '../NodeHandler.vue' import NodeHandler from '../NodeHandler.vue'
import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node' import { useNodeName2, useWatchNode, useTaskStatusClass } from '../node'
import RouteNodeConfig from '../nodes-config/RouteNodeConfig.vue' import RouterNodeConfig from '../nodes-config/RouterNodeConfig.vue'
defineOptions({ defineOptions({
name: 'RouteNode' name: 'RouterNode'
}) })
const props = defineProps({ const props = defineProps({
@ -77,7 +77,7 @@ const readonly = inject<Boolean>('readonly')
// //
const currentNode = useWatchNode(props) const currentNode = useWatchNode(props)
// //
const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTE_BRANCH_NODE) const { showInput, blurEvent, clickTitle } = useNodeName2(currentNode, NodeType.ROUTER_BRANCH_NODE)
const nodeSetting = ref() const nodeSetting = ref()
// //

View File

@ -44,6 +44,12 @@
:rows="4" :rows="4"
/> />
</el-form-item> </el-form-item>
<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-form-item>
<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) }} {{ getButtonDisplayName(OperationButtonType.APPROVE) }}
@ -471,6 +477,8 @@
<Icon :size="14" icon="ep:refresh" />&nbsp; 再次提交 <Icon :size="14" icon="ep:refresh" />&nbsp; 再次提交
</div> </div>
</div> </div>
<SignDialog ref="signRef" @success="handleSignFinish"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useUserStoreWithOut } from '@/store/modules/user' import { useUserStoreWithOut } from '@/store/modules/user'
@ -484,6 +492,7 @@ import {
} from '@/components/SimpleProcessDesignerV2/src/consts' } from '@/components/SimpleProcessDesignerV2/src/consts'
import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants' import { BpmProcessInstanceStatus, BpmModelFormType } from '@/utils/constants'
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules } from 'element-plus'
import SignDialog from "./SignDialog.vue";
defineOptions({ name: 'ProcessInstanceBtnContainer' }) defineOptions({ name: 'ProcessInstanceBtnContainer' })
const router = useRouter() // const router = useRouter() //
@ -522,11 +531,15 @@ const approveFormFApi = ref<any>({}) // approveForms 的 fAPi
// //
const approveFormRef = ref<FormInstance>() const approveFormRef = ref<FormInstance>()
const signRef = ref()
const approveSignFormRef = ref()
const approveReasonForm = reactive({ const approveReasonForm = reactive({
reason: '' reason: '',
sign: ''
}) })
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' }]
}) })
// //
const rejectFormRef = ref<FormInstance>() const rejectFormRef = ref<FormInstance>()
@ -672,6 +685,10 @@ const handleAudit = async (pass: boolean, formRef: FormInstance | undefined) =>
reason: approveReasonForm.reason, reason: approveReasonForm.reason,
variables // , variables // ,
} }
//
if (runningTask.value.signEnable) {
data.sign = approveReasonForm.sign
}
// approveForm + data // approveForm + data
// TODO // TODO
const formCreateApi = approveFormFApi.value const formCreateApi = approveFormFApi.value
@ -966,6 +983,11 @@ const getUpdatedProcessInstanceVaiables = ()=> {
return variables return variables
} }
const handleSignFinish = (url) => {
approveReasonForm.sign = url
approveSignFormRef.value.validate('change')
}
defineExpose({ loadTodoTask }) defineExpose({ loadTodoTask })
</script> </script>

View File

@ -128,7 +128,7 @@ const setSimpleModelNodeTaskStatus = (
simpleModel.type === NodeType.CONDITION_BRANCH_NODE || simpleModel.type === NodeType.CONDITION_BRANCH_NODE ||
simpleModel.type === NodeType.PARALLEL_BRANCH_NODE || simpleModel.type === NodeType.PARALLEL_BRANCH_NODE ||
simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE || simpleModel.type === NodeType.INCLUSIVE_BRANCH_NODE ||
simpleModel.type === NodeType.ROUTE_BRANCH_NODE simpleModel.type === NodeType.ROUTER_BRANCH_NODE
) { ) {
// //
if (finishedActivityIds.includes(simpleModel.id)) { if (finishedActivityIds.includes(simpleModel.id)) {

View File

@ -123,6 +123,15 @@
> >
审批意见{{ task.reason }} 审批意见{{ task.reason }}
</div> </div>
<div
v-if="task.sign && 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]"/>
</div>
</teleport> </teleport>
</div> </div>
<!-- 情况二遍历每个审批节点下的候选的task 任务例如说1依次审批2未来的审批任务等 --> <!-- 情况二遍历每个审批节点下的候选的task 任务例如说1依次审批2未来的审批任务等 -->

View File

@ -0,0 +1,84 @@
<template>
<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"/>
<el-button
style="position: absolute; bottom: 20px; right: 10px"
type="primary"
text
size="small"
@click="signature.clear()"
>
<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>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import Vue3Signature from "vue3-signature"
import * as FileApi from '@/api/infra/file'
const message = useMessage() //
const signDialogVisible = ref(false)
const signature = ref()
const open = async () => {
signDialogVisible.value = true
}
defineExpose({open})
const emits = defineEmits(['success'])
const submit = async () => {
message.success('签名上传中请稍等。。。')
const res = await FileApi.updateFile({file: base64ToFile(signature.value.save('image/png'), '签名')})
emits('success', res.data)
signDialogVisible.value = false
}
const base64ToFile = (base64, fileName) => {
// base64 ,
let data = base64.split(',');
// image/pngimage/jpegimage/webp
let type = data[0].match(/:(.*?);/)[1];
// pngjpegwebp
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>