feat: enhance DiagramCanvas and Pin components with wire creation and tooltip functionality
- Added wire creation logic in DiagramCanvas.vue with mouse tracking and event handling. - Implemented tooltip display for pins in Pin.vue with detailed information on hover. - Updated ProjectView.vue to handle wire creation and deletion events. - Refactored Wire.vue to support dynamic path rendering based on routing mode.
This commit is contained in:
@@ -5,15 +5,18 @@
|
||||
:height="height"
|
||||
:viewBox="'0 0 ' + viewBoxWidth + ' ' + viewBoxHeight"
|
||||
class="pin-component"
|
||||
:data-pin-id="props.label"
|
||||
>
|
||||
<g :transform="`translate(${viewBoxWidth/2}, ${viewBoxHeight/2})`">
|
||||
<g v-if="props.appearance === 'None'">
|
||||
<g transform="translate(-12.5, -12.5)" class="interactive">
|
||||
<circle
|
||||
<g :transform="`translate(${viewBoxWidth/2}, ${viewBoxHeight/2})`"> <g v-if="props.appearance === 'None'">
|
||||
<g transform="translate(-12.5, -12.5)" class="interactive"> <circle
|
||||
style="fill:#909090"
|
||||
cx="12.5"
|
||||
cy="12.5"
|
||||
r="3.75" />
|
||||
r="3.75"
|
||||
@mouseenter="showPinTooltip"
|
||||
@mouseleave="hidePinTooltip"
|
||||
@click.stop="handlePinClick"
|
||||
:data-pin-id="`${props.label}-${props.constraint}`" />
|
||||
</g>
|
||||
</g>
|
||||
<g v-else-if="props.appearance === 'Dip'">
|
||||
@@ -25,31 +28,52 @@
|
||||
height="25"
|
||||
x="0"
|
||||
y="0"
|
||||
rx="2.5" />
|
||||
<circle
|
||||
rx="2.5" /> <circle
|
||||
style="fill:#ecececc5;fill-opacity:0.772973"
|
||||
cx="12.5"
|
||||
cy="12.5"
|
||||
r="3.75" />
|
||||
r="3.75"
|
||||
@mouseenter="showPinTooltip"
|
||||
@mouseleave="hidePinTooltip"
|
||||
@click.stop="handlePinClick"
|
||||
:data-pin-id="`${props.label}-${props.constraint}`" />
|
||||
<text
|
||||
style="font-size:6.85px;text-align:start;fill:#ffffff;fill-opacity:0.772973"
|
||||
x="7.3"
|
||||
y="7"
|
||||
xml:space="preserve">{{ props.label }}</text>
|
||||
</g>
|
||||
</g>
|
||||
<g v-else-if="props.appearance === 'SMT'">
|
||||
</g> <g v-else-if="props.appearance === 'SMT'">
|
||||
<rect x="-20" y="-10" width="40" height="20" fill="#aaa" rx="2" ry="2" />
|
||||
<rect x="-18" y="-8" width="36" height="16" :fill="getColorByType" rx="1" ry="1" />
|
||||
<rect x="-16" y="-6" width="26" height="12" :fill="getColorByType" rx="1" ry="1" />
|
||||
<text text-anchor="middle" dominant-baseline="middle" font-size="8" fill="white" x="-3">{{ props.label }}</text>
|
||||
</g>
|
||||
</g>
|
||||
<!-- SMT样式的针脚 --> <circle
|
||||
fill="#ecececc5"
|
||||
cx="10"
|
||||
cy="0"
|
||||
r="3.75"
|
||||
class="interactive"
|
||||
@mouseenter="showPinTooltip"
|
||||
@mouseleave="hidePinTooltip"
|
||||
@click.stop="handlePinClick"
|
||||
:data-pin-id="`${props.label}-${props.constraint}`" />
|
||||
</g> </g>
|
||||
</svg>
|
||||
<!-- 提示框 - 在SVG外部 -->
|
||||
<div v-if="showTooltip" class="pin-tooltip" :style="{
|
||||
position: 'absolute',
|
||||
top: tooltipPosition.top + 'px',
|
||||
left: tooltipPosition.left + 'px',
|
||||
transform: 'translate(-50%, -100%)',
|
||||
marginTop: '-5px'
|
||||
}">
|
||||
{{ tooltipText }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, reactive } from 'vue';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
@@ -70,11 +94,92 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'value-change'
|
||||
'value-change',
|
||||
'pin-click' // 新增Pin点击事件
|
||||
]);
|
||||
|
||||
// 内部状态
|
||||
const analogValue = ref(0);
|
||||
const showTooltip = ref(false);
|
||||
const tooltipText = ref('');
|
||||
const tooltipPosition = reactive({
|
||||
top: 0,
|
||||
left: 0
|
||||
});
|
||||
|
||||
// 显示针脚提示
|
||||
function showPinTooltip(event: MouseEvent) {
|
||||
showTooltip.value = true;
|
||||
const target = event.target as SVGElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
|
||||
// 更新提示位置
|
||||
tooltipPosition.top = rect.top;
|
||||
tooltipPosition.left = rect.left + rect.width / 2;
|
||||
|
||||
// 更新提示文本
|
||||
tooltipText.value = generateTooltipText();
|
||||
}
|
||||
|
||||
// 隐藏针脚提示
|
||||
function hidePinTooltip() {
|
||||
showTooltip.value = false;
|
||||
}
|
||||
|
||||
// 生成提示文本
|
||||
function generateTooltipText() {
|
||||
const parts = [];
|
||||
parts.push(`标签: ${props.label}`);
|
||||
|
||||
if (props.constraint) {
|
||||
parts.push(`约束: ${props.constraint}`);
|
||||
} else {
|
||||
parts.push('约束: 未定义');
|
||||
}
|
||||
|
||||
parts.push(`方向: ${getDirectionText()}`);
|
||||
parts.push(`类型: ${props.type === 'digital' ? '数字' : '模拟'}`);
|
||||
|
||||
return parts.join(' | ');
|
||||
}
|
||||
|
||||
// 获取方向文本
|
||||
function getDirectionText() {
|
||||
switch (props.direction) {
|
||||
case 'input': return '输入';
|
||||
case 'output': return '输出';
|
||||
case 'inout': return '双向';
|
||||
default: return '未知';
|
||||
}
|
||||
}
|
||||
|
||||
// 处理针脚点击
|
||||
function handlePinClick(event: MouseEvent) {
|
||||
// 获取针脚在SVG中的位置
|
||||
const target = event.target as SVGElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const pinCenter = {
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.top + rect.height / 2
|
||||
};
|
||||
|
||||
console.log(`针脚 ${props.label} 被点击,位置:`, pinCenter);
|
||||
|
||||
// 发送针脚点击事件给父组件
|
||||
emit('pin-click', {
|
||||
label: props.label,
|
||||
constraint: props.constraint,
|
||||
type: props.type,
|
||||
direction: props.direction,
|
||||
// 获取针脚在页面上的位置
|
||||
position: {
|
||||
x: pinCenter.x,
|
||||
y: pinCenter.y
|
||||
},
|
||||
// 获取原始事件
|
||||
originalEvent: event
|
||||
});
|
||||
}
|
||||
|
||||
const width = computed(() => props.appearance === 'None' ? 40 * props.size : 30 * props.size);
|
||||
const height = computed(() => {
|
||||
@@ -112,7 +217,46 @@ defineExpose({
|
||||
direction: props.direction,
|
||||
type: props.type,
|
||||
appearance: props.appearance
|
||||
})
|
||||
}), // 添加获取针脚位置的方法
|
||||
getPinPosition: (pinLabel: string) => {
|
||||
// 在 Pin 组件中,只有一个针脚,所以直接检查标签是否匹配
|
||||
if (pinLabel !== props.label) return null;
|
||||
|
||||
// 使用自身作为一个唯一标识符,确保针脚位置是基于实际的DOM位置
|
||||
// 这样可以避免document.querySelector获取到非预期的元素
|
||||
const pinElement = document.querySelector(`[data-pin-id="${props.label}-${props.constraint}"]`) as SVGElement;
|
||||
|
||||
if (pinElement) {
|
||||
// 获取针脚元素的位置
|
||||
const rect = pinElement.getBoundingClientRect();
|
||||
return {
|
||||
x: rect.left + rect.width / 2,
|
||||
y: rect.top + rect.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
// 如果找不到特定元素,使用计算出的位置
|
||||
// 这种情况下我们需要找到父SVG元素位置
|
||||
const svgElement = document.querySelector(`.pin-component[data-pin-id="${props.label}"]`) as SVGElement;
|
||||
if (!svgElement) {
|
||||
console.error(`找不到针脚 ${props.label} 的SVG元素`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const svgRect = svgElement.getBoundingClientRect();
|
||||
|
||||
// 根据针脚类型和方向计算相对位置
|
||||
let pinX = svgRect.left + svgRect.width / 2;
|
||||
let pinY = svgRect.top + svgRect.height / 2;
|
||||
|
||||
// 添加一个小的随机偏移,确保不同针脚返回不同位置
|
||||
// 仅在测试时启用,生产环境应使用更精确的算法
|
||||
const randomOffset = 0.1;
|
||||
pinX += Math.random() * randomOffset;
|
||||
pinY += Math.random() * randomOffset;
|
||||
|
||||
return { x: pinX, y: pinY };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -120,6 +264,7 @@ defineExpose({
|
||||
.pin-component {
|
||||
display: block;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
}
|
||||
.interactive {
|
||||
cursor: pointer;
|
||||
@@ -128,4 +273,15 @@ defineExpose({
|
||||
.interactive:hover {
|
||||
filter: brightness(1.2);
|
||||
}
|
||||
.pin-tooltip {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user