您现在的位置是:亿华云 > IT科技
低代码平台的撤销与重做该如何设计?
亿华云2025-10-09 12:56:00【IT科技】4人已围观
简介在上一篇文章《低代码平台的属性面板该如何设计?》中聊到了低代码平台的属性面板的设计,今天来聊一下画布区域的撤销、重做的设计。撤销、重做其实是我们平时一直在用的操作。对应快捷键一般就是⌘ Z /
在上一篇文章《低代码平台的低代属性面板该如何设计?》中聊到了低代码平台的属性面板的设计,今天来聊一下画布区域的码平撤销、重做的撤销设计。
撤销、该何重做其实是设计我们平时一直在用的操作。对应快捷键一般就是低代⌘ Z / Ctrl+Z、⌘⇧ Z / Ctrl+Shift+Z。码平这个功能是撤销很常见的,它可以极大的该何提升用户体验,提高编辑效率,设计但是低代用代码应该如何实现呢?再具体点,在我们的码平低代码平台,针对画布区域元素的撤销一系列操作,又该如何去设计呢?该何
我们先对其中的一系列状态变更做一下分析。
默认情况下,设计用户在画布的一系列操作会改变整个画布的呈现状态:
在进行到某个操作时,用户是高防服务器可以回退到之前的状态的,也就是撤销:
当然在进行撤销操作后,用户是可以恢复这个操作的,对应的就是重做:
来看下之前画布的数据结构:
const editorModule = {
state: {
components: [],
},
mutations: {
addComponent(state, component) {
component.id = uuidv4();
state.components.push(component);
},
updateComponent(state, { id, key, value, isProps }) {
const updatedComponent = state.components.find(
(component) => component.id === (id || state.currentElement)
);
if (updatedComponent) {
if (isProps) {
updatedComponent.props[key] = value;
} else {
updatedComponent[key] = value;
}
}
},
deleteComponent(state, id) {
state.components = state.components.filter(
(component) => component.id !== id
);
},
},
}对应操作:
添加组件:addComponent更新组件:updateComponent删除组件:deleteComponent结合上面的三张图,不难想到我们要单独维护一份数据来存储变更记录,执行撤销和重做操作时就是在这份变更记录取出已有的数据,然后去更新原来的components。在原有的state中添加:
// 变更记录
histories: [],
// 游标,用来标记变更的位置
historyIndex: -1,在画布区域操作(添加、删除、更新)时,更新原有组件数据的同时,也要维护变更记录。
我们需要封装一个更新变更记录的方法updateHistory。
正常情况下其实只用往histories添加记录就可以了:
const updateHistory = (state, historyRecord) => {
state.histories.push(historyRecord);
}在之前的添加组件、更新组件、云南idc服务商删除组件节点做一下调整:
添加组件添加组件的同时往histories添加一项changeType为add的组件数据,不过这里的component要做下深拷贝:
addComponent(state, component) {
component.id = uuidv4();
state.components.push(component);
updateHistory(state, {
id: uuidv4(),
componentId: component.id,
changeType: "add",
data: cloneDeep(component),
});
}更新组件
更新组件时向histories添加一项changeType为modify的组件数据,同时要把新/老value和key也添加进去:
updateComponent(state, { id, key, value, isProps }) {
const updatedComponent = state.components.find(
(component) => component.id === (id || state.currentElement)
);
if (updatedComponent) {
if (isProps) {
const oldValue = updatedComponent.props[key]
updatedComponent.props[key] = value;
updateHistory(state, {
id: uuidv4(),
componentId: id || state.currentElement,
changeType: "modify",
data: { oldValue, newValue: value, key },
});
} else {
updatedComponent[key] = value;
}
}
},删除组件删除组件时往histories添加一条changeType为delete的数据,同时要把index也做下记录,因为后面做撤销操作时是根据index重新插入到原来的位置:
deleteComponent(state, id) {
const componentData = state.components.find(
(component) => component.id === id
) as ComponentData;
const componentIndex = state.components.findIndex(
(component) => component.id === id
);
state.components = state.components.filter(
(component) => component.id !== id
);
updateHistory(state, {
id: uuidv4(),
componentId: componentData.id,
changeType: "delete",
data: componentData,
index: componentIndex,
});
},可以看到在添加历史记录的过程中,多了一个changeType字段来区分是什么类型的变更:
type changeType = add | modify | delete这个也是为后面的撤销/重做做铺垫,有了历史记录,针对不同的changeType分别执行对应的数据处理。
首先来看下撤销,也就是undo。第一步是要找到当前的游标,也就是撤销操作的位置。如果在此之前,从未有过撤销操作,b2b信息网也就是 historyIndex 为-1 时,这时将 historyIndex 置为历史记录的最后一项。否则就将 historyIndex--:
if (state.historyIndex === -1) {
state.historyIndex = state.histories.length - 1;
} else {
state.historyIndex--;
}找到撤销的位置,下一步就是根据上一步记录到histories中的不同changeType做对应的数据处理:
const history = state.histories[state.historyIndex];
switch (history.changeType) {
case "add":
state.components = state.components.filter(
(component) => component.id !== history.componentId
);
break;
case "delete":
state.components = insert(
state.components,
history.index,
history.data
);
break;
case "modify": {
const { componentId, data } = history;
const { key, oldValue } = data
const updatedComponent = state.component.find(component => component.id === componentId)
if(updatedComponent) {
updatedComponent.props[key] = oldValue
}
break;
}
default:
break;
}如果之前是做了添加组件操作,那么撤销时对应的就是删除处理。
如果之前是做了删除处理,那么撤销时对应的就是把之前删除的组件恢复添加到原来的位置。
如果之前是对组件属性做了改动,那么撤销时对应的就是把组件对应的属性恢复到原来的值。
那么对于重做,就是撤销的逆向操作了,可以理解为就是正常的操作:
const history = state.histories[state.historyIndex];
switch (history.changeType) {
case "add":
state.components.push(history.data);
break;
case "delete":
state.components = state.components.filter(
(component) => component.id !== history.componentId
);
break;
case "modify": {
const { componentId, data } = history;
const { key, newValue } = data
const updatedComponent = state.component.find(component => component.id === componentId)
if(updatedComponent) {
updatedComponent.props[key] = newValue
}
break;
}
default:
break;
}其实到这里,一个基础的撤销、重做就已经实现了。
但这是不符合使用习惯的,我们在用编辑器的时候,不可能让你无限的撤销,这个我们通过设置maxHistoryNumber来控制,调整一下之前的updateHistory:
const updateHistory = (state, historyRecord) => {
if (state.histories.length < maxHistoryNumber) {
state.histories.push(historyRecord);
} else {
state.histories.shift();
state.histories.push(historyRecord);
}
}当历史记录条目小于设定的最大历史条目前,正常往histories添加记录。
如果大于或等于maxHistoryNumber时,就把历史记录中最前面的一个剔除,同时把最新的这条加到历史记录的最后。
还有一个场景是:在撤销/重做的过程中,又正常对画布区域执行了操作。
这种情况,常用的做法就是把大于historyIndex的历史记录直接全部删除,同时把historyIndex置为-1,也就是初始状态。因为现在已经进入了一个新的状态分支:
if (state.historyIndex !== -1) {
state.histories = state.histories.slice(0, state.historyIndex);
state.historyIndex = -1;
}至此,低代码平台的撤销/重做的设计思路就分享结束了。
很赞哦!(9822)
相关文章
- 4、说起来容易
- 2、定期提交和投标域名注册。例如,益华网络点击“立即预订”后,平台会抢先为客户注册域名。当然,一个域名可能会被多个客户预订,所以出价最高的人中标。
- 为了避免将来给我们的个人站长带来的麻烦,在选择域名后缀时,我们的站长最好省略不稳定的后缀域名,比如n,因为我们不知道策略什么时候会改变,更不用说我们将来是否还能控制这个域名了。因此,如果站长不是企业,或者有选择的话,如果不能选择域名的cn类,最好不要选择它。
- 在数以亿计的网站中,我们应该抓住每一个可能带来宣传的机会,域名可以带有企业的名字,一般可以使用汉语拼音或者英语单词或者是相关缩写的形式,只要用户记住了你企业的名字,就能很容易的打出你的网站域名,同样的,记住了网站域名也能很快的记住你公司的名字。
- 旧域名的外链是否会对新建站点产生影响?
- 投资各类域名就像到处打游击战,结果处处失败。因为这样,对任何一个中国域名市场的走势和价格都没有准确的把握,所以最好缩小范围,准确把握战场态势,埋伏。
- 4、待所有域名查询结束后可在右侧点击导出结果,即可以excel的文件方式将查询到的结果导出。
- 最后提醒我们,域名到期后要及时更新域名,否则可能会丢掉域名,每次抢先注册都不会成功。
- (4) 使用何种形式的域名后缀对网页搜索影响不大,但域名后缀也需要考虑方便用户记忆
- 公司和个人选域名方法一样吗?有什么不同?