本章主要讨论表格组件有用的一些扩展功能。

8.1 外边框和列边框

表格组件的默认展现形式是只有行边框,没有列边框。其实这个默认的样子很好看,但如果实在想要方格的表现(即需要列边框),那么可以用一下属性来实现:

  • bordered 是否展示外边框和列边框 ```diff
  • bordered `` 单独把属性名称放在那里,他的值就是true`。

    8.2 超宽自动缩略

    有时候数据的内容可能会比较多,多到把所在列撑得过宽,别的列窄的很难看,这时可以给可能超宽的列加上如下属性:
  • ellipsis 超过宽度将自动省略,暂不支持和排序筛选一起使用。

现在我们做个实验性的测试列来看效果。首先定义一个数组

  1. const novel = [
  2. '却听得杨过朗声说道:“今番良晤,豪兴不浅,他日江湖相逢,再当杯酒言欢。咱们就此别过。”',
  3. '说着袍袖一拂,携着小龙女之手,与神雕并肩下山。',
  4. '其时明月在天,清风吹叶,树巅乌鸦呀啊而鸣,郭襄再也忍耐不住,泪珠夺眶而出。',
  5. ]

然后在主数据的columns定义中增加一项内容

  1. {
  2. title:'神雕',
  3. dataIndex: 'id',
  4. ellipsis: true,
  5. renderText: (val: number) => novel[val % 3],
  6. },

现在我们看到新增的这个内容超宽的列自动以缩略的形式显示。把鼠标放到该列的某个表格上的时候,会弹出它完整内容的提示。

image.png

8.3 列表内容筛选

当数据传到前端的时候,有时候用户可能会希望迅速地找到符合一些简单条件的数据,我们一般用表格的“筛选”功能来满足这种要求。

  • 如果要对某一列数据进行筛选,可以使用列的 属性来指定需要筛选菜单的列
  • filters值为 true 时,自动使用 valueEnum 生成菜单(也可以自定义)
  • onFilter 用于筛选当前数据,可以是三种可能
    • 一个如下面代码的筛选函数
    • true 代表使用 ProTable 自带的筛选机制
    • false 时关闭本地筛选而使用远端筛选
  • filterMultiple 用于指定多选和单选。

下面我们给性别列加上筛选的定义

  1. {
  2. title: '性别',
  3. dataIndex: 'gender',
  4. hideInForm: true,
  5. valueEnum: genderEnum,
  6. + filters: true,
  7. + onFilter: (value, record) => record.gender == value,
  8. },

完成后,在列表头的右边可以看到一个典型的筛选用图标,点击可以看到一个带复选按钮的下来菜单,通过这个菜单,用户可以选择只看他期望的数据。
image.png
列表组件支持更复杂的筛选菜单,详细的见官方文档

这里有个很不理想的机制是所有筛选的信息都会通过参数filter调用请求函数,而我们目前在请求函数中尚无法独立识别这种情况,于是每次筛选都会增加一次数据库查询。所以为了防止软件用户随性操作,应尽可能用下一章讨论的查询功能来执行数据筛选。

另外,如果确实需要设计表头的筛选功能,那么设置onFilter为false,因为既然已经在后端按筛选条件执行了查询,那么就不需要再浪费前端资源进行处理了。

8.4 列表内容排序

8.4.1 列表前端排序

列表的前端排序针对的是当前页的内容(因为其它页并没有从后端传过来)。

  • 通过指定列的 sorter 函数即可启动排序按钮。
  • sorter: function(rowA, rowB) { … }, rowA、rowB 为比较的两个行数据。
  • 使用 defaultSortOrder 属性,设置列的默认排序顺序。
  • 表头显示下一次排序的 tooltip 提示, 覆盖 table 中 showSorterTooltip

我们给出生日期列增加排序功能

  1. +import moment from "moment";
  1. {
  2. title: '出生日期',
  3. dataIndex: 'birthday',
  4. valueType: 'date',
  5. + sorter: (a,b,sortOrder) => {
  6. + const prev = moment(a.birthday).toDate().valueOf()
  7. + const next = moment(b.birthday).toDate().valueOf()
  8. +
  9. + return prev - next
  10. + },
  11. + defaultSortOrder: 'descend',
  12. + showSorterTooltip: false,
  13. },

因为我们定义的出生日期是字符串类型,在进行数据比较的时候要把它们转换称数字。下面是选择升序排列的效果image.png
默认时候,定义了排序的列会在升序、降序、取消三个状态中切换。很多时候,我们只需要升序、降序两个状态,为实现这个要求,可以用下面的ProTable属性:

  • sortDirections: [‘ascend’ | ‘descend’]改变每列可用的排序方式,切换排序时按数组内容依次切换,设置在 table props 上时对所有列生效。

你可以通过设置sortDirections:`` ['ascend', 'descend', 'ascend'] 禁止排序恢复到默认状态。

  1. <ProTable<TYPE.Member, TYPE.PageParams>
  2. ...
  3. ...
  4. + sortDirections={['ascend', 'descend', 'ascend']}

8.4.2 列表内容后端排序

和表头筛选时的逻辑一样,不管前端是否做了对排序的处理,用户改变排序状态时都会通过参数sorter调用请求函数,而我们目前在请求函数中尚无法独立识别这种情况,于是每次筛选都会增加一次数据库查询。

这时我们在后端可以采用与面临筛选时不一样的策略,也就是再重新执行查询的时候完全忽略前端传递过来的与排序有关的信息,仍旧按照既往的逻辑执行查询,把具体的查询工作交给前端去完成。当然,如果需要对所有(满足当前查询条件)的数据进行排序后重新分页显示,后端再重新查询的时候就必须考虑到前端传递的排序要求。

首先我们给身份证号也加上排序属性(在这里只是演示后端排序的处理方法,对它排序其实没有什么实际意义)

  1. {
  2. title: '身份证号',
  3. dataIndex: 'identityNumber',
  4. + sorter: true,
  5. renderText: (val: string) =>
  6. `${val.substr(0,3)}***${val.substr(val.length-3,3)}`,
  7. },

从代码中可见我们只给sorter设置为true而没有定义排序函数,这样前端不会去做排序操作而只是把排序信息传递到后端。

为传递排序信息,要修改响应函数的参数,下面是services/api/member.tsqueryAllMember的定义(到这里我们就明白了当初定义sorter参数的用途)

  1. export async function queryAllMember(
  2. params: TYPE.PageParams & {
  3. pageSize?: number | undefined;
  4. current?: number | undefined;
  5. keyword?: string | undefined;
  6. },
  7. sort: Record<string, SortOrder>,
  8. filter: Record<string, React.ReactText[]>,
  9. ) {
  10. return request<TYPE.MemberList>('/api/member/queryAll', {
  11. method: 'GET',
  12. params: {
  13. ...params,
  14. sort,
  15. filter
  16. },
  17. });
  18. }

然后修改mock/member.ts来看一下传过来的参数

  1. function queryAll(req: Request, res: Response, u: string) {
  2. const { current = 1, pageSize = 10 } = req.query;
  3. + const sort = JSON.parse(req.query.sort || ('{}' as any));
  4. +
  5. + console.log('Current sorter is ', JSON.stringify(sort, null, 2))

现在我们反复点击身份证号和出生日期的表头,不管是从前端页面还是在控制台,都能看到每次都只有一列参与排序
image.png
如果想要多列的组合排序需要用到sorter属性的如下特征:

  • sortermultiple 字段配置多列排序优先级
  • sorter.compare 配置排序逻辑,你可以通过不设置该函数只启动多列排序的交互形式

现在分别修改这两列的定义

  1. {
  2. title: '身份证号',
  3. dataIndex: 'identityNumber',
  4. - sorter: true,
  5. + sorter: {
  6. + multiple: 2,
  7. + },
  8. renderText: (val: string) =>
  9. `${val.substr(0,3)}***${val.substr(val.length-3,3)}`,
  10. },
  1. {
  2. title: '出生日期',
  3. dataIndex: 'birthday',
  4. valueType: 'date',
  5. - sorter: (a,b,sortOrder) => {
  6. - const prev = moment(a.birthday).toDate().valueOf()
  7. - const next = moment(b.birthday).toDate().valueOf()
  8. -
  9. - return prev - next
  10. - },
  11. + sorter: {
  12. + multiple: 1,
  13. + compare: (a,b,sortOrder) => {
  14. + const prev = moment(a.birthday).toDate().valueOf()
  15. + const next = moment(b.birthday).toDate().valueOf()
  16. +
  17. + return prev - next
  18. + },
  19. + },
  20. defaultSortOrder: 'descend',
  21. showSorterTooltip: false,
  22. },

改完以后,在前端可以看到两列的排序标记可以同时出现(当然并不会其组合效果,因为我们没有在前端定义身份证号的排序方法。image.png
控制台的输出也能看到后端收到了组合的排序信息
image.png
这里有几条重要的提示:

  • 不管前端是否定义了排序函数,只要存在sorter属性,后端就能收到排序信息
  • 从控制台信息可以看到,前端sorter.multiple的定义并不会影响传递到后端的内容,所有后端应该是根据业务逻辑的需求定义排序组合中不同字段的优先级,而不应该根据收到的排序数据的顺序做决定
  • 本节的mock代码只是把排序信息输出在控制台上,没有做任何的实际处理,所以看不到按身份证号排序的效果

    8.5 自定义行渲染

    有时候,项目实践中会希望给特定的数据行做不同的渲染。

    8.5.1 按条件渲染

    如果希望根据一定的条件来定义行渲染的方式,可以使用表格的rowClassName属性:rowClassName= { (record) => {} }。在函数内部根据数据内容按预订的逻辑条件返回不同的css(或less)文件中的风格定义名称。

8.5.2 重载默认风格

如果想重载默认风格的定义,可以在css(或less)文件中定义.ant-table-row,用新定义的风格属性来覆盖默认的。还可以用伪类选择器来实现诸如“奇偶行不同背景”这样的效果。

8.6 定义表头提示

给表格列定义增加tooltip属性,会在表头名称之后展示一个 图标,鼠标指上去会提示定义的信息

  1. {
  2. title:'神雕',
  3. dataIndex: 'id',
  4. ellipsis: true,
  5. renderText: (val: number) => novel[val % 3],
  6. + tooltip: '飞雪连天射白鹿,笑书神侠倚碧鸳',
  7. },

下面是效果
image.png

8.7 数据复制按钮

尽管用户可以直接用鼠标选中表格的内容然后执行复制,但我们可以提供一个更加方便的方式——直接在特定的数据列的单元格里放置一个复制按钮,他对于那些超宽自动缩略的列尤其方便。

我们给神雕和身份证号两列都加上copyable: true的属性

  1. {
  2. title:'神雕',
  3. dataIndex: 'id',
  4. ellipsis: true,
  5. + copyable: true,
  6. tooltip: '飞雪连天射白鹿,笑书神侠倚碧鸳',
  7. renderText: (val: number) => novel[val % 3],
  8. },
  9. {
  10. title: '身份证号',
  11. dataIndex: 'identityNumber',
  12. copyable: true,
  13. + sorter: {
  14. multiple: 2,
  15. },
  16. renderText: (val: string) =>
  17. `${val.substr(0,3)}***${val.substr(val.length-3,3)}`,
  18. },

可以看到如下的效果
image.png
点击某个代表复制的图标就可以把对应的内容复制到剪贴板上。通过点击不同的数据,你可以了解到下面几个特性

  • 复制成功后,图标会短时间变成一个对号图标来提示成功的状态
  • 因超过缩略而隐藏的内容会全部被复制
  • 只能复制到自定义渲染后的数据内容(如脱敏后的身份证号)

    8.8 总结

    表格组件的属性非常丰富,本章只是讲解了一些实践中常用的内容,更多的特性可以去看下面两个官方文档(里面有大量的实力):

  • Ant Design Table https://ant.design/components/table-cn/

  • Ant Design ProTable https://procomponents.ant.design/components/table

需要说明的是,一切设计都是为了满足业务需求而完成的,不要因为个人喜好做“炫技”性质的过度设计

版权说明:本文由北京朗思云网科技股份有限公司原创,向互联网开放全部内容但保留所有权力。