树形表格示例

树形表格完整示例(Vue 2 + Element UI)

功能包含:

  • 展示父子层级结构

  • 支持展开/收起子项

  • 操作列(编辑、删除)

  • 自定义状态标签

示例代码

复制代码
<template>
  <div class="tree-table-demo">
    <el-card>
      <el-table
        :data="menuTree"
        row-key="id"
        border
        style="width: 100%"
        :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
      >
        <el-table-column prop="title" label="标题" />
        <el-table-column prop="type" label="类型" />
        <el-table-column prop="path" label="访问路径" />
        <el-table-column prop="route" label="路由地址" />
        <el-table-column prop="component" label="页面组件" />
        <el-table-column prop="status" label="状态">
          <template slot-scope="scope">
            <el-tag type="success">{{ scope.row.status }}</el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="180">
          <template slot-scope="scope">
            <el-button type="text" size="small">编辑</el-button>
            <el-button type="text" size="small">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'TreeTableDemo',
  data() {
    return {
      menuTree: [
        {
          id: 1,
          title: '系统管理',
          type: '目录',
          path: '/system',
          route: '/system',
          component: '',
          status: '已注册',
          children: [
            {
              id: 2,
              title: '菜单管理',
              type: '菜单',
              path: '/system/menu',
              route: '/system/menu',
              component: 'SystemMenuList',
              status: '已注册',
              children: [
                {
                  id: 3,
                  title: '新增',
                  type: '按钮',
                  path: 'SystemMenuCreate',
                  route: 'SystemMenuCreate',
                  component: '',
                  status: '已注册'
                },
                {
                  id: 4,
                  title: '修改',
                  type: '按钮',
                  path: 'SystemMenuUpdate',
                  route: 'SystemMenuUpdate',
                  component: '',
                  status: '已注册'
                },
                {
                  id: 5,
                  title: '删除',
                  type: '按钮',
                  path: 'SystemMenuDelete',
                  route: 'SystemMenuDelete',
                  component: '',
                  status: '已注册'
                }
              ]
            },
            {
              id: 6,
              title: '角色管理',
              type: '菜单',
              path: '/system/role',
              route: '/system/role',
              component: '',
              status: '已注册'
            },
            {
              id: 7,
              title: '用户管理',
              type: '菜单',
              path: '/system/user',
              route: '/system/user',
              component: '',
              status: '已注册'
            }
          ]
        },
        {
          id: 8,
          title: '工作台',
          type: '页面',
          path: '/workspace',
          route: '/dashboard/workspace/index',
          component: '',
          status: '已注册'
        },
        {
          id: 9,
          title: '关于',
          type: '菜单',
          path: '/about',
          route: '/core/about/index',
          component: '',
          status: '已注册'
        }
      ]
    }
  }
}
</script>

<style scoped>
.tree-table-demo {
  padding: 20px;
}
</style>

补充说明

属性 说明
row-key="id" 必须设置唯一标识字段,用于识别每一行
tree-props 指定子节点字段名(如 children)和是否有子节点的标志(可选)
嵌套结构 数据结构中通过 children 字段嵌套子项
操作列 可自定义按钮、图标、弹窗等交互

树形表格完整示例(Vue 2)

示例代码

复制代码
<template>
  <div class="tree-table">
    <table>
      <thead>
        <tr>
          <th>标题</th>
          <th>类型</th>
          <th>访问路径</th>
          <th>路由地址</th>
          <th>页面组件</th>
          <th>状态</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in flatData" :key="row.id" v-show="isVisible(row)">
          <td :class="'indent-' + row.level">
            <span
              v-if="Array.isArray(row.children) && row.children.length"
              class="toggle"
              @click="toggle(row)"
            >
              {{ row.expanded ? '▼' : '▶' }}
            </span>
            {{ row.title }}
          </td>
          <td>{{ row.type }}</td>
          <td>{{ row.path }}</td>
          <td>{{ row.route }}</td>
          <td>{{ row.component }}</td>
          <td>
            <span :class="['tag', row.status === '已注册' ? 'success' : 'info']">
              {{ row.status }}
            </span>
          </td>
          <td>
            <button class="btn" @click="edit(row)">编辑</button>
            <button class="btn" @click="remove(row)">删除</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  name: 'TreeTable',
  data() {
    return {
      treeData: [
        {
          id: 1,
          title: '系统管理',
          type: '目录',
          path: '/system',
          route: '/system',
          component: '',
          status: '已注册',
          children: [
            {
              id: 2,
              title: '菜单管理',
              type: '菜单',
              path: '/system/menu',
              route: '/system/menu',
              component: 'SystemMenuList',
              status: '已注册',
              children: [
                {
                  id: 3,
                  title: '新增',
                  type: '按钮',
                  path: 'SystemMenuCreate',
                  route: 'SystemMenuCreate',
                  component: '',
                  status: '已注册'
                },
                {
                  id: 4,
                  title: '修改',
                  type: '按钮',
                  path: 'SystemMenuUpdate',
                  route: 'SystemMenuUpdate',
                  component: '',
                  status: '已注册'
                },
                {
                  id: 5,
                  title: '删除',
                  type: '按钮',
                  path: 'SystemMenuDelete',
                  route: 'SystemMenuDelete',
                  component: '',
                  status: '已注册'
                }
              ]
            },
            {
              id: 6,
              title: '角色管理',
              type: '菜单',
              path: '/system/role',
              route: '/system/role',
              component: '',
              status: '已注册'
            },
            {
              id: 7,
              title: '用户管理',
              type: '菜单',
              path: '/system/user',
              route: '/system/user',
              component: '',
              status: '已注册'
            }
          ]
        },
        {
          id: 8,
          title: '工作台',
          type: '页面',
          path: '/workspace',
          route: '/dashboard/workspace/index',
          component: '',
          status: '已注册'
        },
        {
          id: 9,
          title: '关于',
          type: '菜单',
          path: '/about',
          route: '/core/about/index',
          component: '',
          status: '已注册'
        }
      ],
      flatData: []
    }
  },
  created() {
    this.flatData = this.flattenTree(this.treeData)
  },
  methods: {
    flattenTree(nodes, level = 0, parent = null) {
      const result = []
      nodes.forEach(node => {
        this.$set(node, 'level', level)
        this.$set(node, 'parent', parent)
        this.$set(node, 'expanded', false)
        result.push(node)
        if (Array.isArray(node.children)) {
          result.push(...this.flattenTree(node.children, level + 1, node))
        }
      })
      return result
    },
    isVisible(row) {
      let parent = row.parent
      while (parent) {
        if (!parent.expanded) return false
        parent = parent.parent
      }
      return true
    },
    toggle(row) {
      row.expanded = !row.expanded
    },
    edit(row) {
      alert(`编辑:${row.title}`)
    },
    remove(row) {
      if (confirm(`确定删除 ${row.title} 吗?`)) {
        alert('删除成功(模拟)')
      }
    }
  }
}
</script>

<style scoped>
table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}
th, td {
  border: 1px solid #ddd;
  padding: 8px 12px;
  text-align: left;
}
.indent-0 { padding-left: 0; }
.indent-1 { padding-left: 20px; }
.indent-2 { padding-left: 40px; }
.indent-3 { padding-left: 60px; }
.toggle {
  cursor: pointer;
  margin-right: 5px;
  font-weight: bold;
}
.tag {
  display: inline-block;
  padding: 2px 6px;
  border-radius: 4px;
  font-size: 12px;
  color: #fff;
}
.success { background-color: #67c23a; }
.info { background-color: #909399; }
.btn {
  padding: 4px 8px;
  font-size: 12px;
  margin-right: 4px;
  cursor: pointer;
  border: none;
  border-radius: 4px;
  background-color: #409eff;
  color: white;
}
.btn:hover {
  background-color: #66b1ff;
}
</style>
相关推荐
是立不是利1 小时前
大文件导致请求被拒的连锁反应
开发语言·前端·javascript
志尊宝1 小时前
Vue3 零基础每日笔记(026):emit 子传父——defineEmits 与事件校验
前端·javascript·html·vue3
ssshooter1 小时前
Node.js 天生就是异步的,为什么还要消息队列?
javascript·后端·面试
mONESY3 小时前
从 RAG 到 Agentic RAG:固定流程的五个毛病,和把它拆成一张图
javascript
梦帮科技3 小时前
从 Guest 到 Platinum:Prompt 配额、API Key 与访问控制的安全闭环
javascript·git·架构·node.js·reactjs·html5·visual studio
linux_cfan3 小时前
videojs v10 源代码系列解读:10 · `DestroyMixin`:双重 rAF 延迟销毁
前端·javascript·音视频
雪芽蓝域zzs3 小时前
第四十六节:顶部【驾驶舱】独立大屏页面实现
前端·javascript·vue.js
右耳朵猫AI4 小时前
Node.js周刊2026W37 | 三处进程崩溃修复、fs 内置 glob、Workers 模块注册表、Vitest 5.0
javascript·后端·node.js
摸鱼仙人~4 小时前
Vue 应用完整启动链路
前端·vue.js·软件工程
右耳朵猫AI4 小时前
Web前端周刊2026W37 | Shopify 转原生、React 编译 Rust 化、Vitest 5.0、Rslib 1.0
前端·javascript·react.js·typescript·node.js