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

· 浏览次数 : 0

正文

好家伙,

 

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();
        },

 

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

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

好家伙, 0.代码已开源 https://github.com/Fattiger4399/ph_questionnaire-.git 1.事件触发 我们先从事件的触发开始讲起 大致上我们有两个思路可以选择 1.监控用户行为 2.监控数据变化 两种选择都会有较难处理的部分,这里我们先选第二个选项 关于

低开开发笔记(二):低代码编辑器基本原理

好家伙, 完整代码已开源 https://github.com/Fattiger4399/ph-questionnaire.git 本片我们来讲述 如何将dsl的数据渲染为视图 1.数据格式 dsl: { component: 'div', wid: 0, props: { }, style: {

低开开发笔记(五):修bug-深拷贝与浅拷贝

好家伙 今天遇到一个bug 0.问题描述 描述如下: 代码如下: copynodefunc() { this.copynode = this.model.selected }, affixnode() { const id = this.model.selected.wid - 1; const g

低开开发笔记(六): 工作台与模板样式开发

好家伙,仅仅只是实现了样式,完整功能暂未完成 完整代码已开源 https://github.com/Fattiger4399/ph-questionnaire.git 1.灵感来源 (抄袭对象) 刚开始想着随便写个低开项目练练手的,然后就写成这样了 1.1.简道云 1.2.问卷星 2.上代码

Gitee千Star优质项目解析: ng-form-element低开引擎解析

好家伙, 在写项目的时候,我发现自己的平台的组件写的实在是太难看了,于是想去gitee上偷点东西,于是我们本期的受害者出现了 gitee项目地址 https://gitee.com/jjxliu306/ng-form-elementplus-sample.git 组件库以及引擎完全开源,非常牛逼的项

Karmada v1.5发布:多调度组助力成本优化

摘要:在最新发布的1.5版本中,Karmada 提供了多调度组的能力,利用该能力,用户可以实现将业务优先调度到成本更低的集群,或者在主集群故障时,优先迁移业务到指定的备份集群。 本文分享自华为云社区《Karmada v1.5发布!多调度组助力成本优化》,作者:华为云云原生团队。 Karmada 是开

Ui2Code+ChatGPT助力低代码搭建

低代码开发平台(LCDP),是低代码或无代码通过快速搭建配置的方式完成一个应用程序的开发与上线,可视化低代码就是可视化的DSL,它的优点更多的是来源可视化,相对的,它的局限性也还是来源于可视化,复杂的业务逻辑用低代码可能会更加复杂。低代码应该是特定领域问题的简化和抽象,如果只是单纯将原有的编码工作转换为 GUI 的模式,并没有多大意义。

LuBase 低代码开发框架介绍 - 可私有化部署

框架定位 面向开发人员,针对管理软件领域,对页面交互和通用功能进行高阶封装,逐步打造成平台型、生态型开发工具。 涓涓细流 ,汇聚成海,基于 PBC(组件式开发)开发理念,让功能模块的复用更简单。 让管理软件开发回归到对需求的深入思考和求解。 框架简介 LuBase 是以数据模型驱动,可视化表单和页面

ChatGPT赋能低代码开发:打造智能应用的双重引擎

摘要:本文摘自葡萄城低代码产品活字格的资深用户(格友超哥)所撰写的文章:《惊叹表现!活字格+ChatGPT:低代码开发智能应用的巨大潜力》。 ChatGPT的functions函数使用方 自从OPENAI发布了最新的GPT引擎gpt-3.5-turbo-0613之后,我就对它的functions参数

为什么现代的低代码开发平台都不支持导出源代码?

> 摘要:本文由葡萄城技术团队于博客园原创并首发。葡萄城为开发者提供专业的开发工具、解决方案和服务,赋能开发者。 初次接触低代码的程序员大多会纠结一个问题,为什么功能越强大的低代码开发平台越不会提供导出源代码的功能? 要想回答这个问题,我们得回顾一下低代码开发的发展史。事实上,支持导出[源代码](h