DataBaseNode.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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
  67. class="relative min-w-[280px] transition-all duration-300 ease-out hover:-translate-y-0.5"
  68. :class="{ 'scale-105': selected }"
  69. >
  70. <!-- 节点主体 -->
  71. <div
  72. class="bg-gradient-to-br from-white to-cyan-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
  73. :class="
  74. selected
  75. ? 'border-cyan-500 shadow-cyan-200 shadow-lg'
  76. : 'border-cyan-300 hover:shadow-lg hover:shadow-cyan-100'
  77. "
  78. >
  79. <!-- 左侧装饰条 -->
  80. <div
  81. class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-cyan-500 to-cyan-400 rounded-l-xl"
  82. ></div>
  83. <!-- 头部 -->
  84. <div class="flex items-center gap-3 px-4 py-3 border-b border-cyan-100">
  85. <!-- 图标 -->
  86. <div
  87. 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"
  88. >
  89. <div
  90. class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
  91. ></div>
  92. <Icon :icon="datasourceIcon" color="#ffffff" class="relative z-10" :size="20" />
  93. </div>
  94. <!-- 标题 -->
  95. <div class="flex-1 min-w-0">
  96. <div class="text-sm font-semibold text-gray-800 truncate">
  97. {{ data.label || '数据查询' }}
  98. </div>
  99. <div v-if="data.description" class="text-xs text-gray-500 mt-0.5 truncate">
  100. {{ data.description }}
  101. </div>
  102. </div>
  103. <!-- 查询类型标签 -->
  104. <div class="flex-shrink-0 px-2 py-1 bg-cyan-100 rounded text-xs font-bold text-cyan-700">
  105. {{ queryTypeLabel }}
  106. </div>
  107. </div>
  108. <!-- 数据源信息 -->
  109. <div class="px-4 py-3 space-y-3">
  110. <!-- 数据源 -->
  111. <div class="flex items-start gap-2">
  112. <Icon icon="lucide:server" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
  113. <div class="flex-1 min-w-0">
  114. <div class="text-xs text-gray-500 mb-1">数据源</div>
  115. <div class="flex items-center gap-2">
  116. <div class="w-2 h-2 rounded-full" :style="{ backgroundColor: datasourceColor }"></div>
  117. <span class="text-xs font-medium text-gray-700 uppercase">
  118. {{ datasourceType }}
  119. </span>
  120. <span v-if="data.datasource?.connectionName" class="text-xs text-gray-500">
  121. - {{ data.datasource.connectionName }}
  122. </span>
  123. </div>
  124. </div>
  125. </div>
  126. <!-- 查询语句 -->
  127. <div class="flex items-start gap-2">
  128. <Icon icon="lucide:code-2" 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
  132. 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"
  133. >
  134. {{ data.query?.content || 'SELECT * FROM table_name' }}
  135. </div>
  136. </div>
  137. </div>
  138. <!-- 查询参数 -->
  139. <div
  140. v-if="data.query?.params && Object.keys(data.query.params).length > 0"
  141. class="flex items-start gap-2"
  142. >
  143. <Icon icon="lucide:list-filter" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
  144. <div class="flex-1 min-w-0">
  145. <div class="text-xs text-gray-500 mb-1">查询参数</div>
  146. <div class="flex flex-wrap gap-1">
  147. <span
  148. v-for="(value, key) in data.query.params"
  149. :key="key"
  150. 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"
  151. >
  152. <span class="font-medium">{{ key }}:</span>
  153. <span class="font-mono">{{ value }}</span>
  154. </span>
  155. </div>
  156. </div>
  157. </div>
  158. <!-- 结果配置 -->
  159. <div v-if="data.result?.limit" class="flex items-center gap-2">
  160. <Icon icon="lucide:layers" color="#94a3b8" :size="14" class="flex-shrink-0" />
  161. <div class="text-xs text-gray-600">
  162. <span class="text-gray-500">返回条数:</span>
  163. <span class="font-medium ml-1">{{ data.result.limit }}</span>
  164. </div>
  165. </div>
  166. </div>
  167. <!-- 底部状态栏 -->
  168. <div
  169. class="flex items-center justify-between px-4 py-2 bg-cyan-50/50 border-t border-cyan-100"
  170. >
  171. <div class="flex items-center gap-2">
  172. <div class="flex items-center gap-1.5">
  173. <div class="w-1.5 h-1.5 bg-cyan-500 rounded-full animate-pulse"></div>
  174. <span class="text-xs text-gray-500">就绪</span>
  175. </div>
  176. <!-- 连接状态 -->
  177. <div class="flex items-center gap-1 px-1.5 py-0.5 bg-green-100 rounded">
  178. <Icon icon="lucide:check-circle-2" color="#52c41a" :size="10" />
  179. <span class="text-xs text-green-700 font-medium">已连接</span>
  180. </div>
  181. </div>
  182. <div class="flex items-center gap-1">
  183. <Icon
  184. icon="lucide:play"
  185. color="#94a3b8"
  186. :size="14"
  187. class="cursor-pointer hover:text-cyan-500 transition-colors"
  188. title="测试查询"
  189. />
  190. <Icon
  191. icon="lucide:settings"
  192. color="#94a3b8"
  193. :size="14"
  194. class="cursor-pointer hover:text-cyan-500 transition-colors"
  195. title="配置"
  196. />
  197. </div>
  198. </div>
  199. </div>
  200. <!-- 输入连接点 -->
  201. <CanvasHandle
  202. handle-id="data-node-input"
  203. type="target"
  204. :connections-count="1"
  205. :position="Position.Left"
  206. />
  207. <!-- 输出连接点 - 成功 -->
  208. <CanvasHandle
  209. handle-id="code-node-output1"
  210. type="source"
  211. :connections-count="2"
  212. :position="Position.Right"
  213. :style="{ top: '40%' }"
  214. label="true"
  215. >
  216. </CanvasHandle>
  217. <!-- 输出连接点 - 失败 -->
  218. <CanvasHandle
  219. handle-id="code-node-output2"
  220. type="source"
  221. :connections-count="2"
  222. :position="Position.Right"
  223. :style="{ top: '60%' }"
  224. label="false"
  225. >
  226. </CanvasHandle>
  227. </div>
  228. </template>
  229. <style scoped>
  230. .overflow-y-auto::-webkit-scrollbar {
  231. width: 4px;
  232. }
  233. .overflow-y-auto::-webkit-scrollbar-track {
  234. background: #cffafe;
  235. border-radius: 4px;
  236. }
  237. .overflow-y-auto::-webkit-scrollbar-thumb {
  238. background: #06b6d4;
  239. border-radius: 4px;
  240. }
  241. .overflow-y-auto::-webkit-scrollbar-thumb:hover {
  242. background: #0891b2;
  243. }
  244. @keyframes pulse {
  245. 0%,
  246. 100% {
  247. opacity: 1;
  248. }
  249. 50% {
  250. opacity: 0.5;
  251. }
  252. }
  253. .animate-pulse {
  254. animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
  255. }
  256. </style>