uniapp树形结构表格左侧第一列与表头固定

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"
            }
          ]
        }
      ]
    }
  ]
);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容