好家伙,
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(); },
标签:状态,防抖,undoStack,false,低开,撤销,编辑器,context,重做 From: https://www.cnblogs.com/FatTiger4399/p/18305653