修改节点连接点左右样式和色彩

This commit is contained in:
2026-08-10 15:02:10 +08:00
parent 5ac546a466
commit 4e1cd284e9
2 changed files with 136 additions and 8 deletions
@@ -0,0 +1,114 @@
<template>
<div class="flow-node" :class="`flow-node--${variant}`">
<!-- target / 开始节点不可作为 target按约束禁止被连接 -->
<template v-if="!isStartNode">
<Handle type="target" :position="Position.Top" id="target-top" />
<Handle type="target" :position="Position.Left" id="target-left" />
</template>
<!-- source / -->
<Handle type="source" :position="Position.Right" id="source-right" />
<Handle type="source" :position="Position.Bottom" id="source-bottom" />
<span class="flow-node-label">{{ props.data?.label }}</span>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { Handle, Position, type NodeProps } from '@vue-flow/core';
const props = defineProps<NodeProps<{ label?: string; nodeCode?: string }>>();
const variantByCode: Record<string, string> = {
__start__: 'start',
model: 'model',
http: 'http',
form: 'form',
judge: 'judge',
data_merge: 'merge',
sub_flow: 'subflow',
script_transcribe: 'transcribe',
};
const isStartNode = computed(() => props.data?.nodeCode === '__start__');
const variant = computed(() => variantByCode[props.data?.nodeCode || ''] || 'default');
</script>
<style scoped lang="scss">
// 注意:根元素不设 position:relative,让 Handle 以外层 .vue-flow__node 为定位参照,才能贴到节点边缘
.flow-node {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
min-width: 100px;
max-width: 220px;
padding: 10px 16px;
border-radius: 10px;
font-size: 13px;
font-weight: 600;
line-height: 1.4;
word-break: break-word;
color: #fff;
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.12);
transition: box-shadow 0.2s ease, transform 0.15s ease;
box-sizing: border-box;
text-align: center;
.flow-node-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.flow-node--start {
background: linear-gradient(135deg, #34d399, #10b981);
border: 2px solid #059669;
}
.flow-node--model {
background: linear-gradient(135deg, #a78bfa, #8b5cf6);
border: 2px solid #7c3aed;
}
.flow-node--http {
background: linear-gradient(135deg, #60a5fa, #3b82f6);
border: 2px solid #2563eb;
}
.flow-node--form {
background: linear-gradient(135deg, #22d3ee, #06b6d4);
border: 2px solid #0891b2;
}
.flow-node--judge {
background: linear-gradient(135deg, #fbbf24, #f59e0b);
border: 2px solid #d97706;
}
.flow-node--merge {
background: linear-gradient(135deg, #818cf8, #6366f1);
border: 2px solid #4f46e5;
}
.flow-node--subflow {
background: linear-gradient(135deg, #f472b6, #ec4899);
border: 2px solid #db2777;
}
.flow-node--transcribe {
background: linear-gradient(135deg, #94a3b8, #64748b);
border: 2px solid #475569;
}
.flow-node--default {
background: linear-gradient(135deg, #94a3b8, #64748b);
border: 2px solid #475569;
}
.flow-node:hover {
box-shadow: 0 4px 16px rgba(15, 23, 42, 0.18);
transform: translateY(-1px);
}
</style>
+22 -8
View File
@@ -43,12 +43,12 @@
:default-viewport="{ zoom: 1 }"
:nodes-connectable="true"
:edges-updatable="true"
:node-types="nodeTypes"
@connect="onConnect"
@node-click="onNodeClick"
>
<Background pattern-color="#cbd5e1" :gap="16" />
<Controls />
<MiniMap />
</VueFlow>
</div>
@@ -86,7 +86,6 @@ import { ref, computed, onMounted } from 'vue';
import { VueFlow, useVueFlow } from '@vue-flow/core';
import { Background } from '@vue-flow/background';
import { Controls } from '@vue-flow/controls';
import { MiniMap } from '@vue-flow/minimap';
import { ElMessage, ElMessageBox } from 'element-plus';
import type { Node, Edge, Connection } from '@vue-flow/core';
import {
@@ -104,6 +103,7 @@ import WorkflowListPanel from './component/WorkflowListPanel.vue';
import SaveWorkflowDialog from './component/SaveWorkflowDialog.vue';
import ModelSelector from './component/ModelSelector.vue';
import SkillSelector from './component/SkillSelector.vue';
import FlowNode from './component/FlowNode.vue';
interface NodeData {
label?: string;
@@ -123,6 +123,9 @@ const { addNodes, addEdges, findNode, removeNodes, getNodes, updateNode } = useV
const START_NODE_CODE = '__start__';
const JUDGE_KEYWORDS = ['判断', 'judge', 'condition', 'if', 'branch', 'gateway'];
// 自定义节点类型:默认节点内置 DefaultNode 只有上下两个 Handle,自定义组件支持左右连接
const nodeTypes = { default: FlowNode, input: FlowNode };
// 节点库相关状态
const nodeLibraryGroups = ref<NodeLibraryGroup[]>([]);
const nodeLibraryCollapsed = ref(false);
@@ -232,6 +235,8 @@ const onConnect = (connection: Connection) => {
id: `edge-${connection.source}-${connection.target}`,
source: connection.source,
target: connection.target,
sourceHandle: connection.sourceHandle,
targetHandle: connection.targetHandle,
type: 'smoothstep',
animated: true,
style: { stroke: '#3b82f6', strokeWidth: 2 },
@@ -474,7 +479,6 @@ const addNodeFromLibrary = (nodeCode: string, nodeName: string) => {
type: 'default',
position: { x: spawnX, y: spawnY },
data: { label: nodeName, nodeCode, desc: '', patchLayout: false },
style: { background: '#fff', border: '2px solid #3b82f6', borderRadius: '8px', padding: '10px 20px' },
},
]);
ElMessage.success(`已添加${nodeName}`);
@@ -502,7 +506,6 @@ const addDefaultStartNode = () => {
type: 'input',
position: { x: 200, y: 200 },
data: { label: '开始', nodeCode: '__start__' },
style: { background: '#10b981', color: '#fff', border: '2px solid #059669', borderRadius: '8px', padding: '10px 20px' },
};
addNodes([startNode]);
nodes.value.push(startNode);
@@ -674,9 +677,6 @@ const loadWorkflowFromDsl = (dsl: any) => {
isSaveFile: Boolean(n.isSaveFile),
preTool: n.preTool ?? null,
},
style: isStart
? { background: '#10b981', color: '#fff', border: '2px solid #059669', borderRadius: '8px', padding: '10px 20px' }
: { background: '#fff', border: '2px solid #3b82f6', borderRadius: '8px', padding: '10px 20px' },
};
});
@@ -684,6 +684,8 @@ const loadWorkflowFromDsl = (dsl: any) => {
id: e.id,
source: e.from,
target: e.to,
sourceHandle: e.sourceHandle || 'source-bottom',
targetHandle: e.targetHandle || 'target-top',
type: 'smoothstep',
animated: true,
style: { stroke: '#3b82f6', strokeWidth: 2 },
@@ -764,6 +766,8 @@ const confirmSaveWorkflow = async () => {
id: e.id,
from: e.source,
to: e.target,
sourceHandle: e.sourceHandle || undefined,
targetHandle: e.targetHandle || undefined,
})),
};
@@ -932,7 +936,6 @@ onMounted(async () => {
@import '@vue-flow/core/dist/style.css';
@import '@vue-flow/core/dist/theme-default.css';
@import '@vue-flow/controls/dist/style.css';
@import '@vue-flow/minimap/dist/style.css';
/* 连接点样式优化 - 更大尺寸,hover 放大但不位移 */
.vue-flow__handle {
@@ -976,6 +979,17 @@ onMounted(async () => {
cursor: move;
}
/* 覆盖 VueFlow 默认主题对节点外层的固定宽/padding/背景,让自定义 FlowNode 渐变背景完全贴合节点边缘 */
.vue-flow__node-default,
.vue-flow__node-input,
.vue-flow__node-output {
width: auto !important;
padding: 0 !important;
background: transparent !important;
border: none !important;
border-radius: 0 !important;
}
.vue-flow__node:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
}