import React from 'react' import type { CSSProperties, ReactNode } from 'react' import styled from 'styled-components' import type { ArtColumn, SortItem, SortOrder, PositionKeysMapType } from '../../interfaces' import { internals } from '../../internals' import { collectNodes, isLeafNode, layeredSort, mergeCellProps, smartCompare } from '../../utils' import { TablePipeline } from '../pipeline' interface SortIconProps { style?: CSSProperties className?: string size?: number order?: SortOrder onClick?(e: React.MouseEvent): void } function SortIcon({ size = 32, style, className, order, onClick }: SortIconProps) { return ( ) } function DefaultSortHeaderCell({ children, column, onToggle, sortOrder, sortIndex, sortOptions, clickArea, }: SortHeaderCellProps) { // 通过 justify-content 来与 col.align 保持对齐方向一致 const justifyContent = column.align === 'right' ? 'flex-end' : column.align === 'center' ? 'center' : 'flex-start' return ( {children} {sortOptions.mode === 'multiple' && sortIndex != -1 && (
{sortIndex + 1}
)}
) } function hasAnySortableColumns(cols: ArtColumn[]): boolean { return cols.some( (col) => Boolean(col.features?.sortable) || (!isLeafNode(col) && hasAnySortableColumns(col.children)), ) } const TableHeaderCell = styled.div` display: flex; align-items: center; ` export interface SortHeaderCellProps { /** 调用 makeSortTransform(...) 时的参数 */ sortOptions: Required> /** 在添加排序相关的内容之前 表头原有的渲染内容 */ children: ReactNode /** 当前排序 */ sortOrder: SortOrder /** 多列排序下,sortIndex 指明了当前排序字段起作用的顺序. 当 sortOrder 为 none 时,sortIndex 固定为 -1 */ sortIndex: number /** 当前列的配置 */ column: ArtColumn /** 切换排序的回调 */ onToggle(e: React.MouseEvent): void /** 点击事件的响应区域 */ clickArea: 'content' | 'icon' } export interface SortFeatureOptions { /** (非受控用法) 默认的排序字段列表 */ defaultSorts?: SortItem[] /** (受控用法) 排序字段列表 */ sorts?: SortItem[] /** 更新排序字段列表的回调函数 */ onChangeSorts?(nextSorts: SortItem[]): void /** 排序切换顺序 */ orders?: SortOrder[] /** 排序模式。单选 single,多选 multiple,默认为多选 */ mode?: 'single' | 'multiple' /** 自定义排序表头 */ SortHeaderCell?: React.ComponentType /** 是否保持 dataSource 不变 */ keepDataSource?: boolean /** 排序激活时 是否高亮这一列的单元格 */ highlightColumnWhenActive?: boolean /** 是否对触发 onChangeOpenKeys 的 click 事件调用 event.stopPropagation() */ stopClickEventPropagation?: boolean /** 点击事件的响应区域,默认为 content */ clickArea?: 'content' | 'icon' /**根据层级进行处理展开节点*/ positionKeysMap?: PositionKeysMapType } const stateKey = 'sort' export function sort(opts: SortFeatureOptions = {}) { return function sortStep(pipeline: TablePipeline) { const { orders = ['desc', 'asc', 'none'], mode = 'multiple', SortHeaderCell = DefaultSortHeaderCell, keepDataSource, highlightColumnWhenActive, stopClickEventPropagation, clickArea = 'content', } = opts const positionKeysMap = opts.positionKeysMap ?? {} const inputSorts = opts.sorts ?? pipeline.getStateAtKey(stateKey) ?? opts.defaultSorts ?? [] const activeSorts = inputSorts.filter((s) => s.order !== 'none') // 单字段排序的情况下 sorts 中只有第一个排序字段才会生效 const sorts = mode === 'multiple' ? activeSorts : activeSorts.slice(0, 1) const onChangeSortsInMultipleMode = (nextSorts: SortItem[]) => { opts.onChangeSorts?.(nextSorts) pipeline.setStateAtKey(stateKey, nextSorts) } const onChangeSorts = mode === 'multiple' ? onChangeSortsInMultipleMode : (nextSorts: SortItem[]) => { // 单字段排序的情况下,nextSorts 中只有最后一个排序字段才会生效 const len = nextSorts.length onChangeSortsInMultipleMode(nextSorts.slice(len - 1)) } const sortOptions: SortHeaderCellProps['sortOptions'] = { sorts, onChangeSorts, orders, mode, keepDataSource, highlightColumnWhenActive, stopClickEventPropagation, clickArea, } const sortMap = new Map(sorts.map((sort, index) => [sort.code, { index, ...sort }])) const dataSource = pipeline.getDataSource() const columns = pipeline.getColumns() if (process.env.NODE_ENV !== 'production') { if (!hasAnySortableColumns(columns)) { console.warn( '[ali-react-table] commonTransform.sort 缺少可排序的列,请通过 column.features.sortable 来指定哪些列可排序', columns, ) } } pipeline.dataSource(processDataSource(dataSource)) pipeline.columns(processColumns(columns)) return pipeline function processDataSource(dataSource: any[]) { if (keepDataSource) { return dataSource } const sortColumnsMap = new Map( collectNodes(columns, 'leaf-only') .filter((col) => col.features?.sortable !== false && col.features?.sortable != null) .map((col) => [col.code, col]), ) return layeredSort(dataSource, (x, y) => { for (const { code, order } of sorts) { const column = sortColumnsMap.get(code) // 如果 code 对应的 column 不可排序,我们跳过该 code if (column == null) { continue } const sortable = column.features.sortable const compareFn = typeof sortable === 'function' ? sortable : smartCompare const xValue = internals.safeGetValue(column, x, -1) const yValue = internals.safeGetValue(column, y, -1) const cmp = compareFn(xValue, yValue, x, y) if (cmp !== 0) { return cmp * (order === 'asc' ? 1 : -1) } } return 0 }, positionKeysMap) } // 在「升序 - 降序 - 不排序」之间不断切换 function toggle(code: string) { const sort = sortMap.get(code) if (sort == null) { onChangeSorts(sorts.concat([{ code, order: orders[0] }])) } else { const index = sorts.findIndex((s) => s.code === code) const nextSorts = sorts.slice(0, index + 1) const nextOrder = getNextOrder(sort.order) if (nextOrder === 'none') { nextSorts.pop() } else { nextSorts[index] = { ...nextSorts[index], order: nextOrder } } onChangeSorts(nextSorts) } } function processColumns(columns: ArtColumn[]) { return columns.map(dfs) function dfs(col: ArtColumn): ArtColumn { const result = { ...col } const sortable = col.code && (col.features?.sortable || sortMap.has(col.code)) const active = sortable && sortMap.has(col.code) if (sortable) { let sortIndex = -1 let sortOrder: SortOrder = 'none' if (active) { const { order, index } = sortMap.get(col.code) sortOrder = order sortIndex = index if (highlightColumnWhenActive) { result.headerCellProps = mergeCellProps(col.headerCellProps, { style: { background: 'var(--header-highlight-bgcolor)' } as any, }) result.getCellProps = (value, row, rowIndex) => { const prevCellProps = internals.safeGetCellProps(col, row, rowIndex) return mergeCellProps(prevCellProps, { style: { background: 'var(--highlight-bgcolor)' } as any, }) } } } result.title = ( { if (stopClickEventPropagation) { e.stopPropagation() } toggle(col.code) }} sortOrder={sortOrder} column={col} sortIndex={sortIndex} sortOptions={sortOptions} > {internals.safeRenderHeader(col)} ) } if (!isLeafNode(col)) { result.children = col.children.map(dfs) } return result } } function getNextOrder(order: SortOrder) { const idx = orders.indexOf(order) return orders[idx === orders.length - 1 ? 0 : idx + 1] } } }