思维导图
本页介绍思维导图模块的设计和实现。
数据结构
typescript
interface MindMapNode {
id: string
text: string
x: number
y: number
color?: string
parentId?: string
relatedSentenceIds?: string[]
}
interface MindMapData {
nodes: MindMapNode[]
articleId: string
createdAt: number
updatedAt: number
}Canvas 渲染
绘制节点
typescript
function drawNode(ctx: CanvasRenderingContext2D, node: MindMapNode) {
// 绘制圆形背景
ctx.beginPath()
ctx.arc(node.x, node.y, 30, 0, Math.PI * 2)
ctx.fillStyle = node.color || '#4ECDC4'
ctx.fill()
// 绘制文字
ctx.fillStyle = 'white'
ctx.font = '14px sans-serif'
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillText(node.text, node.x, node.y)
}绘制连线
typescript
function drawLine(
ctx: CanvasRenderingContext2D,
parent: MindMapNode,
child: MindMapNode
) {
ctx.beginPath()
ctx.moveTo(parent.x, parent.y)
ctx.lineTo(child.x, child.y)
ctx.strokeStyle = '#ccc'
ctx.lineWidth = 2
ctx.stroke()
}交互处理
拖拽
typescript
function handleDrag(canvas: HTMLCanvasElement, nodes: MindMapNode[]) {
let draggedNode: MindMapNode | null = null
let offsetX = 0, offsetY = 0
canvas.addEventListener('mousedown', (e) => {
const { x, y } = getMousePos(canvas, e)
draggedNode = findNodeAt(nodes, x, y)
if (draggedNode) {
offsetX = x - draggedNode.x
offsetY = y - draggedNode.y
}
})
canvas.addEventListener('mousemove', (e) => {
if (draggedNode) {
const { x, y } = getMousePos(canvas, e)
draggedNode.x = x - offsetX
draggedNode.y = y - offsetY
render()
}
})
canvas.addEventListener('mouseup', () => {
draggedNode = null
})
}缩放和平移
typescript
let scale = 1
let offsetX = 0, offsetY = 0
canvas.addEventListener('wheel', (e) => {
e.preventDefault()
const delta = e.deltaY > 0 ? 0.9 : 1.1
scale *= delta
render()
})导出功能
导出为图片
typescript
async function exportAsImage(): Promise<Blob> {
const canvas = document.getElementById('mindmap-canvas')
return new Promise((resolve) => {
canvas.toBlob(resolve, 'image/png')
})
}导出为 JSON
typescript
function exportAsJSON(): string {
return JSON.stringify(mindMapData, null, 2)
}核心 API
typescript
class MindMapService {
// 节点操作
createNode(text: string, x: number, y: number): MindMapNode
updateNode(id: string, updates: Partial<MindMapNode>): void
deleteNode(id: string): void
// 关系操作
setParent(nodeId: string, parentId: string): void
removeParent(nodeId: string): void
// 句子关联
linkSentence(nodeId: string, sentenceId: string): void
unlinkSentence(nodeId: string, sentenceId: string): void
// 导出
exportAsImage(): Promise<Blob>
exportAsJSON(): string
// 保存加载
save(articleId: string): void
load(articleId: string): MindMapData | null
}