需求背景:
日常开发中会碰到历史记录展示的弹窗,有时表格列比较多,数据前后基本变化不大,但这时找需要从大量表格数据中对比数据,有都有那些修改,比较费眼力了,如果标记📌出来前后变更的操作字段,将会省不少眼药水🐶。
实现思路:
基本思路:先对比获取哪条数据的那个字段需要被渲染,然后根据对比数据结果结果进行渲染。
1、 对比时应该永远标记最新修改的那一条数据:
历史记录排序可以分为正序和倒序,一般默认情况下第一条记录应该是最新一条即倒序排序,所以如果是第一条和第二条进行比较的话,默认我们应该把最新的记录给标记出来。这里以倒序为例;
数据决定页面渲染,遍历相邻的两条数据对它们每个展示字段进行比较,如果值不相同,存储值字段名,这时需要有一个地方存放值变化字段名,在这条记录对象上开辟一个新字段changeList(存放需要标记的字段名)
ts
datasource =[{
operationType: 'add',
operator: 'zhangsan',
params1: 'BA',
params2: 'BZ'
...
_changeList: ['operator','params1']
},{
operationType: 'add',
operator: 'lisi',
params1:'CB',
params2: 'BZ'
}]
- 渲染记录的时候,就清楚知道哪条记录的哪些字段列需要标记📌了。
ts
const columns = [
{
title: 'Operation Type',
dataIndex:'operationType'
},
{
title: 'Operator',
dataIndex:'operator'
},
{
title: 'Params1',
dataIndex:'params1'
},
{
title: 'Params2',
dataIndex:'params2'
}
].map(columnItem=>{
const {render,dataIndex} = columnItem
return
{ ...columnItem,
render: (_,record)=> {
const { _changeList } = record
return _changeList.includes(dataIndex) ? <Typography.Text mark>
{render()}
</Typography.Text> : render()
}}}
对比时包含引用类型比较
- 考虑dataIndex 可能是数组的情况:
dataIndex
除了是字符串还有可能是数组对应对象里的字段。这样_changeList
可能存储这样结果:[['company','name'],'operator','params1' ]
, 对于原生类型是赋值是值传递,而对于引用类型(数组、对象)则是地址传递;所以下面比较也不会有问题,但是操作时千万不要修改_changeList
,因为它可能会影响columns
的dataIndex
- value也可能是引用类型:比较的两个字段除了是原始类型以外,它们也可能是引用类型,通过render函数重新渲染。比较引用类型可以使用
isEqual
函数来判断。
ts
const test = () => {
const columns = [
{
title: '服务器',
dataIndex: ['info', 'server'], // 嵌套对象使用数组路径
}
];
let list: (string | string[])[] = [];
list.push(columns[0].dataIndex);
console.log(list.includes(columns[0].dataIndex)); // true
};
考虑一下可以忽略的列: 对于一些创建日期的列,因为可能很大概率都不同,所以这种列没有必要标记📌出来。
完整实现
tsx
import { Table, Typography } from 'antd'
import type { ColumnProps } from 'antd/es/table'
import dayjs from 'dayjs'
import { map, get, isArray, isEqual } from 'lodash'
const CompareTable = () => {
const dataSource = [
{
id: '1',
action: '部署服务',
operator: '赵六',
createTime: '2023-06-03T16:20:00Z', // 最新时间
details: '部署了v2.3.0版本',
status: 'success',
department: '运维部',
cost: 3200,
approval: true,
tags: ['发布', '生产环境'],
info: {
server: 'SI-3344',
location: '深圳机房',
},
},
...
]
const columns = [
{
title: 'ID',
dataIndex: 'id',
},
{
title: '操作类型',
dataIndex: 'action',
},
{
title: '操作人',
dataIndex: 'operator',
},
{
title: '操作时间',
dataIndex: 'createTime',
render: (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm:ss'),
},
{
title: '操作详情',
dataIndex: 'details',
},
{
title: '状态',
dataIndex: 'status',
},
{
title: '部门',
dataIndex: 'department',
},
{
title: '成本',
dataIndex: 'cost',
},
{
title: '审批状态',
dataIndex: 'approval',
},
{
title: '标签',
dataIndex: 'tags',
render: (ts) => {
return Array.isArray(ts) ? ts.join(',') : '--'
},
},
{
title: '服务器',
dataIndex: ['info', 'server'], // 嵌套对象使用数组路径
},
{
title: '位置',
dataIndex: ['info', 'location'], // 嵌套对象使用数组路径
},
].map((item) => {
const { dataIndex, render = (text) => text ?? '--' } = item
return {
...item,
render: (text, record) => {
const { changeList } = record
return changeList?.includes(dataIndex) ? (
<Typography.Text mark>{render(text)}</Typography.Text>
) : (
render(text) ?? text
)
},
}
})
/**
* @description 生成前后比较dataSource
* @param config:{dataSource 数据源, columns: 数据列 , ignoreDataIndexList: 忽略数据列}
* @returns 变更后的dataSource
*/
const compareTable = (config: {
dataSource: Record<string, any>[]
columns: ColumnProps[]
ignoreDataIndexList?: (string | string[])[]
}) => {
const { dataSource, columns, ignoreDataIndexList = [] } = config
const columnList = map(columns, 'dataIndex')
// 从下往上每行遍历最新值
dataSource.reduceRight((nextItem, preItem) => {
const changeList = []
// 每行取每列比较
columnList.forEach((dataIndex) => {
// 跳过忽略列属性
if (
ignoreDataIndexList.some((item) =>
// 比较引用类型可以使用isEqual
isArray(dataIndex) ? isEqual(item, dataIndex) : item === dataIndex
)
) {
return
}
const nextValue = get(nextItem, dataIndex)
const currentValue = get(preItem, dataIndex)
if (
typeof nextValue === 'object'
? !isEqual(nextValue, currentValue)
: nextValue !== currentValue
) {
changeList.push(dataIndex)
}
})
preItem.changeList = changeList
return preItem
})
return dataSource
}
compareTable({
dataSource,
columns,
ignoreDataIndexList: ['createTime', 'id', ['info', 'server']],
})
return (
<Table
rowKey="id"
dataSource={compareDataSource}
columns={columns}
bordered
pagination={{ pageSize: 10 }}
scroll={{ x: 'max-content' }}
/>
)
}
export default CompareTable
上面实现实际会原地修改dataSource,如果后续对dataSource其他计算时需要注意,另外对compareTable调用,放到组件每次渲染时这里只是作为例子,现实中对compareTable调用只有依赖参数变化才会去调用。
如果你有其他好的想法欢迎评论和指正~