经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 编程经验 » 查看文章
低开开发笔记(八): 低代码编辑器实现撤销回退(命令模式,防抖处理)
来源:cnblogs  作者:养肥胖虎  时间:2024/7/17 10:58:50  对本文有异议

好家伙,

 

0.代码已开源

https://github.com/Fattiger4399/ph_questionnaire-.git

 

1.事件触发

我们先从事件的触发开始讲起

大致上我们有两个思路可以选择

  1.监控用户行为

  2.监控数据变化

 

两种选择都会有较难处理的部分,这里我们先选第二个选项

 

关于监控数据,首先你会想到什么?

没错,watch

  1. watch: {
  2. formTemplate: {
  3. handler: function (oldVal, newVal) {
  4. if (!this.ischange) {
  5. // debugger
  6. console.log(oldVal, newVal)
  7. }
  8. },
  9. deep: true,
  10. immediate: true,
  11. }
  12. },

 

但是,这会出现一些问题

 深度监视

 

来看看我们数据的样子

如果我们从数据的角度出发观察变化,在拖拽的过程中,

数据由

  1. {
  2. "list": [],
  3. "config": {
  4. "labelPosition": "top",
  5. "labelWidth": 80,
  6. "size": "mini",
  7. "outputHidden": true,
  8. "hideRequiredMark": false,
  9. "syncLabelRequired": false,
  10. "labelSuffix": "",
  11. "customStyle": ""
  12. }
  13. }

 变成了

  1. {
  2. "list": [
  3. {
  4. "type": "input",
  5. "options": {
  6. "defaultValue": "",
  7. "type": "text",
  8. "prepend": "",
  9. "append": "",
  10. "placeholder": "请输入",
  11. "maxLength": 0,
  12. "clearable": false,
  13. "hidden": false,
  14. "disabled": false
  15. },
  16. "label": "输入框",
  17. "labelWidth": -1,
  18. "width": "100%",
  19. "span": 24,
  20. "model": "input_17211185804812",
  21. "key": "input_17211185804812",
  22. "rules": [
  23. {
  24. "required": false,
  25. "message": "必填项",
  26. "trigger": [
  27. "blur"
  28. ]
  29. }
  30. ],
  31. "dynamicLabel": false
  32. }
  33. ],
  34. "config": {
  35. "labelPosition": "top",
  36. "labelWidth": 80,
  37. "size": "mini",
  38. "outputHidden": true,
  39. "hideRequiredMark": false,
  40. "syncLabelRequired": false,
  41. "labelSuffix": "",
  42. "customStyle": ""
  43. }
  44. }

 由于监控的是一个复杂对象,这会导致watch多次触发

 

 

2.防抖

  1. function debounce(func, wait) {
  2. let timeout;
  3. return function () {
  4. const context = this;
  5. const args = arguments;
  6. clearTimeout(timeout);
  7. timeout = setTimeout(() => {
  8. func.apply(context, args);
  9. }, wait);
  10. };
  11. }

 

  1. watch: {
  2. formTemplate: {
  3. handler: debounce(function (oldVal, newVal) {
  4. if (!this.ischange) {
  5. this.undoStack.push(deepClone(oldVal))
  6. }
  7. }, 300),
  8. deep: true,
  9. immediate: true,
  10. }
  11. },

 

 

3.栈实现撤回

这里我们使用栈去做状态记录的保存

  1.     handleUndo() {
  2. this.ischange = true
  3. if (this.undoStack.length > 1) {
  4. let laststate = this.undoStack[this.undoStack.length - 2]
  5. this.formTemplate = deepClone(laststate)
  6. let redostate = this.undoStack.pop()
  7. this.redoStack.push(redostate)
  8. } else {
  9. alert("撤回栈已空,无法撤回")
  10. }
  11. setTimeout(() => {
  12. this.ischange = false
  13. }, 400)
  14. },
  15. handleRedo() {
  16. if (this.redoStack.length > 0) {
  17. this.formTemplate = this.redoStack.pop()
  18. } else {
  19. alert("无法重做")
  20. }
  21. },
  • 撤销操作:

    • 将当前状态保存到重做栈中。
    • 从撤销栈中取出最后一个状态,并将其设为当前状态。
    • 从撤销栈中移除最后一个状态。
  • 重做操作:

    • 将当前状态保存到撤销栈中。
    • 从重做栈中取出最后一个状态,并将其设为当前状态。
    • 从重做栈中移除最后一个状态。

逻辑图

过程解释

  • 初始状态:

    • 空白的工作区。
    • 撤销栈是空的。
    • 重做栈是空的。
  • 用户进行第一个操作:

    • 用户在工作区添加了“元素一”。
    • 撤销栈中保存了操作前的状态(空白)。
    • 重做栈依然是空的。
  • 用户进行第二个操作:

    • 用户在工作区添加了“元素二”。
    • 撤销栈中保存了操作前的状态(元素一)。
    • 撤销栈现在有两个状态(元素一和空白)。
    • 重做栈依然是空的。
  • 用户点击撤回:

    • 撤回上一步操作,恢复到上一个状态(元素一)。
    • 撤销栈中移除最后一个状态(元素二),撤销栈现在只有一个状态(空白)。
    • 重做栈中保存被撤销的状态(元素二)。
  • 用户点击重做:

    • 重做上一步撤销的操作,恢复到上一个状态(元素一)。
    • 撤销栈中保存恢复前的状态(空白)。
    • 重做栈移除最后一个状态(元素一),现在只有一个状态(元素二)。

 

 

4.使用命令模式思想封装

最后,我们对代码进行封装

  1. //命令类
  2. class Command {
  3. constructor(execute, undo) {
  4. this.execute = execute;
  5. this.undo = undo;
  6. }
  7. }
  8. class UndoCommand extends Command {
  9. constructor(context) {
  10. super(
  11. () => {
  12. if (context.undoStack.length > 1) {
  13. let laststate = context.undoStack[context.undoStack.length - 2];
  14. context.formTemplate = deepClone(laststate);
  15. let redostate = context.undoStack.pop();
  16. context.redoStack.push(redostate);
  17. } else {
  18. alert("撤回栈已空,无法撤回");
  19. }
  20. setTimeout(() => {
  21. context.ischange = false;
  22. }, 400);
  23. },
  24. () => {
  25. if (context.redoStack.length > 0) {
  26. context.formTemplate = context.redoStack.pop();
  27. } else {
  28. alert("无法重做");
  29. }
  30. }
  31. );
  32. }
  33. }
  34. class RedoCommand extends Command {
  35. constructor(context) {
  36. super(
  37. () => {
  38. if (context.redoStack.length > 0) {
  39. context.formTemplate = context.redoStack.pop();
  40. } else {
  41. alert("无法重做");
  42. }
  43. },
  44. () => {
  45. // 这里可以实现撤销 redo 的逻辑,但我们暂时不需要
  46. }
  47. );
  48. }
  49. }
  50. //methods
  51. //撤销重做
  52. handleUndo() {
  53. this.ischange = true;
  54. const undoCommand = new UndoCommand(this);
  55. undoCommand.execute();
  56. },
  57. handleRedo() {
  58. const redoCommand = new RedoCommand(this);
  59. redoCommand.execute();
  60. },

 

原文链接:https://www.cnblogs.com/FatTiger4399/p/18305653

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号