335 lines
8.6 KiB
TypeScript
335 lines
8.6 KiB
TypeScript
import type { JSX } from "vue/jsx-runtime";
|
|
|
|
// 定义 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>;
|
|
capsPage?: JSX.Element;
|
|
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;
|
|
}
|
|
|
|
// 从本地存储加载图表数据
|
|
export async function loadDiagramData(): Promise<DiagramData> {
|
|
try {
|
|
// 先尝试从本地存储加载
|
|
const savedData = localStorage.getItem('diagramData');
|
|
if (savedData) {
|
|
return JSON.parse(savedData);
|
|
}
|
|
|
|
// 如果本地存储没有,从文件加载
|
|
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();
|
|
return data;
|
|
} 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 {
|
|
try {
|
|
localStorage.setItem('diagramData', JSON.stringify(data));
|
|
} catch (error) {
|
|
console.error('Error saving diagram data:', error);
|
|
}
|
|
}
|
|
|
|
// 添加新组件到图表数据
|
|
export function addPart(data: DiagramData, part: DiagramPart): DiagramData {
|
|
return {
|
|
...data,
|
|
parts: [...data.parts, part]
|
|
};
|
|
}
|
|
|
|
// 更新组件位置
|
|
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 deletePart(data: DiagramData, partId: string): DiagramData {
|
|
// 首先找到要删除的组件
|
|
const component = data.parts.find(part => part.id === partId);
|
|
if (!component) return data;
|
|
|
|
// 收集需要删除的组件ID列表
|
|
const componentsToDelete: string[] = [partId];
|
|
|
|
// 如果组件属于一个组,则找出所有同组的组件
|
|
if (component.group && component.group !== '') {
|
|
const groupMembers = data.parts.filter(
|
|
p => p.group === component.group && p.id !== partId
|
|
);
|
|
|
|
// 将同组组件ID添加到删除列表
|
|
componentsToDelete.push(...groupMembers.map(p => p.id));
|
|
console.log(`删除组件 ${partId} 及其组 ${component.group} 中的 ${groupMembers.length} 个组件`);
|
|
}
|
|
|
|
return {
|
|
...data,
|
|
// 删除所有标记的组件
|
|
parts: data.parts.filter(part => !componentsToDelete.includes(part.id)),
|
|
// 删除与这些组件相关的所有连接
|
|
connections: data.connections.filter(conn => {
|
|
const [startPin, endPin] = conn;
|
|
const startCompId = startPin.split(':')[0];
|
|
const endCompId = endPin.split(':')[0];
|
|
|
|
// 检查连接两端的组件是否在删除列表中
|
|
return !componentsToDelete.includes(startCompId) && !componentsToDelete.includes(endCompId);
|
|
})
|
|
};
|
|
}
|
|
|
|
// 添加连接
|
|
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;
|
|
});
|
|
}
|
|
|
|
// 基于组的移动相关组件
|
|
export function moveGroupComponents(
|
|
data: DiagramData,
|
|
groupId: string,
|
|
deltaX: number,
|
|
deltaY: number
|
|
): DiagramData {
|
|
if (!groupId) return data;
|
|
|
|
return {
|
|
...data,
|
|
parts: data.parts.map(part =>
|
|
part.group === groupId
|
|
? { ...part, x: part.x + deltaX, y: part.y + deltaY }
|
|
: part
|
|
)
|
|
};
|
|
}
|
|
|
|
// 添加验证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
|
|
};
|
|
}
|