Element UI / Element Plus 动态表格深度实践 — 动态列、权限控制、列排序、宽度记忆与右键菜单的完整解决方案

2026-01-05 73 浏览 0 评论

在中后台系统中, el-table 动态表格 几乎是标配。但当需求逐步复杂之后,很多问题会集中爆发:

  • 列需要动态显示 / 隐藏
  • 列顺序可配置并持久化
  • 列宽拖拽后需要记忆 / 恢复默认
  • 操作列按钮完全配置化(事件名不固定)
  • 表头右键菜单 + 阻止浏览器默认菜单
  • 动态列顺序更新后, el-table 不刷新

本文基于真实项目踩坑经验,给出一套 稳定、可扩展、可维护 的解决方案。


一、动态列的核心设计思想

1️⃣ 不要依赖 this.$refs.elTable.columns

el-table 内部的 columns

  • 初始化时才注册
  • 有缓存( realWidth 、布局信息)
  • 生命周期和 Vue 不同步

👉 正确做法完全由我们自己维护一份 columns 配置数组,用 v-for 渲染 <el-table-column>


二、基础动态列结构设计

tableColumns = [
  {
    prop: 'userInfo',
    label: '用户信息',
    width: 240,
    show: true,                // boolean | function(row, index)
    slot: 'table-user-info'
  },
  {
    prop: 'action',
    label: '操作',
    width: 140,
    fixed: 'right',
    slot: 'ActionCell',
    dropdown: { atLeast: 1 },
    custom: [
      { text: '编辑', type: 'primary', emit: 'edit', show: true },
      { text: '删除', type: 'danger', emit: 'delete', show: true }
    ]
  }
]

show 支持三种形态

show: true                 // 始终显示
show: false                // 始终隐藏
show: (row, index) => {}   // 按行控制显示

三、动态渲染 el-table-column(关键)

<el-table :data="tableData" ref="dataTable">
  <el-table-column
    v-for="col in tableColumns"
    v-if="col.show !== false"
    :key="(col.prop || col.label) + '-' + col.order"
    :prop="col.prop"
    :label="col.label"
    :width="col.width || undefined"
    :fixed="col.fixed"
  >
    <template #default="scope">
      <!-- 操作列 -->
      <table-action-cell
        v-if="col.slot === 'ActionCell'"
        :row="scope.row"
        :custom="col.custom"
        :dropdown="col.dropdown"
        v-on="actionListeners"
      />

      <!-- 普通插槽列 -->
      <component
        v-else-if="col.slot"
        :is="col.slot"
        :row="scope.row"
        :value="scope.row[col.prop]"
      />

      <!-- 默认 -->
      <span v-else>{{ scope.row[col.prop] }}</span>
    </template>
  </el-table-column>
</el-table>

四、操作列完全配置化(事件名不固定)

1️⃣ 子组件:动态 emit

<el-button
  v-for="btn in custom"
  :key="btn.emit"
  :type="btn.type"
  @click="$emit(btn.emit, row)"
>
  {{ btn.text }}
</el-button>

2️⃣ 父组件:动态事件监听器生成

computed: {
  actionListeners() {
    const actionCol = this.tableColumns.find(v => v.slot === 'ActionCell');
    const ret = {};
    actionCol?.custom?.forEach(item => {
      ret[item.emit] = row => this.$emit(item.emit, row);
    });
    return ret;
  }
}

❗ 注意: this.$emit(item.emit) 不能直接写 ,必须返回函数。


五、列显示隐藏(Checkbox 控制)

列配置对象结构

columns = {
  name: { width: '', show: true },
  age: { width: '', show: false }
}

绑定 checkbox-group

<el-checkbox-group v-model="columnsCheck" @change="syncColumns">
  <el-checkbox
    v-for="(col, key) in columns"
    :key="key"
    :label="key"
  >
    {{ key }}
  </el-checkbox>
</el-checkbox-group>
mounted() {
  this.columnsCheck = Object.keys(this.columns).filter(
    k => this.columns[k].show
  )
},
methods: {
  syncColumns(keys) {
    Object.keys(this.columns).forEach(k => {
      this.columns[k].show = keys.includes(k)
    })
  }
}

六、列顺序 + 宽度 + 显隐的持久化合并

合并规则

  • label 关联
  • colData 顺序排列
  • 不存在的列排最后
  • width !== '' 才覆盖
  • show 始终覆盖
updateColumnsByColData() {
  const map = new Map(this.colData.map((c, i) => [c.label, { ...c, _order: i }]))

  const ordered = this.colData.map((cfg, i) => {
    const t = this.tableColumns.find(x => x.label === cfg.label)
    if (!t) return null
    return {
      ...t,
      order: i,
      width: cfg.width !== '' ? cfg.width : t.width,
      show: cfg.show ?? t.show
    }
  }).filter(Boolean)

  const rest = this.tableColumns
    .filter(c => !map.has(c.label))
    .map((c, i) => ({ ...c, order: ordered.length + i }))

  this.tableColumns = [...ordered, ...rest]
}

七、⚠️ 为什么列顺序变了,el-table 不变?

原因(核心坑)

  • el-table 按 key 复用 DOM
  • 顺序变了,但 key 没变 → 不重建列

✅ 终极解决方案

让顺序参与 key

:key="(col.prop || col.label) + '-' + col.order"

这是整个动态列系统里 最关键的一行代码


八、恢复默认列宽( width='' 无效)

正确方式

resetColumnWidth() {
  this.$refs.dataTable.columns.forEach(col => {
    delete col.width
    delete col.realWidth
  })
  this.$refs.dataTable.doLayout()
}

九、表头右键菜单 + 阻止浏览器默认菜单

<el-table @header-contextmenu="onHeaderContextMenu" />
onHeaderContextMenu(column, event) {
  event.preventDefault()
  this.showMenu = true
  this.menuX = event.pageX
  this.menuY = event.pageY
}

点击任意位置关闭

mounted() {
  document.addEventListener('click', this.closeMenu)
},
beforeUnmount() {
  document.removeEventListener('click', this.closeMenu)
},
methods: {
  closeMenu() {
    this.showMenu = false
  }
}

十、总结(最佳实践)

只信自己的 columns,不信 el-table 内部 columns

动态列必须用 v-for + 正确 key

顺序变动 → key 必须变

配置驱动一切(show / width / emit)

不要直接操作 DOM / 私有 API


发布评论

发布评论前请先 登录

评论列表 0

暂无评论