背景
对于流程设置不友好的问题,国内钉钉另行设计与实现了一套流程建模模式,跟bpmn规范无关,有人仿照实现了下,并做了开源(https://github.com/StavinLi/Workflow-Vue3),效果图如下:
实现大致原理是基于无限嵌套的子节点,输出json数据,传给后端,后端进行解析后,调用Camunda引擎的api,转换成流程模型后持久化。
该系列通过十几篇对Camunda工作流引擎集成做了相对比较详细的设计和实现说明工作,在集成过程中,对原前端Workflow-Vue3做了大量的改造,前面零零星星的也随着部分博客提及过,今天做一个全局性的回顾和整理。
节点类型
目前平台已经实现的节点有三类,发起节点、办理节点、路由节点。
发起节点与办理节点对应着Camunda中的用户任务类型,路由节点对应着Camunda中的兼容网关类型。
后续可以在此基础上进行功能扩展,使用Camunda的服务节点,构建不同用途的前端节点类型,如发送通知(邮件、短信等)、流程归档等。
节点配置
流程是有多个节点组成的,但仅有节点,以及连接节点的边还不够,每个节点需要进行必要的配置。
对于不同类型的节点,配置也有差异。
基础属性
基础属性是节点的通用公共数据结构,所有节点类型都一样,如下图所示:
{id:'',name: '填报',type: 'FIRST_NODE',config: {},branchList: [],child: {}
}
各属性含义如下
属性 | 类型 | 含义 | 说明 |
---|---|---|---|
id | String | 标识 | 唯一性标识,由前端随机生成 |
name | String | 名称 | 节点名称 |
type | String | 类型 | ROOT(发起人,根节点)、HANDLE(办理)、INCLUSIVE_GATEWAY(路由) |
config | Object | 配置 | 节点的设置项内容,不同类型的节点设置内容不同,从而数据结构不同 |
child | Object | 子节点 | 当前节点的子对象 |
branchList | Array | 分支 | 当type 为 INCLUSIVE_GATEWAY 时,该项存在,存放分支数据 |
办理人设置
针对用户任务节点,设置该环节是会签还是单人处理,是否需要用户指定处理人,以及关联的用户组(角色)。
数据结构示例:
"personConfig": {"mode": "NORMAL","setAssigneeFlag": "YES","userGroup": "99","userGroupName": "系统管理员"}
属性说明:
属性 | 类型 | 含义 | 说明 |
---|---|---|---|
mode | String | 模式 | COUNTERSIGN:会签;NORMAL:普通 |
setAssigneeFlag | String | 是否指定人 | YES:是;NO:否 |
userGroup | String | 用户组(角色) | 调用平台的用户组单选组件,选择一个用户组,用户组的标识 |
userGroupName | String | 用户组(角色)的名称 | 用户组的名称 |
表单权限设置
控制该环节表单不同区域的可见性与可编辑性,对发起节点(流程首环节,通常用于填报)和用户任务节点需要设置。
数据结构示例:
"config": {"permissionConfig": [{"areaCode": "applyArea","permission": "EDITABLE"}, {"areaCode": "organizationApproval","permission": "READONLY"}, {"areaCode": "hrApproval","permission": "INVISIBLE"}]}
属性说明:
属性 | 类型 | 含义 | 说明 |
---|---|---|---|
areaCode | String | 区域编码 | 在平台权限项管理中定义与维护 |
permission | String | 权限编码 | INVISIBLE:不可见;READONLY:只读;EDITABLE:可编辑 |
回退节点设置
当前环节可回退到的节点定义。
数据结构示例:
"backNodeList": [{"id": "root","name": "填报"}, {"id": "node1938_8b28_c3ed_030f","name": "部门审批"}]
属性说明:
属性 | 类型 | 含义 | 说明 |
---|---|---|---|
id | String | 环节标识 | 发起环节使用统一约定的root |
name | String | 环节名称 |
跳转节点设置
当前环节可跳转到的节点定义,与回退节点配置类似。
数据结构示例:
"jumpNodeList": [{"id": "root","name": "填报"}, {"id": "node1938_8b28_c3ed_030f","name": "部门审批"}]
属性说明:
属性 | 类型 | 含义 | 说明 |
---|---|---|---|
id | String | 环节标识 | 发起环节使用统一约定的root |
name | String | 环节名称 |
监听器配置
环节监听器的配置,用于实现特定的业务逻辑
数据结构示例:
"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": "完成"
}]
属性说明:
属性 | 类型 | 含义 | 说明 |
---|---|---|---|
category | String | 分类 | TASK:任务监听器;EXECUTION:执行监听器 |
type | String | 类型 | CLASS:类;EXPRESSION:表达式;DELEGATE_EXPRESSION:委托表达式 |
name | String | 名称 | 监听器名称 |
code | String | 编码 | 取决于type,如为类,则本属性放完整的类名 |
event | String | 事件编码 | 取决于category,如为任务监听器,则取值如下: |
CREATE:创建;ASSIGNMENT:指派;COMPLETE:完成;DELETE:删除 | |||
eventName | String | 事件名称 | 参见上面事件编码的枚举值定义中文部分 |
条件配置
设置条件,路由节点后续的分支节点需要配置。
数据结构示例:
{"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": []}]
}
属性说明:
属性 | 类型 | 含义 | 说明 |
---|---|---|---|
expression | String | 表达式 | 示例:${total<=3} |
组件改造
如上面所示,我们对前端数据结构进行了大调整,相应的,原组件也需要进行相应的调整,差不多只留了架子,内部大换血,主要实现如下:
流程建模组件nodeWrap
效果图
源码
<!-- 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)"><</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)">></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
效果图
源码
<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>
发起环节配置
发起环节相对比较特殊,不需要设置处理人员和回退环节,只需要设置表单权限和跳转环节。
源码
<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>
办理环节配置
办理环节要设置多项配置。
源码
<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>
条件配置
条件配置比较简单
源码
<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
开源不易,欢迎收藏、点赞、评论。