Skip to content

表格组件的封装

TIP

这个表格组件只是一个封装,具体实现请自行实现。

CustomTable.vue 组件

vue
<template>
  <div class="custom-table">
    <el-table
      :data="tableData"
      :border="border"
      :stripe="stripe"
      :highlight-current-row="highlightCurrentRow"
      :row-key="rowKey"
      :max-height="maxHeight"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @row-dblclick="handleRowDblclick"
      @sort-change="handleSortChange"
      :row-class-name="rowClassName"
    >
      <!-- 复选框列 -->
      <el-table-column
        v-if="selection"
        type="selection"
        width="55"
      ></el-table-column>

      <!-- 序号列 -->
      <el-table-column
        v-if="showIndex"
        type="index"
        :label="indexLabel"
        :width="indexWidth"
      ></el-table-column>

      <!-- 自定义列 -->
      <el-table-column
        v-for="column in columns"
        :key="column.prop"
        :prop="column.prop"
        :label="column.label"
        :width="column.width"
        :min-width="column.minWidth"
        :fixed="column.fixed"
        :sortable="column.sortable"
        :align="column.align || 'left'"
        :show-overflow-tooltip="column.showOverflowTooltip || true"
      >
        <!-- 插槽支持 -->
        <template #default="scope">
          <slot 
            :name="`column-${column.prop}`" 
            :row="scope.row" 
            :index="scope.$index"
          >
            {{ scope.row[column.prop] }}
          </slot>
        </template>
      </el-table-column>

      <!-- 操作列 -->
      <el-table-column
        v-if="operationOptions && operationOptions.length > 0"
        :label="operationLabel"
        :width="operationWidth"
        align="center"
      >
        <template #default="scope">
          <el-button
            v-for="option in operationOptions"
            :key="option.label"
            :type="option.type || 'text'"
            :size="option.size || 'mini'"
            :disabled="option.disabled ? option.disabled(scope.row) : false"
            @click.stop="handleOperationClick(option, scope.row, scope.$index)"
          >
            {{ option.label }}
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <div class="pagination" v-if="pagination">
      <el-pagination
        :current-page="paginationConfig.currentPage"
        :page-size="paginationConfig.pageSize"
        :total="paginationConfig.total"
        :layout="paginationConfig.layout"
        :page-sizes="paginationConfig.pageSizes"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      ></el-pagination>
    </div>
  </div>
</template>

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

// 定义 props
const props = defineProps({
  // 表格数据
  data: {
    type: Array,
    default: () => []
  },
  // 列配置
  columns: {
    type: Array,
    default: () => []
  },
  // 边框
  border: {
    type: Boolean,
    default: true
  },
  // 斑马纹
  stripe: {
    type: Boolean,
    default: false
  },
  // 高亮当前行
  highlightCurrentRow: {
    type: Boolean,
    default: false
  },
  // 行key,用于高亮
  rowKey: {
    type: String,
    default: ''
  },
  // 最大高度
  maxHeight: {
    type: [Number, String],
    default: ''
  },
  // 是否显示复选框
  selection: {
    type: Boolean,
    default: false
  },
  // 是否显示序号
  showIndex: {
    type: Boolean,
    default: false
  },
  // 序号列标题
  indexLabel: {
    type: String,
    default: '序号'
  },
  // 序号列宽度
  indexWidth: {
    type: [Number, String],
    default: 80
  },
  // 操作列配置
  operationOptions: {
    type: Array,
    default: () => []
  },
  // 操作列标题
  operationLabel: {
    type: String,
    default: '操作'
  },
  // 操作列宽度
  operationWidth: {
    type: [Number, String],
    default: 180
  },
  // 分页配置
  pagination: {
    type: Boolean,
    default: false
  },
  // 分页配置对象
  paginationConfig: {
    type: Object,
    default: () => ({
      currentPage: 1,
      pageSize: 10,
      total: 0,
      layout: 'total, sizes, prev, pager, next, jumper',
      pageSizes: [10, 20, 30, 50, 100]
    })
  },
  // 行类名函数
  rowClassName: {
    type: Function,
    default: null
  }
});

// 定义 emits
const emit = defineEmits([
  'selection-change',
  'row-click',
  'row-dblclick',
  'sort-change',
  'size-change',
  'current-change',
  'operation-click'
]);

// 计算分页后的表格数据
const tableData = computed(() => {
  if (props.pagination) {
    const { currentPage, pageSize } = props.paginationConfig;
    return props.data.slice(
      (currentPage - 1) * pageSize,
      currentPage * pageSize
    );
  }
  return props.data;
});

// 定义方法
const handleSelectionChange = (selection) => {
  emit('selection-change', selection);
};

const handleRowClick = (row, column, event) => {
  emit('row-click', row, column, event);
};

const handleRowDblclick = (row, column, event) => {
  emit('row-dblclick', row, column, event);
};

const handleSortChange = (sort) => {
  emit('sort-change', sort);
};

const handleOperationClick = (option, row, index) => {
  emit('operation-click', option, row, index);
};

const handleSizeChange = (size) => {
  emit('size-change', size);
};

const handleCurrentChange = (page) => {
  emit('current-change', page);
};
</script>

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

.pagination {
  margin-top: 15px;
  display: flex;
  justify-content: flex-end;
}
</style>
<template>
  <div class="custom-table">
    <el-table
      :data="tableData"
      :border="border"
      :stripe="stripe"
      :highlight-current-row="highlightCurrentRow"
      :row-key="rowKey"
      :max-height="maxHeight"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @row-dblclick="handleRowDblclick"
      @sort-change="handleSortChange"
      :row-class-name="rowClassName"
    >
      <!-- 复选框列 -->
      <el-table-column
        v-if="selection"
        type="selection"
        width="55"
      ></el-table-column>

      <!-- 序号列 -->
      <el-table-column
        v-if="showIndex"
        type="index"
        :label="indexLabel"
        :width="indexWidth"
      ></el-table-column>

      <!-- 自定义列 -->
      <el-table-column
        v-for="column in columns"
        :key="column.prop"
        :prop="column.prop"
        :label="column.label"
        :width="column.width"
        :min-width="column.minWidth"
        :fixed="column.fixed"
        :sortable="column.sortable"
        :align="column.align || 'left'"
        :show-overflow-tooltip="column.showOverflowTooltip || true"
      >
        <!-- 插槽支持 -->
        <template #default="scope">
          <slot 
            :name="`column-${column.prop}`" 
            :row="scope.row" 
            :index="scope.$index"
          >
            {{ scope.row[column.prop] }}
          </slot>
        </template>
      </el-table-column>

      <!-- 操作列 -->
      <el-table-column
        v-if="operationOptions && operationOptions.length > 0"
        :label="operationLabel"
        :width="operationWidth"
        align="center"
      >
        <template #default="scope">
          <el-button
            v-for="option in operationOptions"
            :key="option.label"
            :type="option.type || 'text'"
            :size="option.size || 'mini'"
            :disabled="option.disabled ? option.disabled(scope.row) : false"
            @click.stop="handleOperationClick(option, scope.row, scope.$index)"
          >
            {{ option.label }}
          </el-button>
        </template>
      </el-table-column>
    </el-table>

    <!-- 分页 -->
    <div class="pagination" v-if="pagination">
      <el-pagination
        :current-page="paginationConfig.currentPage"
        :page-size="paginationConfig.pageSize"
        :total="paginationConfig.total"
        :layout="paginationConfig.layout"
        :page-sizes="paginationConfig.pageSizes"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      ></el-pagination>
    </div>
  </div>
</template>

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

// 定义 props
const props = defineProps({
  // 表格数据
  data: {
    type: Array,
    default: () => []
  },
  // 列配置
  columns: {
    type: Array,
    default: () => []
  },
  // 边框
  border: {
    type: Boolean,
    default: true
  },
  // 斑马纹
  stripe: {
    type: Boolean,
    default: false
  },
  // 高亮当前行
  highlightCurrentRow: {
    type: Boolean,
    default: false
  },
  // 行key,用于高亮
  rowKey: {
    type: String,
    default: ''
  },
  // 最大高度
  maxHeight: {
    type: [Number, String],
    default: ''
  },
  // 是否显示复选框
  selection: {
    type: Boolean,
    default: false
  },
  // 是否显示序号
  showIndex: {
    type: Boolean,
    default: false
  },
  // 序号列标题
  indexLabel: {
    type: String,
    default: '序号'
  },
  // 序号列宽度
  indexWidth: {
    type: [Number, String],
    default: 80
  },
  // 操作列配置
  operationOptions: {
    type: Array,
    default: () => []
  },
  // 操作列标题
  operationLabel: {
    type: String,
    default: '操作'
  },
  // 操作列宽度
  operationWidth: {
    type: [Number, String],
    default: 180
  },
  // 分页配置
  pagination: {
    type: Boolean,
    default: false
  },
  // 分页配置对象
  paginationConfig: {
    type: Object,
    default: () => ({
      currentPage: 1,
      pageSize: 10,
      total: 0,
      layout: 'total, sizes, prev, pager, next, jumper',
      pageSizes: [10, 20, 30, 50, 100]
    })
  },
  // 行类名函数
  rowClassName: {
    type: Function,
    default: null
  }
});

// 定义 emits
const emit = defineEmits([
  'selection-change',
  'row-click',
  'row-dblclick',
  'sort-change',
  'size-change',
  'current-change',
  'operation-click'
]);

// 计算分页后的表格数据
const tableData = computed(() => {
  if (props.pagination) {
    const { currentPage, pageSize } = props.paginationConfig;
    return props.data.slice(
      (currentPage - 1) * pageSize,
      currentPage * pageSize
    );
  }
  return props.data;
});

// 定义方法
const handleSelectionChange = (selection) => {
  emit('selection-change', selection);
};

const handleRowClick = (row, column, event) => {
  emit('row-click', row, column, event);
};

const handleRowDblclick = (row, column, event) => {
  emit('row-dblclick', row, column, event);
};

const handleSortChange = (sort) => {
  emit('sort-change', sort);
};

const handleOperationClick = (option, row, index) => {
  emit('operation-click', option, row, index);
};

const handleSizeChange = (size) => {
  emit('size-change', size);
};

const handleCurrentChange = (page) => {
  emit('current-change', page);
};
</script>

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

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

页面调用

vue
<template>
  <div class="table-demo">
    <CustomTable
      :data="tableData"
      :columns="columns"
      border
      stripe
      highlight-current-row
      selection
      show-index
      :operationOptions="operationOptions"
      :pagination="true"
      :paginationConfig="paginationConfig"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @operation-click="handleOperationClick"
    />
  </div>
</template>

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

const tableData = ref([
  {
    id: 1,
    name: '张三',
    age: 25,
    address: '北京市海淀区',
    status: '正常',
    createTime: '2023-05-01',
    updateTime: '2023-05-02'
  },
  {
    id: 2,
    name: '李四',
    age: 30,
    address: '上海市浦东新区',
    status: '正常',
    createTime: '2023-05-03',
    updateTime: '2023-05-04'
  },
  {
    id: 3,
    name: '王五',
    age: 28,
    address: '广州市天河区',
    status: '预警',
    createTime: '2023-05-05',
    updateTime: '2023-05-06'
  },
  {
    id: 4,
    name: '赵六',
    age: 35,
    address: '深圳市南山区',
    status: '正常',
    createTime: '2023-05-07',
    updateTime: '2023-05-08'
  },
  {
    id: 5,
    name: '钱七',
    age: 22,
    address: '杭州市西湖区',
    status: '禁用',
    createTime: '2023-05-09',
    updateTime: '2023-05-10'
  }
]);

const columns = ref([
  {
    prop: 'name',
    label: '姓名',
    width: '120'
  },
  {
    prop: 'age',
    label: '年龄',
    width: '80'
  },
  {
    prop: 'address',
    label: '地址',
    minWidth: '180'
  },
  {
    prop: 'status',
    label: '状态',
    width: '100',
    align: 'center'
  },
  {
    prop: 'createTime',
    label: '创建时间',
    width: '150',
    sortable: true
  },
  {
    prop: 'updateTime',
    label: '更新时间',
    width: '150'
  }
]);

const operationOptions = ref([
  {
    label: '编辑',
    type: 'primary',
    size: 'mini',
    disabled: (row) => row.status === '禁用'
  },
  {
    label: '删除',
    type: 'danger',
    size: 'mini'
  }
]);

const paginationConfig = ref({
  currentPage: 1,
  pageSize: 2,
  total: tableData.value.length
});

// 事件处理
const handleSelectionChange = (selection) => {
  console.log('选择的行:', selection);
};

const handleRowClick = (row, column) => {
  console.log('点击行:', row);
};

const handleOperationClick = (option, row) => {
  console.log('操作按钮点击:', option.label, row);
};
</script>
<template>
  <div class="table-demo">
    <CustomTable
      :data="tableData"
      :columns="columns"
      border
      stripe
      highlight-current-row
      selection
      show-index
      :operationOptions="operationOptions"
      :pagination="true"
      :paginationConfig="paginationConfig"
      @selection-change="handleSelectionChange"
      @row-click="handleRowClick"
      @operation-click="handleOperationClick"
    />
  </div>
</template>

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

const tableData = ref([
  {
    id: 1,
    name: '张三',
    age: 25,
    address: '北京市海淀区',
    status: '正常',
    createTime: '2023-05-01',
    updateTime: '2023-05-02'
  },
  {
    id: 2,
    name: '李四',
    age: 30,
    address: '上海市浦东新区',
    status: '正常',
    createTime: '2023-05-03',
    updateTime: '2023-05-04'
  },
  {
    id: 3,
    name: '王五',
    age: 28,
    address: '广州市天河区',
    status: '预警',
    createTime: '2023-05-05',
    updateTime: '2023-05-06'
  },
  {
    id: 4,
    name: '赵六',
    age: 35,
    address: '深圳市南山区',
    status: '正常',
    createTime: '2023-05-07',
    updateTime: '2023-05-08'
  },
  {
    id: 5,
    name: '钱七',
    age: 22,
    address: '杭州市西湖区',
    status: '禁用',
    createTime: '2023-05-09',
    updateTime: '2023-05-10'
  }
]);

const columns = ref([
  {
    prop: 'name',
    label: '姓名',
    width: '120'
  },
  {
    prop: 'age',
    label: '年龄',
    width: '80'
  },
  {
    prop: 'address',
    label: '地址',
    minWidth: '180'
  },
  {
    prop: 'status',
    label: '状态',
    width: '100',
    align: 'center'
  },
  {
    prop: 'createTime',
    label: '创建时间',
    width: '150',
    sortable: true
  },
  {
    prop: 'updateTime',
    label: '更新时间',
    width: '150'
  }
]);

const operationOptions = ref([
  {
    label: '编辑',
    type: 'primary',
    size: 'mini',
    disabled: (row) => row.status === '禁用'
  },
  {
    label: '删除',
    type: 'danger',
    size: 'mini'
  }
]);

const paginationConfig = ref({
  currentPage: 1,
  pageSize: 2,
  total: tableData.value.length
});

// 事件处理
const handleSelectionChange = (selection) => {
  console.log('选择的行:', selection);
};

const handleRowClick = (row, column) => {
  console.log('点击行:', row);
};

const handleOperationClick = (option, row) => {
  console.log('操作按钮点击:', option.label, row);
};
</script>

程序员小洛文档