LiteGraph.js与Vuex持久化:localStorage集成终极指南

LiteGraph.js与Vuex持久化:localStorage集成终极指南

【免费下载链接】litegraph.js A graph node engine and editor written in Javascript similar to PD or UDK Blueprints, comes with its own editor in HTML5 Canvas2D. The engine can run client side or server side using Node. It allows to export graphs as JSONs to be included in applications independently. 【免费下载链接】litegraph.js 项目地址: https://gitcode.com/gh_mirrors/li/litegraph.js

LiteGraph.js是一个强大的JavaScript图形节点引擎和编辑器,类似于Unreal Blueprints或UDK蓝图系统。它允许开发者在HTML5 Canvas2D中创建可视化节点图,并支持将图形导出为JSON格式,可以独立集成到应用程序中。本文将为您提供完整的LiteGraph.js与Vuex状态管理集成的解决方案,重点介绍如何通过localStorage实现持久化存储,确保您的可视化节点数据在页面刷新后不会丢失。

LiteGraph.js核心功能概述

LiteGraph.js作为一个可视化节点编辑器,提供了丰富的功能集,包括:

  • Canvas2D渲染:支持缩放、平移和复杂界面渲染
  • 节点编程:轻松创建自定义节点类型
  • 编辑器功能:搜索框、键盘快捷键、多选、上下文菜单
  • 高性能优化:支持数百个节点同时运行
  • 主题定制:可自定义颜色、形状和背景
  • 子图支持:节点可以包含嵌套的子图
  • 实时模式系统:隐藏图形但调用节点渲染任意内容

LiteGraph.js节点图示例

为什么需要Vuex与localStorage集成?

当您使用LiteGraph.js构建复杂的可视化编辑器时,用户创建的节点图、连接关系和配置信息构成了应用程序的核心状态。Vuex作为Vue.js的官方状态管理库,非常适合管理这种复杂的状态数据。然而,Vuex状态在页面刷新时会重置,这就需要与localStorage集成来实现持久化存储。

基础集成方案

1. 安装与配置LiteGraph.js

首先,在Vue项目中安装LiteGraph.js:

npm install litegraph.js

然后在Vue组件中初始化LiteGraph.js:

import LiteGraph from 'litegraph.js';
import 'litegraph.js/css/litegraph.css';

export default {
  mounted() {
    this.graph = new LiteGraph.LGraph();
    this.canvas = new LiteGraph.LGraphCanvas("#mycanvas", this.graph);
    
    // 初始化示例节点
    const nodeConst = LiteGraph.createNode("basic/const");
    nodeConst.pos = [200, 200];
    this.graph.add(nodeConst);
    nodeConst.setValue(4.5);
    
    this.graph.start();
  }
}

2. 创建Vuex存储模块

创建一个专门的Vuex模块来管理LiteGraph.js的状态:

// store/modules/litegraph.js
const state = {
  graphData: null,
  selectedNodes: [],
  canvasState: {
    zoom: 1,
    offset: [0, 0]
  }
};

const mutations = {
  SET_GRAPH_DATA(state, data) {
    state.graphData = data;
  },
  SET_SELECTED_NODES(state, nodes) {
    state.selectedNodes = nodes;
  },
  SET_CANVAS_STATE(state, canvasState) {
    state.canvasState = canvasState;
  }
};

const actions = {
  saveGraph({ commit, state }) {
    if (state.graph) {
      const graphData = state.graph.serialize();
      commit('SET_GRAPH_DATA', graphData);
      return graphData;
    }
    return null;
  },
  
  loadGraph({ commit, state }, graphData) {
    if (state.graph && graphData) {
      state.graph.configure(graphData);
      commit('SET_GRAPH_DATA', graphData);
    }
  }
};

export default {
  namespaced: true,
  state,
  mutations,
  actions
};

localStorage持久化实现

1. Vuex插件方案

创建一个Vuex插件来自动处理localStorage持久化:

// store/plugins/localStorage.js
const localStoragePlugin = store => {
  // 从localStorage加载初始状态
  const savedState = localStorage.getItem('litegraph-app-state');
  if (savedState) {
    try {
      store.replaceState(JSON.parse(savedState));
    } catch (e) {
      console.error('Failed to load state from localStorage:', e);
    }
  }
  
  // 监听mutation,保存到localStorage
  store.subscribe((mutation, state) => {
    // 只保存litegraph相关的状态变化
    if (mutation.type.includes('litegraph/')) {
      localStorage.setItem('litegraph-app-state', JSON.stringify(state));
    }
  });
};

export default localStoragePlugin;

2. 选择性持久化策略

为了优化性能,我们可以实现选择性持久化:

// utils/persistence.js
export const PERSISTENCE_STRATEGIES = {
  FULL: 'full',           // 保存所有状态
  GRAPH_ONLY: 'graph',    // 只保存图形数据
  SELECTIVE: 'selective'  // 选择性保存
};

export class LiteGraphPersistence {
  constructor(strategy = PERSISTENCE_STRATEGIES.SELECTIVE) {
    this.strategy = strategy;
    this.STORAGE_KEY = 'litegraph_vuex_state';
  }
  
  saveState(state) {
    let dataToSave;
    
    switch(this.strategy) {
      case PERSISTENCE_STRATEGIES.FULL:
        dataToSave = state;
        break;
        
      case PERSISTENCE_STRATEGIES.GRAPH_ONLY:
        dataToSave = {
          litegraph: state.litegraph
        };
        break;
        
      case PERSISTENCE_STRATEGIES.SELECTIVE:
      default:
        dataToSave = {
          litegraph: {
            graphData: state.litegraph?.graphData,
            canvasState: state.litegraph?.canvasState
          }
        };
        break;
    }
    
    localStorage.setItem(this.STORAGE_KEY, JSON.stringify(dataToSave));
  }
  
  loadState() {
    const saved = localStorage.getItem(this.STORAGE_KEY);
    if (!saved) return null;
    
    try {
      return JSON.parse(saved);
    } catch (e) {
      console.error('Failed to parse saved state:', e);
      return null;
    }
  }
  
  clearState() {
    localStorage.removeItem(this.STORAGE_KEY);
  }
}

LiteGraph.js自定义节点示例

高级集成技巧

1. 自动保存与防抖处理

为了避免频繁写入localStorage影响性能,实现自动保存功能:

// utils/autoSave.js
import debounce from 'lodash/debounce';

export class AutoSaveManager {
  constructor(vuexStore, persistence, options = {}) {
    this.store = vuexStore;
    this.persistence = persistence;
    this.options = {
      debounceDelay: 2000, // 2秒防抖
      maxSaveFrequency: 10000, // 最多每10秒保存一次
      ...options
    };
    
    this.lastSaveTime = 0;
    this.isSaving = false;
    
    this.debouncedSave = debounce(this.save.bind(this), this.options.debounceDelay);
  }
  
  start() {
    // 监听Vuex mutations
    this.store.subscribe((mutation) => {
      if (mutation.type.startsWith('litegraph/')) {
        this.debouncedSave();
      }
    });
    
    // 监听窗口关闭事件
    window.addEventListener('beforeunload', () => {
      this.saveImmediate();
    });
  }
  
  save() {
    const now = Date.now();
    if (now - this.lastSaveTime < this.options.maxSaveFrequency && !this.isSaving) {
      return;
    }
    
    this.isSaving = true;
    this.persistence.saveState(this.store.state);
    this.lastSaveTime = now;
    this.isSaving = false;
  }
  
  saveImmediate() {
    this.persistence.saveState(this.store.state);
  }
}

2. 版本控制与迁移

处理数据结构变更的版本控制:

// utils/versionMigration.js
export class StateMigration {
  constructor() {
    this.VERSION_KEY = 'litegraph_state_version';
    this.CURRENT_VERSION = 2;
  }
  
  migrate(state) {
    const savedVersion = localStorage.getItem(this.VERSION_KEY) || 1;
    const version = parseInt(savedVersion);
    
    if (version < this.CURRENT_VERSION) {
      state = this.applyMigrations(state, version);
      localStorage.setItem(this.VERSION_KEY, this.CURRENT_VERSION.toString());
    }
    
    return state;
  }
  
  applyMigrations(state, fromVersion) {
    let migratedState = { ...state };
    
    // 版本1到版本2的迁移
    if (fromVersion < 2) {
      if (migratedState.litegraph && !migratedState.litegraph.version) {
        migratedState.litegraph.version = 2;
        // 添加新的字段或修改结构
        if (migratedState.litegraph.graphData) {
          migratedState.litegraph.graphData.metadata = {
            migratedAt: new Date().toISOString(),
            fromVersion: 1
          };
        }
      }
    }
    
    return migratedState;
  }
}

实战示例:完整的Vue组件

<template>
  <div class="litegraph-editor">
    <div class="toolbar">
      <button @click="saveGraph">💾 保存</button>
      <button @click="loadGraph">📂 加载</button>
      <button @click="clearGraph">🗑️ 清空</button>
      <span class="auto-save-status">
        {{ autoSaveStatus }}
      </span>
    </div>
    
    <div class="canvas-container">
      <canvas id="litegraph-canvas" ref="canvas"></canvas>
    </div>
    
    <div class="node-palette">
      <div class="palette-header">节点库</div>
      <div 
        v-for="nodeType in nodeTypes" 
        :key="nodeType"
        class="node-item"
        draggable="true"
        @dragstart="onNodeDragStart(nodeType, $event)"
      >
        {{ nodeType }}
      </div>
    </div>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex';
import LiteGraph from 'litegraph.js';
import 'litegraph.js/css/litegraph.css';
import { AutoSaveManager } from '@/utils/autoSave';
import { LiteGraphPersistence, PERSISTENCE_STRATEGIES } from '@/utils/persistence';

export default {
  name: 'LiteGraphEditor',
  
  data() {
    return {
      graph: null,
      canvas: null,
      autoSaveManager: null,
      autoSaveStatus: '就绪'
    };
  },
  
  computed: {
    ...mapState('litegraph', ['graphData']),
    nodeTypes() {
      return ['basic/const', 'basic/watch', 'math/sum', 'math/multiply', 'interface/slider'];
    }
  },
  
  mounted() {
    this.initLiteGraph();
    this.initPersistence();
    this.loadSavedGraph();
  },
  
  methods: {
    ...mapActions('litegraph', ['saveGraph', 'loadGraph']),
    
    initLiteGraph() {
      this.graph = new LiteGraph.LGraph();
      this.canvas = new LiteGraph.LGraphCanvas(
        this.$refs.canvas, 
        this.graph
      );
      
      // 配置画布事件
      this.canvas.onNodeSelected = this.onNodeSelected;
      this.canvas.onNodeDeselected = this.onNodeDeselected;
      this.canvas.onGraphChanged = this.onGraphChanged;
      
      this.graph.start();
    },
    
    initPersistence() {
      const persistence = new LiteGraphPersistence(PERSISTENCE_STRATEGIES.SELECTIVE);
      this.autoSaveManager = new AutoSaveManager(this.$store, persistence);
      this.autoSaveManager.start();
    },
    
    async loadSavedGraph() {
      if (this.graphData) {
        this.graph.configure(this.graphData);
        this.autoSaveStatus = '已加载保存的图形';
      }
    },
    
    onNodeSelected(node) {
      this.$store.commit('litegraph/SET_SELECTED_NODES', [node]);
    },
    
    onNodeDeselected(node) {
      this.$store.commit('litegraph/SET_SELECTED_NODES', []);
    },
    
    onGraphChanged() {
      this.autoSaveStatus = '自动保存中...';
      this.$store.dispatch('litegraph/saveGraph');
      setTimeout(() => {
        this.autoSaveStatus = '已保存';
      }, 1000);
    },
    
    onNodeDragStart(nodeType, event) {
      event.dataTransfer.setData('text/plain', nodeType);
    },
    
    clearGraph() {
      this.graph.clear();
      this.$store.commit('litegraph/SET_GRAPH_DATA', null);
      this.autoSaveStatus = '图形已清空';
    }
  },
  
  beforeDestroy() {
    if (this.autoSaveManager) {
      this.autoSaveManager.saveImmediate();
    }
  }
};
</script>

<style scoped>
.litegraph-editor {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.toolbar {
  padding: 10px;
  background: #2c3e50;
  color: white;
  display: flex;
  gap: 10px;
  align-items: center;
}

.canvas-container {
  flex: 1;
  position: relative;
  overflow: hidden;
}

#litegraph-canvas {
  width: 100%;
  height: 100%;
  background: #1a1a1a;
}

.node-palette {
  width: 200px;
  background: #34495e;
  color: white;
  padding: 10px;
}

.palette-header {
  font-weight: bold;
  margin-bottom: 10px;
  padding-bottom: 5px;
  border-bottom: 1px solid #7f8c8d;
}

.node-item {
  padding: 8px;
  margin: 5px 0;
  background: #2c3e50;
  border-radius: 4px;
  cursor: move;
  transition: background 0.2s;
}

.node-item:hover {
  background: #3498db;
}

.auto-save-status {
  margin-left: auto;
  font-size: 0.9em;
  color: #95a5a6;
}
</style>

LiteGraph.js在WebGL Studio中的应用

性能优化与最佳实践

1. 数据压缩策略

对于大型图形,localStorage可能有限制(通常5-10MB),需要数据压缩:

// utils/compression.js
export class GraphCompression {
  static compress(graphData) {
    // 移除临时数据
    const compressed = {
      nodes: graphData.nodes.map(node => ({
        id: node.id,
        type: node.type,
        pos: node.pos,
        size: node.size,
        properties: node.properties,
        inputs: node.inputs,
        outputs: node.outputs
      })),
      links: graphData.links,
      groups: graphData.groups,
      config: graphData.config
    };
    
    return JSON.stringify(compressed);
  }
  
  static decompress(compressedString) {
    const data = JSON.parse(compressedString);
    // 恢复完整结构
    return {
      last_node_id: data.nodes.length > 0 ? 
        Math.max(...data.nodes.map(n => n.id)) : 0,
      last_link_id: data.links.length,
      nodes: data.nodes,
      links: data.links,
      groups: data.groups,
      config: data.config
    };
  }
}

2. 增量保存机制

只保存变化的部分,而不是整个状态:

// utils/incrementalSave.js
export class IncrementalSaveManager {
  constructor() {
    this.lastFullState = null;
    this.changeLog = [];
    this.MAX_CHANGES = 100; // 最多保存100个变更
  }
  
  recordChange(mutation, stateDiff) {
    this.changeLog.push({
      timestamp: Date.now(),
      mutation,
      diff: stateDiff
    });
    
    // 限制日志大小
    if (this.changeLog.length > this.MAX_CHANGES) {
      this.changeLog.shift();
    }
  }
  
  getIncrementalSave(currentState) {
    if (!this.lastFullState) {
      this.lastFullState = currentState;
      return { type: 'full', data: currentState };
    }
    
    const diff = this.calculateDiff(this.lastFullState, currentState);
    if (this.shouldDoFullSave(diff)) {
      this.lastFullState = currentState;
      this.changeLog = [];
      return { type: 'full', data: currentState };
    }
    
    return { type: 'incremental', data: diff };
  }
  
  calculateDiff(oldState, newState) {
    // 简化的差异计算
    const diff = {};
    
    if (JSON.stringify(oldState.litegraph?.graphData) !== 
        JSON.stringify(newState.litegraph?.graphData)) {
      diff.graphData = newState.litegraph?.graphData;
    }
    
    return diff;
  }
  
  shouldDoFullSave(diff) {
    // 如果差异太大,还是完整保存
    const diffSize = JSON.stringify(diff).length;
    const fullSize = JSON.stringify(this.lastFullState).length;
    return diffSize > fullSize * 0.5; // 差异超过50%时完整保存
  }
}

故障排除与调试

常见问题解决

  1. localStorage配额超出

    try {
      localStorage.setItem(key, data);
    } catch (e) {
      if (e.name === 'QuotaExceededError') {
        // 清理旧数据或提示用户
        this.clearOldData();
      }
    }
    
  2. 数据损坏恢复

    class DataIntegrityChecker {
      static validateGraphData(data) {
        if (!data || typeof data !== 'object') return false;
        if (!Array.isArray(data.nodes)) return false;
        if (!Array.isArray(data.links)) return false;
    
        // 验证节点ID唯一性
        const nodeIds = new Set(data.nodes.map(n => n.id));
        if (nodeIds.size !== data.nodes.length) return false;
    
        return true;
      }
    
      static repairGraphData(data) {
        // 尝试修复常见问题
        const repaired = { ...data };
    
        if (!repaired.nodes) repaired.nodes = [];
        if (!repaired.links) repaired.links = [];
        if (!repaired.groups) repaired.groups = [];
    
        // 修复重复ID
        const idMap = new Map();
        repaired.nodes.forEach((node, index) => {
          if (idMap.has(node.id)) {
            node.id = Math.max(...repaired.nodes.map(n => n.id)) + 1;
          }
          idMap.set(node.id, true);
        });
    
        return repaired;
      }
    }
    

总结

LiteGraph.js与Vuex通过localStorage的集成提供了一个强大而灵活的可视化编辑器解决方案。通过合理的持久化策略、性能优化和错误处理,您可以创建出既功能强大又用户体验良好的应用程序。记住以下关键点:

  1. 选择合适的持久化策略:根据应用需求选择完整保存、仅图形保存或选择性保存
  2. 实现自动保存:使用防抖技术避免频繁写入,同时确保数据安全
  3. 处理数据迁移:为未来的数据结构变更做好准备
  4. 优化性能:使用压缩和增量保存处理大型图形
  5. 增强用户体验:提供清晰的保存状态反馈和错误恢复机制

通过本文介绍的技术,您可以构建出专业级的可视化节点编辑器,为用户提供无缝的图形创建和编辑体验。无论是简单的流程图工具还是复杂的可视化编程环境,LiteGraph.js与Vuex的集成都能为您提供坚实的基础架构。

【免费下载链接】litegraph.js A graph node engine and editor written in Javascript similar to PD or UDK Blueprints, comes with its own editor in HTML5 Canvas2D. The engine can run client side or server side using Node. It allows to export graphs as JSONs to be included in applications independently. 【免费下载链接】litegraph.js 项目地址: https://gitcode.com/gh_mirrors/li/litegraph.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

抵扣说明:

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

余额充值