diff --git a/src/components/DiagramCanvas.vue b/src/components/DiagramCanvas.vue index 87b6a81..1abc48f 100644 --- a/src/components/DiagramCanvas.vue +++ b/src/components/DiagramCanvas.vue @@ -9,7 +9,7 @@ :style="{ transform: `translate(${position.x}px, ${position.y}px) scale(${scale})` }"> - - import { ref, reactive, onMounted, onUnmounted } from 'vue'; -import Wire from '@/components/Wire.vue'; +import WireComponent from '@/components/equipments/Wire.vue'; // 定义组件接受的属性 interface ComponentItem { @@ -112,6 +113,7 @@ const selectedComponentId = ref(null); const hoveredComponent = ref(null); const draggingComponentId = ref(null); const componentDragOffset = reactive({ x: 0, y: 0 }); +const wireSelectedId = ref(null); // 组件引用跟踪 const componentRefs = ref>({}); @@ -170,20 +172,27 @@ const getComponentDefinition = (type: string) => { }; // 准备组件属性,确保类型正确 -function prepareComponentProps(props: Record): Record { - const result: Record = {}; - for (const key in props) { - let value = props[key]; +function prepareComponentProps(props: Record, componentId?: string): Record { + const result: Record = { ...props }; + + // 添加组件ID属性,用于唯一标识针脚 + if (componentId) { + result.componentId = componentId; + } + + // 确保某些属性的类型正确 + for (const key in result) { + let value = result[key]; // 只要不是 null/undefined 且不是 string,就强制转字符串 if ( (key === 'style' || key === 'direction' || key === 'type') && value != null && typeof value !== 'string' ) { - value = String(value); + result[key] = String(value); } - result[key] = value; } + return result; } @@ -363,6 +372,17 @@ function getComponentRef(componentId: string) { return componentRefs.value[component.id] || null; } +// 获取Wire组件引用 +function getWireRef(wireId: string) { + // 在SVG中查找Wire组件 + const wire = document.querySelector(`[data-wire-id="${wireId}"]`); + if (wire && '__vueParentInstance' in wire) { + // @ts-ignore - 访问Vue内部属性 + return wire.__vueParentInstance?.component?.exposed; + } + return null; +} + // 暴露给父组件的方法 defineExpose({ getComponentRef, @@ -384,6 +404,9 @@ interface WireItem { color?: string; isActive?: boolean; constraint?: string; + strokeWidth?: number; + routingMode?: 'auto' | 'orthogonal' | 'direct'; + showLabel?: boolean; } const wires = ref([]); @@ -449,52 +472,74 @@ function handlePinClick(componentId: string, pinInfo: any, event: MouseEvent) { // 添加鼠标移动监听 document.addEventListener('mousemove', onCreatingWireMouseMove); - - } else { - // 完成连线 + } else { // 完成连线 if (componentId === creatingWireStartInfo.componentId && pinInfo.label === creatingWireStartInfo.pinLabel) { // 如果点了同一个Pin,取消连线 cancelWireCreation(); return; } - // 检查约束条件 - const startConstraint = creatingWireStartInfo.constraint; - const endConstraint = pinInfo.constraint; - - if (startConstraint && endConstraint && startConstraint !== endConstraint) { - // 两个Pin都有约束,但约束不同,需要询问用户选择哪个约束 - promptForConstraintSelection( - componentId, - pinInfo, - startConstraint, - endConstraint - ); + // 获取起点和终点的约束 + const startConstraint = creatingWireStartInfo.constraint || ''; + const endConstraint = pinInfo.constraint || ''; + let finalConstraint = ''; + // 如果两端约束完全一致,直接用该约束,不弹窗 + if (startConstraint && endConstraint && startConstraint === endConstraint) { + finalConstraint = startConstraint; } else { - // 确定最终约束 - let finalConstraint = ''; - - if (startConstraint) { + // 确定最终要使用的约束 + let replacedConstraint: string | null = null; + let newConstraint: string | null = null; + if (startConstraint && endConstraint) { + const isStartSystemConstraint = startConstraint.startsWith('$'); + const isEndSystemConstraint = endConstraint.startsWith('$'); + if (!isStartSystemConstraint && isEndSystemConstraint) { + finalConstraint = startConstraint; + replacedConstraint = endConstraint; + newConstraint = startConstraint; + } else if (isStartSystemConstraint && !isEndSystemConstraint) { + finalConstraint = endConstraint; + replacedConstraint = startConstraint; + newConstraint = endConstraint; + } else if (isStartSystemConstraint && isEndSystemConstraint) { + finalConstraint = Math.random() < 0.5 ? startConstraint : endConstraint; + replacedConstraint = (finalConstraint === startConstraint) ? endConstraint : startConstraint; + newConstraint = finalConstraint; + } else { + const userChoice = confirm(`针脚约束冲突:${startConstraint} 和 ${endConstraint}。点击"确定"保留${startConstraint},点击"取消"保留${endConstraint}`); + finalConstraint = userChoice ? startConstraint : endConstraint; + replacedConstraint = userChoice ? endConstraint : startConstraint; + newConstraint = finalConstraint; + } + } else if (startConstraint) { finalConstraint = startConstraint; } else if (endConstraint) { finalConstraint = endConstraint; } else { - // 两个Pin都没有约束,生成随机约束 finalConstraint = generateRandomConstraint(); } - // 完成连线 - completeWireCreation( - componentId, - pinInfo.label, - finalConstraint, - pinInfo - ); - - // 更新两个Pin的约束 - updatePinConstraint(creatingWireStartInfo.componentId, creatingWireStartInfo.pinLabel, finalConstraint); - updatePinConstraint(componentId, pinInfo.label, finalConstraint); + // 如果发生了批量替换需求,遍历所有组件的所有Pin + if (replacedConstraint && newConstraint && replacedConstraint !== newConstraint) { + props.components.forEach(comp => { + if (comp.props && comp.props.constraint === replacedConstraint) { + emit('update-component-prop', { id: comp.id, propName: 'constraint', value: newConstraint }); + } + }); + } + } + + // 完成连线 + completeWireCreation( + componentId, + pinInfo.label, + finalConstraint, + pinInfo + ); + + // 更新两个Pin的约束 + updatePinConstraint(creatingWireStartInfo.componentId, creatingWireStartInfo.pinLabel, finalConstraint); + updatePinConstraint(componentId, pinInfo.label, finalConstraint); } - } } // 生成随机约束名 @@ -503,36 +548,6 @@ function generateRandomConstraint() { return `$auto_constraint_${randomId}`; } -// 询问用户选择约束 -function promptForConstraintSelection( - endComponentId: string, - endPinInfo: any, - startConstraint: string, - endConstraint: string -) { - // 在实际应用中,这里可以使用Modal对话框等UI组件 - // 这里简化为使用浏览器的confirm - const useStartConstraint = confirm( - `连接两个不同约束的Pin:\n` + - `- 起点约束: ${startConstraint}\n` + - `- 终点约束: ${endConstraint}\n\n` + - `点击"确定"使用起点约束,点击"取消"使用终点约束。` - ); - - const finalConstraint = useStartConstraint ? startConstraint : endConstraint; - // 完成连线 - completeWireCreation( - endComponentId, - endPinInfo.label, - finalConstraint, - endPinInfo - ); - - // 更新两个Pin的约束 - updatePinConstraint(creatingWireStartInfo.componentId, creatingWireStartInfo.pinLabel, finalConstraint); - updatePinConstraint(endComponentId, endPinInfo.label, finalConstraint); -} - // 更新Pin的约束 function updatePinConstraint(componentId: string, pinLabel: string, constraint: string) { // 通过组件ID获取组件实例 @@ -575,27 +590,16 @@ function completeWireCreation(endComponentId: string, endPinLabel: string, const endX = (pinPosition.x - containerRect.left - position.x) / scale.value; endY = (pinPosition.y - containerRect.top - position.y) / scale.value; console.log(`通过 getPinPosition 获取终点针脚 ${endPinLabel} 的画布坐标: (${endX}, ${endY})`); - } else { - console.warn(`getPinPosition 返回 null,将使用备选方法`); + } else { console.warn(`getPinPosition 返回 null,将使用备选方法`); } } else if (endPinInfo && endPinInfo.position) { // 如果 getPinPosition 不可用,使用传入的针脚位置信息 const pinPagePosition = endPinInfo.position; endX = (pinPagePosition.x - containerRect.left - position.x) / scale.value; endY = (pinPagePosition.y - containerRect.top - position.y) / scale.value; - console.log(`通过 pinInfo.position 获取终点针脚位置: (${endX}, ${endY})`); - } else { - console.warn(`无法获取针脚 ${endPinLabel} 的精确位置,使用鼠标位置代替`); } - // 检查起点和终点是否重合 - const distanceSquared = Math.pow(endX - creatingWireStart.x, 2) + Math.pow(endY - creatingWireStart.y, 2); - if (distanceSquared < 1) { // 如果距离小于1像素 - console.warn(`起点和终点太接近 (${distanceSquared}像素²),调整终点位置`); - // 稍微调整终点位置,避免重合 - endX += 10 + Math.random() * 5; - endY += 10 + Math.random() * 5; - } - // 创建新的连线 + + // 创建新的连线 const newWire: WireItem = { id: `wire-${Date.now()}`, startX: creatingWireStart.x, @@ -607,18 +611,12 @@ function completeWireCreation(endComponentId: string, endPinLabel: string, const endComponentId: endComponentId, endPinLabel: endPinLabel, color: '#4a5568', - constraint: constraint + constraint: constraint, + routingMode: 'orthogonal', + strokeWidth: 2, + showLabel: false }; - console.log(`新连线创建完成:`, newWire); - - // 确保起点和终点不重合 - if (Math.abs(newWire.startX - newWire.endX) < 1 && Math.abs(newWire.startY - newWire.endY) < 1) { - console.warn(`连线的起点和终点重合,调整终点位置`); - newWire.endX += 20; - newWire.endY += 20; - } - wires.value.push(newWire); // 通知父组件连线已创建 @@ -642,28 +640,7 @@ function onCreatingWireMouseMove(e: MouseEvent) { // 处理连线点击 function handleWireClick(wire: WireItem) { - // 这里可以添加连线选中、删除等功能 - const deleteWire = confirm('是否删除此连线?'); - if (deleteWire) { - // 删除连线 - const index = wires.value.findIndex(w => w.id === wire.id); - if (index !== -1) { - const deletedWire = wires.value.splice(index, 1)[0]; - - // 通知父组件连线已删除 - emit('wire-deleted', deletedWire.id); - } - } -} - -// 更新所有连线位置 -function updateAllWires() { - if (!canvasContainer.value) return; - - // 遍历所有连线并更新位置 - wires.value.forEach(wire => { - updateWireWithPinPositions(wire); - }); + wireSelectedId.value = wire.id; } // 根据针脚位置更新连线 @@ -671,32 +648,15 @@ function updateWireWithPinPositions(wire: WireItem) { if (!canvasContainer.value) return; const containerRect = canvasContainer.value.getBoundingClientRect(); - console.log(`更新连线 ${wire.id},当前位置: 起点(${wire.startX}, ${wire.startY}), 终点(${wire.endX}, ${wire.endY})`); - - // 保存原始位置,用于检测变化 - const originalStartX = wire.startX; - const originalStartY = wire.startY; - const originalEndX = wire.endX; - const originalEndY = wire.endY; - // 更新起点 if (wire.startComponentId && wire.startPinLabel) { const startComponentRef = componentRefs.value[wire.startComponentId]; if (startComponentRef && startComponentRef.getPinPosition) { const pinPosition = startComponentRef.getPinPosition(wire.startPinLabel); if (pinPosition) { - const newStartX = (pinPosition.x - containerRect.left - position.x) / scale.value; - const newStartY = (pinPosition.y - containerRect.top - position.y) / scale.value; - - console.log(`更新连线起点 ${wire.startComponentId}/${wire.startPinLabel}: (${wire.startX}, ${wire.startY}) => (${newStartX}, ${newStartY})`); - - wire.startX = newStartX; - wire.startY = newStartY; - } else { - console.warn(`无法获取针脚 ${wire.startComponentId}/${wire.startPinLabel} 的位置`); + wire.startX = (pinPosition.x - containerRect.left - position.x) / scale.value; + wire.startY = (pinPosition.y - containerRect.top - position.y) / scale.value; } - } else { - console.warn(`组件 ${wire.startComponentId} 没有实现 getPinPosition 方法`); } } @@ -706,46 +666,20 @@ function updateWireWithPinPositions(wire: WireItem) { if (endComponentRef && endComponentRef.getPinPosition) { const pinPosition = endComponentRef.getPinPosition(wire.endPinLabel); if (pinPosition) { - const newEndX = (pinPosition.x - containerRect.left - position.x) / scale.value; - const newEndY = (pinPosition.y - containerRect.top - position.y) / scale.value; - - console.log(`更新连线终点 ${wire.endComponentId}/${wire.endPinLabel}: (${wire.endX}, ${wire.endY}) => (${newEndX}, ${newEndY})`); - - wire.endX = newEndX; - wire.endY = newEndY; - } else { - console.warn(`无法获取针脚 ${wire.endComponentId}/${wire.endPinLabel} 的位置`); + wire.endX = (pinPosition.x - containerRect.left - position.x) / scale.value; + wire.endY = (pinPosition.y - containerRect.top - position.y) / scale.value; } - } else { - console.warn(`组件 ${wire.endComponentId} 没有实现 getPinPosition 方法`); } } - - // 在更新后检查起点和终点是否重合 - const positionChanged = - originalStartX !== wire.startX || - originalStartY !== wire.startY || - originalEndX !== wire.endX || - originalEndY !== wire.endY; - - if (positionChanged) { - // 如果位置有更新,确保起点和终点不重合 - ensureUniquePinPositions(wire); - } } // 更新与组件相关的所有连线位置 function updateWiresForComponent(componentId: string) { if (!canvasContainer.value || !componentId) return; - console.log(`更新组件 ${componentId} 相关的连线位置`); - // 检查组件是否存在 const component = props.components.find(c => c.id === componentId); - if (!component) { - console.warn(`找不到组件 ${componentId}`); - return; - } + if (!component) return; // 查找与该组件关联的所有连线 const relatedWires = wires.value.filter(wire => @@ -753,49 +687,14 @@ function updateWiresForComponent(componentId: string) { wire.endComponentId === componentId ); - console.log(`找到 ${relatedWires.length} 条相关连线`); - - if (relatedWires.length === 0) { - // 没有找到直接关联的连线,检查所有连线 - console.log('没有找到直接关联的连线,检查所有连线'); - - // 打印所有连线信息,帮助调试 - wires.value.forEach((wire, index) => { - console.log(`连线 ${index}: startComponentId=${wire.startComponentId}, endComponentId=${wire.endComponentId}`); - }); - - return; - } + if (relatedWires.length === 0) return; // 更新所有相关连线的位置 relatedWires.forEach(wire => { - console.log(`更新连线 ${wire.id} (${wire.startComponentId}/${wire.startPinLabel} -> ${wire.endComponentId}/${wire.endPinLabel})`); updateWireWithPinPositions(wire); }); } -// 确保针脚位置有差异 -function ensureUniquePinPositions(wire: WireItem) { - if (Math.abs(wire.startX - wire.endX) < 5 && Math.abs(wire.startY - wire.endY) < 5) { - console.warn('检测到连线起点和终点非常接近,添加随机偏移'); - - // 根据组件ID和针脚标签生成一个确定性的偏移 - const idSum = (wire.startComponentId?.charCodeAt(0) || 0) + - (wire.endComponentId?.charCodeAt(0) || 0) + - (wire.startPinLabel?.charCodeAt(0) || 0) + - (wire.endPinLabel?.charCodeAt(0) || 0); - - // 使用组件ID和针脚标签生成偏移,确保相同的组件始终有相同的偏移 - const offsetX = 20 * Math.cos(idSum * 0.1); - const offsetY = 20 * Math.sin(idSum * 0.1); - - wire.endX += offsetX; - wire.endY += offsetY; - - console.log(`应用偏移 (${offsetX.toFixed(2)}, ${offsetY.toFixed(2)}) 到连线终点`); - } -} - // --- 生命周期钩子 --- onMounted(() => { // 初始化中心位置 @@ -809,18 +708,6 @@ onMounted(() => { // 添加键盘事件监听器 window.addEventListener('keydown', handleKeyDown); - - // 添加定期更新连线位置的定时器 - const wireUpdateInterval = setInterval(() => { - if (wires.value.length > 0) { - updateAllWires(); - } - }, 1000); // 每秒更新一次所有连线位置 - - // 在组件卸载时清除定时器 - onUnmounted(() => { - clearInterval(wireUpdateInterval); - }); }); // 处理键盘事件 @@ -838,6 +725,16 @@ function handleKeyDown(e: KeyboardEvent) { // 取消连线创建 cancelWireCreation(); } + + // 删除选中连线 + if (wireSelectedId.value && (e.key === 'Delete' || e.key === 'Backspace')) { + const idx = wires.value.findIndex(w => w.id === wireSelectedId.value); + if (idx !== -1) { + const deletedWire = wires.value.splice(idx, 1)[0]; + emit('wire-deleted', deletedWire.id); + wireSelectedId.value = null; + } + } } onUnmounted(() => { @@ -891,7 +788,7 @@ onUnmounted(() => { left: 0; width: 100%; height: 100%; - pointer-events: none; + pointer-events: auto; /* 修复:允许线被点击 */ z-index: 50; } @@ -961,4 +858,9 @@ onUnmounted(() => { .component-wrapper :deep(input) { pointer-events: auto !important; } + +.wire-active { + stroke: #ff9800 !important; + filter: drop-shadow(0 0 4px #ff9800cc); +} diff --git a/src/components/Wire.vue b/src/components/Wire.vue deleted file mode 100644 index 2528d96..0000000 --- a/src/components/Wire.vue +++ /dev/null @@ -1,141 +0,0 @@ - - - - - diff --git a/src/components/equipments/Pin.vue b/src/components/equipments/Pin.vue index ff4e20e..53ba3fd 100644 --- a/src/components/equipments/Pin.vue +++ b/src/components/equipments/Pin.vue @@ -5,71 +5,63 @@ :height="height" :viewBox="'0 0 ' + viewBoxWidth + ' ' + viewBoxHeight" class="pin-component" - :data-pin-id="props.label" - > - - + + + + :data-pin-element="`${props.componentId}-${props.label}`" /> - + + + :data-pin-element="`${props.componentId}-${props.label}`" /> {{ props.label }} - + + {{ props.label }} - - + :data-pin-element="`${props.componentId}-${props.label}`" + /> + + - -
- {{ tooltipText }} -
+ + diff --git a/src/components/equipments/componentConfig.ts b/src/components/equipments/componentConfig.ts index 065ebc6..9bd3c94 100644 --- a/src/components/equipments/componentConfig.ts +++ b/src/components/equipments/componentConfig.ts @@ -318,7 +318,55 @@ const componentConfigs: Record = { description: '相同约束字符串的组件将被视为有电气连接' } ] - } + }, + // 线缆配置 + Wire: { + props: [ + { + name: 'routingMode', + type: 'select', + label: '路由方式', + default: 'orthogonal', + options: [ + { value: 'orthogonal', label: '直角' }, + { value: 'direct', label: '直线' }, + { value: 'auto', label: '自动' } + ], + description: '线路连接方式' + }, + { + name: 'strokeColor', + type: 'string', + label: '线条颜色', + default: '#4a5568', + description: '线条颜色,使用CSS颜色值' + }, + { + name: 'strokeWidth', + type: 'number', + label: '线条宽度', + default: 2, + min: 1, + max: 10, + step: 0.5, + description: '线条宽度' + }, + { + name: 'constraint', + type: 'string', + label: '约束名称', + default: '', + description: '线路约束名称,用于标识连接关系' + }, + { + name: 'showLabel', + type: 'boolean', + label: '显示标签', + default: false, + description: '是否显示连线上的约束标签' + } + ] + }, }; // 获取组件配置的函数 diff --git a/src/views/ProjectView.vue b/src/views/ProjectView.vue index e475bdb..84cbecc 100644 --- a/src/views/ProjectView.vue +++ b/src/views/ProjectView.vue @@ -344,7 +344,7 @@ function updateComponentProp(componentId: string | { id: string; propName: strin // 处理连线创建事件 function handleWireCreated(wireData: any) { console.log('Wire created:', wireData); - // 可以在这里添加连线创建的相关逻辑 + // 连线已在DiagramCanvas.vue中完成约束处理 } // 处理连线删除事件