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;
}
}
}