Ver código fonte

fix: 修复节点问题

jiaxing.liao 3 semanas atrás
pai
commit
73b332d5ea

+ 30 - 31
apps/web/src/components/setter/DatabaseSetter.vue

@@ -6,41 +6,40 @@
  * @Describe: 数据设置器
 -->
 <script lang="ts" setup>
-import { ElDrawer, ElButton } from 'element-plus';
-import { Icon } from '@iconify/vue';
+import { Icon } from '@iconify/vue'
 const props = withDefaults(
-    defineProps<{
-        data: any,
-        visible: boolean,
-    }>(),
-    {
-        visible: false,
-        data: {}
-    }
-);
+	defineProps<{
+		data: any
+		visible: boolean
+	}>(),
+	{
+		visible: false,
+		data: {}
+	}
+)
 const emit = defineEmits<{
-    'update:visible': [value: boolean]
+	'update:visible': [value: boolean]
 }>()
 </script>
 <template>
-    <div class='content'>
-        <ElDrawer :model-value="visible" :show-close="false" size="25%" @close="emit('update:visible', false)">
+	<div class="content">
+		<el-drawer
+			:model-value="visible"
+			:show-close="false"
+			size="25%"
+			@close="emit('update:visible', false)"
+		>
+			<template #header>
+				<h4>数据查询</h4>
+				<Icon icon="lucide:x" height="24" width="24"></Icon>
+			</template>
 
-            <template #header>
-                <h4>数据查询</h4>
-                <Icon icon="lucide:x" height="24" width="24"></Icon>
-            </template>
-
-            <!-- Drawer content -->
-            This is drawer content.
-
-            <!-- <template #footer>
-                <ElButton type="success" size="large" class="w-full" @click="emit('update:visible', false)">
-                    运行
-                </ElButton>
-            </template> -->
-
-        </ElDrawer>
-    </div>
+			<el-collapse>
+				<el-collapse-item title="数据表" name="1"> </el-collapse-item>
+				<el-collapse-item title="输出" name="2"> </el-collapse-item>
+				<el-collapse-item title="异常处理" name="3"> </el-collapse-item>
+			</el-collapse>
+		</el-drawer>
+	</div>
 </template>
-<style lang="scss" scoped></style>
+<style lang="scss" scoped></style>

+ 27 - 175
apps/web/src/views/Editor.vue

@@ -33,6 +33,7 @@
 					@create:connection="onCreateConnection"
 					@drop="handleDrop"
 					@run="handleRunWorkflow"
+					@update:nodes:position="handleUpdateNodesPosition"
 					class="bg-#f5f5f5"
 				/>
 				<RunWork v-model:visible="runVisible" />
@@ -50,6 +51,7 @@
 import { ref, inject, type CSSProperties } from 'vue'
 import { startNode, endNode, httpNode, conditionNode, databaseNode, codeNode } from '@repo/nodes'
 import { Workflow, type IWorkflow, type XYPosition, type Connection } from '@repo/workflow'
+import { v4 as uuid } from 'uuid'
 
 import Setter from '@/components/setter/index.vue'
 import RunWork from '@/components/RunWork.vue'
@@ -68,179 +70,9 @@ layout?.setMainStyle({
 const footerHeight = ref(32)
 
 const workflow = ref<IWorkflow>({
-	id: '1',
-	nodes: [
-		// startNode, // 初始化节点,
-		// endNode, // 初始化节点,
-		// // httpNode,
-		// // conditionNode,
-		// // databaseNode,
-		// // codeNode
-		// {
-		// 	id: 'node-1',
-		// 	type: 'canvas-node',
-		// 	position: { x: 100, y: 100 },
-		// 	data: {
-		// 		version: ['1.0.0'],
-		// 		displayName: '用户输入',
-		// 		name: 'chart',
-		// 		description: '通过用户输入开启流程处理',
-		// 		icon: 'fluent:comment-multiple-28-regular',
-		// 		iconColor: '#296dff',
-		// 		inputs: [],
-		// 		outputs: [
-		// 			{
-		// 				index: 0,
-		// 				type: 'main'
-		// 			}
-		// 		]
-		// 	}
-		// },
-		// {
-		// 	id: 'node-2',
-		// 	type: 'canvas-node',
-		// 	position: { x: 400, y: 100 },
-		// 	data: {
-		// 		version: ['1.0.0'],
-		// 		displayName: '条件判断',
-		// 		name: 'if',
-		// 		description: '通过条件判断拆分多个流程分支',
-		// 		icon: 'roentgen:guidepost',
-		// 		iconColor: '#108e49',
-		// 		inputs: [
-		// 			{
-		// 				index: 0,
-		// 				type: 'main'
-		// 			}
-		// 		],
-		// 		outputs: [
-		// 			{
-		// 				index: 0,
-		// 				type: 'main',
-		// 				label: 'true'
-		// 			},
-		// 			{
-		// 				index: 1,
-		// 				type: 'main',
-		// 				label: 'false'
-		// 			}
-		// 		],
-		// 		outputNames: ['true', 'false']
-		// 	}
-		// },
-		// {
-		// 	id: 'node-3',
-		// 	type: 'canvas-node',
-		// 	width: 96,
-		// 	height: 96,
-		// 	position: { x: 600, y: 300 },
-		// 	data: {
-		// 		version: ['1.0.0'],
-		// 		displayName: '条件判断',
-		// 		name: 'if',
-		// 		description: '通过条件判断拆分多个流程分支',
-		// 		icon: 'roentgen:guidepost',
-		// 		iconColor: '#108e49',
-		// 		inputs: [
-		// 			{
-		// 				index: 0,
-		// 				type: 'main'
-		// 			}
-		// 		],
-		// 		outputs: [
-		// 			{
-		// 				index: 0,
-		// 				type: 'main',
-		// 				label: 'true'
-		// 			},
-		// 			{
-		// 				index: 1,
-		// 				type: 'main',
-		// 				label: 'false'
-		// 			}
-		// 		],
-		// 		outputNames: ['true', 'false']
-		// 	}
-		// },
-		// {
-		// 	id: 'node-note',
-		// 	type: 'canvas-node',
-		// 	position: { x: 600, y: 300 },
-		// 	data: {
-		// 		version: ['1.0.0'],
-		// 		displayName: '条件判断',
-		// 		name: 'if',
-		// 		description: '通过条件判断拆分多个流程分支',
-		// 		icon: 'roentgen:guidepost',
-		// 		iconColor: '#108e49',
-		// 		inputs: [],
-		// 		outputs: [],
-		// 		// 便签数据
-		// 		renderType: 'stickyNote',
-		// 		content:
-		// 			'# 标题\n\n这是一些便签内容,可以使用 **Markdown** 语法进行格式化。\n\n- 列表项 1\n- 列表项 2\n\n[链接](https://example.com)',
-		// 		width: 400,
-		// 		height: 200,
-		// 		color: '#d6f5e3'
-		// 	}
-		// }
-	],
-	edges: [
-		// {
-		// 	id: 'edge-1-2',
-		// 	source: 'node-1',
-		// 	target: 'node-2',
-		// 	type: 'canvas-edge',
-		// 	data: {
-		// 		label: 'Edge 1-2'
-		// 	}
-		// }
-		// {
-		//     id: 'edge-1-2',
-		//     source: 'start-node',
-		//     target: 'http-node',
-		//     type: 'canvas-edge',
-		//     data: {
-		//         label: 'Edge 1-2'
-		//     }
-		// },
-		// {
-		//     id: 'edge-1-6',
-		//     source: 'http-node',
-		//     target: 'condition-node',
-		//     type: 'canvas-edge',
-		//     data: {
-		//         label: 'Edge 1-2'
-		//     }
-		// },
-		// {
-		//     id: 'edge-1-5',
-		//     source: 'condition-node',
-		//     target: 'data-node',
-		//     type: 'canvas-edge',
-		//     data: {
-		//         label: 'Edge 1-2'
-		//     }
-		// },
-		// {
-		//     id: 'edge-1-3',
-		//     source: 'database-node',
-		//     target: 'code-node',
-		//     type: 'canvas-edge',
-		//     data: {
-		//         label: 'Edge 1-2'
-		//     }
-		// },
-		// {
-		//     id: 'edge-1-4',
-		//     source: 'code-node',
-		//     target: 'end-node',
-		//     type: 'canvas-edge',
-		//     data: {
-		//         label: 'Edge 1-2'
-		//     }
-		// }
-	]
+	id: uuid(),
+	nodes: [],
+	edges: []
 })
 
 /**
@@ -266,8 +98,9 @@ const handleNodeCreate = (value: SourceType | string) => {
 	if (typeof value === 'string') {
 		if (value === 'stickyNote') {
 			workflow.value.nodes.push({
-				id: 'node-note',
+				id: uuid(),
 				type: 'canvas-node',
+				zIndex: 0,
 				position: { x: 600, y: 300 },
 				data: {
 					version: ['1.0.0'],
@@ -296,7 +129,11 @@ const handleNodeCreate = (value: SourceType | string) => {
 
 	// 如果存在对应节点则添加
 	if (nodeToAdd) {
-		workflow.value.nodes.push(nodeToAdd)
+		workflow.value.nodes.push({
+			...nodeToAdd,
+			zIndex: 1,
+			id: uuid()
+		})
 	}
 	console.log(workflow.value.nodes, 'workflow.nodes')
 }
@@ -310,6 +147,9 @@ const handleDrop = (position: XYPosition, event: DragEvent) => {
 	console.log('drag and drop at', position, event)
 }
 
+/**
+ * 创建连线
+ */
 const onCreateConnection = (connection: Connection) => {
 	console.log('create connection', connection)
 	const { source, target } = connection
@@ -324,4 +164,16 @@ const onCreateConnection = (connection: Connection) => {
 		})
 	}
 }
+
+/**
+ * 移动位置
+ */
+const handleUpdateNodesPosition = (events: { id: string; position: XYPosition }[]) => {
+	events?.forEach(({ id, position }) => {
+		const node = workflow.value.nodes.find((node) => node.id === id)
+		if (node) {
+			node.position = position
+		}
+	})
+}
 </script>

+ 83 - 63
packages/nodes/materials/toolbar.ts

@@ -6,69 +6,89 @@
  * @Describe: 工具栏配置
  */
 export interface MaterialToolType {
-    label: string;
-    id: string;
-    description: string;
-    source: Array<SourceType>;
-};
+	label: string
+	id: string
+	description: string
+	source: Array<SourceType>
+}
 
 export interface SourceType {
-    name: string;
-    type: string;
-    icon: string;
-    component: string;
-    id: string;
-    data?: any;
-    active: boolean;
-    isEdit: boolean;
-};
+	name: string
+	type: string
+	icon: string
+	component: string
+	id: string
+	data?: any
+	active: boolean
+	isEdit: boolean
+}
 
-
-export  const materialTools:MaterialToolType[] = [
-    {
-        label: '业务逻辑',
-        id: 'basic-nodes',
-        description: '业务节点',
-        source: [{
-                name: 'HTTP请求',
-                type: 'http',
-                icon: 'lucide:link',
-                component: 'Http',
-                id: 'http-node-id',
-                data: {},
-                active: false,
-                isEdit: false,
-            },
-            {
-                name: '条件分支',
-                type: 'condition',
-                icon: 'lucide:trending-up-down',
-                component: 'Condition',
-                id: 'condition-node-id',
-                data: {},
-                active: false,
-                isEdit: false,
-            },
-            {
-                name: '代码执行',
-                type: 'code',
-                icon: 'lucide:code',
-                component: 'Code',
-                id: 'code-node-id',
-                data: {},
-                active: false,
-                isEdit: false,
-            },
-            {
-                name: '数据查询',
-                type: 'database',
-                icon: 'lucide:database-zap',
-                component: 'Database',
-                id: 'data-query-node-id',
-                data: {},
-                active: false,
-                isEdit: false,
-            },
-        ],
-    }
-];
+export const materialTools: MaterialToolType[] = [
+	{
+		label: '业务逻辑',
+		id: 'basic-nodes',
+		description: '业务节点',
+		source: [
+			{
+				name: '开始',
+				type: 'start',
+				icon: 'lucide:play',
+				component: 'Start',
+				id: 'start-node',
+				data: {},
+				active: false,
+				isEdit: false
+			},
+			{
+				name: '结束',
+				type: 'end',
+				icon: 'lucide:unplug',
+				component: 'End',
+				id: 'end-node-id',
+				data: {},
+				active: false,
+				isEdit: false
+			},
+			{
+				name: 'HTTP请求',
+				type: 'http',
+				icon: 'lucide:link',
+				component: 'Http',
+				id: 'http-node-id',
+				data: {},
+				active: false,
+				isEdit: false
+			},
+			{
+				name: '条件分支',
+				type: 'condition',
+				icon: 'lucide:trending-up-down',
+				component: 'Condition',
+				id: 'condition-node-id',
+				data: {},
+				active: false,
+				isEdit: false
+			},
+			{
+				name: '代码执行',
+				type: 'code',
+				icon: 'lucide:code',
+				component: 'Code',
+				id: 'code-node-id',
+				data: {},
+				active: false,
+				isEdit: false
+			},
+			{
+				name: '数据查询',
+				type: 'database',
+				icon: 'lucide:database-zap',
+				component: 'Database',
+				id: 'data-query-node-id',
+				data: {},
+				active: false,
+				isEdit: false
+			}
+		]
+	}
+]

+ 2 - 0
packages/workflow/src/Interface.ts

@@ -51,4 +51,6 @@ export type ConnectStartEvent = {
 	event?: MouseEvent | undefined
 } & OnConnectStartParams
 
+export type CanvasNodeMoveEvent = { id: string; position: IWorkflowNode['position'] }
+
 export { Position }

+ 17 - 3
packages/workflow/src/components/Canvas.vue

@@ -1,7 +1,7 @@
 <script lang="ts" setup>
-import type { IWorkflow, XYPosition, ConnectStartEvent } from '../Interface'
+import type { IWorkflow, XYPosition, ConnectStartEvent, CanvasNodeMoveEvent } from '../Interface'
 import type { SourceType } from '@repo/nodes'
-import type { NodeMouseEvent, Connection } from '@vue-flow/core'
+import type { NodeMouseEvent, Connection, NodeDragEvent } from '@vue-flow/core'
 
 import { ref } from 'vue'
 import { VueFlow, useVueFlow, MarkerType } from '@vue-flow/core'
@@ -25,6 +25,7 @@ defineOptions({
 
 const emit = defineEmits<{
 	'update:node:position': [id: string, position: XYPosition]
+	'update:nodes:position': [events: CanvasNodeMoveEvent[]]
 	'update:node:activated': [id: string, event?: MouseEvent]
 	'update:node:deactivated': [id: string]
 	'update:node:enabled': [id: string]
@@ -142,6 +143,18 @@ const onToggleMinimap = () => {
 	showMinimap.value = !showMinimap.value
 }
 
+function onUpdateNodesPosition(events: CanvasNodeMoveEvent[]) {
+	emit('update:nodes:position', events)
+}
+
+function onUpdateNodePosition(id: string, position: XYPosition) {
+	emit('update:node:position', id, position)
+}
+
+function onNodeDragStop(event: NodeDragEvent) {
+	onUpdateNodesPosition(event.nodes.map(({ id, position }) => ({ id, position })))
+}
+
 /**
  * Connections / Edges
  */
@@ -201,6 +214,7 @@ const handleRun = () => {
 		snap-to-grid
 		:snap-grid="[16, 16]"
 		@node-click="onNodeClick"
+		@node-drag-stop="onNodeDragStop"
 		@drop="onDrop"
 		@connect="onConnect"
 		@connect-start="onConnectStart"
@@ -208,7 +222,7 @@ const handleRun = () => {
 		v-bind="$attrs"
 	>
 		<template #node-canvas-node="nodeProps">
-			<CanvasNode v-bind="nodeProps" />
+			<CanvasNode v-bind="nodeProps" @move="onUpdateNodePosition" />
 		</template>
 
 		<template #node-start-node="nodeProps">

+ 231 - 175
packages/workflow/src/components/elements/node-temp/CodeNode.vue

@@ -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>

+ 175 - 130
packages/workflow/src/components/elements/node-temp/ConditionNode.vue

@@ -11,163 +11,208 @@ import CanvasHandle from '../handles/CanvasHandle.vue'
 import { Icon } from '@repo/ui'
 
 interface Condition {
-    id: string
-    name: string
-    expression: string
-    priority?: number
+	id: string
+	name: string
+	expression: string
+	priority?: number
 }
 
 interface Props {
-    data: {
-        label?: string
-        description?: string
-        conditions?: Condition[]
-        defaultBranch?: string
-        [key: string]: any
-    }
-    selected?: boolean
+	data: {
+		label?: string
+		description?: string
+		conditions?: Condition[]
+		defaultBranch?: string
+		[key: string]: any
+	}
+	selected?: boolean
 }
 
 const props = withDefaults(defineProps<Props>(), {
-    selected: false
+	selected: false
 })
 
 // 如果没有条件,至少显示默认分支
-const branches = props.data.conditions && props.data.conditions.length > 0
-    ? props.data.conditions
-    : [{ id: 'default', name: '默认分支', expression: 'true' }]
+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)
+	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-id" handle-type="target" :position="Position.Left" />
-
-        <!-- 输出连接点 - 为每个分支创建一个 -->
-        <CanvasHandle v-for="(condition, index) in branches" :key="condition.id" :id="`branch-${condition.id}`"
-            handle-id="condition-node-id" handle-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>
+	<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;
+	width: 4px;
 }
 
 .overflow-y-auto::-webkit-scrollbar-track {
-    background: #fed7aa;
-    border-radius: 4px;
+	background: #fed7aa;
+	border-radius: 4px;
 }
 
 .overflow-y-auto::-webkit-scrollbar-thumb {
-    background: #f97316;
-    border-radius: 4px;
+	background: #f97316;
+	border-radius: 4px;
 }
 
 .overflow-y-auto::-webkit-scrollbar-thumb:hover {
-    background: #ea580c;
+	background: #ea580c;
 }
-</style>
+</style>

+ 227 - 179
packages/workflow/src/components/elements/node-temp/DataBaseNode.vue

@@ -12,52 +12,52 @@ import { Icon } from '@repo/ui'
 import { computed } from 'vue'
 
 interface Datasource {
-    type: 'mysql' | 'postgresql' | 'mongodb' | 'redis' | 'api'
-    connectionId: string
-    connectionName?: string
+	type: 'mysql' | 'postgresql' | 'mongodb' | 'redis' | 'api'
+	connectionId: string
+	connectionName?: string
 }
 
 interface Query {
-    type: 'sql' | 'nosql' | 'rest'
-    content: string
-    params?: Record<string, any>
+	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
+	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
+	selected: false
 })
 
 // 数据源类型图标映射
 const datasourceIcons: Record<string, string> = {
-    mysql: 'lucide:database',
-    postgresql: 'lucide:database',
-    mongodb: 'lucide:database',
-    redis: 'lucide:hard-drive',
-    api: 'lucide:cloud'
+	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'
+	mysql: '#13c2c2',
+	postgresql: '#13c2c2',
+	mongodb: '#52c41a',
+	redis: '#ff4d4f',
+	api: '#1890ff'
 }
 
 const datasourceType = computed(() => props.data.datasource?.type || 'mysql')
@@ -66,178 +66,226 @@ const datasourceColor = computed(() => datasourceColors[datasourceType.value] ||
 
 // 查询类型标签
 const queryTypeLabel = computed(() => {
-    const type = props.data.query?.type
-    return type?.toUpperCase() || 'SQL'
+	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>
+	<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-input"
+			type="target"
+			:connections-count="1"
+			:position="Position.Left"
+		/>
+
+		<!-- 输出连接点 - 成功 -->
+		<CanvasHandle
+			handle-id="code-node-output1"
+			type="target"
+			:connections-count="2"
+			: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="code-node-output2"
+			type="target"
+			:connections-count="2"
+			: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;
+	width: 4px;
 }
 
 .overflow-y-auto::-webkit-scrollbar-track {
-    background: #cffafe;
-    border-radius: 4px;
+	background: #cffafe;
+	border-radius: 4px;
 }
 
 .overflow-y-auto::-webkit-scrollbar-thumb {
-    background: #06b6d4;
-    border-radius: 4px;
+	background: #06b6d4;
+	border-radius: 4px;
 }
 
 .overflow-y-auto::-webkit-scrollbar-thumb:hover {
-    background: #0891b2;
+	background: #0891b2;
 }
 
 @keyframes pulse {
+	0%,
+	100% {
+		opacity: 1;
+	}
 
-    0%,
-    100% {
-        opacity: 1;
-    }
-
-    50% {
-        opacity: 0.5;
-    }
+	50% {
+		opacity: 0.5;
+	}
 }
 
 .animate-pulse {
-    animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
+	animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
 }
-</style>
+</style>

+ 60 - 46
packages/workflow/src/components/elements/node-temp/EndNode.vue

@@ -12,64 +12,78 @@ import type { NodeProps } from '@vue-flow/core'
 import { Icon } from '@repo/ui'
 
 const props = withDefaults(defineProps<NodeProps>(), {
-    selected: true
+	selected: true
 })
-
 </script>
 <template>
-    <div class="relative min-w-[200px] transition-all duration-300 ease-out hover:-translate-y-0.5"
-        :class="{ 'scale-105': selected }">
-        <!-- 节点主体 -->
-        <div class="flex items-center gap-3 px-4 py-3 bg-gradient-to-br from-white to-green-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
-            :class="selected ? 'border-green-500 shadow-green-200 shadow-lg' : 'border-green-300 hover:shadow-lg hover:shadow-green-100'">
-            <!-- 左侧装饰条 -->
-            <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-green-500 to-green-400 rounded-l-xl">
-            </div>
+	<div
+		class="relative min-w-[200px] transition-all duration-300 ease-out hover:-translate-y-0.5"
+		:class="{ 'scale-105': selected }"
+	>
+		<!-- 节点主体 -->
+		<div
+			class="flex items-center gap-3 px-4 py-3 bg-gradient-to-br from-white to-green-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
+			:class="
+				selected
+					? 'border-green-500 shadow-green-200 shadow-lg'
+					: 'border-green-300 hover:shadow-lg hover:shadow-green-100'
+			"
+		>
+			<!-- 左侧装饰条 -->
+			<div
+				class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-green-500 to-green-400 rounded-l-xl"
+			></div>
 
-            <!-- 图标区域 -->
-            <div
-                class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-green-500 to-green-400 rounded-lg shadow-md shadow-green-200">
-                <!-- 光泽效果 -->
-                <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
+			<!-- 图标区域 -->
+			<div
+				class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-green-500 to-green-400 rounded-lg shadow-md shadow-green-200"
+			>
+				<!-- 光泽效果 -->
+				<div
+					class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
+				></div>
 
-                <!-- 图标 -->
-                <Icon icon="lucide:unplug" color="#ffffff" class="relative z-10" :size="20" />
-            </div>
+				<!-- 图标 -->
+				<Icon icon="lucide:unplug" 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>
+			<!-- 内容区域 -->
+			<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>
 
-        <!-- 输出连接点 -->
-        <CanvasHandle handle-id="1" handle-type="source" :position="Position.Right" />
-    </div>
+		<!-- 输入连接点 -->
+		<CanvasHandle
+			handle-id="code-node-input"
+			type="target"
+			:connections-count="1"
+			:position="Position.Left"
+		/>
+	</div>
 </template>
 <style lang="less" scoped>
 @keyframes pulse {
+	0%,
+	100% {
+		opacity: 1;
+	}
 
-    0%,
-    100% {
-        opacity: 1;
-    }
-
-    50% {
-        opacity: 0.5;
-    }
+	50% {
+		opacity: 0.5;
+	}
 }
 
 @keyframes ping {
-
-    75%,
-    100% {
-        transform: scale(2);
-        opacity: 0;
-    }
+	75%,
+	100% {
+		transform: scale(2);
+		opacity: 0;
+	}
 }
-</style>
+</style>

+ 114 - 84
packages/workflow/src/components/elements/node-temp/HttpNode1.vue

@@ -11,33 +11,33 @@ import CanvasHandle from '../handles/CanvasHandle.vue'
 import { Icon } from '@repo/ui'
 
 interface HttpConfig {
-    method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
-    url?: string
-    timeout?: number
+	method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
+	url?: string
+	timeout?: number
 }
 
 interface Props {
-    data: {
-        label?: string
-        description?: string
-        config?: HttpConfig
-        [key: string]: any
-    }
-    selected?: boolean
+	data: {
+		label?: string
+		description?: string
+		config?: HttpConfig
+		[key: string]: any
+	}
+	selected?: boolean
 }
 
 const props = withDefaults(defineProps<Props>(), {
-    selected: false
+	selected: false
 })
 
 console.log(props.data.id, '1212121')
 // 请求方法对应的颜色
 const methodColors: Record<string, string> = {
-    GET: '#1890ff',
-    POST: '#52c41a',
-    PUT: '#faad14',
-    DELETE: '#ff4d4f',
-    PATCH: '#722ed1'
+	GET: '#1890ff',
+	POST: '#52c41a',
+	PUT: '#faad14',
+	DELETE: '#ff4d4f',
+	PATCH: '#722ed1'
 }
 
 const currentMethod = props.data.config?.method || 'GET'
@@ -45,81 +45,111 @@ const methodColor = methodColors[currentMethod]
 </script>
 
 <template>
-    <div class="relative min-w-[240px] transition-all duration-300 ease-out hover:-translate-y-0.5"
-        :class="{ 'scale-105': selected }">
-        <!-- 节点主体 -->
-        <div class="bg-gradient-to-br from-white to-blue-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
-            :class="selected ? 'border-blue-500 shadow-blue-200 shadow-lg' : 'border-blue-300 hover:shadow-lg hover:shadow-blue-100'">
-            <!-- 左侧装饰条 -->
-            <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-500 to-blue-400 rounded-l-xl">
-            </div>
+	<div
+		class="relative min-w-[240px] transition-all duration-300 ease-out hover:-translate-y-0.5"
+		:class="{ 'scale-105': selected }"
+	>
+		<!-- 节点主体 -->
+		<div
+			class="bg-gradient-to-br from-white to-blue-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
+			:class="
+				selected
+					? 'border-blue-500 shadow-blue-200 shadow-lg'
+					: 'border-blue-300 hover:shadow-lg hover:shadow-blue-100'
+			"
+		>
+			<!-- 左侧装饰条 -->
+			<div
+				class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-500 to-blue-400 rounded-l-xl"
+			></div>
 
-            <!-- 头部 -->
-            <div class="flex items-center gap-3 px-4 py-3 border-b border-blue-100">
-                <!-- 图标 -->
-                <div
-                    class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-blue-500 to-blue-400 rounded-lg shadow-md shadow-blue-200">
-                    <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
-                    <Icon icon="lucide:cloud" color="#ffffff" class="relative z-10" :size="20" />
-                </div>
+			<!-- 头部 -->
+			<div class="flex items-center gap-3 px-4 py-3 border-b border-blue-100">
+				<!-- 图标 -->
+				<div
+					class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-blue-500 to-blue-400 rounded-lg shadow-md shadow-blue-200"
+				>
+					<div
+						class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
+					></div>
+					<Icon icon="lucide:cloud" 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 || 'HTTP 请求' }}
-                    </div>
-                    <div v-if="data.description" class="text-xs text-gray-500 mt-0.5 truncate">
-                        {{ data.description }}
-                    </div>
-                </div>
+				<!-- 标题 -->
+				<div class="flex-1 min-w-0">
+					<div class="text-sm font-semibold text-gray-800 truncate">
+						{{ data.label || 'HTTP 请求' }}
+					</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 text-white"
-                    :style="{ backgroundColor: methodColor }">
-                    {{ currentMethod }}
-                </div>
-            </div>
+				<!-- 请求方法标签 -->
+				<div
+					class="flex-shrink-0 px-2 py-1 rounded text-xs font-bold text-white"
+					:style="{ backgroundColor: methodColor }"
+				>
+					{{ currentMethod }}
+				</div>
+			</div>
 
-            <!-- 配置信息 -->
-            <div class="px-4 py-3 space-y-2">
-                <!-- URL -->
-                <div class="flex items-start gap-2">
-                    <Icon icon="lucide:link" 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-0.5">请求地址</div>
-                        <div class="text-xs text-gray-700 font-mono bg-gray-50 px-2 py-1 rounded truncate">
-                            {{ data.config?.url || '未配置' }}
-                        </div>
-                    </div>
-                </div>
+			<!-- 配置信息 -->
+			<div class="px-4 py-3 space-y-2">
+				<!-- URL -->
+				<div class="flex items-start gap-2">
+					<Icon icon="lucide:link" 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-0.5">请求地址</div>
+						<div class="text-xs text-gray-700 font-mono bg-gray-50 px-2 py-1 rounded truncate">
+							{{ data.config?.url || '未配置' }}
+						</div>
+					</div>
+				</div>
 
-                <!-- 超时时间 -->
-                <div v-if="data.config?.timeout" class="flex items-center gap-2">
-                    <Icon icon="lucide:clock" 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.config.timeout }}ms</span>
-                    </div>
-                </div>
-            </div>
+				<!-- 超时时间 -->
+				<div v-if="data.config?.timeout" class="flex items-center gap-2">
+					<Icon icon="lucide:clock" 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.config.timeout }}ms</span>
+					</div>
+				</div>
+			</div>
 
-            <!-- 底部状态栏 -->
-            <div class="flex items-center justify-between px-4 py-2 bg-blue-50/50 border-t border-blue-100">
-                <div class="flex items-center gap-1.5">
-                    <div class="w-1.5 h-1.5 bg-blue-500 rounded-full"></div>
-                    <span class="text-xs text-gray-500">就绪</span>
-                </div>
-                <Icon icon="lucide:settings" color="#94a3b8" :size="14"
-                    class="cursor-pointer hover:text-blue-500 transition-colors" />
-            </div>
-        </div>
+			<!-- 底部状态栏 -->
+			<div
+				class="flex items-center justify-between px-4 py-2 bg-blue-50/50 border-t border-blue-100"
+			>
+				<div class="flex items-center gap-1.5">
+					<div class="w-1.5 h-1.5 bg-blue-500 rounded-full"></div>
+					<span class="text-xs text-gray-500">就绪</span>
+				</div>
+				<Icon
+					icon="lucide:settings"
+					color="#94a3b8"
+					:size="14"
+					class="cursor-pointer hover:text-blue-500 transition-colors"
+				/>
+			</div>
+		</div>
 
-        <!-- 输入连接点 -->
-        <CanvasHandle :handle-id="props.data.id" handle-type="target" :position="Position.Left" />
+		<!-- 输入连接点 -->
+		<CanvasHandle
+			handle-id="http-node-input"
+			type="target"
+			:connections-count="1"
+			:position="Position.Left"
+		/>
 
-        <!-- 输出连接点 -->
-        <CanvasHandle :handle-id="props.data.id" handle-type="source" :position="Position.Right" />
-    </div>
+		<!-- 输出连接点 -->
+		<CanvasHandle
+			handle-id="http-node-output"
+			type="source"
+			:connections-count="1"
+			:position="Position.Right"
+		/>
+	</div>
 </template>
 
-<style scoped lang="less"></style>
+<style scoped lang="less"></style>

+ 60 - 47
packages/workflow/src/components/elements/node-temp/StartNode.vue

@@ -12,65 +12,78 @@ import type { NodeProps } from '@vue-flow/core'
 import { Icon } from '@repo/ui'
 
 const props = withDefaults(defineProps<NodeProps>(), {
-    selected: false
+	selected: false
 })
-
 </script>
 <template>
-    <div class="relative min-w-[200px] transition-all duration-300 ease-out hover:-translate-y-0.5"
-        :class="{ 'scale-105': selected }">
-        <!-- 节点主体 -->
-        <div class="flex items-center gap-3 px-4 py-3 bg-gradient-to-br from-white to-green-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
-            :class="selected ? 'border-green-500 shadow-green-200 shadow-lg' : 'border-green-300 hover:shadow-lg hover:shadow-green-100'">
-            <!-- 左侧装饰条 -->
-            <div class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-green-500 to-green-400 rounded-l-xl">
-            </div>
-
-            <!-- 图标区域 -->
-            <div
-                class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-green-500 to-green-400 rounded-lg shadow-md shadow-green-200">
-                <!-- 光泽效果 -->
-                <div class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"></div>
+	<div
+		class="relative min-w-[200px] transition-all duration-300 ease-out hover:-translate-y-0.5"
+		:class="{ 'scale-105': selected }"
+	>
+		<!-- 节点主体 -->
+		<div
+			class="flex items-center gap-3 px-4 py-3 bg-gradient-to-br from-white to-green-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
+			:class="
+				selected
+					? 'border-green-500 shadow-green-200 shadow-lg'
+					: 'border-green-300 hover:shadow-lg hover:shadow-green-100'
+			"
+		>
+			<!-- 左侧装饰条 -->
+			<div
+				class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-green-500 to-green-400 rounded-l-xl"
+			></div>
 
-                <!-- 播放图标 -->
-                <Icon icon="lucide:play" color="#ffffff" class="relative z-10" :size="20" />
-            </div>
+			<!-- 图标区域 -->
+			<div
+				class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-green-500 to-green-400 rounded-lg shadow-md shadow-green-200"
+			>
+				<!-- 光泽效果 -->
+				<div
+					class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
+				></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>
+				<!-- 播放图标 -->
+				<Icon icon="lucide:play" 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>
 
-        <!-- 输出连接点 -->
-        <CanvasHandle handle-id="1" handle-type="source" :position="Position.Right" />
-    </div>
+		<!-- 输出连接点 -->
+		<CanvasHandle
+			handle-id="start-node-output"
+			type="source"
+			:connections-count="1"
+			:position="Position.Right"
+		/>
+	</div>
 </template>
 <style lang="less" scoped>
 @keyframes pulse {
+	0%,
+	100% {
+		opacity: 1;
+	}
 
-    0%,
-    100% {
-        opacity: 1;
-    }
-
-    50% {
-        opacity: 0.5;
-    }
+	50% {
+		opacity: 0.5;
+	}
 }
 
 @keyframes ping {
-
-    75%,
-    100% {
-        transform: scale(2);
-        opacity: 0;
-    }
+	75%,
+	100% {
+		transform: scale(2);
+		opacity: 0;
+	}
 }
-</style>
+</style>

+ 94 - 0
pnpm-lock.yaml

@@ -148,6 +148,9 @@ importers:
       normalize.css:
         specifier: ^8.0.1
         version: 8.0.1
+      pinia:
+        specifier: ^3.0.4
+        version: 3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))
       uuid:
         specifier: ^13.0.0
         version: 13.0.0
@@ -2658,6 +2661,15 @@ packages:
   '@vue/devtools-api@6.6.4':
     resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
 
+  '@vue/devtools-api@7.7.9':
+    resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==}
+
+  '@vue/devtools-kit@7.7.9':
+    resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==}
+
+  '@vue/devtools-shared@7.7.9':
+    resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==}
+
   '@vue/language-core@3.2.2':
     resolution: {integrity: sha512-5DAuhxsxBN9kbriklh3Q5AMaJhyOCNiQJvCskN9/30XOpdLiqZU9Q+WvjArP17ubdGEyZtBzlIeG5nIjEbNOrQ==}
 
@@ -2895,6 +2907,9 @@ packages:
   bignumber.js@9.3.1:
     resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==}
 
+  birpc@2.9.0:
+    resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
+
   bluebird@3.7.2:
     resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
 
@@ -3084,6 +3099,10 @@ packages:
   copy-anything@2.0.6:
     resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
 
+  copy-anything@4.0.5:
+    resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==}
+    engines: {node: '>=18'}
+
   copy-descriptor@0.1.1:
     resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
     engines: {node: '>=0.10.0'}
@@ -4009,6 +4028,9 @@ packages:
   hoist-non-react-statics@3.3.2:
     resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
 
+  hookable@5.5.3:
+    resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
+
   html-entities@2.6.0:
     resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==}
 
@@ -4246,6 +4268,10 @@ packages:
   is-what@3.14.1:
     resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
 
+  is-what@5.5.0:
+    resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==}
+    engines: {node: '>=18'}
+
   is-windows@1.0.2:
     resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
     engines: {node: '>=0.10.0'}
@@ -5103,6 +5129,9 @@ packages:
   pathe@2.0.3:
     resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
 
+  perfect-debounce@1.0.0:
+    resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
+
   perfect-debounce@2.1.0:
     resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==}
 
@@ -5121,6 +5150,15 @@ packages:
     resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
     engines: {node: '>=6'}
 
+  pinia@3.0.4:
+    resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
+    peerDependencies:
+      typescript: '>=4.5.0'
+      vue: ^3.5.11
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
   pkg-types@1.3.1:
     resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
 
@@ -5462,6 +5500,9 @@ packages:
     resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
     engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
 
+  rfdc@1.4.1:
+    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
+
   rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
     deprecated: Rimraf versions prior to v4 are no longer supported
@@ -5686,6 +5727,10 @@ packages:
   space-separated-tokens@2.0.2:
     resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
 
+  speakingurl@14.0.1:
+    resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
+    engines: {node: '>=0.10.0'}
+
   split-string@3.1.0:
     resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
     engines: {node: '>=0.10.0'}
@@ -5781,6 +5826,10 @@ packages:
       react-dom: '>= 16.8.0'
       react-is: '>= 16.8.0'
 
+  superjson@2.2.6:
+    resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
+    engines: {node: '>=16'}
+
   supports-color@2.0.0:
     resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
     engines: {node: '>=0.8.0'}
@@ -9669,6 +9718,24 @@ snapshots:
 
   '@vue/devtools-api@6.6.4': {}
 
+  '@vue/devtools-api@7.7.9':
+    dependencies:
+      '@vue/devtools-kit': 7.7.9
+
+  '@vue/devtools-kit@7.7.9':
+    dependencies:
+      '@vue/devtools-shared': 7.7.9
+      birpc: 2.9.0
+      hookable: 5.5.3
+      mitt: 3.0.1
+      perfect-debounce: 1.0.0
+      speakingurl: 14.0.1
+      superjson: 2.2.6
+
+  '@vue/devtools-shared@7.7.9':
+    dependencies:
+      rfdc: 1.4.1
+
   '@vue/language-core@3.2.2':
     dependencies:
       '@volar/language-core': 2.4.27
@@ -9960,6 +10027,8 @@ snapshots:
 
   bignumber.js@9.3.1: {}
 
+  birpc@2.9.0: {}
+
   bluebird@3.7.2: {}
 
   boolbase@1.0.0: {}
@@ -10148,6 +10217,10 @@ snapshots:
     dependencies:
       is-what: 3.14.1
 
+  copy-anything@4.0.5:
+    dependencies:
+      is-what: 5.5.0
+
   copy-descriptor@0.1.1: {}
 
   copy-text-to-clipboard@2.2.0: {}
@@ -11317,6 +11390,8 @@ snapshots:
     dependencies:
       react-is: 16.13.1
 
+  hookable@5.5.3: {}
+
   html-entities@2.6.0: {}
 
   html-void-elements@3.0.0: {}
@@ -11549,6 +11624,8 @@ snapshots:
 
   is-what@3.14.1: {}
 
+  is-what@5.5.0: {}
+
   is-windows@1.0.2: {}
 
   isarray@1.0.0: {}
@@ -12636,6 +12713,8 @@ snapshots:
 
   pathe@2.0.3: {}
 
+  perfect-debounce@1.0.0: {}
+
   perfect-debounce@2.1.0: {}
 
   picocolors@1.1.1: {}
@@ -12647,6 +12726,13 @@ snapshots:
   pify@4.0.1:
     optional: true
 
+  pinia@3.0.4(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3)):
+    dependencies:
+      '@vue/devtools-api': 7.7.9
+      vue: 3.5.27(typescript@5.9.3)
+    optionalDependencies:
+      typescript: 5.9.3
+
   pkg-types@1.3.1:
     dependencies:
       confbox: 0.1.8
@@ -13074,6 +13160,8 @@ snapshots:
 
   reusify@1.1.0: {}
 
+  rfdc@1.4.1: {}
+
   rimraf@3.0.2:
     dependencies:
       glob: 7.2.3
@@ -13337,6 +13425,8 @@ snapshots:
 
   space-separated-tokens@2.0.2: {}
 
+  speakingurl@14.0.1: {}
+
   split-string@3.1.0:
     dependencies:
       extend-shallow: 3.0.2
@@ -13468,6 +13558,10 @@ snapshots:
     transitivePeerDependencies:
       - '@babel/core'
 
+  superjson@2.2.6:
+    dependencies:
+      copy-anything: 4.0.5
+
   supports-color@2.0.0: {}
 
   supports-color@3.2.3: