动态表格

Posted by Shallow Dreameron December 1, 2024
<script setup>
import { defineProps, defineEmits, useAttrs, ref, defineComponent } from 'vue'
import { ElTable, ElTableColumn, ElPagination } from 'element-plus'

const props = defineProps({
  tableData: {
    type: Array,
    required: true,
    default: () => []
  },
  columns: {
    type: Array,
    required: true,
    default: () => []
  },
  customProps: {
    type: Object,
    default: () => ({})
  },
  total: {
    type: Number,
    default: 0
  },
  currentPage: {
    type: Number,
    default: 1
  },
  pageSize: {
    type: Number,
    default: 10
  },
  // 新增分页相关配置
  pagination: {
    type: Object,
    default: () => ({
      enable: false, // 是否启用分页
      position: 'bottom', // 分页位置: top/bottom/both
      pageSizes: [10, 20, 30, 50], // 每页显示个数选择器的选项
      layout: 'total, sizes, prev, pager, next, jumper', // 组件布局
      justifyContent: 'flex-end', // 分页对齐方式: left/center/right
      alignItems: 'center',
      paginationProps: {}, // 用于传递给 el-pagination 的其他属性,
      padding: '0'
    })
  }
})

const emit = defineEmits([
  'select',
  'select-all',
  'selection-change',
  'cell-click', 
  'cell-dblclick',
  'cell-contextmenu',
  'cell-mouse-enter',
  'cell-mouse-leave',
  'row-click',
  'row-dblclick',
  'row-contextmenu',
  'header-click',
  'header-contextmenu',
  'sort-change',
  'filter-change',
  'current-change',
  'header-dragend',
  'expand-change',
  'update:currentPage',
  'update:pageSize',
  'pagination-change',
  // 新增 el-pagination 的所有事件
  'size-change',
  'prev-click',
  'next-click'
])

const attrs = useAttrs()

const RecursiveColumn = defineComponent({
  name: 'RecursiveColumn',
  props: {
    column: {
      type: Object,
      required: true
    }
  },
  template: `
    <el-table-column
      v-if="column.children && column.children.length"
      :label="column.label"
      :align="column.align"
      :width="column.width"
      :min-width="column.minWidth"
      :fixed="column.fixed"
      :show-overflow-tooltip="column.showOverflowTooltip"
      v-bind="column.customAttrs"
    >
      <recursive-column
        v-for="child in column.children"
        :key="child.prop"
        :column="child"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </recursive-column>
    </el-table-column>
    <el-table-column
      v-else
      :prop="column.prop"
      :label="column.label"
      :align="column.align"
      :width="column.width"
      :min-width="column.minWidth"
      :fixed="column.fixed"
      :sortable="column.sortable"
      :filters="column.filters"
      :filter-method="column.filterMethod"
      :show-overflow-tooltip="column.showOverflowTooltip"
      v-bind="column.customAttrs"
    >
      <template v-if="column.slot" #default="scope">
        <slot :name="column.slot" v-bind="scope"></slot>
      </template>
    </el-table-column>
  `
})

const tableRef = ref(null)

const handleCurrentChange = (val) => {
  emit('update:currentPage', val)
  emit('pagination-change', { page: val, pageSize: props.pageSize })
  emit('current-change', val)
}

const handleSizeChange = (val) => {
  emit('update:pageSize', val)
  emit('pagination-change', { page: props.currentPage, pageSize: val })
  emit('size-change', val)
}

const handlePrevClick = (val) => {
  emit('prev-click', val)
}

const handleNextClick = (val) => {
  emit('next-click', val)
}

defineExpose({
  clearSort: () => tableRef.value?.clearSort(),
  clearFilter: (columnKeys) => tableRef.value?.clearFilter(columnKeys),
  toggleRowExpansion: (row, expanded) => tableRef.value?.toggleRowExpansion(row, expanded),
  toggleAllSelection: () => tableRef.value?.toggleAllSelection(),
  setCurrentRow: (row) => tableRef.value?.setCurrentRow(row),
  clearSelection: () => tableRef.value?.clearSelection(),
  sort: (prop, order) => tableRef.value?.sort(prop, order),
  getTableRef: () => tableRef.value
})
</script>

<template>
  <div class="table-wrapper">
    <!-- 上方分页 -->
    <div v-if="pagination?.enable && ['top', 'both'].includes(pagination.position)" class="pagination-wrapper" :style="{display: 'flex', justifyContent: pagination.align || 'right'}">
      <el-pagination
        v-bind="pagination.paginationProps"
        :current-page="currentPage"
        :page-size="pageSize"
        :page-sizes="pagination.pageSizes"
        :total="total"
        :layout="pagination.layout"
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
        @prev-click="handlePrevClick"
        @next-click="handleNextClick"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </el-pagination>
    </div>

    <el-table 
      ref="tableRef"
      :data="tableData"
      v-bind="{ ...attrs, ...customProps }"
      @select="(selection, row) => emit('select', selection, row)"
      @select-all="(selection) => emit('select-all', selection)"
      @selection-change="(selection) => emit('selection-change', selection)"
      @cell-click="(row, column, cell, event) => emit('cell-click', row, column, cell, event)"
      @cell-dblclick="(row, column, cell, event) => emit('cell-dblclick', row, column, cell, event)"
      @cell-contextmenu="(row, column, cell, event) => emit('cell-contextmenu', row, column, cell, event)"
      @cell-mouse-enter="(row, column, cell, event) => emit('cell-mouse-enter', row, column, cell, event)"
      @cell-mouse-leave="(row, column, cell, event) => emit('cell-mouse-leave', row, column, cell, event)"
      @row-click="(row, column, event) => emit('row-click', row, column, event)"
      @row-dblclick="(row, column, event) => emit('row-dblclick', row, column, event)"
      @row-contextmenu="(row, column, event) => emit('row-contextmenu', row, column, event)"
      @header-click="(column, event) => emit('header-click', column, event)"
      @header-contextmenu="(column, event) => emit('header-contextmenu', column, event)"
      @sort-change="(obj) => emit('sort-change', obj)"
      @filter-change="(filters) => emit('filter-change', filters)"
      @current-change="(currentRow, oldCurrentRow) => emit('current-change', currentRow, oldCurrentRow)"
      @header-dragend="(newWidth, oldWidth, column, event) => emit('header-dragend', newWidth, oldWidth, column, event)"
      @expand-change="(row, expanded) => emit('expand-change', row, expanded)"
    >
      <slot name="selection"></slot>
      <slot name="index"></slot>
      <slot name="expand"></slot>
      
      <template v-for="column in columns" :key="column.prop">
        <recursive-column :column="column">
          <template v-for="(_, name) in $slots" #[name]="slotData">
            <slot :name="name" v-bind="slotData"></slot>
          </template>
        </recursive-column>
      </template>
      
      <template #empty>
        <slot name="empty"></slot>
      </template>
      <template #append>
        <slot name="append"></slot>
      </template>
    </el-table>

    <!-- 下方分页 -->
    <div v-if="pagination?.enable && ['bottom', 'both'].includes(pagination.position)" class="pagination-wrapper" :style="{display: 'flex', justifyContent: pagination.align || 'right', padding: pagination.padding || '0'}">
      <el-pagination
        v-bind="pagination.paginationProps"
        :current-page="currentPage"
        :page-size="pageSize"
        :page-sizes="pagination.pageSizes"
        :total="total"
        :layout="pagination.layout"
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
        @prev-click="handlePrevClick"
        @next-click="handleNextClick"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </el-pagination>
    </div>
  </div>
</template>

<script setup>
import { defineProps, defineEmits, useAttrs, ref, defineComponent } from 'vue'
import { ElTable, ElTableColumn, ElPagination } from 'element-plus'

const props = defineProps({
  tableData: {
    type: Array,
    required: true,
    default: () => []
  },
  columns: {
    type: Array,
    required: true,
    default: () => []
  },
  customProps: {
    type: Object,
    default: () => ({})
  },
  total: {
    type: Number,
    default: 0
  },
  currentPage: {
    type: Number,
    default: 1
  },
  pageSize: {
    type: Number,
    default: 10
  },
  // 新增分页相关配置
  pagination: {
    type: Object,
    default: () => ({
      enable: false, // 是否启用分页
      position: 'bottom', // 分页位置: top/bottom/both
      pageSizes: [10, 20, 30, 50], // 每页显示个数选择器的选项
      layout: 'total, sizes, prev, pager, next, jumper', // 组件布局
      justifyContent: 'flex-end', // 分页对齐方式: left/center/right
      alignItems: 'center',
      paginationProps: {}, // 用于传递给 el-pagination 的其他属性,
      padding: '0'
    })
  }
})

const emit = defineEmits([
  'select',
  'select-all',
  'selection-change',
  'cell-click', 
  'cell-dblclick',
  'cell-contextmenu',
  'cell-mouse-enter',
  'cell-mouse-leave',
  'row-click',
  'row-dblclick',
  'row-contextmenu',
  'header-click',
  'header-contextmenu',
  'sort-change',
  'filter-change',
  'current-change',
  'header-dragend',
  'expand-change',
  'update:currentPage',
  'update:pageSize',
  'pagination-change',
  // 新增 el-pagination 的所有事件
  'size-change',
  'prev-click',
  'next-click'
])

const attrs = useAttrs()

const RecursiveColumn = defineComponent({
  name: 'RecursiveColumn',
  props: {
    column: {
      type: Object,
      required: true
    }
  },
  template: `
    <el-table-column
      v-if="column.children && column.children.length"
      :label="column.label"
      :align="column.align"
      :width="column.width"
      :min-width="column.minWidth"
      :fixed="column.fixed"
      :show-overflow-tooltip="column.showOverflowTooltip"
      v-bind="column.customAttrs"
    >
      <template v-if="column.headerSlot" #header="scope">
        <slot :name="column.headerSlot" v-bind="scope"></slot>
      </template>
      
      <recursive-column
        v-for="child in column.children"
        :key="child.prop"
        :column="child"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </recursive-column>
    </el-table-column>
    <el-table-column
      v-else
      :prop="column.prop"
      :label="column.label"
      :align="column.align"
      :width="column.width"
      :min-width="column.minWidth"
      :fixed="column.fixed"
      :sortable="column.sortable"
      :filters="column.filters"
      :filter-method="column.filterMethod"
      :show-overflow-tooltip="column.showOverflowTooltip"
      v-bind="column.customAttrs"
    >
      <template v-if="column.slot" #default="scope">
        <slot :name="column.slot" v-bind="scope"></slot>
      </template>
      
      <template v-if="column.headerSlot" #header="scope">
        <slot :name="column.headerSlot" v-bind="scope"></slot>
      </template>
      
      <template v-for="(slotName, slotKey) in column.slots" :key="slotKey" #[slotKey]="scope">
        <slot :name="slotName" v-bind="scope"></slot>
      </template>
    </el-table-column>
  `
})

const tableRef = ref(null)

const handleCurrentChange = (val) => {
  emit('update:currentPage', val)
  emit('pagination-change', { page: val, pageSize: props.pageSize })
  emit('current-change', val)
}

const handleSizeChange = (val) => {
  emit('update:pageSize', val)
  emit('pagination-change', { page: props.currentPage, pageSize: val })
  emit('size-change', val)
}

const handlePrevClick = (val) => {
  emit('prev-click', val)
}

const handleNextClick = (val) => {
  emit('next-click', val)
}

defineExpose({
  clearSort: () => tableRef.value?.clearSort(),
  clearFilter: (columnKeys) => tableRef.value?.clearFilter(columnKeys),
  toggleRowExpansion: (row, expanded) => tableRef.value?.toggleRowExpansion(row, expanded),
  toggleAllSelection: () => tableRef.value?.toggleAllSelection(),
  setCurrentRow: (row) => tableRef.value?.setCurrentRow(row),
  clearSelection: () => tableRef.value?.clearSelection(),
  sort: (prop, order) => tableRef.value?.sort(prop, order),
  getTableRef: () => tableRef.value
})
</script>

<template>
  <div class="table-wrapper">
    <!-- 上方分页 -->
    <div v-if="pagination?.enable && ['top', 'both'].includes(pagination.position)" class="pagination-wrapper" :style="{display: 'flex', justifyContent: pagination.align || 'right'}">
      <el-pagination
        v-bind="pagination.paginationProps"
        :current-page="currentPage"
        :page-size="pageSize"
        :page-sizes="pagination.pageSizes"
        :total="total"
        :layout="pagination.layout"
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
        @prev-click="handlePrevClick"
        @next-click="handleNextClick"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </el-pagination>
    </div>

    <el-table 
      ref="tableRef"
      :data="tableData"
      v-bind="{ ...attrs, ...customProps }"
      @select="(selection, row) => emit('select', selection, row)"
      @select-all="(selection) => emit('select-all', selection)"
      @selection-change="(selection) => emit('selection-change', selection)"
      @cell-click="(row, column, cell, event) => emit('cell-click', row, column, cell, event)"
      @cell-dblclick="(row, column, cell, event) => emit('cell-dblclick', row, column, cell, event)"
      @cell-contextmenu="(row, column, cell, event) => emit('cell-contextmenu', row, column, cell, event)"
      @cell-mouse-enter="(row, column, cell, event) => emit('cell-mouse-enter', row, column, cell, event)"
      @cell-mouse-leave="(row, column, cell, event) => emit('cell-mouse-leave', row, column, cell, event)"
      @row-click="(row, column, event) => emit('row-click', row, column, event)"
      @row-dblclick="(row, column, event) => emit('row-dblclick', row, column, event)"
      @row-contextmenu="(row, column, event) => emit('row-contextmenu', row, column, event)"
      @header-click="(column, event) => emit('header-click', column, event)"
      @header-contextmenu="(column, event) => emit('header-contextmenu', column, event)"
      @sort-change="(obj) => emit('sort-change', obj)"
      @filter-change="(filters) => emit('filter-change', filters)"
      @current-change="(currentRow, oldCurrentRow) => emit('current-change', currentRow, oldCurrentRow)"
      @header-dragend="(newWidth, oldWidth, column, event) => emit('header-dragend', newWidth, oldWidth, column, event)"
      @expand-change="(row, expanded) => emit('expand-change', row, expanded)"
    >
      <slot name="selection"></slot>
      <slot name="index"></slot>
      <slot name="expand"></slot>
      
      <template v-for="column in columns" :key="column.prop">
        <recursive-column :column="column">
          <template v-for="(_, name) in $slots" #[name]="slotData">
            <slot :name="name" v-bind="slotData"></slot>
          </template>
        </recursive-column>
      </template>
      
      <template #empty>
        <slot name="empty"></slot>
      </template>
      <template #append>
        <slot name="append"></slot>
      </template>
    </el-table>

    <!-- 下方分页 -->
    <div v-if="pagination?.enable && ['bottom', 'both'].includes(pagination.position)" class="pagination-wrapper" :style="{display: 'flex', justifyContent: pagination.align || 'right', padding: pagination.padding || '0'}">
      <el-pagination
        v-bind="pagination.paginationProps"
        :current-page="currentPage"
        :page-size="pageSize"
        :page-sizes="pagination.pageSizes"
        :total="total"
        :layout="pagination.layout"
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
        @prev-click="handlePrevClick"
        @next-click="handleNextClick"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </el-pagination>
    </div>
  </div>
</template>

useTableHooks

import { ref } from 'vue'

export const useTable = (emit) => {
  const tableRef = ref(null)

  // Table methods
  const clearSort = () => tableRef.value?.clearSort()
  const clearFilter = (columnKeys) => tableRef.value?.clearFilter(columnKeys)
  const toggleRowExpansion = (row, expanded) => tableRef.value?.toggleRowExpansion(row, expanded)
  const toggleAllSelection = () => tableRef.value?.toggleAllSelection()
  const setCurrentRow = (row) => tableRef.value?.setCurrentRow(row)
  const clearSelection = () => tableRef.value?.clearSelection()
  const sort = (prop, order) => tableRef.value?.sort(prop, order)
  const getTableRef = () => tableRef.value

  // Table event handlers
  const handleTableEvents = {
    select: (selection, row) => emit('select', selection, row),
    'select-all': (selection) => emit('select-all', selection),
    'selection-change': (selection) => emit('selection-change', selection),
    'cell-click': (row, column, cell, event) => emit('cell-click', row, column, cell, event),
    'cell-dblclick': (row, column, cell, event) => emit('cell-dblclick', row, column, cell, event),
    'cell-contextmenu': (row, column, cell, event) => emit('cell-contextmenu', row, column, cell, event),
    'cell-mouse-enter': (row, column, cell, event) => emit('cell-mouse-enter', row, column, cell, event),
    'cell-mouse-leave': (row, column, cell, event) => emit('cell-mouse-leave', row, column, cell, event),
    'row-click': (row, column, event) => emit('row-click', row, column, event),
    'row-dblclick': (row, column, event) => emit('row-dblclick', row, column, event),
    'row-contextmenu': (row, column, event) => emit('row-contextmenu', row, column, event),
    'header-click': (column, event) => emit('header-click', column, event),
    'header-contextmenu': (column, event) => emit('header-contextmenu', column, event),
    'sort-change': (obj) => emit('sort-change', obj),
    'filter-change': (filters) => emit('filter-change', filters),
    'current-change': (currentRow, oldCurrentRow) => emit('current-change', currentRow, oldCurrentRow),
    'header-dragend': (newWidth, oldWidth, column, event) => emit('header-dragend', newWidth, oldWidth, column, event),
    'expand-change': (row, expanded) => emit('expand-change', row, expanded)
  }

  return {
    tableRef,
    clearSort,
    clearFilter,
    toggleRowExpansion,
    toggleAllSelection,
    setCurrentRow,
    clearSelection,
    sort,
    getTableRef,
    handleTableEvents
  }
} 

usePaginationHooks

export const usePagination = (props, emit) => {
  const handleCurrentChange = (val) => {
    emit('update:currentPage', val)
    emit('pagination-change', { page: val, pageSize: props.pageSize })
    emit('current-change', val)
  }

  const handleSizeChange = (val) => {
    emit('update:pageSize', val)
    emit('pagination-change', { page: props.currentPage, pageSize: val })
    emit('size-change', val)
  }

  const handlePrevClick = (val) => {
    emit('prev-click', val)
  }

  const handleNextClick = (val) => {
    emit('next-click', val)
  }

  return {
    handleCurrentChange,
    handleSizeChange,
    handlePrevClick,
    handleNextClick
  }
} 

TableTest.vue

<script setup>
import { defineEmits, defineComponent, useAttrs } from 'vue'
import { ElTable, ElTableColumn, ElPagination } from 'element-plus'
import { useTable } from '../hooks/useTable'
import { usePagination } from '../hooks/usePagination'

const props = defineProps({
  tableData: {
    type: Array,
    required: true,
    default: () => []
  },
  columns: {
    type: Array,
    required: true,
    default: () => []
  },
  customProps: {
    type: Object,
    default: () => ({})
  },
  total: {
    type: Number,
    default: 0
  },
  currentPage: {
    type: Number,
    default: 1
  },
  pageSize: {
    type: Number,
    default: 10
  },
  // 新增分页相关配置
  pagination: {
    type: Object,
    default: () => ({
      enable: false, // 是否启用分页
      position: 'bottom', // 分页位置: top/bottom/both
      pageSizes: [10, 20, 30, 50], // 每页显示个数选择器的选项
      layout: 'total, sizes, prev, pager, next, jumper', // 组件布局
      justifyContent: 'flex-end', // 分页对齐方式: left/center/right
      alignItems: 'center',
      paginationProps: {}, // 用于传递给 el-pagination 的其他属性,
      padding: '0'
    })
  }
})

const emit = defineEmits([
  'select',
  'select-all',
  'selection-change',
  'cell-click', 
  'cell-dblclick',
  'cell-contextmenu',
  'cell-mouse-enter',
  'cell-mouse-leave',
  'row-click',
  'row-dblclick',
  'row-contextmenu',
  'header-click',
  'header-contextmenu',
  'sort-change',
  'filter-change',
  'current-change',
  'header-dragend',
  'expand-change',
  'update:currentPage',
  'update:pageSize',
  'pagination-change',
  // 新增 el-pagination 的所有事件
  'size-change',
  'prev-click',
  'next-click'
])

const attrs = useAttrs()

// Use hooks
const {
  tableRef,
  handleTableEvents,
  ...tableExposeMethods
} = useTable(emit)

const {
  handleCurrentChange,
  handleSizeChange,
  handlePrevClick,
  handleNextClick
} = usePagination(props, emit)

// Define expose methods
defineExpose(tableExposeMethods)

const RecursiveColumn = defineComponent({
  name: 'RecursiveColumn',
  props: {
    column: {
      type: Object,
      required: true
    }
  },
  template: `
    <el-table-column
      v-if="column.children && column.children.length"
      :label="column.label"
      :align="column.align"
      :width="column.width"
      :min-width="column.minWidth"
      :fixed="column.fixed"
      :show-overflow-tooltip="column.showOverflowTooltip"
      v-bind="column.customAttrs"
    >
      <template v-if="column.headerSlot" #header="scope">
        <slot :name="column.headerSlot" v-bind="scope"></slot>
      </template>
      
      <recursive-column
        v-for="child in column.children"
        :key="child.prop"
        :column="child"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </recursive-column>
    </el-table-column>
    <el-table-column
      v-else
      :prop="column.prop"
      :label="column.label"
      :align="column.align"
      :width="column.width"
      :min-width="column.minWidth"
      :fixed="column.fixed"
      :sortable="column.sortable"
      :filters="column.filters"
      :filter-method="column.filterMethod"
      :show-overflow-tooltip="column.showOverflowTooltip"
      v-bind="column.customAttrs"
    >
      <template v-if="column.slot" #default="scope">
        <slot :name="column.slot" v-bind="scope"></slot>
      </template>
      
      <template v-if="column.headerSlot" #header="scope">
        <slot :name="column.headerSlot" v-bind="scope"></slot>
      </template>
      
      <template v-for="(slotName, slotKey) in column.slots" :key="slotKey" #[slotKey]="scope">
        <slot :name="slotName" v-bind="scope"></slot>
      </template>
    </el-table-column>
  `
})
</script>

<template>
  <div class="table-wrapper">
    <!-- 上方分页 -->
    <div v-if="pagination?.enable && ['top', 'both'].includes(pagination.position)" class="pagination-wrapper" :style="{display: 'flex', justifyContent: pagination.align || 'right'}">
      <el-pagination
        v-bind="pagination.paginationProps"
        :current-page="currentPage"
        :page-size="pageSize"
        :page-sizes="pagination.pageSizes"
        :total="total"
        :layout="pagination.layout"
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
        @prev-click="handlePrevClick"
        @next-click="handleNextClick"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </el-pagination>
    </div>

    <el-table 
      ref="tableRef"
      :data="tableData"
      v-bind="{ ...attrs, ...customProps, ...handleTableEvents }"
    >
      <slot name="selection"></slot>
      <slot name="index"></slot>
      <slot name="expand"></slot>
      
      <template v-for="column in columns" :key="column.prop">
        <recursive-column :column="column">
          <template v-for="(_, name) in $slots" #[name]="slotData">
            <slot :name="name" v-bind="slotData"></slot>
          </template>
        </recursive-column>
      </template>
      
      <template #empty>
        <slot name="empty"></slot>
      </template>
      <template #append>
        <slot name="append"></slot>
      </template>
    </el-table>

    <!-- 下方分页 -->
    <div v-if="pagination?.enable && ['bottom', 'both'].includes(pagination.position)" class="pagination-wrapper" :style="{display: 'flex', justifyContent: pagination.align || 'right', padding: pagination.padding || '0'}">
      <el-pagination
        v-bind="pagination.paginationProps"
        :current-page="currentPage"
        :page-size="pageSize"
        :page-sizes="pagination.pageSizes"
        :total="total"
        :layout="pagination.layout"
        @current-change="handleCurrentChange"
        @size-change="handleSizeChange"
        @prev-click="handlePrevClick"
        @next-click="handleNextClick"
      >
        <template v-for="(_, name) in $slots" #[name]="slotData">
          <slot :name="name" v-bind="slotData"></slot>
        </template>
      </el-pagination>
    </div>
  </div>
</template>

用法

<template>
  <div class="about-container">
    <table-test
      :table-data="tableData"
      :columns="columns"
      :total="total"
      :current-page="currentPage"
      :page-size="pageSize"
      :pagination="paginationConfig"
      :custom-props="tableProps"
      @pagination-change="handlePaginationChange"
      @filter-change="handleFilterChange"
    >
      <!-- 状态列自定义显示 -->
      <template #status-slot="{ row }">
        <el-tag :type="row.status === '在线' ? 'success' : row.status === '离线' ? 'info' : 'warning'">
          
        </el-tag>
      </template>
    </table-test>
    <virtual-list></virtual-list>
    <virtual-table></virtual-table>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import TableTest from './TableTest.vue'
import { ElMessage } from 'element-plus'
import VirtualList from '@/components/VirtualList.vue'
import VirtualTable from '@/components/VirtualTable.vue'

// 表格数据状态
const tableData = ref([])
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
const loading = ref(false)

// 过滤条件
const filterConditions = ref({})

// 使用本地过滤还是远程过滤的标志
const useLocalFilter = ref(true)

// 自定义过滤处理函数(如果需要的话)
const customFilterHandler = (filters) => {
  console.log('自定义过滤处理:', filters)
  // 这里可以实现自己的过滤逻辑
}

// 模拟后端数据库
const mockDatabase = (() => {
  const data = []
  for (let i = 0; i < 1000; i++) {
    const age = Math.floor(Math.random() * 50) + 18
    const ageGroup = age <= 25 ? '18-25' : 
                    age <= 35 ? '26-35' : 
                    age <= 45 ? '36-45' : '46+'
    
    data.push({
      id: i + 1,
      name: `用户${i + 1}`,
      age,
      ageGroup,
      address: `中国北京市朝阳区第${Math.floor(Math.random() * 1000)}号`,
      email: `user${i + 1}@example.com`,
      status: ['在线', '离线', '忙碌'][Math.floor(Math.random() * 3)]
    })
  }
  return data
})()

// 模拟分页请求
const fetchData = async (params) => {
  try {
    loading.value = true
    await new Promise(resolve => setTimeout(resolve, 300))
    
    const { page, pageSize, filters } = params
    console.log('应用过滤条件:', filters)
    
    let filteredData = [...mockDatabase]
    
    if (filters && Object.keys(filters).length > 0) {
      filteredData = filteredData.filter(item => {
        let match = true
        
        // 状态过滤
        if (filters.status?.length) {
          match = match && filters.status.includes(item.status)
        }
        
        // 年龄段过滤
        if (filters.ageGroup?.length) {
          match = match && filters.ageGroup.includes(item.ageGroup)
        }
        
        // 名称搜索
        if (filters.name) {
          match = match && item.name.toLowerCase().includes(filters.name.toLowerCase())
        }
        
        return match
      })
    }
    
    console.log('过滤���数据条数:', filteredData.length)
    
    const start = (page - 1) * pageSize
    const end = start + pageSize
    
    return {
      list: filteredData.slice(start, end),
      total: filteredData.length
    }
  } finally {
    loading.value = false
  }
}

// 处理过滤变化
const handleFilterChange = (filters) => {
  console.log('过滤条件:', filters)
  // 这里只需要处理分页等逻辑
  currentPage.value = 1
  loadTableData()
}

// 加载表格数据
const loadTableData = async () => {
  try {
    const params = {
      page: currentPage.value,
      pageSize: pageSize.value,
      filters: filterConditions.value
    }
    
    const { list, total: totalCount } = await fetchData(params)
    tableData.value = list
    total.value = totalCount
  } catch (error) {
    ElMessage.error('加载数据失败')
    console.error(error)
  }
}

// 分页变化处理
const handlePaginationChange = ({ page, pageSize: size }) => {
  currentPage.value = page
  pageSize.value = size
  loadTableData()
}

// 表格配置
const columns = [
  {
    prop: 'id',
    label: 'ID',
    width: 80,
    fixed: 'left'
  },
  {
    prop: 'name',
    label: '姓名',
    minWidth: 120,
    filterable: true,
    customAttrs: {
      filterPlacement: 'bottom-start',
      filters: [{ value: '', text: '搜索姓名' }],
      'filter-multiple': false
    }
  },
  {
    prop: 'ageGroup',
    label: '年龄段',
    width: 100,
    sortable: true,
    filterable: true,
    customAttrs: {
      filterPlacement: 'bottom-start',
      filters: [
        { value: '18-25', text: '18-25岁' },
        { value: '26-35', text: '26-35岁' },
        { value: '36-45', text: '36-45岁' },
        { value: '46+', text: '46岁以上' }
      ],
      'filter-multiple': true
    }
  },
  {
    prop: 'email',
    label: '邮箱',
    minWidth: 180,
    showOverflowTooltip: true
  },
  {
    prop: 'address',
    label: '地址',
    minWidth: 220,
    showOverflowTooltip: true
  },
  {
    prop: 'status',
    label: '状态',
    width: 100,
    slot: 'status-slot',
    filterable: true,
    customAttrs: {
      filterPlacement: 'bottom-start',
      filters: [
        { value: '在线', text: '在线' },
        { value: '离线', text: '离线' },
        { value: '忙碌', text: '忙碌' }
      ],
      'filter-multiple': true
    }
  }
]

// 分页配置
const paginationConfig = {
  enable: true,
  position: 'bottom',
  pageSizes: [10, 20, 50, 100],
  layout: 'total, sizes, prev, pager, next, jumper'
}

// 表格属性配置
const tableProps = {
  height: '600px',
  stripe: true,
  border: true,
  highlightCurrentRow: true,
  size: 'default',
  rowKey: 'id',
  loading: loading,
  popperOptions: {
    modifiers: [
      {
        name: 'computeStyles',
        options: {
          adaptive: false,
          gpuAcceleration: false
        }
      }
    ]
  }
}

onMounted(() => {
  loadTableData()
})
</script>

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

:deep(.filter-popper) {
  margin-top: 0;
}

:deep(.el-table-filter) {
  background: var(--el-bg-color);
  border: 1px solid var(--el-border-color-lighter);
  border-radius: 4px;
  box-shadow: var(--el-box-shadow-light);
}
</style>