feat: Pin移动连线也跟着移动

This commit is contained in:
alivender
2025-04-27 14:08:05 +08:00
parent b3a5342d6b
commit 10db7c67bf
6 changed files with 348 additions and 467 deletions

View File

@@ -0,0 +1,149 @@
<template>
<g :data-wire-id="props.id">
<path
:d="pathData"
fill="none"
:stroke="computedStroke"
:stroke-width="strokeWidth"
stroke-linecap="round"
stroke-linejoin="round"
:class="{ 'wire-active': props.isActive }"
@click="handleClick"
/>
<!-- 可选添加连线标签或状态指示器 -->
<text
v-if="showLabel"
:x="labelPosition.x"
:y="labelPosition.y"
class="wire-label"
>{{ props.constraint || '' }}</text>
</g>
</template>
<script setup lang="ts">
import { computed, defineEmits, reactive } from 'vue';
interface Props {
id: string;
startX: number;
startY: number;
endX: number;
endY: number;
strokeColor?: string;
strokeWidth?: number;
isActive?: boolean;
routingMode?: 'auto' | 'orthogonal' | 'direct';
// 针脚引用属性
startComponentId?: string;
startPinLabel?: string;
endComponentId?: string;
endPinLabel?: string;
// 添加约束属性
constraint?: string;
// 显示标签
showLabel?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
strokeColor: '#4a5568',
strokeWidth: 2,
isActive: false,
routingMode: 'orthogonal',
showLabel: false,
constraint: ''
});
const computedStroke = computed(() => props.isActive ? '#ff9800' : props.strokeColor);
const emit = defineEmits(['click', 'update:active', 'update:position']);
function handleClick(event: MouseEvent) {
emit('click', { id: props.id, event });
}
// 计算标签位置 - 放在连线中间
const labelPosition = computed(() => {
return {
x: (props.startX + props.endX) / 2,
y: (props.startY + props.endY) / 2 - 5
};
});
const pathData = computed(() => {
return calculateOrthogonalPath(props.startX, props.startY, props.endX, props.endY);
});
function calculateOrthogonalPath(startX: number, startY: number, endX: number, endY: number) {
// 计算两点之间的水平和垂直距离
const dx = endX - startX;
const dy = endY - startY;
// 如果在同一水平或垂直线上,直接连线
if (dx === 0 || dy === 0) {
return `M ${startX} ${startY} L ${endX} ${endY}`;
}
const absDx = Math.abs(dx);
const absDy = Math.abs(dy);
if (absDx > absDy) {
// 先水平移动,然后垂直移动
const middleX = startX + dx * 0.5;
return `M ${startX} ${startY} L ${middleX} ${startY} L ${middleX} ${endY} L ${endX} ${endY}`;
} else {
// 先垂直移动,然后水平移动
const middleY = startY + dy * 0.5;
return `M ${startX} ${startY} L ${startX} ${middleY} L ${endX} ${middleY} L ${endX} ${endY}`;
}
}
// 暴露方法,用于获取这条连线的信息
defineExpose({ id: props.id,
getInfo: () => ({
id: props.id,
startComponentId: props.startComponentId,
startPinLabel: props.startPinLabel,
endComponentId: props.endComponentId,
endPinLabel: props.endPinLabel,
constraint: props.constraint
}),
// 更新连线位置
updatePosition: (newStartX: number, newStartY: number, newEndX: number, newEndY: number) => {
// 由于 props 是只读的,我们只能通过事件通知父组件更新
emit('update:position', {
id: props.id,
startX: newStartX,
startY: newStartY,
endX: newEndX,
endY: newEndY
});
},
// 获取连线的针脚情况
getPinPosition: () => null, // 为了与其他组件接口一致但Wire不是针脚组件
// 获取连线的路由模式
getRoutingMode: () => props.routingMode,
// 设置连线状态(如高亮等)
setActive: (active: boolean) => {
emit('update:active', active);
}
});
</script>
<style scoped>
.wire-active {
stroke-dasharray: 5;
animation: dash 0.5s linear infinite;
}
@keyframes dash {
to {
stroke-dashoffset: 10;
}
}
.wire-label {
font-size: 10px;
fill: #666;
text-anchor: middle;
pointer-events: none;
user-select: none;
}
</style>