antDesign树形表格或者额外展开行-子父级表格默认展开行

本文介绍了三种在AntDesign中设置表格默认展开行的方法及其优缺点。方案一利用`defaultExpandAllRows`和`v-if`,但在无数据时列表不展示;方案二通过改变`key`值强制重新渲染,但需要频繁监听表格变化;方案三使用`expandedRowKeys`配合手动事件处理,解决了展开收起问题,但增加了复杂性。针对不同场景,开发者可根据需求选择合适方案。

antDesign子父级表格设置默认展开行

方案1:使用defaultExpandAllRows和 v-if来实现

// 需要加个v-if="tableData.length>0"
<a-table
        v-if="tableData.length>0"
        :defaultExpandAllRows="true">
 
// 获取列表时添加延迟函数
 setTimeout(() => {
           this.tableData= res.data.list
          }, 0)

缺点:在使用v-if后,如果列表没数据,列表不展示

方案2:(记得tablekey是一个动态的 可以绑定一个获取详情刷新的值)
给Table设置一个key,获取数据之后改变这个key值,借助了key改变自动变成新的component可以解决这个问题。

// 增加 key
<a-table
  :key="tableKey"
  :defaultExpandAllRows="true"
  :columns="columns"
  :row-key="(record, index) => record.id"
  :data-source="tableData"
>

缺点:要时刻监听列表的所有变化(新增,删除。操作,检索等)一系列影响到表格变化的操作,变化时动态变化tableKey 

方案3:使用expandedRowKeys自定义默认展开行 --- 推荐

使用了expandedRowKeys默认展开行后,点击展开收

import { Card, Button, Spin } from 'antd' import { ProForm, ProFormDateRangePicker, ProFormTreeSelect, } from '@ant-design/pro-components' import ElectricityTotal from '@/assets/electricityTotal.png' import ElectricityWaste from '@/assets/wasteTotal.png' import ElectricityRate from '@/assets/wasteRate.png' import WaterTotal from '@/assets/waterTotal.png' import WaterWaste from '@/assets/waterWasteTotal.png' import WaterRate from '@/assets/waterWasteRate.png' import Electricity from '@/assets/electricity.png' import Water from '@/assets/water.png' import style from './index.module.scss' import { useState, useRef, useEffect } from 'react' import { useAreaListTree, useSearch } from './query/index' // 1. 原始树形数据类型 interface MeterItem { deviceId: string deviceName: string totalWastageNum: number totalWastagePercent: string wastagePercent?: string usageNum: number wastageNum?: number childList?: MeterItem[] } // 2. 层节点类型(新增 rowspan) interface LevelNode extends Omit<MeterItem, 'childList'> { level: number parentId: string | null hasChildren: boolean rowspan: number } export function Component() { const [index, setIndex] = useState(0) const [loading, setLoading] = useState(false) const [treeNodes, setTreeNodes] = useState<LevelNode[]>([]) const [maxLevel, setMaxLevel] = useState(1) const [tableRows, setTableRows] = useState<LevelNode[][]>([]) const { data: areaListTree } = useAreaListTree({}) const [form, setForm] = useState({ dateStart: '', dateEnd: '', areaId: '', }) // 接口数据解构 const { data: { totalUsageNumElectricity, totalUsageNumWater, totalWastageNumElectricity, totalWastageNumWater, totalWastagePercentElectricity, totalWastagePercentWater, childListWater = [], childListElectricity = [], } = { childListWater: [], childListElectricity: [], }, refetch, isSuccess, } = useSearch(form) const isInited = useRef(false) const isUpdating = useRef(false) const items = [ { key: 1, label: '用电损耗' }, { key: 2, label: '用水损耗' }, ] // ------------------------------ 计算垂直合并数(rowspan) ------------------------------ const calculateNodeRowspan = ( node: MeterItem ): { node: LevelNode; children: LevelNode[] } => { const { childList, ...rest } = node const hasChildren = !!childList?.length let totalRowspan = 1 const childNodes: LevelNode[] = [] if (hasChildren) { childList.forEach(child => { const { node: childNode, children: grandChildren } = calculateNodeRowspan(child) childNodes.push(childNode, ...grandChildren) totalRowspan += childNode.rowspan }) } const levelNode: LevelNode = { ...rest, level: 0, parentId: null, hasChildren, rowspan: totalRowspan, } return { node: levelNode, children: childNodes } } // ------------------------------ 生成带层的扁平节点列表 ------------------------------ const generateLevelNodes = ( rootItems: MeterItem[] ): { nodes: LevelNode[]; maxLevel: number } => { if (!rootItems || rootItems.length === 0) { return { nodes: [], maxLevel: 1 } } const allNodes: LevelNode[] = [] const traverse = ( items: MeterItem[], parentId: string | null, currentLevel: number ) => { items.forEach(item => { const { node: currentNode, children: childNodes } = calculateNodeRowspan(item) const levelNode = { ...currentNode, level: currentLevel, parentId } allNodes.push(levelNode) if (item.childList && item.childList.length > 0) { traverse(item.childList, currentNode.deviceId, currentLevel + 1) } }) } traverse(rootItems, null, 1) const currentMaxLevel = Math.max(...allNodes.map(n => n.level), 1) return { nodes: allNodes, maxLevel: currentMaxLevel } } // ------------------------------ 核心:生成表格(每仅含实际列,无空格)------------------------------ const generateTableRows = ( nodes: LevelNode[] ): { rows: LevelNode[][]; rowColMap: Record<number, number> } => { if (!nodes || nodes.length === 0) { return { rows: [], rowColMap: {} } } const rows: LevelNode[][] = [] const rowColMap: Record<number, number> = {} // 构建映射 const childrenMap: Record<string, LevelNode[]> = {} nodes.forEach(node => { const key = node.parentId || 'root' if (!childrenMap[key]) childrenMap[key] = [] childrenMap[key].push(node) }) // DFS 深度优先构建每一的实际内容 const dfs = (parentId: string | null, currentPath: LevelNode[]) => { const key = parentId || 'root' const siblings = childrenMap[key] || [] siblings.forEach(node => { // 创建当前路径副本,并在对应层插入节点 const path = [...currentPath] const idx = node.level - 1 path[idx] = node path.length = idx + 1 // 截断多余部分 rows.push(path) const rowIndex = rows.length - 1 rowColMap[rowIndex] = path.length // 继续节点 if (node.hasChildren) { dfs(node.deviceId, path) } }) } dfs(null, []) return { rows, rowColMap } } // ------------------------------ 空数据重置 ------------------------------ const resetEmptyTable = () => { setTreeNodes([]) setTableRows([]) setMaxLevel(1) } // ------------------------------ 更新表格 ------------------------------ const updateTreeTable = (targetType: 0 | 1) => { if (loading || isUpdating.current) return isUpdating.current = true try { const targetData = targetType === 0 ? childListElectricity : childListWater if (!targetData || targetData.length === 0) { resetEmptyTable() return } const { nodes: levelNodes, maxLevel: newMaxLevel } = generateLevelNodes(targetData) const { rows: newTableRows } = generateTableRows(levelNodes) setTreeNodes(levelNodes) setTableRows(newTableRows) setMaxLevel(newMaxLevel) } finally { isUpdating.current = false } } // 初始加载 useEffect(() => { if (!isInited.current && isSuccess) { updateTreeTable(0) isInited.current = true } }, [isSuccess]) // 查询处理 const handleSearch = async (value: { date?: [string, string] areaId?: string }) => { setLoading(true) try { resetEmptyTable() setForm({ dateStart: value.date?.[0] || '', dateEnd: value.date?.[1] || '', areaId: value.areaId || '', }) const result = await refetch() const data = result.data const targetData = index === 0 ? data?.childListElectricity : data?.childListWater if (!targetData || targetData.length === 0) { resetEmptyTable() return } const { nodes: levelNodes, maxLevel: newMaxLevel } = generateLevelNodes(targetData) const { rows: newTableRows } = generateTableRows(levelNodes) setTreeNodes(levelNodes) setTableRows(newTableRows) setMaxLevel(newMaxLevel) } finally { setLoading(false) } } // Tab切换 const handleTabChange = (i: number) => { if (i === index || loading || isUpdating.current) return setIndex(i) resetEmptyTable() const targetData = i === 0 ? childListElectricity : childListWater if (!targetData || targetData.length === 0) return const { nodes: levelNodes, maxLevel: newMaxLevel } = generateLevelNodes(targetData) const { rows: newTableRows } = generateTableRows(levelNodes) setTreeNodes(levelNodes) setTableRows(newTableRows) setMaxLevel(newMaxLevel) } // 数字格式化 const formatNumber = (num: number | string): string => { const value = Number(num) return isNaN(value) || value === 0 ? '0' : (value / 10000).toFixed(2) } // ============================== 渲染 ============================== return ( <Card> {/* 搜索表单 */} <ProForm layout="inline" style={{ marginTop: '30px' }} onFinish={handleSearch} submitter={{ render: ({ submit, reset }) => ( <> <Button type="primary" onClick={submit} style={{ marginRight: 8 }} loading={loading} disabled={loading} > 查询 </Button> <Button onClick={reset} disabled={loading}> 重置 </Button> </> ), }} > <ProFormDateRangePicker name="date" label="统计日期" fieldProps={{ style: { width: 300 } }} /> <ProFormTreeSelect name="areaId" label="区域" fieldProps={{ style: { width: 300 }, fieldNames: { label: 'name', value: 'id', children: 'children' }, treeData: areaListTree, placeholder: '请选择区域', allowClear: true, disabled: loading, showSearch: true, treeNodeFilterProp: 'name', }} /> </ProForm> {/* 汇总信息栏 */} <div className={style.infoBox}> <div className={style.infoElectricity}> <div className={style.infoElectricityTotal}> <img src={ElectricityTotal} alt="用电总量图标" /> <div className={style.infoExtra}> <div className={style.commonBold}> {formatNumber(totalUsageNumElectricity as number) || 0}万kWh </div> <div>用电总量</div> </div> </div> <div className={style.infoElectricityWaste}> <img src={ElectricityWaste} alt="用电损耗总量图标" /> <div className={style.infoExtra}> <div className={style.commonBold}> {formatNumber(totalWastageNumElectricity as number) || 0}万KWH </div> <div>损耗总量</div> </div> </div> <div className={style.infoElectricityRate}> <img src={ElectricityRate} alt="用电损耗率图标" /> <div className={style.infoExtra}> <div className={style.commonBold}> {totalWastagePercentElectricity || '-'} </div> <div>损耗率</div> </div> </div> </div> <div className={style.infoWater}> <div className={style.infoWaterTotal}> <img src={WaterTotal} alt="用水总量图标" /> <div className={style.infoExtra}> <div className={style.commonBold}> {formatNumber(totalUsageNumWater as number) || 0}万m³ </div> <div>用水总量</div> </div> </div> <div className={style.infoWaterWaste}> <img src={WaterWaste} alt="用水损耗总量图标" /> <div className={style.infoExtra}> <div className={style.commonBold}> {formatNumber(totalWastageNumWater as number) || 0}万m³ </div> <div>损耗总量</div> </div> </div> <div className={style.infoWaterRate}> <img src={WaterRate} alt="用水损耗率图标" /> <div className={style.infoExtra}> <div className={style.commonBold}> {totalWastagePercentWater || '-'} </div> <div>损耗率</div> </div> </div> </div> </div> {/* 标签切换 */} <div className={style.navBox}> {items.map((v, i) => ( <div key={v.key} className={index === i ? style.navActive : ''} onClick={() => handleTabChange(i)} style={{ marginRight: i === 0 ? '32px' : 0, cursor: loading || isUpdating.current ? 'not-allowed' : 'pointer', opacity: loading || isUpdating.current ? 0.6 : 1, }} > {v.label} </div> ))} <div style={{ flexGrow: 1, textAlign: 'right', fontSize: '16px', color: '#333333', paddingRight: '20px', }} > 单位:{index === 0 ? 'kwh' : 'm³'} </div> </div> {/* 树形表格 */} <div className={style.treeTableContainer} style={{ marginTop: 20, overflowX: 'auto' }} > <Spin spinning={loading || (!isInited.current && !isSuccess)}> <table className={style.horizontalTreeTable} border={1} cellPadding="0" cellSpacing="0" > <thead> <tr> {Array.from({ length: maxLevel }).map((_, colIdx) => ( <th key={colIdx} className={style.tableTh} style={{ width: '200px' }} > {colIdx + 1}{index === 0 ? '电表' : '水表'} </th> ))} </tr> </thead> <tbody> {tableRows.length > 0 ? ( tableRows.map((row, rowIndex) => { const actualCols = row.length return ( <tr key={rowIndex} className={style.tableTr}> {row.map((node, colIndex) => { if (!node) return null return ( <td key={`${node.deviceId}-${colIndex}`} className={style.nodeTd} rowSpan={node.rowspan} > <div className={style.nodeContent}> <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', }} > <img src={index === 0 ? Electricity : Water} alt={`${node.level}表`} className={style.nodeIcon} /> <div className={style.deviceName}> {node.deviceName} </div> </div> <div className={style.nodeInfo}> <div className={style.deviceData}> <div> 用量: <span> {formatNumber(node.usageNum) || 0} </span> </div> {node.wastageNum !== undefined && ( <div> 本损耗: <span> {formatNumber(node.wastageNum) || 0} </span> </div> )} {node.wastagePercent !== undefined && ( <div> 本损耗率: <span>{node.wastagePercent || '0%'}</span> </div> )} <div> 总损耗: <span> {formatNumber(node.totalWastageNum) || 0} </span> </div> <div> 总损耗率: <span> {node.totalWastagePercent || '0%'} </span> </div> </div> </div> </div> </td> ) })} {/* 补齐剩余列,使与表头对齐 */} {Array.from({ length: maxLevel - actualCols }).map( (_, i) => ( <td key={`pad-${i}`} className={style.emptyTd}></td> ) )} </tr> ) }) ) : ( <tr> <td colSpan={maxLevel} className={style.emptyRowTd}> 暂无设备数据 </td> </tr> )} </tbody> </table> </Spin> </div> </Card> ) }
最新发布
12-06
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值