FPGA_WebLab/src/components/LabCanvas/composable/diagramManager.ts

308 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 定义 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>;
rotate: number;
group: string;
positionlock: boolean;
hidepins: boolean;
isOn: boolean;
index?: number; // 显示层级,数值越大显示越靠前
}
// 连接类型定义 - 使用元组类型表示四元素数组
export type ConnectionArray = [string, string, number, string[]];
import { AuthManager } from '@/utils/AuthManager';
// 解析连接字符串为组件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;
}
// 从本地存储或动态API加载图表数据
export async function loadDiagramData(examId?: string): Promise<DiagramData> {
try {
// 如果提供了examId优先从API加载实验的diagram
if (examId) {
try {
const examClient = AuthManager.createAuthenticatedExamClient();
// 获取diagram类型的资源列表
const resources = await examClient.getExamResourceList(examId, 'canvas');
if (resources && resources.length > 0) {
// 获取第一个diagram资源
const diagramResource = resources[0];
// 使用动态API获取资源文件内容
const response = await examClient.getExamResourceById(diagramResource.id);
if (response && response.data) {
const text = await response.data.text();
const data = JSON.parse(text);
// 验证数据格式
const validation = validateDiagramData(data);
if (validation.isValid) {
console.log('成功从API加载实验diagram:', examId);
return data;
} else {
console.warn('API返回的diagram数据格式无效:', validation.errors);
}
}
} else {
console.log('未找到实验diagram资源使用默认加载方式');
}
} catch (error) {
console.warn('从API加载实验diagram失败使用默认加载方式:', error);
}
}
// 如果没有examId或API加载失败尝试从静态文件加载不再使用本地存储
// 从静态文件加载(作为备选方案)
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();
// 验证静态文件数据
const validation = validateDiagramData(data);
if (validation.isValid) {
return data;
} else {
console.warn('静态diagram文件数据格式无效:', validation.errors);
throw new Error('所有diagram数据源都无效');
}
} 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 {
// 本地存储功能已禁用 - 不再保存到localStorage
console.debug('saveDiagramData called but localStorage saving is disabled');
}
// 更新组件位置
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 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;
});
}
// 添加验证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
};
}