Skip to content

Commit

Permalink
feat(Table): Table 新增固定列属性 (#696)
Browse files Browse the repository at this point in the history
  • Loading branch information
cuilanxin committed Mar 21, 2022
1 parent d10f59a commit 92abb01
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 38 deletions.
82 changes: 71 additions & 11 deletions packages/react-table/README.md
Expand Up @@ -873,6 +873,64 @@ const Demo = () => (
ReactDOM.render(<Demo />, _mount_);
```

### 固定列

通过使用 fixed 使其列固定
> ⚠️ 注意: 若并没有 scroll 滚动条,fixed 属性并不会有直观的效果
<!--rehype:style=border-left: 8px solid #ffe564;background-color: #ffe56440;padding: 12px 16px;-->
<!--rehype:bgWhite=true&codeSandbox=true&codePen=true-->
```jsx
import ReactDOM from 'react-dom';
import { Table, Button } from 'uiw';

const columns = [
{
title: '姓名',
ellipsis: true,
// fixed: true,
width: 50,
key: 'name',
}, {
// fixed: true,
title: '年龄',
width: 50,
style: { color: 'red' },
key: 'age',
}, {
title: '地址',
width: 50,
key: 'info',
}, {
title: '操作',
key: 'edit',
width: 98,
fixed: 'right',
render: (text, key, rowData, rowNumber, columnNumber) => (
<div>
<Button size="small" type="danger">删除</Button>
<Button size="small" type="success">修改</Button>
</div>
),
},
];
const dataSource = [
{ name: '邓紫棋', age: '12', info: '又名G.E.M.,原名邓诗颖,1991年8月16日生于中国上海,中国香港创作型女歌手。', edit: '' },
{ name: '李易峰', age: '32', info: '1987年5月4日出生于四川成都,中国内地男演员、流行乐歌手、影视制片人', edit: '' },
{ name: '范冰冰', age: '23', info: '1981年9月16日出生于山东青岛,中国影视女演员、制片人、流行乐女歌手', edit: '' },
{ name: '杨幂', age: '34', info: '1986年9月12日出生于北京市,中国内地影视女演员、流行乐歌手、影视制片人。', edit: '' },
{ name: 'Angelababy', age: '54', info: '1989年2月28日出生于上海市,华语影视女演员、时尚模特。', edit: '' },
{ name: '唐嫣', age: '12', info: '1983年12月6日出生于上海市,毕业于中央戏剧学院表演系本科班', edit: '' },
{ name: '吴亦凡', age: '4', info: '1990年11月06日出生于广东省广州市,华语影视男演员、流行乐歌手。', edit: '' },
];
const Demo = () => (
<div>
<Table scroll={{x: 1200}} bordered columns={columns} data={dataSource} />
</div>
);
ReactDOM.render(<Demo />, _mount_);
```

## Props

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


### ColumnProps

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

### expandable

Expand All @@ -914,11 +974,11 @@ ReactDOM.render(<Demo />, _mount_);
|--------- |-------- |--------- |-------- |
| expandedRowRender | 自定义展开行| (record, index, expanded) => React.ReactNode | - |
| expandIcon | 自定义图标 | (expanded, record, index) => React.ReactNode; | - |
| rowExpandable | 是否允许展开| (record)=>boolean | - |
| defaultExpandAllRows | 初始时,是否展开所有行| boolean | false |
| rowExpandable | 是否允许展开| (record)=>Boolean | - |
| defaultExpandAllRows | 初始时,是否展开所有行| Boolean | false |
| defaultExpandedRowKeys | 初始时,默认展开的行 rowKey数组 | Array | - |
| expandedRowKeys | 控制展开的行 rowKey数组 | Array | - |
| onExpandedRowsChange | 展开的行变化触发 | (expandedRows)=>void | - |
| onExpand | 点击展开图标触发 | (expanded,record,index)=>void | - |
| indentSize | 控制树形结构每一层的缩进宽度 | number | 16 |
| childrenColumnName | 指定树形结构的列名 | string | children |
| indentSize | 控制树形结构每一层的缩进宽度 | Number | 16 |
| childrenColumnName | 指定树形结构的列名 | String | children |
26 changes: 19 additions & 7 deletions packages/react-table/src/TableTr.tsx
@@ -1,8 +1,9 @@
import React, { useMemo, useState, useEffect } from 'react';
import Icon from '@uiw/react-icon';
import { TableProps } from './';
import { LocationWidth, TableProps } from './';
import './style/index.less';
import { noop } from '@uiw/utils';
import { locationFixed } from './util';

interface TableTrProps<T> {
rowKey?: keyof T;
Expand All @@ -18,6 +19,7 @@ interface TableTrProps<T> {
// 层级
hierarchy: number;
childrenColumnName: string;
locationWidth: { [key: number]: LocationWidth };
}

export default function TableTr<T extends { [key: string]: any }>(props: TableTrProps<T>) {
Expand All @@ -33,6 +35,7 @@ export default function TableTr<T extends { [key: string]: any }>(props: TableTr
hierarchy,
indentSize,
childrenColumnName,
locationWidth,
} = props;
const [isOpacity, setIsOpacity] = useState(false);
const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);
Expand Down Expand Up @@ -88,9 +91,6 @@ export default function TableTr<T extends { [key: string]: any }>(props: TableTr
}
}
}
if (ellipsis && ellipsis[keyName.key!]) {
objs.className = `${prefixCls}-ellipsis`;
}
const isHasChildren = Array.isArray(trData[childrenColumnName]);
if (colNum === 0 && (isOpacity || hierarchy || isHasChildren)) {
objs.children = (
Expand All @@ -101,13 +101,25 @@ export default function TableTr<T extends { [key: string]: any }>(props: TableTr
</>
);
}
if (keyName.fixed) {
if (keyName.fixed === 'right') {
objs.className = `${objs.className} ${prefixCls}-fixed-right`;
} else {
objs.className = `${objs.className} ${prefixCls}-fixed-true`;
}
}
return (
<td
{...objs}
style={{ ...locationFixed(keyName.fixed!, locationWidth, colNum) }}
children={
<span className={ellipsis && ellipsis[keyName.key!] ? `${prefixCls}-ellipsis` : undefined}>
{objs.children}
</span>
}
key={colNum}
// style={keyName?.style}
className={`${objs.className || ''} ${prefixCls}-tr-children-${keyName.align || 'left'} ${
keyName.className || ''
className={`${prefixCls}-tr-children-${keyName.align || 'left'} ${keyName.className || ''} ${
objs.className || ''
}`}
onClick={(evn) => onCell(trData, { rowNum, colNum, keyName: keyName.key! }, evn)}
/>
Expand Down
45 changes: 45 additions & 0 deletions packages/react-table/src/ThComponent.tsx
@@ -0,0 +1,45 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { TableColumns, TableProps, LocationWidth } from './';
import { locationFixed } from './util';

interface ThComponentProps<T> {
colNum: number;
item: TableColumns<T>;
prefixCls: string;
titleNode: JSX.Element;
onCellHead: TableProps<T>['onCellHead'];
rowNum: number;
locationWidth: { [key: number]: LocationWidth };
updateLocation: (params: LocationWidth, index: number) => void;
}
export default class ThComponent<T> extends Component<ThComponentProps<T>> {
componentDidMount() {
const rect = ReactDOM.findDOMNode(this);
this.props.updateLocation({ width: (rect as Element).getBoundingClientRect().width }, this.props.colNum);
}

render() {
const { colNum, prefixCls, item, titleNode, onCellHead, rowNum, locationWidth } = this.props;
const { title, key, render, children, ellipsis, fixed = false, ...thProps } = item;
let cls = '';
if (fixed) {
if (fixed === 'right') {
cls = prefixCls + '-fixed-right';
} else {
cls = prefixCls + '-fixed-true';
}
}
return (
<th
key={colNum}
{...thProps}
style={{ ...thProps.style, ...locationFixed(fixed, locationWidth, colNum) }}
className={`${prefixCls}-tr-children-${item?.align || 'left'} ${item.className || ''} ${cls}`}
onClick={(evn) => onCellHead?.(item, colNum, rowNum!, evn)}
>
{titleNode}
</th>
);
}
}
44 changes: 29 additions & 15 deletions packages/react-table/src/Thead.tsx
@@ -1,44 +1,58 @@
import React from 'react';
import { IProps, noop } from '@uiw/utils';
import { TableProps, TableColumns } from './';
import { TableProps, TableColumns, LocationWidth } from './';
import './style/index.less';
import ThComponentProps from './ThComponent';

export interface TheadProps<T extends { [key: string]: V }, V = any> extends IProps {
data?: TableColumns<T>[][];
onCellHead?: TableProps<T, V>['onCellHead'];
align?: TableColumns['align'];
className?: TableColumns['className'];
locationWidth?: { [key: number]: LocationWidth };
updateLocation?: (params: LocationWidth, index: number) => void;
}

export default function TheadComponent<T extends { [key: string]: V }, V>(
props: TheadProps<T, V> & React.HTMLAttributes<HTMLTableSectionElement> = {},
) {
const { prefixCls = 'w-table', className, data = [], onCellHead = noop, ...other } = props;
const {
prefixCls = 'w-table',
className,
data = [],
onCellHead = noop,
locationWidth,
updateLocation,
...other
} = props;
return (
<thead className={[prefixCls, className].filter(Boolean).join(' ').trim()} {...other}>
{data &&
data.length > 0 &&
data.map((tds?: TableColumns<T>[], rowNum?: number) => (
<tr key={rowNum}>
{(tds || []).map((item, colNum) => {
const { title, key, render, children, ellipsis, ...thProps } = item;
const titleNode: TableColumns<T>['title'] =
typeof title === 'function' ? title(item, colNum, rowNum!) : title;
const { title, key, render, children, ellipsis, fixed = false, ...thProps } = item;
const titleNode: TableColumns<T>['title'] = (
<span className={ellipsis ? `${thProps.className || ''} ${prefixCls}-ellipsis` : undefined}>
{typeof title === 'function' ? title(item, colNum, rowNum!) : title}
</span>
);
if (thProps.colSpan === 0) {
return null;
}
if (ellipsis) {
thProps.className = `${thProps.className || ''} ${prefixCls}-ellipsis`;
}
return (
<th
<ThComponentProps
colNum={colNum}
item={item}
key={colNum}
{...thProps}
className={`${prefixCls}-tr-children-${item?.align || 'left'} ${className || ''}`}
onClick={(evn) => onCellHead(item, colNum, rowNum!, evn)}
>
{titleNode}
</th>
prefixCls={prefixCls}
onCellHead={onCellHead}
rowNum={rowNum!}
titleNode={titleNode}
locationWidth={locationWidth!}
updateLocation={updateLocation!}
/>
);
})}
</tr>
Expand Down
49 changes: 47 additions & 2 deletions packages/react-table/src/index.tsx
@@ -1,4 +1,4 @@
import React, { useMemo, useState, useEffect } from 'react';
import React, { useMemo, useState, useEffect, useRef } from 'react';
import { IProps, HTMLDivProps, noop } from '@uiw/utils';
import Icon from '@uiw/react-icon';
import Thead from './Thead';
Expand Down Expand Up @@ -42,6 +42,7 @@ export type TableColumns<T = any> = {
style?: React.CSSProperties;
align?: 'left' | 'center' | 'right';
className?: string;
fixed?: boolean | 'left' | 'right';
[key: string]: any;
};

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

export interface LocationWidth {
left?: number;
right?: number;
width: number;
}
export interface ICellOptions {
rowNum: number;
colNum: number;
Expand All @@ -93,6 +99,37 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
...other
} = props;
const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);
const [locationWidth, setLocationWidth] = useState<{ [key: number]: LocationWidth }>({});
const finalLocationWidth = useRef<{ [key: number]: LocationWidth }>({});
const updateLocation = (params: LocationWidth, index: number) => {
finalLocationWidth.current = {
...finalLocationWidth.current,
[index]: {
...finalLocationWidth.current[index],
...params,
},
};
if (index === columns.length - 1) {
setLocationWidth(computed());
}
};
const computed = () => {
let left = 0,
right = 0;
for (let index = 0; index < columns.length; index++) {
if (finalLocationWidth.current[index]) {
finalLocationWidth.current[index].left = left;
left = finalLocationWidth.current[index].width + left;
}
}
for (let index = columns.length - 1; index > -1; index--) {
if (finalLocationWidth.current[index]) {
finalLocationWidth.current[index].right = right;
right = finalLocationWidth.current[index].width + right;
}
}
return finalLocationWidth.current;
};
useEffect(() => {
if (expandable) {
if (expandable.defaultExpandAllRows) {
Expand Down Expand Up @@ -215,11 +252,19 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
<div className={cls} {...other} style={{ ...other.style, ...style.div }}>
<table style={{ tableLayout: ellipsis ? 'fixed' : 'auto', ...style.table }}>
{title && <caption>{title}</caption>}
{columns && columns.length > 0 && <Thead onCellHead={onCellHead} data={header} />}
{columns && columns.length > 0 && (
<Thead
onCellHead={onCellHead}
data={header}
locationWidth={locationWidth}
updateLocation={updateLocation}
/>
)}
{data && data.length > 0 && (
<tbody>
<TableTr
rowKey={rowKey}
locationWidth={locationWidth}
data={data}
keys={self.keys}
render={render}
Expand Down

0 comments on commit 92abb01

Please sign in to comment.