1. 程式人生 > 實用技巧 >可固定列可排序table

可固定列可排序table

  1 import React, { useState } from 'react'
  2 import { Icon } from 'antd'
  3 import $ from './table.scss'
  4 
  5 export interface Column {
  6   key: string
  7   title: string | JSX.Element
  8   headerClassName?: string
  9   tdClassName?: string
 10   width?: number | string
 11   alignment?: string
12 render?: (record: any) => React.ReactNode 13 sortChange?: (flag: sortE, col: any) => void 14 defaultSort?: string 15 } 16 interface Props { 17 columns: Column[] 18 dataSource: any[] 19 rowKey: string 20 tableWidth?: number | string 21 fixedLeft?: number 22 } 23 enum sortE {
24 'asc' = 'asc', 25 'desc' = 'desc', 26 'unsort' = 'unsort', 27 } 28 enum textAlign { 29 'left' = 'left', 30 'right' = 'right', 31 } 32 export default function({ columns, dataSource, rowKey, tableWidth = '', fixedLeft = 0 }: Props) { 33 const colSortMap = new Map<string | Column, [sortE, React.Dispatch<any>]>()
34 const sortCols: Column[] = columns.filter(col => { 35 return col.sortChange 36 }) 37 sortCols.forEach(col => { 38 colSortMap.set(col, useState(sortE.unsort)) 39 }) 40 const sortDefaultCols: Column[] = sortCols.filter(col => { 41 return col.defaultSort 42 }) 43 if (sortDefaultCols.length) { 44 if (sortDefaultCols[0].defaultSort === sortE.desc) { 45 colSortMap.set(sortDefaultCols[0], useState(sortE.desc)) 46 } else if (sortDefaultCols[0]!.defaultSort === sortE.asc) { 47 colSortMap.set(sortDefaultCols[0], useState(sortE.asc)) 48 } 49 } 50 51 const sortChange = (col: Column, flag: sortE) => { 52 colSortMap.forEach((val, k) => { 53 colSortMap.get(k)![1](sortE.unsort) 54 }) 55 colSortMap.get(col)![1](flag) 56 if (col.sortChange) { 57 col.sortChange(flag, col) 58 } 59 } 60 const sorter = (col: Column) => { 61 return ( 62 <span className={$.sorter}> 63 <Icon 64 type="caret-up" 65 onClick={() => sortChange(col, sortE.asc)} 66 style={{ color: colSortMap.get(col)![0] === sortE.asc ? 'gray' : 'gainsboro' }} 67 /> 68 <Icon 69 type="caret-down" 70 onClick={() => sortChange(col, sortE.desc)} 71 style={{ color: colSortMap.get(col)![0] === sortE.desc ? 'gray' : 'gainsboro' }} 72 /> 73 </span> 74 ) 75 } 76 const isVisible = (fixcol: number, isFixed: boolean, index: number) => { 77 if (isFixed && fixcol > 0) { 78 return index < fixcol ? 'visible' : 'hidden' 79 } else if (!isFixed && fixcol > 0) { 80 return index < fixcol ? 'hidden' : 'visible' 81 } else { 82 return 'visible' 83 } 84 } 85 const head = (cols: Column[], isFixed = false) => { 86 return ( 87 <thead> 88 <tr> 89 {cols.map((col, i) => ( 90 <th 91 key={col.key} 92 className={isFixed && i === fixedLeft - 1 ? $.shadow : ''} 93 style={{ 94 visibility: isVisible(fixedLeft, isFixed, i), 95 textAlign: (col.alignment as textAlign) || 'left', 96 width: col.width, 97 }} 98 > 99 <div> 100 <span>{col.title}</span> 101 {col.sortChange && sorter(col)} 102 </div> 103 </th> 104 ))} 105 </tr> 106 </thead> 107 ) 108 } 109 110 const body = (bodyData: any[], cols: Column[], isFixed = false) => { 111 return ( 112 <tbody> 113 {bodyData.map((record, index) => ( 114 <tr key={record[rowKey]}> 115 {cols.map(({ key, alignment, render, width }, i) => ( 116 <td 117 key={key} 118 className={isFixed && i === fixedLeft - 1 ? $.shadow : ''} 119 style={{ 120 visibility: isVisible(fixedLeft, isFixed, i), 121 textAlign: (alignment as textAlign) || 'left', 122 width: width || '', 123 }} 124 > 125 <div>{render ? render({ index, ...record }) : record[key]}</div> 126 </td> 127 ))} 128 </tr> 129 ))} 130 </tbody> 131 ) 132 } 133 return ( 134 <div className={$.table_content}> 135 <div className={$.table_scroll_x}> 136 <table style={{ width: tableWidth && tableWidth }}> 137 <colgroup> 138 {columns.map(item => { 139 return <col key={item.key} /> 140 })} 141 </colgroup> 142 {head(columns)} 143 {body(dataSource, columns)} 144 </table> 145 </div> 146 {fixedLeft > 0 && ( 147 <div 148 className={$.table_fixed_left} 149 style={{ 150 backgroundColor: 'transparent', 151 pointerEvents: 'none', 152 maxWidth: '100%', 153 overflow: 'hidden', 154 }} 155 > 156 <table style={{ width: tableWidth && tableWidth }}> 157 <colgroup> 158 {columns.map(item => { 159 return <col key={item.key} /> 160 })} 161 </colgroup> 162 {head(columns, true)} 163 {body(dataSource, columns, true)} 164 </table> 165 </div> 166 )} 167 </div> 168 ) 169 }

table.scss
.table_content{
    position: relative;
    td,th{
        text-align: left;
        padding: 8px;
        border-bottom: 2px solid #EBEEF5;
        &:last-child{
            text-align: right;
        }
    }
    th{
        background-color: #F1F1F1;
        font-size: 13px;
        color: #909399;
    }
    .table_scroll_x{
        overflow: auto;
        overflow-x: scroll;
        transition: opacity .3s;
        height: 100%;
        &::-webkit-scrollbar { display: none !important }
        table{
            min-width: 100%;
            border-collapse: separate;
            border-spacing: 0;
            table-layout: fixed;
            height: 100%;
        }
    }
    .table_fixed_left{
        position: absolute;
        top: 0;
        z-index: 1;
        left: 0;
        overflow: hidden;
        width: auto;
        height: 100%;
        table{
            border-collapse: separate;
            border-spacing: 0;
            table-layout: fixed;
            height: 100%;
            td{
                background: #fff;
            }
            .shadow{
                position: relative;
                &::after{
                    content:'';
                    position: absolute;
                    display: inline-block;
                    top: 0;
                    right: -8px;
                    bottom: -2px;
                    width: 8px;
                    background-image: linear-gradient(90deg, rgba(0,0,0,0.10) 0%, rgba(0,0,0,0.00) 100%);
                }
            }
        }
    }
    .sorter{
        display: inline-flex;
        justify-content: center;
        align-items: center;
        flex-direction: column;
    }
}