修改节点连接点左右样式和色彩
This commit is contained in:
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user