import React, { useState,useEffect,ReactNode} from ‘react‘ import { Icon } from ‘antd‘ import $ from ‘./styleTableGroup.scss‘ export interface Column { key: string title: ReactNode thClassName?: string tdClassName?: string width?: number | string alignment?: string render?: (value: any, record: any) => ReactNode sorter?: ‘asc‘|"desc"| boolean children?: any[] } interface Props { columns: Column[] dataSource: any[] rowKey: string tableWidth?: number | string maxHeight?: number | string className?: string stickyTop?: boolean stickyLeft?: boolean onChange?:(flag: ‘asc‘| "desc", col: any) => void } enum textAlign { ‘left‘ = ‘left‘, ‘right‘ = ‘right‘, } export default function({ columns, dataSource, rowKey, className, tableWidth = ‘‘, maxHeight, stickyTop = false, stickyLeft = true, onChange=()=>{} }: Props) { const [currentSortCol,setCurrentSortCol] =useState<Column>() const sortChange = (col: Column, flag:‘asc‘|"desc") => { setCurrentSortCol({...col,sorter:flag}) onChange(flag, col) } useEffect(()=>{ let realCols:Column[] = [] columns.forEach(col=>{ if(col.children?.length){ realCols = realCols.concat(col.children) }else{ realCols = realCols.concat(col) } }) const sortsColumns = realCols.filter(col=>col.sorter) setCurrentSortCol({...sortsColumns[0],sorter:"desc"}) /* eslint-disable-next-line */ },[]) const sorter = (col: Column) => { return ( <span className={$.sorter}> <Icon type="caret-up" // onClick={() => sortChange(col, "asc")} style={{ color: col.key=== currentSortCol?.key&¤tSortCol?.sorter === "asc" ? ‘#1989FA‘:‘#C0C4CC‘ }} /> <Icon type="caret-down" // onClick={() => sortChange(col, "desc")} style={{ color:col.key=== currentSortCol?.key&¤tSortCol?.sorter === "desc" ? ‘#1989FA‘:‘#C0C4CC‘ }} /> </span> ) } const isSticky = (index: number, left: boolean, top: boolean): object => { if (top && !left) { return { position: ‘sticky‘, top: 0 } } else if (top && left) { if (index === 0) { return { position: ‘sticky‘, left: 0, top: 0, zIndex: 4 } } else { return { position: ‘sticky‘, top: 0 } } } else if (left && index === 0) { return { position: ‘sticky‘, left: 0, zIndex: 3 } } return {} } const head = (cols: Column[]) => { return ( <thead> <tr> {cols.map((col, i) => ( <th key={col.key} className={stickyLeft && i === 0 ? `${col.thClassName} group_${i} ${$.shadow}` : `group_${i} ${col.thClassName}`} style={{ ...isSticky(i, stickyLeft, stickyTop), textAlign: (col.alignment as textAlign) || ‘left‘, }} colSpan={col.children?.length} > <div style={{ width: col.width, }} > <span>{col.title}</span> </div> </th> ))} </tr> <tr> {cols.map((colG, i) => ( colG.children?.map((col,idx)=>( <th key={col.key} className={stickyLeft && i === 0 ? `${col.thClassName} group_${i}_col_${idx} ${$.shadow}` : `group_${i}_col_${idx} ${col.thClassName}`} style={{ ...isSticky(i, stickyLeft, stickyTop), textAlign: (col.alignment as textAlign) || ‘left‘, }} onClick={col.sorter?() => sortChange(col, currentSortCol?.sorter === "desc"?"asc":"desc"):()=>{}} > <div style={{ width: col.width, display: ‘flex‘, justifyContent: col.alignment === ‘right‘ ? ‘flex-end‘ : ‘flex-start‘, alignItems: ‘center‘, }} > <span>{col.title}</span> {col.sorter && sorter(col)} </div> </th> )) )) } </tr> </thead> ) } const body = (bodyData: any[], cols: Column[]) => { return ( <tbody> {bodyData.map((record, index) => ( <tr key={record[rowKey]}> { cols.map((colsG,i)=>( colsG.children?.map(({ key, alignment, render, width, tdClassName }, idx) => ( <td key={key} className={stickyLeft && i === 0 ? `${tdClassName} group_${i}_col_${idx} ${$.shadow}` : `group_${i}_col_${idx} ${tdClassName}`} style={{ ...isSticky(i, stickyLeft, false), textAlign: (alignment as textAlign) || ‘left‘, }} > <div style={{ width: width || ‘‘ }}> {render ? render(record[key], { index, ...record }) : record[key]} </div> </td> )) )) } </tr> ))} </tbody> ) } return ( <div className={`${$.table_wrap} ${className}`} style={{width: tableWidth && tableWidth, maxHeight }} > <table> <colgroup> {columns.map(item => { return <col key={item.key} className={`colgroup_${item.children?.length}`} span={item.children?.length} /> })} </colgroup> {head(columns)} {body(dataSource, columns)} </table> </div> ) } //./styleTableGroup.scss .table_wrap { position: relative; overflow-x: auto; overflow-y: hidden; transition: opacity 0.3s; height: 100%; &::-webkit-scrollbar { display: none !important; } table { min-width: 100%; border-collapse: separate; border-spacing: 0; table-layout: fixed; height: 100%; } :global { .colgroup_1 { background-color: #fff; z-index: 2; } .colgroup_2, .colgroup_3, .colgroup_4, .colgroup_5 { background-image: linear-gradient(90deg, #f8f8f8 0%, #ffffff 100%); } .group_0, .group_0_col_0 { background-color: #fff; } } thead { tr { &:first-child { th { border-bottom: unset; background-color: #fff; padding: 8px 0; } } } } td, th { text-align: left; padding: 8px; border-bottom: 1px solid #ebeef5; &:last-child { text-align: right; } } th { font-family: PingFangSC-Medium; font-size: 13px; color: #909399; letter-spacing: 0; z-index: 2; } .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.1) 0%, rgba(0, 0, 0, 0) 100%); // } } .sorter { display: inline-flex; justify-content: center; align-items: center; flex-direction: column; margin: 0 4px; i{ font-size: 16px; color: rgb(192, 196, 204); margin: -4px 0px; } } }