ConditionNode.vue 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. <!--
  2. * @Author: liuJie
  3. * @Date: 2026-01-25 16:56:14
  4. * @LastEditors: liuJie
  5. * @LastEditTime: 2026-01-25 19:03:49
  6. * @Describe: 条件节点
  7. -->
  8. <script setup lang="ts">
  9. import { Position } from '@vue-flow/core'
  10. import CanvasHandle from '../handles/CanvasHandle.vue'
  11. import { Icon } from '@repo/ui'
  12. interface Condition {
  13. id: string
  14. name: string
  15. expression: string
  16. priority?: number
  17. }
  18. interface Props {
  19. data: {
  20. label?: string
  21. description?: string
  22. conditions?: Condition[]
  23. defaultBranch?: string
  24. [key: string]: any
  25. }
  26. selected?: boolean
  27. }
  28. const props = withDefaults(defineProps<Props>(), {
  29. selected: false
  30. })
  31. // 如果没有条件,至少显示默认分支
  32. const branches =
  33. props.data.conditions && props.data.conditions.length > 0
  34. ? props.data.conditions
  35. : [{ id: 'default', name: '默认分支', expression: 'true' }]
  36. // 计算每个分支 Handle 的位置
  37. const getBranchPosition = (index: number, total: number) => {
  38. if (total === 1) return 50
  39. const spacing = 70 / (total - 1) // 从 20% 到 90% 分布
  40. return 15 + index * spacing
  41. }
  42. </script>
  43. <template>
  44. <div class="relative min-w-[260px] transition-all duration-300 ease-out hover:-translate-y-0.5"
  45. :class="{ 'scale-105': selected }">
  46. <!-- 节点主体 -->
  47. <div class="bg-gradient-to-br from-white to-orange-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
  48. :class="selected
  49. ? 'border-orange-500 shadow-orange-200 shadow-lg'
  50. : 'border-orange-300 hover:shadow-lg hover:shadow-orange-100'
  51. ">
  52. <!-- 左侧装饰条 -->
  53. <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-orange-500 to-orange-400 rounded-l-xl">
  54. </div>
  55. <!-- 头部 -->
  56. <div class="flex items-center gap-3 px-4 py-3 border-b border-orange-100">
  57. <!-- 图标 - 菱形形状 -->
  58. <div
  59. class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-orange-500 to-orange-400 rounded-lg shadow-md shadow-orange-200 rotate-45">
  60. <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
  61. <div class="-rotate-45">
  62. <Icon icon="lucide:git-branch" color="#ffffff" class="relative z-10" :size="20" />
  63. </div>
  64. </div>
  65. <!-- 标题 -->
  66. <div class="flex-1 min-w-0">
  67. <div class="text-sm font-semibold text-gray-800 truncate">
  68. {{ data.label || '条件分支' }}
  69. </div>
  70. <div v-if="data.description" class="text-xs text-gray-500 mt-0.5 truncate">
  71. {{ data.description }}
  72. </div>
  73. </div>
  74. <!-- 分支数量标签 -->
  75. <div
  76. class="flex-shrink-0 flex items-center gap-1 px-2 py-1 bg-orange-100 rounded text-xs font-medium text-orange-700">
  77. <Icon icon="lucide:split" color="#ea580c" :size="12" />
  78. <span>{{ branches.length }}</span>
  79. </div>
  80. </div>
  81. <!-- 分支列表 -->
  82. <div class="px-4 py-3 space-y-2 max-h-[200px] overflow-y-auto">
  83. <div v-for="(condition, index) in branches" :key="condition.id"
  84. class="flex items-start gap-2 p-2 rounded-lg bg-orange-50/50 hover:bg-orange-100/50 transition-colors group">
  85. <!-- 分支序号 -->
  86. <div
  87. class="flex-shrink-0 flex items-center justify-center w-5 h-5 bg-orange-500 text-white text-xs font-bold rounded-full">
  88. {{ index + 1 }}
  89. </div>
  90. <!-- 分支信息 -->
  91. <div class="flex-1 min-w-0">
  92. <div class="flex items-center gap-1.5 mb-1">
  93. <span class="text-xs font-medium text-gray-700 truncate">
  94. {{ condition.name }}
  95. </span>
  96. <Icon v-if="condition.priority" icon="lucide:star" color="#f97316" :size="12"
  97. class="flex-shrink-0" />
  98. </div>
  99. <div class="text-xs text-gray-500 font-mono bg-white px-2 py-1 rounded truncate">
  100. {{ condition.expression }}
  101. </div>
  102. </div>
  103. <!-- 分支指示箭头 -->
  104. <div class="flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
  105. <Icon icon="lucide:arrow-right" color="#f97316" :size="14" />
  106. </div>
  107. </div>
  108. <!-- 默认分支提示 -->
  109. <div v-if="data.defaultBranch"
  110. class="flex items-center gap-2 p-2 rounded-lg bg-gray-50 border border-dashed border-gray-300">
  111. <Icon icon="lucide:shield-check" color="#94a3b8" :size="14" />
  112. <span class="text-xs text-gray-500">
  113. 默认: <span class="font-medium text-gray-700">{{ data.defaultBranch }}</span>
  114. </span>
  115. </div>
  116. </div>
  117. <!-- 底部状态栏 -->
  118. <div class="flex items-center justify-between px-4 py-2 bg-orange-50/50 border-t border-orange-100">
  119. <div class="flex items-center gap-1.5">
  120. <Icon icon="lucide:zap" color="#f97316" :size="12" />
  121. <span class="text-xs text-gray-500">条件判断</span>
  122. </div>
  123. <Icon icon="lucide:settings" color="#94a3b8" :size="14"
  124. class="cursor-pointer hover:text-orange-500 transition-colors" />
  125. </div>
  126. </div>
  127. <!-- 输入连接点 -->
  128. <CanvasHandle handle-id="condition-node-input" type="target" :connections-count="1" :position="Position.Left" />
  129. <!-- 输出连接点 - 为每个分支创建一个 -->
  130. <CanvasHandle v-for="(condition, index) in branches" :key="condition.id" :id="`branch-${condition.id}`"
  131. :handle-id="`condition-node-id-${index}`" :connections-count="branches.length" type="source"
  132. :position="Position.Right" :style="{
  133. top: `${getBranchPosition(index, branches.length)}%`
  134. }">
  135. <!-- 多个分支标签提示 -->
  136. <div
  137. class="absolute left-5 top-1/2 -translate-y-1/2 px-2 py-0.5 bg-orange-500 text-white text-xs rounded whitespace-nowrap opacity-0 hover:opacity-100 transition-opacity pointer-events-none">
  138. {{ condition.name }}
  139. </div>
  140. </CanvasHandle>
  141. </div>
  142. </template>
  143. <style scoped>
  144. .overflow-y-auto::-webkit-scrollbar {
  145. width: 4px;
  146. }
  147. .overflow-y-auto::-webkit-scrollbar-track {
  148. background: #fed7aa;
  149. border-radius: 4px;
  150. }
  151. .overflow-y-auto::-webkit-scrollbar-thumb {
  152. background: #f97316;
  153. border-radius: 4px;
  154. }
  155. .overflow-y-auto::-webkit-scrollbar-thumb:hover {
  156. background: #ea580c;
  157. }
  158. </style>