image.png
将上一个树形结构表格修改为表头宽度传入,左侧第一列固定,并增加竖向滚动条与表头固定展示功能
修改后的代码如下:
TreeTable.vue
<template>
<!-- 树形表格容器 -->
<div class="tree-table-container">
<!-- 表格主体 -->
<table class="tree-table">
<!-- 表头 -->
<thead>
<tr>
<th v-for="(col, index) in columns" :key="index" :style="{
width: index === 0 ? '300rpx' : (col.width ? (typeof col.width === 'number' ? `${col.width}rpx` : col.width) : 'auto'),
left: col.fixed === 'left' ? '0' : 'auto',
zIndex: col.fixed ? 12 : 11
}" :data-fixed="col.fixed">
{{ col.title }}
</th>
</tr>
</thead>
<!-- 表格内容 -->
<tbody>
<!-- 循环渲染每一行数据 -->
<tr v-for="item in flattenedData" :key="item.Fid" :class="{ 'has-children': item.children }">
<!-- 动态渲染列 -->
<td v-for="(col, colIndex) in columns" :key="colIndex" :style="{
width: colIndex === 0 ? '300rpx' : (col.width ? (typeof col.width === 'number' ? `${col.width}rpx` : col.width) : 'auto'),
paddingLeft: colIndex === 0 ? `${(item.level + 1) * 16}px` : '12px',
position: col.fixed ? 'sticky' : 'static',
left: col.fixed === 'left' ? '0' : 'auto',
zIndex: col.fixed ? 1 : 'auto',
background: col.fixed ? 'white' : 'inherit'
}" :data-fixed="col.fixed">
<!-- 第一列特殊处理 -->
<template v-if="colIndex === 0">
<!-- 展开/折叠图标 -->
<span v-if="item.children" @click="() => toggleExpand(item.Fid)" class="expand-icon">
{{ expandedItems.includes(item.Fid) ? '−' : '+' }}
</span>
<!-- 名称文本 -->
<span class="name-text">{{ item[col.key] }}</span>
</template>
<template v-else>
{{ item[col.key] }}
</template>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
import { ref, computed, defineProps, defineExpose, PropType, nextTick } from 'vue'
/**
* 表格列定义接口
* @property {string} title - 列标题
* @property {string} key - 对应数据项的键名
* @property {number|string} [width] - 列宽度,支持数字(rpx)或字符串格式
* @property {'left'|'right'} [fixed] - 是否固定列及其位置
*/
interface Column {
title: string
key: string
width?: number | string // 单位rpx
fixed?: 'left' | 'right'
}
// 定义组件props
const props = defineProps({
/**
* 树形结构数据源
* @type {TreeItem[]}
*/
data: {
type: Array as PropType<TreeItem[]>,
required: true
},
/**
* 表格列配置
* @type {Column[]}
*/
columns: {
type: Array as PropType<Column[]>,
required: true
}
})
/**
* 树形数据项接口
* @property {string} Fid - 节点唯一标识
* @property {string} FparentId - 父节点标识
* @property {any} [key: string] - 其他动态属性
* @property {TreeItem[]} [children] - 子节点数组
*/
interface TreeItem {
Fid: string
FparentId: string
[key: string]: any
children?: TreeItem[]
}
/**
* 当前展开的节点ID数组
* @type {Ref<string[]>}
*/
const expandedItems = ref<string[]>([])
/**
* 递归扁平化树形数据
* @param {TreeItem[]} data - 树形数据
* @param {number} [level=0] - 当前层级
* @param {string} [parentId] - 父节点ID
* @returns {Array} 扁平化后的数据数组,包含level和parentId信息
*/
const flattenTree = (data: TreeItem[], level = 0, parentId?: string) => {
return data.flatMap((item) => {
const currentItem = { ...item, level, parentId }
if (item.children) {
return [currentItem, ...flattenTree(item.children, level + 1, item.Fid)]
}
return [currentItem]
})
}
/**
* 切换节点的展开/折叠状态
* @param {string} Fid - 要切换的节点ID
*/
const toggleExpand = (Fid: string) => {
const index = expandedItems.value.indexOf(Fid)
if (index > -1) {
expandedItems.value.splice(index, 1) // 如果已展开则折叠
} else {
expandedItems.value.push(Fid) // 如果已折叠则展开
}
// 视图更新后执行回调
nextTick(() => {
console.log('View updated')
})
}
/**
* 计算属性:获取当前可见的扁平化数据
* 根据展开状态过滤数据,只显示展开的节点及其子节点
* @returns {Array} 过滤后的扁平化数据
*/
const flattenedData = computed(() => {
const allItems = flattenTree(props.data)
return allItems.filter((item) => {
if (item.level === 0) return true // 始终显示根节点
let parentId = item.parentId
// 检查所有父节点是否都已展开
while (parentId !== undefined) {
if (!expandedItems.value.includes(parentId)) {
return false // 如果有父节点未展开,则隐藏该节点
}
const parentItem = allItems.find(i => i.Fid === parentId)
parentId = parentItem?.parentId
}
return true
})
})
</script>
<style scoped lang="scss">
.tree-table-container {
overflow-x: auto;
overflow-y: auto;
width: 100%;
position: relative;
height: 600px; /* 设置固定高度 */
max-height: 80vh; /* 最大高度为视口高度的80% */
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
/* 确保sticky定位正常工作 */
display: block;
}
/* 隐藏Webkit浏览器的滚动条 */
.tree-table-container::-webkit-scrollbar {
display: none;
}
.tree-table {
table-layout: fixed !important;
border-collapse: collapse;
font-family: Arial, sans-serif;
min-width: 100%;
width: auto;
}
.tree-table-container::-webkit-scrollbar {
height: 8px;
}
.tree-table-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 4px;
}
.tree-table-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
.tree-table-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
.tree-table th,
.tree-table td {
min-width: 100rpx;
width: auto;
padding: 12rpx;
text-align: left;
border: 1px solid #e0e0e0;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tree-table th {
background-color: #1976d2;
color: white;
font-weight: 500;
position: sticky;
top: 0;
// z-index: 10; /* 提高z-index确保在最上层 */
/* 告知浏览器某个元素即将发生 transform 属性的变化 */
will-change: transform;
}
.tree-table .expand-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 36rpx;
height: 36rpx;
background-color: #1976d2;
color: white;
border-radius: 50%;
cursor: pointer;
margin: 0 16rpx;
user-select: none;
transition: all 0.2s ease;
&:hover {
background-color: #1565c0;
}
}
.tree-table .name-text {
display: inline-block;
margin-left: 16rpx;
vertical-align: middle;
max-width: 200rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.tree-table .has-children>td {
font-weight: bold;
}
.tree-table tr:hover {
background-color: #f5f5f5;
}
</style>
传入的数据:
<TreeTable :data="result" :columns="columns" />
const columns = [
{ title: '科目', key: '科目', prop: 'subject', width: 400, fixed: 'left' },
{ title: '三栏账',key: '三栏账', prop: 'threeColumnAccount', width: 300 },
{ title: '明细账',key: '明细账', prop: 'detailAccount', width: '300' },
{ title: '账户方向',key: '账户方向', prop: 'accountDirection', width: 180 },
{ title: '期初借方',key: '期初借方', prop: 'beginningDebit', width: '80' },
{ title: '期初贷方',key: '期初贷方', prop: 'beginningCredit', width: '80' },
{ title: '本期借方',key: '本期借方', prop: 'currentDebit', width: '80' },
{ title: '本期贷方',key: '本期贷方', prop: 'currentCredit', width: '80' },
{ title: '累计借方',key: '累计借方', prop: 'cumulativeDebit', width: '80' },
{ title: '累计贷方',key: '累计贷方', prop: 'cumulativeCredit', width: '80' },
{ title: '期末借方',key: '期末借方', prop: 'endingDebit', width: '80' },
{ title: '期末贷方',key: '期末贷方', prop: 'endingCredit', width: '80' }
]
const result = ref(
[
{
orderStepId: "1",
Fid: "5",
FparentId: "0",
Pid: "5530",
核算主体: "5530",
科目: "我是测试",
会计科目: "0",
ItemGrp: "",
三栏账: "",
明细账: "",
账户方向: "",
期初借方: "4106236.00",
期初贷方: "4106236.00",
本期借方: "35250.00",
本期贷方: "35250.00",
累计借方: "35250.00",
累计贷方: "35250.00",
本期损益: "5486.73",
累计损益: "5486.73",
期末借方: "4106014.75",
期末贷方: "4106014.75",
开始时间: "2025-01-01 00:00:00.000",
截至时间: "2025-01-31 23:59:59.000",
children: [
{
orderStepId: "16",
Fid: "4",
FparentId: "5",
Pid: "5530,4939",
核算主体: "5530",
科目: "银行存款",
会计科目: "4939",
ItemGrp: "",
三栏账: "三栏式明细账",
明细账: "",
账户方向: "借",
期初借方: "2.67",
期初贷方: "0.00",
本期借方: "0.00",
本期贷方: "0.00",
累计借方: "0.00",
累计贷方: "0.00",
本期损益: "0.00",
累计损益: "0.00",
期末借方: "2.67",
期末贷方: "0.00",
开始时间: "2025-01-01 00:00:00.000",
截至时间: "2025-01-31 23:59:59.000",
children: [
{
orderStepId: "2",
Fid: "15",
FparentId: "4",
Pid: "5530,4939,5422",
核算主体: "5530",
科目: "工商银行",
会计科目: "0",
ItemGrp: "4939|5422",
三栏账: "",
明细账: "核算项目明细账",
账户方向: "",
期初借方: "2.67",
期初贷方: "0.00",
本期借方: "0.00",
本期贷方: "0.00",
累计借方: "0.00",
累计贷方: "0.00",
本期损益: "0.00",
累计损益: "0.00",
期末借方: "2.67",
期末贷方: "0.00",
开始时间: "2025-01-01 00:00:00.000",
截至时间: "2025-01-31 23:59:59.000"
}
]
}
]
}
]
);