基本思路是:一组数据同时创建两个table并使其完全重叠;将其中一个做隐藏化处理,
包括适用pointer-event取消对事件的感知,visibility隐藏部分cell,此table作为显示固定部分;
升降序可根据字段按需设置,多个字段都设了默认值的话则取第0个,多个字段的排序互斥;
适用方法与antd 的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)
54 })
55 colSortMap.get(col)
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;
}
}