Skip to content

Commit

Permalink
feat(Table): Table 新增树形数据结构展示功能 #539 (#679)
Browse files Browse the repository at this point in the history
  • Loading branch information
cuilanxin committed Mar 16, 2022
1 parent 1f4d321 commit 6026664
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 85 deletions.
83 changes: 83 additions & 0 deletions packages/react-table/README.md
Expand Up @@ -724,6 +724,87 @@ const Demo = () => (
ReactDOM.render(<Demo />, _mount_);
```

### 树形数据展示

表格支持树形数据的展示,当数据中有 children 字段时会自动展示为树形表格,如果不需要或配置为其他字段可以用 childrenColumnName 进行配置。

可以通过设置 indentSize 以控制每一层的缩进宽度

> ⚠️ 注意: 树形数据展示和`expandable.expandedRowRender`请不要同时出现,后续或将支持
<!--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 React from 'react';
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',
children: [
{
name: '邓紫棋-0-1',
age: '10',
id: '1-1',
children: [
{ name: '邓紫棋-0-1-1', age: '10', id: '1-1-1',},
{ name: '邓紫棋-0-1-2', age: '10', id: '1-1-2',}
]
},
{name: '邓紫棋-0-2', age: '10', id: '1-1'},
{name: '邓紫棋-0-3', age: '10', id: '1-1'},
]
},
{ name: '李易峰', age: '32', id: '2',},
{ name: '范冰冰', age: '23', id: '3',
children: [
{name: '范冰冰0-1', age: '23', id: '3-1'},
{name: '范冰冰0-2', age: '23', id: '3-2'},
{name: '范冰冰0-3', age: '23', id: '3-3'},
]
},
];
const Demo = () => {
const [expandedRowKeys, setExpandedRowKeys] = React.useState([])
return (
<div>
<Table
rowKey="id"
columns={columns}
data={dataSource}
/>
</div>
)
};
ReactDOM.render(<Demo />, _mount_);
```

## Props

### Table
Expand Down Expand Up @@ -768,3 +849,5 @@ ReactDOM.render(<Demo />, _mount_);
| expandedRowKeys | 控制展开的行 rowKey数组 | Array | - |
| onExpandedRowsChange | 展开的行变化触发 | (expandedRows)=>void | - |
| onExpand | 点击展开图标触发 | (expanded,record,index)=>void | - |
| indentSize | 控制树形结构每一层的缩进宽度 | number | 16 |
| childrenColumnName | 指定树形结构的列名 | string | children |
109 changes: 109 additions & 0 deletions packages/react-table/src/TableTr.tsx
@@ -0,0 +1,109 @@
import React, { useMemo, useState } from 'react';
import Icon from '@uiw/react-icon';
import { TableProps } from './';
import './style/index.less';
import { noop } from '@uiw/utils';

interface TableTrProps<T> {
rowKey?: keyof T;
data: T[];
keys: string[];
render: { [key: string]: any };
ellipsis?: Record<string, boolean>;
prefixCls: string;
onCell: TableProps['onCell'];
isExpandedDom: (record: T, index: number) => false | JSX.Element;
// 控制树形结构每一层的缩进宽度
indentSize: number;
// 层级
hierarchy: number;
childrenColumnName: string;
}

export default function TableTr<T extends { [key: string]: any }>(props: TableTrProps<T>) {
const {
rowKey,
data,
keys,
render,
ellipsis,
prefixCls,
onCell = noop,
isExpandedDom,
hierarchy,
indentSize,
childrenColumnName,
} = props;

const [expandIndex, setExpandIndex] = useState<Array<T[keyof T] | number>>([]);

const IconDom = useMemo(() => {
return (key: T[keyof T] | number, isOpacity: boolean) => {
const flag = expandIndex.includes(key);
return (
<Icon
type={flag ? 'minus-square-o' : 'plus-square-o'}
style={{ marginRight: 10, opacity: isOpacity ? 1 : 0, marginLeft: hierarchy * indentSize }}
onClick={() => {
setExpandIndex(flag ? expandIndex.filter((it) => it !== key) : [...expandIndex, key]);
}}
/>
);
};
}, [expandIndex]);
if (!Array.isArray(data) || !data.length) {
return null;
}
return (
<React.Fragment>
{data.map((trData, rowNum) => {
const key = rowKey ? trData[rowKey] : rowNum;
return (
<React.Fragment key={rowNum}>
<tr key={key}>
{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 (ellipsis && ellipsis[keyName]) {
objs.className = `${prefixCls}-ellipsis`;
}
const isHasChildren = Array.isArray(trData[childrenColumnName]);
if (colNum === 0 && (hierarchy || isHasChildren)) {
objs.className = `${objs.className} ${prefixCls}-has-children`;
objs.children = (
<>
{IconDom(key, isHasChildren)}
{objs.children}
</>
);
}
return (
<td {...objs} key={colNum} onClick={(evn) => onCell(trData, { rowNum, colNum, keyName }, evn)} />
);
})}
</tr>
{expandIndex.includes(key) && (
<TableTr {...props} data={trData[childrenColumnName]} hierarchy={hierarchy + 1} />
)}
{isExpandedDom(trData, rowNum)}
</React.Fragment>
);
})}
</React.Fragment>
);
}
158 changes: 73 additions & 85 deletions packages/react-table/src/index.tsx
Expand Up @@ -4,6 +4,7 @@ import Icon from '@uiw/react-icon';
import Thead from './Thead';
import { getLevelItems, getAllColumnsKeys } from './util';
import ExpandableComponent from './Expandable';
import TableTr from './TableTr';
import './style/index.less';

// 展开配置
Expand All @@ -16,14 +17,18 @@ export interface ExpandableType<T> {
rowExpandable?: (record: T) => boolean;
// 初始时,是否展开所有行
defaultExpandAllRows?: boolean;
// 默认展开的行 rowKey数组
// 默认展开的行 rowKey数组
defaultExpandedRowKeys?: Array<T[keyof T] | number>;
// 控制展开的行 rowKey数组
// 控制展开的行 rowKey数组
expandedRowKeys?: Array<T[keyof T] | number>;
// 展开的行变化触发
onExpandedRowsChange?: (expandedRows: Array<T[keyof T] | number>) => void;
// 点击展开图标触发
onExpand?: (expanded: boolean, record: T, index: number) => void;
// 控制树形结构每一层的缩进宽度
indentSize?: number;
// 指定树形结构的列名
childrenColumnName?: string;
}

export type TableColumns<T = any> = {
Expand Down Expand Up @@ -127,54 +132,62 @@ export default function Table<T extends { [key: string]: V }, V>(props: TablePro
);
};
}, [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);

const self = useMemo(() => {
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
}
return expand ? <Icon type="minus-square-o" /> : <Icon type="plus-square-o" />;
}}
/>
);
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];
}
...columns,
];
} else {
selfColumns = [...columns];
}
return {
keys,
selfColumns,
};
}, [columns, expandIndex]);

const cls = [prefixCls, className, bordered ? `${prefixCls}-bordered` : null].filter(Boolean).join(' ').trim();
const { header, render, ellipsis } = getLevelItems(selfColumns);
const { header, render, ellipsis } = getLevelItems(self.selfColumns);

return (
<div>
Expand All @@ -184,44 +197,19 @@ 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) => {
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 (ellipsis && ellipsis[keyName]) {
objs.className = `${prefixCls}-ellipsis`;
}
return (
<td
{...objs}
key={colNum}
onClick={(evn) => onCell(trData, { rowNum, colNum, keyName }, evn)}
/>
);
})}
</tr>
{isExpandedDom(trData, rowNum)}
</React.Fragment>
);
})}
<TableTr
rowKey={rowKey}
data={data}
keys={self.keys}
render={render}
ellipsis={ellipsis}
prefixCls={prefixCls}
onCell={onCell}
hierarchy={0}
isExpandedDom={isExpandedDom}
indentSize={expandable?.indentSize || 16}
childrenColumnName={expandable?.childrenColumnName || 'children'}
/>
</tbody>
)}
{data && data.length === 0 && empty && (
Expand Down

0 comments on commit 6026664

Please sign in to comment.