目的
呦呦呦,又碰见一个需求,要实现一个类似 C# GridView控件 根据GroupIndex 进行分组功能,好好好 你winform直接设置groupIndex组件内部处理渲染了,没有办法只能我们自己处理数据了。
思路
原理很简单就是用el-table的展开行
row-key 行数据的 Key,用来优化 Table 的渲染;这个一定要设置哦,而且不能重复,不然会有渲染问题
tree-props 渲染嵌套数据的配置选项
span-method 合并行或列的计算方法
实现方法
第一个版本 暂时写一个方法先实现 后续再优化
<template>
<el-table
ref="tableRef"
:data="state.tableData"
show-overflow-tooltip
row-key="regItrId"
default-expand-all
:tree-props="{ children: 'children', hasChildren: 'true' }"
:span-method="tableSpanMethod"
height="80vh"
border
>
<el-table-column width="80" :show-overflow-tooltip="false" align="center"></el-table-column>
<el-table-column prop="regRegisterDate" label="保养时间" align="left" :formatter="groupNameFormtter" />
<el-table-column prop="regContent" label="保养操作" min-width="100" align="center" />
<el-table-column prop="regExp" label="保养备注" min-width="100" align="center" />
<el-table-column prop="userName" label="操作人" min-width="100" align="center" />
<el-table-column prop="overIntervalTime" label="超出保养时间" min-width="100" align="center" />
</el-table>
</template>
<script setup lang="ts">
import { MultilevelDataGrouping, data } from "@/utils/format";
import { ElMessage } from "element-plus";
const state = reactive({
tableData: [] as any
});
/** ###################### 表格 ###################### */
const tableRef = ref();
const groupNameFormtter = (row: any, column: any, cellValue: any, index: number) => {
if (!row.children) {
return row.regRegisterDate;
} else {
return row.groupsLable;
}
};
// 合并行
const tableSpanMethod = (data: { row: any; column: any; rowIndex: number; columnIndex: number }) => {
const { columnIndex } = data;
if (data.row.children && columnIndex == 1) {
return [1, 5];
} else {
return [1, 1];
}
};
onMounted(() => {
const { nodeList, parentNodeKeyList } = MultilevelDataGrouping(data, {
id: "regItrId",
groups: ["maiContent", "itrEname", "proName"],
groupLables: ["保养内容:", "仪器:", "实验组:"]
});
state.tableData = nodeList;
state.tableDataKeyList = parentNodeKeyList;
});
</script>
// 初始化 format.ts
export function MultilevelDataGrouping(
data: any[],
{ id, groups, groupLables }: { id: string; groups: string[]; groupLables: string[] }
) {
let TransitData = data;
groups.forEach((groupKey, groupIndex) => {
const groupMap = new Map();
const nodeList: any[] = [];
TransitData.forEach((node: any) => {
const nodeId = node[id];
const nodeGroup = node[groupKey];
if (!groupMap.has(nodeGroup)) {
const length = nodeList.length;
groupMap.set(nodeGroup, length);
const cloneNode: any = { ...node };
cloneNode[id] = groupKey + nodeId ?? length;
cloneNode["children"] = [];
const label = groupLables ? groupLables[groupIndex] : "";
cloneNode["groupsLable"] = label + (nodeGroup ?? "");
nodeList.push(cloneNode);
}
const index = groupMap.get(nodeGroup);
nodeList[index].children.push(node);
});
TransitData = nodeList;
});
return TransitData;
}
主要优化点说明:
类型定义:通过IDataNode接口和IGroupingParams以及IGroupingResult接口的定义,增强了函数的输入和输出的类型安全性。
参数校验:在函数的开始处添加了简单的参数校验逻辑,确保必要的参数存在且有效。
性能考虑:虽然原始算法已经相对高效,对于类型转换和数据处理进行了最小化,这里主要通过增强类型检查和优化代码结构来提升可读性和可维护性,而不是性能。需要注意的是,对于大多数情况,原算法的性能已经是可接受的;如果处理极大规模数据集,可能需要考虑更专门的算法优化。
代码可维护性和可读性:通过使用接口和类型定义,以及合理的参数校验,提升了代码的可维护性和可读性。
// 优化后 format.ts
interface IDataNode {
[id: string]: any; // 使用字符串作为键,值可以是任意类型
children?: IDataNode[]; // 可选的子节点数组
groupsLable?: string; // 可选的节点所属分组标签
}
interface IGroupingParams {
id: string; // 用于标识节点的属性名
groups: string[]; // 用于分组的属性名数组
groupLables?: string[]; // 可选的分组标签数组,对应groups中的每个分组
}
interface IGroupingResult {
nodeList: IDataNode[]; // 分组后的节点数组
parentNodeKeyList: string[]; // 分组后所有父节点的键值数组
}
/**
* 多级数据分组函数
* @param data 要进行分组的数据节点数组,每个节点应包含指定的id属性
* @param params 分组参数,包括需要分组的属性名和可选的分组标签
* @return 返回一个对象,包含分组后的节点数组和父节点键值数组
* @author:
*/
export function MultilevelDataGrouping(data: IDataNode[], params: IGroupingParams): IGroupingResult {
// 校验输入参数
if (!params.id || !Array.isArray(params.groups)) {
throw new Error("Invalid parameters: 'id' and 'groups' are required.");
}
const { id, groups, groupLables = [] } = params;
const parentNodeKeyList: string[] = [];
let TransitData = [...data]; // 用于分组操作的数据副本
// 遍历每个分组条件,依次进行分组
groups.forEach((groupKey, groupIndex) => {
const groupMap = new Map<string, number>(); // 用于映射节点组到其在节点数组中的索引
const nodeList: IDataNode[] = []; // 当前分组的节点数组
// 遍历数据,为每个节点创建或找到对应的分组
TransitData.forEach(node => {
const nodeId = node[id];
const nodeGroup = node[groupKey];
// 如果节点组尚未存在于节点数组中,则添加新节点组
if (!groupMap.has(nodeGroup)) {
const length = nodeList.length;
groupMap.set(nodeGroup, length);
// 克隆节点,并适应分组信息
const cloneNode: IDataNode = { ...node };
cloneNode[id] = `${groupKey}${nodeId ?? length}`;
cloneNode["children"] = [];
cloneNode["groupsLable"] = `${groupLables[groupIndex] ?? ""}${nodeGroup ?? ""}`;
nodeList.push(cloneNode);
parentNodeKeyList.push(cloneNode[id]);
}
// 将节点添加到对应的分组中
const index = groupMap.get(nodeGroup)!;
nodeList[index].children!.push(node);
});
// 更新数据副本为当前分组的节点数组,以进行下一次分组
TransitData = nodeList;
});
// 返回分组结果
return { nodeList: TransitData, parentNodeKeyList };
}