Skip to content

Commit 92abb01

Browse files
authoredMar 21, 2022
feat(Table): Table 新增固定列属性 (#696)
1 parent d10f59a commit 92abb01

File tree

7 files changed

+273
-38
lines changed

7 files changed

+273
-38
lines changed
 

‎packages/react-table/README.md

+71-11
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,64 @@ const Demo = () => (
873873
ReactDOM.render(<Demo />, _mount_);
874874
```
875875

876+
### 固定列
877+
878+
通过使用 fixed 使其列固定
879+
> ⚠️ 注意: 若并没有 scroll 滚动条,fixed 属性并不会有直观的效果
880+
<!--rehype:style=border-left: 8px solid #ffe564;background-color: #ffe56440;padding: 12px 16px;-->
881+
882+
<!--rehype:bgWhite=true&codeSandbox=true&codePen=true-->
883+
```jsx
884+
import ReactDOM from 'react-dom';
885+
import { Table, Button } from 'uiw';
886+
887+
const columns = [
888+
{
889+
title: '姓名',
890+
ellipsis: true,
891+
// fixed: true,
892+
width: 50,
893+
key: 'name',
894+
}, {
895+
// fixed: true,
896+
title: '年龄',
897+
width: 50,
898+
style: { color: 'red' },
899+
key: 'age',
900+
}, {
901+
title: '地址',
902+
width: 50,
903+
key: 'info',
904+
}, {
905+
title: '操作',
906+
key: 'edit',
907+
width: 98,
908+
fixed: 'right',
909+
render: (text, key, rowData, rowNumber, columnNumber) => (
910+
<div>
911+
<Button size="small" type="danger">删除</Button>
912+
<Button size="small" type="success">修改</Button>
913+
</div>
914+
),
915+
},
916+
];
917+
const dataSource = [
918+
{ name: '邓紫棋', age: '12', info: '又名G.E.M.,原名邓诗颖,1991年8月16日生于中国上海,中国香港创作型女歌手。', edit: '' },
919+
{ name: '李易峰', age: '32', info: '1987年5月4日出生于四川成都,中国内地男演员、流行乐歌手、影视制片人', edit: '' },
920+
{ name: '范冰冰', age: '23', info: '1981年9月16日出生于山东青岛,中国影视女演员、制片人、流行乐女歌手', edit: '' },
921+
{ name: '杨幂', age: '34', info: '1986年9月12日出生于北京市,中国内地影视女演员、流行乐歌手、影视制片人。', edit: '' },
922+
{ name: 'Angelababy', age: '54', info: '1989年2月28日出生于上海市,华语影视女演员、时尚模特。', edit: '' },
923+
{ name: '唐嫣', age: '12', info: '1983年12月6日出生于上海市,毕业于中央戏剧学院表演系本科班', edit: '' },
924+
{ name: '吴亦凡', age: '4', info: '1990年11月06日出生于广东省广州市,华语影视男演员、流行乐歌手。', edit: '' },
925+
];
926+
const Demo = () => (
927+
<div>
928+
<Table scroll={{x: 1200}} bordered columns={columns} data={dataSource} />
929+
</div>
930+
);
931+
ReactDOM.render(<Demo />, _mount_);
932+
```
933+
876934
## Props
877935

878936
### Table
@@ -881,14 +939,16 @@ ReactDOM.render(<Demo />, _mount_);
881939
|--------- |-------- |--------- |-------- |
882940
| columns | 表格列的配置描述,可以内嵌 `children`,以渲染分组表头。| ColumnProps[] | `[]` |
883941
| data | 数据数组。| Array[] | `[]` |
884-
| title | 表格标题 | ~~Function(text, key, rowData, rowNumber, columnNumber)~~ /<br/> Function(data: IColumns, rowNum: number, colNum: number)`@3.0.0+` /<br/> String / ReactNode | - |
942+
| title | 表格标题 | ~~Function(text, key, rowData, rowNumber, columnNumber)~~ /<br/> Function(data: IColumns, rowNum: Number, colNum: Number)`@3.0.0+` /<br/> String / ReactNode | - |
885943
| footer | 表格尾部 | String/ReactNode | - |
886944
| bordered | 是否展示外边框和列边框 | Boolean | - |
887945
| empty | 无数据状态 | ReactNode | - |
888-
| onCellHead | 表头单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /<br/> Function(data: IColumns, colNum: number, rowNum: number, evn: React.MouseEvent<HTMLTableCellElement\>) `@3.0.0+` | - |
889-
| onCell | 单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /<br/> Function(data: IColumns, options:{ colNum: number, rowNum: number, keyName: string }, evn: React.MouseEvent<HTMLTableCellElement\>) `@3.1.0+` | - |
946+
| onCellHead | 表头单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /<br/> Function(data: IColumns, colNum: Number, rowNum: Number, evn: React.MouseEvent<HTMLTableCellElement\>) `@3.0.0+` | - |
947+
| onCell | 单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /<br/> Function(data: IColumns, options:{ colNum: Number, rowNum: Number, keyName: String }, evn: React.MouseEvent<HTMLTableCellElement\>) `@3.1.0+` | - |
890948
| expandable | 可展开配置 | ExpandableType | - |
891-
| rowKey | 表格行 key 的取值 | string | - |
949+
| rowKey | 表格行 key 的取值 | String | - |
950+
| scroll | 表格是否可滚动,也可以指定滚动区域的宽、高 | { x?: React.CSSProperties['width'], y?: React.CSSProperties['height'] } | - |
951+
892952

893953
### ColumnProps
894954

@@ -902,9 +962,9 @@ ReactDOM.render(<Demo />, _mount_);
902962
| colSpan | 合并表头行。| Number | - |
903963
| ellipsis | 超过宽度将自动省略。`v4.8.7+`| Boolean | `false` |
904964
| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前值的 `key`,行索引数据,当前行号,当前列号。| `Function(text, key, rowData, rowNumber, columnNumber)` | - |
905-
| align | 设置列的对齐方式 | "left"|"center"|"right" | - |
906-
| className | 列样式类名 | string | - |
907-
| scroll | 表格是否可滚动,也可以指定滚动区域的宽、高 | { x?: React.CSSProperties['width'], y?: React.CSSProperties['height'] } | - |
965+
| align | 设置列的对齐方式 | "left"\|"center"\|"right" | - |
966+
| className | 列样式类名 | String | - |
967+
| fixed | 把选择框列固定 | Boolean \|"left"\|"right" | - |
908968

909969
### expandable
910970

@@ -914,11 +974,11 @@ ReactDOM.render(<Demo />, _mount_);
914974
|--------- |-------- |--------- |-------- |
915975
| expandedRowRender | 自定义展开行| (record, index, expanded) => React.ReactNode | - |
916976
| expandIcon | 自定义图标 | (expanded, record, index) => React.ReactNode; | - |
917-
| rowExpandable | 是否允许展开| (record)=>boolean | - |
918-
| defaultExpandAllRows | 初始时,是否展开所有行| boolean | false |
977+
| rowExpandable | 是否允许展开| (record)=>Boolean | - |
978+
| defaultExpandAllRows | 初始时,是否展开所有行| Boolean | false |
919979
| defaultExpandedRowKeys | 初始时,默认展开的行 rowKey数组 | Array | - |
920980
| expandedRowKeys | 控制展开的行 rowKey数组 | Array | - |
921981
| onExpandedRowsChange | 展开的行变化触发 | (expandedRows)=>void | - |
922982
| onExpand | 点击展开图标触发 | (expanded,record,index)=>void | - |
923-
| indentSize | 控制树形结构每一层的缩进宽度 | number | 16 |
924-
| childrenColumnName | 指定树形结构的列名 | string | children |
983+
| indentSize | 控制树形结构每一层的缩进宽度 | Number | 16 |
984+
| childrenColumnName | 指定树形结构的列名 | String | children |

‎packages/react-table/src/TableTr.tsx

+19-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React, { useMemo, useState, useEffect } from 'react';
22
import Icon from '@uiw/react-icon';
3-
import { TableProps } from './';
3+
import { LocationWidth, TableProps } from './';
44
import './style/index.less';
55
import { noop } from '@uiw/utils';
6+
import { locationFixed } from './util';
67

78
interface TableTrProps<T> {
89
rowKey?: keyof T;
@@ -18,6 +19,7 @@ interface TableTrProps<T> {
1819
// 层级
1920
hierarchy: number;
2021
childrenColumnName: string;
22+
locationWidth: { [key: number]: LocationWidth };
2123
}
2224

2325
export default function TableTr<T extends { [key: string]: any }>(props: TableTrProps<T>) {
@@ -33,6 +35,7 @@ export default function TableTr<T extends { [key: string]: any }>(props: TableTr
3335
hierarchy,
3436
indentSize,
3537
childrenColumnName,
38+
locationWidth,
3639
} = props;
3740
const [isOpacity, setIsOpacity] = useState(false);
3841
const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);
@@ -88,9 +91,6 @@ export default function TableTr<T extends { [key: string]: any }>(props: TableTr
8891
}
8992
}
9093
}
91-
if (ellipsis && ellipsis[keyName.key!]) {
92-
objs.className = `${prefixCls}-ellipsis`;
93-
}
9494
const isHasChildren = Array.isArray(trData[childrenColumnName]);
9595
if (colNum === 0 && (isOpacity || hierarchy || isHasChildren)) {
9696
objs.children = (
@@ -101,13 +101,25 @@ export default function TableTr<T extends { [key: string]: any }>(props: TableTr
101101
</>
102102
);
103103
}
104+
if (keyName.fixed) {
105+
if (keyName.fixed === 'right') {
106+
objs.className = `${objs.className} ${prefixCls}-fixed-right`;
107+
} else {
108+
objs.className = `${objs.className} ${prefixCls}-fixed-true`;
109+
}
110+
}
104111
return (
105112
<td
106113
{...objs}
114+
style={{ ...locationFixed(keyName.fixed!, locationWidth, colNum) }}
115+
children={
116+
<span className={ellipsis && ellipsis[keyName.key!] ? `${prefixCls}-ellipsis` : undefined}>
117+
{objs.children}
118+
</span>
119+
}
107120
key={colNum}
108-
// style={keyName?.style}
109-
className={`${objs.className || ''} ${prefixCls}-tr-children-${keyName.align || 'left'} ${
110-
keyName.className || ''
121+
className={`${prefixCls}-tr-children-${keyName.align || 'left'} ${keyName.className || ''} ${
122+
objs.className || ''
111123
}`}
112124
onClick={(evn) => onCell(trData, { rowNum, colNum, keyName: keyName.key! }, evn)}
113125
/>
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React, { Component } from 'react';
2+
import ReactDOM from 'react-dom';
3+
import { TableColumns, TableProps, LocationWidth } from './';
4+
import { locationFixed } from './util';
5+
6+
interface ThComponentProps<T> {
7+
colNum: number;
8+
item: TableColumns<T>;
9+
prefixCls: string;
10+
titleNode: JSX.Element;
11+
onCellHead: TableProps<T>['onCellHead'];
12+
rowNum: number;
13+
locationWidth: { [key: number]: LocationWidth };
14+
updateLocation: (params: LocationWidth, index: number) => void;
15+
}
16+
export default class ThComponent<T> extends Component<ThComponentProps<T>> {
17+
componentDidMount() {
18+
const rect = ReactDOM.findDOMNode(this);
19+
this.props.updateLocation({ width: (rect as Element).getBoundingClientRect().width }, this.props.colNum);
20+
}
21+
22+
render() {
23+
const { colNum, prefixCls, item, titleNode, onCellHead, rowNum, locationWidth } = this.props;
24+
const { title, key, render, children, ellipsis, fixed = false, ...thProps } = item;
25+
let cls = '';
26+
if (fixed) {
27+
if (fixed === 'right') {
28+
cls = prefixCls + '-fixed-right';
29+
} else {
30+
cls = prefixCls + '-fixed-true';
31+
}
32+
}
33+
return (
34+
<th
35+
key={colNum}
36+
{...thProps}
37+
style={{ ...thProps.style, ...locationFixed(fixed, locationWidth, colNum) }}
38+
className={`${prefixCls}-tr-children-${item?.align || 'left'} ${item.className || ''} ${cls}`}
39+
onClick={(evn) => onCellHead?.(item, colNum, rowNum!, evn)}
40+
>
41+
{titleNode}
42+
</th>
43+
);
44+
}
45+
}

‎packages/react-table/src/Thead.tsx

+29-15
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,58 @@
11
import React from 'react';
22
import { IProps, noop } from '@uiw/utils';
3-
import { TableProps, TableColumns } from './';
3+
import { TableProps, TableColumns, LocationWidth } from './';
44
import './style/index.less';
5+
import ThComponentProps from './ThComponent';
56

67
export interface TheadProps<T extends { [key: string]: V }, V = any> extends IProps {
78
data?: TableColumns<T>[][];
89
onCellHead?: TableProps<T, V>['onCellHead'];
910
align?: TableColumns['align'];
1011
className?: TableColumns['className'];
12+
locationWidth?: { [key: number]: LocationWidth };
13+
updateLocation?: (params: LocationWidth, index: number) => void;
1114
}
1215

1316
export default function TheadComponent<T extends { [key: string]: V }, V>(
1417
props: TheadProps<T, V> & React.HTMLAttributes<HTMLTableSectionElement> = {},
1518
) {
16-
const { prefixCls = 'w-table', className, data = [], onCellHead = noop, ...other } = props;
19+
const {
20+
prefixCls = 'w-table',
21+
className,
22+
data = [],
23+
onCellHead = noop,
24+
locationWidth,
25+
updateLocation,
26+
...other
27+
} = props;
1728
return (
1829
<thead className={[prefixCls, className].filter(Boolean).join(' ').trim()} {...other}>
1930
{data &&
2031
data.length > 0 &&
2132
data.map((tds?: TableColumns<T>[], rowNum?: number) => (
2233
<tr key={rowNum}>
2334
{(tds || []).map((item, colNum) => {
24-
const { title, key, render, children, ellipsis, ...thProps } = item;
25-
const titleNode: TableColumns<T>['title'] =
26-
typeof title === 'function' ? title(item, colNum, rowNum!) : title;
35+
const { title, key, render, children, ellipsis, fixed = false, ...thProps } = item;
36+
const titleNode: TableColumns<T>['title'] = (
37+
<span className={ellipsis ? `${thProps.className || ''} ${prefixCls}-ellipsis` : undefined}>
38+
{typeof title === 'function' ? title(item, colNum, rowNum!) : title}
39+
</span>
40+
);
2741
if (thProps.colSpan === 0) {
2842
return null;
2943
}
30-
if (ellipsis) {
31-
thProps.className = `${thProps.className || ''} ${prefixCls}-ellipsis`;
32-
}
3344
return (
34-
<th
45+
<ThComponentProps
46+
colNum={colNum}
47+
item={item}
3548
key={colNum}
36-
{...thProps}
37-
className={`${prefixCls}-tr-children-${item?.align || 'left'} ${className || ''}`}
38-
onClick={(evn) => onCellHead(item, colNum, rowNum!, evn)}
39-
>
40-
{titleNode}
41-
</th>
49+
prefixCls={prefixCls}
50+
onCellHead={onCellHead}
51+
rowNum={rowNum!}
52+
titleNode={titleNode}
53+
locationWidth={locationWidth!}
54+
updateLocation={updateLocation!}
55+
/>
4256
);
4357
})}
4458
</tr>

‎packages/react-table/src/index.tsx

+47-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useMemo, useState, useEffect } from 'react';
1+
import React, { useMemo, useState, useEffect, useRef } from 'react';
22
import { IProps, HTMLDivProps, noop } from '@uiw/utils';
33
import Icon from '@uiw/react-icon';
44
import Thead from './Thead';
@@ -42,6 +42,7 @@ export type TableColumns<T = any> = {
4242
style?: React.CSSProperties;
4343
align?: 'left' | 'center' | 'right';
4444
className?: string;
45+
fixed?: boolean | 'left' | 'right';
4546
[key: string]: any;
4647
};
4748

@@ -69,6 +70,11 @@ export interface TableProps<T extends { [key: string]: V } = any, V = any> exten
6970
scroll?: { x?: React.CSSProperties['width']; y?: React.CSSProperties['height'] };
7071
}
7172

73+
export interface LocationWidth {
74+
left?: number;
75+
right?: number;
76+
width: number;
77+
}
7278
export interface ICellOptions {
7379
rowNum: number;
7480
colNum: number;
@@ -93,6 +99,37 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
9399
...other
94100
} = props;
95101
const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);
102+
const [locationWidth, setLocationWidth] = useState<{ [key: number]: LocationWidth }>({});
103+
const finalLocationWidth = useRef<{ [key: number]: LocationWidth }>({});
104+
const updateLocation = (params: LocationWidth, index: number) => {
105+
finalLocationWidth.current = {
106+
...finalLocationWidth.current,
107+
[index]: {
108+
...finalLocationWidth.current[index],
109+
...params,
110+
},
111+
};
112+
if (index === columns.length - 1) {
113+
setLocationWidth(computed());
114+
}
115+
};
116+
const computed = () => {
117+
let left = 0,
118+
right = 0;
119+
for (let index = 0; index < columns.length; index++) {
120+
if (finalLocationWidth.current[index]) {
121+
finalLocationWidth.current[index].left = left;
122+
left = finalLocationWidth.current[index].width + left;
123+
}
124+
}
125+
for (let index = columns.length - 1; index > -1; index--) {
126+
if (finalLocationWidth.current[index]) {
127+
finalLocationWidth.current[index].right = right;
128+
right = finalLocationWidth.current[index].width + right;
129+
}
130+
}
131+
return finalLocationWidth.current;
132+
};
96133
useEffect(() => {
97134
if (expandable) {
98135
if (expandable.defaultExpandAllRows) {
@@ -215,11 +252,19 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
215252
<div className={cls} {...other} style={{ ...other.style, ...style.div }}>
216253
<table style={{ tableLayout: ellipsis ? 'fixed' : 'auto', ...style.table }}>
217254
{title && <caption>{title}</caption>}
218-
{columns && columns.length > 0 && <Thead onCellHead={onCellHead} data={header} />}
255+
{columns && columns.length > 0 && (
256+
<Thead
257+
onCellHead={onCellHead}
258+
data={header}
259+
locationWidth={locationWidth}
260+
updateLocation={updateLocation}
261+
/>
262+
)}
219263
{data && data.length > 0 && (
220264
<tbody>
221265
<TableTr
222266
rowKey={rowKey}
267+
locationWidth={locationWidth}
223268
data={data}
224269
keys={self.keys}
225270
render={render}

‎packages/react-table/src/style/index.less

+50-2
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,30 @@
2222
}
2323
> tbody > tr {
2424
transition: all 0.3s;
25+
> td {
26+
background-color: #fff;
27+
position: relative;
28+
z-index: 1;
29+
}
2530
&:hover,
2631
&:hover:nth-child(2n) {
27-
background-color: #efefef;
32+
> td {
33+
background-color: #efefef;
34+
}
2835
}
2936
&:nth-child(2n) {
30-
background-color: #f9f9f9;
37+
> td {
38+
background-color: #f9f9f9;
39+
}
3140
}
3241
}
3342
> thead {
3443
> tr > th {
3544
font-weight: normal;
3645
padding: 8px;
46+
background-color: #f6f9fb;
47+
position: relative;
48+
z-index: 1;
3749
}
3850
> tr,
3951
tr:nth-child(2n) {
@@ -47,6 +59,7 @@
4759
white-space: nowrap;
4860
text-overflow: ellipsis;
4961
word-break: keep-all;
62+
display: block;
5063
}
5164
&-tr-children-center {
5265
text-align: center;
@@ -57,6 +70,41 @@
5770
&-tr-children-right {
5871
text-align: right;
5972
}
73+
&-fixed-true {
74+
position: sticky !important;
75+
z-index: 2 !important;
76+
// border: 0; 透风 1px
77+
}
78+
&-fixed-right {
79+
position: sticky !important;
80+
z-index: 2 !important;
81+
// border: 0; 透风 1px
82+
}
83+
&-fixed-true::after {
84+
box-shadow: inset 10px 0 8px -8px rgb(0 0 0 / 15%);
85+
position: absolute;
86+
top: 0;
87+
right: 0;
88+
bottom: -1px;
89+
width: 30px;
90+
transform: translateX(100%);
91+
transition: box-shadow 0.3s;
92+
content: '';
93+
pointer-events: none;
94+
}
95+
&-fixed-right::after {
96+
box-shadow: inset -10px 0 8px -8px rgb(0 0 0 / 15%);
97+
position: absolute;
98+
top: 0;
99+
bottom: -1px;
100+
left: 0;
101+
width: 30px;
102+
transform: translateX(-100%);
103+
transition: box-shadow 0.3s;
104+
content: '';
105+
pointer-events: none;
106+
border-right: 1px solid #f0f0f0;
107+
}
60108
&-bordered {
61109
> table {
62110
tr > th,

‎packages/react-table/src/util.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { TableColumns } from './';
1+
import { TableColumns, LocationWidth } from './';
2+
import React from 'react';
23

34
/**
45
* Get colspan number
@@ -127,3 +128,13 @@ export function getAllColumnsKeys<T>(data: TableColumns<T>[], keys: TableColumns
127128
}
128129
return keys;
129130
}
131+
132+
export function locationFixed(
133+
fixed: boolean | 'left' | 'right',
134+
location: { [key: number]: LocationWidth },
135+
index: number,
136+
): React.CSSProperties {
137+
if (!fixed) return {};
138+
if (fixed === 'right') return { right: location[index]?.right };
139+
return { left: location[index]?.left };
140+
}

0 commit comments

Comments
 (0)
Please sign in to comment.