可复用表格组件设计与实现:分页、排序、筛选全功能解析

一、组件设计思路

1.1 功能需求分析

20% 25% 25% 20% 10% 表格功能需求 基础展示 分页功能 排序功能 筛选功能 性能优化

1.2 技术选型

功能模块技术方案说明
基础表格HTML5 Table语义化标签
状态管理Vue Composition API响应式数据管理
分页控制自定义分页组件灵活可控
排序算法数组排序支持多列排序
筛选功能组合式筛选器支持多条件筛选

二、组件架构设计

2.1 组件结构

TableComponent
TableHeader
TableBody
Pagination
SortControl
FilterControl
TableRow
TableCell

2.2 数据流设计

用户 表格组件 状态管理 服务端 点击排序 更新排序状态 触发重新渲染 请求排序数据 返回新数据 更新视图 修改筛选条件 更新筛选状态 触发重新渲染 请求筛选数据 返回新数据 更新视图 用户 表格组件 状态管理 服务端

三、核心代码实现

3.1 基础表格组件

<template>
  <div class="table-container">
    <table>
      <thead>
        <tr>
          <th v-for="col in columns" :key="col.key">
            {{ col.label }}
            <SortControl 
              :column="col"
              @sort="handleSort"
            />
          </th>
        </tr>
        <tr>
          <th v-for="col in columns" :key="col.key">
            <FilterControl 
              v-if="col.filterable"
              :column="col"
              @filter="handleFilter"
            />
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in paginatedData" :key="row.id">
          <td v-for="col in columns" :key="col.key">
            {{ row[col.key] }}
          </td>
        </tr>
      </tbody>
    </table>
    <Pagination 
      :total="filteredData.length"
      :current-page="currentPage"
      :page-size="pageSize"
      @page-change="handlePageChange"
    />
  </div>
</template>

3.2 状态管理

import { ref, computed } from 'vue'

export function useTable(data, columns) {
  const currentPage = ref(1)
  const pageSize = ref(10)
  const sortState = ref({})
  const filterState = ref({})

  const filteredData = computed(() => {
    return data.value.filter(row => {
      return Object.entries(filterState.value).every(([key, filter]) => {
        if (!filter) return true
        return filter(row[key])
      })
    })
  })

  const sortedData = computed(() => {
    const { key, order } = sortState.value
    if (!key) return filteredData.value
    
    return [...filteredData.value].sort((a, b) => {
      if (order === 'asc') {
        return a[key] > b[key] ? 1 : -1
      } else {
        return a[key] < b[key] ? 1 : -1
      }
    })
  })

  const paginatedData = computed(() => {
    const start = (currentPage.value - 1) * pageSize.value
    const end = start + pageSize.value
    return sortedData.value.slice(start, end)
  })

  return {
    currentPage,
    pageSize,
    sortState,
    filterState,
    filteredData,
    sortedData,
    paginatedData
  }
}

四、功能模块实现

4.1 分页组件

<template>
  <div class="pagination">
    <button 
      v-for="page in pages"
      :key="page"
      :class="{ active: page === currentPage }"
      @click="handlePageChange(page)"
    >
      {{ page }}
    </button>
  </div>
</template>

<script setup>
const props = defineProps({
  total: Number,
  currentPage: Number,
  pageSize: Number
})

const emit = defineEmits(['page-change'])

const totalPages = computed(() => Math.ceil(props.total / props.pageSize))

const pages = computed(() => {
  const range = []
  for (let i = 1; i <= totalPages.value; i++) {
    range.push(i)
  }
  return range
})

function handlePageChange(page) {
  emit('page-change', page)
}
</script>

4.2 排序控制

<template>
  <span class="sort-control" @click="toggleSort">
    <span v-if="sortState.key === column.key">
      {{ sortState.order === 'asc' ? '↑' : '↓' }}
    </span>
  </span>
</template>

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

const props = defineProps({
  column: Object
})

const sortState = inject('sortState')

function toggleSort() {
  if (sortState.value.key !== props.column.key) {
    sortState.value = { key: props.column.key, order: 'asc' }
  } else {
    sortState.value.order = sortState.value.order === 'asc' ? 'desc' : 'asc'
  }
}
</script>

4.3 筛选控制

<template>
  <input 
    v-if="column.filterType === 'text'"
    type="text"
    :value="filterValue"
    @input="handleInput"
  />
  <select 
    v-else-if="column.filterType === 'select'"
    :value="filterValue"
    @change="handleChange"
  >
    <option value="">All</option>
    <option v-for="option in column.filterOptions" :key="option">
      {{ option }}
    </option>
  </select>
</template>

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

const props = defineProps({
  column: Object
})

const filterState = inject('filterState')

const filterValue = computed({
  get: () => filterState.value[props.column.key] || '',
  set: value => {
    filterState.value[props.column.key] = value
  }
})

function handleInput(e) {
  filterValue.value = e.target.value
}

function handleChange(e) {
  filterValue.value = e.target.value
}
</script>

五、性能优化方案

5.1 虚拟滚动

function useVirtualScroll(items, itemHeight, containerHeight) {
  const scrollTop = ref(0)
  const visibleCount = Math.ceil(containerHeight / itemHeight)
  
  const startIndex = computed(() => {
    return Math.floor(scrollTop.value / itemHeight)
  })
  
  const endIndex = computed(() => {
    return startIndex.value + visibleCount
  })
  
  const visibleItems = computed(() => {
    return items.value.slice(startIndex.value, endIndex.value)
  })
  
  const paddingTop = computed(() => {
    return startIndex.value * itemHeight
  })
  
  const paddingBottom = computed(() => {
    return (items.value.length - endIndex.value) * itemHeight
  })
  
  return {
    scrollTop,
    visibleItems,
    paddingTop,
    paddingBottom
  }
}

5.2 防抖筛选

function useDebouncedFilter(filterState) {
  const debouncedFilter = ref({})
  
  watch(filterState, () => {
    clearTimeout(filterTimeout)
    filterTimeout = setTimeout(() => {
      debouncedFilter.value = { ...filterState.value }
    }, 300)
  }, { deep: true })
  
  return debouncedFilter
}

六、完整测试方案

6.1 测试用例设计

测试场景验证目标方法
基础渲染数据展示正确性验证渲染结果
分页功能分页逻辑正确性模拟分页操作
排序功能排序算法正确性验证排序结果
筛选功能筛选条件有效性测试不同筛选条件
性能测试大数据量渲染性能加载10万条数据

6.2 自动化测试示例

describe('TableComponent', () => {
  let wrapper
  const columns = [
    { key: 'name', label: 'Name' },
    { key: 'age', label: 'Age', sortable: true }
  ]
  const data = [
    { id: 1, name: 'Alice', age: 25 },
    { id: 2, name: 'Bob', age: 30 }
  ]

  beforeEach(() => {
    wrapper = mount(TableComponent, {
      props: { columns, data }
    })
  })

  test('renders correct number of rows', () => {
    expect(wrapper.findAll('tbody tr').length).toBe(data.length)
  })

  test('sorts data correctly', async () => {
    const ageHeader = wrapper.find('th', { text: 'Age' })
    await ageHeader.trigger('click')
    const firstRow = wrapper.find('tbody tr:first-child')
    expect(firstRow.text()).toContain('Bob')
  })
})

总结:本文从设计到实现详细讲解了可复用表格组件的完整开发方案,包含分页、排序、筛选等核心功能,并提供了性能优化和测试方案。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北辰alk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值