| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- <!--
- * @Author: liuJie
- * @Date: 2026-01-25 16:57:09
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 19:09:53
- * @Describe: 数据查询节点
- -->
- <script setup lang="ts">
- import { Position } from '@vue-flow/core'
- import CanvasHandle from '../handles/CanvasHandle.vue'
- import { Icon } from '@repo/ui'
- import { computed } from 'vue'
- interface Datasource {
- type: 'mysql' | 'postgresql' | 'mongodb' | 'redis' | 'api'
- connectionId: string
- connectionName?: string
- }
- interface Query {
- type: 'sql' | 'nosql' | 'rest'
- content: string
- params?: Record<string, any>
- }
- interface Props {
- data: {
- label?: string
- description?: string
- datasource?: Datasource
- query?: Query
- result?: {
- limit?: number
- mapping?: Record<string, string>
- }
- [key: string]: any
- }
- selected?: boolean
- }
- const props = withDefaults(defineProps<Props>(), {
- selected: false
- })
- // 数据源类型图标映射
- const datasourceIcons: Record<string, string> = {
- mysql: 'lucide:database',
- postgresql: 'lucide:database',
- mongodb: 'lucide:database',
- redis: 'lucide:hard-drive',
- api: 'lucide:cloud'
- }
- // 数据源类型颜色映射
- const datasourceColors: Record<string, string> = {
- mysql: '#13c2c2',
- postgresql: '#13c2c2',
- mongodb: '#52c41a',
- redis: '#ff4d4f',
- api: '#1890ff'
- }
- const datasourceType = computed(() => props.data.datasource?.type || 'mysql')
- const datasourceIcon = computed(() => datasourceIcons[datasourceType.value] || 'lucide:database')
- const datasourceColor = computed(() => datasourceColors[datasourceType.value] || '#13c2c2')
- // 查询类型标签
- const queryTypeLabel = computed(() => {
- const type = props.data.query?.type
- return type?.toUpperCase() || 'SQL'
- })
- </script>
- <template>
- <div class="relative min-w-[280px] transition-all duration-300 ease-out hover:-translate-y-0.5"
- :class="{ 'scale-105': selected }">
- <!-- 节点主体 -->
- <div class="bg-gradient-to-br from-white to-cyan-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
- :class="selected ? 'border-cyan-500 shadow-cyan-200 shadow-lg' : 'border-cyan-300 hover:shadow-lg hover:shadow-cyan-100'">
- <!-- 左侧装饰条 -->
- <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-cyan-500 to-cyan-400 rounded-l-xl">
- </div>
- <!-- 头部 -->
- <div class="flex items-center gap-3 px-4 py-3 border-b border-cyan-100">
- <!-- 图标 -->
- <div
- class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-cyan-500 to-cyan-400 rounded-lg shadow-md shadow-cyan-200">
- <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
- <Icon :icon="datasourceIcon" color="#ffffff" class="relative z-10" :size="20" />
- </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 px-2 py-1 bg-cyan-100 rounded text-xs font-bold text-cyan-700">
- {{ queryTypeLabel }}
- </div>
- </div>
- <!-- 数据源信息 -->
- <div class="px-4 py-3 space-y-3">
- <!-- 数据源 -->
- <div class="flex items-start gap-2">
- <Icon icon="lucide:server" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
- <div class="flex-1 min-w-0">
- <div class="text-xs text-gray-500 mb-1">数据源</div>
- <div class="flex items-center gap-2">
- <div class="w-2 h-2 rounded-full" :style="{ backgroundColor: datasourceColor }"></div>
- <span class="text-xs font-medium text-gray-700 uppercase">
- {{ datasourceType }}
- </span>
- <span v-if="data.datasource?.connectionName" class="text-xs text-gray-500">
- - {{ data.datasource.connectionName }}
- </span>
- </div>
- </div>
- </div>
- <!-- 查询语句 -->
- <div class="flex items-start gap-2">
- <Icon icon="lucide:code-2" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
- <div class="flex-1 min-w-0">
- <div class="text-xs text-gray-500 mb-1">查询语句</div>
- <div
- class="text-xs text-gray-700 font-mono bg-gray-50 px-2 py-2 rounded border border-gray-200 max-h-20 overflow-y-auto">
- {{ data.query?.content || 'SELECT * FROM table_name' }}
- </div>
- </div>
- </div>
- <!-- 查询参数 -->
- <div v-if="data.query?.params && Object.keys(data.query.params).length > 0"
- class="flex items-start gap-2">
- <Icon icon="lucide:list-filter" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
- <div class="flex-1 min-w-0">
- <div class="text-xs text-gray-500 mb-1">查询参数</div>
- <div class="flex flex-wrap gap-1">
- <span v-for="(value, key) in data.query.params" :key="key"
- class="inline-flex items-center gap-1 px-2 py-0.5 bg-cyan-50 text-cyan-700 text-xs rounded border border-cyan-200">
- <span class="font-medium">{{ key }}:</span>
- <span class="font-mono">{{ value }}</span>
- </span>
- </div>
- </div>
- </div>
- <!-- 结果配置 -->
- <div v-if="data.result?.limit" class="flex items-center gap-2">
- <Icon icon="lucide:layers" color="#94a3b8" :size="14" class="flex-shrink-0" />
- <div class="text-xs text-gray-600">
- <span class="text-gray-500">返回条数:</span>
- <span class="font-medium ml-1">{{ data.result.limit }}</span>
- </div>
- </div>
- </div>
- <!-- 底部状态栏 -->
- <div class="flex items-center justify-between px-4 py-2 bg-cyan-50/50 border-t border-cyan-100">
- <div class="flex items-center gap-2">
- <div class="flex items-center gap-1.5">
- <div class="w-1.5 h-1.5 bg-cyan-500 rounded-full animate-pulse"></div>
- <span class="text-xs text-gray-500">就绪</span>
- </div>
- <!-- 连接状态 -->
- <div class="flex items-center gap-1 px-1.5 py-0.5 bg-green-100 rounded">
- <Icon icon="lucide:check-circle-2" color="#52c41a" :size="10" />
- <span class="text-xs text-green-700 font-medium">已连接</span>
- </div>
- </div>
- <div class="flex items-center gap-1">
- <Icon icon="lucide:play" color="#94a3b8" :size="14"
- class="cursor-pointer hover:text-cyan-500 transition-colors" title="测试查询" />
- <Icon icon="lucide:settings" color="#94a3b8" :size="14"
- class="cursor-pointer hover:text-cyan-500 transition-colors" title="配置" />
- </div>
- </div>
- </div>
- <!-- 输入连接点 -->
- <CanvasHandle handle-id="data-node-id" handle-type="target" :position="Position.Left" />
- <!-- 输出连接点 - 成功 -->
- <CanvasHandle handle-id="success" handle-type="source" :position="Position.Right" :style="{ top: '40%' }">
- <div
- class="absolute left-5 top-1/2 -translate-y-1/2 px-2 py-0.5 bg-green-500 text-white text-xs rounded whitespace-nowrap opacity-0 hover:opacity-100 transition-opacity pointer-events-none">
- 成功
- </div>
- </CanvasHandle>
- <!-- 输出连接点 - 失败 -->
- <CanvasHandle handle-id="error" handle-type="source" :position="Position.Right" :style="{ top: '60%' }">
- <div
- class="absolute left-5 top-1/2 -translate-y-1/2 px-2 py-0.5 bg-red-500 text-white text-xs rounded whitespace-nowrap opacity-0 hover:opacity-100 transition-opacity pointer-events-none">
- 失败
- </div>
- </CanvasHandle>
- </div>
- </template>
- <style scoped>
- .overflow-y-auto::-webkit-scrollbar {
- width: 4px;
- }
- .overflow-y-auto::-webkit-scrollbar-track {
- background: #cffafe;
- border-radius: 4px;
- }
- .overflow-y-auto::-webkit-scrollbar-thumb {
- background: #06b6d4;
- border-radius: 4px;
- }
- .overflow-y-auto::-webkit-scrollbar-thumb:hover {
- background: #0891b2;
- }
- @keyframes pulse {
- 0%,
- 100% {
- opacity: 1;
- }
- 50% {
- opacity: 0.5;
- }
- }
- .animate-pulse {
- animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
- }
- </style>
|