Merge branch 'main' of http://113.45.157.195:3003/insiinc/re-mooc
This commit is contained in:
commit
4124dcb02c
|
@ -7,13 +7,118 @@ interface LayoutOptions {
|
||||||
nodeSeparation?: number;
|
nodeSeparation?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NodeWithLayout extends Node {
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
children?: NodeWithLayout[];
|
||||||
|
parent?: NodeWithLayout;
|
||||||
|
subtreeHeight?: number;
|
||||||
|
isRight?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function getMindMapLayout(options: LayoutOptions) {
|
export function getMindMapLayout(options: LayoutOptions) {
|
||||||
return {
|
const {
|
||||||
nodes: options.nodes,
|
nodes,
|
||||||
edges: options.edges,
|
edges,
|
||||||
|
levelSeparation = 200,
|
||||||
|
nodeSeparation = 60
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// 构建树形结构
|
||||||
|
const nodeMap = new Map<string, NodeWithLayout>();
|
||||||
|
nodes.forEach(node => {
|
||||||
|
nodeMap.set(node.id, { ...node, children: [], width: 150, height: 40 });
|
||||||
|
});
|
||||||
|
|
||||||
|
let rootNode: NodeWithLayout | undefined;
|
||||||
|
edges.forEach(edge => {
|
||||||
|
const source = nodeMap.get(edge.source);
|
||||||
|
const target = nodeMap.get(edge.target);
|
||||||
|
if (source && target) {
|
||||||
|
source.children?.push(target);
|
||||||
|
target.parent = source;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 找到根节点
|
||||||
|
rootNode = Array.from(nodeMap.values()).find(node => !node.parent);
|
||||||
|
if (!rootNode) return { nodes, edges };
|
||||||
|
|
||||||
|
// 分配节点到左右两侧
|
||||||
|
function assignSides(node: NodeWithLayout, isRight: boolean = true) {
|
||||||
|
if (!node.children?.length) return;
|
||||||
|
|
||||||
|
const len = node.children.length;
|
||||||
|
const midIndex = Math.floor(len / 2);
|
||||||
|
|
||||||
|
// 如果是根节点,将子节点分为左右两部分
|
||||||
|
if (!node.parent) {
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const child = node.children[i];
|
||||||
|
assignSides(child, i < midIndex);
|
||||||
|
child.isRight = i < midIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果不是根节点,所有子节点继承父节点的方向
|
||||||
|
else {
|
||||||
|
node.children.forEach(child => {
|
||||||
|
assignSides(child, isRight);
|
||||||
|
child.isRight = isRight;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算子树高度
|
||||||
|
function calculateSubtreeHeight(node: NodeWithLayout): number {
|
||||||
|
if (!node.children?.length) {
|
||||||
|
node.subtreeHeight = node.height || 40;
|
||||||
|
return node.subtreeHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
const childrenHeight = node.children.reduce((sum, child) => {
|
||||||
|
return sum + calculateSubtreeHeight(child);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
const totalGaps = (node.children.length - 1) * nodeSeparation;
|
||||||
|
node.subtreeHeight = Math.max(node.height || 40, childrenHeight + totalGaps);
|
||||||
|
return node.subtreeHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 布局计算
|
||||||
|
function calculateLayout(node: NodeWithLayout, x: number, y: number) {
|
||||||
|
node.position = { x, y };
|
||||||
|
if (!node.children?.length) return;
|
||||||
|
|
||||||
|
let currentY = y - (node.subtreeHeight || 0) / 2;
|
||||||
|
|
||||||
|
node.children.forEach(child => {
|
||||||
|
const direction = child.isRight ? 1 : -1;
|
||||||
|
const childX = x + (levelSeparation * direction);
|
||||||
|
const childY = currentY + (child.subtreeHeight || 0) / 2;
|
||||||
|
|
||||||
|
calculateLayout(child, childX, childY);
|
||||||
|
currentY += (child.subtreeHeight || 0) + nodeSeparation;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行布局流程
|
||||||
|
if (rootNode) {
|
||||||
|
// 1. 分配节点到左右两侧
|
||||||
|
assignSides(rootNode);
|
||||||
|
// 2. 计算子树高度
|
||||||
|
calculateSubtreeHeight(rootNode);
|
||||||
|
// 3. 执行布局计算
|
||||||
|
calculateLayout(rootNode, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换回原始格式
|
||||||
|
const layoutedNodes = Array.from(nodeMap.values()).map(node => ({
|
||||||
|
...node,
|
||||||
|
position: node.position,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodes: layoutedNodes,
|
||||||
|
edges,
|
||||||
|
};
|
||||||
}
|
}
|
|
@ -116,9 +116,7 @@ export const GraphNode = memo(({ id, selected, data, isConnectable }: NodeProps<
|
||||||
${isEditing ? 'ring-2 ring-white/50' : ''}
|
${isEditing ? 'ring-2 ring-white/50' : ''}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className={`p-2 ${baseTextStyles}`}>
|
|
||||||
{data.label}
|
|
||||||
</div>
|
|
||||||
<textarea
|
<textarea
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
defaultValue={data.label}
|
defaultValue={data.label}
|
||||||
|
|
|
@ -10,28 +10,28 @@ services:
|
||||||
- POSTGRES_PASSWORD=Letusdoit000
|
- POSTGRES_PASSWORD=Letusdoit000
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/postgres:/var/lib/postgresql/data
|
- ./volumes/postgres:/var/lib/postgresql/data
|
||||||
minio:
|
# minio:
|
||||||
image: minio/minio
|
# image: minio/minio
|
||||||
ports:
|
# ports:
|
||||||
- "9000:9000"
|
# - "9000:9000"
|
||||||
- "9001:9001"
|
# - "9001:9001"
|
||||||
volumes:
|
# volumes:
|
||||||
- ./volumes/minio:/minio_data
|
# - ./volumes/minio:/minio_data
|
||||||
environment:
|
# environment:
|
||||||
- MINIO_ACCESS_KEY=minioadmin
|
# - MINIO_ACCESS_KEY=minioadmin
|
||||||
- MINIO_SECRET_KEY=minioadmin
|
# - MINIO_SECRET_KEY=minioadmin
|
||||||
command: minio server /minio_data --console-address ":9001" -address ":9000"
|
# command: minio server /minio_data --console-address ":9001" -address ":9000"
|
||||||
healthcheck:
|
# healthcheck:
|
||||||
test:
|
# test:
|
||||||
[
|
# [
|
||||||
"CMD",
|
# "CMD",
|
||||||
"curl",
|
# "curl",
|
||||||
"-f",
|
# "-f",
|
||||||
"http://192.168.2.1:9001/minio/health/live"
|
# "http://192.168.2.1:9001/minio/health/live"
|
||||||
]
|
# ]
|
||||||
interval: 30s
|
# interval: 30s
|
||||||
timeout: 20s
|
# timeout: 20s
|
||||||
retries: 3
|
# retries: 3
|
||||||
pgadmin:
|
pgadmin:
|
||||||
image: dpage/pgadmin4
|
image: dpage/pgadmin4
|
||||||
ports:
|
ports:
|
||||||
|
@ -54,15 +54,15 @@ services:
|
||||||
# - "host.docker.internal:host-gateway"
|
# - "host.docker.internal:host-gateway"
|
||||||
# depends_on:
|
# depends_on:
|
||||||
# - minio
|
# - minio
|
||||||
tusd:
|
# tusd:
|
||||||
image: tusproject/tusd
|
# image: tusproject/tusd
|
||||||
ports:
|
# ports:
|
||||||
- "8080:8080"
|
# - "8080:8080"
|
||||||
command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook
|
# command: -verbose -upload-dir /data -hooks-http http://host.docker.internal:3000/upload/hook
|
||||||
volumes:
|
# volumes:
|
||||||
- ./uploads:/data
|
# - ./uploads:/data
|
||||||
extra_hosts:
|
# extra_hosts:
|
||||||
- "host.docker.internal:host-gateway"
|
# - "host.docker.internal:host-gateway"
|
||||||
nginx:
|
nginx:
|
||||||
image: nice-nginx:latest
|
image: nice-nginx:latest
|
||||||
ports:
|
ports:
|
||||||
|
|
Loading…
Reference in New Issue