Skip to content

Commit

Permalink
feat(Table): Table组件添加可展开属性 (#664)
Browse files Browse the repository at this point in the history
  • Loading branch information
cuilanxin committed Mar 15, 2022
1 parent d894734 commit 5d1d047
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 40 deletions.
73 changes: 73 additions & 0 deletions packages/react-table/README.md
Expand Up @@ -668,6 +668,62 @@ const Demo = () => (
ReactDOM.render(<Demo />, _mount_);
```

### 可展开

<!--rehype:bgWhite=true&codeSandbox=true&codePen=true-->
```jsx
import ReactDOM from 'react-dom';
import { Table, Button, Icon } from 'uiw';

const columns = [
{
title: '姓名',
ellipsis: true,
key: 'name',
},
{
title: '年龄',
style: { color: 'red' },
key: 'age',
},
{
title: '操作',
key: 'edit',
width: 98,
render: (text, key, rowData, rowNumber, columnNumber) => (
<div>
<Button size="small" type="danger">删除</Button>
<Button size="small" type="success">修改</Button>
</div>
),
},
];
const dataSource = [
{ name: '邓紫棋', age: '10', id: '1'},
{ name: '李易峰', age: '32', id: '2'},
{ name: '范冰冰', age: '23', id: '3'},
];
const Demo = () => (
<div>
<Table
rowKey="id"
expandable={{
expandedRowRender: (record, index, expanded)=>{
return <div>{record.name}</div>
},
// defaultExpandAllRows: true,
rowExpandable: (r)=>r.name!=='李易峰',
expandIcon: (expanded) => expanded ? <Icon type="minus-circle-o"/> : <Icon type="plus-circle-o"/>,
defaultExpandedRowKeys: ["1"]
}}
columns={columns}
data={dataSource}
/>
</div>
);
ReactDOM.render(<Demo />, _mount_);
```

## Props

### Table
Expand All @@ -682,6 +738,8 @@ ReactDOM.render(<Demo />, _mount_);
| 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+` | - |
| expandable | 可展开配置 | ExpandableType | - |
| rowKey | 表格行 key 的取值 | string | - |

### ColumnProps

Expand All @@ -695,3 +753,18 @@ ReactDOM.render(<Demo />, _mount_);
| colSpan | 合并表头行。| Number | - |
| ellipsis | 超过宽度将自动省略。`v4.8.7+`| Boolean | `false` |
| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前值的 `key`,行索引数据,当前行号,当前列号。| `Function(text, key, rowData, rowNumber, columnNumber)` | - |

### expandable

注意 expandedRowKeys 与 onExpandedRowsChange 必须成对出现

| 参数 | 说明 | 类型 | 默认值 |
|--------- |-------- |--------- |-------- |
| expandedRowRender | 自定义展开行| (record, index, expanded) => React.ReactNode | - |
| expandIcon | 自定义图标 | (expanded, record, index) => React.ReactNode; | - |
| rowExpandable | 是否允许展开| (record)=>boolean | - |
| defaultExpandAllRows | 初始时,是否展开所有行| boolean | false |
| defaultExpandedRowKeys | 初始时,默认展开的行 rowKey数组 | Array | - |
| expandedRowKeys | 控制展开的行 rowKey数组 | Array | - |
| onExpandedRowsChange | 展开的行变化触发 | (expandedRows)=>void | - |
| onExpand | 点击展开图标触发 | (expanded,record,index)=>void | - |
1 change: 1 addition & 0 deletions packages/react-table/package.json
Expand Up @@ -44,6 +44,7 @@
"react-dom": ">=16.9.0"
},
"dependencies": {
"@uiw/react-icon": "^4.13.11",
"@uiw/utils": "^4.13.12"
}
}
22 changes: 22 additions & 0 deletions packages/react-table/src/Expandable.tsx
@@ -0,0 +1,22 @@
import React, {useState} from 'react';

interface ExpandableProps<T> {
onClick: (expand: boolean)=>void;
expandIcon: (expanded: boolean) => React.ReactNode,
defaultExpand: boolean,
}
/**
* 可展开配置
*/
export default function ExpandableComponent<T>({defaultExpand,onClick, expandIcon}: ExpandableProps<T>) {
const [ expand, setExpand ] = useState<boolean>(defaultExpand)
return <div
style={{display: 'flex',justifyContent:'center',alignItems:'center'}}
onClick={()=>{
setExpand(!expand)
onClick(expand)
// record: T, index: number, expanded: boolean
}}
children={expandIcon(expand)}
/>
}
2 changes: 1 addition & 1 deletion packages/react-table/src/Thead.tsx
Expand Up @@ -4,7 +4,7 @@ import { TableProps, TableColumns } from './';
import './style/index.less';

export interface TheadProps<T extends { [key: string]: V }, V = any> extends IProps {
data?: TableColumns<T, V>[][];
data?: TableColumns<T>[][];
onCellHead?: TableProps<T, V>['onCellHead'];
}

Expand Down
197 changes: 160 additions & 37 deletions packages/react-table/src/index.tsx
@@ -1,24 +1,46 @@
import React from 'react';
import React, { useMemo, useState, useEffect } from 'react';
import { IProps, HTMLDivProps, noop } from '@uiw/utils';
import Icon from '@uiw/react-icon';
import Thead from './Thead';
import { getLevelItems, getAllColumnsKeys } from './util';
import ExpandableComponent from './Expandable';
import './style/index.less';

export type TableColumns<T = any, V = any> = {
title?: string | ((data: TableColumns<T, V>, rowNum: number, colNum: number) => JSX.Element) | JSX.Element;
// 展开配置
export interface ExpandableType<T> {
// 展开行
expandedRowRender?: (record: T, index: number, expanded: boolean) => React.ReactNode;
// 自定义图标
expandIcon?: (expanded: boolean, record: T, index: number) => React.ReactNode;
// 是否允许展开
rowExpandable?: (record: T) => boolean;
// 初始时,是否展开所有行
defaultExpandAllRows?: boolean;
// 默认展开的行 rowKey数组
defaultExpandedRowKeys?: Array<T[keyof T] | number>;
// 控制展开的行 rowKey数组
expandedRowKeys?: Array<T[keyof T] | number>;
// 展开的行变化触发
onExpandedRowsChange?: (expandedRows: Array<T[keyof T] | number>) => void;
// 点击展开图标触发
onExpand?: (expanded: boolean, record: T, index: number) => void;
}

export type TableColumns<T = any> = {
title?: string | ((data: TableColumns<T>, rowNum: number, colNum: number) => JSX.Element) | JSX.Element;
key?: string;
width?: number;
colSpan?: number;
children?: TableColumns<T, V>[];
children?: TableColumns<T>[];
ellipsis?: boolean;
render?: (text: V, keyName: V, rowData: T, rowNumber: number, columnNumber: number) => React.ReactNode;
render?: (text: string, keyName: string, rowData: T, rowNumber: number, columnNumber: number) => React.ReactNode;
style?: React.CSSProperties;
[key: string]: any;
};

export interface TableProps<T extends { [key: string]: V } = any, V = any> extends IProps, Omit<HTMLDivProps, 'title'> {
prefixCls?: string;
columns?: TableColumns<T, V>[];
columns?: TableColumns<T>[];
data?: Array<T>;
title?: React.ReactNode;
footer?: React.ReactNode;
Expand All @@ -30,19 +52,20 @@ export interface TableProps<T extends { [key: string]: V } = any, V = any> exten
evn: React.MouseEvent<HTMLTableCellElement>,
) => void | React.ReactNode;
onCellHead?: (
data: TableColumns<T, V>,
data: TableColumns<T>,
rowNum: number,
colNum: number,
evn: React.MouseEvent<HTMLTableCellElement>,
) => void;
expandable?: ExpandableType<T>;
rowKey?: keyof T;
}

export interface ICellOptions {
rowNum: number;
colNum: number;
keyName: string;
}

export default function Table<T extends { [key: string]: V }, V>(props: TableProps<T, V> = {}) {
const {
prefixCls = 'w-table',
Expand All @@ -56,12 +79,103 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
onCellHead = noop,
empty,
children,
expandable,
rowKey,
...other
} = props;
const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);
useEffect(() => {
if (expandable) {
if (expandable.defaultExpandAllRows) {
setExpandIndex(data.map((it, index) => (rowKey ? it[rowKey] : index)));
return;
}
if (expandable.defaultExpandedRowKeys) {
setExpandIndex(expandable.defaultExpandedRowKeys);
return;
}
}
}, []);
useEffect(() => {
if (expandable) {
if (expandable.expandedRowKeys && JSON.stringify(expandable.expandedRowKeys) !== JSON.stringify(expandIndex)) {
setExpandIndex(expandable.expandedRowKeys);
}
}
}, [expandable?.expandedRowKeys]);

const isExpandedDom = useMemo(() => {
return (record: T, index: number) => {
if (!expandable) {
return false;
}
if (!expandable.expandedRowRender) {
return false;
}
let flag = true;
if (expandable.rowExpandable) {
flag = expandable.rowExpandable(record);
}
return (
flag && (
<tr style={expandIndex.includes(rowKey ? record[rowKey] : index) ? {} : { display: 'none' }}>
<td style={{ paddingLeft: 16 }} colSpan={columns.length + 1}>
{expandable.expandedRowRender(record, index, true)}
</td>
</tr>
)
);
};
}, [expandable, expandIndex]);
let keys = getAllColumnsKeys<T>(columns);
let selfColumns: TableColumns<T>[] = [];
if (expandable?.expandedRowRender) {
keys = ['uiw-expanded', ...keys];
selfColumns = [
{
title: '',
key: 'uiw-expanded',
width: 50,
align: 'center',
render: (text, key, record, index) => {
return (
<ExpandableComponent
defaultExpand={
expandable.defaultExpandAllRows === undefined
? !!expandable.defaultExpandedRowKeys?.includes(rowKey ? record[rowKey] : index)
: !!expandable.defaultExpandAllRows
}
onClick={(expand) => {
expandable.onExpand?.(expand, record, index);
if (expand) {
const result = expandIndex.filter((it) => (rowKey ? it !== record[rowKey] : it !== index));
expandable.onExpandedRowsChange ? expandable.onExpandedRowsChange(result) : setExpandIndex(result);
} else {
const result = [...expandIndex, rowKey ? record[rowKey] : index];
expandable.onExpandedRowsChange ? expandable.onExpandedRowsChange(result) : setExpandIndex(result);
}
}}
expandIcon={(expand) => {
if (expandable.rowExpandable && !expandable.rowExpandable?.(record)) {
return null;
}
if (expandable.expandIcon) {
return expandable.expandIcon(expand, record, index);
}
return expand ? <Icon type="minus-square-o" /> : <Icon type="plus-square-o" />;
}}
/>
);
},
},
...columns,
];
} else {
selfColumns = [...columns];
}
const cls = [prefixCls, className, bordered ? `${prefixCls}-bordered` : null].filter(Boolean).join(' ').trim();
const { header, render, ellipsis } = getLevelItems(columns);
const keys = getAllColumnsKeys<T>(columns);
const { header, render, ellipsis } = getLevelItems(selfColumns);

return (
<div>
<div style={{ overflowY: 'scroll' }} className={cls} {...other}>
Expand All @@ -70,35 +184,44 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
{columns && columns.length > 0 && <Thead onCellHead={onCellHead} data={header} />}
{data && data.length > 0 && (
<tbody>
{data.map((trData, rowNum) => (
<tr key={rowNum}>
{keys.map((keyName, colNum) => {
let objs: React.TdHTMLAttributes<HTMLTableDataCellElement> = {
children: trData[keyName],
};
if (render[keyName]) {
const child = render[keyName](trData[keyName], keyName, trData, rowNum, colNum);
if (React.isValidElement(child)) {
objs.children = child;
} else {
if (child.props) {
objs = { ...child.props, children: objs.children };
if (child.props.rowSpan === 0 || child.props.colSpan === 0) return null;
{data.map((trData, rowNum) => {
return (
<React.Fragment key={rowNum}>
<tr key={rowKey ? trData[rowKey] + '' : rowNum}>
{keys.map((keyName, colNum) => {
let objs: React.TdHTMLAttributes<HTMLTableDataCellElement> = {
children: trData[keyName],
};
if (render[keyName]) {
const child = render[keyName](trData[keyName], keyName, trData, rowNum, colNum);
if (React.isValidElement(child)) {
objs.children = child;
} else {
if (child.props) {
objs = { ...child.props, children: objs.children };
if (child.props.rowSpan === 0 || child.props.colSpan === 0) return null;
}
if (child.children) {
objs.children = child.children;
}
}
}
if (child.children) {
objs.children = child.children;
if (ellipsis && ellipsis[keyName]) {
objs.className = `${prefixCls}-ellipsis`;
}
}
}
if (ellipsis && ellipsis[keyName]) {
objs.className = `${prefixCls}-ellipsis`;
}
return (
<td {...objs} key={colNum} onClick={(evn) => onCell(trData, { rowNum, colNum, keyName }, evn)} />
);
})}
</tr>
))}
return (
<td
{...objs}
key={colNum}
onClick={(evn) => onCell(trData, { rowNum, colNum, keyName }, evn)}
/>
);
})}
</tr>
{isExpandedDom(trData, rowNum)}
</React.Fragment>
);
})}
</tbody>
)}
{data && data.length === 0 && empty && (
Expand Down
2 changes: 1 addition & 1 deletion packages/react-table/src/util.ts
Expand Up @@ -126,4 +126,4 @@ export function getAllColumnsKeys<T>(data: TableColumns<T>[], keys: any[] = []):
}
}
return keys;
}
}

0 comments on commit 5d1d047

Please sign in to comment.