import React, { SyntheticEvent, ReactNode } from "react"; import classNames from "classnames"; import { ControlledProps, ChangeContext } from "../../form/controlled"; import { StyledProps } from "../../_type"; import { Text } from "../../text"; import { Icon } from "../../icon"; import { TableAddon } from "../TableProps"; import { useConfig } from "../../_util/config-context"; import { getValueByPathKey } from "../util/get-value-by-path-key"; /** * `sortable` 插件用于支持表格的排序操作。 */ export interface SortableOptions extends ControlledProps< SortBy[], SyntheticEvent, SortableChangeContext > { /** * 不支持非受控模式 */ defaultValue?: never; /** * 指定哪些列支持排序,可传入键值,或者详细配置 */ columns: (string | SortableColumn)[]; } /** * 比较函数 */ type CompareFunction = (first: Record, second: Record) => number; export interface SortableColumn { /** * 要支持排序的列的键值 */ key: string; /** * 当列从未排序进入排序状态时,优先使用的排序状态 */ prefer: SortOrder; /** * 自定义排序函数 * @since 2.6.0 */ sorter?: CompareFunction; } export interface SortableChangeContext extends ChangeContext { /** * 当前引起变更的排序信息 */ sort: SortBy; } export type SortOrder = "asc" | "desc" | undefined; export interface SortBy { /** * 按照哪一列排序 */ by: string; /** * 升序/倒序/未排序 */ order: SortOrder; /** * 自定义排序函数 * @since 2.6.0 */ sorter?: CompareFunction; } export function sortable(options: SortableOptions): TableAddon { // 当前列的排序优先级 const sortablePrefer = new Map( (options.columns || []).map(column => typeof column === "string" ? ([column, "asc"] as [string, SortOrder]) : ([column.key, column.prefer] as [string, SortOrder]) ) ); // 当前列的自定义排序函数 const sortableCompare = new Map( (options.columns || []).map(column => typeof column === "string" ? ([column, undefined] as [string, CompareFunction]) : ([column.key, column.sorter] as [string, CompareFunction]) ) ); // 当前列的排序状态 const sortMap = new Map( (options.value || []).map( sort => [sort.by, sort.order] as [string, SortOrder] ) ); return { getInfo: () => ({ name: "sortable" }), onInjectColumn: renderColumn => ( record, rowKey, recordIndex, column, columnIndex ) => { const columnResult = renderColumn( record, rowKey, recordIndex, column, columnIndex ); // recordIndex > -1 是记录行,我们只注入表头 if (recordIndex > -1) { return columnResult; } // 不是可排序列,跳过 if (!sortablePrefer.has(column.key)) { return columnResult; } // 获取原始渲染内容 let { children } = columnResult; // 当前的排序状态 undefined | "asc" | "desc" const order = sortMap.get(column.key); // 当前列的自定义排序函数 const sorter = sortableCompare.get(column.key); // 切换排序状态 const changeOrder = (event: React.MouseEvent) => { if (typeof options.onChange !== "function") { return; } let nextOrder: SortOrder; const prefer = sortablePrefer.get(column.key); if (!order) { nextOrder = prefer; } if (order === "asc") { nextOrder = prefer === "asc" ? "desc" : undefined; } if (order === "desc") { nextOrder = prefer === "asc" ? undefined : "asc"; } const sort: SortBy = { by: column.key, order: nextOrder, sorter, }; // 删除旧排序规则,保证新的在前 sortMap.delete(column.key); const value: SortBy[] = (nextOrder ? [sort] : []).concat( Array.from(sortMap).map(([by, order]) => ({ by, order })) ); options.onChange(value, { event, sort }); }; // 包装一个排序按钮 children = ( {children} ); return { ...columnResult, children }; }, }; } export interface SortButtonProps extends StyledProps { children?: ReactNode; order?: SortOrder; onClick?: (evt: React.MouseEvent) => void; } export function SortButton({ order, className, style, children, onClick, }: SortButtonProps) { const { classPrefix } = useConfig(); let iconType = "sort"; if (order === "asc") { iconType = "sortup"; } if (order === "desc") { iconType = "sortdown"; } return ( {children} ); } function comparer(sorts: SortBy[]) { return (a: any, b: any) => { for (const { by, order, sorter } of sorts) { const hasSorter = typeof sorter === "function"; const firstValue = getValueByPathKey(a, by); const secondValue = getValueByPathKey(b, by); const isAsc = hasSorter ? sorter(a, b) < 0 : firstValue < secondValue; const isDesc = hasSorter ? sorter(a, b) > 0 : firstValue > secondValue; if (isAsc || isDesc) { if (order === "asc") return isAsc ? -1 : 1; if (order === "desc") return isDesc ? -1 : 1; } } return 0; }; } sortable.comparer = comparer as (sorts: SortBy[]) => (a: any, b: any) => number;