325 lines
8.5 KiB
TypeScript
325 lines
8.5 KiB
TypeScript
import { ResourceClient, ResourcePurpose } from "@/APIClient";
|
||
import { AuthManager } from "@/utils/AuthManager";
|
||
|
||
// 定义 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[]];
|
||
|
||
// 解析连接字符串为组件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 resourceClient = AuthManager.createClient(ResourceClient);
|
||
|
||
// 获取diagram类型的资源列表
|
||
const resources = await resourceClient.getResourceList(
|
||
examId,
|
||
"canvas",
|
||
ResourcePurpose.Template,
|
||
);
|
||
|
||
if (resources && resources.length > 0) {
|
||
// 获取第一个diagram资源
|
||
const diagramResource = resources[0];
|
||
|
||
// 使用动态API获取资源文件内容
|
||
const response = await resourceClient.getResourceById(
|
||
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,
|
||
};
|
||
}
|