// 定义 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; 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 { 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 }; }