From 8d71cec37b2c09a94a0773dc353e2fa62d36ac52 Mon Sep 17 00:00:00 2001 From: wang_jingbao Date: Sun, 27 Mar 2022 12:17:26 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=9A=E6=A0=91=E7=8A=B6=E8=A1=A8=E6=A0=BC=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=A0=91=E8=8A=82=E7=82=B9=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E5=9C=A8=E5=93=AA=E4=B8=80=E5=88=97=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pipeline/features/treeMode.tsx | 30 +++++++++++-------- .../docs/pipeline/features/tree-mode.mdx | 3 ++ .../website/examples/pipeline.stories.tsx | 13 ++++++-- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/packages/ali-react-table/src/pipeline/features/treeMode.tsx b/packages/ali-react-table/src/pipeline/features/treeMode.tsx index 571e18214..36323089c 100644 --- a/packages/ali-react-table/src/pipeline/features/treeMode.tsx +++ b/packages/ali-react-table/src/pipeline/features/treeMode.tsx @@ -38,6 +38,9 @@ export interface TreeModeFeatureOptions { /** 指定表格每一行元信息的记录字段 */ treeMetaKey?: string | symbol + + /** 指定树节点所在列的code,匹配不到时默认第一列 */ + treeColumnCode?: string } export function treeMode(opts: TreeModeFeatureOptions = {}) { @@ -79,6 +82,7 @@ export function treeMode(opts: TreeModeFeatureOptions = {}) { const iconIndent = opts.iconIndent ?? ctx.indents.iconIndent const iconGap = opts.iconGap ?? ctx.indents.iconGap const indentSize = opts.indentSize ?? ctx.indents.indentSize + const treeColumnCode = opts.treeColumnCode return pipeline.mapDataSource(processDataSource).mapColumns(processColumns) @@ -110,7 +114,10 @@ export function treeMode(opts: TreeModeFeatureOptions = {}) { if (columns.length === 0) { return columns } - const [firstCol, ...others] = columns + + columns = columns.slice() + const treeColIndex: number = !treeColumnCode ? 0 : Math.max(columns.findIndex(col => col.code === treeColumnCode), 0) + const firstCol: ArtColumn = columns[treeColIndex] const render = (value: any, record: any, recordIndex: number) => { const content = internals.safeRender(firstCol, record, recordIndex) @@ -184,17 +191,16 @@ export function treeMode(opts: TreeModeFeatureOptions = {}) { }) } - return [ - { - ...firstCol, - title: ( - {internals.safeRenderHeader(firstCol)} - ), - render, - getCellProps: clickArea === 'cell' ? getCellProps : firstCol.getCellProps, - }, - ...others, - ] + columns[treeColIndex] = { + ...firstCol, + title: ( + {internals.safeRenderHeader(firstCol)} + ), + render, + getCellProps: clickArea === 'cell' ? getCellProps : firstCol.getCellProps, + } + + return columns } } } diff --git a/packages/website/docs/pipeline/features/tree-mode.mdx b/packages/website/docs/pipeline/features/tree-mode.mdx index 5fe506950..20f75c912 100644 --- a/packages/website/docs/pipeline/features/tree-mode.mdx +++ b/packages/website/docs/pipeline/features/tree-mode.mdx @@ -65,6 +65,9 @@ export interface TreeModeFeatureOptions { /** 指定表格每一行元信息的记录字段 */ treeMetaKey?: string | symbol + + /** 指定树节点所在列的code,匹配不到时默认第一列 */ + treeColumnCode?: string } ``` diff --git a/packages/website/examples/pipeline.stories.tsx b/packages/website/examples/pipeline.stories.tsx index abf5f15a8..b9316f29c 100644 --- a/packages/website/examples/pipeline.stories.tsx +++ b/packages/website/examples/pipeline.stories.tsx @@ -1,5 +1,5 @@ import * as fusion from '@alifd/next' -import { Button } from '@alifd/next' +import { Button, Divider, Radio } from '@alifd/next' import { ArtColumn, collectNodes, features, isLeafNode, useTablePipeline } from 'ali-react-table' import { columns1, @@ -26,10 +26,11 @@ export default { title: 'pipeline 功能拓展' } export function 树形表格() { const [openKeys, onChangeOpenKeys] = useState(['4', '4-2']) + const [treeColumnCode, setTreeColumnCode] = useState('title') const pipeline = useTablePipeline({ components: fusion as any }) .input({ dataSource: dataSource4, columns: columns4 }) .primaryKey('id') - .use(features.treeMode({ openKeys, onChangeOpenKeys })) + .use(features.treeMode({ openKeys, onChangeOpenKeys, treeColumnCode })) const allParentKeys = collectNodes(dataSource4, 'pre') .filter((row) => !isLeafNode(row)) @@ -41,6 +42,14 @@ export function 树形表格() { + + 树节点所在列: + setTreeColumnCode(v)} + dataSource={columns4.slice(0, 4).map(({ code, name }: any) => ({ label: name, value: code }))} + />

openKeys: {openKeys.join(', ')} {openKeys.length === 0 && '[空]'}

From f70539b4ce828c1dc5af79c8612b48069bf026f5 Mon Sep 17 00:00:00 2001 From: wang_jingbao Date: Sun, 27 Mar 2022 16:27:19 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=9A=E8=A1=A8=E6=A0=BC=E6=94=AF=E6=8C=81=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E5=88=B0=E6=8C=87=E5=AE=9A=E4=BD=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/base-table/html-table.tsx | 3 +- .../ali-react-table/src/base-table/table.tsx | 121 +++++++++++++++ packages/website/docs/table/api.mdx | 21 +++ packages/website/docs/table/examples.mdx | 5 + packages/website/example-sidebars.js | 1 + packages/website/examples/scroll-to.mdx | 11 ++ .../website/examples/scroll-to.stories.tsx | 143 ++++++++++++++++++ 7 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 packages/website/examples/scroll-to.mdx create mode 100644 packages/website/examples/scroll-to.stories.tsx diff --git a/packages/ali-react-table/src/base-table/html-table.tsx b/packages/ali-react-table/src/base-table/html-table.tsx index 816aeb25a..86c15f461 100644 --- a/packages/ali-react-table/src/base-table/html-table.tsx +++ b/packages/ali-react-table/src/base-table/html-table.tsx @@ -71,10 +71,12 @@ export function HtmlTable({ rowProps?.className, ) + const key = internals.safeGetRowKey(primaryKey, row, rowIndex) const trProps = { ...rowProps, className: rowClass, 'data-rowindex': rowIndex, + id: `art-table-row-${key}`, // 添加id,可用于滚动精准定位或与其他地方 children: hozInfo.visible.map((descriptor) => { if (descriptor.type === 'blank') { return @@ -83,7 +85,6 @@ export function HtmlTable({ }), } - const key = internals.safeGetRowKey(primaryKey, row, rowIndex) if (Row != null && tbodyHtmlTag === 'tbody') { return React.createElement(Row, { key, row, rowIndex, trProps }) } else { diff --git a/packages/ali-react-table/src/base-table/table.tsx b/packages/ali-react-table/src/base-table/table.tsx index c0d000347..89c256818 100644 --- a/packages/ali-react-table/src/base-table/table.tsx +++ b/packages/ali-react-table/src/base-table/table.tsx @@ -192,6 +192,127 @@ export class BaseTable extends React.Component { } } + /** + * 定位滚动条 + * @desc 优先级按参数顺序降序 + * @param top 目标值 + * @param element 目标元素 + * @param rowKey 行主键 + * @param rowIndex 行索引 + * @param offset 纵向滚动偏移量,用于抵消固定表头 + */ + scrollTo ({ top, element, rowKey, rowIndex, offset } + : { element: HTMLElement, top: number, rowKey: string, rowIndex: number, offset: number } + ) { + if (top) return this.scrollToValue(top) + if (element) return this.scrollToElement(element, offset) + if (rowKey) return this.scrollToRowKey(rowKey, offset) + if (rowIndex) return this.scrollToRowIndex(rowIndex, offset) + } + + /** + * 定位到指定位置 + * @desc 动态行高时如需精准定位,可以传入目标元素进行二次修正 + * @param top 纵向滚动条位置 + * @param selector 目标元素选择器 + * @param offset 纵向滚动偏移量,用于抵消固定表头 + */ + private scrollToValue (top: number, selector?: string, offset?: number) { + let view = this.artTableWrapperRef.current + if (view.clientHeight === view.scrollHeight) { + view = document.documentElement as HTMLDivElement + } + if (!selector) { + return view.scrollTop = top + } + const element = view.querySelector(selector) + // 目标元素真实存在时直接精准定位 + if (element) { + return this.scrollToElement(element, offset) + } + view.scrollTop = top + // 延迟修正 + setTimeout(() => { + this.scrollToElement(view.querySelector(selector), offset) + }, 16) + } + + /** + * 定位到指定元素 + * @desc 虚拟表格不应该直接使用该方法 + * @param element + * @param offset 纵向滚动偏移量,用于抵消固定表头 + */ + private scrollToElement (element: Element, offset?: number) { + if (!element) return + let view = this.artTableWrapperRef.current + if (view.clientHeight === view.scrollHeight) { + view = document.documentElement as HTMLDivElement + } + view.scrollTop = this.getScrollOffsetTop(element, view) + (offset || 0); + } + + /** + * 定位到指定行 + * @desc 树形表格有节点展开时不应该使用此方法 + * @param rowIndex 行号(从1开始) + * @param offset 纵向滚动偏移量,用于抵消固定表头 + */ + private scrollToRowIndex (rowIndex: number, offset?: number) { + const { cache } = this.rowHeightManager + const end = Math.min(rowIndex + 1, cache.length) + let scrollTop = 0 + for (let i = 0; i < end; i++) { + scrollTop += cache[i] + } + this.scrollToValue(scrollTop, `[data-rowindex="${rowIndex}"]`, offset) + } + + /** + * 根据primaryKey定位到指定行 + * @desc 支持在树型表格中定位到未展开的节点,调用时应提前更新该节点的所有父节点key至openKeys中,并延迟调用该方法) + * @param rowKey 行主键 + * @param offset 纵向滚动偏移量,用于抵消固定表头 + */ + private scrollToRowKey (rowKey: string, offset?: number) { + const { dataSource } = this.props + const { cache } = this.rowHeightManager + const length = dataSource.length + const { primaryKey } = this.props + let scrollTop = 0 + for (let i = 1; i < length; i++) { + scrollTop += cache[i] + if (dataSource[i][primaryKey as string] === rowKey) break + } + this.scrollToValue(scrollTop, `#art-table-row-${rowKey}`, offset) + } + + /** + * 获取元素在滚动区域中的位置 + * @desc 节选自:https://github.com/Stanko/animated-scroll-to + * @param element 目标元素 + * @param view 滚动区域元素 + * @private + */ + private getScrollOffsetTop (element: Element, view: Element): number { + return this.getElementOffset(element).top - this.getElementOffset(view).top; + } + + private getScrollOffsetLeft (element: Element, view: Element): number { + return this.getElementOffset(element).left - this.getElementOffset(view).left; + } + + private getElementOffset (element: any) { + let top = 0; + let left = 0; + do { + top += element.offsetTop || 0; + left += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return { top, left }; + } + /** 自定义滚动条宽度为table宽度,使滚动条滑块宽度相同 */ private updateStickyScroll() { const { stickyScroll, artTable, stickyScrollItem } = this.domHelper diff --git a/packages/website/docs/table/api.mdx b/packages/website/docs/table/api.mdx index d20a56312..c27b2f17c 100644 --- a/packages/website/docs/table/api.mdx +++ b/packages/website/docs/table/api.mdx @@ -116,6 +116,27 @@ function Cell({ row, rowIndex, column, colIndex, tdProps }) { | `headerCellProps` | `React.ThHTMLAttributes` | 表头单元格的 props | | `features` | `{ [key: string]: any }` | 功能开关标记,用于对表格功能进行拓展 | +## 滚动到指定位置 + +定位到行,支持普通、虚拟表格(固定、动态行高均可): +```js +tableRef.current.scrollTo({ + top: 'number', + element: 'HTMLElement', + rowKey: 'string', + rowIndex: 'number', + offset: 'number' +}) +```` + +| 字段 | 类型 | | +| ----------------- | ---------------------------------------------------------- |------------------------------------------------------------------------------------| +| `top` | `number` | 通过具体数值定位 | +| `element` | `HTMLElement` | 通过目标元素定位 | +| `rowKey` | `string` | 通过行数据的主键值定位,可用于树形虚拟表格中任意节点的定位。如需定位未展开的节点,可先根据此rowKey递归展开父节点,[详见示例](/examples/scroll-to)| +| `rowIndex` | `number` | 通过行索引定位 | +| `offset` | `number` | 纵向滚动偏移量,可用于抵消固定表头 | + ## CSS 变量列表 `` 中大部分样式都是通过 CSS variables 来定义的,你可以通过下面的方式对表格进行风格化: diff --git a/packages/website/docs/table/examples.mdx b/packages/website/docs/table/examples.mdx index 140558293..6b169add3 100644 --- a/packages/website/docs/table/examples.mdx +++ b/packages/website/docs/table/examples.mdx @@ -343,3 +343,8 @@ function 表格页脚() { ) } ``` + + +### 滚动到指定位置 + +[完整示例](/examples/scroll-to) diff --git a/packages/website/example-sidebars.js b/packages/website/example-sidebars.js index 76c666aa7..820137b7d 100644 --- a/packages/website/example-sidebars.js +++ b/packages/website/example-sidebars.js @@ -4,6 +4,7 @@ module.exports = { 'table', 'pipeline', 'big-data', + 'scroll-to', 'theme', { type: 'category', diff --git a/packages/website/examples/scroll-to.mdx b/packages/website/examples/scroll-to.mdx new file mode 100644 index 000000000..9f3393317 --- /dev/null +++ b/packages/website/examples/scroll-to.mdx @@ -0,0 +1,11 @@ +--- +id: scroll-to +title: 滚动到指定位置 +hide_table_of_contents: true +hide_title: true +--- + +import * as stories from './scroll-to.stories.tsx' +import { Stories } from './helpers/Stories' + + diff --git a/packages/website/examples/scroll-to.stories.tsx b/packages/website/examples/scroll-to.stories.tsx new file mode 100644 index 000000000..519203e2e --- /dev/null +++ b/packages/website/examples/scroll-to.stories.tsx @@ -0,0 +1,143 @@ +import { ArtColumn, features, useTablePipeline } from 'ali-react-table' +import { WebsiteBaseTable } from 'assets/WebsiteBaseTable' +import React, { useRef, useState } from 'react' +import { Button, Checkbox, Divider } from "@alifd/next"; + +export default { title: '滚动到指定位置' } + +interface Node { + id: string, + pid: string, + name?: string, + value?: string, + children?: Node[] +} + +const ROOT_ID = '0' +const NODE = 10000 // 一级节点数量 +const CHILDREN = 3 // 后代节点数量 +const TEXT = 100 // 随机文本长度 + +const random = (count: number) => Math.ceil(Math.random() * count) + +const buildData = function (depth: number, size: number, pid: string, parents: Node[]) { + for (let i = 0; i < size; i++) { + const id = [pid, i + 1].join('-') + const node: Node = { + id, + pid: pid, + name: `键-${id}`, + value: new Array(random(TEXT)).fill('文本').join('-'), + children: [] + } + parents.push(node) + depth > 0 && buildData(depth - 1, CHILDREN, node.id, node.children) + } +} + +const dataSource: Node[] = [] +buildData(3, NODE, ROOT_ID, dataSource) + +/** + * 根据节点查找所有上级节点,返回完整层级的key集合 + * @param node + * @param tree + * @param idProp + * @param pidProp + */ +function findParents (node: any, tree: any[], idProp = 'id', pidProp = 'pid'): string[] { + const findParentById = (id: string, nodes: any[], parents: any[]) => { + for (let i = 0; i < nodes.length; i++) { + let item = nodes[i] + if (item[idProp] === id) { + return parents.push(item) + } + if (item.children && item.children.length) { + findParentById(id, item.children, parents) + } + } + } + const findParentChainById = (id: string, nodes: any[], parents: any[] = [], index = 0) => { + if (id === '0') return + findParentById(id, nodes, parents) + findParentChainById(parents[index][pidProp], nodes, parents, ++index) + return parents + } + return findParentChainById(node[idProp], tree).map(item => item[idProp]) +} + +export function 基本用法 () { + const columns: ArtColumn[] = [ + { code: 'index', name: '索引', width: 80, align: 'center', getValue: (row: any, rowIndex: number): any => rowIndex }, + { code: 'name', name: '键', width: 200 }, + { code: 'value', name: '值', width: 200 }, + ] + + const [useDiv, setUseDiv] = useState(true) + const [openKeys, setOpenKeys] = useState([]) + const [rowIndex, setRowIndex] = useState(0) + const [rowKey, setRowKey] = useState('?') + + const tableRef = useRef(null) + + const pipeline = useTablePipeline() + .input({ dataSource: dataSource, columns: columns }) + .primaryKey('id') + .use(features.columnResize()) + .use(features.treeMode({ + openKeys, + onChangeOpenKeys: setOpenKeys, + treeColumnCode: 'name' + })) + + return ( +
+
+ + + + + + + + + + setUseDiv(v)}>使用DIV作为容器 +
+ + +
+ ) +} From 82f8dbba172b51b8fe5b10da7db4cf7c22d751a3 Mon Sep 17 00:00:00 2001 From: Saionji SeKai Date: Mon, 10 Oct 2022 13:33:04 +0800 Subject: [PATCH 3/3] fix: scroll to zero --- packages/ali-react-table/src/base-table/table.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ali-react-table/src/base-table/table.tsx b/packages/ali-react-table/src/base-table/table.tsx index 89c256818..86597d449 100644 --- a/packages/ali-react-table/src/base-table/table.tsx +++ b/packages/ali-react-table/src/base-table/table.tsx @@ -204,7 +204,7 @@ export class BaseTable extends React.Component { scrollTo ({ top, element, rowKey, rowIndex, offset } : { element: HTMLElement, top: number, rowKey: string, rowIndex: number, offset: number } ) { - if (top) return this.scrollToValue(top) + if (top > -1) return this.scrollToValue(top) if (element) return this.scrollToElement(element, offset) if (rowKey) return this.scrollToRowKey(rowKey, offset) if (rowIndex) return this.scrollToRowIndex(rowIndex, offset)