设计开发一个data-table

news2024/11/16 23:35:28

前言

在日常开发中,数据表格扮演着至关重要的角色。它以结构化的形式展现信息,使数据清晰易懂,开发者基于此类表格可以对其进行拓展和复用,本篇文章我们将循序渐进地介绍如何构建一个功能完善、易于使用的表格组件,并探讨其背后的设计理念和最佳实践

阅读之前,你可能需要了解以下技术栈:TS + React + ​Antd ProComponents

是什么?

data-table(数据表格)是一种常见的用户界面组件,用于展示和操作数据集合,由于后端的数据与前端某些表格深度绑定,所以需要通过此类数据表格实现对应的诉求。

一个完整的数据功能表格通常有以下常用功能

  1. 数据展示:以表格的形式展示数据集,其中数据通常按行和列组织。

  2. 动态数据绑定:能够绑定到动态数据源,实时展示数据变化。

  3. 排序:允许用户根据一列或多列对数据进行排序。

  4. 筛选:提供筛选器,使用户可以基于特定条件筛选数据。

  5. 分页:对于大量数据,提供分页功能以便用户可以逐页浏览。

  6. 交互操作:支持用户对数据进行操作,如编辑、删除、添加等。

  7. 自定义列定义:允许开发者自定义表格的每一列,包括列的标题、数据索引、宽度、格式化等。

  8. 响应式设计:能够适应不同的屏幕尺寸和设备。

  9. 状态管理:跟踪和展示数据的状态,如是否被选中、是否处于活动状态等。

  10. 工具栏:可能包括额外的工具栏,提供导出、打印、自定义列可见性等高级功能。

为什么我们会需要如此复杂的功能表格?或者说我们如何写好一个表格?

为什么?

参考类似JavaScript中继承的概念,通过定义一个基类(或基组件)来设定通用的属性和方法,然后根据特定需求对其进行扩展,后续对其属性进行拓展

设计数据表格的一些关键因素有以下几点:

  1. 设计一个基类数据表格,使其包含通用的功能和属性,以便在不同的场景下复用

  2. 将数据表格分解为多个模块或组件,如表头、表体、分页器等,每个模块负责特定的功能

  3. 允许通过配置来定义表格的行为和外观,而不是硬编码,增加了灵活性并简化了扩展

  4. 将通用的属性和方法封装在基类中,如数据加载、行选择、单元格格式化等

  5. 集成数据验证和错误处理机制,确保数据的准确性和完整性

  6. 管理好不同版本的数据表格,确保向后兼容,减少升级的复杂性

基于实际情况和表格数据类型,使得我们系统偏向于使用数据表格的输出形式

怎么做?

说了这么多,如何规范实现一个data-table

先来看看antd提供的高级表格组件(Antd ProComponents-ProTable:https://pro-components.antdigital.dev/components/table

ProTable 的诞生是为了解决项目中需要写很多 table 的样板代码的问题,所以在其中做了封装了很多常用的逻辑。这些封装可以简单的分类为预设行为与预设逻辑。

设计方向很简单,基于antd提供的如此便捷的组件,我们可以通过上层传递的参数来达到表格多态效果,提高表格的可用性和灵活性

下面代码中的ProTable是隶属于公司的sz-components库中,这个库包含了antd的ProTable,并对其做了修改

以近期迭代的实际场景为例:

在resource.types中定义了该模块下的ts types类型

将后端文档中的表格结构统一存放在TableData类型中,用于限制和提示相关类型结构

将与枚举相关的类型值存放在Enum中

接着我们创建相关的data-table,来看看代码结构

export const ResourceExerciseDataTable: React.FC<DataTableProps<IResourceExercise.TableData>> = props => {
  const { AuthorityMap, ManualActivationMap, ProductSalesMap, StateMap } = IResourceExercise.Enum;
  const { hideInSearch, hideInTable, extraColumns, ...rest } = props;
  const { isUcc, dataSourceFrom } = useAppVariable();
  const { packageExerciseApi } = useAppApi();
  const columns = useMemo(() => {
    const cols: ProColumns<IResourceExercise.TableData>[] = [
      {
        dataIndex: 'packageName',
        title: '习题包名称',
        ellipsis: true,
        fixed: 'left',
        width: 140,
      },
      {
        dataIndex: 'versionDate',
        title: '资源包版本',
        hideInSearch: true,
        width: 120,
        render: (text, record) => {
          const { versionDate, versionNum } = record;
          return <div>{formatVersion({ versionDate, versionNum }).version}</div>;
        },
      },
      {
        dataIndex: 'permission',
        title: '可用权限',
        valueEnum: AuthorityMap,
        width: 80,
      },
      {
        dataIndex: 'needActive',
        title: '需手动开通后使用',
        valueEnum: ManualActivationMap,
        width: 120,
      },
      {
        dataIndex: 'asProduct',
        title: '是否作为产品售卖',
        valueEnum: ProductSalesMap,
        width: 120,
      },
      {
        dataIndex: 'exerciseCount',
        title: '总包含题目数',
        hideInSearch: true,
        width: 120,
      },
      {
        dataIndex: 'state',
        title: '状态',
        fieldProps: {
          mode: 'multiple',
        },
        valueEnum: StateMap,
        width: 80,
      },
      {
        dataIndex: 'createUserName',
        title: '版本作者',
        hideInSearch: true,
        ellipsis: true,
        width: 120,
      },
      {
        dataIndex: 'createTime',
        title: '创建时间',
        ellipsis: true,
        valueType: 'dateTime',
        searchType: 'dateRange',
        width: 120,
      },
      {
        dataIndex: 'firstShelfTime',
        title: '首次上架时间',
        ellipsis: true,
        valueType: 'dateTime',
        searchType: 'dateRange',
        width: 120,
      },
      {
        dataIndex: 'shelfTime',
        title: '最新上架时间',
        ellipsis: true,
        valueType: 'dateTime',
        searchType: 'dateRange',
        width: 120,
      },
    ];

    return DataTableUtils.resolve(cols, {
      hideInTable,
      hideInSearch,
      extraColumns,
    });
  }, [isUcc, dataSourceFrom, extraColumns, hideInSearch, hideInTable]);
  const itemValueToNum = (list: string[]) => list.map(item => Number(item));
  /**
   * 获取习题包分页列表
   * @param param
   * @returns
   */
  const handleTableSearch = async (param: any) => {
    const data = await packageExerciseApi.getExerciseList(
      Object.assign({}, omit(param, 'state', 'createTime', 'firstShelfTime', 'shelfTime', 'permission', 'needActive', 'asProduct'), {
        pageNo: param.current,
        permissionList: param.permission ? itemValueToNum([param.permission]) : [],
        needActiveList: param.needActive ? itemValueToNum([param.needActive]) : [],
        asProductList: param.asProduct ? itemValueToNum([param.asProduct]) : [],
        stateList: param.state,
        ...timeCollect(param),
      }),
    );
    return {
      data: data.records,
      total: data.total,
      success: true,
    };
  };

  return (
    <ProTable
      scroll={{ x: '100%', y: window.innerHeight * 0.6 }}
      search={{ labelWidth: 100 }}
      {...rest}
      queryKey={[IResourceExercise.key, 'table']}
      rowKey="id"
      columns={columns}
      request={rest.dataSource ? undefined : rest.request ?? handleTableSearch}
    />
  );
};

首先限制Props类型为DataTableProps<IResourceExercise.TableData>以便上层更好控制表格参数

表头部分使用固定写法是因为:数据展示及数据查询条件与后端接口强关联,如果接口层发生数据或功能模块发生变化,需要调整当前data-table

接着通过DataTableUtils中的表格函数对外层的参数做处理,来看看resolve函数的写法

  static resolve<T>(
    list: ProColumns<T>[],
    options?: {
      hideInSearch?: string[];
      hideInTable?: string[];
      extraColumns?: ProColumns<T>[];
    },
  ): ProColumns<T>[] {
    const { hideInSearch, hideInTable, extraColumns } = options ?? {};
    let cols = list.concat();
    cols.forEach(col => {
      if (hideInSearch && hideInSearch.length > 0) {
        if (hideInSearch.includes(col.dataIndex as any)) {
          col.hideInSearch = true;
        }
      }
      if (hideInTable && hideInTable.length > 0) {
        if (hideInTable.includes(col.dataIndex as any)) {
          col.hideInTable = true;
        }
      }
    });

    if (extraColumns) {
      cols = cols.concat(extraColumns);
    }

    cols.sort((a, b) => (a.index ?? 0) - (b.index ?? 0));

    return cols.map(item => omit(item, ['index']));
  }

hideInSearch, hideInTable, extraColumns分别代表上级对表格搜索栏,表格表头,列数据的控制,其中参数引入了index的概念,通过index来控制在哪一列中插入新的列,打个比方:在data-table中使用index对表格进行排序,思考下面的ProColumns代码

[{
        dataIndex: 'packageName',
        title: '习题包名称',
        ellipsis: true,
        fixed: 'left',
        width: 140,
        index: 1,
      },
      {
        dataIndex: 'versionDate',
        title: '资源包版本',
        hideInSearch: true,
        width: 120,
        index: 2,
        render: (text, record) => {
          const { versionDate, versionNum } = record;
          return <div>{formatVersion({ versionDate, versionNum }).version}</div>;
        },
      },
      {
        dataIndex: 'permission',
        title: '可用权限',
        valueEnum: AuthorityMap,
        width: 80,
        index: 3
      }]

如果我想在第二列后面插入一行,只需要通过extraColumns参数传入列的index>2&& <3的数字即可,思考以下代码

{
        dataIndex: 'line',
        title: '新插入的列',
        width: 140,
        index: 2.1,
}

以上代码可以在资源包和权限中间插入新的列

说完resolve函数,接下来就是表格自带的request函数,request是表格提供的请求函数,在第一次加载时可以通过该函数将分页,工具栏过滤等参数通过调用该函数将参数格式化,但是一般推荐在search.transform中将搜索值进行格式化

ProTable提供一个params参数,传入该参数可以拓展request函数的参数,方便在外层维护使用

最后需要分享的是queryKey的概念,这个是在公共组件封装的一个用法,其借鉴的是react-query的queryKey的概念,使用该字段可以使表格数据对请求函数(request)深度绑定,在其他地方使用时可以直接使用以下代码实现表格数据刷新的效果

const { refetch } = ProTable.useApi()
refetch([IResourceExercise.key, 'table'])

总结

以上就是文章全部内容了,本文详细介绍了如何构建一个功能完善、易于使用的data-table(数据表格)组件,并探讨了其设计理念和最佳实践。感谢看到最后,如果觉得有所帮助,还望三连支持一下,感谢

相关文章:

https://procomponents.ant.design/components/table

GitHub - TanStack/query: 🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2052936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Coze插件发布!PDF转Markdown功能便捷集成,打造你的专属智能体

近日&#xff0c;TextIn开发的PDF转Markdown插件正式上架Coze。 在扣子搜索“pdf转markdown”&#xff0c;或在Coze搜索“pdf2markdown” 即可找到插件&#xff0c;在你的专属智能体中便捷使用文档解析功能。 如果想测试解析插件在你需要的场景下表现如何&#xff0c;可以直接…

数据链路层 I(组帧、差错控制)【★★★★★】

&#xff08;★★&#xff09;代表非常重要的知识点&#xff0c;&#xff08;★&#xff09;代表重要的知识点。 为了把主要精力放在点对点信道的数据链路层协议上&#xff0c;可以采用下图&#xff08;a&#xff09;所示的三层模型。在这种三层模型中&#xff0c;不管在哪一段…

09.直线图

9. 直线图 9.1 普通直线图 self.add_heading("直线图", level1)self.add_heading("普通直线图", level2)# 数据源data [10, 20]data2 [40, 60]data3 [80,90]self.add_quick_chart(data[[2018,2024],data, data2, data3],series[pool1使用情况(TiB),poo…

大数据学习-Spark基础入门

一、Spark是什么&#xff1f; Stack Overflow的数据可以看出&#xff0c;2015年开始Spark每月的问题提交数量已经超越Hadoop&#xff0c;而2018年Spark Python版本的API PySpark每月的问题提交数量也已超过Hadoop。2019年排名Spark第一&#xff0c;PySpark第二&#xff1b;而十…

【三维目标检测模型】ImVoxelNet

【版权声明】本文为博主原创文章&#xff0c;未经博主允许严禁转载&#xff0c;我们会定期进行侵权检索。 参考书籍&#xff1a;《人工智能点云处理及深度学习算法》 ImVoxelNet是一种基于RGB图像的三维目标检测模型&#xff0c;发表在WACV 2022 《ImVoxelNet: Image to Vo…

记事本/软件商店/xbox打不开(不会丢失数据)(保姆级教程)

软件商店的安装 &#xff1a; 在某些情况下&#xff0c;系统更新可能导致本地账户和微软账户出现问题&#xff0c; 使得更新似乎只影响到了一个账户&#xff0c;而非我当前使用的账户。 这会导致我环境中的某些Windows自带应用&#xff0c;如微软商店、电影与电视、画图、记事…

鸿蒙开发入门day10-组件导航

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;还请三连支持一波哇ヾ(&#xff20;^∇^&#xff20;)ノ&#xff09; 目录 组件导航 (Navigation) 设置页面显示模式 设置标题栏模式 设置菜…

论文降重切勿“本末倒置”!一文教你如何有效降重

【SciencePub学术】本期热点 论文降重 原创性是评价论文质量的重要标准之一。许多读者在撰写论文都担心重复率过高的问题。 本篇文章旨在分享有效降低学术论文重复率的策略&#xff0c;从而促进学术诚信&#xff0c;提高研究工作的创新性和学术价值。通过一系列实用的写作技巧…

高可用集群keepalived 原理+实战

keepalived 1.高可用集群1.1简介1.2原理1.3 集群类型1.4实现高可用1.5VRRP&#xff1a;Virtual Router Redundancy Protocol1.5.1 VRRP 相关术语1.5.2VRRP 相关技术 2.实验2.1keepalived环境部署2.2抢占模式和非抢占模式2.2.1非抢占模式2.2.2抢占延迟模式 preempt_delay 2.3VIP…

MATLAB 手动实现投影密度法分割建筑物立面 (73)

专栏文章往期回顾,包含本文章 MATLAB 手动实现投影密度法分割建筑物立面 (73) 一、算法介绍二、算法实现1.代码2.效果总结一、算法介绍 从原始点云中,自动分割提取建筑物立面点云用于立面绘图,可以减少人为操作流程。这里从0开始,手动实现一种基于投影密度法的建筑物立…

Unity动画模块 之 3D模型导入基础设置Animation页签

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 基础设置就截图看看辣&#xff0c;只有实际应用中才会使用到&#xff0c;现在死记硬背也不可能记住 1.基础设置 笔记来…

STM32常用C语言知识总结

目录 一、引言 二、C 语言基础 1.数据类型 2.变量与常量 3.控制结构 4.数组与指针 5.字符串 6. extern变量声明 7.内存管理 三、STM32 中的 C 语言特性 1.位操作 2.寄存器操作 一、引言 STM32 作为一款广泛应用的微控制器&#xff0c;其开发离不开 C 语言的支持。C …

编写日志文件

精灵程序 #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <sys/stat.h> #include…

vue3 响应式 API:ref() 和 reactive()

在 Vue 3 中&#xff0c;响应式系统是其核心特性之一&#xff0c;它使得数据的变化能够自动触发视图的更新。 官方文档&#xff1a; 响应式 API&#xff1a;核心 要更好地了解响应式 API&#xff0c;推荐阅读官方指南中的章节&#xff1a; 响应式基础 (with the API preference…

【STM32单片机_(HAL库)】3-2-1【中断EXTI】【电动车报警器项目】震动点灯

1.硬件 STM32单片机最小系统LED灯模块震动传感器模块 2.软件 exti驱动文件添加GPIO常用函数中断配置流程main.c程序 #include "sys.h" #include "delay.h" #include "led.h" #include "exti.h"int main(void) {HAL_Init(); …

Linux常用命令 ---- rmdir 命令[删除一个空目录]

rmdir 命令 功能&#xff1a;删除一个空目录 我们使用 mkdir 命令创建一个名为 test 空文件夹&#xff0c;如下图所示。 现在使用 rmdir 命令将 test 文件夹进行删除&#xff0c;如下图所示。 注意&#xff1a;rmdir 命令只能删除一个空目录&#xff0c;如果这个目录中有其他文…

【云原生】Kubernetes中的名称空间和资源配额详细用法与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

医疗器械维修其实没有想的那么难

在很多人的印象中&#xff0c;医疗器械维修是一项极其复杂且神秘的工作&#xff0c;似乎只有专业的技术精英才能胜任。然而&#xff0c;事实并非如此&#xff0c;医疗器械维修其实并没有想象中那么难。 首先&#xff0c;现代医疗器械的设计越来越注重人性化和可维护性。制造商…

迎接开学新生活!高三开学必备物品推荐~

步入高三&#xff0c;意味着每一位学子都将面临人生中重要的转折点——高考。为了帮助高三学生们准备充分&#xff0c;让学习生活之路更加顺畅。今天小编综合了实用性、性价比以及学生需求的考量&#xff0c;精选了一系列必备物品&#xff0c;旨在为高三学生创造一个更为舒适、…

ICMP互联网控制报文协议

ICMP 互联网控制报文协议 ICMP &#xff08; Internet Control Message Protocol &#xff0c;也就是互联⽹控制报⽂协议&#xff09;。 ⽹络包在复杂的⽹络传输环境⾥&#xff0c;常常会遇到各种问题。 当遇到问题的时候&#xff0c;总不能死个不明不⽩&#xff0c;没头没脑…