Vue2 + Element UI 通用表格组件封装:高效搞定中后台数据展示

在 Vue2 中后台管理系统中,表格是数据展示的核心组件 ------ 用户列表、订单管理、数据统计等场景都离不开它。但重复编写表格结构、分页逻辑、筛选功能,不仅效率低下,还会导致代码冗余、维护困难。本文基于 Vue2 + Element UI,封装一个配置化、高复用、功能完备的通用表格组件,支持动态列、分页、排序、选择、树形结构等核心功能,让表格开发从 "重复编码" 变成 "配置化组装"。

一、封装目标与核心价值

1. 封装核心目标

  • 支持 Element UI 表格核心特性(边框、斑马纹、固定列、树形结构等)
  • 配置化生成表格列,无需重复编写 <el-table-column>
  • 内置分页组件,统一分页逻辑(页码切换、条数选择)
  • 支持筛选区、选择列、序号列、操作列的灵活开关
  • 支持自定义单元格渲染、操作列按钮,适配复杂业务场景
  • 提供表格操作 API(展开 / 收起树形行、获取选中行等)

2. 相比原生开发的优势

对比维度 原生开发方式 通用表格封装方式
开发效率 重复编写表格 + 分页结构,效率低 配置化生成,核心功能一键启用
代码维护 表格结构分散,修改需改多处 配置集中管理,修改更便捷
功能统一性 分页、排序逻辑易不一致 统一封装,交互体验一致
扩展性 自定义单元格需单独编写 支持 render 函数 + 插槽,灵活扩展
易用性 需手动维护分页、选中状态 内置状态管理,对外暴露简洁 API

二、技术选型

  • 核心框架:Vue2(Options API)
  • UI 组件库:Element UI(2.x 版本,表格 + 分页核心依赖)
  • 样式预处理:SCSS(结构化编写样式,支持组件样式隔离)

三、通用表格组件完整实现(BaseTable)

1. 组件核心思路

通过 props 接收数据源列配置分页参数 等核心配置,内部集成 <el-table><el-pagination>,通过 v-for 遍历列配置动态渲染表格列,同时封装分页、排序、选择等逻辑,对外暴露统一事件和 API,满足不同业务场景需求。

2. 完整组件代码(BaseTable.vue)

js 复制代码
<template>
  <div class="base-table">
    <!-- 筛选搜索区域(支持插槽自定义) -->
    <div v-if="showSearch" class="filter-container">
      <el-row :gutter="20">
        <el-col :span="24">
          <slot name="search"></slot> <!-- 筛选表单插槽,父组件自定义 -->
        </el-col>
      </el-row>
    </div>

    <!-- 表格主体 -->
    <el-table
      v-loading="loading"
      ref="table"
      :data="data"
      :border="border"
      :stripe="stripe"
      :height="height"
      :max-height="maxHeight"
      :row-key="rowKey"
      :tree-props="treeProps"
      :default-sort="defaultSort"
      :header-cell-style="headerCellStyle"
      :cell-style="cellStyle"
      @sort-change="handleSortChange" <!-- 排序事件 -->
      @selection-change="handleSelectionChange" <!-- 选择事件 -->
      @row-click="handleRowClick" <!-- 行点击事件 -->
    >
      <!-- 选择列(批量操作) -->
      <el-table-column
        v-if="showSelection"
        type="selection"
        width="55"
        align="center"
        :selectable="selectable" <!-- 自定义是否可选择 -->
      />

      <!-- 序号列 -->
      <el-table-column
        v-if="showIndex"
        type="index"
        label="序号"
        width="80"
        align="center"
        :index="getIndex" <!-- 自定义序号计算(支持分页) -->
      />

      <!-- 动态列(核心配置) -->
      <template v-for="(column, index) in columns">
        <el-table-column
          :key="column.prop || index"
          v-bind="column" <!-- 透传列配置属性(width、fixed、label等) -->
          :align="column.align || 'center'"
          :min-width="column.minWidth || 100"
        >
          <template #default="scope"> <!-- 单元格内容插槽 -->
            <template v-if="column.render">
              <!-- 支持 render 函数自定义渲染(复杂单元格) -->
              <render-content
                :render="column.render"
                :row="scope.row"
                :index="scope.$index"
                :column="column"
              ></render-content>
            </template>
            <template v-else-if="column.slotName">
              <!-- 支持具名插槽自定义渲染(更灵活的自定义) -->
              <slot :name="column.slotName" :row="scope.row" :index="scope.$index"></slot>
            </template>
            <template v-else>
              <!-- 默认渲染:直接显示字段值(支持格式化) -->
              {{ formatCellValue(scope.row[column.prop], column, scope.row) }}
            </template>
          </template>
        </el-table-column>
      </template>

      <!-- 操作列(支持插槽自定义按钮) -->
      <el-table-column
        v-if="showOperation"
        :label="$t('common.operation')" <!-- 国际化支持 -->
        :width="operationWidth"
        :min-width="operationWidth"
        :align="operationAlign"
        :fixed="operationFixed" <!-- 支持固定操作列 -->
        class-name="small-padding fixed-width"
      >
        <template #default="scope">
          <slot name="operation" :row="scope.row" :index="scope.$index"></slot>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页组件 -->
    <div v-if="showPagination && total > 0" class="pagination-container">
      <el-pagination
        :background="background"
        :current-page.sync="currentPage"
        :page-sizes="pageSizes"
        :page-size.sync="pageSize"
        :layout="paginationLayout"
        :total="total"
        @size-change="handleSizeChange" <!-- 每页条数变化 -->
        @current-change="handleCurrentChange" <!-- 页码变化 -->
      />
    </div>
  </div>
</template>

<script>
export default {
  name: 'BaseTable',
  props: {
    // 1. 核心数据
    data: {
      type: Array,
      required: true,
      default: () => []
    },

    // 2. 列配置(核心)
    columns: {
      type: Array,
      required: true,
      default: () => []
    },

    // 3. 加载状态
    loading: {
      type: Boolean,
      default: false
    },

    // 4. 表格样式
    border: {
      type: Boolean,
      default: true
    },
    stripe: {
      type: Boolean,
      default: true
    },
    height: {
      type: [String, Number],
      default: null
    },
    maxHeight: {
      type: [String, Number],
      default: null
    },
    headerCellStyle: {
      type: [Object, Function],
      default: () => ({ background: '#f5f7fa', fontWeight: 'bold' })
    },
    cellStyle: {
      type: [Object, Function],
      default: () => ({})
    },

    // 5. 行配置
    rowKey: {
      type: String,
      default: 'id' // 树形表格/选择列依赖的行唯一标识
    },
    treeProps: {
      type: Object,
      default: () => ({
        children: 'children',
        hasChildren: 'hasChildren'
      })
    },

    // 6. 排序配置
    defaultSort: {
      type: Object,
      default: () => ({}) // { prop: 'createTime', order: 'descending' }
    },

    // 7. 功能开关
    showSearch: {
      type: Boolean,
      default: true // 是否显示筛选区
    },
    showSelection: {
      type: Boolean,
      default: false // 是否显示选择列
    },
    showIndex: {
      type: Boolean,
      default: false // 是否显示序号列
    },
    showPagination: {
      type: Boolean,
      default: true // 是否显示分页
    },
    showOperation: {
      type: Boolean,
      default: true // 是否显示操作列
    },

    // 8. 选择列配置
    selectable: {
      type: Function,
      default: () => true // 自定义是否可选择(返回布尔值)
    },

    // 9. 分页配置
    background: {
      type: Boolean,
      default: true // 分页按钮背景色
    },
    paginationLayout: {
      type: String,
      default: 'total, sizes, prev, pager, next, jumper' // 分页布局
    },
    pageSizes: {
      type: Array,
      default: () => [10, 20, 30, 50] // 每页条数选项
    },
    total: {
      type: Number,
      default: 0 // 总条数
    },
    currentPage: {
      type: Number,
      default: 1 // 当前页码(支持.sync修饰符)
    },
    pageSize: {
      type: Number,
      default: 10 // 每页条数(支持.sync修饰符)
    },

    // 10. 操作列配置
    operationWidth: {
      type: [String, Number],
      default: 230 // 操作列宽度
    },
    operationAlign: {
      type: String,
      default: 'center' // 操作列对齐方式
    },
    operationFixed: {
      type: [String, Boolean],
      default: false // 操作列是否固定('right'/'left'/false)
    }
  },
  data() {
    return {
      selectedRows: [] // 选中的行数据
    }
  },
  methods: {
    /**
     * 格式化单元格值(支持自定义格式化函数)
     * @param {*} value - 单元格原始值
     * @param {Object} column - 列配置
     * @param {Object} row - 行数据
     * @returns {*} 格式化后的值
     */
    formatCellValue(value, column, row) {
      if (column.formatter && typeof column.formatter === 'function') {
        return column.formatter(value, row, column)
      }
      // 默认处理:null/undefined 显示为 '-'
      return value === null || value === undefined ? '-' : value
    },

    /**
     * 序号列计算(支持分页,显示全局序号)
     * @param {Number} index - 当前页行索引
     * @returns {Number} 全局序号
     */
    getIndex(index) {
      return (this.currentPage - 1) * this.pageSize + index + 1
    },

    /**
     * 排序变化事件(对外暴露)
     * @param {Object} sort - { prop: 排序字段, order: 排序方向 }
     */
    handleSortChange(sort) {
      this.$emit('sort-change', sort)
    },

    /**
     * 选择变化事件(对外暴露选中行)
     * @param {Array} selection - 选中的行数据数组
     */
    handleSelectionChange(selection) {
      this.selectedRows = selection
      this.$emit('selection-change', selection)
    },

    /**
     * 行点击事件(对外暴露)
     * @param {Object} row - 点击的行数据
     * @param {Object} event - 事件对象
     * @param {Object} column - 点击的列配置
     */
    handleRowClick(row, event, column) {
      this.$emit('row-click', row, event, column)
    },

    /**
     * 每页条数变化(对外暴露分页参数)
     * @param {Number} val - 新的每页条数
     */
    handleSizeChange(val) {
      this.pageSize = val
      this.$emit('pagination', { page: 1, limit: val }) // 条数变化时重置为第1页
      this.$emit('update:pageSize', val) // 支持.sync修饰符
    },

    /**
     * 当前页变化(对外暴露分页参数)
     * @param {Number} val - 新的页码
     */
    handleCurrentChange(val) {
      this.currentPage = val
      this.$emit('pagination', { page: val, limit: this.pageSize })
      this.$emit('update:currentPage', val) // 支持.sync修饰符
    },

    /**
     * 树形表格:展开/收起指定行
     * @param {Object} row - 目标行数据
     * @param {Boolean} expanded - 是否展开(true/false)
     */
    toggleRowExpansion(row, expanded) {
      this.$refs.table.toggleRowExpansion(row, expanded)
    },

    /**
     * 树形表格:展开所有行
     */
    expandAll() {
      this.data.forEach(row => {
        this.$refs.table.toggleRowExpansion(row, true)
      })
    },

    /**
     * 树形表格:收起所有行
     */
    collapseAll() {
      this.data.forEach(row => {
        this.$refs.table.toggleRowExpansion(row, false)
      })
    },

    /**
     * 手动设置选中行(对外暴露API)
     * @param {Array} rows - 需要选中的行数据数组
     */
    setSelectedRows(rows) {
      this.$refs.table.clearSelection()
      rows.forEach(row => {
        this.$refs.table.toggleRowSelection(row, true)
      })
    },

    /**
     * 清空选中行(对外暴露API)
     */
    clearSelectedRows() {
      this.$refs.table.clearSelection()
      this.selectedRows = []
    },

    /**
     * 刷新表格数据(保留分页状态)
     * @param {Array} newData - 新的表格数据
     */
    refreshData(newData) {
      this.$set(this, 'data', newData)
    }
  },
  // 注册 render 函数渲染组件(用于自定义单元格)
  components: {
    RenderContent: {
      functional: true,
      props: {
        render: Function,
        row: Object,
        index: Number,
        column: Object
      },
      render: (h, ctx) => {
        // 调用父组件传递的 render 函数,传入 h、行数据、索引、列配置
        return ctx.props.render(h, ctx.props.row, ctx.props.index, ctx.props.column)
      }
    }
  },
  // 监听 props 中的 currentPage 和 pageSize 变化(支持外部修改)
  watch: {
    currentPage: {
      handler(val) {
        this.$set(this, 'currentPage', val)
      },
      immediate: true
    },
    pageSize: {
      handler(val) {
        this.$set(this, 'pageSize', val)
      },
      immediate: true
    }
  }
}
</script>

<style lang="scss" scoped>
.base-table {
  width: 100%;
  box-sizing: border-box;

  // 筛选区样式
  .filter-container {
    padding: 10px 0;
    margin-bottom: 10px;
    box-sizing: border-box;
  }

  // 表格样式优化
  .el-table {
    width: 100%;
    box-sizing: border-box;

    // 表头样式
    th.el-table__cell {
      text-align: center;
    }

    // 单元格样式
    td.el-table__cell {
      padding: 12px 0;
      box-sizing: border-box;
    }

    // 固定列边框优化
    .el-table__fixed-right {
      height: 100% !important;
      box-shadow: -2px 0 8px rgba(0, 0, 0, 0.05);
    }
  }

  // 分页区样式
  .pagination-container {
    padding: 15px 0;
    text-align: right;
    box-sizing: border-box;

    .el-pagination {
      display: inline-block;
    }
  }

  // 操作列按钮间距
  .small-padding {
    .el-button {
      margin: 0 4px;
    }
  }
}
</style>

四、组件核心特性详解

1. 配置化列(columns)

columns 是表格的核心配置,每个元素对应一列,支持 Element UI 表格列的所有原生属性,同时扩展了自定义渲染能力:

属性名 类型 说明 必传
prop String 列数据绑定的字段(与 data 中字段对应) 是(默认渲染时)
label String 列标题文本
width String/Number 列宽度
minWidth String/Number 列最小宽度 否(默认 100)
fixed String/Boolean 列是否固定('left'/'right'/false)
align String 列对齐方式(left/center/right) 否(默认 center)
formatter Function 单元格值格式化函数(value, row, column)
render Function 单元格自定义渲染函数(h, row, index, column)
slotName String 单元格自定义插槽名称
hidden Boolean 是否隐藏该列(动态控制)

2. 内置核心功能

(1)筛选区(showSearch)

通过 slot="search" 自定义筛选表单,与通用表单组件(BaseForm)搭配使用,完美实现 "筛选 + 表格" 一体化:

vue 复制代码
<base-table :data="tableData" :columns="columns">
  <template #search>
    <base-form :formData="searchForm" :formItems="searchFormItems" inline />
  </template>
</base-table>

(2)选择列(showSelection)

启用后显示复选框列,支持批量操作,通过 selectable 控制是否可选择某行,通过 selectedRowsselection-change 事件获取选中行:

javascript 复制代码
// 只允许选中状态为"启用"的行
selectable(row) {
  return row.status === 1
}

(3)序号列(showIndex)

自动计算全局序号(支持分页),无需手动维护,序号公式:(当前页-1)*每页条数 + 行索引 + 1

(4)树形表格(treeProps)

通过 treeProps 配置树形结构字段,支持展开 / 收起所有行(expandAll()/collapseAll()),适配层级数据展示:

javascript 复制代码
treeProps: {
  children: 'children', // 子节点字段名
  hasChildren: 'hasChildren' // 是否有子节点的标记字段
}

(5)分页功能(showPagination)

内置分页组件,支持页码切换、条数选择,通过 pagination 事件对外暴露分页参数(page/limit),无需手动维护分页状态。

3. 灵活的自定义能力

(1)单元格格式化(formatter)

简单的文本格式化(如状态转换、日期格式化),直接通过 formatter 函数实现:

javascript 复制代码
columns: [
  {
    prop: 'status',
    label: '状态',
    formatter: (value) => {
      const statusMap = { 1: '启用', 0: '禁用' }
      return statusMap[value] || '-'
    }
  },
  {
    prop: 'createTime',
    label: '创建时间',
    formatter: (value) => {
      return value ? new Date(value).toLocaleString() : '-'
    }
  }
]

(2)单元格自定义渲染(render 函数)

复杂的单元格内容(如按钮、标签、图片),通过 render 函数渲染:

javascript 复制代码
columns: [
  {
    prop: 'tags',
    label: '标签',
    render: (h, row) => {
      return h('div', row.tags.map(tag => {
        return h('el-tag', { props: { type: 'primary', size: 'mini' } }, tag)
      }))
    }
  },
  {
    prop: 'avatar',
    label: '头像',
    width: 80,
    render: (h, row) => {
      return h('el-image', {
        props: { src: row.avatar, fit: 'cover' },
        style: { width: '40px', height: '40px', borderRadius: '50%' }
      })
    }
  }
]

(3)单元格插槽(slotName)

需要更灵活的自定义(如嵌套组件、复杂逻辑),通过具名插槽实现:

vue 复制代码
<!-- 父组件中 -->
<base-table :data="tableData" :columns="columns">
  <!-- 自定义"操作状态"列 -->
  <template #operationStatus="scope">
    <el-button
      type="text"
      :style="{ color: scope.row.status === 1 ? 'green' : 'red' }"
      @click="handleStatusClick(scope.row)"
    >
      {{ scope.row.status === 1 ? '禁用' : '启用' }}
    </el-button>
  </template>
</base-table>

<!-- 列配置 -->
columns: [
  {
    label: '操作状态',
    width: 100,
    slotName: 'operationStatus' // 与插槽名称对应
  }
]

(4)操作列自定义(operation 插槽)

操作列按钮完全自定义,通过 slot="operation" 传入,支持权限控制、按钮禁用等逻辑:

vue 复制代码
<base-table :data="tableData" :columns="columns">
  <template #operation="scope">
    <el-button type="text" size="mini" @click="handleView(scope.row)">查看</el-button>
    <el-button type="text" size="mini" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
    <el-button 
      type="text" 
      size="mini" 
      type="danger" 
      @click="handleDelete(scope.row)"
      :disabled="scope.row.status === 1"
    >
      删除
    </el-button>
  </template>
</base-table>

4. 对外暴露的 API

组件提供常用操作方法,通过 ref 调用:

方法名 说明 参数
toggleRowExpansion 树形表格:展开 / 收起指定行 row(行数据)、expanded(是否展开)
expandAll 树形表格:展开所有行 -
collapseAll 树形表格:收起所有行 -
setSelectedRows 手动设置选中行 rows(行数据数组)
clearSelectedRows 清空选中行 -
refreshData 刷新表格数据(保留分页状态) newData(新数据数组)

五、组件使用示例

1. 基础使用(简单列表 + 分页)

js 复制代码
<template>
  <div class="user-list">
    <base-table
      ref="userTable"
      :data="tableData"
      :columns="columns"
      :total="total"
      :loading="loading"
      :show-index="true"
      @pagination="handlePagination"
      @sort-change="handleSortChange"
    >
      <!-- 筛选区 -->
      <template #search>
        <el-row :gutter="20">
          <el-col :span="6">
            <el-input
              v-model="searchForm.username"
              placeholder="请输入用户名"
              size="mini"
              style="width: 100%"
            />
          </el-col>
          <el-col :span="6">
            <el-select
              v-model="searchForm.status"
              placeholder="请选择状态"
              size="mini"
              style="width: 100%"
            >
              <el-option label="全部" value="" />
              <el-option label="启用" value="1" />
              <el-option label="禁用" value="0" />
            </el-select>
          </el-col>
          <el-col :span="4">
            <el-button type="primary" size="mini" @click="handleSearch">查询</el-button>
            <el-button size="mini" @click="handleReset">重置</el-button>
          </el-col>
        </el-row>
      </template>

      <!-- 操作列 -->
      <template #operation="scope">
        <el-button type="text" size="mini" @click="handleView(scope.row)">查看</el-button>
        <el-button type="text" size="mini" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
        <el-button type="text" size="mini" type="danger" @click="handleDelete(scope.row)">删除</el-button>
      </template>
    </base-table>
  </div>
</template>

<script>
import BaseTable from '@/components/BaseTable'

export default {
  components: { BaseTable },
  data() {
    return {
      loading: false,
      tableData: [],
      total: 0,
      searchForm: {
        username: '',
        status: ''
      },
      // 列配置
      columns: [
        {
          prop: 'username',
          label: '用户名',
          width: 120
        },
        {
          prop: 'email',
          label: '邮箱',
          width: 180
        },
        {
          prop: 'gender',
          label: '性别',
          width: 80,
          formatter: (value) => {
            return value === 1 ? '男' : value === 2 ? '女' : '未知'
          }
        },
        {
          prop: 'status',
          label: '状态',
          width: 80,
          formatter: (value) => {
            return value === 1 ? '<span style="color: green">启用</span>' : '<span style="color: red">禁用</span>'
          },
          // 支持HTML渲染
          props: {
            raw: true
          }
        },
        {
          prop: 'createTime',
          label: '创建时间',
          width: 180,
          sortable: true, // 启用排序
          formatter: (value) => {
            return value ? new Date(value).toLocaleString() : '-'
          }
        }
      ]
    }
  },
  mounted() {
    this.fetchData() // 初始化加载数据
  },
  methods: {
    // 加载数据
    async fetchData() {
      this.loading = true
      try {
        const params = {
          page: this.$refs.userTable.currentPage,
          limit: this.$refs.userTable.pageSize,
          username: this.searchForm.username,
          status: this.searchForm.status,
          // 排序参数(从sort-change事件获取)
          ...this.sortParams
        }
        const res = await this.$api.user.getUserList(params)
        this.tableData = res.data.list
        this.total = res.data.total
      } catch (error) {
        console.error('加载用户列表失败:', error)
      } finally {
        this.loading = false
      }
    },

    // 分页变化
    handlePagination({ page, limit }) {
      this.fetchData()
    },

    // 排序变化
    handleSortChange(sort) {
      this.sortParams = sort.order ? {
        sortField: sort.prop,
        sortOrder: sort.order === 'ascending' ? 'asc' : 'desc'
      } : {}
      this.fetchData()
    },

    // 搜索
    handleSearch() {
      // 搜索时重置为第1页
      this.$refs.userTable.currentPage = 1
      this.fetchData()
    },

    // 重置
    handleReset() {
      this.searchForm = { username: '', status: '' }
      this.$refs.userTable.currentPage = 1
      this.sortParams = {}
      this.fetchData()
    },

    // 查看
    handleView(row) {
      console.log('查看用户:', row)
      // 打开查看弹窗...
    },

    // 编辑
    handleEdit(row) {
      console.log('编辑用户:', row)
      // 打开编辑弹窗...
    },

    // 删除
    handleDelete(row) {
      this.$confirm('确定要删除该用户吗?', '提示', {
        type: 'warning'
      }).then(async () => {
        try {
          await this.$api.user.deleteUser(row.id)
          this.$message.success('删除成功')
          this.fetchData()
        } catch (error) {
          this.$message.error('删除失败')
        }
      })
    }
  }
}
</script>

2. 进阶使用(树形表格 + 选择列 + 自定义渲染)

js 复制代码
<template>
  <div class="dept-list">
    <base-table
      ref="deptTable"
      :data="tableData"
      :columns="columns"
      :total="total"
      :loading="loading"
      :show-selection="true"
      :tree-props="treeProps"
      :selectable="handleSelectable"
      @selection-change="handleSelectionChange"
    >
      <!-- 操作列 -->
      <template #operation="scope">
        <el-button type="text" size="mini" @click="handleAdd(scope.row)">新增子部门</el-button>
        <el-button type="text" size="mini" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
        <el-button 
          type="text" 
          size="mini" 
          type="danger" 
          @click="handleDelete(scope.row)"
          :disabled="scope.row.hasChildren"
        >
          删除
        </el-button>
      </template>
    </base-table>
  </div>
</template>

<script>
import BaseTable from '@/components/BaseTable'

export default {
  components: { BaseTable },
  data() {
    return {
      loading: false,
      tableData: [],
      total: 0,
      treeProps: {
        children: 'children',
        hasChildren: 'hasChildren'
      },
      columns: [
        {
          prop: 'deptName',
          label: '部门名称',
          width: 200
        },
        {
          prop: 'deptCode',
          label: '部门编码',
          width: 150
        },
        {
          prop: 'leader',
          label: '负责人',
          width: 120
        },
        {
          prop: 'userCount',
          label: '用户数',
          width: 100
        },
        {
          prop: 'status',
          label: '状态',
          width: 100,
          render: (h, row) => {
            return h('el-switch', {
              props: {
                value: row.status === 1,
                disabled: true
              },
              scopedSlots: {
                open: () => h('span', '启用'),
                close: () => h('span', '禁用')
              }
            })
          }
        }
      ]
    }
  },
  mounted() {
    this.fetchData()
  },
  methods: {
    // 加载树形部门数据
    async fetchData() {
      this.loading = true
      try {
        const res = await this.$api.dept.getDeptTree()
        this.tableData = res.data
        this.total = res.data.length // 树形结构无需分页,总条数为根节点数
      } catch (error) {
        console.error('加载部门树失败:', error)
      } finally {
        this.loading = false
      }
    },

    // 自定义选择规则:不允许选择有子部门的行
    handleSelectable(row) {
      return !row.hasChildren
    },

    // 选中行变化
    handleSelectionChange(selection) {
      console.log('选中的部门:', selection)
      // 批量操作逻辑...
    },

    // 新增子部门
    handleAdd(parentDept) {
      console.log('新增子部门,父部门:', parentDept)
      // 打开新增弹窗...
    },

    // 编辑部门
    handleEdit(dept) {
      console.log('编辑部门:', dept)
      // 打开编辑弹窗...
    },

    // 删除部门
    handleDelete(dept) {
      this.$confirm('确定要删除该部门吗?', '提示', { type: 'warning' }).then(async () => {
        try {
          await this.$api.dept.deleteDept(dept.id)
          this.$message.success('删除成功')
          this.fetchData()
        } catch (error) {
          this.$message.error('删除失败')
        }
      })
    }
  }
}
</script>
相关推荐
jason_yang12 天前
vue3+element-plus按需自动导入-正确姿势
vue.js·vite·element
BumBle13 天前
使用 SortableJS 实现vue3 + Element Plus 表格拖拽排序
前端·vue.js·element
练习前端两年半24 天前
🚀 Vue3按钮组件Loading状态最佳实践:优雅的通用解决方案
前端·vue.js·element
华仔啊1 个月前
加班到凌晨,我用 Vue3 + ElementUI 写了个可编辑的表格组件
vue.js·element
Yu8751 个月前
el-select-v2 滚动选中选项之后会自动回滚到顶部
element
guojb8241 个月前
元数据驱动:打造动态灵活的Vue键值对表格组件
前端·vue.js·element
_AaronWong1 个月前
仿 ElementPlus 的函数式弹窗调用
前端·element
那你能帮帮我吗2 个月前
el-tree过滤后的数据,选择父节点,仅选中过滤后的子节点
vue.js·element
牧野星辰2 个月前
让el-table长个小脑袋,记住我的滚动位置
前端·javascript·element