首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

低开开发笔记(八): 低代码编辑器实现撤销回退(命令模式,防抖处理)

编程知识
2024年07月16日 17:23

好家伙,

 

0.代码已开源

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

 

1.事件触发

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

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

  1.监控用户行为

  2.监控数据变化

 

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

 

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

没错,watch

watch: {
        formTemplate: {
            handler: function (oldVal, newVal) {
                if (!this.ischange) {
                    // debugger
                    console.log(oldVal, newVal)
                }
            },
            deep: true,
            immediate: true,
        }
    },

 

但是,这会出现一些问题

 深度监视

 

来看看我们数据的样子

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

数据由

{
    "list": [],
    "config": {
        "labelPosition": "top",
        "labelWidth": 80,
        "size": "mini",
        "outputHidden": true,
        "hideRequiredMark": false,
        "syncLabelRequired": false,
        "labelSuffix": "",
        "customStyle": ""
    }
}

 变成了

{
    "list": [
        {
            "type": "input",
            "options": {
                "defaultValue": "",
                "type": "text",
                "prepend": "",
                "append": "",
                "placeholder": "请输入",
                "maxLength": 0,
                "clearable": false,
                "hidden": false,
                "disabled": false
            },
            "label": "输入框",
            "labelWidth": -1,
            "width": "100%",
            "span": 24,
            "model": "input_17211185804812",
            "key": "input_17211185804812",
            "rules": [
                {
                    "required": false,
                    "message": "必填项",
                    "trigger": [
                        "blur"
                    ]
                }
            ],
            "dynamicLabel": false
        }
    ],
    "config": {
        "labelPosition": "top",
        "labelWidth": 80,
        "size": "mini",
        "outputHidden": true,
        "hideRequiredMark": false,
        "syncLabelRequired": false,
        "labelSuffix": "",
        "customStyle": ""
    }
}

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

 

 

2.防抖

function debounce(func, wait) {
    let timeout;
    return function () {
        const context = this;
        const args = arguments;
        clearTimeout(timeout);
        timeout = setTimeout(() => {
            func.apply(context, args);
        }, wait);
    };
}

 

watch: {
        formTemplate: {
            handler: debounce(function (oldVal, newVal) {
                if (!this.ischange) {
                    this.undoStack.push(deepClone(oldVal))
                }
            }, 300),
            deep: true,
            immediate: true,
        }
    },

 

 

3.栈实现撤回

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

    handleUndo() {
            this.ischange = true
            if (this.undoStack.length > 1) {

                let laststate = this.undoStack[this.undoStack.length - 2]
                
                this.formTemplate = deepClone(laststate)

                let redostate = this.undoStack.pop()

                this.redoStack.push(redostate)

            } else {
                alert("撤回栈已空,无法撤回")
            }
            setTimeout(() => {
                this.ischange = false
            }, 400)
        },

        handleRedo() {
            if (this.redoStack.length > 0) {
                this.formTemplate = this.redoStack.pop()
            } else {
                alert("无法重做")
            }
        },
  • 撤销操作:

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

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

逻辑图

过程解释

  • 初始状态:

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

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

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

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

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

 

 

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

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

//命令类
class Command {
    constructor(execute, undo) {
        this.execute = execute;
        this.undo = undo;
    }
}
class UndoCommand extends Command {
    constructor(context) {
        super(
            () => {
                if (context.undoStack.length > 1) {
                    let laststate = context.undoStack[context.undoStack.length - 2];
                    context.formTemplate = deepClone(laststate);
                    let redostate = context.undoStack.pop();
                    context.redoStack.push(redostate);
                } else {
                    alert("撤回栈已空,无法撤回");
                }
                setTimeout(() => {
                    context.ischange = false;
                }, 400);
            },
            () => {
                if (context.redoStack.length > 0) {
                    context.formTemplate = context.redoStack.pop();
                } else {
                    alert("无法重做");
                }
            }
        );
    }
}


class RedoCommand extends Command {
    constructor(context) {
        super(
            () => {
                if (context.redoStack.length > 0) {
                    context.formTemplate = context.redoStack.pop();
                } else {
                    alert("无法重做");
                }
            },
            () => {
                // 这里可以实现撤销 redo 的逻辑,但我们暂时不需要
            }
        );
    }
}


//methods
//撤销重做
        handleUndo() {
            this.ischange = true;
            const undoCommand = new UndoCommand(this);
            undoCommand.execute();
        },
        handleRedo() {
            const redoCommand = new RedoCommand(this);
            redoCommand.execute();
        },

 

From:https://www.cnblogs.com/FatTiger4399/p/18305653
本文地址: http://www.shuzixingkong.net/article/77
0评论
提交 加载更多评论
其他文章 Linux 文件夹和文件操作【Linux 常用命令系列一】
本文首先介绍了 Linux 中文件的结构,将全部文件夹罗列并介绍了大概的用途,然后通过实例介绍了文件夹相关的常用操作,仅供参考。
从Mybatis-Plus开始认识SerializedLambda
从Mybatis-Plus开始认识SerializedLambda 背景 对于使用过Mybatis-Plus的Java开发者来说,肯定对以下代码不陌生: @TableName("t_user") @Data public class User { private String i
折腾指南: 将光猫改造成你的NAS,WebDAV+网页文件管理器vList5+natmap
光猫在家里只能担当拨号上网的角色吗?不,稍加改造就是一台NAS! 在这里,使用natmap映射IPV4,使用nginx+njs+vList5打造在线文件预览+管理,使用nginx+davext实现远程文件管理 甚至还可以使用aria2下载磁力做下载器,使用clash让光猫变成真的猫!只要你敢想,光猫
折腾指南: 将光猫改造成你的NAS,WebDAV+网页文件管理器vList5+natmap 折腾指南: 将光猫改造成你的NAS,WebDAV+网页文件管理器vList5+natmap 折腾指南: 将光猫改造成你的NAS,WebDAV+网页文件管理器vList5+natmap
设计模式之工厂模式(学习笔记)
定义 工厂方法模式是一种创建型设计模式,它定义了一个用于创建对象的接口,但由子类来决定实例化哪一个类。工厂方法使得类的实例化延迟到子类,这样可以让客户端在不需要知道具体类的情况下创建对象。工厂方法模式通过使用继承和多态性,允许子类来控制对象的创建方式,能够更好地应对对象创建的复杂性和变化性。 为什么
.NET科普:.NET简史、.NET Standard以及C#和.NET Framework之间的关系
最近在不少自媒体上看到有关.NET与C#的资讯与评价,感觉大家对.NET与C#还是不太了解,尤其是对2016年6月发布的跨平台.NET Core 1.0,更是知之甚少。在考虑一番之后,还是决定写点东西总结一下,也回顾一下.NET的发展历史。 首先,你没看错,.NET是跨平台的,可以在Windows、
.NET科普:.NET简史、.NET Standard以及C#和.NET Framework之间的关系 .NET科普:.NET简史、.NET Standard以及C#和.NET Framework之间的关系 .NET科普:.NET简史、.NET Standard以及C#和.NET Framework之间的关系
超级炫酷的终端神器 eDEX-UI
目录eDEX-UI主要亮点:优点:软件简介安装LinuxWindows效果更换皮肤matrixTron-disrupted退出常见问题解答 eDEX-UI,不仅是一款全屏幕、跨平台的终端模拟器和系统监视器,更是一件被封存的艺术品,让你尽情沉浸于科幻般的装逼幻想之中。它的界面设计独特,仿佛来自未来世界
超级炫酷的终端神器 eDEX-UI 超级炫酷的终端神器 eDEX-UI 超级炫酷的终端神器 eDEX-UI
Java21的虚拟线程Virtual Thread初体验
我们之前使用的是操作系统平台的线程,就称之为“系统线程”吧。虚拟线程是JDK维护的,原理跟WebFlux的底层实现差不多,都是工作线程分离。 要使用虚拟线程,需要使用JDK21以上,包括21。 虚拟线程可以创建很多很多 系统线程不能轻易创建太多,我们一直被教导创建线程是很重的活动。 for (int
Java21的虚拟线程Virtual Thread初体验
Netcode for Entities如何添加自定义序列化,让GhostField支持任意类型?以int3为例(1.2.3版本)
一句话省流:很麻烦也很抽象,能用内置支持的类型就尽量用。 首先看文档。官方文档里一开头就列出了所有内置的支持的类型:Ghost Type Templates 其中Entity类型需要特别注意一下:在同步这个类型的时候,如果是刚刚Instantiate的Ghost(也就是GhostId尚未生效,上一篇