InclusiveNode.vue 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. <template>
  2. <div class="branch-node-wrapper">
  3. <div class="branch-node-container">
  4. <div
  5. v-if="readonly"
  6. class="branch-node-readonly"
  7. :class="`${useTaskStatusClass(currentNode?.activityStatus)}`"
  8. >
  9. <span class="iconfont icon-inclusive icon-size inclusive"></span>
  10. </div>
  11. <el-button v-else class="branch-node-add" color="#345da2" @click="addCondition" plain
  12. >添加条件</el-button
  13. >
  14. <div
  15. class="branch-node-item"
  16. v-for="(item, index) in currentNode.conditionNodes"
  17. :key="index"
  18. >
  19. <template v-if="index == 0">
  20. <div class="branch-line-first-top"> </div>
  21. <div class="branch-line-first-bottom"></div>
  22. </template>
  23. <template v-if="index + 1 == currentNode.conditionNodes?.length">
  24. <div class="branch-line-last-top"></div>
  25. <div class="branch-line-last-bottom"></div>
  26. </template>
  27. <div class="node-wrapper">
  28. <div class="node-container">
  29. <div
  30. class="node-box"
  31. :class="[
  32. { 'node-config-error': !item.showText },
  33. `${useTaskStatusClass(item.activityStatus)}`
  34. ]"
  35. >
  36. <div class="branch-node-title-container">
  37. <div v-if="!readonly && showInputs[index]">
  38. <input
  39. type="text"
  40. class="editable-title-input"
  41. @blur="blurEvent(index)"
  42. v-mountedFocus
  43. v-model="item.name"
  44. />
  45. </div>
  46. <div v-else class="branch-title" @click="clickEvent(index)"> {{ item.name }} </div>
  47. </div>
  48. <div class="branch-node-content" @click="conditionNodeConfig(item.id)">
  49. <div class="branch-node-text" :title="item.showText" v-if="item.showText">
  50. {{ item.showText }}
  51. </div>
  52. <div class="branch-node-text" v-else>
  53. {{ NODE_DEFAULT_TEXT.get(NodeType.CONDITION_NODE) }}
  54. </div>
  55. </div>
  56. <div
  57. class="node-toolbar"
  58. v-if="!readonly && index + 1 !== currentNode.conditionNodes?.length"
  59. >
  60. <div class="toolbar-icon">
  61. <Icon
  62. color="#0089ff"
  63. icon="ep:circle-close-filled"
  64. :size="18"
  65. @click="deleteCondition(index)"
  66. />
  67. </div>
  68. </div>
  69. <div
  70. class="branch-node-move move-node-left"
  71. v-if="!readonly && index != 0 && index + 1 !== currentNode.conditionNodes?.length"
  72. @click="moveNode(index, -1)"
  73. >
  74. <Icon icon="ep:arrow-left" />
  75. </div>
  76. <div
  77. class="branch-node-move move-node-right"
  78. v-if="
  79. !readonly &&
  80. currentNode.conditionNodes &&
  81. index < currentNode.conditionNodes.length - 2
  82. "
  83. @click="moveNode(index, 1)"
  84. >
  85. <Icon icon="ep:arrow-right" />
  86. </div>
  87. </div>
  88. <NodeHandler v-model:child-node="item.childNode" :current-node="item" />
  89. </div>
  90. </div>
  91. <ConditionNodeConfig :node-index="index" :condition-node="item" :ref="item.id" />
  92. <!-- 递归显示子节点 -->
  93. <ProcessNodeTree
  94. v-if="item && item.childNode"
  95. :parent-node="item"
  96. v-model:flow-node="item.childNode"
  97. @find:recursive-find-parent-node="recursiveFindParentNode"
  98. />
  99. </div>
  100. </div>
  101. <NodeHandler
  102. v-if="currentNode"
  103. v-model:child-node="currentNode.childNode"
  104. :current-node="currentNode"
  105. />
  106. </div>
  107. </template>
  108. <script setup lang="ts">
  109. import NodeHandler from '../NodeHandler.vue'
  110. import ProcessNodeTree from '../ProcessNodeTree.vue'
  111. import {
  112. SimpleFlowNode,
  113. NodeType,
  114. ConditionType,
  115. DEFAULT_CONDITION_GROUP_VALUE,
  116. NODE_DEFAULT_TEXT
  117. } from '../consts'
  118. import { useTaskStatusClass } from '../node'
  119. import { getDefaultInclusiveConditionNodeName } from '../utils'
  120. import { generateUUID } from '@/utils'
  121. import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
  122. import { cloneDeep } from 'lodash-es'
  123. const { proxy } = getCurrentInstance() as any
  124. defineOptions({
  125. name: 'InclusiveNode'
  126. })
  127. const props = defineProps({
  128. flowNode: {
  129. type: Object as () => SimpleFlowNode,
  130. required: true
  131. }
  132. })
  133. // 定义事件,更新父组件
  134. const emits = defineEmits<{
  135. 'update:modelValue': [node: SimpleFlowNode | undefined]
  136. 'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
  137. 'find:recursiveFindParentNode': [
  138. nodeList: SimpleFlowNode[],
  139. curentNode: SimpleFlowNode,
  140. nodeType: number
  141. ]
  142. }>()
  143. // 是否只读
  144. const readonly = inject<Boolean>('readonly')
  145. const currentNode = ref<SimpleFlowNode>(props.flowNode)
  146. watch(
  147. () => props.flowNode,
  148. (newValue) => {
  149. currentNode.value = newValue
  150. }
  151. )
  152. const showInputs = ref<boolean[]>([])
  153. // 失去焦点
  154. const blurEvent = (index: number) => {
  155. showInputs.value[index] = false
  156. const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
  157. conditionNode.name =
  158. conditionNode.name ||
  159. getDefaultInclusiveConditionNodeName(index, conditionNode.conditionSetting?.defaultFlow)
  160. }
  161. // 点击条件名称
  162. const clickEvent = (index: number) => {
  163. showInputs.value[index] = true
  164. }
  165. const conditionNodeConfig = (nodeId: string) => {
  166. if (readonly) {
  167. return
  168. }
  169. const conditionNode = proxy.$refs[nodeId][0]
  170. conditionNode.open()
  171. }
  172. // 新增条件
  173. const addCondition = () => {
  174. const conditionNodes = currentNode.value.conditionNodes
  175. if (conditionNodes) {
  176. const len = conditionNodes.length
  177. let lastIndex = len - 1
  178. const conditionData: SimpleFlowNode = {
  179. id: 'Flow_' + generateUUID(),
  180. name: '包容条件' + len,
  181. showText: '',
  182. type: NodeType.CONDITION_NODE,
  183. childNode: undefined,
  184. conditionNodes: [],
  185. conditionSetting: {
  186. defaultFlow: false,
  187. conditionType: ConditionType.RULE,
  188. conditionGroups: cloneDeep(DEFAULT_CONDITION_GROUP_VALUE)
  189. }
  190. }
  191. conditionNodes.splice(lastIndex, 0, conditionData)
  192. }
  193. }
  194. // 删除条件
  195. const deleteCondition = (index: number) => {
  196. const conditionNodes = currentNode.value.conditionNodes
  197. if (conditionNodes) {
  198. conditionNodes.splice(index, 1)
  199. if (conditionNodes.length == 1) {
  200. const childNode = currentNode.value.childNode
  201. // 更新此节点为后续孩子节点
  202. emits('update:modelValue', childNode)
  203. }
  204. }
  205. }
  206. // 移动节点
  207. const moveNode = (index: number, to: number) => {
  208. // -1 :向左 1: 向右
  209. if (currentNode.value.conditionNodes) {
  210. currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
  211. index + to,
  212. 1,
  213. currentNode.value.conditionNodes[index]
  214. )[0]
  215. }
  216. }
  217. // 递归从父节点中查询匹配的节点
  218. const recursiveFindParentNode = (
  219. nodeList: SimpleFlowNode[],
  220. node: SimpleFlowNode,
  221. nodeType: number
  222. ) => {
  223. if (!node || node.type === NodeType.START_USER_NODE) {
  224. return
  225. }
  226. if (node.type === nodeType) {
  227. nodeList.push(node)
  228. }
  229. // 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.INCLUSIVE_BRANCH_NODE) 继续查找
  230. emits('find:parentNode', nodeList, nodeType)
  231. }
  232. </script>
  233. <style lang="scss" scoped></style>