Files
yudao-ui-admin-vben/apps/web-antd/src/components/simple-process-design/components/nodes/exclusive-node.vue
2025-06-15 15:53:12 +08:00

309 lines
9.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { SimpleFlowNode } from '../../consts';
import { getCurrentInstance, inject, nextTick, ref, watch } from 'vue';
import { IconifyIcon } from '@vben/icons';
import { cloneDeep, buildShortUUID as generateUUID } from '@vben/utils';
import { Button, Input } from 'ant-design-vue';
import { BpmNodeTypeEnum } from '#/utils';
import {
ConditionType,
DEFAULT_CONDITION_GROUP_VALUE,
NODE_DEFAULT_TEXT,
} from '../../consts';
import { getDefaultConditionNodeName, useTaskStatusClass } from '../../helpers';
import ConditionNodeConfig from '../nodes-config/condition-node-config.vue';
import ProcessNodeTree from '../process-node-tree.vue';
import NodeHandler from './node-handler.vue';
defineOptions({ name: 'ExclusiveNode' });
const props = defineProps({
flowNode: {
type: Object as () => SimpleFlowNode,
required: true,
},
});
// 定义事件,更新父组件
const emits = defineEmits<{
findParentNode: [nodeList: SimpleFlowNode[], nodeType: number];
recursiveFindParentNode: [
nodeList: SimpleFlowNode[],
curentNode: SimpleFlowNode,
nodeType: number,
];
'update:modelValue': [node: SimpleFlowNode | undefined];
}>();
const { proxy } = getCurrentInstance() as any;
// 是否只读
const readonly = inject<Boolean>('readonly');
const currentNode = ref<SimpleFlowNode>(props.flowNode);
watch(
() => props.flowNode,
(newValue) => {
currentNode.value = newValue;
},
);
// 条件节点名称输入框引用
const inputRefs = ref<HTMLInputElement[]>([]);
// 节点名称输入框显示状态
const showInputs = ref<boolean[]>([]);
// 监听显示状态变化
watch(
showInputs,
(newValues) => {
// 当状态为 true 时, 自动聚焦
newValues.forEach((value, index) => {
if (value) {
// 当显示状态从 false 变为 true 时, 自动聚焦
nextTick(() => {
inputRefs.value[index]?.focus();
});
}
});
},
{ deep: true },
);
// 修改节点名称
function changeNodeName(index: number) {
showInputs.value[index] = false;
const conditionNode = currentNode.value.conditionNodes?.at(
index,
) as SimpleFlowNode;
conditionNode.name =
conditionNode.name ||
getDefaultConditionNodeName(
index,
conditionNode.conditionSetting?.defaultFlow,
);
}
// 点击条件名称
function clickEvent(index: number) {
showInputs.value[index] = true;
}
function conditionNodeConfig(nodeId: string) {
if (readonly) {
return;
}
const conditionNode = proxy.$refs[nodeId][0];
conditionNode.open();
}
// 新增条件
function addCondition() {
const conditionNodes = currentNode.value.conditionNodes;
if (conditionNodes) {
const len = conditionNodes.length;
const lastIndex = len - 1;
const conditionData: SimpleFlowNode = {
id: `Flow_${generateUUID()}`,
name: `条件${len}`,
showText: '',
type: BpmNodeTypeEnum.CONDITION_NODE,
childNode: undefined,
conditionNodes: [],
conditionSetting: {
defaultFlow: false,
conditionType: ConditionType.RULE,
conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE),
},
};
conditionNodes.splice(lastIndex, 0, conditionData);
}
}
// 删除条件
function deleteCondition(index: number) {
const conditionNodes = currentNode.value.conditionNodes;
if (conditionNodes) {
conditionNodes.splice(index, 1);
if (conditionNodes.length === 1) {
const childNode = currentNode.value.childNode;
// 更新此节点为后续孩子节点
emits('update:modelValue', childNode);
}
}
}
// 移动节点
function moveNode(index: number, to: number) {
// -1 :向左 1 向右
if (
currentNode.value.conditionNodes &&
currentNode.value.conditionNodes[index]
) {
currentNode.value.conditionNodes[index] =
currentNode.value.conditionNodes.splice(
index + to,
1,
currentNode.value.conditionNodes[index],
)[0] as SimpleFlowNode;
}
}
// 递归从父节点中查询匹配的节点
function recursiveFindParentNode(
nodeList: SimpleFlowNode[],
node: SimpleFlowNode,
nodeType: number,
) {
if (!node || node.type === BpmNodeTypeEnum.START_USER_NODE) {
return;
}
if (node.type === nodeType) {
nodeList.push(node);
}
// 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点NodeType.EXCLUSIVE_NODE) 继续查找
emits('findParentNode', nodeList, nodeType);
}
</script>
<template>
<div class="branch-node-wrapper">
<div class="branch-node-container">
<div
v-if="readonly"
class="branch-node-readonly"
:class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
>
<span class="iconfont icon-exclusive icon-size condition"></span>
</div>
<Button v-else class="branch-node-add" @click="addCondition">
添加条件
</Button>
<!-- 排他网关节点下面可以多个分支每个分支第一个节点是条件节点 NodeType.CONDITION_NODE -->
<div
class="branch-node-item"
v-for="(item, index) in currentNode.conditionNodes"
:key="index"
>
<template v-if="index === 0">
<div class="branch-line-first-top"></div>
<div class="branch-line-first-bottom"></div>
</template>
<template v-if="index + 1 === currentNode.conditionNodes?.length">
<div class="branch-line-last-top"></div>
<div class="branch-line-last-bottom"></div>
</template>
<div class="node-wrapper">
<div class="node-container">
<div
class="node-box"
:class="[
{ 'node-config-error': !item.showText },
`${useTaskStatusClass(item.activityStatus)}`,
]"
>
<div class="branch-node-title-container">
<div v-if="!readonly && showInputs[index]">
<Input
:ref="
(el) => {
inputRefs[index] = el as HTMLInputElement;
}
"
type="text"
class="editable-title-input"
@blur="changeNodeName(index)"
@press-enter="changeNodeName(index)"
v-model:value="item.name"
/>
</div>
<div v-else class="branch-title" @click="clickEvent(index)">
{{ item.name }}
</div>
<div class="branch-priority">优先级{{ index + 1 }}</div>
</div>
<div
class="branch-node-content"
@click="conditionNodeConfig(item.id)"
>
<div
class="branch-node-text"
:title="item.showText"
v-if="item.showText"
>
{{ item.showText }}
</div>
<div class="branch-node-text" v-else>
{{ NODE_DEFAULT_TEXT.get(BpmNodeTypeEnum.CONDITION_NODE) }}
</div>
</div>
<div
class="node-toolbar"
v-if="
!readonly && index + 1 !== currentNode.conditionNodes?.length
"
>
<div class="toolbar-icon">
<IconifyIcon
color="#0089ff"
icon="lucide:circle-x"
:size="18"
@click="deleteCondition(index)"
/>
</div>
</div>
<div
class="branch-node-move move-node-left"
v-if="
!readonly &&
index !== 0 &&
index + 1 !== currentNode.conditionNodes?.length
"
@click="moveNode(index, -1)"
>
<IconifyIcon icon="lucide:chevron-left" />
</div>
<div
class="branch-node-move move-node-right"
v-if="
!readonly &&
currentNode.conditionNodes &&
index < currentNode.conditionNodes.length - 2
"
@click="moveNode(index, 1)"
>
<IconifyIcon icon="lucide:chevron-right" />
</div>
</div>
<NodeHandler
v-model:child-node="item.childNode"
:current-node="item"
/>
</div>
</div>
<!-- 条件节点配置 -->
<ConditionNodeConfig
:node-index="index"
:condition-node="item"
:ref="item.id"
/>
<!-- 递归显示子节点 -->
<ProcessNodeTree
v-if="item && item.childNode"
:parent-node="item"
v-model:flow-node="item.childNode"
@recursive-find-parent-node="recursiveFindParentNode"
/>
</div>
</div>
<NodeHandler
v-if="currentNode"
v-model:child-node="currentNode.childNode"
:current-node="currentNode"
/>
</div>
</template>