Skip to content
vue3+elementplus表格组件封装

CustomTable 组件使用文档

1. 组件概述

CustomTable 是一个基于 Element Plus 封装的表格组件,提供了丰富的配置选项和灵活的插槽机制,支持分页、多选、自定义列内容等功能。它简化了表格的使用流程,使开发者能够更专注于业务逻辑的实现。

2. 组件特性

  • ✅ 支持动态列配置
  • ✅ 支持分页功能
  • ✅ 支持多选框功能
  • ✅ 支持自定义插槽(首列、末列、自定义类型列)
  • ✅ 支持表格宽高配置
  • ✅ 支持列宽配置
  • ✅ 支持列内容换行控制
  • ✅ 支持固定列

3. 安装和使用

3.1 安装依赖

bash
# 安装 Element Plus
npm install element-plus --save

# 安装 Vue 3
npm install vue@3 --save

3.2 组件代码

js
<template>
  <div class="custom-table-container" :style="tableContainerStyle">
    <!-- 表格 -->
    <el-table
      :data="data"
      :style="tableStyle"
      border
      @selection-change="handleSelectionChange"
    >
      <!-- 多选框列 -->
      <el-table-column
        v-if="showSelection"
        type="selection"
        :width="selectionWidth"
        fixed="left"
      />
      
      <!-- 开始插槽(第一列) -->
      <el-table-column
        v-if="$slots.start"
        :width="startColumnWidth"
        fixed="left"
        :label="startColumnLabel"
      >
        <template #default="{ row }">
          <slot name="start" :row="row"></slot>
        </template>
      </el-table-column>
      
      <!-- 动态列 -->
      <el-table-column
        v-for="column in columns"
        :key="column.prop"
        :prop="column.prop"
        :label="column.label"
        :width="column.width"
      >
        <template #default="{ row }">
          <!-- 自定义插槽 -->
          <template v-if="$slots[column.type]">
            <slot :name="column.type" :row="row"></slot>
          </template>
          <!-- 默认显示 -->
          <template v-else>
            <span 
              v-if="column.formatter && typeof column.formatter === 'function'"
              :style="{ whiteSpace: column.noWrap ? 'nowrap' : 'normal' }"
            >
              {{ column.formatter(row) }}
            </span>
            <span 
              v-else
              :style="{ whiteSpace: column.noWrap ? 'nowrap' : 'normal' }"
            >
              {{ row[column.prop] }}
            </span>
          </template>
        </template>
      </el-table-column>
      
      <!-- 结束插槽(最后一列) -->
      <el-table-column
        v-if="$slots.end"
        :width="endColumnWidth"
        fixed="right"
        :label="endColumnLabel"
      >
        <template #default="{ row }">
          <slot name="end" :row="row"></slot>
        </template>
      </el-table-column>
    </el-table>
    
    <!-- 分页 -->
    <div class="pagination-container" v-if="showPagination">
      <el-pagination
        :current-page="localCurrentPage"
        :page-size="localPageSize"
        :page-sizes="[10, 20, 50, 100]"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

<script setup>
import { ref, watch, computed } from 'vue';

// 接收的props
const props = defineProps({
  // 表格列配置
  columns: {
    type: Array,
    required: true
  },
  // 表格数据
  data: {
    type: Array,
    default: () => []
  },
  // 总条数
  total: {
    type: Number,
    default: 0
  },
  // 当前页码
  currentPage: {
    type: Number,
    default: 1
  },
  // 每页条数
  pageSize: {
    type: Number,
    default: 10
  },
  // 是否显示分页
  showPagination: {
    type: Boolean,
    default: true
  },
  // 多选框配置
  showSelection: {
    type: Boolean,
    default: false
  },
  // 多选框列宽度
  selectionWidth: {
    type: [String, Number],
    default: '55px'
  },
  // 首列标题
  startColumnLabel: {
    type: String,
    default: ''
  },
  // 末列标题
  endColumnLabel: {
    type: String,
    default: ''
  },
  // 表格宽度
  tableWidth: {
    type: String,
    default: '100%'
  },
  // 表格高度
  tableHeight: {
    type: String,
    default: ''
  },
  // 开始插槽宽度
  startColumnWidth: {
    type: [String, Number],
    default: '80px'
  },
  // 结束插槽宽度
  endColumnWidth: {
    type: [String, Number],
    default: '150px'
  }
});

// 定义事件
const emit = defineEmits(['page-change', 'selection-change']);

// 本地分页数据
const localCurrentPage = ref(props.currentPage);
const localPageSize = ref(props.pageSize);

// 表格样式
const tableStyle = computed(() => {
  const style = {
    width: props.tableWidth
  };
  if (props.tableHeight) {
    style.height = props.tableHeight;
  }
  return style;
});

// 表格容器样式
const tableContainerStyle = computed(() => {
  const style = {};
  if (props.tableWidth !== '100%') {
    style.width = props.tableWidth;
  }
  return style;
});

// 监听props变化
watch(() => props.currentPage, (newVal) => {
  localCurrentPage.value = newVal;
});

watch(() => props.pageSize, (newVal) => {
  localPageSize.value = newVal;
});

// 处理每页条数变化
const handleSizeChange = (size) => {
  localPageSize.value = size;
  emit('page-change', {
    currentPage: localCurrentPage.value,
    pageSize: size
  });
};

// 处理当前页码变化
const handleCurrentChange = (current) => {
  localCurrentPage.value = current;
  emit('page-change', {
    currentPage: current,
    pageSize: localPageSize.value
  });
};

// 处理多选框选择变化
const handleSelectionChange = (selection) => {
  emit('selection-change', selection);
};

// 暴露方法
defineExpose({
  // 刷新表格
  refresh: () => {
    // 可以在这里添加刷新逻辑
  },
  // 获取当前分页信息
  getPagination: () => {
    return {
      currentPage: localCurrentPage.value,
      pageSize: localPageSize.value
    };
  }
});
</script>

<style scoped>
.custom-table-container {
  width: 100%;
  margin: 0 auto;
}

.pagination-container {
  margin-top: 20px;
  display: flex;
  justify-content: flex-start;
}
</style>

3.3 引入组件

vue
<template>
  <CustomTable
    :columns="tableColumns"
    :data="tableData"
    :total="total"
    :current-page="pagination.currentPage"
    :page-size="pagination.pageSize"
    @page-change="handlePageChange"
  >
    <!-- 插槽内容 -->
  </CustomTable>
</template>

<script setup>
import CustomTable from './components/CustomTable.vue';

// 组件配置
const tableColumns = ref([
  // 列配置
]);

const tableData = ref([]);
const total = ref(0);
const pagination = ref({
  currentPage: 1,
  pageSize: 10
});

const handlePageChange = (pageInfo) => {
  // 分页处理逻辑
};
</script>

4. Props 详细说明

Prop类型默认值说明
columnsArray必填表格列配置,详细见下方列配置说明
dataArray[]表格数据
totalNumber0总条数,用于分页显示
currentPageNumber1当前页码
pageSizeNumber10每页条数
showPaginationBooleantrue是否显示分页
showSelectionBooleanfalse是否显示多选框
selectionWidth[String, Number]'55px'多选框列宽度
startColumnLabelString''首列标题
endColumnLabelString''末列标题
tableWidthString'100%'表格宽度
tableHeightString''表格高度,为空时自适应
startColumnWidth[String, Number]'80px'开始插槽宽度
endColumnWidth[String, Number]'150px'结束插槽宽度

4.1 列配置说明

字段类型说明
labelString列标题
propString数据属性名
width[String, Number]列宽度
typeString列类型,用于自定义插槽
formatterFunction格式化函数,接收行数据作为参数,返回格式化后的值
noWrapBoolean是否禁止换行,默认 false(支持换行)

5. Events 详细说明

Event参数说明
page-change{ currentPage, pageSize }分页变化时触发,返回当前页码和每页条数
selection-changeselection多选框选择变化时触发,返回选中的行数据数组

6. Slots 详细说明

Slot参数说明
start{ row }首列插槽,位于多选框列之后(如果显示多选框)
end{ row }末列插槽,位于所有动态列之后
[type]{ row }自定义类型插槽,根据列配置中的 type 字段匹配

7. 方法说明

方法返回值说明
refresh()刷新表格
getPagination(){ currentPage, pageSize }获取当前分页信息

8. 完整使用示例

8.1 基础使用

vue
<template>
  <CustomTable
    :columns="tableColumns"
    :data="tableData"
    :total="total"
    :current-page="pagination.currentPage"
    :page-size="pagination.pageSize"
    @page-change="handlePageChange"
  />
</template>

<script setup>
import { ref } from 'vue';
import CustomTable from './components/CustomTable.vue';

const tableColumns = ref([
  {
    label: '姓名',
    prop: 'name',
    width: '120px'
  },
  {
    label: '年龄',
    prop: 'age',
    width: '80px'
  },
  {
    label: '性别',
    prop: 'gender',
    width: '80px'
  }
]);

const tableData = ref([
  { id: 1, name: '张三', age: 25, gender: '男' },
  { id: 2, name: '李四', age: 30, gender: '女' }
]);

const total = ref(2);
const pagination = ref({
  currentPage: 1,
  pageSize: 10
});

const handlePageChange = (pageInfo) => {
  pagination.value.currentPage = pageInfo.currentPage;
  pagination.value.pageSize = pageInfo.pageSize;
  // 加载数据
};
</script>

8.2 完整使用示例代码

vue
<template>
  <div class="app-container">
    <h1>CustomTable 组件使用示例</h1>
    
    <!-- 搜索框和控制按钮 -->
    <div class="search-container">
      <el-input
        v-model="searchQuery"
        placeholder="请输入姓名"
        style="width: 300px"
        clearable
      >
        <template #append>
          <el-button @click="handleSearch">
            <el-icon><Search /></el-icon>
            搜索
          </el-button>
        </template>
      </el-input>
      
      <el-checkbox v-model="showSelection" style="margin-left: 20px">
        显示多选框
      </el-checkbox>
      
      <el-button type="primary" @click="handleViewSelected" style="margin-left: 20px">
        查看选中值
      </el-button>
    </div>
    
    <!-- 表格组件 -->
    <CustomTable
      :columns="tableColumns"
      :data="tableData"
      :total="total"
      :current-page="pagination.currentPage"
      :page-size="pagination.pageSize"
      :show-pagination="true"
      :show-selection="showSelection"
      :start-column-label="'操作'"
      :end-column-label="'操作'"
      :table-width="'100%'"
      :table-height="'500px'"
      :start-column-width="'100px'"
      :end-column-width="'180px'"
      @page-change="handlePageChange"
      @selection-change="handleSelectionChange"
    >
      <!-- 首列插槽 -->
      <template #start="{ row }">
        <el-button type="info" size="small">
          开始{{row.id}}
        </el-button>
      </template>
      
      <!-- 末列插槽 -->
      <template #end="{ row }">
        <el-button type="primary" size="small" @click="handleEdit(row)" style="margin-right: 5px">
          编辑
        </el-button>
        <el-button type="danger" size="small" @click="handleDelete(row.id)">
          删除
        </el-button>
      </template>
      
      <!-- 自定义类型插槽 -->
      <template #status="{ row }">
        <el-tag :type="row.status === 'active' ? 'success' : 'danger'">
          {{ row.status === 'active' ? '自定义插槽激活' : '自定义插槽禁用' }}
        </el-tag>
      </template>
      
      <template #action="{ row }">
        <el-button type="info" size="small">
          自定义插槽{{row.id}}
        </el-button>
      </template>
    </CustomTable>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import CustomTable from './components/CustomTable.vue';
import { Search } from '@element-plus/icons-vue';

// 搜索查询
const searchQuery = ref('');

// 多选框开关
const showSelection = ref(false);
// 选中的行数据
const selectedRows = ref([]);

// 分页参数
const pagination = ref({
  currentPage: 1,
  pageSize: 10
});

// 总条数
const total = ref(100);

// 表格列配置
const tableColumns = ref([
  {
    label: '姓名',
    prop: 'name',
    width: '120px'
  },
  {
    label: '年龄',
    prop: 'age',
    width: '80px'
  },
  {
    label: '性别',
    prop: 'gender',
    width: '80px'
  },
  {
    label: '邮箱',
    prop: 'email',
    width: '200px'
  },
  {
    label: '状态',
    prop: 'status',
    width: '150px',
    type: 'status' // 自定义类型,用于插槽
  },
  {
    label: '操作',
    prop: 'action',
    width: '120px',
    type: 'action' // 自定义类型,用于插槽
  }
]);

// 表格数据
const tableData = ref([]);

// 固定的模拟数据
const mockData = ref([]);

// 生成模拟数据
const generateMockData = (count) => {
  const data = [];
  
  for (let i = 1; i <= count; i++) {
    data.push({
      id: i,
      name: `用户${i}`,
      age: Math.floor(Math.random() * 30) + 20,
      gender: Math.random() > 0.5 ? '男' : '女',
      email: `user${i}@example.com`,
      status: Math.random() > 0.5 ? 'active' : 'inactive',
      checked: false
    });
  }
  
  return data;
};

// 初始化数据
mockData.value = generateMockData(100); // 生成100条固定数据

// 加载数据
const loadData = (currentPage, pageSize, searchKeyword = '') => {
  // 模拟异步加载
  setTimeout(() => {
    let filteredData = [...mockData.value]; // 使用固定的模拟数据
    
    // 真实筛选逻辑
    if (searchKeyword) {
      filteredData = filteredData.filter(item => 
        item.name.toLowerCase().includes(searchKeyword.toLowerCase())
      );
    }
    
    // 计算总条数
    total.value = filteredData.length;
    
    // 分页处理
    const startIndex = (currentPage - 1) * pageSize;
    const endIndex = startIndex + pageSize;
    tableData.value = filteredData.slice(startIndex, endIndex);
  }, 300);
};

// 搜索
const handleSearch = () => {
  // 重置页码
  pagination.value.currentPage = 1;
  // 重新加载数据(带搜索条件)
  loadData(pagination.value.currentPage, pagination.value.pageSize, searchQuery.value);
};

// 分页变化
const handlePageChange = (pageInfo) => {
  pagination.value.currentPage = pageInfo.currentPage;
  pagination.value.pageSize = pageInfo.pageSize;
  // 重新加载数据(带搜索条件)
  loadData(pageInfo.currentPage, pageInfo.pageSize, searchQuery.value);
};

// 处理多选框选择变化
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};

// 查看选中值
const handleViewSelected = () => {
  if (selectedRows.value.length === 0) {
    alert('请先选择数据');
    return;
  }
  console.log('选中的数据:', selectedRows.value);
  alert(`已选中 ${selectedRows.value.length} 条数据,详情请查看控制台`);
};

// 初始化
onMounted(() => {
  loadData(pagination.value.currentPage, pagination.value.pageSize);
});
</script>

<style scoped>
.app-container {
  padding: 20px;
  max-width: 1200px;
  margin: 0 auto;
}

.search-container {
  margin-bottom: 20px;
}

/* 表格样式 */
:deep(.el-table) {
  margin-top: 20px;
}
</style>

9. 常见问题和解决方案

9.1 表格数据不显示

问题:表格没有显示数据
解决方案

  • 检查 data 属性是否正确传递
  • 检查 columns 配置是否正确,确保 prop 字段与数据对象的属性名匹配

9.2 分页不生效

问题:分页组件显示但点击页码无反应
解决方案

  • 确保添加了 @page-change 事件监听器
  • 在事件处理函数中更新 currentPagepageSize,并重新加载数据

9.3 多选框不显示

问题:启用 showSelection 后多选框不显示
解决方案

  • 确保 showSelection 属性设置为 true
  • 检查表格数据是否有唯一的 id 字段(Element Plus 表格需要)

9.4 插槽不显示

问题:自定义插槽内容不显示
解决方案

  • 对于首列和末列插槽,确保使用了正确的插槽名称:#start#end
  • 对于自定义类型插槽,确保列配置中的 type 字段与插槽名称匹配

9.5 表格宽度不正确

问题:表格宽度没有按照设置的值显示
解决方案

  • 确保 tableWidth 属性设置正确,如 '100%''800px'
  • 检查父容器是否有宽度限制

10. 浏览器兼容性

  • Chrome ✅
  • Firefox ✅
  • Safari ✅
  • Edge ✅

11. 版本历史

版本日期变更内容
1.0.02024-01-01初始版本
1.0.12024-01-02添加多选框功能
1.0.22024-01-03优化表格样式和配置选项

12. 总结

CustomTable 组件是一个功能强大、配置灵活的表格组件,它基于 Element Plus 进行封装,提供了丰富的功能和良好的用户体验。通过本文档的介绍,您应该能够快速上手并使用 CustomTable 组件来构建各种复杂的表格场景。

如果您在使用过程中遇到任何问题,欢迎随时提出反馈和建议。祝您使用愉快!