一、前言
项目是一个后台管理系统,可供用户编写文章。需要查看历史痕迹。
二、前提
- 后台模板用的是vue-element-admin,编辑器也是自带的Tinymce编辑器。
- 需要下载Google的文件对比模板 google-diff-match-patch 。
三、原理
根据后端记录的每次修改,将返回的数组数据循环,让前后两个数据两两比较,根据google-diff-match-patch可以将对比结果通过数组的形式返回,记录修改前与修改后的首尾下标,存到一个数组里,后续利用同样的方法,获取到数组数据之后可以跟上一个的得到的数组进行比对,或进行新增、删除、保持不变。
展示一下diff对比后返回的数组数据,如截图
说一下数组的每一项的含义:
graph TD
下标0 --> 0 --> 未修改 --> =
下标0 --> 1 --> 新增 --> +
下标0 --> -1 --> 删除--> -
下标1 --> 内容
这样我们就可以通过状态以及内容length来获取首尾index,然后就可以做上标记(不变、新增、删除)。
四、简单实现
1.获取前后数组数据
举个栗子: 假如现在数据从12 -> 152再到152 -> 12
首先第一个图是从12 -> 152,对比左右的文本信息,根据google-diff-match-patch的对比情况可得数组
数组中下标为0的key代表增加(1)、删除(-1)、不变(0) 数组中下标为1的key代表的是内容。
同理图2如下图
// list是google-diff-match-patch对比之后的数组
// leftIndex:修改前的下标,代表字符串执行到哪里
// rightIndex: 修改后的下标,代表字符串执行到哪里
// sp1:修改前的首下标, sp2:修改后的首下标, ep1:修改前的尾下标, ep2:修改后的尾下标
let leftIndex = 0
let rightindex = 0
let newArray = []
for(let i=0;i<list.length-1;i++) {
const item = list[i]
switch(item[0]) {
// case === 0,说明不变,此时修改前后的index都应该往后加内容的长度
case 0:
var sp1 = leftIndex
var sp2 = rightIndex
var ep1 = sp1 + item[1].length
var ep2 = sp2 + item[1].length
newArray.push({
type: '=',
sp1: sp1,
sp2: sp2,
ep1: ep1,
ep2: ep2,
content: item[1]
})
leftIndex = leftIndex + item[1].length
rightindex = rightindex + item[1].length
break;
// case === 1,说明是新增,此时应该修改前的下标不变,修改后的下标加内容的长度
case 1:
var sp1 = leftIndex
var sp2 = rightIndex
var ep1 = sp1
var ep2 = sp2 + item[1].length
newArray.push({
type: '+',
sp1: sp1,
sp2: sp2,
ep1: ep1,
ep2: ep2,
content: item[1]
})
rightindex = rightindex + item[1].length
break;
// case === -1,说明是删除,此时应该修改前的下标加内容的长度,修改后的下标不变
case: -1
var sp1 = leftIndex
var sp2 = rightIndex
var ep1 = sp1 + item[1].length
var ep2 = sp2
newArray.push({
type: '-',
sp1: sp1,
sp2: sp2,
ep1: ep1,
ep2: ep2,
content: item[1]
})
leftIndex = leftIndex + item[1].length
break;
default:
break;
}
}
根据上面的代码可得下面两种情况的数据:
下图为第一种情况(=:不变,+:新增,-删除)
(sp1:修改前的首下标, sp2:修改后的首下标, ep1:修改前的尾下标, ep2:修改后的尾下标,尾下标 = 首下标+内容)
| 首尾下标 / 修改状态 | = | + | = |
|---|---|---|---|
| sp1 | 0 | 1 | 1 |
| sp2 | 0 | 1 | 2 |
| ep1 | 1 | 1 | 2 |
| ep2 | 1 | 2 | 3 |
下图为第二种情况(=:不变(0),+:新增(1),-:删除(-1))
(sp1:修改前的首下标, sp2:修改后的首下标, ep1:修改前的尾下标, ep2:修改后的尾下标,尾下标 = 首下标+内容)
| 首尾下标 / 修改状态 | = | - | = |
|---|---|---|---|
| sp1 | 0 | 1 | 2 |
| sp2 | 0 | 1 | 1 |
| ep1 | 1 | 2 | 3 |
| ep2 | 1 | 1 | 2 |
2.对数组数据进行循环对比
获取到的数组数据经过变形之后大概长这样
第一个数组数据
const preArray = [{
type: '=',
sp1: 0,
sp2: 0,
ep1: 1,
ep2: 1,
content: '1'
}, {
type: '+',
sp1: 1,
sp2: 1,
ep1: 1,
ep2: 2,
content: '5'
}, {
type: '=',
sp1: 1,
sp2: 2,
ep1: 2,
ep2: 3,
content: '2'
}]
第二个数组数据
const newArray = [{
type: '=',
sp1: 0,
sp2: 0,
ep1: 1,
ep2: 1,
content: '1'
}, {
type: '-',
sp1: 1,
sp2: 1,
ep1: 2,
ep2: 1,
content: '5'
}, {
type: '=',
sp1: 2,
sp2: 1,
ep1: 3,
ep2: 2,
content: '2'
}]
得到两个数组数据之后,进行对比,根据符号以及下标可判断,后面相对于前面来说做了什么操作,判断大致代码如下
先循环后面一项数组数据newArray,然后每一项去跟前面的数组数据的每一项去比较
for(let i=0;i<newArray.length;i++) {
const new = newArray[i]
// 寻找与当前的项对应的上一个数组中的某项
for(let j=0;j<preArray.length;j++) {
const pre = preArray[j]
// 如果上一个数组的项是被删除的,则不会影响下一个数组数据
if (pre.type === '-') {
continue;
}
// 根据下标可查到当前项对应的上一个数组的哪一项
if (pre.sp1 === new.sp2) {
break;
}
}
// 获取数组数据对比情况
const method = preArray[j].type + new.type
merge[method].call(preArray, j, newArray, i)
}
const merge = {
'==': function(preArray, j, newArray, i) {
// 替换
const pre = preArray[j]
const new = newArray[i]
preArray.splice(j, 1, new)
},
'=+': function(preArray, j, newArray, i) {
// 直接插入
const pre = preArray[j]
const new = newArray[i]
preArray.splice(j, 0, new)
},
'=-': function(preArray, j, newArray, i) {
// 替换,将type从'='替换成'-'
const pre = preArray[j]
const new = newArray[i]
preArray.splice(j, 1, new)
},
'+=': funciton(preArray, j, newArray, i) {
// 替换,type固定为'+'
const pre = preArray[j]
const new = newArray[i]
const n = { ...new, type: '+' }
preArray.splice(j, 1, new)
},
'++': function(preArray, j, newArray, i) {
// 直接插入
const pre = preArray[j]
const new = newArray[i]
preArray.splice(j, 0, new)
},
'+-': function(preArray, j, newArray, i) {
// 替换
const pre = preArray[j]
const new = newArray[i]
preArray.splice(j, 1, new)
}
}
merge两个数组数据之后,合并成一个数组数据,后续的newArray又将与合并后的preArray继续调用merge进行合并。