From a196114661444e7afad152b681da48a5dc7d62f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E9=91=AB?= <1192065030@qq.com> Date: Wed, 16 Mar 2022 22:19:47 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat(Table):=20Table=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=A0=91=E5=BD=A2=E6=95=B0=E6=8D=AE=E7=BB=93=E6=9E=84=E5=B1=95?= =?UTF-8?q?=E7=A4=BA=E5=8A=9F=E8=83=BD=20#539?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-table/README.md | 83 ++++++++++++ packages/react-table/src/TableTr.tsx | 109 +++++++++++++++ packages/react-table/src/index.tsx | 158 ++++++++++------------ packages/react-table/src/style/index.less | 4 + 4 files changed, 269 insertions(+), 85 deletions(-) create mode 100644 packages/react-table/src/TableTr.tsx diff --git a/packages/react-table/README.md b/packages/react-table/README.md index 1c7ed51b03..fd12557402 100644 --- a/packages/react-table/README.md +++ b/packages/react-table/README.md @@ -724,6 +724,87 @@ const Demo = () => ( ReactDOM.render(, _mount_); ``` +### 树形数据展示 + +表格支持树形数据的展示,当数据中有 children 字段时会自动展示为树形表格,如果不需要或配置为其他字段可以用 childrenColumnName 进行配置。 + +可以通过设置 indentSize 以控制每一层的缩进宽度 + +> ⚠️ 注意: 树形数据展示和`expandable.expandedRowRender`请不要同时出现,后续或将支持 + + + +```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) => ( +
+ + +
+ ), + }, +]; +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 ( +
+ + + ) +}; +ReactDOM.render(, _mount_); +``` + ## Props ### Table @@ -768,3 +849,5 @@ ReactDOM.render(, _mount_); | expandedRowKeys | 控制展开的行 rowKey数组 | Array | - | | onExpandedRowsChange | 展开的行变化触发 | (expandedRows)=>void | - | | onExpand | 点击展开图标触发 | (expanded,record,index)=>void | - | +| indentSize | 控制树形结构每一层的缩进宽度 | number | 16 | +| childrenColumnName | 指定树形结构的列名 | string | children | diff --git a/packages/react-table/src/TableTr.tsx b/packages/react-table/src/TableTr.tsx new file mode 100644 index 0000000000..0e32bece77 --- /dev/null +++ b/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 { + rowKey?: keyof T; + data: T[]; + keys: string[]; + render: { [key: string]: any }; + ellipsis?: Record; + prefixCls: string; + onCell: TableProps['onCell']; + isExpandedDom: (record: T, index: number) => false | JSX.Element; + // 控制树形结构每一层的缩进宽度 + indentSize: number; + // 层级 + hierarchy: number; + childrenColumnName: string; +} + +export default function TableTr(props: TableTrProps) { + const { + rowKey, + data, + keys, + render, + ellipsis, + prefixCls, + onCell = noop, + isExpandedDom, + hierarchy, + indentSize, + childrenColumnName, + } = props; + + const [expandIndex, setExpandIndex] = useState>([]); + + const IconDom = useMemo(() => { + return (key: T[keyof T] | number, isOpacity: boolean) => { + const flag = expandIndex.includes(key); + return ( + { + setExpandIndex(flag ? expandIndex.filter((it) => it !== key) : [...expandIndex, key]); + }} + /> + ); + }; + }, [expandIndex]); + if (!Array.isArray(data) || !data.length) { + return null; + } + return ( + + {data.map((trData, rowNum) => { + const key = rowKey ? trData[rowKey] : rowNum; + return ( + + + {keys.map((keyName, colNum) => { + let objs: React.TdHTMLAttributes = { + 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 ( + + {expandIndex.includes(key) && ( + + )} + {isExpandedDom(trData, rowNum)} + + ); + })} + + ); +} diff --git a/packages/react-table/src/index.tsx b/packages/react-table/src/index.tsx index 63a418845f..9ca34ca989 100644 --- a/packages/react-table/src/index.tsx +++ b/packages/react-table/src/index.tsx @@ -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'; // 展开配置 @@ -16,14 +17,18 @@ export interface ExpandableType { rowExpandable?: (record: T) => boolean; // 初始时,是否展开所有行 defaultExpandAllRows?: boolean; - // 默认展开的行 rowKey数组 + // 默认展开的行 rowKey数组 defaultExpandedRowKeys?: Array; - // 控制展开的行 rowKey数组 + // 控制展开的行 rowKey数组 expandedRowKeys?: Array; // 展开的行变化触发 onExpandedRowsChange?: (expandedRows: Array) => void; // 点击展开图标触发 onExpand?: (expanded: boolean, record: T, index: number) => void; + // 控制树形结构每一层的缩进宽度 + indentSize?: number; + // 指定树形结构的列名 + childrenColumnName?: string; } export type TableColumns = { @@ -127,54 +132,62 @@ export default function Table(props: TablePro ); }; }, [expandable, expandIndex]); - let keys = getAllColumnsKeys(columns); - let selfColumns: TableColumns[] = []; - if (expandable?.expandedRowRender) { - keys = ['uiw-expanded', ...keys]; - selfColumns = [ - { - title: '', - key: 'uiw-expanded', - width: 50, - align: 'center', - render: (text, key, record, index) => { - return ( - { - 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(columns); + let selfColumns: TableColumns[] = []; + if (expandable?.expandedRowRender) { + keys = ['uiw-expanded', ...keys]; + selfColumns = [ + { + title: '', + key: 'uiw-expanded', + width: 50, + align: 'center', + render: (text, key, record, index) => { + return ( + : ; - }} - /> - ); + 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 ? : ; + }} + /> + ); + }, }, - }, - ...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 (
@@ -184,44 +197,19 @@ export default function Table(props: TablePro {columns && columns.length > 0 &&
} {data && data.length > 0 && ( - {data.map((trData, rowNum) => { - return ( - - - {keys.map((keyName, colNum) => { - let objs: React.TdHTMLAttributes = { - 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 ( - - {isExpandedDom(trData, rowNum)} - - ); - })} + )} {data && data.length === 0 && empty && ( diff --git a/packages/react-table/src/style/index.less b/packages/react-table/src/style/index.less index 148b44a12e..d2952a6495 100644 --- a/packages/react-table/src/style/index.less +++ b/packages/react-table/src/style/index.less @@ -48,6 +48,10 @@ text-overflow: ellipsis; word-break: keep-all; } + &-has-children { + display: flex; + align-items: center; + } &-bordered { > table { tr > th, From 8a0608220248beb1b15cb46b03e835d1590cf2ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E9=91=AB?= <1192065030@qq.com> Date: Fri, 18 Mar 2022 11:59:45 +0800 Subject: [PATCH 2/7] =?UTF-8?q?feat(Table):=20Table=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20scroll=20=E5=B1=9E=E6=80=A7=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E5=86=97=E4=BD=99=20div=20#687?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-table/README.md | 62 +++++++++++++++++++++++++++- packages/react-table/src/TableTr.tsx | 4 +- packages/react-table/src/aaa.tsx | 42 +++++++++++++++++++ packages/react-table/src/index.tsx | 31 +++++++++++--- 4 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 packages/react-table/src/aaa.tsx diff --git a/packages/react-table/README.md b/packages/react-table/README.md index 9e2dab1954..f964c3b2de 100644 --- a/packages/react-table/README.md +++ b/packages/react-table/README.md @@ -65,7 +65,6 @@ const Demo = () => ( ); ReactDOM.render(, _mount_); ``` - ### 表头分组 表头分组通过 `columns` 数组中对象的 `children` 来实现,以渲染分组表头。。 @@ -814,6 +813,66 @@ const Demo = () => { ReactDOM.render(, _mount_); ``` +### 表格列过宽导致 footer 滑动出表格底部 + +使用 scroll 属性给表格设置宽或高即可 + + +```jsx +import ReactDOM from 'react-dom'; +import { Table, Button } from 'uiw'; + +const columns = [ + { + // title: '姓名', + ellipsis: true, + width: 1000, + title: ({ key }) => { + return ( + 字段: {key} + ) + }, + key: 'name', + }, { + title: '年龄', + style: { color: 'red' }, + key: 'age', + }, { + title: '地址', + key: 'info', + }, { + title: '操作', + key: 'edit', + width: 98, + render: (text, key, rowData, rowNumber, columnNumber) => ( +
+ + +
+ ), + }, +]; +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 = () => ( +
+
onCell(trData, { rowNum, colNum, keyName }, evn)} /> + ); + })} +
onCell(trData, { rowNum, colNum, keyName }, evn)} - /> - ); - })} -
这个是footer} + columns={columns} data={dataSource} + /> + +); +ReactDOM.render(, _mount_); +``` + ## Props ### Table @@ -845,6 +904,7 @@ ReactDOM.render(, _mount_); | render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前值的 `key`,行索引数据,当前行号,当前列号。| `Function(text, key, rowData, rowNumber, columnNumber)` | - | | align | 设置列的对齐方式 | "left"|"center"|"right" | - | | className | 列样式类名 | string | - | +| scroll | 表格是否可滚动,也可以指定滚动区域的宽、高 | { x?: React.CSSProperties['width'], y?: React.CSSProperties['height'] } | - | ### expandable diff --git a/packages/react-table/src/TableTr.tsx b/packages/react-table/src/TableTr.tsx index 2b09e74c04..884f3ec0a2 100644 --- a/packages/react-table/src/TableTr.tsx +++ b/packages/react-table/src/TableTr.tsx @@ -75,7 +75,7 @@ export default function TableTr(props: TableTr children: trData[keyName.key!], }; if (render[keyName.key!]) { - const child = render[keyName.key!](trData[keyName.key!], keyName.key!, trData, rowNum, colNum); + const child = render[keyName.key!](trData[keyName.key!], keyName.key, trData, rowNum, colNum); if (React.isValidElement(child)) { objs.children = child; } else { @@ -106,7 +106,7 @@ export default function TableTr(props: TableTr {...objs} key={colNum} // style={keyName?.style} - className={`${objs.className} ${prefixCls}-tr-children-${keyName?.align || 'left'} ${ + className={`${objs.className || ''} ${prefixCls}-tr-children-${keyName.align || 'left'} ${ keyName.className || '' }`} onClick={(evn) => onCell(trData, { rowNum, colNum, keyName: keyName.key! }, evn)} diff --git a/packages/react-table/src/aaa.tsx b/packages/react-table/src/aaa.tsx new file mode 100644 index 0000000000..d03ad13ad4 --- /dev/null +++ b/packages/react-table/src/aaa.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Table, Button } from '../../uiw'; + +const columns = [ + { + // title: '姓名', + width: 90008, + ellipsis: true, + // title: ({ key }) => { + // return ( + // 字段: {key} + // ) + // }, + key: 'name', + }, + { + title: '年龄', + style: { color: 'red' }, + width: 100, + key: 'age', + }, +]; +const dataSource = [ + { + name: '邓紫棋', + age: '12', + info: '又名G.E.M.,原名邓诗颖,1991年8月16日生于中国上海,中国香港创作型女歌手。', + edit: '', + }, + { name: '李易峰', age: '32', info: '1987年5月4日出生于四川成都,中国内地男演员、流行乐歌手、影视制片人', edit: '' }, +]; +const Demo = () => ( +
+
} + /> + +); diff --git a/packages/react-table/src/index.tsx b/packages/react-table/src/index.tsx index 76854026ab..1beef63205 100644 --- a/packages/react-table/src/index.tsx +++ b/packages/react-table/src/index.tsx @@ -66,6 +66,7 @@ export interface TableProps exten ) => void; expandable?: ExpandableType; rowKey?: keyof T; + scroll?: { x?: React.CSSProperties['width']; y?: React.CSSProperties['height'] }; } export interface ICellOptions { @@ -88,6 +89,7 @@ export default function Table(props: TablePro children, expandable, rowKey, + scroll, ...other } = props; const [expandIndex, setExpandIndex] = useState>([]); @@ -187,14 +189,31 @@ export default function Table(props: TablePro selfColumns, }; }, [columns, expandIndex]); - + const style: { table: React.CSSProperties; div: React.CSSProperties } = useMemo(() => { + const style: { table: React.CSSProperties; div: React.CSSProperties } = { + table: {}, + div: {}, + }; + if (scroll) { + if (scroll.x !== undefined) { + style.table.minWidth = '100%'; + style.table.width = scroll.x; + style.div.overflowX = 'auto'; + style.div.overflowY = 'hidden'; + } + if (scroll.y !== undefined) { + style.div.maxHeight = scroll.y; + style.div.overflowY = 'scroll'; + } + } + return style; + }, [scroll]); const cls = [prefixCls, className, bordered ? `${prefixCls}-bordered` : null].filter(Boolean).join(' ').trim(); const { header, render, ellipsis } = getLevelItems(self.selfColumns); - return ( -
-
-
+ +
+
{title && } {columns && columns.length > 0 && } {data && data.length > 0 && ( @@ -227,6 +246,6 @@ export default function Table(props: TablePro
{title}
{footer &&
{footer}
} - + ); } From a11db86a21df29b129f0f026fa4f46f9dfd460fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E9=91=AB?= <1192065030@qq.com> Date: Fri, 18 Mar 2022 12:02:11 +0800 Subject: [PATCH 3/7] =?UTF-8?q?feat(Table):=20Table=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20scroll=20=E5=B1=9E=E6=80=A7=EF=BC=8C=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E8=A1=A8=E6=A0=BC=E5=86=97=E4=BD=99=20div=20#687?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-table/README.md | 2 +- packages/react-table/src/aaa.tsx | 42 -------------------------------- 2 files changed, 1 insertion(+), 43 deletions(-) delete mode 100644 packages/react-table/src/aaa.tsx diff --git a/packages/react-table/README.md b/packages/react-table/README.md index f964c3b2de..d9c45704d7 100644 --- a/packages/react-table/README.md +++ b/packages/react-table/README.md @@ -815,7 +815,7 @@ ReactDOM.render(, _mount_); ### 表格列过宽导致 footer 滑动出表格底部 -使用 scroll 属性给表格设置宽或高即可 +使用 scroll 属性给表格设置宽(x)或高(y)即可 ```jsx diff --git a/packages/react-table/src/aaa.tsx b/packages/react-table/src/aaa.tsx deleted file mode 100644 index d03ad13ad4..0000000000 --- a/packages/react-table/src/aaa.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import { Table, Button } from '../../uiw'; - -const columns = [ - { - // title: '姓名', - width: 90008, - ellipsis: true, - // title: ({ key }) => { - // return ( - // 字段: {key} - // ) - // }, - key: 'name', - }, - { - title: '年龄', - style: { color: 'red' }, - width: 100, - key: 'age', - }, -]; -const dataSource = [ - { - name: '邓紫棋', - age: '12', - info: '又名G.E.M.,原名邓诗颖,1991年8月16日生于中国上海,中国香港创作型女歌手。', - edit: '', - }, - { name: '李易峰', age: '32', info: '1987年5月4日出生于四川成都,中国内地男演员、流行乐歌手、影视制片人', edit: '' }, -]; -const Demo = () => ( -
- } - /> - -); From 2bb39f933a20f67782e78e9320236662cbd6037f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E9=91=AB?= <1192065030@qq.com> Date: Mon, 21 Mar 2022 19:51:01 +0800 Subject: [PATCH 4/7] =?UTF-8?q?feat(Table):=20Table=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E5=88=97=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-table/README.md | 82 ++++++++++++++++++++--- packages/react-table/src/TableTr.tsx | 26 +++++-- packages/react-table/src/ThComponent.tsx | 45 +++++++++++++ packages/react-table/src/Thead.tsx | 44 +++++++----- packages/react-table/src/index.tsx | 49 +++++++++++++- packages/react-table/src/style/index.less | 52 +++++++++++++- packages/react-table/src/util.ts | 13 +++- 7 files changed, 273 insertions(+), 38 deletions(-) create mode 100644 packages/react-table/src/ThComponent.tsx diff --git a/packages/react-table/README.md b/packages/react-table/README.md index d9c45704d7..fa8a7d876a 100644 --- a/packages/react-table/README.md +++ b/packages/react-table/README.md @@ -873,6 +873,64 @@ const Demo = () => ( ReactDOM.render(, _mount_); ``` +### 固定列 + +通过使用 fixed 使其列固定 +> ⚠️ 注意: 若并没有 scroll 滚动条,fixed 属性并不会有直观的效果 + + + +```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) => ( +
+ + +
+ ), + }, +]; +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 = () => ( +
+
+ +); +ReactDOM.render(, _mount_); +``` + ## Props ### Table @@ -881,14 +939,16 @@ ReactDOM.render(, _mount_); |--------- |-------- |--------- |-------- | | columns | 表格列的配置描述,可以内嵌 `children`,以渲染分组表头。| ColumnProps[] | `[]` | | data | 数据数组。| Array[] | `[]` | -| title | 表格标题 | ~~Function(text, key, rowData, rowNumber, columnNumber)~~ /
Function(data: IColumns, rowNum: number, colNum: number)`@3.0.0+` /
String / ReactNode | - | +| title | 表格标题 | ~~Function(text, key, rowData, rowNumber, columnNumber)~~ /
Function(data: IColumns, rowNum: Number, colNum: Number)`@3.0.0+` /
String / ReactNode | - | | footer | 表格尾部 | String/ReactNode | - | | bordered | 是否展示外边框和列边框 | Boolean | - | | empty | 无数据状态 | ReactNode | - | -| onCellHead | 表头单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /
Function(data: IColumns, colNum: number, rowNum: number, evn: React.MouseEvent) `@3.0.0+` | - | -| onCell | 单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /
Function(data: IColumns, options:{ colNum: number, rowNum: number, keyName: string }, evn: React.MouseEvent) `@3.1.0+` | - | +| onCellHead | 表头单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /
Function(data: IColumns, colNum: Number, rowNum: Number, evn: React.MouseEvent) `@3.0.0+` | - | +| onCell | 单元格点击回调 | ~~`Function(text, key, rowData, rowNumber, columnNumber)`~~ /
Function(data: IColumns, options:{ colNum: Number, rowNum: Number, keyName: String }, evn: React.MouseEvent) `@3.1.0+` | - | | expandable | 可展开配置 | ExpandableType | - | -| rowKey | 表格行 key 的取值 | string | - | +| rowKey | 表格行 key 的取值 | String | - | +| scroll | 表格是否可滚动,也可以指定滚动区域的宽、高 | { x?: React.CSSProperties['width'], y?: React.CSSProperties['height'] } | - | + ### ColumnProps @@ -902,9 +962,9 @@ ReactDOM.render(, _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 @@ -914,11 +974,11 @@ ReactDOM.render(, _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 | diff --git a/packages/react-table/src/TableTr.tsx b/packages/react-table/src/TableTr.tsx index 884f3ec0a2..ce05e5c540 100644 --- a/packages/react-table/src/TableTr.tsx +++ b/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 { rowKey?: keyof T; @@ -18,6 +19,7 @@ interface TableTrProps { // 层级 hierarchy: number; childrenColumnName: string; + locationWidth: { [key: number]: LocationWidth }; } export default function TableTr(props: TableTrProps) { @@ -33,6 +35,7 @@ export default function TableTr(props: TableTr hierarchy, indentSize, childrenColumnName, + locationWidth, } = props; const [isOpacity, setIsOpacity] = useState(false); const [expandIndex, setExpandIndex] = useState>([]); @@ -88,9 +91,6 @@ export default function TableTr(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 = ( @@ -101,13 +101,25 @@ export default function TableTr(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 (
+ ); + } +} diff --git a/packages/react-table/src/Thead.tsx b/packages/react-table/src/Thead.tsx index 7af3e644da..5cffc332c0 100644 --- a/packages/react-table/src/Thead.tsx +++ b/packages/react-table/src/Thead.tsx @@ -1,19 +1,30 @@ 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 extends IProps { data?: TableColumns[][]; onCellHead?: TableProps['onCellHead']; align?: TableColumns['align']; className?: TableColumns['className']; + locationWidth?: { [key: number]: LocationWidth }; + updateLocation?: (params: LocationWidth, index: number) => void; } export default function TheadComponent( props: TheadProps & React.HTMLAttributes = {}, ) { - const { prefixCls = 'w-table', className, data = [], onCellHead = noop, ...other } = props; + const { + prefixCls = 'w-table', + className, + data = [], + onCellHead = noop, + locationWidth, + updateLocation, + ...other + } = props; return ( {data && @@ -21,24 +32,27 @@ export default function TheadComponent( data.map((tds?: TableColumns[], rowNum?: number) => ( {(tds || []).map((item, colNum) => { - const { title, key, render, children, ellipsis, ...thProps } = item; - const titleNode: TableColumns['title'] = - typeof title === 'function' ? title(item, colNum, rowNum!) : title; + const { title, key, render, children, ellipsis, fixed = false, ...thProps } = item; + const titleNode: TableColumns['title'] = ( + + {typeof title === 'function' ? title(item, colNum, rowNum!) : title} + + ); if (thProps.colSpan === 0) { return null; } - if (ellipsis) { - thProps.className = `${thProps.className || ''} ${prefixCls}-ellipsis`; - } return ( - + prefixCls={prefixCls} + onCellHead={onCellHead} + rowNum={rowNum!} + titleNode={titleNode} + locationWidth={locationWidth!} + updateLocation={updateLocation!} + /> ); })} diff --git a/packages/react-table/src/index.tsx b/packages/react-table/src/index.tsx index 1beef63205..445e4492ce 100644 --- a/packages/react-table/src/index.tsx +++ b/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'; @@ -42,6 +42,7 @@ export type TableColumns = { style?: React.CSSProperties; align?: 'left' | 'center' | 'right'; className?: string; + fixed?: boolean | 'left' | 'right'; [key: string]: any; }; @@ -69,6 +70,11 @@ export interface TableProps 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; @@ -93,6 +99,37 @@ export default function Table(props: TablePro ...other } = props; const [expandIndex, setExpandIndex] = useState>([]); + 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) { @@ -215,11 +252,19 @@ export default function Table(props: TablePro
+ {objs.children} + + } 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)} /> diff --git a/packages/react-table/src/ThComponent.tsx b/packages/react-table/src/ThComponent.tsx new file mode 100644 index 0000000000..75bcf81a6f --- /dev/null +++ b/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 { + colNum: number; + item: TableColumns; + prefixCls: string; + titleNode: JSX.Element; + onCellHead: TableProps['onCellHead']; + rowNum: number; + locationWidth: { [key: number]: LocationWidth }; + updateLocation: (params: LocationWidth, index: number) => void; +} +export default class ThComponent extends Component> { + componentDidMount() { + const rect = ReactDOM.findDOMNode(this); + this.props.updateLocation({ width: (rect as any).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 ( + onCellHead?.(item, colNum, rowNum!, evn)} + > + {titleNode} +
onCellHead(item, colNum, rowNum!, evn)} - > - {titleNode} -
{title && } - {columns && columns.length > 0 && } + {columns && columns.length > 0 && ( + + )} {data && data.length > 0 && ( tbody > tr { transition: all 0.3s; + > td { + background-color: #fff; + position: relative; + z-index: 1; + } &:hover, &:hover:nth-child(2n) { - background-color: #efefef; + > td { + background-color: #efefef; + } } &:nth-child(2n) { - background-color: #f9f9f9; + > td { + background-color: #f9f9f9; + } } } > thead { > tr > th { font-weight: normal; padding: 8px; + background-color: #f6f9fb; + position: relative; + z-index: 1; } > tr, tr:nth-child(2n) { @@ -47,6 +59,7 @@ white-space: nowrap; text-overflow: ellipsis; word-break: keep-all; + display: block; } &-tr-children-center { text-align: center; @@ -57,6 +70,41 @@ &-tr-children-right { text-align: right; } + &-fixed-true { + position: sticky !important; + z-index: 2 !important; + // border: 0; 透风 1px + } + &-fixed-right { + position: sticky !important; + z-index: 2 !important; + // border: 0; 透风 1px + } + &-fixed-true::after { + box-shadow: inset 10px 0 8px -8px rgb(0 0 0 / 15%); + position: absolute; + top: 0; + right: 0; + bottom: -1px; + width: 30px; + transform: translateX(100%); + transition: box-shadow 0.3s; + content: ''; + pointer-events: none; + } + &-fixed-right::after { + box-shadow: inset -10px 0 8px -8px rgb(0 0 0 / 15%); + position: absolute; + top: 0; + bottom: -1px; + left: 0; + width: 30px; + transform: translateX(-100%); + transition: box-shadow 0.3s; + content: ''; + pointer-events: none; + border-right: 1px solid #f0f0f0; + } &-bordered { > table { tr > th, diff --git a/packages/react-table/src/util.ts b/packages/react-table/src/util.ts index 68a0c8236f..5f96cf9d80 100644 --- a/packages/react-table/src/util.ts +++ b/packages/react-table/src/util.ts @@ -1,4 +1,5 @@ -import { TableColumns } from './'; +import { TableColumns, LocationWidth } from './'; +import React from 'react'; /** * Get colspan number @@ -127,3 +128,13 @@ export function getAllColumnsKeys(data: TableColumns[], keys: TableColumns } return keys; } + +export function locationFixed( + fixed: boolean | 'left' | 'right', + location: { [key: number]: LocationWidth }, + index: number, +): React.CSSProperties { + if (!fixed) return {}; + if (fixed === 'right') return { right: location[index]?.right }; + return { left: location[index]?.left }; +} From a86201ea64ebfc9fd165b173fe0bc7c57c589337 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E9=91=AB?= <1192065030@qq.com> Date: Mon, 21 Mar 2022 19:54:35 +0800 Subject: [PATCH 5/7] =?UTF-8?q?feat(Table):=20Table=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E5=9B=BA=E5=AE=9A=E5=88=97=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-table/src/ThComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-table/src/ThComponent.tsx b/packages/react-table/src/ThComponent.tsx index 75bcf81a6f..5711369687 100644 --- a/packages/react-table/src/ThComponent.tsx +++ b/packages/react-table/src/ThComponent.tsx @@ -16,7 +16,7 @@ interface ThComponentProps { export default class ThComponent extends Component> { componentDidMount() { const rect = ReactDOM.findDOMNode(this); - this.props.updateLocation({ width: (rect as any).getBoundingClientRect().width }, this.props.colNum); + this.props.updateLocation({ width: (rect as Element).getBoundingClientRect().width }, this.props.colNum); } render() { From 35816f77babcafb01a3030fff02f49bc90b53aa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E9=91=AB?= <1192065030@qq.com> Date: Mon, 21 Mar 2022 20:43:33 +0800 Subject: [PATCH 6/7] doc(Table): Update README.md --- packages/react-table/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/react-table/README.md b/packages/react-table/README.md index fa8a7d876a..0fc5d69ad8 100644 --- a/packages/react-table/README.md +++ b/packages/react-table/README.md @@ -954,17 +954,17 @@ ReactDOM.render(, _mount_); 列描述数据对象,是 columns 中的一项, -| 参数 | 说明 | 类型 | 默认值 | -|--------- |-------- |--------- |-------- | -| title | 列头显示文字。| ReactNode | - | -| key | 需要的 key,可以忽略这个属性,如果标题带有 `render` 函数,那么这个 `key` 为必须非常重要。| String | - | -| width | 列宽度。| Number | - | -| colSpan | 合并表头行。| Number | - | -| ellipsis | 超过宽度将自动省略。`v4.8.7+`| Boolean | `false` | -| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前值的 `key`,行索引数据,当前行号,当前列号。| `Function(text, key, rowData, rowNumber, columnNumber)` | - | -| align | 设置列的对齐方式 | "left"\|"center"\|"right" | - | -| className | 列样式类名 | String | - | -| fixed | 把选择框列固定 | Boolean \|"left"\|"right" | - | +| 参数 | 说明 | 类型 | 默认值 | 版本 | +|--------- |-------- |--------- |-------- |-------- | +| title | 列头显示文字。| ReactNode | - | - | +| key | 需要的 key,可以忽略这个属性,如果标题带有 `render` 函数,那么这个 `key` 为必须非常重要。| String | - | - | +| width | 列宽度。| Number | - | - | +| colSpan | 合并表头行。| Number | - | - | +| ellipsis | 超过宽度将自动省略。`v4.8.7+`| Boolean | `false` | - | +| render | 生成复杂数据的渲染函数,参数分别为当前行的值,当前值的 `key`,行索引数据,当前行号,当前列号。| `Function(text, key, rowData, rowNumber, columnNumber)` | - | - | +| align | 设置列的对齐方式 | "left"\|"center"\|"right" | - | - | +| className | 列样式类名 | String | - | - | +| fixed | 把选择框列固定 | Boolean \|"left"\|"right" | - | 4.15.1 | ### expandable From 68f82624299c27bfa142d9724b50bf5c2281b657 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=B0=E9=91=AB?= <1192065030@qq.com> Date: Tue, 22 Mar 2022 16:30:34 +0800 Subject: [PATCH 7/7] =?UTF-8?q?update(Table):=20Table=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=A0=91=E5=BD=A2=E6=95=B0=E6=8D=AE=E6=98=BE=E7=A4=BA=E5=92=8C?= =?UTF-8?q?=E5=8F=AF=E5=B1=95=E5=BC=80=E5=90=8C=E6=97=B6=E5=87=BA=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/react-table/README.md | 8 +++----- packages/react-table/src/TableTr.tsx | 6 ++++-- packages/react-table/src/index.tsx | 13 ++++++++++++- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/react-table/README.md b/packages/react-table/README.md index 0fc5d69ad8..de95f97d6c 100644 --- a/packages/react-table/README.md +++ b/packages/react-table/README.md @@ -729,9 +729,6 @@ ReactDOM.render(, _mount_); 可以通过设置 indentSize 以控制每一层的缩进宽度 -> ⚠️ 注意: 树形数据展示和`expandable.expandedRowRender`请不要同时出现,后续或将支持 - - ```jsx import ReactDOM from 'react-dom'; @@ -805,7 +802,7 @@ const Demo = () => {
{title}
) @@ -968,7 +965,8 @@ ReactDOM.render(, _mount_); ### expandable -注意 expandedRowKeys 与 onExpandedRowsChange 必须成对出现 +> ⚠️ 注意: expandedRowKeys 与 onExpandedRowsChange 必须同时出现或不出现 + | 参数 | 说明 | 类型 | 默认值 | |--------- |-------- |--------- |-------- | diff --git a/packages/react-table/src/TableTr.tsx b/packages/react-table/src/TableTr.tsx index ce05e5c540..2f1d2d7cff 100644 --- a/packages/react-table/src/TableTr.tsx +++ b/packages/react-table/src/TableTr.tsx @@ -38,9 +38,11 @@ export default function TableTr(props: TableTr locationWidth, } = props; const [isOpacity, setIsOpacity] = useState(false); + const [childrenIndex, setChildrenIndex] = useState(0); const [expandIndex, setExpandIndex] = useState>([]); useEffect(() => { setIsOpacity(!!data?.find((it) => it[childrenColumnName])); + setChildrenIndex(keys?.findIndex((it) => it.key === 'uiw-expanded') === -1 ? 0 : 1); }, [data]); const IconDom = useMemo(() => { @@ -92,7 +94,7 @@ export default function TableTr(props: TableTr } } const isHasChildren = Array.isArray(trData[childrenColumnName]); - if (colNum === 0 && (isOpacity || hierarchy || isHasChildren)) { + if (colNum === childrenIndex && (isOpacity || hierarchy || isHasChildren)) { objs.children = ( <> {IconDom(key, isHasChildren)} @@ -126,10 +128,10 @@ export default function TableTr(props: TableTr ); })} + {isExpandedDom(trData, rowNum)} {expandIndex.includes(key) && ( )} - {isExpandedDom(trData, rowNum)} ); })} diff --git a/packages/react-table/src/index.tsx b/packages/react-table/src/index.tsx index 445e4492ce..1a2fc640bc 100644 --- a/packages/react-table/src/index.tsx +++ b/packages/react-table/src/index.tsx @@ -131,9 +131,20 @@ export default function Table(props: TablePro return finalLocationWidth.current; }; useEffect(() => { + const childKey = expandable?.childrenColumnName || 'children'; + const deep = (params: TableColumns) => { + const arr1: Array = []; + const arr = params.map((it: T, index: number) => { + if (Array.isArray(it[childKey])) { + arr1.push(...deep(it[childKey])); + } + return rowKey ? it[rowKey] : index; + }); + return [...arr1, ...arr]; + }; if (expandable) { if (expandable.defaultExpandAllRows) { - setExpandIndex(data.map((it, index) => (rowKey ? it[rowKey] : index))); + setExpandIndex(deep(data)); return; } if (expandable.defaultExpandedRowKeys) {