利用vue對(duì)比兩組數(shù)據(jù)差異的可視化組件詳解
如題,朋友有個(gè)這樣的需求,感覺挺常見,發(fā)出來給大家參考一下
需求:
用el-table展示兩組數(shù)據(jù),有差異的單元格顯示紅色,新增的顯示整行綠色
大概要點(diǎn):
- 需要一個(gè)數(shù)據(jù)組,里面包含兩組需要對(duì)比的數(shù)據(jù)
- 需要一個(gè)唯一的key,用來確定某一行的數(shù)據(jù)是否在其他數(shù)據(jù)中存在(是否是新增
- 接受一個(gè)表格列的配置,用于渲染表格,同時(shí)對(duì)比差異只按照配置的數(shù)據(jù)來,其他的數(shù)據(jù)無需進(jìn)行對(duì)比
根據(jù)剛才的要點(diǎn)可以建立一下組件的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>
如上所示,就是把兩個(gè)表格簡單的橫向排布。這里的completedData指進(jìn)行diff處理完成之后的數(shù)據(jù),格式和傳進(jìn)來的dataGroup是一樣的。markRowStyles和markRowStyles都是el-table提供的,分別指行和列的樣式,值為對(duì)象或返回一個(gè)對(duì)象的函數(shù)。
接下來定義兩個(gè)Symbol,之后在diff數(shù)據(jù)的時(shí)候會(huì)給數(shù)據(jù)加上標(biāo)記,用Symbol做標(biāo)記可以防止屬性名沖突。
data() { return { DIFF_CELL_KEY: Symbol("diffCells"), // 一個(gè)數(shù)組,存儲(chǔ)有差異的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的處理了,直接用計(jì)算屬性去做,處理完成之后返回新數(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)該是這么寫,其實(shí)就是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),因?yàn)橹惶幚韈olumns里面定義的屬性,其他屬性不做對(duì)比 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ù)中打一個(gè)標(biāo)識(shí)表示已處理過,不是新增的 _row[COMPLETED_KEY] = true; original[COMPLETED_KEY] = true; // 最后對(duì)比兩個(gè)屬性值,如果相同就push進(jìn)DIFF_CELL_KEY數(shù)組中 // 注意這里DIFF_CELL_KEY數(shù)組只存在于第一組數(shù)據(jù)當(dāng)中 // 因?yàn)橹灰胁町惥蜁?huì)在所有表格中顯示,所以不用每一組數(shù)據(jù)都存 _row[prop] !== original[prop] && original[DIFF_CELL_KEY].push(prop); } } // 將map存一份到this中,因?yàn)闀?huì)在處理樣式的時(shí)候用到 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ù)覽:
擴(kuò)展功能TODO:
- 可配置n組數(shù)據(jù)進(jìn)行對(duì)比
- 數(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對(duì)比兩組數(shù)據(jù)差異的可視化組件的文章就介紹到這了,更多相關(guān)vue對(duì)比兩組數(shù)據(jù)差異內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決vue.js中settimeout遇到的問題(時(shí)間參數(shù)短效果不穩(wěn)定)
這篇文章主要介紹了解決vue.js中settimeout遇到的問題(時(shí)間參數(shù)短效果不穩(wěn)定),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07淺談vue單頁面中有多個(gè)echarts圖表時(shí)的公用代碼寫法
這篇文章主要介紹了淺談vue單頁面中有多個(gè)echarts圖表時(shí)的公用代碼寫法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-07-07