InclusiveNode.vue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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="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 { SimpleFlowNode, NodeType, NODE_DEFAULT_TEXT } from '../consts'
  112. import { useTaskStatusClass } from '../node'
  113. import { getDefaultInclusiveConditionNodeName } from '../utils'
  114. import { generateUUID } from '@/utils'
  115. import ConditionNodeConfig from '../nodes-config/ConditionNodeConfig.vue'
  116. const { proxy } = getCurrentInstance() as any
  117. defineOptions({
  118. name: 'InclusiveNode'
  119. })
  120. const props = defineProps({
  121. flowNode: {
  122. type: Object as () => SimpleFlowNode,
  123. required: true
  124. }
  125. })
  126. // 定义事件,更新父组件
  127. const emits = defineEmits<{
  128. 'update:modelValue': [node: SimpleFlowNode | undefined]
  129. 'find:parentNode': [nodeList: SimpleFlowNode[], nodeType: number]
  130. 'find:recursiveFindParentNode': [
  131. nodeList: SimpleFlowNode[],
  132. curentNode: SimpleFlowNode,
  133. nodeType: number
  134. ]
  135. }>()
  136. // 是否只读
  137. const readonly = inject<Boolean>('readonly')
  138. const currentNode = ref<SimpleFlowNode>(props.flowNode)
  139. watch(
  140. () => props.flowNode,
  141. (newValue) => {
  142. currentNode.value = newValue
  143. }
  144. )
  145. const showInputs = ref<boolean[]>([])
  146. // 失去焦点
  147. const blurEvent = (index: number) => {
  148. showInputs.value[index] = false
  149. const conditionNode = currentNode.value.conditionNodes?.at(index) as SimpleFlowNode
  150. conditionNode.name =
  151. conditionNode.name || getDefaultInclusiveConditionNodeName(index, conditionNode.defaultFlow)
  152. }
  153. // 点击条件名称
  154. const clickEvent = (index: number) => {
  155. showInputs.value[index] = true
  156. }
  157. const conditionNodeConfig = (nodeId: string) => {
  158. if (readonly) {
  159. return
  160. }
  161. const conditionNode = proxy.$refs[nodeId][0]
  162. conditionNode.open()
  163. }
  164. // 新增条件
  165. const addCondition = () => {
  166. const conditionNodes = currentNode.value.conditionNodes
  167. if (conditionNodes) {
  168. const len = conditionNodes.length
  169. let lastIndex = len - 1
  170. const conditionData: SimpleFlowNode = {
  171. id: 'Flow_' + generateUUID(),
  172. name: '包容条件' + len,
  173. showText: '',
  174. type: NodeType.CONDITION_NODE,
  175. childNode: undefined,
  176. conditionNodes: [],
  177. conditionType: 1,
  178. defaultFlow: false
  179. }
  180. conditionNodes.splice(lastIndex, 0, conditionData)
  181. }
  182. }
  183. // 删除条件
  184. const deleteCondition = (index: number) => {
  185. const conditionNodes = currentNode.value.conditionNodes
  186. if (conditionNodes) {
  187. conditionNodes.splice(index, 1)
  188. if (conditionNodes.length == 1) {
  189. const childNode = currentNode.value.childNode
  190. // 更新此节点为后续孩子节点
  191. emits('update:modelValue', childNode)
  192. }
  193. }
  194. }
  195. // 移动节点
  196. const moveNode = (index: number, to: number) => {
  197. // -1 :向左 1: 向右
  198. if (currentNode.value.conditionNodes) {
  199. currentNode.value.conditionNodes[index] = currentNode.value.conditionNodes.splice(
  200. index + to,
  201. 1,
  202. currentNode.value.conditionNodes[index]
  203. )[0]
  204. }
  205. }
  206. // 递归从父节点中查询匹配的节点
  207. const recursiveFindParentNode = (
  208. nodeList: SimpleFlowNode[],
  209. node: SimpleFlowNode,
  210. nodeType: number
  211. ) => {
  212. if (!node || node.type === NodeType.START_USER_NODE) {
  213. return
  214. }
  215. if (node.type === nodeType) {
  216. nodeList.push(node)
  217. }
  218. // 条件节点 (NodeType.CONDITION_NODE) 比较特殊。需要调用其父节点条件分支节点(NodeType.INCLUSIVE_BRANCH_NODE) 继续查找
  219. emits('find:parentNode', nodeList, nodeType)
  220. }
  221. </script>
  222. <style lang="scss" scoped></style>