最近写了一个表格设计组件,用户可以通过右侧的属性面板,设置表格行数,列数,调整单元格填充色、边框线颜色、文本颜色。还可以直接操作单元格,插入行列,删除行列,合并拆分单元格,清空内容以及样式。
然而在这样一个复杂的操作过程中,很有可能存在一些误操作或者调整效果不满意,想要撤销设置的效果。针对一些简单的效果还好,再重新设置下就能到达撤销效果,那如果是无法手动撤销的效果,或者说撤销的步骤比较多,操作繁琐。这样用户体验感就下降了。所以,手动实现一个撤销、还原操作是非常有必要的。
基本实现思路:
1、准备一个储存历史记录的数组。
2、记录当前状态数据的索引。
3、初始打开时,将数据存入历史记录数组中,设置索引位置。
4、每操作一步,先删除历史记录数组索引位置之后的数据,再向历史记录数组末尾添加一条数据,并设置索引位置为数组最后一个元素。
5、当前历史记录数组长度到底一定值,删除数组第一个元素保证长度不超过指定值。
6、执行撤销操作时,将索引位置向前移动,再取出数据,进行表格渲染(业务操作)。
7、执行还原操作是,将索引位置向后移动,再取出数据,进行表格渲染(业务操作)。
难点以及解决方式:
1、何时触发添加历史记录操作?
当表格数据发生变化时触发,且不是由撤销、还原操作引发的数据变化时
2、如何实现短时间内的操作视作一步变化?
记录历史记录数据时,额外增加时间毫秒数,当下一次触发变化时,如果当前时间与最后一次记录时间间隔过短,则直接覆盖最后一条历史记录
3、如何实现快捷键监听?
使用widow.document.addEventListener添加事件监听,监听keydown事件,需要注意的是,一定要在销毁组件时删除事件监听,否则会出现多次调用。
演示地址:https://www.zjh336.cn/generalTable#/demo
代码:
data(){ tableJsonOperateArray: [], // 表格json操作数组 用作表格操作历史记录 operateIndex: -1, // 操作记录索引 用于标识在历史记录中的索引 operateHistoryDataFlag: false, // 操作历史记录数据标志 }, method(){ // 表格json对象变化 触发 tableJsonChange({ oldTableJsonObj, newTableJsonObj }) { // 如果操作历史记录标志为true则不进行后续操作 if (this.operateHistoryDataFlag) { return } // 操作记录索引不等于数组的最后一个下标 需要销毁该索引之后的记录 if (this.operateIndex !== this.tableJsonOperateArray.length - 1) { // 删除的索引 操作记录索引+1 const deleteIndex = this.operateIndex + 1 // 删除的长度 总长度 - (索引下标+1) const deleteLen = this.tableJsonOperateArray.length - (this.operateIndex + 1) // 销毁当前索引之后的历史记录 this.tableJsonOperateArray.splice(deleteIndex, deleteLen) } const curDate = new Date() // 操作记录数据 const operateData = { historyTime: curDate.getTime(), // 历史记录时间毫秒数 historyTimeFormat: this.$date.formatDate(curDate, 'yyyy-MM-dd hh:mm:ss'), // 历史记录时间格式化 preSecond: 0, // 距离上一次历史记录间隔秒数 tableJson: JSON.stringify(newTableJsonObj) // 表格json字符串 } // 取数组最后一条数据 const lastData = this.tableJsonOperateArray.length > 0 ? this.tableJsonOperateArray[this.tableJsonOperateArray.length - 1] : null // 获取历史记录时间毫秒数 const historyTime = lastData ? lastData.historyTime : 0 // 获取当前时间毫秒数 const curDateTime = new Date().getTime() // 相差秒数 const subSecond = (curDateTime - historyTime) / 1000 // 设置间隔秒数 operateData.preSecond = subSecond // 超过1秒 if (subSecond > 1) { // 向后插入数据 this.tableJsonOperateArray.push(operateData) // 不足1秒 } else { // 更新最后一条数据 this.$set(this.tableJsonOperateArray, this.tableJsonOperateArray.length - 1, operateData) } // 如果长度 大于50 if (this.tableJsonOperateArray.length > 50) { // 删除第一个元素 this.tableJsonOperateArray.shift() } // 设置操作记录索引为数组最后一个下标 this.operateIndex = this.tableJsonOperateArray.length - 1 }, // 操作历史记录 operateType 操作类型 撤销(repeal) 或者 还原(restore) operateHistory(operateType) { // 操作历史记录必须保证 历史记录数组有数据 if (!this.tableJsonOperateArray.length) { // 无数据直接返回 return } // 撤销操作且当前历史记录索引等于0 // 或者 还原操作且当前列数记录索引等于最后一位 if ((operateType === 'repeal' && this.operateIndex === 0) || (operateType === 'restore' && this.operateIndex === this.tableJsonOperateArray.length - 1)) { // 返回 return } // 开启操作历史记录标志 this.operateHistoryDataFlag = true // 撤销 if (operateType === 'repeal') { // 操作索引前移 this.operateIndex-- // 还原 } else if (operateType === 'restore') { // 操作索引后移 this.operateIndex++ } // 取出历史记录数据 const historyData = this.tableJsonOperateArray[this.operateIndex] // 获取表格json数据 const tableJson = historyData.tableJson // 转换tableJson对象 const tableJsonObj = JSON.parse(tableJson) this.$refs.tableProperties.tableProperty = JSON.parse(JSON.stringify(this.tableOptions)) this.$refs.tableProperties.tablePropertyCopy = JSON.parse(JSON.stringify(this.tableOptions)) // 刷新表格 this.getTableRef().refreshTable(tableJson) // 获取表格配置信息 this.tableOptions = tableJsonObj.options // 获取表格扩展属性值 const extendOptions = tableJsonObj.extendOptions // 获取预览主题色编码 const previewThemeColorCode = extendOptions ? extendOptions.previewThemeColorCode : '' // 预览主题色不为空 if (previewThemeColorCode) { // 触发修改主题色方法 this.$EventBus.$emit('updatePreviewThemeColorCode', previewThemeColorCode) } // 触发修改配置模式方法 this.$EventBus.$emit('configModeChange', this.tableOptions.configMode) // 刷新业务数据 this.$EventBus.$emit('refreshServiceData', { data: tableJsonObj.data }) this.$forceUpdate() setTimeout(() => { this.operateHistoryDataFlag = false }, 0) }, keydownFun(event) { // 撤销方法 当前为编辑模式 if (event.ctrlKey && event.keyCode === 90 && !this.showPreview) { this.$nextTick(() => { this.$refs.generalTableDesignRef.operateHistory('repeal') }) event.preventDefault() // 还原方法 当前为编辑模式 } else if (event.ctrlKey && event.keyCode === 89 && !this.showPreview) { this.$nextTick(() => { this.$refs.generalTableDesignRef.operateHistory('restore') }) event.preventDefault() } } }, mounted() { // 监听键盘事件 window.document.addEventListener('keydown', this.keydownFun, false) }, destroyed() { window.document.removeEventListener('keydown', this.keydownFun) }
发表评论