【可视化系统】如何维护保存状态并实现undo和redo?

1,097

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

前言

这是我们 手摸手实现可视化系统系列文章的第六篇,前面我们完成了:

  • 主舞台的搭建
  • 拖放组件到主舞台
  • 组件的自由变换
  • 属性配置面板的设计与实现 专栏地址: 前端可视化

接下来我们来探讨保存按钮的状态维护和撤销重做的设计和实现,如图右上角:

image.png

需求分析

保存的状态维护: 截止到上次保存之后,属性和数据未发生变化时,按钮应该是禁用状态; 有数据和属性变化时,保存按钮可点击。

undo和redo: 每次操作都需要记录,用户可点击按钮回退(undo),回退之后可以向前(redo)。

方案设计

保存状态

保存状态的实现相对比较简单,我们在项目中使用了vuex来存储状态,screen数据用来记录主舞台的属性,elements用来记录舞台上添加的全部组件的属性数据。

const state = {
  elements: [],
  screen: {},
  screenId: "",
  screenChanged: false,
  elementsChanged: false,
};

当我们调用更新elementsscreen方法的时候,修改screenChange和elementsChange

  setScreenData(state, value) {
    if (state.screen.id && state.screen.id === value.id) {
      state.screenChanged = true;
    }
    Object.assign(state.screen, { ...value });
  },

这里state.screen.id && state.screen.id === value.id判断是为了排除掉切换项目的操作,刚进来一个项目并不算数据变动,不需要激活保存按钮。

  changed: state => {
    return state.screenChanged || state.elementsChanged;
  },

两者任意数据变动都应激活保存按钮。

undo和redo

undo和redo 相对来说就比较复杂一些。 先来画个图分析一下:

场景一:从E回退到C ,然后执行了新的操作F,那么D E 需要从记录队列中删除,F加入到C之后。

image.png

场景二: F之后回退到C,还能够再redo到F

image.png

场景三: 队列达到最大值之后,每从队尾加一个,就需要从队首剔除一个。

image.png

思路比较清晰了:

  1. 维护一个队列(用数组模拟), 存储每一步的操作
  2. 回退之后再执行新操作,清除已回退的步骤 -- 场景一
  3. 数组达到最大值之后,每添加一天新纪录就删除一条最旧记录

这里的难点也就是如何存储每一步操作? 以怎样的格式存储?

  • 方案一: 把全部的数据序列化之后,直接存入数组,回退就从数组中取出数据进行反序列化。 这种方案简单粗暴,能够实现功能,但是会需要存储大量的冗余数据。

  • 方案二: vue虚拟dom的更新不是用到了diff算法吗? 我们是不是也可以比对 数据的变化, 只记录变化的数据。

最终方案

方案有了,下一步就是撸代码了~

TODO

p0:

  • 预览页面
  • 图表组件的完善

p1:

  • 看板导出成文件(可以独立运行)
  • 数据接入

项目源码地址

github: vis-board-webpack 有兴趣的小伙伴可以fork、star、pr,欢迎大佬莅临指导~

写在最后

更文不易,你的支持是我持续创作的动力~

如果对可视化系统感兴趣,可以持续关注专栏。