Skip to content

BaseTable 组件使用文档


组件代码
vue
<template>
  <div class="table-container">
    <el-table ref="tableRef" :data="tableData" style="width: 100%" v-bind="tableProps" @selection-change="(newSelection) => emit('selection-change',newSelection)">
      <!-- 表格插槽 -->
      <template v-if="$slots.append" #append>
        <slot name="append"></slot>
      </template>
      <template v-if="$slots.empty" #empty>
        <slot name="empty"></slot>
      </template>

      <template v-for="item in columns" :key="item.prop">
        <!-- 自定义列模板 -->
        <template v-if="item.slot">
          <el-table-column
            v-bind="item"
            :align="item.align"
            :header-align="item.headerAlign"
            :class-name="item.className"
            :label-class-name="item.labelClassName"
            :show-overflow-tooltip="item.showOverflowTooltip"
            :sortable="item.sortable"
            :sort-by="item.sortBy"
            :sort-orders="item.sortOrders"
            :resizable="item.resizable"
            :formatter="item.formatter">
            <!-- 列头部插槽 -->
            <template v-if="$slots[`${item.prop}-header`]" #header="scope">
              <slot :name="`${item.prop}-header`" v-bind="scope"></slot>
            </template>
            <!-- 过滤图标插槽 -->
            <template v-if="$slots[`${item.prop}-filter-icon`]" #filter-icon="scope">
              <slot :name="`${item.prop}-filter-icon`" v-bind="scope"></slot>
            </template>
            <template #default="scope">
              <slot :name="item.slot" :row="scope.row" :index="scope.$index"></slot>
            </template>
          </el-table-column>
        </template>
        <!-- 操作列 -->
        <template v-else-if="item.type === 'operation'">
          <el-table-column
            v-bind="item"
            :align="item.align || 'center'"
            :header-align="item.headerAlign"
            :class-name="item.className"
            :label-class-name="item.labelClassName"
            :show-overflow-tooltip="item.showOverflowTooltip"
            :resizable="item.resizable">
            <!-- 操作列头部插槽 -->
            <template v-if="$slots['operation-header']" #header="scope">
              <slot name="operation-header" v-bind="scope"></slot>
            </template>
            <template #default="scope">
              <el-button
                v-for="btn in item.buttons"
                :key="btn.text"
                :type="btn.type || 'primary'"
                :size="btn.size || 'small'"
                @click="handleOperation(btn.action, scope.row, scope.$index)"
              >
                {{ btn.text }}
              </el-button>
            </template>
          </el-table-column>
        </template>
        <!-- 普通列 -->
        <template v-else>
          <el-table-column
            v-bind="item"
            :align="item.align"
            :header-align="item.headerAlign"
            :class-name="item.className"
            :label-class-name="item.labelClassName"
            :show-overflow-tooltip="item.showOverflowTooltip"
            :sortable="item.sortable"
            :sort-by="item.sortBy"
            :sort-orders="item.sortOrders"
            :resizable="item.resizable"
            :formatter="item.formatter">
            <!-- 普通列头部插槽 -->
            <template v-if="$slots[`${item.prop}-header`]" #header="scope">
              <slot :name="`${item.prop}-header`" v-bind="scope"></slot>
            </template>
          </el-table-column>
        </template>
      </template>
    </el-table>
  </div>
</template>

<script setup>
import { defineProps, defineEmits, ref } from 'vue'

const tableRef = ref()

// 暴露表格实例的所有方法和属性给父组件
defineExpose({
  // 表格实例的引用
  tableRef,
  // 表格实例的方法
  clearSelection: () => tableRef.value?.clearSelection(),
  toggleRowSelection: (row, selected) => tableRef.value?.toggleRowSelection(row, selected),
  toggleAllSelection: () => tableRef.value?.toggleAllSelection(),
  toggleRowExpansion: (row, expanded) => tableRef.value?.toggleRowExpansion(row, expanded),
  setCurrentRow: (row) => tableRef.value?.setCurrentRow(row),
  clearSort: () => tableRef.value?.clearSort(),
  clearFilter: (columnKeys) => tableRef.value?.clearFilter(columnKeys),
  doLayout: () => tableRef.value?.doLayout(),
  sort: (prop, order) => tableRef.value?.sort(prop, order),
  scrollTo: (options, yCoord) => tableRef.value?.scrollTo(options, yCoord),
  setScrollTop: (top) => tableRef.value?.setScrollTop(top),
  setScrollLeft: (left) => tableRef.value?.setScrollLeft(left),
  // 表格实例的属性
  getSelectionRows: () => tableRef.value?.getSelectionRows()
})

const props = defineProps({
  // 表格数据
  tableData: {
    type: Array,
    required: true,
    default: () => []
  },
  // 列配置
  columns: {
    type: Array,
    required: true,
    default: () => [],
    validator: (value) => {
      return value.every(item => {
        if (item.fixed) {
          return ['left', 'right'].includes(item.fixed)
        }
        if (item.align) {
          return ['left', 'center', 'right'].includes(item.align)
        }
        return true
      })
    }
  },
  // 表格属性
  tableProps: {
    type: Object,
    default: () => ({})
  }
})

const emit = defineEmits(['operation','selection-change'])

// 处理操作按钮点击
const handleOperation = (action, row, index) => {
  emit('operation', { action, row, index })
}
</script>

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

组件介绍

BaseTable 是一个基于 Element Plus Table 组件的二次封装,提供了更简便的配置方式和完整的功能支持。

基础用法

最简单的示例

loading

完整示例

loading

API 文档

Props

参数说明类型必填默认值
tableData表格数据Array[]
columns列配置Array[]
tableProps表格属性Object{}

Column 配置项

参数说明类型必填默认值
prop列属性名string-
label列标签名string-
width列宽度number/string-
fixed列固定位置'left'/'right'-
sortable是否可排序booleanfalse
slot自定义列插槽名string-
align对齐方式'left'/'center'/'right''left'
formatter格式化函数Function-
showOverflowTooltip是否显示 tooltipbooleanfalse
type列类型,可选 'operation'string-
buttons操作列按钮配置Array-

事件

事件名说明参数
select当用户手动勾选数据行时触发selection, row
select-all当用户手动勾选全选时触发selection
selection-change当选择项发生变化时触发selection
cell-mouse-enter当单元格 hover 进入时触发row, column, cell, event
cell-mouse-leave当单元格 hover 离开时触发row, column, cell, event
cell-click当单元格被点击时触发row, column, cell, event
row-click当某一行被点击时触发row, column, event
sort-change当表格的排序条件发生变化时触发column, prop, order
filter-change当表格的筛选条件发生变化时触发filters
operation当操作列按钮被点击时触发action, row, index

方法

方法名说明参数
clearSelection清空选择-
toggleRowSelection切换某一行的选中状态row, selected
toggleAllSelection切换所有行的选中状态-
toggleRowExpansion切换某一行的展开状态row, expanded
setCurrentRow设置某一行为当前行row
clearSort清空排序条件-
clearFilter清空过滤条件columnKeys
doLayout重新布局表格-
sort手动排序prop, order
scrollTo滚动到指定位置options, yCoord
setScrollTop设置垂直滚动条位置top
setScrollLeft设置水平滚动条位置left

插槽

插槽名说明作用域参数
empty空数据时的内容-
append表格底部内容-
[prop]-header自定义表头column, $index
[slot]自定义列内容row, column, $index

注意事项

  1. 操作列的 type 必须设置为 'operation'
  2. 使用自定义列时,需要在 columns 中设置 slot 属性
  3. 自定义表头插槽名格式为 [prop]-header
  4. 所有的 Element Plus Table 的属性都可以通过 tableProps 传入

好的,我继续补充文档的其他部分:

高级用法示例

1. 多选表格

loading

2. 可展开行表格

loading

3. 带有筛选和排序的表格

loading

4. 自定义列模板和表头

loading

进阶配置

1. 表格属性(tableProps)完整配置

js
const tableProps = {
  // 基础属性
  border: true,                 // 是否带有纵向边框
  stripe: true,                 // 是否为斑马纹表格
  size: 'default',             // 表格大小
  fit: true,                   // 列的宽度是否自撑开
  show-header: true,           // 是否显示表头
  highlight-current-row: true, // 是否高亮当前行
  
  // 样式相关
  height: '400px',             // 表格高度
  max-height: '600px',         // 表格最大高度
  row-class-name: 'row-class', // 行的 className
  cell-class-name: 'cell-class', // 单元格的 className
  
  // 功能相关
  default-sort: { prop: 'id', order: 'descending' }, // 默认排序
  default-expand-all: false,   // 是否默认展开所有行
  tree-props: {               // 树形数据配置
    hasChildren: 'hasChildren',
    children: 'children'
  },
  
  // 事件处理
  onSelect: (selection, row) => {},
  onSelectAll: (selection) => {},
  onSelectionChange: (selection) => {},
  onCellMouseEnter: ({ row, column, cell, event }) => {},
  onCellMouseLeave: ({ row, column, cell, event }) => {},
  onCellClick: ({ row, column, cell, event }) => {},
  onRowClick: (row, column, event) => {},
  onSortChange: ({ column, prop, order }) => {},
  onFilterChange: (filters) => {},
  onHeaderDragend: (newWidth, oldWidth, column, event) => {}
}

2. 列配置(columns)完整选项

js
const columns = [
  {
    // 基础属性
    prop: 'name',              // 列属性名
    label: '姓名',             // 列标签名
    width: 120,               // 列宽度
    minWidth: 100,            // 最小列宽度
    fixed: 'left',            // 列固定位置
    
    // 样式相关
    align: 'center',          // 对齐方式
    headerAlign: 'center',    // 表头对齐方式
    className: 'custom-class', // 列的 className
    labelClassName: 'label-class', // 表头的 className
    
    // 功能相关
    sortable: true,           // 是否可排序
    sortMethod: (a, b) => {}, // 自定义排序方法
    sortBy: 'custom',         // 指定排序的列
    sortOrders: ['ascending', 'descending', null], // 排序顺序
    
    // 筛选相关
    filters: [                // 筛选选项
      { text: '选项1', value: 1 }
    ],
    filterPlacement: 'bottom', // 筛选框的弹出位置
    filterMultiple: true,     // 是否多选
    filterMethod: (value, row) => {}, // 筛选方法
    
    // 自定义相关
    slot: 'custom',           // 使用自定义插槽
    formatter: (row, column, cellValue, index) => {}, // 格式化函数
    showOverflowTooltip: true, // 当内容过长被隐藏时显示 tooltip
    
    // 操作列特殊配置
    type: 'operation',        // 操作列类型
    buttons: [                // 操作按钮配置
      {
        text: '编辑',
        type: 'primary',
        size: 'small',
        action: 'edit',
        show: (row) => true,  // 控制按钮显示
        disabled: (row) => false // 控制按钮禁用
      }
    ]
  }
]

最佳实践

1. 数据加载和刷新

loading

2. 分页处理

loading

3. 表格工具栏

loading

常见问题

1. 表格不能自适应容器宽度

解决方案:

js
// 监听窗口大小变化
window.addEventListener('resize', () => {
  baseTableRef.value?.doLayout()
})

// 在容器大小变化时调用
const handleResize = () => {
  baseTableRef.value?.doLayout()
}

2. 表格高度计算问题

解决方案:

vue
<template>
  <div class="table-container" ref="containerRef">
    <BaseTable
      ref="baseTableRef"
      :table-props="{
        height: tableHeight
      }"
    />
  </div>
</template>

<script setup>
import { ref, onMounted, nextTick } from 'vue'

const containerRef = ref()
const tableHeight = ref(400)

const calculateTableHeight = async () => {
  await nextTick()
  if (containerRef.value) {
    const containerHeight = containerRef.value.offsetHeight
    tableHeight.value = containerHeight - 20 // 减去padding
  }
}

onMounted(() => {
  calculateTableHeight()
  window.addEventListener('resize', calculateTableHeight)
})
</script>

3. 动态列处理

loading

性能优化建议

  1. 大数据量处理:
js
// 使用虚拟滚动
const tableProps = {
  'virtual-scrolling': true,
  'scroll-row-height': 48
}
  1. 避免不必要的重渲染:
js
// 使用 computed 处理数据
const processedData = computed(() => {
  return tableData.value.map(item => ({
    ...item,
    formattedDate: formatDate(item.date)
  }))
})
  1. 合理使用 v-showv-if
vue
<!-- 频繁切换使用 v-show -->
<div v-show="loading" class="loading-mask" />

<!-- 条件渲染使用 v-if -->
<template v-if="hasPermission">
  <el-button @click="handleEdit">编辑</el-button>
</template>

Released under the MIT License.