feat: 前端七段数码管添加数字孪生功能

This commit is contained in:
2025-08-16 11:56:27 +08:00
parent 3644c75304
commit 0a1e0982c2
12 changed files with 775 additions and 199 deletions

View File

@@ -1,3 +1,6 @@
import { ResourcePurpose } from "@/APIClient";
import { AuthManager } from "@/utils/AuthManager";
// 定义 diagram.json 的类型结构
export interface DiagramData {
version: number;
@@ -26,40 +29,43 @@ export interface DiagramPart {
// 连接类型定义 - 使用元组类型表示四元素数组
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(':');
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 }
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);
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,
endX: endPos.x,
endY: endPos.y,
startComponentId,
startPinId,
endComponentId,
endPinId,
strokeWidth: width,
color: '#4a5568', // 默认颜色
routingMode: 'path',
color: "#4a5568", // 默认颜色
routingMode: "path",
pathCommands: path,
showLabel: false
showLabel: false,
};
}
@@ -76,7 +82,7 @@ export interface WireItem {
endPinId?: string;
strokeWidth: number;
color: string;
routingMode: 'orthogonal' | 'path';
routingMode: "orthogonal" | "path";
constraint?: string;
pathCommands?: string[];
showLabel: boolean;
@@ -89,57 +95,63 @@ export async function loadDiagramData(examId?: string): Promise<DiagramData> {
if (examId) {
try {
const resourceClient = AuthManager.createAuthenticatedResourceClient();
// 获取diagram类型的资源列表
const resources = await resourceClient.getResourceList(examId, 'canvas', 'template');
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);
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);
console.log("成功从API加载实验diagram:", examId);
return data;
} else {
console.warn('API返回的diagram数据格式无效:', validation.errors);
console.warn("API返回的diagram数据格式无效:", validation.errors);
}
}
} else {
console.log('未找到实验diagram资源使用默认加载方式');
console.log("未找到实验diagram资源使用默认加载方式");
}
} catch (error) {
console.warn('从API加载实验diagram失败使用默认加载方式:', error);
console.warn("从API加载实验diagram失败使用默认加载方式:", error);
}
}
// 如果没有examId或API加载失败尝试从静态文件加载不再使用本地存储
// 从静态文件加载(作为备选方案)
const response = await fetch('/src/components/diagram.json');
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数据源都无效');
console.warn("静态diagram文件数据格式无效:", validation.errors);
throw new Error("所有diagram数据源都无效");
}
} catch (error) {
console.error('Error loading diagram data:', error);
console.error("Error loading diagram data:", error);
// 返回空的默认数据结构
return createEmptyDiagram();
}
@@ -149,33 +161,31 @@ export async function loadDiagramData(examId?: string): Promise<DiagramData> {
export function createEmptyDiagram(): DiagramData {
return {
version: 1,
author: 'user',
editor: 'user',
author: "user",
editor: "user",
parts: [],
connections: []
connections: [],
};
}
// 保存图表数据(已禁用本地存储)
export function saveDiagramData(data: DiagramData): void {
// 本地存储功能已禁用 - 不再保存到localStorage
console.debug('saveDiagramData called but localStorage saving is disabled');
console.debug("saveDiagramData called but localStorage saving is disabled");
}
// 更新组件位置
export function updatePartPosition(
data: DiagramData,
partId: string,
x: number,
y: number
data: DiagramData,
partId: string,
x: number,
y: number,
): DiagramData {
return {
...data,
parts: data.parts.map(part =>
part.id === partId
? { ...part, x, y }
: part
)
parts: data.parts.map((part) =>
part.id === partId ? { ...part, x, y } : part,
),
};
}
@@ -184,21 +194,21 @@ export function updatePartAttribute(
data: DiagramData,
partId: string,
attrName: string,
value: any
value: any,
): DiagramData {
return {
...data,
parts: data.parts.map(part =>
part.id === partId
? {
...part,
attrs: {
...part.attrs,
[attrName]: value
}
}
: part
)
parts: data.parts.map((part) =>
part.id === partId
? {
...part,
attrs: {
...part.attrs,
[attrName]: value,
},
}
: part,
),
};
}
@@ -210,72 +220,79 @@ export function addConnection(
endComponentId: string,
endPinId: string,
width: number = 2,
path: string[] = []
path: string[] = [],
): DiagramData {
const newConnection: ConnectionArray = [
`${startComponentId}:${startPinId}`,
`${endComponentId}:${endPinId}`,
width,
path
path,
];
return {
...data,
connections: [...data.connections, newConnection]
connections: [...data.connections, newConnection],
};
}
// 删除连接
export function deleteConnection(
data: DiagramData,
connectionIndex: number
connectionIndex: number,
): DiagramData {
return {
...data,
connections: data.connections.filter((_, index) => index !== connectionIndex)
connections: data.connections.filter(
(_, index) => index !== connectionIndex,
),
};
}
// 查找与组件关联的所有连接
export function findConnectionsByPart(
data: DiagramData,
partId: string
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];
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[] } {
export function validateDiagramData(data: any): {
isValid: boolean;
errors: string[];
} {
const errors: string[] = [];
// 检查版本号
if (!data.version) {
errors.push('缺少version字段');
errors.push("缺少version字段");
}
// 检查parts数组
if (!Array.isArray(data.parts)) {
errors.push('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坐标`);
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字段不是数组');
errors.push("connections字段不是数组");
} else {
// 验证connections中的每个数组
data.connections.forEach((conn: any, index: number) => {
@@ -283,25 +300,25 @@ export function validateDiagramData(data: any): { isValid: boolean; errors: stri
errors.push(`connections[${index}]不是有效的连接数组`);
return;
}
const [startPin, endPin, width] = conn;
if (typeof startPin !== 'string' || !startPin.includes(':')) {
if (typeof startPin !== "string" || !startPin.includes(":")) {
errors.push(`connections[${index}]的起始针脚格式无效`);
}
if (typeof endPin !== 'string' || !endPin.includes(':')) {
if (typeof endPin !== "string" || !endPin.includes(":")) {
errors.push(`connections[${index}]的结束针脚格式无效`);
}
if (typeof width !== 'number') {
if (typeof width !== "number") {
errors.push(`connections[${index}]的宽度不是有效的数字`);
}
});
}
return {
isValid: errors.length === 0,
errors
errors,
};
}