文章目录
- 一、组件设计思路
-
- 二、组件架构设计
-
- 三、核心代码实现
-
- 四、功能模块实现
-
- 五、性能优化方案
-
- 六、完整测试方案
-
一、组件设计思路
1.1 功能需求分析
1.2 技术选型
功能模块 | 技术方案 | 说明 |
---|
基础表格 | HTML5 Table | 语义化标签 |
状态管理 | Vue Composition API | 响应式数据管理 |
分页控制 | 自定义分页组件 | 灵活可控 |
排序算法 | 数组排序 | 支持多列排序 |
筛选功能 | 组合式筛选器 | 支持多条件筛选 |
二、组件架构设计
2.1 组件结构
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')
})
})
总结:本文从设计到实现详细讲解了可复用表格组件的完整开发方案,包含分页、排序、筛选等核心功能,并提供了性能优化和测试方案。
