DataBaseNode.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. <!--
  2. * @Author: liuJie
  3. * @Date: 2026-01-25 16:57:09
  4. * @LastEditors: liuJie
  5. * @LastEditTime: 2026-01-25 19:09:53
  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. import { computed } from 'vue'
  13. interface Datasource {
  14. type: 'mysql' | 'postgresql' | 'mongodb' | 'redis' | 'api'
  15. connectionId: string
  16. connectionName?: string
  17. }
  18. interface Query {
  19. type: 'sql' | 'nosql' | 'rest'
  20. content: string
  21. params?: Record<string, any>
  22. }
  23. interface Props {
  24. data: {
  25. label?: string
  26. description?: string
  27. datasource?: Datasource
  28. query?: Query
  29. result?: {
  30. limit?: number
  31. mapping?: Record<string, string>
  32. }
  33. [key: string]: any
  34. }
  35. selected?: boolean
  36. }
  37. const props = withDefaults(defineProps<Props>(), {
  38. selected: false
  39. })
  40. // 数据源类型图标映射
  41. const datasourceIcons: Record<string, string> = {
  42. mysql: 'lucide:database',
  43. postgresql: 'lucide:database',
  44. mongodb: 'lucide:database',
  45. redis: 'lucide:hard-drive',
  46. api: 'lucide:cloud'
  47. }
  48. // 数据源类型颜色映射
  49. const datasourceColors: Record<string, string> = {
  50. mysql: '#13c2c2',
  51. postgresql: '#13c2c2',
  52. mongodb: '#52c41a',
  53. redis: '#ff4d4f',
  54. api: '#1890ff'
  55. }
  56. const datasourceType = computed(() => props.data.datasource?.type || 'mysql')
  57. const datasourceIcon = computed(() => datasourceIcons[datasourceType.value] || 'lucide:database')
  58. const datasourceColor = computed(() => datasourceColors[datasourceType.value] || '#13c2c2')
  59. // 查询类型标签
  60. const queryTypeLabel = computed(() => {
  61. const type = props.data.query?.type
  62. return type?.toUpperCase() || 'SQL'
  63. })
  64. </script>
  65. <template>
  66. <div class="relative min-w-[280px] transition-all duration-300 ease-out hover:-translate-y-0.5"
  67. :class="{ 'scale-105': selected }">
  68. <!-- 节点主体 -->
  69. <div class="bg-gradient-to-br from-white to-cyan-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
  70. :class="selected ? 'border-cyan-500 shadow-cyan-200 shadow-lg' : 'border-cyan-300 hover:shadow-lg hover:shadow-cyan-100'">
  71. <!-- 左侧装饰条 -->
  72. <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-cyan-500 to-cyan-400 rounded-l-xl">
  73. </div>
  74. <!-- 头部 -->
  75. <div class="flex items-center gap-3 px-4 py-3 border-b border-cyan-100">
  76. <!-- 图标 -->
  77. <div
  78. 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">
  79. <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
  80. <Icon :icon="datasourceIcon" color="#ffffff" class="relative z-10" :size="20" />
  81. </div>
  82. <!-- 标题 -->
  83. <div class="flex-1 min-w-0">
  84. <div class="text-sm font-semibold text-gray-800 truncate">
  85. {{ data.label || '数据查询' }}
  86. </div>
  87. <div v-if="data.description" class="text-xs text-gray-500 mt-0.5 truncate">
  88. {{ data.description }}
  89. </div>
  90. </div>
  91. <!-- 查询类型标签 -->
  92. <div class="flex-shrink-0 px-2 py-1 bg-cyan-100 rounded text-xs font-bold text-cyan-700">
  93. {{ queryTypeLabel }}
  94. </div>
  95. </div>
  96. <!-- 数据源信息 -->
  97. <div class="px-4 py-3 space-y-3">
  98. <!-- 数据源 -->
  99. <div class="flex items-start gap-2">
  100. <Icon icon="lucide:server" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
  101. <div class="flex-1 min-w-0">
  102. <div class="text-xs text-gray-500 mb-1">数据源</div>
  103. <div class="flex items-center gap-2">
  104. <div class="w-2 h-2 rounded-full" :style="{ backgroundColor: datasourceColor }"></div>
  105. <span class="text-xs font-medium text-gray-700 uppercase">
  106. {{ datasourceType }}
  107. </span>
  108. <span v-if="data.datasource?.connectionName" class="text-xs text-gray-500">
  109. - {{ data.datasource.connectionName }}
  110. </span>
  111. </div>
  112. </div>
  113. </div>
  114. <!-- 查询语句 -->
  115. <div class="flex items-start gap-2">
  116. <Icon icon="lucide:code-2" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
  117. <div class="flex-1 min-w-0">
  118. <div class="text-xs text-gray-500 mb-1">查询语句</div>
  119. <div
  120. 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">
  121. {{ data.query?.content || 'SELECT * FROM table_name' }}
  122. </div>
  123. </div>
  124. </div>
  125. <!-- 查询参数 -->
  126. <div v-if="data.query?.params && Object.keys(data.query.params).length > 0"
  127. class="flex items-start gap-2">
  128. <Icon icon="lucide:list-filter" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
  129. <div class="flex-1 min-w-0">
  130. <div class="text-xs text-gray-500 mb-1">查询参数</div>
  131. <div class="flex flex-wrap gap-1">
  132. <span v-for="(value, key) in data.query.params" :key="key"
  133. 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">
  134. <span class="font-medium">{{ key }}:</span>
  135. <span class="font-mono">{{ value }}</span>
  136. </span>
  137. </div>
  138. </div>
  139. </div>
  140. <!-- 结果配置 -->
  141. <div v-if="data.result?.limit" class="flex items-center gap-2">
  142. <Icon icon="lucide:layers" color="#94a3b8" :size="14" class="flex-shrink-0" />
  143. <div class="text-xs text-gray-600">
  144. <span class="text-gray-500">返回条数:</span>
  145. <span class="font-medium ml-1">{{ data.result.limit }}</span>
  146. </div>
  147. </div>
  148. </div>
  149. <!-- 底部状态栏 -->
  150. <div class="flex items-center justify-between px-4 py-2 bg-cyan-50/50 border-t border-cyan-100">
  151. <div class="flex items-center gap-2">
  152. <div class="flex items-center gap-1.5">
  153. <div class="w-1.5 h-1.5 bg-cyan-500 rounded-full animate-pulse"></div>
  154. <span class="text-xs text-gray-500">就绪</span>
  155. </div>
  156. <!-- 连接状态 -->
  157. <div class="flex items-center gap-1 px-1.5 py-0.5 bg-green-100 rounded">
  158. <Icon icon="lucide:check-circle-2" color="#52c41a" :size="10" />
  159. <span class="text-xs text-green-700 font-medium">已连接</span>
  160. </div>
  161. </div>
  162. <div class="flex items-center gap-1">
  163. <Icon icon="lucide:play" color="#94a3b8" :size="14"
  164. class="cursor-pointer hover:text-cyan-500 transition-colors" title="测试查询" />
  165. <Icon icon="lucide:settings" color="#94a3b8" :size="14"
  166. class="cursor-pointer hover:text-cyan-500 transition-colors" title="配置" />
  167. </div>
  168. </div>
  169. </div>
  170. <!-- 输入连接点 -->
  171. <CanvasHandle handle-id="data-node-id" handle-type="target" :position="Position.Left" />
  172. <!-- 输出连接点 - 成功 -->
  173. <CanvasHandle handle-id="success" handle-type="source" :position="Position.Right" :style="{ top: '40%' }">
  174. <div
  175. 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">
  176. 成功
  177. </div>
  178. </CanvasHandle>
  179. <!-- 输出连接点 - 失败 -->
  180. <CanvasHandle handle-id="error" handle-type="source" :position="Position.Right" :style="{ top: '60%' }">
  181. <div
  182. 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">
  183. 失败
  184. </div>
  185. </CanvasHandle>
  186. </div>
  187. </template>
  188. <style scoped>
  189. .overflow-y-auto::-webkit-scrollbar {
  190. width: 4px;
  191. }
  192. .overflow-y-auto::-webkit-scrollbar-track {
  193. background: #cffafe;
  194. border-radius: 4px;
  195. }
  196. .overflow-y-auto::-webkit-scrollbar-thumb {
  197. background: #06b6d4;
  198. border-radius: 4px;
  199. }
  200. .overflow-y-auto::-webkit-scrollbar-thumb:hover {
  201. background: #0891b2;
  202. }
  203. @keyframes pulse {
  204. 0%,
  205. 100% {
  206. opacity: 1;
  207. }
  208. 50% {
  209. opacity: 0.5;
  210. }
  211. }
  212. .animate-pulse {
  213. animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
  214. }
  215. </style>