|
|
@@ -12,46 +12,46 @@ import { Icon } from '@repo/ui'
|
|
|
import { computed } from 'vue'
|
|
|
|
|
|
interface CodeConfig {
|
|
|
- language: 'javascript' | 'python' | 'groovy' | 'java'
|
|
|
- content: string
|
|
|
- inputVars?: string[]
|
|
|
- outputVar?: string
|
|
|
+ language: 'javascript' | 'python' | 'groovy' | 'java'
|
|
|
+ content: string
|
|
|
+ inputVars?: string[]
|
|
|
+ outputVar?: string
|
|
|
}
|
|
|
|
|
|
interface Environment {
|
|
|
- timeout?: number
|
|
|
- memory?: number
|
|
|
+ timeout?: number
|
|
|
+ memory?: number
|
|
|
}
|
|
|
|
|
|
interface Props {
|
|
|
- data: {
|
|
|
- label?: string
|
|
|
- description?: string
|
|
|
- code?: CodeConfig
|
|
|
- environment?: Environment
|
|
|
- [key: string]: any
|
|
|
- }
|
|
|
- selected?: boolean
|
|
|
+ data: {
|
|
|
+ label?: string
|
|
|
+ description?: string
|
|
|
+ code?: CodeConfig
|
|
|
+ environment?: Environment
|
|
|
+ [key: string]: any
|
|
|
+ }
|
|
|
+ selected?: boolean
|
|
|
}
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
- selected: false
|
|
|
+ selected: false
|
|
|
})
|
|
|
|
|
|
// 语言图标映射
|
|
|
const languageIcons: Record<string, string> = {
|
|
|
- javascript: 'lucide:file-code',
|
|
|
- python: 'lucide:file-code-2',
|
|
|
- groovy: 'lucide:coffee',
|
|
|
- java: 'lucide:coffee'
|
|
|
+ javascript: 'lucide:file-code',
|
|
|
+ python: 'lucide:file-code-2',
|
|
|
+ groovy: 'lucide:coffee',
|
|
|
+ java: 'lucide:coffee'
|
|
|
}
|
|
|
|
|
|
// 语言颜色映射
|
|
|
-const languageColors: Record<string, { bg: string, text: string, badge: string }> = {
|
|
|
- javascript: { bg: '#fef3c7', text: '#f59e0b', badge: '#fbbf24' },
|
|
|
- python: { bg: '#dbeafe', text: '#3b82f6', badge: '#60a5fa' },
|
|
|
- groovy: { bg: '#e0e7ff', text: '#6366f1', badge: '#818cf8' },
|
|
|
- java: { bg: '#fecaca', text: '#dc2626', badge: '#f87171' }
|
|
|
+const languageColors: Record<string, { bg: string; text: string; badge: string }> = {
|
|
|
+ javascript: { bg: '#fef3c7', text: '#f59e0b', badge: '#fbbf24' },
|
|
|
+ python: { bg: '#dbeafe', text: '#3b82f6', badge: '#60a5fa' },
|
|
|
+ groovy: { bg: '#e0e7ff', text: '#6366f1', badge: '#818cf8' },
|
|
|
+ java: { bg: '#fecaca', text: '#dc2626', badge: '#f87171' }
|
|
|
}
|
|
|
|
|
|
const language = computed(() => props.data.code?.language || 'javascript')
|
|
|
@@ -60,180 +60,236 @@ const languageColor = computed(() => languageColors[language.value] || languageC
|
|
|
|
|
|
// 获取代码预览(前3行)
|
|
|
const codePreview = computed(() => {
|
|
|
- const code = props.data.code?.content || '// 编写代码'
|
|
|
- const lines = code.split('\n').slice(0, 3)
|
|
|
- return lines.join('\n')
|
|
|
+ const code = props.data.code?.content || '// 编写代码'
|
|
|
+ const lines = code.split('\n').slice(0, 3)
|
|
|
+ return lines.join('\n')
|
|
|
})
|
|
|
|
|
|
console.log(props.data)
|
|
|
// 代码行数
|
|
|
const codeLines = computed(() => {
|
|
|
- const code = props.data.code?.content || ''
|
|
|
- return code.split('\n').length
|
|
|
+ const code = props.data.code?.content || ''
|
|
|
+ return code.split('\n').length
|
|
|
})
|
|
|
</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-purple-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
|
|
|
- :class="selected ? 'border-purple-500 shadow-purple-200 shadow-lg' : 'border-purple-300 hover:shadow-lg hover:shadow-purple-100'">
|
|
|
- <!-- 左侧装饰条 -->
|
|
|
- <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-purple-500 to-purple-400 rounded-l-xl">
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 头部 -->
|
|
|
- <div class="flex items-center gap-3 px-4 py-3 border-b border-purple-100">
|
|
|
- <!-- 图标 -->
|
|
|
- <div
|
|
|
- class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-purple-500 to-purple-400 rounded-lg shadow-md shadow-purple-200">
|
|
|
- <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
|
|
|
- <Icon icon="lucide:code-2" 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 rounded text-xs font-bold uppercase" :style="{
|
|
|
- backgroundColor: languageColor?.badge || '#fef3c7',
|
|
|
- color: 'white'
|
|
|
- }">
|
|
|
- {{ language }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 代码信息 -->
|
|
|
- <div class="px-4 py-3 space-y-3">
|
|
|
- <!-- 代码预览 -->
|
|
|
- <div class="flex items-start gap-2">
|
|
|
- <Icon :icon="languageIcon" :color="languageColor?.text || '#6b7280'" :size="14"
|
|
|
- class="flex-shrink-0 mt-0.5" />
|
|
|
- <div class="flex-1 min-w-0">
|
|
|
- <div class="flex items-center justify-between mb-1">
|
|
|
- <span class="text-xs text-gray-500">代码片段</span>
|
|
|
- <span class="text-xs text-gray-400">{{ codeLines }} 行</span>
|
|
|
- </div>
|
|
|
- <div class="relative">
|
|
|
- <pre
|
|
|
- class="text-xs font-mono bg-gray-900 text-gray-100 px-3 py-2 rounded border border-gray-700 overflow-hidden max-h-16">{{ codePreview }}</pre>
|
|
|
- <div
|
|
|
- class="absolute bottom-0 left-0 right-0 h-6 bg-gradient-to-t from-gray-900 to-transparent pointer-events-none">
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 输入变量 -->
|
|
|
- <div v-if="data.code?.inputVars && data.code.inputVars.length > 0" class="flex items-start gap-2">
|
|
|
- <Icon icon="lucide:import" 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="varName in data.code.inputVars" :key="varName"
|
|
|
- class="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-50 text-blue-700 text-xs rounded border border-blue-200 font-mono">
|
|
|
- <Icon icon="lucide:arrow-down-to-line" :size="10" />
|
|
|
- {{ varName }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 输出变量 -->
|
|
|
- <div v-if="data.code?.outputVar" class="flex items-start gap-2">
|
|
|
- <Icon icon="lucide:export" 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="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 text-xs rounded border border-green-200 font-mono">
|
|
|
- <Icon icon="lucide:arrow-up-from-line" :size="10" />
|
|
|
- {{ data.code.outputVar }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 执行环境 -->
|
|
|
- <div v-if="data.environment?.timeout || data.environment?.memory"
|
|
|
- class="flex items-center gap-3 pt-2 border-t border-purple-100">
|
|
|
- <div v-if="data.environment.timeout" class="flex items-center gap-1.5">
|
|
|
- <Icon icon="lucide:timer" color="#94a3b8" :size="12" />
|
|
|
- <span class="text-xs text-gray-600">
|
|
|
- <span class="text-gray-500">超时:</span>
|
|
|
- <span class="font-medium ml-1">{{ data.environment.timeout }}ms</span>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div v-if="data.environment.memory" class="flex items-center gap-1.5">
|
|
|
- <Icon icon="lucide:memory-stick" color="#94a3b8" :size="12" />
|
|
|
- <span class="text-xs text-gray-600">
|
|
|
- <span class="text-gray-500">内存:</span>
|
|
|
- <span class="font-medium ml-1">{{ data.environment.memory }}MB</span>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 底部状态栏 -->
|
|
|
- <div class="flex items-center justify-between px-4 py-2 bg-purple-50/50 border-t border-purple-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-purple-500 rounded-full"></div>
|
|
|
- <span class="text-xs text-gray-500">就绪</span>
|
|
|
- </div>
|
|
|
- <!-- 运行状态 -->
|
|
|
- <div class="flex items-center gap-1 px-1.5 py-0.5 bg-purple-100 rounded">
|
|
|
- <Icon icon="lucide:zap" color="#9333ea" :size="10" />
|
|
|
- <span class="text-xs text-purple-700 font-medium">待执行</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="flex items-center gap-1">
|
|
|
- <Icon icon="lucide:play-circle" color="#94a3b8" :size="14"
|
|
|
- class="cursor-pointer hover:text-purple-500 transition-colors" title="运行代码" />
|
|
|
- <Icon icon="lucide:file-edit" color="#94a3b8" :size="14"
|
|
|
- class="cursor-pointer hover:text-purple-500 transition-colors" title="编辑代码" />
|
|
|
- <Icon icon="lucide:settings" color="#94a3b8" :size="14"
|
|
|
- class="cursor-pointer hover:text-purple-500 transition-colors" title="配置" />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 输入连接点 -->
|
|
|
- <CanvasHandle handle-id="code-node-id" handle-type="target" :position="Position.Left" />
|
|
|
- <!-- 输出连接点 -->
|
|
|
- <CanvasHandle handle-id="code-node-id" handle-type="source" :position="Position.Right" />
|
|
|
- </div>
|
|
|
+ <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-purple-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
|
|
|
+ :class="
|
|
|
+ selected
|
|
|
+ ? 'border-purple-500 shadow-purple-200 shadow-lg'
|
|
|
+ : 'border-purple-300 hover:shadow-lg hover:shadow-purple-100'
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <!-- 左侧装饰条 -->
|
|
|
+ <div
|
|
|
+ class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-purple-500 to-purple-400 rounded-l-xl"
|
|
|
+ ></div>
|
|
|
+
|
|
|
+ <!-- 头部 -->
|
|
|
+ <div class="flex items-center gap-3 px-4 py-3 border-b border-purple-100">
|
|
|
+ <!-- 图标 -->
|
|
|
+ <div
|
|
|
+ class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-purple-500 to-purple-400 rounded-lg shadow-md shadow-purple-200"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
|
|
|
+ ></div>
|
|
|
+ <Icon icon="lucide:code-2" 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 rounded text-xs font-bold uppercase"
|
|
|
+ :style="{
|
|
|
+ backgroundColor: languageColor?.badge || '#fef3c7',
|
|
|
+ color: 'white'
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ {{ language }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 代码信息 -->
|
|
|
+ <div class="px-4 py-3 space-y-3">
|
|
|
+ <!-- 代码预览 -->
|
|
|
+ <div class="flex items-start gap-2">
|
|
|
+ <Icon
|
|
|
+ :icon="languageIcon"
|
|
|
+ :color="languageColor?.text || '#6b7280'"
|
|
|
+ :size="14"
|
|
|
+ class="flex-shrink-0 mt-0.5"
|
|
|
+ />
|
|
|
+ <div class="flex-1 min-w-0">
|
|
|
+ <div class="flex items-center justify-between mb-1">
|
|
|
+ <span class="text-xs text-gray-500">代码片段</span>
|
|
|
+ <span class="text-xs text-gray-400">{{ codeLines }} 行</span>
|
|
|
+ </div>
|
|
|
+ <div class="relative">
|
|
|
+ <pre
|
|
|
+ class="text-xs font-mono bg-gray-900 text-gray-100 px-3 py-2 rounded border border-gray-700 overflow-hidden max-h-16"
|
|
|
+ >{{ codePreview }}</pre
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="absolute bottom-0 left-0 right-0 h-6 bg-gradient-to-t from-gray-900 to-transparent pointer-events-none"
|
|
|
+ ></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 输入变量 -->
|
|
|
+ <div
|
|
|
+ v-if="data.code?.inputVars && data.code.inputVars.length > 0"
|
|
|
+ class="flex items-start gap-2"
|
|
|
+ >
|
|
|
+ <Icon icon="lucide:import" 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="varName in data.code.inputVars"
|
|
|
+ :key="varName"
|
|
|
+ class="inline-flex items-center gap-1 px-2 py-0.5 bg-blue-50 text-blue-700 text-xs rounded border border-blue-200 font-mono"
|
|
|
+ >
|
|
|
+ <Icon icon="lucide:arrow-down-to-line" :size="10" />
|
|
|
+ {{ varName }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 输出变量 -->
|
|
|
+ <div v-if="data.code?.outputVar" class="flex items-start gap-2">
|
|
|
+ <Icon icon="lucide:export" 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="inline-flex items-center gap-1 px-2 py-0.5 bg-green-50 text-green-700 text-xs rounded border border-green-200 font-mono"
|
|
|
+ >
|
|
|
+ <Icon icon="lucide:arrow-up-from-line" :size="10" />
|
|
|
+ {{ data.code.outputVar }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 执行环境 -->
|
|
|
+ <div
|
|
|
+ v-if="data.environment?.timeout || data.environment?.memory"
|
|
|
+ class="flex items-center gap-3 pt-2 border-t border-purple-100"
|
|
|
+ >
|
|
|
+ <div v-if="data.environment.timeout" class="flex items-center gap-1.5">
|
|
|
+ <Icon icon="lucide:timer" color="#94a3b8" :size="12" />
|
|
|
+ <span class="text-xs text-gray-600">
|
|
|
+ <span class="text-gray-500">超时:</span>
|
|
|
+ <span class="font-medium ml-1">{{ data.environment.timeout }}ms</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div v-if="data.environment.memory" class="flex items-center gap-1.5">
|
|
|
+ <Icon icon="lucide:memory-stick" color="#94a3b8" :size="12" />
|
|
|
+ <span class="text-xs text-gray-600">
|
|
|
+ <span class="text-gray-500">内存:</span>
|
|
|
+ <span class="font-medium ml-1">{{ data.environment.memory }}MB</span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 底部状态栏 -->
|
|
|
+ <div
|
|
|
+ class="flex items-center justify-between px-4 py-2 bg-purple-50/50 border-t border-purple-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-purple-500 rounded-full"></div>
|
|
|
+ <span class="text-xs text-gray-500">就绪</span>
|
|
|
+ </div>
|
|
|
+ <!-- 运行状态 -->
|
|
|
+ <div class="flex items-center gap-1 px-1.5 py-0.5 bg-purple-100 rounded">
|
|
|
+ <Icon icon="lucide:zap" color="#9333ea" :size="10" />
|
|
|
+ <span class="text-xs text-purple-700 font-medium">待执行</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="flex items-center gap-1">
|
|
|
+ <Icon
|
|
|
+ icon="lucide:play-circle"
|
|
|
+ color="#94a3b8"
|
|
|
+ :size="14"
|
|
|
+ class="cursor-pointer hover:text-purple-500 transition-colors"
|
|
|
+ title="运行代码"
|
|
|
+ />
|
|
|
+ <Icon
|
|
|
+ icon="lucide:file-edit"
|
|
|
+ color="#94a3b8"
|
|
|
+ :size="14"
|
|
|
+ class="cursor-pointer hover:text-purple-500 transition-colors"
|
|
|
+ title="编辑代码"
|
|
|
+ />
|
|
|
+ <Icon
|
|
|
+ icon="lucide:settings"
|
|
|
+ color="#94a3b8"
|
|
|
+ :size="14"
|
|
|
+ class="cursor-pointer hover:text-purple-500 transition-colors"
|
|
|
+ title="配置"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 输入连接点 -->
|
|
|
+ <CanvasHandle
|
|
|
+ handle-id="code-node-input"
|
|
|
+ type="target"
|
|
|
+ :connections-count="1"
|
|
|
+ :position="Position.Left"
|
|
|
+ />
|
|
|
+ <!-- 输出连接点 -->
|
|
|
+ <CanvasHandle
|
|
|
+ handle-id="code-node-output"
|
|
|
+ type="source"
|
|
|
+ :connections-count="1"
|
|
|
+ :position="Position.Right"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</template>
|
|
|
|
|
|
<style scoped>
|
|
|
pre {
|
|
|
- line-height: 1.4;
|
|
|
- white-space: pre-wrap;
|
|
|
- word-wrap: break-word;
|
|
|
+ line-height: 1.4;
|
|
|
+ white-space: pre-wrap;
|
|
|
+ word-wrap: break-word;
|
|
|
}
|
|
|
|
|
|
-
|
|
|
.overflow-y-auto::-webkit-scrollbar {
|
|
|
- width: 4px;
|
|
|
+ width: 4px;
|
|
|
}
|
|
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-track {
|
|
|
- background: #f3e8ff;
|
|
|
- border-radius: 4px;
|
|
|
+ background: #f3e8ff;
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-thumb {
|
|
|
- background: #9333ea;
|
|
|
- border-radius: 4px;
|
|
|
+ background: #9333ea;
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
|
|
|
- background: #7e22ce;
|
|
|
+ background: #7e22ce;
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|