FPGA_WebLab/src/components/diagramManager.ts

335 lines
8.6 KiB
TypeScript

import type { JSX } from "vue/jsx-runtime";
// 定义 diagram.json 的类型结构
export interface DiagramData {
version: number;
author: string;
editor: string;
parts: DiagramPart[];
connections: ConnectionArray[];
exportTime?: string; // 导出时的时间戳
}
// 组件部分的类型定义
export interface DiagramPart {
id: string;
type: string;
x: number;
y: number;
attrs: Record<string, any>;
capsPage?: JSX.Element;
rotate: number;
group: string;
positionlock: boolean;
hidepins: boolean;
isOn: boolean;
index?: number; // 显示层级,数值越大显示越靠前
}
// 连接类型定义 - 使用元组类型表示四元素数组
export type ConnectionArray = [string, string, number, string[]];
// 解析连接字符串为组件ID和引脚ID
export function parseConnectionPin(connectionPin: string): { componentId: string; pinId: string } {
const [componentId, pinId] = connectionPin.split(':');
return { componentId, pinId };
}
// 将连接数组转换为适用于渲染的格式
export function connectionArrayToWireItem(
connection: ConnectionArray,
index: number,
startPos = { x: 0, y: 0 },
endPos = { x: 0, y: 0 }
): WireItem {
const [startPinStr, endPinStr, width, path] = connection;
const { componentId: startComponentId, pinId: startPinId } = parseConnectionPin(startPinStr);
const { componentId: endComponentId, pinId: endPinId } = parseConnectionPin(endPinStr);
return {
id: `wire-${index}`,
startX: startPos.x,
startY: startPos.y,
endX: endPos.x,
endY: endPos.y,
startComponentId,
startPinId,
endComponentId,
endPinId,
strokeWidth: width,
color: '#4a5568', // 默认颜色
routingMode: 'path',
pathCommands: path,
showLabel: false
};
}
// WireItem 接口定义
export interface WireItem {
id: string;
startX: number;
startY: number;
endX: number;
endY: number;
startComponentId: string;
startPinId?: string;
endComponentId: string;
endPinId?: string;
strokeWidth: number;
color: string;
routingMode: 'orthogonal' | 'path';
constraint?: string;
pathCommands?: string[];
showLabel: boolean;
}
// 从本地存储加载图表数据
export async function loadDiagramData(): Promise<DiagramData> {
try {
// 先尝试从本地存储加载
const savedData = localStorage.getItem('diagramData');
if (savedData) {
return JSON.parse(savedData);
}
// 如果本地存储没有,从文件加载
const response = await fetch('/src/components/diagram.json');
if (!response.ok) {
throw new Error(`Failed to load diagram.json: ${response.statusText}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error loading diagram data:', error);
// 返回空的默认数据结构
return createEmptyDiagram();
}
}
// 创建空的图表数据
export function createEmptyDiagram(): DiagramData {
return {
version: 1,
author: 'user',
editor: 'user',
parts: [],
connections: []
};
}
// 保存图表数据到本地存储
export function saveDiagramData(data: DiagramData): void {
try {
localStorage.setItem('diagramData', JSON.stringify(data));
} catch (error) {
console.error('Error saving diagram data:', error);
}
}
// 添加新组件到图表数据
export function addPart(data: DiagramData, part: DiagramPart): DiagramData {
return {
...data,
parts: [...data.parts, part]
};
}
// 更新组件位置
export function updatePartPosition(
data: DiagramData,
partId: string,
x: number,
y: number
): DiagramData {
return {
...data,
parts: data.parts.map(part =>
part.id === partId
? { ...part, x, y }
: part
)
};
}
// 更新组件属性
export function updatePartAttribute(
data: DiagramData,
partId: string,
attrName: string,
value: any
): DiagramData {
return {
...data,
parts: data.parts.map(part =>
part.id === partId
? {
...part,
attrs: {
...part.attrs,
[attrName]: value
}
}
: part
)
};
}
// 删除组件及同组组件
export function deletePart(data: DiagramData, partId: string): DiagramData {
// 首先找到要删除的组件
const component = data.parts.find(part => part.id === partId);
if (!component) return data;
// 收集需要删除的组件ID列表
const componentsToDelete: string[] = [partId];
// 如果组件属于一个组,则找出所有同组的组件
if (component.group && component.group !== '') {
const groupMembers = data.parts.filter(
p => p.group === component.group && p.id !== partId
);
// 将同组组件ID添加到删除列表
componentsToDelete.push(...groupMembers.map(p => p.id));
console.log(`删除组件 ${partId} 及其组 ${component.group} 中的 ${groupMembers.length} 个组件`);
}
return {
...data,
// 删除所有标记的组件
parts: data.parts.filter(part => !componentsToDelete.includes(part.id)),
// 删除与这些组件相关的所有连接
connections: data.connections.filter(conn => {
const [startPin, endPin] = conn;
const startCompId = startPin.split(':')[0];
const endCompId = endPin.split(':')[0];
// 检查连接两端的组件是否在删除列表中
return !componentsToDelete.includes(startCompId) && !componentsToDelete.includes(endCompId);
})
};
}
// 添加连接
export function addConnection(
data: DiagramData,
startComponentId: string,
startPinId: string,
endComponentId: string,
endPinId: string,
width: number = 2,
path: string[] = []
): DiagramData {
const newConnection: ConnectionArray = [
`${startComponentId}:${startPinId}`,
`${endComponentId}:${endPinId}`,
width,
path
];
return {
...data,
connections: [...data.connections, newConnection]
};
}
// 删除连接
export function deleteConnection(
data: DiagramData,
connectionIndex: number
): DiagramData {
return {
...data,
connections: data.connections.filter((_, index) => index !== connectionIndex)
};
}
// 查找与组件关联的所有连接
export function findConnectionsByPart(
data: DiagramData,
partId: string
): { connection: ConnectionArray; index: number }[] {
return data.connections
.map((connection, index) => ({ connection, index }))
.filter(({ connection }) => {
const [startPin, endPin] = connection;
const startCompId = startPin.split(':')[0];
const endCompId = endPin.split(':')[0];
return startCompId === partId || endCompId === partId;
});
}
// 基于组的移动相关组件
export function moveGroupComponents(
data: DiagramData,
groupId: string,
deltaX: number,
deltaY: number
): DiagramData {
if (!groupId) return data;
return {
...data,
parts: data.parts.map(part =>
part.group === groupId
? { ...part, x: part.x + deltaX, y: part.y + deltaY }
: part
)
};
}
// 添加验证diagram.json文件的函数
export function validateDiagramData(data: any): { isValid: boolean; errors: string[] } {
const errors: string[] = [];
// 检查版本号
if (!data.version) {
errors.push('缺少version字段');
}
// 检查parts数组
if (!Array.isArray(data.parts)) {
errors.push('parts字段不是数组');
} else {
// 验证parts中的每个对象
data.parts.forEach((part: any, index: number) => {
if (!part.id) errors.push(`parts[${index}]缺少id`);
if (!part.type) errors.push(`parts[${index}]缺少type`);
if (typeof part.x !== 'number') errors.push(`parts[${index}]缺少有效的x坐标`);
if (typeof part.y !== 'number') errors.push(`parts[${index}]缺少有效的y坐标`);
});
}
// 检查connections数组
if (!Array.isArray(data.connections)) {
errors.push('connections字段不是数组');
} else {
// 验证connections中的每个数组
data.connections.forEach((conn: any, index: number) => {
if (!Array.isArray(conn) || conn.length < 3) {
errors.push(`connections[${index}]不是有效的连接数组`);
return;
}
const [startPin, endPin, width] = conn;
if (typeof startPin !== 'string' || !startPin.includes(':')) {
errors.push(`connections[${index}]的起始针脚格式无效`);
}
if (typeof endPin !== 'string' || !endPin.includes(':')) {
errors.push(`connections[${index}]的结束针脚格式无效`);
}
if (typeof width !== 'number') {
errors.push(`connections[${index}]的宽度不是有效的数字`);
}
});
}
return {
isValid: errors.length === 0,
errors
};
}