308 lines
8.4 KiB
TypeScript
308 lines
8.4 KiB
TypeScript
// 定义 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
|
||
};
|
||
}
|