背景
对于流程设置不友好的问题,国内钉钉另行设计与实现了一套流程建模模式,跟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'">
<div
class="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);`">
<input
v-if="isInput"
type="text"
class="ant-input editable-title-input"
@blur="blurEvent()"
@focus="$event.currentTarget.select()"
v-focus
v-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">
<span
class="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">
<input
v-if="isInputList[index]"
type="text"
class="ant-input editable-title-input"
@blur="blurEvent(index)"
@focus="$event.currentTarget.select()"
v-focus
v-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>
<div
class="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().uid
let 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-props
props.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-props
props.modelValue.branchList[index].name = props.modelValue.branchList[index].name || '条件'
} else {
isInput.value = false
// eslint-disable-next-line vue/no-mutating-props
props.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-props
props.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-props
props.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-props
props.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-props
props.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和flag
const conditionNodeConfig = {
expression: myConfig.expression
}
// 获取条件节点标识
const conditionNodeId = myConfig.nodeId
let newModelValue = props.modelValue
newModelValue.branchList.forEach((element) => {
if (element.id === conditionNodeId) {
element.config = conditionNodeConfig
return
}
})
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 = false
const data = {
name: '办理环节',
id: 'node' + uuid(),
type: 'HANDLE',
config: {
personConfig: {},
permissionConfig: []
},
child: {}
}
emits('update:childNodeP', data)
}
const addConditionBranch = () => {
visible.value = false
const 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-group
v-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.permissionConfig
if (permissionConfig && permissionConfig.length > 0) {
this.permissionData.forEach((item) => {
permissionConfig.forEach((config) => {
if (config.areaCode == item.areaCode) {
item.permission = config.permission
return
}
})
})
}
}
})
// 加载回退环节列表
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-form
ref="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-group
v-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-eval
api: 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.processDefinitionId
this.$api.workflow.workflowNodePermissionConfig
.getNodePermissionConfig(processDefinitionId, value.id)
.then((res) => {
if (res.data) {
this.permissionData = res.data
// 根据配置更新
const permissionConfig = value.config.permissionConfig
if (permissionConfig && permissionConfig.length > 0) {
this.permissionData.forEach((item) => {
permissionConfig.forEach((config) => {
if (config.areaCode == item.areaCode) {
item.permission = config.permission
return
}
})
})
}
}
})
// 加载回退环节列表
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-form
ref="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
开源不易,欢迎收藏、点赞、评论。