Переглянути джерело

fix: 修改画布初始视图,添加挂起运行样式,修改节点属性更新

jiaxing.liao 3 тижнів тому
батько
коміт
1fb3212579
23 змінених файлів з 110 додано та 1102 видалено
  1. 5 0
      apps/web/src/features/RunWorkflow/components/DetailTab.vue
  2. 5 0
      apps/web/src/features/RunWorkflow/components/TraceTab.vue
  3. 1 0
      apps/web/src/features/RunWorkflow/index.vue
  4. 2 0
      apps/web/src/features/editorFooter/index.vue
  5. 5 0
      apps/web/src/features/setter/NodeLog.vue
  6. 3 1
      apps/web/src/nodes/Interface.ts
  7. 4 1
      apps/web/src/nodes/src/index.ts
  8. 16 1
      apps/web/src/store/modules/runner.store.ts
  9. 26 3
      apps/web/src/views/editor/NodeView.vue
  10. 3 0
      packages/api-service/agent.openapi.json
  11. 13 11
      packages/api-service/servers/api/agent.ts
  12. 1 1
      packages/workflow/src/Interface.ts
  13. 7 1
      packages/workflow/src/components/Canvas.vue
  14. 2 0
      packages/workflow/src/components/elements/handles/CanvasHandle.vue
  15. 2 1
      packages/workflow/src/components/elements/handles/HandlePort.vue
  16. 0 295
      packages/workflow/src/components/elements/node-temp/CodeNode.vue
  17. 0 178
      packages/workflow/src/components/elements/node-temp/ConditionNode.vue
  18. 0 262
      packages/workflow/src/components/elements/node-temp/DataBaseNode.vue
  19. 0 89
      packages/workflow/src/components/elements/node-temp/EndNode.vue
  20. 0 169
      packages/workflow/src/components/elements/node-temp/HttpNode1.vue
  21. 0 89
      packages/workflow/src/components/elements/node-temp/StartNode.vue
  22. 7 0
      packages/workflow/src/components/elements/nodes/CanvasNode.vue
  23. 8 0
      packages/workflow/src/utils/execution-status.ts

+ 5 - 0
apps/web/src/features/RunWorkflow/components/DetailTab.vue

@@ -117,6 +117,11 @@ const { t } = useI18n()
 	background: linear-gradient(135deg, #fef2f2, #fee4e2);
 }
 
+.summary-card.is-suspended {
+	border-color: #98a2b3;
+	background: linear-gradient(135deg, #f8fafc, #eef2f6);
+}
+
 .summary-item {
 	min-width: 0;
 }

+ 5 - 0
apps/web/src/features/RunWorkflow/components/TraceTab.vue

@@ -36,6 +36,7 @@ function statusClass(status?: string | null) {
 	if (status === 'success' || status === 'finished') return 'is-success'
 	if (status === 'failed' || status === 'error') return 'is-failed'
 	if (status === 'running' || status === 'connecting') return 'is-running'
+	if (status === 'suspended') return 'is-suspended'
 	return 'is-idle'
 }
 
@@ -214,6 +215,10 @@ function formatDisplayValue(value: unknown) {
 	background: #98a2b3;
 }
 
+.status-dot.is-suspended {
+	background: #64748b;
+}
+
 .trace-body {
 	display: flex;
 	flex-direction: column;

+ 1 - 0
apps/web/src/features/RunWorkflow/index.vue

@@ -535,6 +535,7 @@ function statusClass(status?: NodeStatus | RunnerStatus | null) {
 	if (status === 'success' || status === 'finished') return 'is-success'
 	if (status === 'failed' || status === 'error') return 'is-failed'
 	if (status === 'running' || status === 'connecting') return 'is-running'
+	if (status === 'suspended') return 'is-suspended'
 	return 'is-idle'
 }
 </script>

+ 2 - 0
apps/web/src/features/editorFooter/index.vue

@@ -35,6 +35,7 @@ const statusText = (status: NodeStatus | RunnerStatus) => {
 	if (status === 'finished') return t('pages.editorFooter.finished')
 	if (status === 'failed') return t('pages.editorFooter.failed')
 	if (status === 'error') return t('pages.editorFooter.error')
+	if (status === 'suspended') return t('pages.runWorkflow.suspended')
 	return t('pages.editorFooter.ready')
 }
 
@@ -42,6 +43,7 @@ const statusTagType = (
 	status: NodeStatus | RunnerStatus
 ): 'info' | 'success' | 'warning' | 'danger' => {
 	if (status === 'running') return 'warning'
+	if (status === 'suspended') return 'warning'
 	if (status === 'success' || status === 'finished') return 'success'
 	if (status === 'failed' || status === 'error') return 'danger'
 	return 'info'

+ 5 - 0
apps/web/src/features/setter/NodeLog.vue

@@ -72,6 +72,7 @@ const normalizedStatus = computed(() => {
 
 	const status = nodeLog.value.status?.toLowerCase()
 	if (status === 'running' || status === 'connecting') return 'running'
+	if (status === 'suspended') return 'suspended'
 	if (status === 'failed' || status === 'error') return 'failed'
 	if (status === 'success' || status === 'finished' || status === 'finish') return 'success'
 
@@ -90,6 +91,8 @@ const statusText = computed(() => {
 			return t('pages.setter.nodeLog.success')
 		case 'failed':
 			return t('pages.setter.nodeLog.failed')
+		case 'suspended':
+			return t('pages.runWorkflow.suspended')
 		default:
 			return t('pages.setter.nodeLog.notRun')
 	}
@@ -103,6 +106,8 @@ const statusType = computed<'info' | 'success' | 'warning' | 'danger'>(() => {
 			return 'success'
 		case 'failed':
 			return 'danger'
+		case 'suspended':
+			return 'warning'
 		default:
 			return 'info'
 	}

+ 3 - 1
apps/web/src/nodes/Interface.ts

@@ -80,7 +80,9 @@ export interface ConfigSchema {
 	renderItem?: (renderCallbackParams: RenderCallbackParams) => Element
 }
 
-type EndpointType = string | { type: string; label: string; id: string }
+type EndpointType =
+	| string
+	| { type: string; label: string; id: string; labelStyle?: Record<string, any> }
 
 export interface INodeType {
 	/**

+ 4 - 1
apps/web/src/nodes/src/index.ts

@@ -93,7 +93,10 @@ const withFailBranchOutput = (node: INodeType): INodeType => {
 				outputs.push({
 					id: failOutputId,
 					type: 'port',
-					label: '异常'
+					label: '异常分支',
+					labelStyle: {
+						color: '#dc6803'
+					}
 				})
 			}
 

+ 16 - 1
apps/web/src/store/modules/runner.store.ts

@@ -348,7 +348,22 @@ export const useRunnerStore = defineStore('runner', () => {
 			 * 智能体运行被挂起
 			 */
 			case 'CMD_AGENT_SUSPEND_MSG': {
-				
+				status.value = 'suspended'
+
+				const execution = getCurrentExecution()
+				if (execution) {
+					execution.status = 'suspended'
+				}
+
+				Object.values(nodesMap).forEach((node) => {
+					if (node.status === 'running') {
+						updateNodeState(node, {
+							status: 'suspended',
+							lastUpdateTime: data?.time || node.lastUpdateTime
+						})
+					}
+				})
+				break
 			}
 			default:
 				break

+ 26 - 3
apps/web/src/views/editor/NodeView.vue

@@ -118,6 +118,10 @@ const mapNodeExecutionStatus = (status?: string): CanvasExecutionStatus => {
 		return 'warning'
 	}
 
+	if (status === 'suspended') {
+		return 'suspended'
+	}
+
 	return 'idle'
 }
 
@@ -865,9 +869,28 @@ const handleUpdateNode = (node: IWorkflowNode) => {
 		}
 		agent
 			.postAgentDoUpdateAgentNode(buildUpdateNodePayload(syncedNode))
-			.then((response) => {
-				// todo: 更新完成后会返回节点数据,同步本地节点数据
-				handleApiResult(response, undefined, t('pages.nodeView.messages.updateNodeFailed'))
+			.then((response: any) => {
+				if (!handleApiResult(response, undefined, t('pages.nodeView.messages.updateNodeFailed'))) {
+					return
+				}
+
+				const responseNode = response?.result
+				if (!responseNode || typeof responseNode !== 'object') {
+					return
+				}
+
+				const latestNode = props.workflow?.nodes.find((item) => item.id === (responseNode.id || node.id))
+				if (!latestNode) {
+					return
+				}
+
+				const mergedData = {
+					...(latestNode.data || {}),
+					...(responseNode.data || {})
+				}
+
+				Object.assign(latestNode, responseNode)
+				latestNode.data = mergedData
 			})
 			.catch((error) => {
 				console.error('postDoUpdateAgentNode error', error)

+ 3 - 0
packages/api-service/agent.openapi.json

@@ -3603,6 +3603,9 @@
 										},
 										"isAuthorized": {
 											"type": "boolean"
+										},
+										"result": {
+											"type": "object"
 										}
 									},
 									"required": ["isSuccess", "code", "isAuthorized"]

+ 13 - 11
packages/api-service/servers/api/agent.ts

@@ -241,17 +241,19 @@ export async function postAgentDoUpdateAgentNode(
   },
   options?: { [key: string]: any }
 ) {
-  return request<{ isSuccess: boolean; code: number; isAuthorized: boolean }>(
-    '/api/agent/doUpdateAgentNode',
-    {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json'
-      },
-      data: body,
-      ...(options || {})
-    }
-  )
+  return request<{
+    isSuccess: boolean
+    code: number
+    isAuthorized: boolean
+    result?: Record<string, any>
+  }>('/api/agent/doUpdateAgentNode', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json'
+    },
+    data: body,
+    ...(options || {})
+  })
 }
 
 /** 获取智能体信息 POST /api/agent/getAgentInfo */

+ 1 - 1
packages/workflow/src/Interface.ts

@@ -21,7 +21,7 @@ export interface CanvasElementPortWithRenderData extends CanvasConnectionPort {
 	offset?: { top?: string; left?: string }
 }
 
-export type CanvasExecutionStatus = 'idle' | 'running' | 'success' | 'warning'
+export type CanvasExecutionStatus = 'idle' | 'running' | 'success' | 'warning' | 'suspended'
 export interface IWorkflowNode extends Node {
 	appAgentId: string
 	name?: string

+ 7 - 1
packages/workflow/src/components/Canvas.vue

@@ -419,7 +419,13 @@ function onRunNode(id: string) {
 let loaded = false
 async function onNodesInitialized() {
 	if (!loaded) {
-		if (props.autoFit) {
+		if (props.readOnly) {
+			await fitView({
+				maxZoom: 1
+			})
+		} else if (props.defaultViewport) {
+			await setViewport(props.defaultViewport)
+		} else if (props.autoFit) {
 			await fitView({
 				maxZoom: 1
 			})

+ 2 - 0
packages/workflow/src/components/elements/handles/CanvasHandle.vue

@@ -14,6 +14,7 @@ const props = defineProps<{
 	label?: string
 	maxConnections?: number
 	connectionsCount: number
+	labelStyle?: Record<string, any>
 }>()
 
 const emit = defineEmits<{
@@ -54,6 +55,7 @@ const isConnectableEnd = computed(() => {
 			:position="position"
 			:type="type"
 			:label="label"
+			:label-style="labelStyle"
 			:show-add-action="type === 'source' && connectionsCount === 0"
 			@add="emit('add', { handleId, event: $event })"
 		/>

+ 2 - 1
packages/workflow/src/components/elements/handles/HandlePort.vue

@@ -7,6 +7,7 @@ const props = defineProps<{
 	position: string
 	type: 'source' | 'target'
 	label?: string
+	labelStyle?: Record<string, any>
 	showAddAction?: boolean
 }>()
 
@@ -71,7 +72,7 @@ const onAdd = (event: MouseEvent) => {
 			</svg>
 		</Transition>
 
-		<div v-if="label" class="w-max ml-8px text-12px text-#666">{{ label }}</div>
+		<div v-if="label" class="w-max ml-8px text-12px text-#666" :style="labelStyle">{{ label }}</div>
 	</div>
 </template>
 

+ 0 - 295
packages/workflow/src/components/elements/node-temp/CodeNode.vue

@@ -1,295 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-25 16:56:55
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 19:10:12
- * @Describe: 代码执行节点
--->
-<script setup lang="ts">
-import { Position } from '@vue-flow/core'
-import CanvasHandle from '../handles/CanvasHandle.vue'
-import { Icon } from '@repo/ui'
-import { computed } from 'vue'
-
-interface CodeConfig {
-	language: 'javascript' | 'python' | 'groovy' | 'java'
-	content: string
-	inputVars?: string[]
-	outputVar?: string
-}
-
-interface Environment {
-	timeout?: number
-	memory?: number
-}
-
-interface Props {
-	data: {
-		label?: string
-		description?: string
-		code?: CodeConfig
-		environment?: Environment
-		[key: string]: any
-	}
-	selected?: boolean
-}
-
-const props = withDefaults(defineProps<Props>(), {
-	selected: false
-})
-
-// 语言图标映射
-const languageIcons: Record<string, string> = {
-	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 language = computed(() => props.data.code?.language || 'javascript')
-const languageIcon = computed(() => languageIcons[language.value] || 'lucide:file-code')
-const languageColor = computed(() => languageColors[language.value] || languageColors.javascript)
-
-// 获取代码预览(前3行)
-const codePreview = computed(() => {
-	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
-})
-</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-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;
-}
-
-.overflow-y-auto::-webkit-scrollbar {
-	width: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-track {
-	background: #f3e8ff;
-	border-radius: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-thumb {
-	background: #9333ea;
-	border-radius: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-thumb:hover {
-	background: #7e22ce;
-}
-</style>

+ 0 - 178
packages/workflow/src/components/elements/node-temp/ConditionNode.vue

@@ -1,178 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-25 16:56:14
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 19:03:49
- * @Describe: 条件节点
--->
-<script setup lang="ts">
-import { Position } from '@vue-flow/core'
-import CanvasHandle from '../handles/CanvasHandle.vue'
-import { Icon } from '@repo/ui'
-
-interface Condition {
-    id: string
-    name: string
-    expression: string
-    priority?: number
-}
-
-interface Props {
-    data: {
-        label?: string
-        description?: string
-        conditions?: Condition[]
-        defaultBranch?: string
-        [key: string]: any
-    }
-    selected?: boolean
-}
-
-const props = withDefaults(defineProps<Props>(), {
-    selected: false
-})
-
-// 如果没有条件,至少显示默认分支
-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
-}
-</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-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;
-}
-
-.overflow-y-auto::-webkit-scrollbar-track {
-    background: #fed7aa;
-    border-radius: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-thumb {
-    background: #f97316;
-    border-radius: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-thumb:hover {
-    background: #ea580c;
-}
-</style>

+ 0 - 262
packages/workflow/src/components/elements/node-temp/DataBaseNode.vue

@@ -1,262 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-25 16:57:09
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 19:09:53
- * @Describe: 数据查询节点
--->
-<script setup lang="ts">
-import { Position } from '@vue-flow/core'
-import CanvasHandle from '../handles/CanvasHandle.vue'
-import { Icon } from '@repo/ui'
-import { computed } from 'vue'
-
-import type { NodeProps } from '@vue-flow/core'
-import type { IWorkflowNode } from '../../../Interface'
-
-type Props = NodeProps<IWorkflowNode['data']> & {
-	readOnly?: boolean
-	hovered?: boolean
-}
-
-const props = defineProps<Props>()
-
-// 数据源类型图标映射
-const datasourceIcons: Record<string, string> = {
-	mysql: 'lucide:database',
-	postgresql: 'lucide:database',
-	mongodb: 'lucide:database',
-	redis: 'lucide:hard-drive',
-	api: 'lucide:cloud'
-}
-
-// 数据源类型颜色映射
-const datasourceColors: Record<string, string> = {
-	mysql: '#13c2c2',
-	postgresql: '#13c2c2',
-	mongodb: '#52c41a',
-	redis: '#ff4d4f',
-	api: '#1890ff'
-}
-
-const datasourceType = computed(() => props.data.datasource?.type || 'mysql')
-const datasourceIcon = computed(() => datasourceIcons[datasourceType.value] || 'lucide:database')
-const datasourceColor = computed(() => datasourceColors[datasourceType.value] || '#13c2c2')
-
-// 查询类型标签
-const queryTypeLabel = computed(() => {
-	const type = props.data.query?.type
-	return type?.toUpperCase() || 'SQL'
-})
-</script>
-
-<template>
-	<div
-		class="relative min-w-[280px] transition-all duration-300 ease-out hover:-translate-y-0.5"
-		:class="{ 'scale-105': selected }"
-	>
-		<!-- 节点主体 -->
-		<div
-			class="bg-gradient-to-br from-white to-cyan-50 border-2 rounded-xl shadow-md transition-all duration-300 relative overflow-hidden"
-			:class="
-				selected
-					? 'border-cyan-500 shadow-cyan-200 shadow-lg'
-					: 'border-cyan-300 hover:shadow-lg hover:shadow-cyan-100'
-			"
-		>
-			<!-- 左侧装饰条 -->
-			<div
-				class="absolute left-0 top-0 bottom-0 w-1 bg-gradient-to-b from-cyan-500 to-cyan-400 rounded-l-xl"
-			></div>
-
-			<!-- 头部 -->
-			<div class="flex items-center gap-3 px-4 py-3 border-b border-cyan-100">
-				<!-- 图标 -->
-				<div
-					class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 bg-gradient-to-br from-cyan-500 to-cyan-400 rounded-lg shadow-md shadow-cyan-200"
-				>
-					<div
-						class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
-					></div>
-					<Icon :icon="datasourceIcon" color="#ffffff" class="relative z-10" :size="20" />
-				</div>
-
-				<!-- 标题 -->
-				<div class="flex-1 min-w-0">
-					<div class="text-sm font-semibold text-gray-800 truncate">
-						{{ data.label || '数据查询' }}
-					</div>
-					<div v-if="data.description" class="text-xs text-gray-500 mt-0.5 truncate">
-						{{ data.description }}
-					</div>
-				</div>
-
-				<!-- 查询类型标签 -->
-				<div class="flex-shrink-0 px-2 py-1 bg-cyan-100 rounded text-xs font-bold text-cyan-700">
-					{{ queryTypeLabel }}
-				</div>
-			</div>
-
-			<!-- 数据源信息 -->
-			<div class="px-4 py-3 space-y-3">
-				<!-- 数据源 -->
-				<div class="flex items-start gap-2">
-					<Icon icon="lucide:server" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
-					<div class="flex-1 min-w-0">
-						<div class="text-xs text-gray-500 mb-1">数据源</div>
-						<div class="flex items-center gap-2">
-							<div class="w-2 h-2 rounded-full" :style="{ backgroundColor: datasourceColor }"></div>
-							<span class="text-xs font-medium text-gray-700 uppercase">
-								{{ datasourceType }}
-							</span>
-							<span v-if="data.datasource?.connectionName" class="text-xs text-gray-500">
-								- {{ data.datasource.connectionName }}
-							</span>
-						</div>
-					</div>
-				</div>
-
-				<!-- 查询语句 -->
-				<div class="flex items-start gap-2">
-					<Icon icon="lucide:code-2" color="#94a3b8" :size="14" class="flex-shrink-0 mt-0.5" />
-					<div class="flex-1 min-w-0">
-						<div class="text-xs text-gray-500 mb-1">数据表</div>
-						<div
-							class="text-xs text-gray-700 font-mono bg-gray-50 px-2 py-2 rounded border border-gray-200 max-h-20 overflow-y-auto"
-						>
-							{{ data.tableList?.[0]?.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="database-node-output1"
-			type="source"
-			:connections-count="2"
-			:position="Position.Right"
-			:style="data.exception === 'exception_branch' ? { top: '40%' } : {}"
-		>
-		</CanvasHandle>
-
-		<!-- 输出连接点 - 失败 -->
-		<CanvasHandle
-			v-if="data.exception === 'exception_branch'"
-			handle-id="database-node-output2"
-			type="source"
-			:connections-count="2"
-			:position="Position.Right"
-			:style="{ top: '60%' }"
-			label="exception"
-		>
-		</CanvasHandle>
-	</div>
-</template>
-
-<style scoped>
-.overflow-y-auto::-webkit-scrollbar {
-	width: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-track {
-	background: #cffafe;
-	border-radius: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-thumb {
-	background: #06b6d4;
-	border-radius: 4px;
-}
-
-.overflow-y-auto::-webkit-scrollbar-thumb:hover {
-	background: #0891b2;
-}
-
-@keyframes pulse {
-	0%,
-	100% {
-		opacity: 1;
-	}
-
-	50% {
-		opacity: 0.5;
-	}
-}
-
-.animate-pulse {
-	animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
-}
-</style>

+ 0 - 89
packages/workflow/src/components/elements/node-temp/EndNode.vue

@@ -1,89 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-25 15:24:20
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 18:49:09
- * @Describe: 结束节点
--->
-<script lang="ts" setup>
-import { Position } from '@vue-flow/core'
-import CanvasHandle from '../handles/CanvasHandle.vue'
-import type { NodeProps } from '@vue-flow/core'
-import { Icon } from '@repo/ui'
-
-const props = withDefaults(defineProps<NodeProps>(), {
-	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 right-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>
-
-				<!-- 图标 -->
-				<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>
-
-		<!-- 输入连接点 -->
-		<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;
-	}
-
-	50% {
-		opacity: 0.5;
-	}
-}
-
-@keyframes ping {
-	75%,
-	100% {
-		transform: scale(2);
-		opacity: 0;
-	}
-}
-</style>

+ 0 - 169
packages/workflow/src/components/elements/node-temp/HttpNode1.vue

@@ -1,169 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-25 15:18:09
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 19:05:43
- * @Describe: http节点
--->
-<script setup lang="ts">
-import { Position } from '@vue-flow/core'
-import CanvasHandle from '../handles/CanvasHandle.vue'
-import { Icon } from '@repo/ui'
-import NodeToolbar from "../node-tool-bar/index.vue";
-
-import type { NodeProps } from '@vue-flow/core'
-import type { IWorkflowNode } from '../../../Interface'
-
-type Props = NodeProps<IWorkflowNode['data']> & {
-	readOnly?: boolean
-	hovered?: boolean
-}
-
-const props = defineProps<Props>()
-
-// 请求方法对应的颜色
-const methodColors: Record<string, string> = {
-	GET: '#1890ff',
-	POST: '#52c41a',
-	PUT: '#faad14',
-	DELETE: '#ff4d4f',
-	PATCH: '#722ed1'
-}
-</script>
-
-<template>
-	<div
-		class="relative min-w-[240px] node-http transition-all duration-300 ease-out hover:-translate-y-0.5"
-		:class="{ 'scale-105': selected }"
-	>
-		<div class="tool-bar absolute -top-10 right-0  h-7 pb-1 transition-all duration-300 ease-out">
-			<NodeToolbar  />
-		</div>
-
-		<!-- 节点主体 -->
-		<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-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: methodColors[data.method || 'GET'] }"
-				>
-					{{ data.method || 'GET' }}
-				</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?.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.timeoutConfig.connect }}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>
-
-		<!-- 输入连接点 -->
-		<CanvasHandle
-			handle-id="http-node-input"
-			type="target"
-			:connections-count="1"
-			:position="Position.Left"
-		/>
-
-		<!-- 输出连接点 - 成功 -->
-		<CanvasHandle
-			handle-id="http-node-output1"
-			type="source"
-			:connections-count="2"
-			:position="Position.Right"
-			:style="data.exception === 'exception_branch' ? { top: '40%' } : {}"
-		>
-		</CanvasHandle>
-
-		<!-- 输出连接点 - 失败 -->
-		<CanvasHandle
-			v-if="data.exception === 'exception_branch'"
-			handle-id="http-node-output2"
-			type="source"
-			:connections-count="2"
-			:position="Position.Right"
-			:style="{ top: '60%' }"
-			label="exception"
-		>
-		</CanvasHandle>
-	</div>
-</template>
-
-<style scoped lang="less">
-.tool-bar{
-	//display: none;
-}
-.node-http:hover{
-	.tool-bar{
-		display: block;
-	}
-}
-</style>

+ 0 - 89
packages/workflow/src/components/elements/node-temp/StartNode.vue

@@ -1,89 +0,0 @@
-<!--
- * @Author: liuJie
- * @Date: 2026-01-25 15:24:20
- * @LastEditors: liuJie
- * @LastEditTime: 2026-01-25 19:06:20
- * @Describe: 开始节点
--->
-<script lang="ts" setup>
-import { Position } from '@vue-flow/core'
-import CanvasHandle from '../handles/CanvasHandle.vue'
-import type { NodeProps } from '@vue-flow/core'
-import { Icon } from '@repo/ui'
-
-const props = withDefaults(defineProps<NodeProps>(), {
-	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>
-
-				<!-- 播放图标 -->
-				<Icon icon="lucide:play" 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>
-
-		<!-- 输出连接点 -->
-		<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;
-	}
-
-	50% {
-		opacity: 0.5;
-	}
-}
-
-@keyframes ping {
-	75%,
-	100% {
-		transform: scale(2);
-		opacity: 0;
-	}
-}
-</style>

+ 7 - 0
packages/workflow/src/components/elements/nodes/CanvasNode.vue

@@ -289,6 +289,13 @@ provideCanvasNodeContext({
 	border-color: #f59e0b !important;
 }
 
+.canvas-node-status-shell[data-execution-status='suspended'] {
+	border-color: #64748b !important;
+	box-shadow:
+		0 0 0 1px rgb(100 116 139 / 20%),
+		0 0 14px rgb(100 116 139 / 22%);
+}
+
 .canvas-node-status-shell[data-pending='true'] {
 	opacity: 0.7;
 	border-color: #409eff !important;

+ 8 - 0
packages/workflow/src/utils/execution-status.ts

@@ -20,6 +20,10 @@ export const getEdgeExecutionStatus = (
 		return 'running'
 	}
 
+	if (sourceStatus === 'suspended' || targetStatus === 'suspended') {
+		return 'suspended'
+	}
+
 	if (sourceStatus === 'warning' || targetStatus === 'warning') {
 		return 'warning'
 	}
@@ -44,5 +48,9 @@ export const getExecutionStatusColor = (status: CanvasExecutionStatus): string =
 		return '#f59e0b'
 	}
 
+	if (status === 'suspended') {
+		return '#64748b'
+	}
+
 	return '#b1b1b7'
 }