利用vue對比兩組數(shù)據(jù)差異的可視化組件詳解
如題,朋友有個這樣的需求,感覺挺常見,發(fā)出來給大家參考一下
需求:
用el-table展示兩組數(shù)據(jù),有差異的單元格顯示紅色,新增的顯示整行綠色
大概要點:
- 需要一個數(shù)據(jù)組,里面包含兩組需要對比的數(shù)據(jù)
- 需要一個唯一的key,用來確定某一行的數(shù)據(jù)是否在其他數(shù)據(jù)中存在(是否是新增
- 接受一個表格列的配置,用于渲染表格,同時對比差異只按照配置的數(shù)據(jù)來,其他的數(shù)據(jù)無需進(jìn)行對比
根據(jù)剛才的要點可以建立一下組件的props:
props: {
uniqueKey: {
type: String,
default: "id"
},
dataGroup: {
type: Array,
validator: val => val.length === 2
},
columns: {
type: Array,
required: true
}
}
唯一id默認(rèn)為id;columns的格式就按照el-table-column的來,定義為{ label, prop, ... }
組件的基本樣式也很簡單:
<template>
<div class="diff-table-container">
<el-table
v-for="(data, i) in completedData"
:key="i"
:data="data"
:row-style="markRowStyles"
:cell-style="markCellStyles"
>
<el-table-column v-for="item in columns" :key="`${i}${item.prop}`" align="center" v-bind="item" />
</el-table>
</div>
</template>
<style lang="scss" scoped>
.diff-table-container {
display: flex;
align-items: flex-start;
.el-table + .el-table {
margin-left: 20px;
}
}
</style>
如上所示,就是把兩個表格簡單的橫向排布。這里的completedData指進(jìn)行diff處理完成之后的數(shù)據(jù),格式和傳進(jìn)來的dataGroup是一樣的。markRowStyles和markRowStyles都是el-table提供的,分別指行和列的樣式,值為對象或返回一個對象的函數(shù)。
接下來定義兩個Symbol,之后在diff數(shù)據(jù)的時候會給數(shù)據(jù)加上標(biāo)記,用Symbol做標(biāo)記可以防止屬性名沖突。
data() {
return {
DIFF_CELL_KEY: Symbol("diffCells"), // 一個數(shù)組,存儲有差異的cell屬性名
COMPLETED_KEY: Symbol("completed") // 標(biāo)記已完成處理
};
}
然后diff的樣式處理也可以直接定下來了。
methods: {
// 完成處理之后沒有標(biāo)記的,就表示只在一組數(shù)據(jù)中出現(xiàn),也就是新增數(shù)據(jù)
markRowStyles({ row }) {
return (
!row[this.COMPLETED_KEY] && {
backgroundColor: "#E1F3D8"
}
);
},
// 根據(jù)當(dāng)前行的唯一key,找到map中緩存的行數(shù)據(jù)
// 就是dataGroup[0].find(item => item[uniqueKey] === row[uniqueKey])
// 然后判斷DIFF_CELL_KEY數(shù)組中是否包含當(dāng)前列的屬性名
markCellStyles({ row, column }) {
const { $_cacheMap, uniqueKey, DIFF_CELL_KEY } = this;
const _cacheRow = $_cacheMap.get(row[uniqueKey]);
return (
_cacheRow &&
_cacheRow[DIFF_CELL_KEY].includes(column.property) && {
backgroundColor: "#FDE2E2"
}
);
}
}
最后就是diff的處理了,直接用計算屬性去做,處理完成之后返回新數(shù)據(jù):
computed: {
// 處理完成的數(shù)據(jù)
completedData({ dataGroup, uniqueKey, columns, DIFF_CELL_KEY, COMPLETED_KEY }) {
// 這一步不是必要的,根據(jù)業(yè)務(wù)需求來,如果規(guī)定不能修改原數(shù)據(jù)的話就做一下深拷貝
const _dataGroup = deepClone(dataGroup);
// Map<string|number, object>,ts不太熟,應(yīng)該是這么寫,其實就是row[unique]: row
const cacheMap = new Map();
// 先遍歷一次第一組數(shù)據(jù),初始化DIFF_CELL_KEY數(shù)組,然后存進(jìn)map中
for (const _row of _dataGroup[0]) {
_row[DIFF_CELL_KEY] = [];
cacheMap.set(_row[uniqueKey], _row);
}
// 遍歷第二組數(shù)據(jù),里面還有一次循環(huán),因為只處理columns里面定義的屬性,其他屬性不做對比
for (const _row of _dataGroup[1]) {
for (const { prop } of columns) {
// 如果是唯一key就直接跳過
if (prop === uniqueKey) continue;
// 從緩存中查找相同的一條數(shù)據(jù)
const original = cacheMap.get(_row[uniqueKey]);
// 如果找不到就說明這條數(shù)據(jù)是新增的,直接跳過
if (!original) continue;
// 否則就在兩組數(shù)據(jù)中打一個標(biāo)識表示已處理過,不是新增的
_row[COMPLETED_KEY] = true;
original[COMPLETED_KEY] = true;
// 最后對比兩個屬性值,如果相同就push進(jìn)DIFF_CELL_KEY數(shù)組中
// 注意這里DIFF_CELL_KEY數(shù)組只存在于第一組數(shù)據(jù)當(dāng)中
// 因為只要有差異就會在所有表格中顯示,所以不用每一組數(shù)據(jù)都存
_row[prop] !== original[prop] && original[DIFF_CELL_KEY].push(prop);
}
}
// 將map存一份到this中,因為會在處理樣式的時候用到
this.$_cacheMap = cacheMap;
return _dataGroup;
}
}
完事了,最后貼一下完整代碼:
<template>
<div class="diff-table-container">
<el-table
v-for="(data, i) in completedData"
:key="i"
:data="data"
:row-style="markRowStyles"
:cell-style="markCellStyles"
>
<el-table-column v-for="item in columns" :key="`${i}${item.prop}`" v-bind="item" align="center" />
</el-table>
</div>
</template>
<script>
function deepClone(val) {
// 看需求要不要做深拷貝
return val;
}
export default {
name: "DiffTable",
props: {
uniqueKey: {
type: String,
default: "id"
},
dataGroup: {
type: Array,
validator: val => val.length === 2
},
columns: {
type: Array,
required: true
}
},
data() {
return {
DIFF_CELL_KEY: Symbol("diffCells"),
COMPLETED_KEY: Symbol("completed")
};
},
computed: {
completedData({ dataGroup, uniqueKey, columns, DIFF_CELL_KEY, COMPLETED_KEY }) {
const _dataGroup = deepClone(dataGroup);
const cacheMap = new Map();
for (const _row of _dataGroup[0]) {
_row[DIFF_CELL_KEY] = [];
cacheMap.set(_row[uniqueKey], _row);
}
for (const _row of _dataGroup[1]) {
for (const { prop } of columns) {
if (prop === uniqueKey) continue;
const original = cacheMap.get(_row[uniqueKey]);
if (!original) continue;
_row[COMPLETED_KEY] = true;
original[COMPLETED_KEY] = true;
_row[prop] !== original[prop] && original[DIFF_CELL_KEY].push(prop);
}
}
this.$_cacheMap = cacheMap;
return _dataGroup;
}
},
methods: {
markRowStyles({ row }) {
return (
!row[this.COMPLETED_KEY] && {
backgroundColor: "#E1F3D8"
}
);
},
markCellStyles({ row, column }) {
const { $_cacheMap, uniqueKey, DIFF_CELL_KEY } = this;
const _cacheRow = $_cacheMap.get(row[uniqueKey]);
return (
_cacheRow &&
_cacheRow[DIFF_CELL_KEY].includes(column.property) && {
backgroundColor: "#FDE2E2"
}
);
}
}
};
</script>
<style lang="scss" scoped>
.diff-table-container {
display: flex;
align-items: flex-start;
.el-table + .el-table {
margin-left: 20px;
}
}
</style>
使用示例:
<template>
<diff-table :data-group="[oldData, newData]" :columns="tableColumns" />
</template>
<script>
import DiffTable from "./DiffTable.vue";
export default {
name: "Index",
components: {
DiffTable
},
data() {
return {
oldData: [
{ id: 1, name: "zhangsan1", age: 23, address: "zxczxczxc" },
{ id: 2, name: "zhangsan2", age: 23.5, address: "zxczxczxc" },
{ id: 3, name: "zhangsan34", age: 23, address: "zxczxczxc" },
{ id: 4, name: "zhangsan4", age: 23, address: "zxczxczxc" },
{ id: 5, name: "zhangsan5", age: 23, address: "zxczxczxc" },
{ id: 6, name: "zhangsan5", age: 23, address: "zxczxczxc" }
],
newData: [
{ id: 1, name: "zhangsan1", age: 23, address: "zxczxczxc" },
{ id: 2, name: "zhangsan2", age: 23, address: "zxczxczxc" },
{ id: 4, name: "zhangsan4", age: 23, address: "地址地址地址" },
{ id: 3, name: "zhangsan3", age: 23, address: "zxczxczxc" },
{ id: 5, name: "zhangsan5", age: 23, address: "zxczxczxc" },
{ id: 7, name: "zhangsan5", age: 23, address: "zxczxczxc" },
{ id: 8, name: "zhangsan5", age: 23, address: "zxczxczxc" }
],
tableColumns: [
{ label: "唯一id", prop: "id" },
{ label: "名稱", prop: "name" },
{ label: "年齡", prop: "age" },
{ label: "地址", prop: "address" }
]
};
}
};
</script>
效果預(yù)覽:

擴展功能TODO:
- 可配置n組數(shù)據(jù)進(jìn)行對比
- 數(shù)據(jù)超過兩組之后應(yīng)該新增DELETE_ROW_KEY標(biāo)記一條刪除的數(shù)據(jù)
- 邏輯大概為:只存在于一組數(shù)據(jù)中的為新增;存在多組數(shù)據(jù)中但不是所有數(shù)據(jù)的,不包含的數(shù)據(jù)組內(nèi)就要標(biāo)記為刪除的數(shù)據(jù)
- 可配置diff樣式、自定義diff規(guī)則等
總結(jié)
到此這篇關(guān)于利用vue對比兩組數(shù)據(jù)差異的可視化組件的文章就介紹到這了,更多相關(guān)vue對比兩組數(shù)據(jù)差異內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決vue.js中settimeout遇到的問題(時間參數(shù)短效果不穩(wěn)定)
這篇文章主要介紹了解決vue.js中settimeout遇到的問題(時間參數(shù)短效果不穩(wěn)定),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-07-07

