feat: Enhance equipment components with pin functionality and constraint management
- Added pin support to MechanicalButton, enabling pin-click events and componentId handling. - Updated Pin component to manage constraint states and colors dynamically. - Integrated SMT_LED with pin functionality, allowing LED state to respond to constraints. - Enhanced Wire component to reflect constraint colors and manage wire states based on pin connections. - Introduced wireManager for managing wire states and constraints. - Implemented a constraints store for managing and notifying constraint state changes across components. - Updated component configuration to remove appearance options and clarify constraint descriptions. - Improved ProjectView to handle optional chaining for props and ensure robust data handling. - Initialized constraint communication in main application entry point.
This commit is contained in:
@@ -19,13 +19,10 @@
|
||||
:end-y="wire.endY"
|
||||
:stroke-color="wire.color || '#4a5568'"
|
||||
:stroke-width="2"
|
||||
:is-active="wireSelectedId === wire.id"
|
||||
:is-active="false"
|
||||
:start-component-id="wire.startComponentId"
|
||||
:start-pin-label="wire.startPinLabel"
|
||||
:end-component-id="wire.endComponentId"
|
||||
:end-pin-label="wire.endPinLabel"
|
||||
:constraint="wire.constraint"
|
||||
@click="handleWireClick(wire)"
|
||||
/>
|
||||
|
||||
<!-- 正在创建的连线 -->
|
||||
@@ -82,6 +79,15 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted } from 'vue';
|
||||
import WireComponent from '@/components/equipments/Wire.vue';
|
||||
import { wires, addWire, deleteWire, updateWiresConstraintByPin, findWiresByPin, isCreatingWire, creatingWireStart, creatingWireStartInfo, mousePosition, resetWireCreation, setWireCreationStart, setMousePosition } from './wireManager';
|
||||
import type { WireItem } from './wireManager';
|
||||
|
||||
// 右键菜单处理函数(如无特殊需求可为空实现)
|
||||
function handleContextMenu(e: MouseEvent) {
|
||||
// 可根据需要自定义右键菜单逻辑
|
||||
// 目前只是阻止默认行为
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
// 定义组件接受的属性
|
||||
interface ComponentItem {
|
||||
@@ -113,7 +119,6 @@ const selectedComponentId = ref<string | null>(null);
|
||||
const hoveredComponent = ref<string | null>(null);
|
||||
const draggingComponentId = ref<string | null>(null);
|
||||
const componentDragOffset = reactive({ x: 0, y: 0 });
|
||||
const wireSelectedId = ref<string | null>(null);
|
||||
|
||||
// 组件引用跟踪
|
||||
const componentRefs = ref<Record<string, any>>({});
|
||||
@@ -174,25 +179,9 @@ const getComponentDefinition = (type: string) => {
|
||||
// 准备组件属性,确保类型正确
|
||||
function prepareComponentProps(props: Record<string, any>, componentId?: string): Record<string, any> {
|
||||
const result: Record<string, any> = { ...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'
|
||||
) {
|
||||
result[key] = String(value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -360,6 +349,19 @@ function stopComponentDrag() {
|
||||
|
||||
// 更新组件属性
|
||||
function updateComponentProp(componentId: string, propName: string, value: any) {
|
||||
// 如果是引脚约束变更,自动调用updatePinConstraint
|
||||
if (propName === 'constraint') {
|
||||
// 查找该组件的Pin label(假设label唯一,或可扩展为多Pin)
|
||||
const componentRef = componentRefs.value[componentId];
|
||||
if (componentRef && componentRef.getInfo) {
|
||||
const pinInfo = componentRef.getInfo();
|
||||
if (pinInfo) {
|
||||
updatePinConstraint(componentId, value);
|
||||
return; // 已处理,无需再emit
|
||||
}
|
||||
}
|
||||
}
|
||||
// 其它属性正常emit
|
||||
emit('update-component-prop', { id: componentId, propName, value });
|
||||
}
|
||||
|
||||
@@ -391,94 +393,75 @@ defineExpose({
|
||||
});
|
||||
|
||||
// --- 连线状态 ---
|
||||
interface WireItem {
|
||||
id: string;
|
||||
startX: number;
|
||||
startY: number;
|
||||
endX: number;
|
||||
endY: number;
|
||||
startComponentId: string;
|
||||
startPinLabel: string;
|
||||
endComponentId?: string;
|
||||
endPinLabel?: string;
|
||||
color?: string;
|
||||
isActive?: boolean;
|
||||
constraint?: string;
|
||||
strokeWidth?: number;
|
||||
routingMode?: 'auto' | 'orthogonal' | 'direct';
|
||||
showLabel?: boolean;
|
||||
// 处理连线创建事件
|
||||
function handleWireCreated(wireData: any) {
|
||||
addWire(wireData);
|
||||
emit('wire-created', wireData);
|
||||
}
|
||||
// 删除连线
|
||||
function handleWireDeleted(wireId: string) {
|
||||
deleteWire(wireId);
|
||||
emit('wire-deleted', wireId);
|
||||
}
|
||||
// 更新Pin的约束时同步Wire
|
||||
function updatePinConstraint(componentId: string, constraint: string) {
|
||||
// 通过组件ID获取组件实例
|
||||
const component = props.components.find(c => c.id === componentId);
|
||||
if (!component) return;
|
||||
|
||||
// 获取组件引用
|
||||
const componentRef = componentRefs.value[componentId];
|
||||
if (!componentRef) return;
|
||||
|
||||
// 更新组件属性
|
||||
if (component.props && componentRef.getInfo) {
|
||||
const pinInfo = componentRef.getInfo();
|
||||
if (pinInfo) {
|
||||
emit('update-component-prop', {
|
||||
id: componentId,
|
||||
propName: 'constraint',
|
||||
value: constraint
|
||||
});
|
||||
}
|
||||
}
|
||||
// 同步所有相关Wire的constraint
|
||||
updateWiresConstraintByPin(componentId, constraint);
|
||||
}
|
||||
|
||||
const wires = ref<WireItem[]>([]);
|
||||
const isCreatingWire = ref(false);
|
||||
const creatingWireStart = reactive({ x: 0, y: 0 });
|
||||
const creatingWireStartInfo = reactive({
|
||||
componentId: '',
|
||||
pinLabel: '',
|
||||
constraint: ''
|
||||
});
|
||||
const mousePosition = reactive({ x: 0, y: 0 });
|
||||
|
||||
// 更新鼠标位置
|
||||
function updateMousePosition(e: MouseEvent) {
|
||||
if (!canvasContainer.value) return;
|
||||
|
||||
const containerRect = canvasContainer.value.getBoundingClientRect();
|
||||
mousePosition.x = (e.clientX - containerRect.left - position.x) / scale.value;
|
||||
mousePosition.y = (e.clientY - containerRect.top - position.y) / scale.value;
|
||||
setMousePosition(
|
||||
(e.clientX - containerRect.left - position.x) / scale.value,
|
||||
(e.clientY - containerRect.top - position.y) / scale.value
|
||||
);
|
||||
}
|
||||
|
||||
// 处理Pin点击事件
|
||||
function handlePinClick(componentId: string, pinInfo: any, event: MouseEvent) {
|
||||
if (!canvasContainer.value) return;
|
||||
|
||||
// 获取容器位置
|
||||
const containerRect = canvasContainer.value.getBoundingClientRect();
|
||||
|
||||
// 更新鼠标位置 (用于跟踪临时连线)
|
||||
updateMousePosition(event);
|
||||
|
||||
// 检查 pinInfo 是否有效
|
||||
if (!pinInfo || !pinInfo.label) {
|
||||
console.error('无效的针脚信息:', pinInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
// 针脚在页面上的绝对位置
|
||||
if (!pinInfo.position) {
|
||||
console.error('针脚信息中缺少位置数据:', pinInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
const pinPagePosition = pinInfo.position;
|
||||
console.log(`针脚 ${pinInfo.label} 的页面坐标:`, pinPagePosition);
|
||||
|
||||
// 将针脚页面位置转换为画布坐标系中的位置
|
||||
const pinCanvasX = (pinPagePosition.x - containerRect.left - position.x) / scale.value;
|
||||
const pinCanvasY = (pinPagePosition.y - containerRect.top - position.y) / scale.value;
|
||||
console.log(`针脚 ${pinInfo.label} 的画布坐标:`, { x: pinCanvasX, y: pinCanvasY });
|
||||
|
||||
if (!isCreatingWire.value) {
|
||||
// 开始创建连线
|
||||
isCreatingWire.value = true;
|
||||
// 使用针脚的实际位置作为连线起点
|
||||
creatingWireStart.x = pinCanvasX;
|
||||
creatingWireStart.y = pinCanvasY;
|
||||
creatingWireStartInfo.componentId = componentId;
|
||||
creatingWireStartInfo.pinLabel = pinInfo.label;
|
||||
creatingWireStartInfo.constraint = pinInfo.constraint;
|
||||
|
||||
console.log(`开始创建连线,起点针脚: ${componentId}/${pinInfo.label}, 位置: (${pinCanvasX}, ${pinCanvasY})`);
|
||||
|
||||
// 添加鼠标移动监听
|
||||
setWireCreationStart(pinCanvasX, pinCanvasY, componentId, pinInfo.label, pinInfo.constraint);
|
||||
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 || '';
|
||||
@@ -531,15 +514,14 @@ function handlePinClick(componentId: string, pinInfo: any, event: MouseEvent) {
|
||||
// 完成连线
|
||||
completeWireCreation(
|
||||
componentId,
|
||||
pinInfo.label,
|
||||
finalConstraint,
|
||||
pinInfo
|
||||
);
|
||||
|
||||
// 更新两个Pin的约束
|
||||
updatePinConstraint(creatingWireStartInfo.componentId, creatingWireStartInfo.pinLabel, finalConstraint);
|
||||
updatePinConstraint(componentId, pinInfo.label, finalConstraint);
|
||||
}
|
||||
updatePinConstraint(creatingWireStartInfo.componentId, finalConstraint);
|
||||
updatePinConstraint(componentId, finalConstraint);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成随机约束名
|
||||
@@ -548,31 +530,8 @@ function generateRandomConstraint() {
|
||||
return `$auto_constraint_${randomId}`;
|
||||
}
|
||||
|
||||
// 更新Pin的约束
|
||||
function updatePinConstraint(componentId: string, pinLabel: string, constraint: string) {
|
||||
// 通过组件ID获取组件实例
|
||||
const component = props.components.find(c => c.id === componentId);
|
||||
if (!component) return;
|
||||
|
||||
// 获取组件引用
|
||||
const componentRef = componentRefs.value[componentId];
|
||||
if (!componentRef) return;
|
||||
|
||||
// 更新组件属性
|
||||
if (component.props && componentRef.getInfo) {
|
||||
const pinInfo = componentRef.getInfo();
|
||||
if (pinInfo && pinInfo.label === pinLabel) {
|
||||
emit('update-component-prop', {
|
||||
id: componentId,
|
||||
propName: 'constraint',
|
||||
value: constraint
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 完成连线创建
|
||||
function completeWireCreation(endComponentId: string, endPinLabel: string, constraint: string, endPinInfo?: any) {
|
||||
function completeWireCreation(endComponentId: string, constraint: string, endPinInfo?: any) {
|
||||
if (!canvasContainer.value) return;
|
||||
const containerRect = canvasContainer.value.getBoundingClientRect();
|
||||
|
||||
@@ -585,11 +544,11 @@ function completeWireCreation(endComponentId: string, endPinLabel: string, const
|
||||
// 尝试使用 getPinPosition 获取精确的针脚位置
|
||||
const endComponentRef = componentRefs.value[endComponentId];
|
||||
if (endComponentRef && endComponentRef.getPinPosition) {
|
||||
const pinPosition = endComponentRef.getPinPosition(endPinLabel);
|
||||
const pinPosition = endComponentRef.getPinPosition(endComponentId);
|
||||
if (pinPosition) {
|
||||
endX = (pinPosition.x - containerRect.left - position.x) / scale.value;
|
||||
endY = (pinPosition.y - containerRect.top - position.y) / scale.value;
|
||||
console.log(`通过 getPinPosition 获取终点针脚 ${endPinLabel} 的画布坐标: (${endX}, ${endY})`);
|
||||
console.log(`通过 getPinPosition 获取终点针脚 ${endComponentId} 的画布坐标: (${endX}, ${endY})`);
|
||||
} else { console.warn(`getPinPosition 返回 null,将使用备选方法`);
|
||||
}
|
||||
} else if (endPinInfo && endPinInfo.position) {
|
||||
@@ -607,9 +566,7 @@ function completeWireCreation(endComponentId: string, endPinLabel: string, const
|
||||
endX: endX,
|
||||
endY: endY,
|
||||
startComponentId: creatingWireStartInfo.componentId,
|
||||
startPinLabel: creatingWireStartInfo.pinLabel,
|
||||
endComponentId: endComponentId,
|
||||
endPinLabel: endPinLabel,
|
||||
color: '#4a5568',
|
||||
constraint: constraint,
|
||||
routingMode: 'orthogonal',
|
||||
@@ -629,7 +586,7 @@ function completeWireCreation(endComponentId: string, endPinLabel: string, const
|
||||
|
||||
// 取消连线创建
|
||||
function cancelWireCreation() {
|
||||
isCreatingWire.value = false;
|
||||
resetWireCreation();
|
||||
document.removeEventListener('mousemove', onCreatingWireMouseMove);
|
||||
}
|
||||
|
||||
@@ -638,36 +595,51 @@ function onCreatingWireMouseMove(e: MouseEvent) {
|
||||
updateMousePosition(e);
|
||||
}
|
||||
|
||||
// 处理连线点击
|
||||
function handleWireClick(wire: WireItem) {
|
||||
wireSelectedId.value = wire.id;
|
||||
}
|
||||
|
||||
// 根据针脚位置更新连线
|
||||
function updateWireWithPinPositions(wire: WireItem) {
|
||||
if (!canvasContainer.value) return;
|
||||
const containerRect = canvasContainer.value.getBoundingClientRect();
|
||||
|
||||
// 更新起点
|
||||
if (wire.startComponentId && wire.startPinLabel) {
|
||||
if (wire.startComponentId) {
|
||||
const startComponentRef = componentRefs.value[wire.startComponentId];
|
||||
if (startComponentRef && startComponentRef.getPinPosition) {
|
||||
const pinPosition = startComponentRef.getPinPosition(wire.startPinLabel);
|
||||
const pinPosition = startComponentRef.getPinPosition(wire.startComponentId);
|
||||
if (pinPosition) {
|
||||
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}的针脚${wire.startComponentId}位置`);
|
||||
// 尝试多次获取位置(针对初次渲染可能有延迟的情况)
|
||||
setTimeout(() => {
|
||||
const retryPosition = startComponentRef.getPinPosition(wire.startComponentId);
|
||||
if (retryPosition) {
|
||||
wire.startX = (retryPosition.x - containerRect.left - position.x) / scale.value;
|
||||
wire.startY = (retryPosition.y - containerRect.top - position.y) / scale.value;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新终点
|
||||
if (wire.endComponentId && wire.endPinLabel) {
|
||||
if (wire.endComponentId) {
|
||||
const endComponentRef = componentRefs.value[wire.endComponentId];
|
||||
if (endComponentRef && endComponentRef.getPinPosition) {
|
||||
const pinPosition = endComponentRef.getPinPosition(wire.endPinLabel);
|
||||
const pinPosition = endComponentRef.getPinPosition(wire.endComponentId);
|
||||
if (pinPosition) {
|
||||
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}的针脚${wire.endComponentId}位置`);
|
||||
// 尝试多次获取位置(针对初次渲染可能有延迟的情况)
|
||||
setTimeout(() => {
|
||||
const retryPosition = endComponentRef.getPinPosition(wire.endComponentId);
|
||||
if (retryPosition) {
|
||||
wire.endX = (retryPosition.x - containerRect.left - position.x) / scale.value;
|
||||
wire.endY = (retryPosition.y - containerRect.top - position.y) / scale.value;
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -725,16 +697,6 @@ 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(() => {
|
||||
@@ -757,11 +719,11 @@ onUnmounted(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-image:
|
||||
/* background-image:
|
||||
linear-gradient(to right, rgba(100, 100, 100, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgba(100, 100, 100, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(to right, rgba(80, 80, 80, 0.2) 100px, transparent 100px),
|
||||
linear-gradient(to bottom, rgba(80, 80, 80, 0.2) 100px, transparent 100px);
|
||||
linear-gradient(to bottom, rgba(80, 80, 80, 0.2) 100px, transparent 100px); */
|
||||
background-size: 20px 20px, 20px 20px, 100px 100px, 100px 100px;
|
||||
background-position: 0 0;
|
||||
user-select: none;
|
||||
@@ -827,13 +789,13 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
/* 为黑暗模式设置不同的网格线颜色 */
|
||||
:root[data-theme="dark"] .diagram-container {
|
||||
/* :root[data-theme="dark"] .diagram-container {
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(200, 200, 200, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgba(200, 200, 200, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(to right, rgba(180, 180, 180, 0.15) 100px, transparent 100px),
|
||||
linear-gradient(to bottom, rgba(180, 180, 180, 0.15) 100px, transparent 100px);
|
||||
}
|
||||
} */
|
||||
|
||||
/* 深度选择器 - 默认阻止SVG内部元素的鼠标事件,但允许SVG本身和特定交互元素 */
|
||||
.component-wrapper :deep(svg) {
|
||||
@@ -860,7 +822,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.wire-active {
|
||||
stroke: #ff9800 !important;
|
||||
filter: drop-shadow(0 0 4px #ff9800cc);
|
||||
stroke: #0099ff !important;
|
||||
filter: drop-shadow(0 0 4px #0099ffcc);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user