import React from 'react' import { noop } from 'rxjs' import type { BaseTableProps } from '../../base-table' import { features, TablePipeline } from '../../pipeline' import { isLeafNode as standardIsLeafNode } from '../../utils' import type { ArtColumn } from '../../interfaces' import { ROW_KEY, } from '../cross-table' import type { BuildCrossTableOptions, CrossTableLeftMetaColumn, LeftCrossTreeNode, TopCrossTreeNode, } from '../cross-table' interface CrossTreeTableRenderRow { [ROW_KEY]: string node: LeftCrossTreeNode nodes: LeftCrossTreeNode[] children?: CrossTreeTableRenderRow[] } export type BuildCrossTreeTableOptions = Omit< BuildCrossTableOptions, 'leftMetaColumns' | 'leftTotalNode' | 'topTotalNode' > & { primaryColumn?: CrossTableLeftMetaColumn openKeys: string[] onChangeOpenKeys(nextOpenKeys: string[]): void indentSize?: number isLeafNode?(node: any, nodeMeta: { depth: number; expanded: boolean; rowKey: string }): boolean } export default function buildCrossTreeTable( options: BuildCrossTreeTableOptions, ): Pick { const { primaryColumn = { name: '' }, openKeys, onChangeOpenKeys, indentSize, isLeafNode: isLeafNodeOpt = standardIsLeafNode, } = options // 有的时候 leftTree/topTree 是通过 node.children 传入的 // 此时 leftTree/topTree 等于 null 和等于空数组是等价的 // 故在这里兼容 leftTree/topTree 为空的情况 const leftTree = options.leftTree ?? [] const topTree = options.topTree ?? [] const pipeline = new TablePipeline({ state: {}, setState: noop, ctx: { primaryKey: ROW_KEY }, }) pipeline.input({ dataSource: getDataSource(), columns: getColumns() }) pipeline.use( features.treeMode({ openKeys, onChangeOpenKeys, indentSize, isLeafNode(row: CrossTreeTableRenderRow, nodeMeta) { // 调用上层 isLeafNodeOpt 时,会从 row.node 中读取该表格行对应的 leftTreeNode return isLeafNodeOpt(row.node, nodeMeta) }, }), ) return { dataSource: pipeline.getDataSource(), columns: pipeline.getColumns(), } /** 获取表格的列配置 */ function getColumns(): ArtColumn[] { return [ { ...primaryColumn, getValue(row: CrossTreeTableRenderRow) { return row.node.value }, getCellProps(value: any, row: any) { if (primaryColumn.getCellProps) { return primaryColumn.getCellProps(row.node, row.nodes.length - 1) } }, render(value: any, row: any) { if (primaryColumn.render) { return primaryColumn.render(row.node, row.nodes.length - 1) } return value }, }, ...getDataPartColumns(), ] /** 获取表格数据部分的列配置 */ function getDataPartColumns() { return dfs(topTree, { depth: 0 }) function dfs(nodes: TopCrossTreeNode[], ctx: { depth: number }): ArtColumn[] { const result: ArtColumn[] = [] for (const node of nodes) { if (standardIsLeafNode(node)) { result.push(getDataColumn(node, ctx.depth)) } else { const { key, value, children, ...others } = node result.push({ ...others, name: value, children: dfs(children, { depth: ctx.depth + 1 }), }) } } return result } } function getDataColumn(topNode: TopCrossTreeNode, topDepth: number): ArtColumn { const columnGetValue = (row: CrossTreeTableRenderRow) => { const leftDepth = row.nodes.length - 1 const leftNode = row.node if (options.getValue) { return options.getValue(leftNode, topNode, leftDepth, topDepth) } return null } const { key, value, children, ...others } = topNode return { ...others, getValue: columnGetValue, name: value, children: null, render(value: any, row: CrossTreeTableRenderRow) { if (options.render) { const leftDepth = row.nodes.length - 1 const leftNode = row.node return options.render(value, leftNode, topNode, leftDepth, topDepth) } return value }, getCellProps(value, row: CrossTreeTableRenderRow) { if (options.getCellProps) { const leftDepth = row.nodes.length - 1 const leftNode = row.node return options.getCellProps(value, leftNode, topNode, leftDepth, topDepth) } }, } } } function getDataSource(): CrossTreeTableRenderRow[] { return dfs(leftTree, { nodes: [] }) function dfs(nodes: LeftCrossTreeNode[], ctx: { nodes: LeftCrossTreeNode[] }): CrossTreeTableRenderRow[] { const result: CrossTreeTableRenderRow[] = [] for (const node of nodes) { if (node.hidden) { // 跳过被隐藏的节点 continue } if (standardIsLeafNode(node)) { result.push({ [ROW_KEY]: node.key, node, nodes: [...ctx.nodes, node], }) } else { const nodes = [...ctx.nodes, node] ctx.nodes.push(node) const children = dfs(node.children, ctx) result.push({ [ROW_KEY]: node.key, node, nodes, children }) ctx.nodes.pop() } } return result } } }