| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- <!--
- * @Author: liuJie
- * @Date: 2026-01-25 16:56:14
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 19:03:49
- * @Describe: 条件节点
- -->
- <script setup lang="ts">
- import { Position } from '@vue-flow/core'
- import CanvasHandle from '../handles/CanvasHandle.vue'
- import { Icon } from '@repo/ui'
- interface Condition {
- id: string
- name: string
- expression: string
- priority?: number
- }
- interface Props {
- data: {
- label?: string
- description?: string
- conditions?: Condition[]
- defaultBranch?: string
- [key: string]: any
- }
- selected?: boolean
- }
- const props = withDefaults(defineProps<Props>(), {
- selected: false
- })
- // 如果没有条件,至少显示默认分支
- const branches =
- props.data.conditions && props.data.conditions.length > 0
- ? props.data.conditions
- : [{ id: 'default', name: '默认分支', expression: 'true' }]
- // 计算每个分支 Handle 的位置
- const getBranchPosition = (index: number, total: number) => {
- if (total === 1) return 50
- const spacing = 70 / (total - 1) // 从 20% 到 90% 分布
- return 15 + index * spacing
- }
- </script>
- <template>
- <div class="relative min-w-[260px] transition-all duration-300 ease-out hover:-translate-y-0.5"
- :class="{ 'scale-105': selected }">
- <!-- 节点主体 -->
- <div class="bg-gradient-to-br from-white to-orange-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
- :class="selected
- ? 'border-orange-500 shadow-orange-200 shadow-lg'
- : 'border-orange-300 hover:shadow-lg hover:shadow-orange-100'
- ">
- <!-- 左侧装饰条 -->
- <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-orange-500 to-orange-400 rounded-l-xl">
- </div>
- <!-- 头部 -->
- <div class="flex items-center gap-3 px-4 py-3 border-b border-orange-100">
- <!-- 图标 - 菱形形状 -->
- <div
- 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">
- <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
- <div class="-rotate-45">
- <Icon icon="lucide:git-branch" color="#ffffff" class="relative z-10" :size="20" />
- </div>
- </div>
- <!-- 标题 -->
- <div class="flex-1 min-w-0">
- <div class="text-sm font-semibold text-gray-800 truncate">
- {{ data.label || '条件分支' }}
- </div>
- <div v-if="data.description" class="text-xs text-gray-500 mt-0.5 truncate">
- {{ data.description }}
- </div>
- </div>
- <!-- 分支数量标签 -->
- <div
- class="flex-shrink-0 flex items-center gap-1 px-2 py-1 bg-orange-100 rounded text-xs font-medium text-orange-700">
- <Icon icon="lucide:split" color="#ea580c" :size="12" />
- <span>{{ branches.length }}</span>
- </div>
- </div>
- <!-- 分支列表 -->
- <div class="px-4 py-3 space-y-2 max-h-[200px] overflow-y-auto">
- <div v-for="(condition, index) in branches" :key="condition.id"
- class="flex items-start gap-2 p-2 rounded-lg bg-orange-50/50 hover:bg-orange-100/50 transition-colors group">
- <!-- 分支序号 -->
- <div
- class="flex-shrink-0 flex items-center justify-center w-5 h-5 bg-orange-500 text-white text-xs font-bold rounded-full">
- {{ index + 1 }}
- </div>
- <!-- 分支信息 -->
- <div class="flex-1 min-w-0">
- <div class="flex items-center gap-1.5 mb-1">
- <span class="text-xs font-medium text-gray-700 truncate">
- {{ condition.name }}
- </span>
- <Icon v-if="condition.priority" icon="lucide:star" color="#f97316" :size="12"
- class="flex-shrink-0" />
- </div>
- <div class="text-xs text-gray-500 font-mono bg-white px-2 py-1 rounded truncate">
- {{ condition.expression }}
- </div>
- </div>
- <!-- 分支指示箭头 -->
- <div class="flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
- <Icon icon="lucide:arrow-right" color="#f97316" :size="14" />
- </div>
- </div>
- <!-- 默认分支提示 -->
- <div v-if="data.defaultBranch"
- class="flex items-center gap-2 p-2 rounded-lg bg-gray-50 border border-dashed border-gray-300">
- <Icon icon="lucide:shield-check" color="#94a3b8" :size="14" />
- <span class="text-xs text-gray-500">
- 默认: <span class="font-medium text-gray-700">{{ data.defaultBranch }}</span>
- </span>
- </div>
- </div>
- <!-- 底部状态栏 -->
- <div class="flex items-center justify-between px-4 py-2 bg-orange-50/50 border-t border-orange-100">
- <div class="flex items-center gap-1.5">
- <Icon icon="lucide:zap" color="#f97316" :size="12" />
- <span class="text-xs text-gray-500">条件判断</span>
- </div>
- <Icon icon="lucide:settings" color="#94a3b8" :size="14"
- class="cursor-pointer hover:text-orange-500 transition-colors" />
- </div>
- </div>
- <!-- 输入连接点 -->
- <CanvasHandle handle-id="condition-node-input" type="target" :connections-count="1" :position="Position.Left" />
- <!-- 输出连接点 - 为每个分支创建一个 -->
- <CanvasHandle v-for="(condition, index) in branches" :key="condition.id" :id="`branch-${condition.id}`"
- :handle-id="`condition-node-id-${index}`" :connections-count="branches.length" type="source"
- :position="Position.Right" :style="{
- top: `${getBranchPosition(index, branches.length)}%`
- }">
- <!-- 多个分支标签提示 -->
- <div
- 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">
- {{ condition.name }}
- </div>
- </CanvasHandle>
- </div>
- </template>
- <style scoped>
- .overflow-y-auto::-webkit-scrollbar {
- width: 4px;
- }
- .overflow-y-auto::-webkit-scrollbar-track {
- background: #fed7aa;
- border-radius: 4px;
- }
- .overflow-y-auto::-webkit-scrollbar-thumb {
- background: #f97316;
- border-radius: 4px;
- }
- .overflow-y-auto::-webkit-scrollbar-thumb:hover {
- background: #ea580c;
- }
- </style>
|