应用开发平台集成工作流系列之17——流程建模功能前端设计与改造回顾

背景

对于流程设置不友好的问题,国内钉钉另行设计与实现了一套流程建模模式,跟bpmn规范无关,有人仿照实现了下,并做了开源(https://github.com/StavinLi/Workflow-Vue3),效果图如下:

实现大致原理是基于无限嵌套的子节点,输出json数据,传给后端,后端进行解析后,调用Camunda引擎的api,转换成流程模型后持久化。

该系列通过十几篇对Camunda工作流引擎集成做了相对比较详细的设计和实现说明工作,在集成过程中,对原前端Workflow-Vue3做了大量的改造,前面零零星星的也随着部分博客提及过,今天做一个全局性的回顾和整理。

节点类型

目前平台已经实现的节点有三类,发起节点、办理节点、路由节点。
发起节点与办理节点对应着Camunda中的用户任务类型,路由节点对应着Camunda中的兼容网关类型。
后续可以在此基础上进行功能扩展,使用Camunda的服务节点,构建不同用途的前端节点类型,如发送通知(邮件、短信等)、流程归档等。

节点配置

流程是有多个节点组成的,但仅有节点,以及连接节点的边还不够,每个节点需要进行必要的配置。
对于不同类型的节点,配置也有差异。

基础属性

基础属性是节点的通用公共数据结构,所有节点类型都一样,如下图所示:

{id:'',name: '填报',type: 'FIRST_NODE',config: {},branchList: [],child: {}
}

各属性含义如下

属性类型含义说明
idString标识唯一性标识,由前端随机生成
nameString名称节点名称
typeString类型ROOT(发起人,根节点)、HANDLE(办理)、INCLUSIVE_GATEWAY(路由)
configObject配置节点的设置项内容,不同类型的节点设置内容不同,从而数据结构不同
childObject子节点当前节点的子对象
branchListArray分支当type 为 INCLUSIVE_GATEWAY 时,该项存在,存放分支数据

办理人设置

针对用户任务节点,设置该环节是会签还是单人处理,是否需要用户指定处理人,以及关联的用户组(角色)。
数据结构示例:

	"personConfig": {"mode": "NORMAL","setAssigneeFlag": "YES","userGroup": "99","userGroupName": "系统管理员"}

属性说明:

属性类型含义说明
modeString模式COUNTERSIGN:会签;NORMAL:普通
setAssigneeFlagString是否指定人YES:是;NO:否
userGroupString用户组(角色)调用平台的用户组单选组件,选择一个用户组,用户组的标识
userGroupNameString用户组(角色)的名称用户组的名称

表单权限设置

控制该环节表单不同区域的可见性与可编辑性,对发起节点(流程首环节,通常用于填报)和用户任务节点需要设置。

数据结构示例:

	"config": {"permissionConfig": [{"areaCode": "applyArea","permission": "EDITABLE"}, {"areaCode": "organizationApproval","permission": "READONLY"}, {"areaCode": "hrApproval","permission": "INVISIBLE"}]}

属性说明:

属性类型含义说明
areaCodeString区域编码在平台权限项管理中定义与维护
permissionString权限编码INVISIBLE:不可见;READONLY:只读;EDITABLE:可编辑

回退节点设置

当前环节可回退到的节点定义。

数据结构示例:

	"backNodeList": [{"id": "root","name": "填报"}, {"id": "node1938_8b28_c3ed_030f","name": "部门审批"}]

属性说明:

属性类型含义说明
idString环节标识发起环节使用统一约定的root
nameString环节名称

跳转节点设置

当前环节可跳转到的节点定义,与回退节点配置类似。

数据结构示例:

	"jumpNodeList": [{"id": "root","name": "填报"}, {"id": "node1938_8b28_c3ed_030f","name": "部门审批"}]

属性说明:

属性类型含义说明
idString环节标识发起环节使用统一约定的root
nameString环节名称

监听器配置

环节监听器的配置,用于实现特定的业务逻辑

数据结构示例:

	"listenerList": [{"category": "TASK","type": "CLASS","name": "请假申请部门审批完成","code": "tech.abc.platform.businessflow.listener.LeaveDepartApprovalCompleteListener","event": "COMPLETE","eventName": "完成"}, {"category": "TASK","type": "CLASS","name": "请假申请部门审批完成","code": "tech.abc.platform.businessflow.listener.LeaveDepartApprovalCompleteListener","event": "COMPLETE","eventName": "完成"
}]

属性说明:

属性类型含义说明
categoryString分类TASK:任务监听器;EXECUTION:执行监听器
typeString类型CLASS:类;EXPRESSION:表达式;DELEGATE_EXPRESSION:委托表达式
nameString名称监听器名称
codeString编码取决于type,如为类,则本属性放完整的类名
eventString事件编码取决于category,如为任务监听器,则取值如下:
CREATE:创建;ASSIGNMENT:指派;COMPLETE:完成;DELETE:删除
eventNameString事件名称参见上面事件编码的枚举值定义中文部分

条件配置

设置条件,路由节点后续的分支节点需要配置。

数据结构示例:

{"name": "路由","id": "node3278_00b0_e238_a105","type": "INCLUSIVE_GATEWAY","config": {},"child": null,"branchList": [{"name": "3天以内","id": "condition5914_12fb_e783_f171","type": "CONDITION","config": {"expression": "${total<=3}"},"branchList": []},{"name": "超过3天","id": "condition10081_fd56_1fb6_f8ed","type": "CONDITION","config": {"expression": "${total>3}"},"branchList": []}]
}

属性说明:

属性类型含义说明
expressionString表达式示例:${total<=3}

组件改造

如上面所示,我们对前端数据结构进行了大调整,相应的,原组件也需要进行相应的调整,差不多只留了架子,内部大换血,主要实现如下:

流程建模组件nodeWrap

效果图
image.png

源码

<!-- eslint-disable vue/no-mutating-props -->
<!--* @Date: 2022-09-21 14:41:53* @LastEditors: StavinLi 495727881@qq.com* @LastEditTime: 2023-05-24 15:20:24* @FilePath: /Workflow-Vue3/src/components/nodeWrap.vue
-->
<template><!-- 发起环节 --><div class="node-wrap" v-if="modelValue.type == 'ROOT'"><divclass="node-wrap-box":class="'start-node' + (isTried && modelValue.error ? 'active error' : '')"><div class="title" :style="`background: rgb(87, 106, 149);`"><span v-if="modelValue.type == 'ROOT'">{{ modelValue.name }}</span></div><div class="content" @click="setRootNode"><div class="text"> 发起环节 </div><i class="anticon anticon-right arrow"></i></div><div class="error_tip" v-if="isTried && modelValue.error"><i class="anticon anticon-exclamation-circle"></i></div></div><addNode v-model:childNodeP="modelValue.child" /></div><!-- 办理环节  --><div class="node-wrap" v-else-if="modelValue.type == 'HANDLE'"><div class="node-wrap-box" :class="isTried && modelValue.error ? 'active error' : ''"><div class="title" :style="`background: rgb(255, 148, 62);`"><inputv-if="isInput"type="text"class="ant-input editable-title-input"@blur="blurEvent()"@focus="$event.currentTarget.select()"v-focusv-model="modelValue.name"/><span v-else class="editable-title" @click="clickEvent()">{{ modelValue.name }}</span><i class="anticon anticon-close close" @click="delNode"></i></div><div class="content" @click="setHandleNode"><div class="text"><spanclass="placeholder"v-if="!modelValue.config.personConfig || !modelValue.config.personConfig.userGroupName">待配置</span><template v-else> {{ modelValue.config.personConfig.userGroupName }}</template></div><i class="anticon anticon-right arrow"></i></div><div class="error_tip" v-if="isTried && modelValue.error"><i class="anticon anticon-exclamation-circle"></i></div></div><addNode v-model:childNodeP="modelValue.child" /></div><!-- 路由节点 --><div class="branch-wrap" v-else-if="modelValue.type == 'INCLUSIVE_GATEWAY'"><div class="branch-box-wrap"><div class="branch-box"><button class="add-branch" @click="addCondition">添加条件</button><div class="col-box" v-for="(item, index) in modelValue.branchList" :key="index"><div class="condition-node"><div class="condition-node-box"><div class="auto-judge" :class="isTried && item.error ? 'error active' : ''"><div class="sort-left" v-if="index != 0" @click="arrTransfer(index, -1)">&lt;</div><div class="title-wrapper"><inputv-if="isInputList[index]"type="text"class="ant-input editable-title-input"@blur="blurEvent(index)"@focus="$event.currentTarget.select()"v-focusv-model="item.name"/><span v-else class="editable-title" @click="clickEvent(index)">{{item.name}}</span><i class="anticon anticon-close close" @click="removeCondition(index)"></i></div><divclass="sort-right"v-if="index != modelValue.branchList.length - 1"@click="arrTransfer(index)">&gt;</div><div class="content" @click="setConditionNode(item)"><span class="placeholder" v-if="!item.config.expression">未设置</span>{{ item.config.expression }}</div><div class="error_tip" v-if="isTried && item.error"><i class="anticon anticon-exclamation-circle"></i></div></div><addNode v-model:childNodeP="item.child" /></div></div><nodeWrap v-if="item.child" v-model:modelValue="item.child" /><template v-if="index == 0"><div class="top-left-cover-line"></div><div class="bottom-left-cover-line"></div></template><template v-if="index == modelValue.branchList.length - 1"><div class="top-right-cover-line"></div><div class="bottom-right-cover-line"></div></template></div></div><addNode v-model:childNodeP="modelValue.child" /></div></div><nodeWrap v-if="modelValue.child" v-model:modelValue="modelValue.child" />
</template>
<script setup>
import { onMounted, ref, watch, getCurrentInstance, computed } from 'vue'
import addNode from './addNode.vue'
import $func from '../utils/index'
import { useStore } from '../stores/index'let _uid = getCurrentInstance().uidlet props = defineProps({modelValue: {type: Object,default: () => ({})}
})let isInputList = ref([])
let isInput = ref(false)
const resetConditionNodesErr = () => {for (var i = 0; i < props.modelValue.branchList.length; i++) {// eslint-disable-next-line vue/no-mutating-propsprops.modelValue.branchList[i].error =$func.conditionStr(props.modelValue, i) == '请设置条件' &&i != props.modelValue.branchList.length - 1}
}
onMounted(() => {})
let emits = defineEmits(['update:modelValue'])
let store = useStore()
let {setRootNodeConfigVisible,setRootNodeConfig,setHandleNodeConfigVisible,setHandleNodeConfig,setConditionNodeConfigVisible,setConditionNodeConfig
} = store
let isTried = computed(() => store.isTried)const clickEvent = (index) => {if (index || index === 0) {isInputList.value[index] = true} else {isInput.value = true}
}
const blurEvent = (index) => {if (index || index === 0) {isInputList.value[index] = false// eslint-disable-next-line vue/no-mutating-propsprops.modelValue.branchList[index].name = props.modelValue.branchList[index].name || '条件'} else {isInput.value = false// eslint-disable-next-line vue/no-mutating-propsprops.modelValue.name = props.modelValue.name}
}
const delNode = () => {emits('update:modelValue', props.modelValue.child)
}
const addCondition = () => {let len = props.modelValue.branchList.length + 1// eslint-disable-next-line vue/no-mutating-propsprops.modelValue.branchList.push({name: '条件' + len,type: 'CONDITION',child: null})resetConditionNodesErr()emits('update:modelValue', props.modelValue)
}
const removeCondition = (index) => {// eslint-disable-next-line vue/no-mutating-propsprops.modelValue.branchList.splice(index, 1)props.modelValue.branchList.map((item, index) => {item.name = `条件${index + 1}`})resetConditionNodesErr()emits('update:modelValue', props.modelValue)if (props.modelValue.branchList.length == 1) {if (props.modelValue.child) {if (props.modelValue.branchList[0].child) {reData(props.modelValue.branchList[0].child, props.modelValue.child)} else {// eslint-disable-next-line vue/no-mutating-propsprops.modelValue.branchList[0].child = props.modelValue.child}}emits('update:modelValue', props.modelValue.branchList[0].child)}
}
const reData = (data, addData) => {if (!data.child) {data.child = addData} else {reData(data.child, addData)}
}const arrTransfer = (index, type = 1) => {//向左-1,向右1// eslint-disable-next-line vue/no-mutating-propsprops.modelValue.branchList[index] = props.modelValue.branchList.splice(index + type,1,props.modelValue.branchList[index])[0]props.modelValue.branchList.map((item, index) => {item.priorityLevel = index + 1})resetConditionNodesErr()emits('update:modelValue', props.modelValue)
}// 设置发起环节配置
const setRootNode = () => {setRootNodeConfigVisible(true)const nodeConfig = {config: props.modelValue.config,flag: false,componentId: _uid,id: props.modelValue.id,model: props.modelValue}setRootNodeConfig(nodeConfig)
}
let rootNodeConfig = computed(() => store.rootNodeConfig)
watch(rootNodeConfig,(myConfig) => {if (myConfig.flag && myConfig.componentId === _uid) {const modelValue = Object.assign(props.modelValue, { config: myConfig.config })emits('update:modelValue', modelValue)}},{ deep: true }
)// 设置办理环节配置
const setHandleNode = () => {setHandleNodeConfigVisible(true)const handleNodeConfig = {config: props.modelValue.config,flag: false,componentId: _uid,id: props.modelValue.id,model: props.modelValue}setHandleNodeConfig(handleNodeConfig)
}let handleNodeConfig = computed(() => store.handleNodeConfig)
watch(handleNodeConfig,(myConfig) => {if (myConfig.flag && myConfig.componentId === _uid) {const modelValue = Object.assign(props.modelValue, { config: myConfig.config })emits('update:modelValue', modelValue)}},{ deep: true }
)// 设置条件节点配置
const setConditionNode = (condition) => {setConditionNodeConfigVisible(true)const conditionNodeConfig = {...condition.config,flag: false,componentId: _uid,nodeId: condition.id}setConditionNodeConfig(conditionNodeConfig)
}let conditionNodeConfig = computed(() => store.conditionNodeConfig)
watch(conditionNodeConfig,(myConfig) => {if (myConfig.flag && myConfig.componentId === _uid) {// 只保留必要属性,移除辅助使用的componentId和flagconst conditionNodeConfig = {expression: myConfig.expression}// 获取条件节点标识const conditionNodeId = myConfig.nodeIdlet newModelValue = props.modelValuenewModelValue.branchList.forEach((element) => {if (element.id === conditionNodeId) {element.config = conditionNodeConfigreturn}})emits('update:modelValue', newModelValue)}},{ deep: true }
)
</script>
<style scoped>
@import '../css/workflow.css';
.error_tip {position: absolute;top: 0px;right: 0px;transform: translate(150%, 0px);font-size: 24px;
}.promoter_person .el-dialog__body {padding: 10px 20px 14px 20px;
}.selected_list {margin-bottom: 20px;line-height: 30px;
}.selected_list span {margin-right: 10px;padding: 3px 6px 3px 9px;line-height: 12px;white-space: nowrap;border-radius: 2px;border: 1px solid rgba(220, 220, 220, 1);
}.selected_list img {margin-left: 5px;width: 7px;height: 7px;cursor: pointer;
}
</style>

添加节点组件addNode

效果图
image.png

源码

<template><div class="add-node-btn-box"><div class="add-node-btn"><el-popover placement="right-start" v-model="visible" width="auto"><div class="add-node-popover-body"><a class="add-node-popover-item approver" @click="addHandle()"><div class="item-wrapper"><span class="iconfont"></span></div><p>办理节点</p></a><a class="add-node-popover-item condition" @click="addConditionBranch"><div class="item-wrapper"><span class="iconfont"></span></div><p>路由分支</p></a></div><template #reference><button class="btn" type="button"><span class="iconfont"></span></button></template></el-popover></div></div>
</template>
<script setup>
import { ref } from 'vue'
import { uuid } from '@/utils'let props = defineProps({childNodeP: {type: Object,default: () => ({})}
})
let emits = defineEmits(['update:childNodeP'])
let visible = ref(false)
const addHandle = () => {visible.value = falseconst data = {name: '办理环节',id: 'node' + uuid(),type: 'HANDLE',config: {personConfig: {},permissionConfig: []},child: {}}emits('update:childNodeP', data)
}
const addConditionBranch = () => {visible.value = falseconst data = {name: '路由',id: 'node' + uuid(),type: 'INCLUSIVE_GATEWAY',config: {},child: null,branchList: [{name: '条件1',id: 'condition' + uuid(),type: 'CONDITION',config: {},branchList: [],child: props.childNodeP},{name: '条件2',id: 'condition' + uuid(),type: 'CONDITION',config: {},branchList: []}]}emits('update:childNodeP', data)
}
</script>
<style scoped lang="less">
.add-node-btn-box {width: 240px;display: -webkit-inline-box;display: -ms-inline-flexbox;display: inline-flex;-ms-flex-negative: 0;flex-shrink: 0;-webkit-box-flex: 1;-ms-flex-positive: 1;position: relative;&:before {content: '';position: absolute;top: 0;left: 0;right: 0;bottom: 0;z-index: -1;margin: auto;width: 2px;height: 100%;background-color: #cacaca;}.add-node-btn {user-select: none;width: 240px;padding: 20px 0 32px;display: flex;-webkit-box-pack: center;justify-content: center;flex-shrink: 0;-webkit-box-flex: 1;flex-grow: 1;.btn {outline: none;box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);width: 30px;height: 30px;background: #3296fa;border-radius: 50%;position: relative;border: none;line-height: 30px;-webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);.iconfont {color: #fff;font-size: 16px;}&:hover {transform: scale(1.3);box-shadow: 0 13px 27px 0 rgba(0, 0, 0, 0.1);}&:active {transform: none;background: #1e83e9;box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);}}}
}
</style>
<style lang="less" scoped>
@import '../css/workflow.css';
.add-node-popover-body {display: flex;.add-node-popover-item {margin-right: 10px;cursor: pointer;text-align: center;flex: 1;color: #191f25 !important;.item-wrapper {user-select: none;display: inline-block;width: 80px;height: 80px;margin-bottom: 5px;background: #fff;border: 1px solid #e2e2e2;border-radius: 50%;transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);.iconfont {font-size: 35px;line-height: 80px;}}&.approver {.item-wrapper {color: #ff943e;}}&.notifier {.item-wrapper {color: #3296fa;}}&.condition {.item-wrapper {color: #15bc83;}}&:hover {.item-wrapper {background: #3296fa;box-shadow: 0 10px 20px 0 rgba(50, 150, 250, 0.4);}.iconfont {color: #fff;}}&:active {.item-wrapper {box-shadow: none;background: #eaeaea;}.iconfont {color: inherit;}}}
}
</style>

发起环节配置

发起环节相对比较特殊,不需要设置处理人员和回退环节,只需要设置表单权限和跳转环节。
image.png

源码

<template><el-drawer:append-to-body="true"title="环节设置"v-model="visible":show-close="false":size="550":before-close="close"destroy-on-close><el-collapse v-model="activeName"><el-collapse-item title="权限设置" name="permissionConfig"><el-table :data="permissionData" style="width: 100%" highlight-current-row border><el-table-column label="区域" width="120"><template #default="scope">{{ scope.row.areaName }}</template></el-table-column><el-table-column label="权限"><template #default="scope"><dictionary-radio-groupv-model="scope.row.permission"code="NodePermissionCode"class="form-item"/></template></el-table-column></el-table></el-collapse-item><el-collapse-item title="跳转环节" name="jumpNodeListConfig"><div class="mb-10px mt-10px"><el-button type="primary" icon="plus" @click="addJump">新增</el-button></div><el-table :data="jumpNodeList" style="width: 100%" highlight-current-row border><el-table-column label="环节名称"><template #default="scope">{{ scope.row.name }}</template></el-table-column><el-table-column fixed="right" label="操作" width="90"><template #default="scope"><el-button type="primary" @click="removeJump(scope.row)">移除</el-button></template></el-table-column></el-table></el-collapse-item></el-collapse><FlowStepSelect ref="flowStepSelectJump" @update="updateJump" /><template #footer><el-button type="primary" @click="save">确 定</el-button><el-button @click="close">取 消</el-button></template></el-drawer>
</template>
<script>
import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue'
import FlowStepSelect from '../dialog/FlowStepSelect.vue'
import { useStore } from '../../stores/index'
let store = useStore()
export default {components: { DictionaryRadioGroup, FlowStepSelect },data() {return {activeName: ['permissionConfig', 'jumpNodeListConfig'],// 权限数据permissionData: [],// 跳转环节jumpNodeList: []}},computed: {visible() {return store.rootNodeConfigVisible},rootNodeConfig() {return store.rootNodeConfig},processDefinitionId() {return store.processDefinitionId}},watch: {rootNodeConfig(value) {// 加载权限设置this.$api.workflow.workflowNodePermissionConfig.getNodePermissionConfig(this.processDefinitionId, value.id).then((res) => {if (res.data) {this.permissionData = res.data// 根据配置更新const permissionConfig = value.config.permissionConfigif (permissionConfig && permissionConfig.length > 0) {this.permissionData.forEach((item) => {permissionConfig.forEach((config) => {if (config.areaCode == item.areaCode) {item.permission = config.permissionreturn}})})}}})// 加载回退环节列表this.backNodeList = value.config.backNodeList// 加载跳转环节列表this.jumpNodeList = value.config.jumpNodeList}},methods: {close() {store.setRootNodeConfigVisible(false)},save() {const permissionConfig = this.permissionData.map((item) => {return {areaCode: item.areaCode,permission: item.permission}})const nodeConfig = Object.assign(store.rootNodeConfig,{config: {permissionConfig: permissionConfig,backNodeList: this.backNodeList,jumpNodeList: this.jumpNodeList}},{ flag: true })store.setRootNodeConfig(nodeConfig)this.close()},addJump() {this.$refs.flowStepSelectJump.init(this.rootNodeConfig.model, this.rootNodeConfig.id, false)},updateJump(jumpNodeList) {if (this.jumpNodeList) {const idList = this.jumpNodeList.map((item) => item.id)jumpNodeList.forEach((item) => {if (!idList.includes(item.id)) {this.jumpNodeList.push(item)}})} else {this.jumpNodeList = jumpNodeList}},removeJump(row) {// 找到要移除的元素的索引let index = this.jumpNodeList.findIndex((item) => item.id === row.id)// 使用splice方法移除该元素if (index !== -1) {this.jumpNodeList.splice(index, 1)}}}
}
</script>
<style scoped></style>

办理环节配置

办理环节要设置多项配置。
image.png

源码

<template><el-drawer:append-to-body="true"title="环节设置"v-model="visible":show-close="false":size="550":before-close="close"destroy-on-close><el-collapse v-model="activeName" style="padding: 0"><el-collapse-item title="人员设置" name="personConfig"><el-formref="form":model="entityData":rules="rules"label-width="120px"label-position="right"style="width: 90%; margin: 0px auto"><!--表单区域 --><el-form-item label="模式" prop="mode"><dictionary-radio-group v-model="entityData.mode" code="NodeMode" /></el-form-item><el-form-item label="指定处理人" prop="setAssigneeFlag"><dictionary-radio-group v-model="entityData.setAssigneeFlag" code="YesOrNo" /></el-form-item><el-form-item label="用户组" prop="userGroup"><UserGroupReference v-model="entityData.userGroup" @my-change="userGroupchange" /></el-form-item><el-form-item label="用户组名称" prop="userGroupName" v-show="false"><el-input v-model="entityData.userGroupName" /></el-form-item></el-form></el-collapse-item><el-collapse-item title="权限设置" name="permissionConfig"><el-table :data="permissionData" style="width: 100%" highlight-current-row border><el-table-column label="区域" width="120"><template #default="scope">{{ scope.row.areaName }}</template></el-table-column><el-table-column label="权限"><template #default="scope"><dictionary-radio-groupv-model="scope.row.permission"code="NodePermissionCode"class="form-item"/></template></el-table-column></el-table></el-collapse-item><el-collapse-item title="回退环节" name="backNodeListConfig"><div class="mb-10px mt-10px"><el-button type="primary" icon="plus" @click="addBack">新增</el-button></div><el-table :data="backNodeList" style="width: 100%" highlight-current-row border><el-table-column label="环节名称"><template #default="scope">{{ scope.row.name }}</template></el-table-column><el-table-column fixed="right" label="操作" width="90"><template #default="scope"><el-button type="primary" @click="removeBack(scope.row)">移除</el-button></template></el-table-column></el-table></el-collapse-item><el-collapse-item title="跳转环节" name="jumpNodeListConfig"><div class="mb-10px mt-10px"><el-button type="primary" icon="plus" @click="addJump">新增</el-button></div><el-table :data="jumpNodeList" style="width: 100%" highlight-current-row border><el-table-column label="环节名称"><template #default="scope">{{ scope.row.name }}</template></el-table-column><el-table-column fixed="right" label="操作" width="90"><template #default="scope"><el-button type="primary" @click="removeJump(scope.row)">移除</el-button></template></el-table-column></el-table></el-collapse-item><el-collapse-item title="监听器" name="listenerListConfig"><div class="mb-10px mt-10px"><el-button type="primary" icon="plus" @click="addListener">新增</el-button></div><el-table :data="listenerList" style="width: 100%" highlight-current-row border><el-table-column label="事件"><template #default="scope">{{ scope.row.eventName }}</template></el-table-column><el-table-column label="名称"><template #default="scope">{{ scope.row.name }}</template></el-table-column><el-table-column fixed="right" label="操作" width="90"><template #default="scope"><el-button type="primary" @click="removeListener(scope.row)">移除</el-button></template></el-table-column></el-table></el-collapse-item></el-collapse><FlowStepSelect ref="flowStepSelectBack" @update="updateBack" /><FlowStepSelect ref="flowStepSelectJump" @update="updateJump" /><FlowListenerSelect ref="flowListenerSelect" @update="updateListener" /><template #footer><el-button type="primary" @click="save">确 定</el-button><el-button @click="close">取 消</el-button></template></el-drawer>
</template>
<script>
import DictionaryRadioGroup from '@/components/abc/DictionarySelect/DictionaryRadioGroup.vue'
import UserGroupReference from '@/modules/system/view/userGroup/treeReference.vue'
import FlowStepSelect from '../dialog/FlowStepSelect.vue'
import FlowListenerSelect from '../dialog/FlowListenerSelect.vue'
import { useStore } from '../../stores/index'
let store = useStore()
const MODULE_CODE = 'workflow'
const ENTITY_TYPE = 'workflowNodeConfig'
export default {name: ENTITY_TYPE + '-modify',components: { DictionaryRadioGroup, UserGroupReference, FlowStepSelect, FlowListenerSelect },props: {modelData: {type: Object}},data() {return {entityType: ENTITY_TYPE,moduleCode: MODULE_CODE,// eslint-disable-next-line no-evalapi: eval('this.$api.' + MODULE_CODE + '.' + ENTITY_TYPE),pageCode: MODULE_CODE + ':' + ENTITY_TYPE + ':',entityData: {},rules: {//前端验证规则mode: [{ required: true, message: '【模式】不能为空', trigger: 'blur' }],setAssigneeFlag: [{ required: true, message: '【指定处理人】不能为空', trigger: 'blur' }],userGroup: [{ required: true, message: '【用户组】不能为空', trigger: 'blur' }]},activeName: ['personConfig','permissionConfig','backNodeListConfig','jumpNodeListConfig','listenerListConfig'],// 权限数据permissionData: [],// 回退环节backNodeList: [],// 跳转环节jumpNodeList: [],// 监听器listenerList: []}},computed: {visible() {return store.handleNodeConfigVisible},handleNodeConfig() {return store.handleNodeConfig}},watch: {handleNodeConfig(value) {// 加载人员设置if (value.config.personConfig) {this.entityData = value.config.personConfig}// 加载权限设置const processDefinitionId = store.processDefinitionIdthis.$api.workflow.workflowNodePermissionConfig.getNodePermissionConfig(processDefinitionId, value.id).then((res) => {if (res.data) {this.permissionData = res.data// 根据配置更新const permissionConfig = value.config.permissionConfigif (permissionConfig && permissionConfig.length > 0) {this.permissionData.forEach((item) => {permissionConfig.forEach((config) => {if (config.areaCode == item.areaCode) {item.permission = config.permissionreturn}})})}}})// 加载回退环节列表this.backNodeList = value.config.backNodeList// 加载跳转环节列表this.jumpNodeList = value.config.jumpNodeList// 加载监听器列表this.listenerList = value.config.listenerList || []}},methods: {close() {store.setHandleNodeConfigVisible(false)},save() {this.$refs.form.validate((valid) => {if (valid) {const permissionConfig = this.permissionData.map((item) => {return {areaCode: item.areaCode,permission: item.permission}})const nodeConfig = Object.assign(store.handleNodeConfig,{config: {personConfig: this.entityData,permissionConfig: permissionConfig,backNodeList: this.backNodeList,jumpNodeList: this.jumpNodeList,listenerList: this.listenerList}},{ flag: true })store.setHandleNodeConfig(nodeConfig)this.close()}})},userGroupchange(id, name) {this.entityData.userGroupName = name},addBack() {this.$refs.flowStepSelectBack.init(this.modelData, this.handleNodeConfig.id, true)},updateBack(backNodeList) {if (this.backNodeList) {const idList = this.backNodeList.map((item) => item.id)backNodeList.forEach((item) => {if (!idList.includes(item.id)) {this.backNodeList.push(item)}})} else {this.backNodeList = backNodeList}},removeBack(row) {// 找到要移除的元素的索引let index = this.backNodeList.findIndex((item) => item.id === row.id)// 使用splice方法移除该元素if (index !== -1) {this.backNodeList.splice(index, 1)}},addJump() {this.$refs.flowStepSelectJump.init(this.modelData, this.handleNodeConfig.id, false)},updateJump(jumpNodeList) {if (this.jumpNodeList) {const idList = this.jumpNodeList.map((item) => item.id)jumpNodeList.forEach((item) => {if (!idList.includes(item.id)) {this.jumpNodeList.push(item)}})} else {this.jumpNodeList = jumpNodeList}},removeJump(row) {// 找到要移除的元素的索引let index = this.jumpNodeList.findIndex((item) => item.id === row.id)// 使用splice方法移除该元素if (index !== -1) {this.jumpNodeList.splice(index, 1)}},addListener() {// 限定只能是任务监听器const category = ['TASK']this.$refs.flowListenerSelect.init(category)},updateListener(listener) {this.listenerList.push(listener)},removeListener(row) {// 找到要移除的元素的索引let index = this.listenerList.findIndex((item) => item.id === row.id)// 使用splice方法移除该元素if (index !== -1) {this.listenerList.splice(index, 1)}}}
}
</script>
<style></style>

条件配置

条件配置比较简单
image.png
源码

<template><el-drawer:append-to-body="true"title="条件设置"v-model="visible":show-close="false":size="550":before-close="close"destroy-on-close><el-formref="form":model="entityData":rules="rules"label-width="120px"label-position="right"style="width: 90%; margin: 0px auto"><!--表单区域 --><el-form-item label="表达式" prop="expression"><el-input v-model="entityData.expression" type="textarea" rows="4" /></el-form-item><el-form-item style="float: right; margin-top: 20px"><el-button type="primary" @click="save">确 定</el-button><el-button @click="close">取 消</el-button></el-form-item></el-form></el-drawer>
</template>
<script>
import { useStore } from '../../stores/index'
let store = useStore()export default {data() {return {entityData: {},rules: {//前端验证规则}}},computed: {visible() {return store.conditionNodeConfigVisible},conditionNodeConfig() {return store.conditionNodeConfig}},watch: {conditionNodeConfig(value) {this.entityData = value}},methods: {close() {store.setConditionNodeConfigVisible(false)},save() {const nodeConfig = Object.assign(store.conditionNodeConfig,{ ...this.entityData },{ flag: true })store.setConditionNodeConfig(nodeConfig)this.close()}}
}
</script>
<style scoped></style>

开发平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:csdn专栏
开源地址:Gitee
开源协议:MIT
开源不易,欢迎收藏、点赞、评论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/174094.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯每日一题2023.10.28

题目描述 递增三元组 - 蓝桥云课 (lanqiao.cn) 题目分析 60分解法&#xff1a; 直接暴力循环每一个数进行比较 #include<bits/stdc.h> using namespace std; const int N 2e5 10; typedef long long ll; ll n, a[N], b[N], c[N], ans; int main() {cin >> n;…

从InnoDB索引的数据结构,去理解索引

从InnoDB索引的数据结构&#xff0c;去理解索引 1、InnoDB 中的 BTree1.1、BTree 的组成1.2、BTree中的数据页 2、聚簇索引2.1、聚簇索引的特点2.2、聚簇索引的结构示例2.3、聚簇索引的优缺点 3、非聚簇索引3.1、非聚簇索引结构示例3.2、关于回表3.3、聚簇索引和非聚簇索引的区…

STM32G030F6P6点灯闪烁

前言 &#xff08;1&#xff09;如果有嵌入式企业需要招聘湖南区域日常实习生&#xff0c;任何区域的暑假Linux驱动实习岗位&#xff0c;可C站直接私聊&#xff0c;或者邮件&#xff1a;zhangyixu02gmail.com&#xff0c;此消息至2025年1月1日前均有效 &#xff08;2&#xff0…

centos ubantu IP一直变化,远程连接不上问题

文章目录 一、为什么IP地址会变1.主机DHCP导致 二、解决IP地址变化1.centos2.ubantu 总结 虚拟机能连接为互联网,但下一次启动IP地址再发生变化,无法使用ssh远程连接 一、为什么IP地址会变 1.主机DHCP导致 虚拟机系统(ubantu,centos…)启动后会向本地申请IP地址租约,租聘的I…

单片机为什么一直用C语言,不用其他编程语言?

单片机为什么一直用C语言&#xff0c;不用其他编程语言&#xff1f; 51 单片机规模小得拮据&#xff0c;C 的优势几乎看不到。放个类型信息进去都费劲&#xff0c;你还想用虚函数&#xff1f;还想模板展开&#xff1f;程序轻松破 10k。最近很多小伙伴找我&#xff0c;说想要一些…

vue3学习(十四)--- vue3中css新特性

文章目录 样式穿透:deep()scoped的原理 插槽选择器:slotted()全局选择器:global()动态绑定CSScss module 样式穿透:deep() 主要是用于修改很多vue常用的组件库&#xff08;element, vant, AntDesigin&#xff09;&#xff0c;虽然配好了样式但是还是需要更改其他的样式就需要用…

Linux系统之file命令的基本使用

Linux系统之file命令的基本使用 一、file命令介绍1.1 Linux简介1.2 file命令简介 二、file命令的使用帮助2.1 file命令的help帮助信息2.2 file命令的语法解释2.3 file命令的man手册 三、文件类型介绍四、file命令的基本使用4.1 查询file版本4.2 显示文件类型4.3 输出时不显示文…

【Truffle】二、自定义合约测试

一、准备测试 上期我们自己安装部署了truffle&#xff0c;并且体验了测试用例的整个测试流程&#xff0c;实际开发中&#xff0c;我们可以对自己的合约进行测试。 我们首先先明白自定义合约测试需要几个文件 合约文件&#xff1a;既然要测试合约&#xff0c;肯定要有合约的源码…

玩转视图变量,轻松实现动态可视化数据分析

前言 在当今数据驱动的世界中&#xff0c;数据分析已经成为了企业和组织中不可或缺的一部分。传统的静态数据分析方法往往无法满足快速变化的业务需求和实时决策的要求。为了更好地应对这些挑战&#xff0c;观测云的动态可视化数据分析应运而生。 在动态可视化数据分析中&…

WLAN的组网架构和工作原理

目录 WLAN的组网架构 FAT AP架构 AC FIT AP架构 敏捷分布式AP 下一代园区网络&#xff1a;智简园区&#xff08;大中型园区网络&#xff09; WLAN工作原理 WLAN工作流程 1.AP上线 &#xff08;1&#xff09;AP获取IP地址&#xff1b; &#xff08;2&#xff09;AP发…

刷题学习记录

sql注入&#xff08;bugkuctf&#xff09; 打开显示一个登录框 照常用admin用户名登录&#xff0c;密码随便填一个&#xff0c;显示密码错误 接着用admin为用户名登录&#xff0c;密码照样随便填,结果显示用户名不存在 题目提示基于布尔的SQL盲注&#xff0c;猜测后端是判断用…

【华为OD:C++机试】Day-1

目录 &#x1f337;1. 统计监控、需要打开多少监控器&#xff1a; &#x1f337;2. 阿里巴巴找黄金宝箱&#xff1a; &#x1f337;3. 事件推送&#xff1a; &#x1f337;4. 分苹果&#xff1a; &#x1f337;5. 乱序整数序列两数之和绝对值最小&#xff1a; &#x1f337;6.卡…

JDK项目分析的经验分享

基本类型的包装类(Character放在最后) String、StringBuffer、StringBuilder、StringJoiner、StringTokenizer(补充正则表达式的知识) CharacterIterator、StringCharacterIterator、CharsetProvider、CharsetEncoder、CharsetDecoder(较难) java.util.function下的函数表…

koa搭建服务器(二)

在上一篇文章已经成功的运行了一个http服务器&#xff0c;接下来就是使用Sequelize ORM&#xff08;官方文档&#xff1a;Sequelize 简介 | Sequelize中文文档 | Sequelize中文网&#xff09;来操作数据库。 1、安装依赖 首先也是需要安装相关的依赖 npm i sequelize npm i …

计算机网络——物理层

目录 一、物理层的基本概念 &#xff08;一&#xff09;四大特征 &#xff08;二&#xff09;两种信号 &#xff08;三&#xff09;调制和编码 &#xff08;四&#xff09;传输介质 1. 双绞线 &#xff08;1&#xff09;屏蔽双绞线 STP &#xff08;2&#xff09;非屏蔽…

Go学习第十三章——Gin入门与路由

Go web框架——Gin入门与路由 1 Gin框架介绍1.1 基础介绍1.2 安装Gin1.3 快速使用 2 路由2.1 基本路由GET请求POST请求 2.2 路由参数2.3 路由分组基本分组带中间件的分组 2.4 重定向 1 Gin框架介绍 github链接&#xff1a;https://github.com/gin-gonic/gin 中文文档&#xf…

基础课13——数据异常处理

数据异常是指数据不符合预期或不符合常识的情况。数据异常可能会导致数据分析结果不准确&#xff0c;甚至是错误&#xff0c;因此在进行数据分析之前需要对数据进行清洗和验证。 常见的数据异常包括缺失值、重复值、异常值等。 缺失值是指数据中存在未知值或未定义的值&#…

详解类生到死的来龙去脉

类生命周期和加载过程 一个类在 JVM 里的生命周期有 7 个阶段&#xff0c;分别是加载&#xff08;Loading&#xff09;、校验&#xff08;Verification&#xff09;、准备&#xff08;Preparation&#xff09;、解析&#xff08;Resolution&#xff09;、初始化&#xff08;Ini…

前端 :用HTML , CSS ,JS 做一个秒表

1.HTML&#xff1a; <body><div id "content"><div id "top"><div id"time">00:00:000</div></div><div id "bottom"><div id "btn_start">开始</div><div …

网络协议--TCP的保活定时器

23.1 引言 许多TCP/IP的初学者会很惊奇地发现可以没有任何数据流通过一个空闲的TCP连接。也就是说&#xff0c;如果TCP连接的双方都没有向对方发送数据&#xff0c;则在两个TCP模块之间不交换任何信息。例如&#xff0c;没有可以在其他网络协议中发现的轮询。这意味着我们可以…