BaseNode.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. <template>
  2. <div :class="{ 'ep-node': true, 'ep-node-arrows': !isStart }">
  3. <div :class="{ 'ep-node-content': true, 'ep-node-error': isError }">
  4. <!-- header -->
  5. <div class="ep-node-header" :style="{ color: config.color, 'background-color': config.bgColor }">
  6. <Icon class="ep-node-icon" :icon="config.icon.name" :color="config.icon.color || '#FFFFFF'" />
  7. <div class="ep-node-header-title">{{ config.title }}</div>
  8. <Icon class="ep-node-close cursor-pointer text-white" :icon="'&#xe83a;'" v-if="canRemoved" @click.stop="removeNode" />
  9. </div>
  10. <!-- body -->
  11. <div class="ep-node-body" @click="showNodeDrawer">
  12. <component ref="node" :is="nodeComponents[props.node.nodeType]" :tempNodeId="tempNodeId" :node="props.node" />
  13. </div>
  14. <!-- 校验错误提示 -->
  15. <div class="ep-node-error-msg" v-if="isError">
  16. <div class="ep-node-error-msg-box">
  17. <icon icon="svg:tips" :width="23" height="23" color="red" @mouseenter="showErrorTips(true)" @mouseleave="showErrorTips(false)" />
  18. <div class="ep-node-error-tips" v-if="errorTips">{{ errorMessage }}</div>
  19. </div>
  20. </div>
  21. </div>
  22. <NodeAdd :node="props.node" />
  23. <!-- 节点配置Drawer -->
  24. <BaseDrawer ref="nodeDrawer" @updateConfig="updateConfig" @cancelUpdateConfig="cancelUpdateConfig" />
  25. </div>
  26. </template>
  27. <script setup lang="ts" name="BaseNode">
  28. import { ref, shallowRef, onMounted, getCurrentInstance, defineAsyncComponent, watch, computed, onUnmounted, inject } from 'vue'
  29. import BaseDrawer from './BaseDrawer.vue'
  30. import NodeAdd from './node_add.vue'
  31. import { nodeConfigData, ProcessConstant } from '../../common/process.data'
  32. import { getUUID } from '../../common/common.util'
  33. import { EasyProcess } from '../../common/process.type'
  34. import { Validator } from '../../common/validator.util'
  35. import NodeStart from '../start/startNode.vue'
  36. import NodeOperation from '../operation/operationNode.vue'
  37. import NodeFault from '../fault/faultNode.vue'
  38. import NodeVoice from '../voice/voiceNode.vue'
  39. const nodeComponents = shallowRef({
  40. start: NodeStart,
  41. operation: NodeOperation,
  42. fault: NodeFault,
  43. voice: NodeVoice
  44. })
  45. const props = withDefaults(defineProps<{ node: EasyProcess.NodeConfig }>(), {})
  46. // 节点配置数据
  47. const config = ref(nodeConfigData[props.node.nodeType])
  48. const isStart = computed(() => props.node.nodeType == 'start')
  49. const validator = inject<Validator>(ProcessConstant.KEY_VALIDATOR)
  50. watch(
  51. () => props.node,
  52. () => {
  53. config.value = nodeConfigData[props.node.nodeType]
  54. validator?.validate()
  55. }
  56. )
  57. // ---------- 节点数据校验 ---------- start
  58. const errorTips = ref<boolean>(false)
  59. const showErrorTips = (flag: boolean) => (errorTips.value = flag)
  60. const errorMessage = computed<string>(() => {
  61. let result = validator?.getResult(tempNodeId)
  62. return result ? (result.message ?? '') : ''
  63. })
  64. const isError = computed<boolean>(() => {
  65. let result = validator?.getResult(tempNodeId)
  66. return result ? !result.valid : false
  67. })
  68. // ---------- 节点数据校验 --------- end
  69. // 节点是否可以被移除
  70. const canRemoved = computed(() => {
  71. if (isLastCondition.value) return false
  72. return config.value.canRemoved
  73. })
  74. // 是否是最后一个条件节点
  75. const isLastCondition = computed<boolean>(() => {
  76. return props.node.nodeType == 'condition' && props.node.isLastCondition
  77. })
  78. const { proxy } = getCurrentInstance()
  79. // 生成临时节点ID
  80. const tempNodeId = getUUID()
  81. onUnmounted(async () => {
  82. validator?.remove(tempNodeId)
  83. validator?.validate()
  84. })
  85. // 显示节点配置组件
  86. const showNodeDrawer = () => {
  87. if (config.value.hasDrawer) {
  88. if (isLastCondition()) {
  89. return false
  90. }
  91. proxy.$refs.nodeDrawer.show(props.node)
  92. }
  93. }
  94. // 移除当前节点
  95. const emit = defineEmits(['removeNode'])
  96. const removeNode = () => {
  97. emit('removeNode')
  98. }
  99. // 更新节点配置属性
  100. const updateConfig = (data: EasyProcess.NodeConfig) => {
  101. props.node = data
  102. validator?.validate()
  103. }
  104. // 取消更新节点配置属性
  105. const cancelUpdateConfig = () => validator?.validate()
  106. </script>
  107. <style lang="scss" scoped>
  108. .ep-node {
  109. position: relative;
  110. display: flex;
  111. flex-direction: column;
  112. align-items: center;
  113. margin: 0 100px;
  114. }
  115. .ep-node-arrows {
  116. &::before {
  117. position: absolute;
  118. top: -12px;
  119. left: 50%;
  120. width: 0;
  121. height: 4px;
  122. content: '';
  123. background: #f5f5f7;
  124. border-color: #cacaca transparent transparent;
  125. border-style: solid;
  126. border-width: 8px 6px 4px;
  127. -webkit-transform: translateX(-50%);
  128. transform: translateX(-50%);
  129. }
  130. }
  131. .ep-node-content {
  132. position: relative;
  133. box-sizing: border-box;
  134. display: flex;
  135. flex-direction: column;
  136. align-items: center;
  137. width: 200px;
  138. min-height: 80px;
  139. font-size: 14px;
  140. word-break: break-word;
  141. white-space: normal;
  142. border-radius: 5px;
  143. box-shadow: 5px 5px 10px 2px rgb(0 0 0 / 20%);
  144. transition-duration: 0.1s;
  145. transition-property: box-shadow;
  146. &:hover {
  147. box-shadow: 5px 5px 12px 5px rgb(0 0 0 / 20%);
  148. }
  149. .ep-node-header {
  150. box-sizing: border-box;
  151. display: flex;
  152. align-items: center;
  153. width: 100%;
  154. padding: 5px 10px;
  155. color: #fff;
  156. background-color: #1e83e9;
  157. border-top-left-radius: 5px;
  158. border-top-right-radius: 5px;
  159. .ep-node-icon {
  160. font-size: 16px;
  161. }
  162. .ep-node-header-title {
  163. flex: 1;
  164. margin-left: 4px;
  165. }
  166. .ep-node-close {
  167. font-size: 16px;
  168. text-align: center;
  169. cursor: pointer;
  170. }
  171. }
  172. .ep-node-body {
  173. box-sizing: border-box;
  174. display: flex;
  175. flex: 1;
  176. flex-direction: column;
  177. justify-content: center;
  178. width: 100%;
  179. padding: 10px;
  180. color: #5a5e66;
  181. cursor: pointer;
  182. background-color: #fff;
  183. border-bottom-right-radius: 5px;
  184. border-bottom-left-radius: 5px;
  185. }
  186. }
  187. .ep-node-error {
  188. box-shadow: 0 0 10px 2px rgb(243 5 5 / 50%);
  189. &:hover {
  190. box-shadow: 0 0 12px 4px rgb(243 5 5 / 50%);
  191. }
  192. }
  193. .ep-node-error-msg {
  194. position: absolute;
  195. top: 50%;
  196. right: -55px;
  197. z-index: 1;
  198. transform: translateY(-50%);
  199. .ep-node-error-msg-box {
  200. position: relative;
  201. .ep-node-error-icon {
  202. width: 25px;
  203. height: 25px;
  204. cursor: pointer;
  205. }
  206. .ep-node-error-tips {
  207. position: absolute;
  208. top: 50%;
  209. left: 45px;
  210. z-index: 3;
  211. display: flex;
  212. min-width: 150px;
  213. padding: 16px;
  214. background-color: #fff;
  215. border-radius: 5px;
  216. box-shadow: 5px 5px 10px 2px rgb(0 0 0 / 20%);
  217. transform: translateY(-50%);
  218. &::before {
  219. position: absolute;
  220. top: 50%;
  221. left: -20px;
  222. width: 0;
  223. height: 0;
  224. content: '';
  225. border: 10px solid;
  226. border-color: transparent #fff transparent transparent;
  227. transform: translateY(-50%);
  228. }
  229. }
  230. }
  231. }
  232. .ep-node-move {
  233. position: absolute;
  234. top: 50%;
  235. transform: translateY(-50%);
  236. }
  237. .ep-node-move-left {
  238. left: -30px;
  239. }
  240. .ep-node-move-right {
  241. right: -30px;
  242. }
  243. .ep-node-move-icon {
  244. width: 35px;
  245. height: 35px;
  246. cursor: pointer;
  247. }
  248. </style>