表格组件功能文档

195 阅读7分钟

组件概述

本文档描述了一个基于Element Plus的可复用表格组件实现,包含一个通用表格组件(UseTable)和其使用示例(BasicTableDemo)。

UseTable 组件

组件说明

UseTable是一个封装了Element Plus表格的通用组件,提供了灵活的配置选项和插槽功能。

属性说明

属性名类型默认值说明
indexBooleanfalse是否显示序号列
selectionBooleanfalse是否显示多选列
tableHeaderArray-表格列配置数组
attrsObject-传递给el-table的其他属性

tableHeader配置项说明

每个列配置对象包含以下属性:

{
  id: String,       // 列唯一标识
  prop: String,     // 对应数据字段名
  label: String,    // 列标题
  sort: Boolean,    // 是否可排序
  // ...其他el-table-column支持的属性
}

插槽

  1. 默认插槽:可以通过列的prop名称来自定义列内容
  2. 支持el-table的所有内置插槽

暴露方法

  • clearSelection(): 清空表格选择状态

组件封装代码

UseTable 组件完整代码

<template>
  <div class="use-table">
    <!-- 表格主体 -->
    <el-table
      ref="tableRef"
      v-bind="attrs"
      :data="data"
      :border="border"
      :stripe="stripe"
      :height="height"
      :max-height="maxHeight"
      :show-header="showHeader"
      :highlight-current-row="highlightCurrentRow"
      :show-overflow-tooltip="showOverflowTooltip"
      @selection-change="handleSelectionChange"
      @sort-change="handleSortChange"
      @row-click="handleRowClick"
    >
      <!-- 序号列 -->
      <el-table-column
        v-if="index"
        type="index"
        label="序号"
        width="60"
        align="center"
      />
      
      <!-- 多选列 -->
      <el-table-column
        v-if="selection"
        type="selection"
        width="55"
        align="center"
      />
      
      <!-- 动态列 -->
      <template v-for="column in tableHeader" :key="column.id">
        <el-table-column
          v-bind="getColumnProps(column)"
          :align="column.align || 'left'"
        >
          <template #default="scope">
            <!-- 使用自定义插槽 -->
            <slot
              v-if="$slots[column.prop]"
              :name="column.prop"
              v-bind="scope"
              :index="scope.$index"
              :prop="column.prop"
            />
            <!-- 默认内容展示 -->
            <span v-else>
              {{ formatColumnValue(scope.row, column) }}
            </span>
          </template>
          
          <!-- 表头插槽 -->
          <template v-if="$slots[`${column.prop}-header`]" #header="scope">
            <slot :name="\`${column.prop}-header\`" v-bind="scope" />
          </template>
        </el-table-column>
      </template>

      <!-- 操作列 -->
      <slot name="operation" />
    </el-table>

    <!-- 分页组件 -->
    <div v-if="showPagination" class="pagination-container">
      <el-pagination
        v-bind="paginationProps"
        :current-page="currentPage"
        :page-size="pageSize"
        :total="total"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      />
    </div>
  </div>
</template>

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

const props = defineProps({
  // 表格数据
  data: {
    type: Array,
    default: () => []
  },
  // 表格列配置
  tableHeader: {
    type: Array,
    default: () => []
  },
  // 是否显示序号列
  index: {
    type: Boolean,
    default: false
  },
  // 是否显示多选列
  selection: {
    type: Boolean,
    default: false
  },
  // 是否显示边框
  border: {
    type: Boolean,
    default: true
  },
  // 是否显示斑马纹
  stripe: {
    type: Boolean,
    default: true
  },
  // 表格高度
  height: {
    type: [String, Number],
    default: ''
  },
  // 表格最大高度
  maxHeight: {
    type: [String, Number],
    default: ''
  },
  // 是否显示表头
  showHeader: {
    type: Boolean,
    default: true
  },
  // 是否高亮当前行
  highlightCurrentRow: {
    type: Boolean,
    default: false
  },
  // 当内容过长时是否显示 tooltip
  showOverflowTooltip: {
    type: Boolean,
    default: true
  },
  // 分页配置
  paginationProps: {
    type: Object,
    default: () => ({})
  },
  // 是否显示分页
  showPagination: {
    type: Boolean,
    default: false
  },
  // 其他 el-table 属性
  attrs: {
    type: Object,
    default: () => ({})
  }
})

const emit = defineEmits([
  'selection-change',
  'sort-change',
  'row-click',
  'page-change',
  'size-change'
])

// 表格实例
const tableRef = ref(null)

// 分页相关数据
const currentPage = computed(() => props.paginationProps?.currentPage || 1)
const pageSize = computed(() => props.paginationProps?.pageSize || 10)
const total = computed(() => props.paginationProps?.total || 0)

// 获取列配置
const getColumnProps = (column) => {
  const { prop, label, width, minWidth, sortable, fixed, ...rest } = column
  return {
    prop,
    label,
    width,
    minWidth,
    sortable,
    fixed,
    ...rest
  }
}

// 格式化列值
const formatColumnValue = (row, column) => {
  const value = row[column.prop]
  if (column.formatter && typeof column.formatter === 'function') {
    return column.formatter(row, column)
  }
  return value ?? '--'
}

// 多选变化事件
const handleSelectionChange = (selection) => {
  emit('selection-change', selection)
}

// 排序变化事件
const handleSortChange = (sort) => {
  emit('sort-change', sort)
}

// 行点击事件
const handleRowClick = (row, column, event) => {
  emit('row-click', row, column, event)
}

// 每页条数变化
const handleSizeChange = (size) => {
  emit('size-change', size)
}

// 当前页变化
const handleCurrentChange = (page) => {
  emit('page-change', page)
}

// 暴露方法
defineExpose({
  // 清空选择
  clearSelection: () => {
    tableRef.value?.clearSelection()
  },
  // 切换行选中状态
  toggleRowSelection: (row, selected) => {
    tableRef.value?.toggleRowSelection(row, selected)
  },
  // 切换全选状态
  toggleAllSelection: () => {
    tableRef.value?.toggleAllSelection()
  },
  // 设置当前行
  setCurrentRow: (row) => {
    tableRef.value?.setCurrentRow(row)
  }
})
</script>

<style scoped>
.use-table {
  width: 100%;
}

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

类型定义 (types.ts)

// 表格列配置接口
export interface TableColumn {
  id: string | number;
  prop: string;
  label: string;
  width?: string | number;
  minWidth?: string | number;
  fixed?: boolean | 'left' | 'right';
  sortable?: boolean;
  align?: 'left' | 'center' | 'right';
  formatter?: (row: any, column: TableColumn) => any;
  [key: string]: any;
}

// 分页配置接口
export interface PaginationProps {
  currentPage?: number;
  pageSize?: number;
  total?: number;
  pageSizes?: number[];
  layout?: string;
  background?: boolean;
  [key: string]: any;
}

// 表格属性接口
export interface TableProps {
  data: any[];
  tableHeader: TableColumn[];
  index?: boolean;
  selection?: boolean;
  border?: boolean;
  stripe?: boolean;
  height?: string | number;
  maxHeight?: string | number;
  showHeader?: boolean;
  highlightCurrentRow?: boolean;
  showOverflowTooltip?: boolean;
  paginationProps?: PaginationProps;
  showPagination?: boolean;
  attrs?: Record<string, any>;
}

这个封装的表格组件具有以下特点:

  1. 完整的类型定义:使用 TypeScript 定义了所有的接口,提供更好的类型提示
  2. 灵活的列配置:支持自定义列的各种属性,包括宽度、排序、对齐方式等
  3. 丰富的功能
    • 支持序号列和多选列
    • 支持自定义列模板
    • 支持表头自定义
    • 集成分页功能
    • 支持行选择、排序等事件
  4. 可扩展性
    • 通过 attrs 属性支持传入任意 el-table 的原生属性
    • 提供了丰富的插槽支持
    • 暴露了常用的表格方法
  5. 样式优化
    • 默认添加了基础样式
    • 分页组件布局优化

使用这个组件可以大大简化表格的开发工作,同时保持了足够的灵活性和可扩展性。

使用示例 (BasicTableDemo)

基本用法

<template>
  <div>
    <MyTable
      :table-header="tableHeader"
      :index="true"
      :data="tableAttrs"
      :selection="true"
    >
      <!-- 自定义列插槽 -->
      <template #status="{ row, prop }">
        <el-tag :type="getTagType(row[prop])">
          {{ getStatusText(row[prop]) }}
        </el-tag>
      </template>
    </MyTable>
  </div>
</template>

<script setup>
import MyTable from '@/components/UseTable/index.vue'
import { ref } from 'vue'

// 定义表格列配置
const tableHeader = ref([
  { id: 'id', prop: 'id', label: 'ID', sort: true },
  { id: 'name', prop: 'name', label: '名称', sort: true },
  { id: 'custom', prop: 'customField', label: '自定义字段' },
  { id: 'status', prop: 'status', label: '状态' },
])

// 表格数据
const tableAttrs = ref([
  { id: 1, name: '示例1', customField: '自定义值1', status: 1 },
  { id: 2, name: '示例2', customField: '自定义值2', status: 2 },
  { id: 3, name: '示例3', customField: '自定义值3', status: 3 },
])

// 状态标签类型处理函数
function getTagType(status) {
  switch (status) {
    case 1: return 'success'
    case 2: return 'info'
    case 3: return 'danger'
    default: return 'warning'
  }
}

// 状态文本处理函数
function getStatusText(status) {
  const statusMap = {
    1: '已处理',
    2: '未处理',
    3: '紧急',
  }
  return statusMap[status] || '未知状态'
}
</script>

更多使用示例

1. 带分页的表格

<template>
  <div>
    <MyTable
      :table-header="tableHeader"
      :data="tableData"
      :attrs="{
        'pagination-props': {
          total: total,
          currentPage: currentPage,
          pageSize: pageSize,
        },
        'on-page-change': handlePageChange
      }"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'

const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(100)

const handlePageChange = (page) => {
  currentPage.value = page
  // 这里可以调用接口获取数据
}
</script>

2. 带搜索和操作按钮的表格

<template>
  <div>
    <!-- 搜索区域 -->
    <div class="search-area">
      <el-input
        v-model="searchKeyword"
        placeholder="请输入搜索关键词"
        @input="handleSearch"
      />
      <el-button type="primary" @click="handleAdd">新增</el-button>
    </div>

    <!-- 表格 -->
    <MyTable
      :table-header="tableHeader"
      :data="tableData"
      :selection="true"
    >
      <!-- 操作列 -->
      <template #operation="{ row }">
        <el-button type="text" @click="handleEdit(row)">编辑</el-button>
        <el-button type="text" @click="handleDelete(row)">删除</el-button>
      </template>
    </MyTable>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const searchKeyword = ref('')
const tableHeader = ref([
  { id: 'name', prop: 'name', label: '名称' },
  { id: 'operation', prop: 'operation', label: '操作', width: '150px' }
])

const handleSearch = (value) => {
  // 实现搜索逻辑
  console.log('搜索关键词:', value)
}

const handleAdd = () => {
  // 实现新增逻辑
}

const handleEdit = (row) => {
  // 实现编辑逻辑
  console.log('编辑行:', row)
}

const handleDelete = (row) => {
  // 实现删除逻辑
  console.log('删除行:', row)
}
</script>

<style scoped>
.search-area {
  margin-bottom: 20px;
  display: flex;
  gap: 10px;
}
</style>

3. 自定义列模板的表格

<template>
  <div>
    <MyTable
      :table-header="tableHeader"
      :data="tableData"
    >
      <!-- 图片列 -->
      <template #image="{ row }">
        <el-image
          :src="row.image"
          :preview-src-list="[row.image]"
          fit="cover"
          style="width: 50px; height: 50px"
        />
      </template>
      
      <!-- 标签列 -->
      <template #tags="{ row }">
        <el-tag
          v-for="tag in row.tags"
          :key="tag"
          class="mx-1"
        >
          {{ tag }}
        </el-tag>
      </template>
      
      <!-- 开关列 -->
      <template #status="{ row }">
        <el-switch
          v-model="row.status"
          @change="(val) => handleStatusChange(row, val)"
        />
      </template>
    </MyTable>
  </div>
</template>

<script setup>
import { ref } from 'vue'

const tableHeader = ref([
  { id: 'image', prop: 'image', label: '图片', width: '80px' },
  { id: 'name', prop: 'name', label: '名称' },
  { id: 'tags', prop: 'tags', label: '标签' },
  { id: 'status', prop: 'status', label: '状态', width: '100px' }
])

const tableData = ref([
  {
    image: 'https://example.com/image1.jpg',
    name: '示例项目1',
    tags: ['标签1', '标签2'],
    status: true
  },
  {
    image: 'https://example.com/image2.jpg',
    name: '示例项目2',
    tags: ['标签3', '标签4'],
    status: false
  }
])

const handleStatusChange = (row, value) => {
  console.log('状态改变:', row, value)
}
</script>

这些示例展示了表格组件的不同使用场景,包括:

  • 基础表格的使用
  • 分页功能的实现
  • 搜索和操作按钮的集成
  • 自定义列模板的使用(图片、标签、开关等)

每个示例都包含了完整的代码实现,您可以根据实际需求选择合适的示例进行参考和使用。

状态展示功能

组件实现了一个状态标签展示功能,包含以下状态:

状态码状态文本标签类型
1已处理success
2未处理info
3紧急danger
其他未知状态warning

辅助函数

  1. getTagType(status): 根据状态码返回对应的标签类型
  2. getStatusText(status): 根据状态码返回对应的状态文本

最佳实践

  1. 表格列的配置应通过tableHeader属性统一管理
  2. 对于需要自定义渲染的列,使用具名插槽实现
  3. 状态类型的展示建议使用标签组件,并通过状态映射保持一致性

注意事项

  1. 确保提供正确的数据结构和必要的属性
  2. 自定义插槽的使用需要与tableHeader中定义的prop对应
  3. 状态码的使用需要与预定义的映射保持一致