基于 elements3 包装的 可展开 table 组件
基于 elements3 包装的 可展开 table 组件
1.手风琴效果,一次只能展开一个
2.动态加载 展开内容 (根据接口)
效果:
<template><el-tableref="tableRef":data="tableData"v-loading="loading"v-if="columns.length > 0"style="width: 100%"@expand-change="handleExpand"class="custom-expend-table"header-row-class-name="custom-table-header"row-class-name="custom-table-row"@row-click="handleClick"><!-- 折叠列 --><el-table-column type="expand" v-if="hasExpand" width="20"><template #default="{ row }"><div class="expand-content-wrapper"><el-tablev-if="childDataMap[row[rowKey]]?.length":data="childDataMap[row[rowKey]]"size="small"header-row-class-name="child-table-header"row-class-name="child-table-row"><el-table-columnv-for="col in childColumns":key="col.column":label="col.showName":prop="col.column":min-width="col.width || col.column === 'month' ? 50 : 80"align="center"><template #default="{ row }">{{ row[col.column] || '-' }}</template></el-table-column></el-table><div v-else class="text-gray-500 text-center py-4 empty-box">{{ expandLoadingMap[row[rowKey]] ? '加载中...' : '暂无数据' }}</div></div></template></el-table-column><!-- 主表列 --><el-table-columnv-for="col in columns":key="col.column":prop="col.column":label="col.showName":min-width="col.width || col.column === 'year' ? 50 : 80"align="center"><template #default="{ row }">{{ row[col.column] || '-' }}</template></el-table-column><template #empty><EmptyBox desc="无数据" v-if="height" :height="'auto'" /><EmptyBox desc="无数据" v-else /></template></el-table>
</template><script setup lang="ts">
import { ref , PropType,computed } from 'vue'
import { ElMessage } from 'element-plus'
import {sortByDate} from "@/utils/formate/sort_formate";
import { isEqual } from 'lodash-es';const props = defineProps({height: {type: [String, Boolean],default: '180'},tableData: {type: Array as PropType<any[]>,required: true,},columns: {type: Array as PropType<any[]>, // 使用定义好的 Column 类型required: true,}, // 主表字段rowKey: {type: String,default: 'id',},fetchChildData: {type: Function || Function as PropType<(row: any) => Promise<any>> ,required: false,default: undefined},loading: {type: Boolean,default: false,},isThrottle: {type: Boolean,default: false,}
})const hasExpand = computed(() => typeof props.fetchChildData === 'function')// 保存每行子表数据
const childDataMap = ref<Record<string, any[]>>({})
const childColumns = ref<any[]>([])
const expandLoadingMap = ref<Record<string, boolean>>({})// 展开行触发:异步加载子表
const handleExpand = async (row: any, expanded: any[]) => {const rowKey: any = row[props.rowKey]// 当前展开的 rowKey(row 点击后立即触发)const isExpanding: any = expanded.some((t: any) => rowKey == t[props.rowKey])// 收起时清空记录if (!isExpanding) {if (currentExpandedRowKey.value === rowKey) {currentExpandedRowKey.value = null}return}// 若当前点击的不是同一行,先收起上一个if (currentExpandedRowKey.value && currentExpandedRowKey.value !== rowKey) {const prevRow = props.tableData?.find((item: any) => item[props.rowKey] === currentExpandedRowKey.value)if (prevRow && tableRef.value) {tableRef.value.toggleRowExpansion(prevRow, false)}}// 设置当前展开行为当前行currentExpandedRowKey.value = rowKey// 判断是否需要加载数据if (props.isThrottle && childDataMap.value[rowKey]) returnexpandLoadingMap.value[rowKey] = truetry {if (!props.fetchChildData) returnconst { head, data } = await props.fetchChildData(row)sortByDate(data, 'month', 'desc')childDataMap.value[rowKey] = data || []childColumns.value = head || []} catch (err) {ElMessage.error('子表加载失败')childDataMap.value[rowKey] = []} finally {expandLoadingMap.value[rowKey] = false}
}const tableRef = ref()
const currentExpandedRowKey = ref<string | null>(null)
const currentExpandedRow = ref<any>(null)
const handleClick = (row: any) => {if (!hasExpand.value) returnconst rowKey = row[props.rowKey]// 从 tableData 中找到真正要展开的那一行(必要)const matchedRow: any = props.tableData?.find((item: any) => item[props.rowKey] === rowKey)if (!matchedRow) {console.warn('找不到匹配的行数据:', rowKey)return}// 👉 判断是否是“当前已展开的那一行”const isSameRow = (currentExpandedRowKey.value === rowKey &&isEqual(currentExpandedRow.value, matchedRow))// 如果点击的是当前已展开行 → 收起if ( isSameRow ) {tableRef.value.toggleRowExpansion(matchedRow, false)currentExpandedRowKey.value = nullcurrentExpandedRow.value = nullreturn}// 收起之前已展开的if (currentExpandedRowKey.value !== null) {const prevRow = props.tableData.find((item: any) => item[props.rowKey] === currentExpandedRowKey.value)if (prevRow) {tableRef.value.toggleRowExpansion(prevRow, false)}}// 展开新行tableRef.value.toggleRowExpansion(matchedRow, true)currentExpandedRowKey.value = rowKeycurrentExpandedRow.value = matchedRow
}defineExpose({handleClick,
});</script>
使用:
<CustomExpandTableref="expandTable":columns="tableData.head":tableData="tableData.data":fetchChildData="getSubTable"rowKey="stattime"></CustomExpandTable>