vuex实现历史记录的示例代码

 更新时间:2021年05月12日 08:36:05   作者:KwokRonny  
这篇文章主要介绍了vuex实现历史记录的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

最近自研着一个可视化操作平台,其中涉及到用户操作后可撤销或重做,在网上搜了一些解决思路,完善自己所设想的解决思路。

历史记录需求的要点

  • 可存储在 localStorage 中
  • 可多次撤销或多次重做
  • 点击列表中的一项,将历史倒退或前进至指定位置

看似简单的需求,在基础建设设计上的错误,亦会在未来导致更多的工作量。所以结合上面两点的要求,发现 vuex 的基本思路非常适合完成这个需求,redux 同样。

实现思路

此项目用了 typescript 来加强代码的严谨性,方便日后维护,大家简单看个思路。

1. 先定义历史记录的数据结构

interface HistoryItem {
  timestrap: number; // 记录时间戳
  name: string; // 记录名称
  redo: string; // 重做Mutation
  undo: string; // 撤销Mutation
  redoParams: any[]; // 重做Mutation提交参数
  undoParams: any[]; // 撤销Mutation提交参数
}

interface HistoryStatus {
  historys: HistoryItem[]; // 记录history数组
  _currentHistory: number; // 当前节点索引
}

2. 编写 History 状态模块

编写基础操作history状态的vuex module,创建记录的Mutation,重做和撤销的Action
一条记录是包含对这个步骤的执行redo操作与撤销undo操作的。所以在用户点击列表其中一项时,应该是循环回退到当前项的前一项undo,或循环redo到当前项

所以需要增加一条空记录,方便用户点击空记录撤销最初的操作。

运用了vuex-module-decorators 装饰器,写更易维护的代码

import { VuexModule, Module, Mutation, Action } from "vuex-module-decorators";

@Module({ namespaced: true })
export class HistoryModule extends VuexModule<HistoryStatus> implements HistoryStatus {
  /** 
   * 初始化一个空记录的原因主要是方便列表操作时:
   * 当用户点击最早的一条记录时,可以正常撤销用户操作的第一步
  **/
  public historys: HistoryItem[] = [
    {
      name: `打开`,
      timestrap: Date.now(),
      redo: "",
      redoParams: [],
      undo: "",
      undoParams: [],
    },
  ];
  public _currentHistory: number = 0;

  // getter
  get current(){
    return this._currentHistory;
  }

  // getter
  get historyList(): HistoryItem[] {
    return this.historys || [];
  }

  // 创建历史记录
  @Mutation
  public CREATE_HISTORY(payload: HistoryItem) {
    if (this._currentHistory < this.historys.length - 1) {
      this.historys = this.historys.slice(0, this._currentHistory);
    }
    // 由于js的深浅拷贝问题,所以在创建时都需要对数据进行深拷贝
    // 想尝试lodash的clone函数,但发现好像JSON.stringify的方式clone应该更快的,毕竟我们的数据不存在函数
    // 我这里就先不改了,主要是表达出思路即可
    this.historys.push(_.cloneDeep(payload));
    this._currentHistory = this.historys.length - 1;
  }

  @Mutation
  public SET_CURRENT_HISTORY(index: number) {
    this._currentHistory = index < 0 ? 0 : index;
  }

  // 重做
  @Action
  public RedoHistory(times: number = 1) {
    let { state, commit } = this.context;
    let historys: HistoryItem[] = state.historys;
    let current: number = state._currentHistory;
    if (current + times >= historys.length) return;
    while (times > 0) {
      current++;
      let history = historys[current];
      if (history) {
        commit(history.redo, ...history.redoParams, { root: true });
      }
      times--;
    }
    commit("SET_CURRENT_HISTORY", current);
  }

  // 撤销
  @Action
  public UndoHistory(times: number = 1) {
    let { state, commit } = this.context;
    let historys: HistoryItem[] = state.historys;
    let current: number = state._currentHistory;
    if (current - times < 0) return;
    while (times > 0) {
      let history = historys[current];
      if (history) {
        commit(history.undo, ...history.undoParams, { root: true });
      }
      times--;
      current--;
    }
    commit("SET_CURRENT_HISTORY", current);
  }
}

3. 编写可以撤销或重做的功能

完成上面两步后,我们就可以编写各种操作了

编写对数据基础操作的Mutation

@Mutation
public CREATE_PAGE(payload: { page: PageItem; index: number }) {
  this.pages.splice(payload.index, 0, _.cloneDeep(payload.page));
  this._currentPage = this.pages.length - 1;
}

@Mutation
public REMOVE_PAGE(id: string) {
  let index = this.pages.findIndex((p) => p.id == id);
  index > -1 && this.pages.splice(index, 1);
  if (this._currentPage == index) {
    this._currentPage = this.pages.length > 0 ? 0 : -1;
  }
}

将基础操作按要求封装成带保存->记录->执行的Action

// 包装创建页面函数
@Action
public CreatePage(type: "page" | "dialog") {
  let { state, commit } = this.context;
  
  // 记录保存即将创建的页面
  let id = _.uniqueId(type) + Date.now();
  let pageName = pageType[type];
  let page: PageItem = {
    id,
    name: `${pageName}${state.pages.length + 1}`,
    type,
    layers: [],
    style: { width: 720, height: 1280 },
  };

  //创建历史记录
  let history: HistoryItem = {
    name: `创建${pageName}`,
    timestrap: Date.now(),
    redo: "Page/CREATE_PAGE",
    redoParams: [{ index: state.pages.length - 1, page }],
    undo: "Page/REMOVE_PAGE",
    undoParams: [id],
  };
  // 保存记录此历史记录
  commit("Histroy/CREATE_HISTORY", history, { root: true });

  commit(history.redo, ...history.redoParams, { root: true });
}

@Action
public RemovePage(id: string) {
  // 记录保存现场状态
  let index = this.pages.findIndex((p) => p.id == id);
  if (index < 0) return;
  let page: PageItem = this.context.state.pages[index];

  //创建历史记录
  let history: HistoryItem = {
    name: `删除 ${page.name}`,
    timestrap: Date.now(),
    redo: "Page/REMOVE_PAGE",
    redoParams: [id],
    undo: "Page/CREATE_PAGE",
    undoParams: [{ page, index }],
  };

  // 保存记录此历史记录
  this.context.commit("Histroy/CREATE_HISTORY", history, { root: true });
  this.context.commit(history.redo, ...history.redoParams, { root: true });
}

以上,撤销与重做的功能就基本完成了

4. 使用

1. 我们现在只需要在使用时创建或删除页面时使用封装的`Action`后

  private create(type: "page" | "dialog") {
    this.$store.dispatch("Page/CreatePage", type);
  }

  private remove(id: number) {
    this.$store.dispatch("Page/RemovePage", id);
  }

2. 配置全局热键

typescript App.vue

private mounted() {
    let self = this;
    hotkeys("ctrl+z", function (event, handler) {
      self.$store.dispatch("History/UndoHistory");
    });
    hotkeys("ctrl+y", function (event, handler) {
      self.$store.dispatch("History/RedoHistory");
    });
  }

效果

到此这篇关于vuex实现历史记录的示例代码的文章就介绍到这了,更多相关vuex 历史记录内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue.js实现图片压缩封装方法

    vue.js实现图片压缩封装方法

    这篇文章主要介绍了vue.js实现图片压缩封装方法,包括全局main.js引入方法,通过引入imgupload方法结合实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • uni-app中app和webview的h5通信简单步骤

    uni-app中app和webview的h5通信简单步骤

    这篇文章主要介绍了如何在nvue页面中使用webview组件,并详细介绍了如何在h5项目中安装和配置npmiy_uniwebview插件,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2025-02-02
  • vue-resource拦截器设置头信息的实例

    vue-resource拦截器设置头信息的实例

    下面小编就为大家带来一篇vue-resource拦截器设置头信息的实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-10-10
  • 基于vue.js实现的分页

    基于vue.js实现的分页

    本文主要给大家介绍基于vue的分页原生写法,代码分为html部分和js部分,简单易懂,非常不错,具有参考借鉴价值,需要的朋友参考下吧
    2018-03-03
  • 一次前端Vue项目国际化解决方案的实战记录

    一次前端Vue项目国际化解决方案的实战记录

    这篇文章主要给大家介绍了关于前端Vue项目国际化解决方案的实战记录,以上只是一部分Vue项目开发中遇到的典型问题和解决方案,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-07-07
  • 优雅地使用loading(推荐)

    优雅地使用loading(推荐)

    这篇文章主要介绍了在Vue和React中如何优雅地使用loading,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • vue3成功创建项目后 run serve启动项目报错的解决

    vue3成功创建项目后 run serve启动项目报错的解决

    这篇文章主要介绍了vue3成功创建项目后 run serve启动项目报错的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Vue组件化(ref,props, mixin,.插件)详解

    Vue组件化(ref,props, mixin,.插件)详解

    这篇文章主要介绍了Vue组件化(ref, props, mixin, 插件)的相关知识,包括ref属性,props配置项及mixin混入的方式,本文通过示例代码多种方式相结合给大家介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • Vue中emit事件无法触发的问题及解决

    Vue中emit事件无法触发的问题及解决

    这篇文章主要介绍了Vue中emit事件无法触发的问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • Vue数据代理的实现流程逐步讲解

    Vue数据代理的实现流程逐步讲解

    通过一个对象代理对另一个对象中的属性的操作(读/写),就是数据代理。要搞懂Vue数据代理这个概念,那我们就要从Object.defineProperty()入手,Object.defineProperty()是Vue中比较底层的一个方法,在数据劫持,数据代理以及计算属性等地方都或多或少的用到了本函数
    2023-01-01

最新评论