基于 Electron、Vue3 和 TypeScript 的辅助创作工具全链路开发方案:涵盖画布系统到数据持久化的完整实现

基于 Electron、Vue3 和 TypeScript 的辅助创作工具全链路开发方案:涵盖画布系统到数据持久化的完整实现

在这里插入图片描述


引言

在数字内容创作领域,高效的辅助工具是连接创意与实现的关键桥梁。创作者需要一款集可视化画布、节点关系管理、数据持久化于一体的专业工具,以应对复杂场景下的逻辑梳理与流程管控。

为此,本文提出一套基于 Electron + Vue3 + TypeScript 的全链路开发方案,深度整合 Konva.js 图形渲染Pinia 状态管理Lowdb 轻量化数据存储,构建从画布交互系统到数据持久化的完整技术体系。方案聚焦 栅格化布局双模式切换约束性交互规则等核心功能,通过模块化架构设计与策略模式解耦,实现高可用、易扩展的辅助创作工具。

无论你是桌面应用开发者、可视化工具设计者,还是创意产业技术赋能者,本文将为你呈现:

  • 画布系统:如何通过 Konva.js 实现高效的节点渲染与连线交互,支持万级元素流畅操作;
  • 数据架构:双数据库结构(事件库与画布库)如何隔离业务逻辑与空间关系,保障数据一致性;
  • 最佳实践:Electron 主渲染进程协作、命令模式实现撤销/重做、策略模式扩展约束规则等工程化经验。

通过这套方案,你将掌握从需求分析到落地实现的全流程技术细节,为打造专业级创作工具奠定坚实基础。


第一部分:功能要求总结及初步方案

一、辅助创作工具核心功能

  1. 画布系统
  • 双模式:编辑模式(可操作元素)/浏览模式(仅查看)
  • 栅格化布局(固定间距a=100px)
  • 视图控制:缩放/全景移动
  1. 节点系统
  • 圆形基础节点(4向锚点)
  • 栅格对齐(坐标必须为a的整数倍)
  • 事件元数据存储(时间/地点/人物等)
  1. 连线系统
  • 直角折线连接
  • 防重叠规则
  • 碰撞检测(不可穿越节点)
  1. 交互规则
  • 复合选择逻辑(单选/多选/框选)
  • 形心锚点生成(框选移动中心点)
  • 约束性复制(禁止单独连线复制)
  • 节点插入机制(连带位移效应)
  1. 数据持久化
  • 双数据库结构:
    • 事件数据库(MD结构化存储)
    • 画布数据库(节点/连线空间关系)
  1. 输出系统
  • 可配置MD文件生成
  • 显示选项过滤(标题/时间等)

一、技术栈推荐

  1. 前端框架:Electron + Vue3 + TypeScript(桌面应用开发)
  2. 图形库:Konva.js(Canvas交互处理)
  3. 状态管理:Pinia
  4. 数据存储:Lowdb(基于JSON的本地数据库)
  5. UI组件库:Naive UI
  6. 打包工具:electron-builder

二、模块划分方案

  1. 核心模块:

    • CanvasManager.ts(画布管理)
    • NodeSystem/
      │─ NodeManager.ts(节点管理)
      │─ AnchorSystem.ts(锚点系统)
      │─ WireManager.ts(连线管理)
      │─ ConstraintRules.ts(约束规则验证)
    • EventSystem/
      │─ EventEditor.ts(事件编辑)
      │─ TemplateManager.ts(模板管理)
  2. 数据模块:

    • schemas/
      │─ EventSchema.ts(事件数据模型)
      │─ CanvasSchema.ts(画布元素模型)
    • db/
      │─ EventDB.ts(事件数据库)
      │─ CanvasDB.ts(画布数据库)
  3. UI模块:

    • Toolbars/
      │─ MainToolbar.vue
      │─ OperationToolbar.vue
      │─ EventList.vue
    • Canvas/
      │─ KonvaCanvas.vue
      │─ GridLayer.vue

三、核心数据结构设计(TypeScript接口)

// 节点接口
interface ICanvasNode {
  id: string;
  x: number;  // 格栅对齐坐标
  y: number;
  anchors: {
    top: boolean;
    bottom: boolean;
    left: boolean;
    right: boolean;
  };
  style: {
    radius: number;
    color: string;
  };
}

// 连线接口
interface IWire {
  id: string;
  from: { nodeId: string; anchor: AnchorType };
  to: { nodeId: string; anchor: AnchorType };
  path: Array<{x: number, y: number}>; // 折线路径点
  style: {
    stroke: string;
    dash: number[];
  };
}

// 事件数据接口
interface IEvent {
  id: string;
  title: string;
  time: string;
  location: string;
  characters: string[];
  foreshadowing: string;
  summary: string;
  details: string;
  displayFlags: {
    showTitle: boolean;
    showTime: boolean;
    showCharacters: boolean;
    showForeshadowing: boolean;
  };
}

四、开发步骤规划

  1. 阶段一:基础框架搭建(预计2周)

    • 安装Electron基础模板
    • 配置Vue3+TS开发环境
    • 实现画布网格渲染(GridLayer)
    • 建立基础节点渲染系统
    • 实现LowDB数据库连接
  2. 阶段二:核心交互开发(预计3周)

    • 节点锚点系统
    • 连线约束规则模块
    • 框选与形心锚点计算
    • 移动/复制约束逻辑
    • 节点事件编辑模态框
  3. 阶段三:工具栏与辅助功能(预计2周)

    • 实现可折叠工具栏组件
    • 主题切换功能
    • 导出MD文件功能
    • 撤销/重做堆栈实现
    • 列表排序功能
  4. 阶段四:优化与测试(预计1周)

    • 性能优化(虚拟滚动列表)
    • 碰撞检测算法优化
    • 单元测试(使用Vitest)
    • 打包成exe文件

五、关键依赖清单

# 核心依赖
npm install electron vue@next konva@8.4.3 pinia naive-ui
# 开发依赖
npm install -D electron-builder vitest lowdb sass

六、核心算法建议

  1. 格栅定位算法:
const GRID_SIZE = 50;

function snapToGrid(pos: number): number {
  return Math.round(pos / GRID_SIZE) * GRID_SIZE;
}
  1. 连线路径生成算法(直角折线):
function generateRightAnglePath(start: Point, end: Point): Point[] {
  const midX = (start.x + end.x) / 2;
  return [
    start,
    { x: midX, y: start.y },
    { x: midX, y: end.y },
    end
  ];
}

七、代码组织建议

/my-novel-helper
├─ electron/
│  ├─ main.js          # 主进程入口
│  └─ preload.js       # 进程通信脚本
├─ src/
│  ├─ renderer/        
│  │  ├─ core/         # 核心逻辑
│  │  ├─ components/   # Vue组件
│  │  ├─ stores/       # Pinia状态库
│  │  └─ assets/       # 静态资源
│  ├─ db/              # 数据库模块
│  └─ types/           # TS类型定义
├─ package.json
└─ tsconfig.json

八、开发建议:

  1. 使用单向数据流架构,避免直接操作DOM
  2. 对画布操作使用Command模式实现撤销/重做
  3. 采用事件总线处理跨模块通信
  4. 对约束规则实现策略模式,便于扩展

九、后续可逐步实现的功能扩展点:

  1. 导入JSON/MD文件功能
  2. 时间轴视图
  3. 关系图谱分析
  4. 自动布局算法
  5. 版本快照功能

这种架构设计能有效隔离画布操作、业务逻辑和数据持久化层,符合模块化开发需求。每个阶段完成后都可独立测试运行,降低后期维护复杂度。


第二部分:模块划分方案详解(含可视化说明)

一、模块架构全景图

主进程
渲染进程
核心模块
数据模块
UI模块
CanvasManager
NodeSystem
NodeManager
AnchorSystem
WireManager
ConstraintRules
Schemas
DB
工具栏组件
画布组件
事件面板

二、核心模块深度解析

1. CanvasManager.ts
职责类比:画布系统的"交通指挥中心"
关键功能流程图

用户操作 CanvasManager GridLayer NodeSystem WireManager 模式切换指令 更新网格显示状态 启用/禁用交互 切换连线策略 用户操作 CanvasManager GridLayer NodeSystem WireManager

▨ 代码实例

// 模式切换实现
class CanvasManager {
  private editMode = ref(true);
  
  switchMode(isEdit: boolean) {
    this.editMode.value = isEdit;
    GridLayer.setGridVisible(isEdit); // 格栅可见性
    NodeSystem.setDraggable(isEdit);  // 节点可拖动状态
    WireManager.setInteractive(isEdit); // 连线可编辑
  }
}

2. NodeSystem 子系统
▨ 模块协作关系

创建节点
生成锚点
请求验证
用户点击
NodeManager
AnchorSystem
WireManager
ConstraintRules

▨ 典型场景示例
当用户按住Shift键点击锚点新增节点时:

  1. NodeManager 接收点击事件
  2. 调用 ConstraintRules.checkInsertPosition() 验证位置合法性
  3. 通过后 AnchorSystem.generateNewAnchors() 创建新锚点
  4. WireManager.adjustExistingWires() 调整已有连线

▨ 数据结构示例

// 节点内存结构
{
  id: 'NODE_2023-08-20_08:45:00',
  x: 300, // 必为100的整数倍
  y: 200,
  anchors: {
    top: true,   // 存在上锚点
    right: false // 无右锚点
  },
  style: {
    radius: 12,
    color: '#4CAF50'
  }
}

3. ConstraintRules.ts
▨ 规则验证流程图

操作请求
是否复制操作?
检查被复制元素
包含单独连线?
阻止操作
执行复制
执行其他验证
碰撞检测
格栅对齐
连线重叠检查

▨ 典型约束实现

// 连线防重叠规则
function checkWireOverlap(newWire: IWire) {
  const allWires = WireManager.getAllWires();
  
  return allWires.some(existingWire => {
    // 使用矢量比对算法
    const path1 = simplifyPath(newWire.path);
    const path2 = simplifyPath(existingWire.path);
    return isPathOverlap(path1, path2);
  });
}

三、数据模块详解

1. 数据结构设计
▨ 事件数据模型

// EventSchema.ts
export interface IEvent {
  id: string;           // 唯一标识符
  nodeId: string;       // 关联的节点ID
  title: string;        // 事件标题
  time: string;         // ISO8601时间格式
  characters: string[]; // 涉及人物列表
  foreshadowing: string;// 伏笔标记
  displayOptions: {     // 显示配置
    showTime: boolean;
    showCharacters: boolean;
  };
}

▨ 数据库关系图

EVENT NODE WIRE ANCHOR 1:1关联 1:N连接 起点/终点

2. 数据流示意图

用户操作
用户操作
点击新增节点 --> NodeManager
点击新增节点 --> NodeManager
数据存储
数据存储
NodeManager --> CanvasDB
NodeManager --> CanvasDB
CanvasDB --> EventDB
CanvasDB --> EventDB
界面更新
界面更新
EventDB --> EventList
EventDB --> EventList
CanvasDB --> KonvaCanvas
CanvasDB --> KonvaCanvas
数据生命周期

第三部分:核心数据结构深度解析

一、节点系统数据结构(可视化说明)

ICanvasNode
+string id
+number x
+number y
+AnchorConfig anchors
+NodeStyle style
AnchorConfig
+boolean top
+boolean bottom
+boolean left
+boolean right
NodeStyle
+number radius
+string color
+number strokeWidth

▨ 字段解释表

字段示例值作用说明新手类比
x300横向坐标(像素)棋盘上的列编号
y200纵向坐标(像素)棋盘上的行编号
anchors.toptrue顶部是否有连接点机器人的顶部充电接口
style.radius12节点显示大小纽扣的直径尺寸

二、连线系统设计原理

1. 路径存储策略

起点锚点
是否同轴?
直线连接
直角折线
生成中间路径点

▨ 路径点数据结构示例

// 从(200,300)到(400,300)的连线路径
const wirePath = [
  { x: 200, y: 300 }, // 起点
  { x: 300, y: 300 }, // 中间转折点
  { x: 300, y: 400 }, // 第二个转折点
  { x: 400, y: 400 }  // 终点
]

2. 样式控制逻辑

// 连线样式管理器
class WireStyleManager {
  private static presetStyles = {
    default: { stroke: '#666', dash: [] },
    selected: { stroke: '#2196F3', dash: [5,5] },
    error: { stroke: '#FF5722', dash: [10,5] }
  };

  updateWireStyle(wire: IWire, status: 'default' | 'selected' | 'error') {
    Object.assign(wire.style, this.presetStyles[status]);
  }
}

第四部分:开发步骤拆解(含里程碑图示)

2023-08-06 2023-08-13 2023-08-20 2023-08-27 2023-09-03 2023-09-10 环境搭建 网格渲染 节点渲染 锚点系统 连线生成 约束规则 工具栏组件 导出功能 撤销重做 基础框架 核心交互 辅助功能 开发进度甘特图

分阶段开发重点说明

1. 阶段一:基础框架搭建
▨ 关键技术点

  • 使用Electron的BrowserWindow创建窗口

  • 实现画布网格的数学计算:

    // 网格绘制算法
    function drawGrid(ctx: CanvasRenderingContext2D) {
      const spacing = 100; // 格栅间距
      for(let x = 0; x < ctx.canvas.width; x += spacing){
        ctx.moveTo(x, 0);
        ctx.lineTo(x, ctx.canvas.height);
      }
      // 同理绘制纵向线条...
    }
    

▨ 新手常见问题

  • Q:为什么节点位置需要对齐格栅?
  • A:就像停车场车位需要标准间距,保证元素排列整齐和连线规范

2. 阶段二:核心交互开发
▨ 关键技术点

  • 锚点碰撞检测算法:

    function findNearestAnchor(pos: Point) {
      return nodes.reduce((nearest, node) => {
        const anchors = getAnchorPositions(node);
        const dist = calculateDistance(pos, anchors);
        return dist < nearest.dist ? { node, dist } : nearest;
      }, { dist: Infinity });
    }
    

▨ 可视化调试技巧

// 开发时开启调试模式显示锚点半径
const DEBUG_MODE = true;
function drawAnchors() {
  if(DEBUG_MODE) {
    ctx.fillStyle = 'rgba(255,0,0,0.3)';
    ctx.fillRect(anchor.x-5, anchor.y-5, 10, 10);
  }
}

第五部分:关键技术点详解(含实战示例)

一、Electron 主进程与渲染进程协作

主进程 渲染进程 文件系统 创建BrowserWindow IPC通信(文件操作请求) 读写数据库 返回操作结果 主进程 渲染进程 文件系统

▨ 典型代码结构

// 主进程 main.js
const { app, BrowserWindow, ipcMain } = require('electron')

ipcMain.handle('save-data', async (event, data) => {
  await fs.writeFile('data.json', JSON.stringify(data))
})

function createWindow() {
  const win = new BrowserWindow({
    webPreferences: {
      nodeIntegration: false,
      contextIsolation: true
    }
  })
  win.loadFile('index.html')
}

二、Konva.js 图形系统实践

1. 节点渲染实现

// 单个节点渲染组件
const NodeComponent = ({ node }) => {
  const [selected, setSelected] = useState(false);

  return (
    <Group x={node.x} y={node.y}>
      {/* 主体圆形 */}
      <Circle
        radius={node.style.radius}
        fill={selected ? '#FFEE58' : node.style.color}
        onClick={handleSelect}
      />
      
      {/* 锚点可视化 */}
      {Object.entries(node.anchors).map(([direction, visible]) => (
        visible && <AnchorPoint direction={direction} />
      ))}
    </Group>
  );
};

2. 连线交互示意图

Alt+点击起点
移动鼠标
点击终点
按ESC
Idle
Drawing
Preview
Committed
Canceled

三、Pinia 状态管理实战

1. 状态仓库设计

// stores/canvas.ts
export const useCanvasStore = defineStore('canvas', {
  state: () => ({
    nodes: [] as ICanvasNode[],
    wires: [] as IWire[],
    gridSize: 100
  }),
  actions: {
    addNode(newNode: ICanvasNode) {
      this.nodes.push(newNode);
    },
    // 其他操作方法...
  }
})

2. 组件中调用示例

<script setup>
import { useCanvasStore } from './stores/canvas'

const store = useCanvasStore()

// 添加新节点
const handleClick = () => {
  store.addNode({
    id: Date.now().toString(),
    x: 0,
    y: 0,
    anchors: { top: true, bottom: false, left: false, right: false },
    style: { radius: 12, color: '#4CAF50' }
  })
}
</script>

四、Lowdb 数据库操作

1. 数据库初始化

// db/EventDB.ts
import { Low } from 'lowdb'
import { JSONFile } from 'lowdb/node'

type EventData = {
  events: IEvent[]
}

const adapter = new JSONFile<EventData>('events.json')
const db = new Low(adapter, { events: [] })

export const initializeDB = async () => {
  await db.read()
  db.data ||= { events: [] }
  await db.write()
}

2. 增删改查示例

// 添加事件
export const addEvent = async (event: IEvent) => {
  db.data.events.push(event)
  await db.write()
}

// 查询未闭合伏笔
export const getUnresolvedForeshadowing = () => {
  return db.data.events.filter(e => 
    e.foreshadowing && !e.foreshadowing.resolved
  )
}

五、撤销/重做实现方案

1. 命令模式架构

«interface»
Command
+execute()
+undo()
AddNodeCommand
-node: ICanvasNode
+execute()
+undo()
CommandManager
-history: Command[]
-pointer: number
+executeCommand()
+undo()
+redo()

2. 具体实现代码

class AddNodeCommand implements Command {
  private node: ICanvasNode
  private store: ReturnType<typeof useCanvasStore>

  constructor(node: ICanvasNode) {
    this.node = node
    this.store = useCanvasStore()
  }

  execute() {
    this.store.addNode(this.node)
  }

  undo() {
    this.store.nodes = this.store.nodes.filter(n => n.id !== this.node.id)
  }
}

第六部分:复杂交互逻辑深度解析

一、形心锚点计算算法(带可视化推导)

1. 计算原理图示

框选范围
收集所有被选元素
是否包含连线?
计算所有节点几何中心
提取连线端点坐标
组合所有坐标点
计算加权平均值

2. 数学公式实现

function calculateCentroid(elements: (ICanvasNode | IWire)[]): Point {
  let totalX = 0, totalY = 0, count = 0;
  
  elements.forEach(element => {
    if ('x' in element) { // 节点类型
      totalX += element.x;
      totalY += element.y;
      count++;
    } else { // 连线类型
      element.path.forEach(point => {
        totalX += point.x;
        totalY += point.y;
        count++;
      });
    }
  });

  return {
    x: Math.round(totalX / count / GRID_SIZE) * GRID_SIZE,
    y: Math.round(totalY / count / GRID_SIZE) * GRID_SIZE
  };
}

3. 调试可视化技巧

// 开发时显示形心标记
function debugShowCentroid(ctx, centroid) {
  ctx.fillStyle = 'rgba(255,0,0,0.5)';
  ctx.beginPath();
  ctx.arc(centroid.x, centroid.y, 8, 0, Math.PI*2);
  ctx.fill();
  
  ctx.strokeStyle = '#FF0000';
  ctx.setLineDash([5, 3]);
  ctx.beginPath();
  ctx.moveTo(centroid.x-15, centroid.y);
  ctx.lineTo(centroid.x+15, centroid.y);
  ctx.moveTo(centroid.x, centroid.y-15);
  ctx.lineTo(centroid.x, centroid.y+15);
  ctx.stroke();
}

二、节点插入位移算法(带分步演示)

1. 操作流程图解

用户 系统 约束检查 位移引擎 节点系统 坐标计算 UI系统 画布 Shift+点击右侧锚点 验证目标位置合法性 返回可用空间信息 请求右侧节点位移 遍历右侧所有节点 返回受影响节点列表 批量增加X坐标(+100px) 更新节点位置 触发重绘 用户 系统 约束检查 位移引擎 节点系统 坐标计算 UI系统 画布

2. 核心代码实现

class DisplacementEngine {
  static shiftNodes(startX: number, direction: 'left'|'right') {
    const store = useCanvasStore();
    const offset = direction === 'right' ? GRID_SIZE : -GRID_SIZE;
    
    // 获取需要移动的节点
    const affectedNodes = store.nodes
      .filter(node => node.x >= startX)
      .sort((a, b) => a.x - b.x);

    // 执行位移(需从最右侧开始处理)
    const sortedNodes = direction === 'right' 
      ? affectedNodes.reverse() 
      : affectedNodes;

    sortedNodes.forEach(node => {
      node.x += offset;
      // 更新关联连线
      WireManager.updateWirePositions(node.id);
    });
  }
}

3. 位移效果可视化

初始状态:
[节点A]-(连线)-[节点B]-(连线)-[节点C]

插入新节点后:
[节点A]-(连线)-[新节点]-(连线)-[节点B(原x+100)]-(连线)-[节点C(原x+100)]

三、连线防重叠算法详解

1. 碰撞检测原理

新连线路径
分解线段集合
获取现有连线路径
线段交叉检测
存在交叉?
拒绝绘制
允许创建

2. 核心数学方法

// 线段交叉检测函数
function isLineSegmentsIntersect(a1: Point, a2: Point, b1: Point, b2: Point) {
  const denominator = (b2.y - b1.y)*(a2.x - a1.x) - (b2.x - b1.x)*(a2.y - a1.y);
  
  // 线段平行处理
  if (denominator === 0) return false;

  const ua = ((b2.x - b1.x)*(a1.y - b1.y) - (b2.y - b1.y)*(a1.x - b1.x)) / denominator;
  const ub = ((a2.x - a1.x)*(a1.y - b1.y) - (a2.y - a1.y)*(a1.x - b1.x)) / denominator;

  return ua >= 0 && ua <= 1 && ub >= 0 && ub <= 1;
}

3. 性能优化策略

// 空间分区加速检测
const SPACE_GRID = 200; // 分区尺寸

function checkCollisionFast(newWire: IWire) {
  // 建立空间索引
  const gridMap = new Map<string, IWire[]>();
  
  // 将现有连线分配到网格
  allWires.forEach(wire => {
    wire.path.forEach(point => {
      const gridKey = `${Math.floor(point.x/SPACE_GRID)}_${Math.floor(point.y/SPACE_GRID)}`;
      gridMap.set(gridKey, [...(gridMap.get(gridKey) || []), wire]);
    });
  });

  // 只检查新连线所在网格
  return newWire.path.some(point => {
    const gridKey = `${Math.floor(point.x/SPACE_GRID)}_${Math.floor(point.y/SPACE_GRID)}`;
    return gridMap.get(gridKey)?.some(wire => checkCollision(newWire, wire)) || false;
  });
}

第七部分:事件系统与导出功能实现

一、事件编辑系统实现

1. 模态框组件架构

保存
取消
双击节点
事件总线触发编辑事件
模态框组件监听事件
加载关联数据
渲染表单界面
用户操作
更新数据库
关闭模态框

2. 核心组件代码

<!-- EventModal.vue -->
<template>
  <div class="modal-mask">
    <div class="modal-container">
      <div class="header">
        <h3>事件编辑器 - {{ nodeId }}</h3>
      </div>
      
      <div class="form-section">
        <label>事件名称:
          <input v-model="formData.title" />
          <input type="checkbox" v-model="formData.displayFlags.showTitle" />
        </label>
        
        <!-- 其他字段类似 -->
      </div>

      <button @click="saveChanges">保存</button>
      <button @click="close">取消</button>
    </div>
  </div>
</template>

<script setup lang="ts">
const props = defineProps<{ nodeId: string }>()

// 从数据库加载初始数据
const initialData = await EventDB.getEventByNode(props.nodeId)
const formData = reactive({ ...initialData })

const saveChanges = async () => {
  await EventDB.updateEvent(props.nodeId, formData)
  emit('update:node')
}
</script>

3. 显示控制逻辑

// 节点渲染时检查显示配置
function renderNodeInfo(node: ICanvasNode) {
  const eventData = EventDB.getEventByNode(node.id)
  let infoText = ''
  
  if(eventData.displayFlags.showTitle) infoText += eventData.title + '\n'
  if(eventData.displayFlags.showTime) infoText += eventData.time + '\n'
  
  context.fillText(infoText, node.x, node.y - 20)
}

二、Markdown导出系统实现

1. 转换流程图

获取所有事件
按时间排序
转换为Markdown段落
添加关系图代码块
生成完整文档

2. 核心转换代码

class MDExporter {
  static async generate() {
    const events = await EventDB.getAllEvents()
    const canvasData = CanvasDB.getSnapshot()
    
    let mdContent = `# 故事大纲\n\n`
    
    // 时间线部分
    mdContent += '## 事件时间线\n'
    events.sort(byTime).forEach(event => {
      mdContent += `### ${event.title}\n`
      mdContent += `**时间:** ${event.time}\n\n`
      mdContent += `${event.summary}\n\n`
    })
    
    // 关系图谱
    mdContent += '## 关系图谱\n```mermaid\ngraph TD\n'
    canvasData.wires.forEach(wire => {
      const fromNode = canvasData.nodes.find(n => n.id === wire.from.nodeId)!
      const toNode = canvasData.nodes.find(n => n.id === wire.to.nodeId)!
      mdContent += `${fromNode.id}["${fromNode.title}"] --> ${toNode.id}["${toNode.title}"]\n`
    })
    mdContent += '```\n'
    
    return mdContent
  }
}

3. 文件保存实现

// 主进程处理文件保存
ipcMain.handle('export-md', async (event) => {
  const content = await MDExporter.generate()
  
  const { filePath } = await dialog.showSaveDialog({
    title: '导出Markdown',
    filters: [{ name: 'Markdown', extensions: ['md'] }]
  })
  
  if(filePath) {
    await fs.promises.writeFile(filePath, content)
    return { success: true, path: filePath }
  }
  return { success: false }
})

三、主题切换系统实现

1. CSS变量控制方案

/* 全局样式表 */
:root {
  --bg-color: #ffffff;
  --text-color: #333333;
  --grid-color: #eeeeee;
}

[data-theme="dark"] {
  --bg-color: #1a1a1a;
  --text-color: #e0e0e0;
  --grid-color: #404040;
}

.canvas-container {
  background-color: var(--bg-color);
}

2. 主题切换控制器

// themeManager.ts
class ThemeManager {
  private currentTheme = ref<'light' | 'dark'>('light')
  
  toggleTheme() {
    this.currentTheme.value = this.currentTheme.value === 'light' ? 'dark' : 'light'
    document.documentElement.setAttribute('data-theme', this.currentTheme.value)
  }
  
  watchTheme(callback: (theme: string) => void) {
    watch(this.currentTheme, callback)
  }
}

第八部分:调试与优化策略

1. 性能监控面板

// 开发时显示调试信息
function renderDebugOverlay() {
  if(!DEBUG_MODE) return
  
  ctx.fillStyle = 'rgba(0,0,0,0.7)'
  ctx.fillRect(10, 10, 200, 120)
  ctx.fillStyle = '#00FF00'
  ctx.textAlign = 'left'
  ctx.fillText(`节点数量: ${nodes.length}`, 15, 30)
  ctx.fillText(`连线数量: ${wires.length}`, 15, 50)
  ctx.fillText(`内存占用: ${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)}MB`, 15, 70)
}

2. 自动化测试示例

// 约束规则测试用例
describe('连线约束规则', () => {
  test('禁止重复连线', () => {
    const wire1 = createWire('A', 'B')
    const wire2 = createWire('A', 'B')
    
    expect(ConstraintRules.checkWireExists(wire2)).toBe(true)
  })

  test('允许不同锚点的连线', () => {
    const wire1 = createWire('A.top', 'B.bottom')
    const wire2 = createWire('A.right', 'B.left')
    
    expect(ConstraintRules.checkWireExists(wire2)).toBe(false)
  })
})

第九部分:开发建议深度解析

一、单向数据流架构实践

1. 数据流向示意图

禁止反向
用户操作
Action
State更新
DOM渲染

2. 具体实现示例

// 正确做法:通过Store更新状态
function handleMoveNode(nodeId: string, newPos: Point) {
  const store = useCanvasStore();
  store.updateNodePosition(nodeId, snapToGrid(newPos));
}

// 错误做法:直接修改DOM
function badPractice(nodeElement: HTMLElement) {
  // 直接操作DOM元素(禁止!)
  nodeElement.style.left = '300px'; 
}

3. 优势对比表

方式调试难度可维护性状态追溯组件复用性
单向数据流完整
直接DOM操作困难

二、Command模式实现撤销/重做

1. 类结构设计

«interface»
Command
+execute()
+undo()
AddNodeCommand
-node: ICanvasNode
+execute()
+undo()
MoveNodeCommand
-nodeId: string
-oldPos: Point
-newPos: Point
+execute()
+undo()

2. 核心实现代码

// 命令管理器
class CommandManager {
  private stack: Command[] = [];
  private pointer = -1;

  execute(command: Command) {
    this.stack.splice(this.pointer + 1);
    this.stack.push(command);
    command.execute();
    this.pointer++;
  }

  undo() {
    if (this.pointer >= 0) {
      this.stack[this.pointer--].undo();
    }
  }

  redo() {
    if (this.pointer < this.stack.length - 1) {
      this.stack[++this.pointer].execute();
    }
  }
}

// 使用示例
const cmd = new AddNodeCommand(newNode);
commandManager.execute(cmd);

三、事件总线实现跨模块通信

1. 通信架构图

组件A 事件总线 组件B 组件C 发射nodeSelected事件 监听nodeSelected 监听nodeSelected 更新节点详情 高亮相关连线 组件A 事件总线 组件B 组件C

2. 基于Vue的实现

// eventBus.ts
import mitt from 'mitt';

type Events = {
  nodeSelected: ICanvasNode;
  wireCreated: IWire;
  modeChanged: 'edit' | 'view';
};

export const eventBus = mitt<Events>();

// 组件A发送事件
eventBus.emit('nodeSelected', currentNode);

// 组件B监听事件
eventBus.on('nodeSelected', (node) => {
  showNodeDetail(node);
});

四、策略模式实现约束规则

1. 策略模式结构

约束上下文
移动策略
复制策略
连线策略
基础移动规则
位移传播规则

2. 可扩展规则实现

// 策略接口
interface IConstraintStrategy {
  check(context: OperationContext): boolean;
}

// 具体策略
class MoveStrategy implements IConstraintStrategy {
  check(context: MoveContext) {
    // 实现移动约束逻辑
  }
}

// 策略管理器
class ConstraintManager {
  private strategies: Map<OperationType, IConstraintStrategy> = new Map();

  register(type: OperationType, strategy: IConstraintStrategy) {
    this.strategies.set(type, strategy);
  }

  validate(type: OperationType, context: OperationContext) {
    return this.strategies.get(type)?.check(context) ?? true;
  }
}

// 注册新策略示例
const manager = new ConstraintManager();
manager.register('COPY', new CopyConstraintStrategy());

第十部分:扩展功能实现指南

一、导入功能实现方案

1. MD文件解析流程

选择文件
解析MD结构
是否包含Mermaid?
提取节点关系
创建基础节点
生成画布结构
创建时间线节点

2. 关键解析代码

class MDImporter {
  static parse(content: string) {
    const events = this.extractEvents(content);
    const relations = this.extractMermaidRelations(content);
    
    return {
      events,
      canvasData: this.buildCanvasData(relations)
    };
  }

  private static extractMermaidRelations(content: string) {
    const mermaidBlocks = content.match(/```mermaid([\s\S]*?)```/g);
    // 解析Mermaid语法中的节点关系...
  }
}

二、时间轴视图实现

1. 双视图联动设计

点击节点
拖动事件
画布视图
时间轴定位
时间轴
更新节点时间
重排画布布局

2. 时间轴组件示例

<template>
  <div class="timeline">
    <div v-for="event in sortedEvents" 
         :key="event.id"
         class="timeline-item"
         :style="{ left: calcPosition(event.time) }"
         @click="selectNode(event.nodeId)">
      {{ event.title }}
    </div>
  </div>
</template>

<script setup>
const calcPosition = (time) => {
  const start = new Date('2023-01-01').getTime();
  const totalDays = 365;
  const day = (new Date(time) - start) / (1000*3600*24);
  return `${(day / totalDays * 100)}%`;
}
</script>

三、自动布局算法实现

1. 力导向布局伪代码

# 简化的布局算法
def force_directed_layout(nodes, wires):
    for _ in range(iterations):
        # 节点间斥力
        for node1 in nodes:
            for node2 in nodes:
                if node1 != node2:
                    repel(node1, node2)
        
        # 连线拉力
        for wire in wires:
            attract(wire.fromNode, wire.toNode)
        
        # 更新位置
        update_positions()

2. 实现建议

  • 使用现有库:d3-force(推荐)

  • 自定义参数:

    const simulation = d3.forceSimulation(nodes)
      .force("charge", d3.forceManyBody().strength(-50))
      .force("link", d3.forceLink(wires).distance(100))
      .force("grid", gridForce(100)); // 自定义格栅对齐力
    

四、版本快照实现方案

1. 快照管理设计

2023-10-01 2024-01-01 2024-04-01 2024-07-01 2024-10-01 2025-01-01 2025-04-01 创建节点A 移动节点B 修改连线 添加事件描述 版本1 版本2 版本快照示例

2. 核心实现代码

class SnapshotManager {
  private snapshots: ProjectSnapshot[] = [];
  private currentVersion = 0;

  createSnapshot() {
    const snapshot = {
      canvas: CanvasDB.export(),
      events: EventDB.export(),
      timestamp: new Date()
    };
    this.snapshots = this.snapshots.slice(0, this.currentVersion + 1);
    this.snapshots.push(snapshot);
    this.currentVersion++;
  }

  restore(version: number) {
    const snapshot = this.snapshots[version];
    CanvasDB.import(snapshot.canvas);
    EventDB.import(snapshot.events);
  }
}

五、最终建议实施路线图

下一步 已完成 进行中
核心功能
核心功能
已完成
基础框架
基础框架
已完成
节点系统
节点系统
进行中
连线系统
连线系统
扩展功能
扩展功能
下一步
撤销重做
撤销重做
导入导出
导入导出
自动布局
自动布局
时间轴
时间轴
快照管理
快照管理
功能开发优先级
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灏瀚星空

你的鼓励是我前进和创作的源泉!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值