[email protected] 表格多列联动排序

表格有一个常用的功能就是排序功能,排序功能依据谁排分为两种:

  1. 前端排序(简单的序号等)

  2. 后端排序(复杂点的排序,一般依赖数据库的排序功能)

多个表头排序依据之间有没有分为:

  1. 单列排序(互不影响)

  2. 多列排序(根据点击顺序联动排序,注意 Antd3 不支持多列排序)

一、掌握基础

以下摘录 Antd 官网,静静的看一遍全部搞懂:

  • 筛选和排序

对某一列数据进行筛选,使用列的 filters 属性来指定需要筛选菜单的列,onFilter 用于筛选当前数据,filterMultiple 用于指定筛选条件是多选和单选。(筛选不是排序不看)

重点来了:

对某一列数据进行排序,通过指定列的 sorter 函数即可启动排序按钮。sorter: function(rowA, rowB) { ... }, rowA、rowB 为比较的两个行数据。(亲测排序条件只要为真,排序就会生效,不一定非要为函数)

sortDirections: ['ascend' | 'descend']改变每列可用的排序方式,切换排序时按数组内容依次切换,设置在 table props 上时对所有列生效。(sortDirections: ['ascend'] 这样设置只会有一个升序排序)

使用 defaultSortOrder 属性,设置列的默认排序顺序。

  • 多列排序

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

格式如下:

{
    title: 'Chinese Score',
    dataIndex: 'chinese',
    sorter: {
        compare: (a, b) => a.chinese - b.chinese,
        multiple: 3,
    },
},

multiple 表示多列排序的顺序,纯前端排序的话,要注意 multiple 的值,比如我点了 multiple: 1, 的排序,然后点了 multiple: 2, 的排序,最终结果会这样显示,先按 1 条件排,拍完之后里面重的内容,再按 2 的条件排列。

  • 可控的筛选和排序

使用受控属性对筛选和排序状态进行控制。

  1. columns 中定义了 filteredValue 和 sortOrder 属性即视为受控模式。(筛选不是排序不看)
  2. 只支持同时对一列进行排序,请保证只有一列的 sortOrder 属性是生效的。(重点:这句话是不对的,这句话成立的条件是,单列可控排序。多列可控排序多个 sortOrder 属性可以同时生效
  3. 务必指定 column.key(这个是 React 需要的 key,如果已经设置了唯一的 dataIndex,可以忽略这个属性,所以不用排序的时候我们一般都不会特意指定 columnKey 的值,但是这里为什么要求务必指定呢?看官网可控组件的例子,它是这么写的sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order, 通过 columnKey 来判断某一列的排序,要求指定 columnKey 其实就是为了可控组件排序)。

补充第三点的内容:1. 正常情况下使用 Antd 的 Table,在给列数据的时候,官方建议指定 dataIndexcolumnKey ,这两个完全可以就写一个,未必要这个麻烦,当然官方说明在已经设置了 dataIndex 可以省略 columnKey。2. 至于可控排序务必指定 columnKey 也是没有必要,因为当你不指定的时候,点击排序 onChange 的 sorter 参数内容是这样的 {column: {…}, order: "ascend", field: "name", columnKey: "name"} ,完全没影响,而且 filed 也可以替代 columnKey。

总结: columnKey 完全没有指定的必要,可控排序通过 filed 替代 columnKey 变量。

写到下面才发现 columnKey 和 key 搞混了。真实的结论应该是:不使用可控排序,在指定 dataIndex 情况下可以省略给 React 使用的 key ,使用可控排序,请指定 key ,这样 onChange 函数 sorter 里面的 columnKey 才会有值,再去控制 sorterOrder

  • onChange 事件
    触发条件:分页、排序、筛选变化时触发,Function(pagination, filters, sorter, extra: { currentDataSource: [] }),前三个参数好理解,最后一个参数 extra 表示当前表格的数据,也就是 dataSource 的内容。

好了,以上内容搞懂,我们来看看多列可控排序配合分页出现的小 bug(我认为是 bug,可能官网就是这样设计的)。

二、多列可控排序配合分页出现的小 bug

先给出项目代码:

import React , { Component , Fragment } from 'react';
import ReactDOM from "react-dom";
import "antd/dist/antd.min.css";
import { Table , Space , Button , Input } from 'antd';



export default class App extends Component {
  constructor(props){
      super(props);
      this.state = {
          pageSize: 20
      };
      this.onChange = this.onChange.bind(this);
      this.clearSorterMulti = this.clearSorterMulti.bind(this);
      this.getQuery = this.getQuery.bind(this);
  }

  getQuery(){
      console.log("getQuery");
  }

  clearSorterMulti(){
      console.log("clearSorterMulti");
  }

  onChange(pagination, filters, sorter, extra) {
      const { pageSize } = pagination;
      this.setState({
          pageSize
      })
      console.log(sorter);
  }
  render() {
      const columns = [
          {
              title: 'Name',
              dataIndex: 'name',
              key: 'name',
              sorter: {
                  multiple: 0
              },
              sortOrder: false,
          },
          {
              title: 'Age',
              dataIndex: 'age',
              key: 'age',
              sorter: {
                  multiple: 1
              },
              sortOrder: false,
          },
          {
              title: 'Address',
              dataIndex: 'address',
              key: 'address',
              sorter: {
                  multiple: 2
              },
              sortOrder: false,
          },
      ];
      const data = new Array(100).fill(Math.random().toFixed(3)).map((item, index) => (
          {
              key: index,
              name: item,
              age: item,
              address: item,
          }
      ))

      return (
          
              
                  
                  
                  
              
              
      )
  }
}


ReactDOM.render(
  
, document.getElementById('root'));

效果图应该是这样:


接下来我们只关心改变 sortOrder 的值,点击翻页 onChange 函数 sorter 参数的变化:

  1. 当没有 sortOrder 的时候,也就是不可控排序

在没有点击表头排序的情况下,点击分页,点击翻页 onChange 函数 sorter 参数是个空对象:{}。注意 ⚠️ 如果点击表头 onChange 函数 sorter 参数是正常的。

  1. sortOrder 的值为假

在没有点击表头排序的情况下,点击分页,点击翻页 onChange 函数 sorter 参数是第一个可控排序的对象:{column: undefined, order: false, field: "name", columnKey: "name"}

  1. sortOrder 的值为真

在没有点击表头排序的情况下,点击分页,点击翻页 onChange 函数 sorter 参数是所有 sorterOrder 为真的集合(如果只有一个 sorterOrder 为真,sorter 参数是一个对象,如果有多个 sorterOrder 为真, sorter 参数是一个包含 sorterOrder 为 true 所有对象的一个数组)

当我们设置所有的 sorterOrder 为真打印出来的 sorter 如下:

[
    {"column": {"title": "Name","dataIndex": "name", "key": "name", "sorter": { "multiple": 0 }, "sortOrder": true }, "order": true, "field": "name", "columnKey": "name" }, 
    { "column": { "title": "Age", "dataIndex": "age", "key": "age", "sorter": { "multiple": 1 }, "sortOrder": true }, "order": true, "field": "age", "columnKey": "age" }, 
    { "column": { "title": "Address", "dataIndex": "address", "key": "address", "sorter": { "multiple": 2 }, "sortOrder": true }, "order": true, "field": "address", "columnKey": "address" } 
]

补充上面的数据我是如何复制过来的?首先:更改 console.log(window.a = sorter); ,然后在控制台输出一下变量 a,再在控制台运行代码:copy($_) 把代码复制到剪切板,回到 VSCode 里通过快捷键 ctrl+j压平。

  1. sortOrder 其他情况分析
  • onChange 函数 sorter能打印出来结果的前提是 sortOrder 为真,但是 sortOrder 必须为 "ascend""descend" 排序才能生效。

  • 单列排序的时候 onChange 函数 sorter 永远是个对象。

  • 多列排序的时候,只点击了一次排序表头, onChange 函数 sorter 是个对象,点击了多个排序表头,onChange 函数 sorter 是个数组。

三、对接后台进行多列顺序排序

多列就不用多讲了,主要是顺序,我们要求最后点击排序的权重最低,很明显我们要维护一个数组。这个数组大概长成这样:[NAME_ASC,AGE_DESC,ADDRESS_DESC] ,演示排序字段如下:

/* 
    NAME_ASC    名字正序
    NAME_DESC   名字倒序
    AGE_ASC
    AGE_DESC
    ADDRESS_ASC
    ADDRESS_DESC
*/

后台排序发送接口是 query 形式的,但不是 a=descend&b=ascend 形式,而是直接发送一个数组,直接表现结果为:sortKey=a,b,c,d 。两种形式大同小异,没啥区别。

先把难点给讲了:难点就是维护排序的数组,何时增,何时减,何时替换。

  • 何时增? 排序数组的长度大于我们维护排序数组的长度,是增加查询条件。

  • 何时替换?排序数组的长度等于我们维护排序数组的长度,是替换查询条件。

  • 何时减?排序数组的长度小于我们维护排序数组的长度,是减少查询条件。

除此之外,你会发现开头我们演示页面上带了两个按钮,表示查询带上排序条件,重置清空排序条件。

源代码:

import React, { Component, Fragment } from 'react';
import ReactDOM from "react-dom";
import "antd/dist/antd.min.css";
import { Table, Space, Button, Input } from 'antd';


/* 
    NAME_ASC    名字正序
    NAME_DESC   名字倒序
    AGE_ASC
    AGE_DESC
    ADDRESS_ASC
    ADDRESS_DESC
*/
export default class App extends Component {
    constructor(props) {
        super(props);
        this.state = {
            pageSize: 20,
            /* 顺序排列的数组 */
            sorterKey: [],
            sorterArr: [],
        };
        this.onChange = this.onChange.bind(this);
        this.clearSorterMulti = this.clearSorterMulti.bind(this);
        this.getQuery = this.getQuery.bind(this);
    }

    getQuery() {
        console.log("getQuery")

        /*此处携带 sorterKey 和 input 输入的查询条件,发送Ajax然后setState更新 */
    }

    clearSorterMulti() {

        /*此处发Ajax然后setState更新  */

        this.setState({
            sorterKey: [],
            sorterArr: [],
        });
        
    }
    addReplaceQuery( queryName, sorterKey, sortOrder ){
        sorterKey = sorterKey.filter( item=>!item.includes(queryName))
        /* 最后一次取消的时候,sorter随机返回 sorterKey置空*/
        sortOrder.order ? sorterKey.push( sortOrder.order === "ascend" ? `${queryName}ASC` : `${queryName}DESC` ) : (sorterKey = []);
        console.log(sorterKey)
        return sorterKey;
    }
    onChange(pagination, filters, sorter, extra) {
        console.log(sorter)
        /* sorter可能是个组数也可能不是个数组,我们统一按数组处理 */
        const sorterArr = Array.isArray(sorter) ? sorter : [ sorter ];
        let sorterKey = JSON.parse(JSON.stringify(this.state.sorterKey));
        const { pageSize } = pagination;

        /* 排序数组sorterKey */
        /* 增加替换条件逻辑 */
        if(sorterArr.length >= sorterKey.length){
            /* 获取最新一次排序的对象 */
            const sortOrder = sorterArr[ sorterArr.length - 1 ]; 
            switch(sortOrder && sortOrder.columnKey){
                case "NAME":
                    sorterKey = this.addReplaceQuery( "NAME_", sorterKey, sortOrder);
                    break;
                case "AGE":
                    sorterKey = this.addReplaceQuery( "AGE_", sorterKey, sortOrder);
                    break;
                case "ADDRESS":
                    sorterKey = this.addReplaceQuery( "ADDRESS_", sorterKey, sortOrder);
                    break;
                default:
                    break;
            }
            
        }else{
            /* 减条件逻辑 */
            const arr = [];
            sorterArr.forEach(item=>sorterKey.forEach((key,idx)=> key.includes(item.columnKey) && arr.push(key) ));
            sorterKey = arr;
        }


        /*此处发Ajax然后setState更新  */


        this.setState({
            pageSize,
            sorterArr,
            sorterKey,
        })

        
    }
    render() {
        const { sorterArr = [] } =  this.state;
        /* sorterArr 是个数组,处理下格式*/
        const sorterInfo = {};
        sorterArr.forEach((item,index)=>item && (sorterInfo[item["field"]] = item["field"]) && (sorterInfo[item[["columnKey"]]] = item["order"]));

        const columns = [
            {
                title: 'Name',
                dataIndex: 'name',
                key: 'NAME',
                sorter: {
                    multiple: 0
                },
                sortOrder: sorterInfo.name === "name" && sorterInfo.NAME,
            },
            {
                title: 'Age',
                dataIndex: 'age',
                key: 'AGE',
                sorter: {
                    multiple: 1
                },
                sortOrder: sorterInfo.age === "age" && sorterInfo.AGE,
            },
            {
                title: 'Address',
                dataIndex: 'address',
                key: 'ADDRESS',
                sorter: {
                    multiple: 2
                },
                sortOrder: sorterInfo.address === "address" && sorterInfo.ADDRESS,
            },
        ];
        const data = new Array(100).fill(Math.random().toFixed(3)).map((item, index) => (
            {
                key: index,
                name: item,
                age: item,
                address: item,
            }
        ))

        return (
            
                
                    
                    
                    
                
                
) } } ReactDOM.render(
, document.getElementById('root'));

总结:现在捋一捋思路,我感觉逻辑变的更加清晰了,我第一次在项目中写的挺乱的,我还记得测试测出问题,我脑子都懵了,愣是不明白删除查询条件逻辑错误,我写的是既然最新点击是数组的最后一个值,那我删除的时候,就直接数组 pop 好了。这个逻辑最大的问题就是点击最新的排序不能停,直至它被删除,但是这有个问题就是,我依次点击 A、B、C 三列进行排序,A 现在是降序,再点一次就排序就失效了,但是最后一次点击的是 C 。这时我点击 A 就会 pop 了 C 的排序。当时愣是没检查出来,而且最奇葩的是我连 if 语句都不会写了。我记得当时是这么写的 if -else ,惊呆了我的前端小伙伴:

if(query.queryData.queryAction === 'search'){
    //这里发生了什么 **  **
}else{
    if(sortKeys.length <= 1){
        sortKeys.pop();
    }else{
        const sorterRouter = {
            PROMOTION_BEGIN_DATE_ASC: 'beginDate',
            PROMOTION_BEGIN_DATE_DESC: 'beginDate',
            PROMOTION_END_DATE_ASC: 'endDate',
            PROMOTION_END_DATE_DESC: 'endDate',
            PROMOTION_TYPE_ASC: 'type',
            PROMOTION_TYPE_DESC: 'type',
        };
        sortKeys = sortKeys.filter(item=>(sortArr.map(item=>item.field)).includes(sorterRouter[ item ]));
    }
}

写作时间: Sunday, June 21, 2020 02:02:44

你可能感兴趣的:([email protected] 表格多列联动排序)