Files
school-developer/docs/tableHook使用方式.md
2026-03-19 15:55:37 +08:00

664 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# useTableColumnControl Hook 使用文档
## 一、简介
`useTableColumnControl` 是一个用于统一管理表格列显示/隐藏和排序功能的 Vue 3 Composition API Hook。它封装了列配置的加载、保存、同步等逻辑让开发者只需几行代码即可实现完整的表格列控制功能。
### 主要功能
- ✅ 列显示/隐藏控制
- ✅ 列排序管理
- ✅ 配置自动保存到本地存储
- ✅ 配置同步到后端服务器
- ✅ 自动加载已保存的配置
- ✅ 类型安全支持
---
## 二、安装和引入
### 文件位置
```
src/hooks/tableColumn.ts
```
### 引入方式
```typescript
import { useTableColumnControl, type TableColumn } from '/@/hooks/tableColumn'
```
---
## 三、基础使用
### 1. 引入公共样式(推荐)
首先在 `<style>` 中引入现代化页面布局样式:
```vue
<style scoped lang="scss">
@import '/@/assets/styles/modern-page.scss';
</style>
```
### 2. 定义表格列配置
```typescript
import { User, Calendar, Phone } from '@element-plus/icons-vue'
const tableColumns = [
{ prop: 'name', label: '姓名', icon: User },
{ prop: 'age', label: '年龄', icon: Calendar },
{ prop: 'phone', label: '电话', icon: Phone }
]
```
### 3. 使用 Hook
```typescript
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns)
```
### 4. 在模板中使用
```vue
<template>
<div class="modern-page-container">
<div class="page-wrapper">
<!-- 搜索表单卡片 -->
<el-card v-show="showSearch" class="search-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Search /></el-icon>
筛选条件
</span>
</div>
</template>
<el-form class="search-form">
<!-- 搜索表单项 -->
</el-form>
</el-card>
<!-- 内容卡片 -->
<el-card class="content-card" shadow="never">
<template #header>
<div class="card-header">
<span class="card-title">
<el-icon class="title-icon"><Document /></el-icon>
数据列表
</span>
<div class="header-actions">
<!-- TableColumnControl 组件 -->
<TableColumnControl
:columns="tableColumns"
v-model="visibleColumns"
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
/>
</div>
</div>
</template>
<!-- 表格 -->
<el-table :data="dataList" stripe class="modern-table">
<el-table-column type="index" label="序号" width="70">
<template #default="{ $index }">
{{ $index + 1 + ((pagination?.current || 1) - 1) * (pagination?.size || 10) }}
</template>
</el-table-column>
<!-- 动态渲染列 -->
<template v-for="col in visibleColumnsSorted" :key="col.prop">
<el-table-column
v-if="checkColumnVisible(col.prop || '')"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
>
<template #header>
<el-icon v-if="col.icon">
<component :is="col.icon" />
</el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
</template>
</el-table>
<!-- 分页 -->
<div class="pagination-wrapper">
<pagination v-bind="pagination" />
</div>
</el-card>
</div>
</div>
</template>
```
---
## 四、完整示例
```vue
<template>
<div>
<right-toolbar>
<TableColumnControl
ref="columnControlRef"
:columns="tableColumns"
v-model="visibleColumns"
trigger-type="default"
trigger-circle
@change="handleColumnChange"
@order-change="handleColumnOrderChange"
>
<template #trigger>
<el-tooltip content="列设置" placement="top">
<el-button circle>
<el-icon><Menu /></el-icon>
</el-button>
</el-tooltip>
</template>
</TableColumnControl>
</right-toolbar>
<el-table :data="state.dataList" stripe>
<el-table-column type="index" label="序号" width="70">
<template #default="{ $index }">
{{ $index + 1 + ((state.pagination?.current || 1) - 1) * (state.pagination?.size || 10) }}
</template>
</el-table-column>
<template v-for="col in visibleColumnsSorted" :key="col.prop">
<el-table-column
v-if="checkColumnVisible(col.prop || '')"
:prop="col.prop"
:label="col.label"
:width="col.width"
:min-width="col.minWidth"
show-overflow-tooltip
>
<template #header>
<el-icon v-if="col.icon">
<component :is="col.icon" />
</el-icon>
<span style="margin-left: 4px">{{ col.label }}</span>
</template>
</el-table-column>
</template>
<el-table-column label="操作" width="180" fixed="right">
<!-- 操作列内容 -->
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useTableColumnControl } from '/@/hooks/tableColumn'
import { User, Calendar, Phone, Menu } from '@element-plus/icons-vue'
import TableColumnControl from '/@/components/TableColumnControl/index.vue'
// 定义表格列
const tableColumns = [
{ prop: 'name', label: '姓名', icon: User },
{ prop: 'age', label: '年龄', icon: Calendar },
{ prop: 'phone', label: '电话', icon: Phone }
]
// 使用 Hook
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns)
const columnControlRef = ref()
const state = ref({
dataList: [],
pagination: { current: 1, size: 10 }
})
</script>
```
---
## 五、API 参考
### TableColumn 类型定义
```typescript
interface TableColumn {
prop?: string // 列属性名
label?: string // 列标题
icon?: any // 列图标组件
width?: number | string // 列宽度
minWidth?: number | string // 最小宽度
showOverflowTooltip?: boolean // 是否显示溢出提示
align?: 'left' | 'center' | 'right' // 对齐方式
alwaysShow?: boolean // 是否始终显示(不参与列控制)
fixed?: boolean | 'left' | 'right' // 是否固定列
[key: string]: any // 其他自定义属性
}
```
### Hook 返回值
| 属性/方法 | 类型 | 说明 |
|----------|------|------|
| `visibleColumns` | `Ref<string[]>` | 当前可见的列 key 数组 |
| `columnOrder` | `Ref<string[]>` | 列排序顺序数组 |
| `visibleColumnsSorted` | `ComputedRef<TableColumn[]>` | 排序后的可见列配置数组 |
| `checkColumnVisible` | `(prop: string) => boolean` | 检查列是否可见 |
| `handleColumnChange` | `(value: string[]) => void` | 处理列显示变化 |
| `handleColumnOrderChange` | `(order: string[]) => void` | 处理列排序变化 |
| `loadSavedConfig` | `() => void` | 手动加载保存的配置 |
### Hook 选项参数
```typescript
interface Options {
autoLoad?: boolean // 是否自动加载配置,默认 true
storageKey?: string // 自定义存储 key默认使用路由路径
}
```
---
## 六、高级用法
### 1. 自定义存储 Key
如果不想使用默认的路由路径作为存储 key可以自定义
```typescript
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns, {
storageKey: 'custom-table-columns-key'
})
```
### 2. 禁用自动加载
如果需要在特定时机手动加载配置:
```typescript
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange,
loadSavedConfig
} = useTableColumnControl(tableColumns, {
autoLoad: false
})
// 在需要的时候手动加载
onMounted(() => {
loadSavedConfig()
})
```
### 3. 固定列和始终显示的列
某些列(如操作列)不应该参与列控制:
```typescript
const tableColumns = [
{ prop: 'name', label: '姓名', icon: User },
{ prop: 'age', label: '年龄', icon: Calendar },
{
prop: 'action',
label: '操作',
alwaysShow: true, // 始终显示
fixed: 'right' // 固定在右侧
}
]
```
### 4. 特殊列模板
如果需要为某些列添加特殊模板:
```vue
<template v-for="col in visibleColumnsSorted" :key="col.prop">
<el-table-column
v-if="checkColumnVisible(col.prop || '')"
:prop="col.prop"
:label="col.label"
>
<!-- 状态列特殊模板 -->
<template v-if="col.prop === 'status'" #default="scope">
<el-tag :type="scope.row.status === '1' ? 'success' : 'warning'">
{{ scope.row.status }}
</el-tag>
</template>
<!-- 其他列默认显示 -->
<template v-else #default="scope">
{{ scope.row[col.prop] }}
</template>
</el-table-column>
</template>
```
---
## 七、注意事项
### 1. 列 Key 的唯一性
确保每列的 `prop``label` 是唯一的,因为 Hook 使用它们作为标识:
```typescript
// ✅ 正确
const tableColumns = [
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' }
]
// ❌ 错误:两个列都没有 prop
const tableColumns = [
{ label: '姓名' },
{ label: '年龄' }
]
```
### 2. 响应式更新
`tableColumns` 应该是响应式的,如果需要在运行时动态修改列配置:
```typescript
const tableColumns = ref([
{ prop: 'name', label: '姓名' },
{ prop: 'age', label: '年龄' }
])
// 动态添加列
tableColumns.value.push({ prop: 'phone', label: '电话' })
```
### 3. 配置存储位置
- **本地存储**:使用 `localStorage`key 为 `user-table-configs-all`
- **后端存储**:通过 API 同步key 为 `user-table-configs`
- **页面存储**:每个页面的配置通过路由路径生成唯一 key
### 4. 配置加载时机
Hook 默认在组件挂载时自动加载配置。如果需要在数据加载后重新加载:
```typescript
const { loadSavedConfig } = useTableColumnControl(tableColumns)
watch(() => someData.value, () => {
// 数据变化后重新加载配置
loadSavedConfig()
})
```
---
## 八、扩展方式
### 1. 添加列宽保存功能
如果需要保存列宽,可以扩展 Hook
```typescript
// 扩展 Hook
function useTableColumnControlWithWidth(tableColumns: TableColumn[]) {
const baseHook = useTableColumnControl(tableColumns)
const columnWidths = ref<Record<string, number>>({})
const handleColumnWidthChange = (prop: string, width: number) => {
columnWidths.value[prop] = width
// 保存到本地存储
const storageKey = getStorageKey()
const config = getTableConfigFromLocal(storageKey) || {}
config.columnWidths = columnWidths.value
saveTableConfigToLocal(storageKey, config)
}
return {
...baseHook,
columnWidths,
handleColumnWidthChange
}
}
```
### 2. 添加列固定功能
如果需要保存列的固定状态:
```typescript
function useTableColumnControlWithFixed(tableColumns: TableColumn[]) {
const baseHook = useTableColumnControl(tableColumns)
const fixedColumns = ref<string[]>([])
const toggleColumnFixed = (prop: string) => {
const index = fixedColumns.value.indexOf(prop)
if (index > -1) {
fixedColumns.value.splice(index, 1)
} else {
fixedColumns.value.push(prop)
}
// 保存配置
}
return {
...baseHook,
fixedColumns,
toggleColumnFixed
}
}
```
### 3. 添加列分组功能
如果需要按分组管理列:
```typescript
function useTableColumnControlWithGroup(
tableColumns: TableColumn[],
groups: Record<string, string[]>
) {
const baseHook = useTableColumnControl(tableColumns)
const getColumnsByGroup = (groupName: string) => {
return tableColumns.filter(col =>
groups[groupName]?.includes(col.prop || '')
)
}
return {
...baseHook,
getColumnsByGroup
}
}
```
### 4. 自定义存储策略
如果需要使用不同的存储策略(如 IndexedDB
```typescript
function useTableColumnControlWithCustomStorage(
tableColumns: TableColumn[],
storageAdapter: {
get: (key: string) => Promise<any>
set: (key: string, value: any) => Promise<void>
}
) {
// 实现自定义存储逻辑
}
```
---
## 九、常见问题
### Q1: 为什么配置没有保存?
**A:** 检查以下几点:
1. 确保 `handleColumnChange``handleColumnOrderChange` 已正确绑定到组件
2. 检查浏览器控制台是否有错误
3. 确认 API 调用是否成功(检查网络请求)
### Q2: 为什么某些列无法隐藏?
**A:** 检查列配置中是否设置了 `alwaysShow: true``fixed` 属性:
```typescript
// 这些列无法隐藏
{ prop: 'action', label: '操作', alwaysShow: true }
{ prop: 'id', label: 'ID', fixed: 'left' }
```
### Q3: 如何重置列配置?
**A:** 可以手动清除本地存储或调用 API 删除配置:
```typescript
// 清除本地存储
localStorage.removeItem('user-table-configs-all')
// 或通过 API 删除
import { deleteUserTableConfig } from '/@/api/admin/usertable'
deleteUserTableConfig(storageKey)
```
### Q4: 如何在多个页面共享列配置?
**A:** 使用相同的 `storageKey`
```typescript
const { visibleColumns } = useTableColumnControl(tableColumns, {
storageKey: 'shared-table-columns'
})
```
---
## 十、最佳实践
### 1. 列配置管理
将列配置提取到单独的文件中,便于维护:
```typescript
// src/views/xxx/columns.ts
import { User, Calendar } from '@element-plus/icons-vue'
export const tableColumns = [
{ prop: 'name', label: '姓名', icon: User },
{ prop: 'age', label: '年龄', icon: Calendar }
]
```
### 2. 类型定义
为列配置添加类型约束:
```typescript
import type { TableColumn } from '/@/hooks/tableColumn'
const tableColumns: TableColumn[] = [
{ prop: 'name', label: '姓名', icon: User }
]
```
### 3. 统一命名
保持列 `prop` 与后端字段名一致,便于数据处理。
### 4. 性能优化
对于大量列的表格,考虑使用虚拟滚动或分页加载列配置。
---
## 十一、迁移指南
### 从旧实现迁移到 Hook
**旧代码(~90 行):**
```typescript
const visibleColumns = ref<string[]>([])
const columnOrder = ref<string[]>([])
const loadSavedConfig = () => { /* ... */ }
const handleColumnChange = () => { /* ... */ }
// ... 更多代码
```
**新代码(~5 行):**
```typescript
import { useTableColumnControl } from '/@/hooks/tableColumn'
const {
visibleColumns,
visibleColumnsSorted,
checkColumnVisible,
handleColumnChange,
handleColumnOrderChange
} = useTableColumnControl(tableColumns)
```
### 迁移步骤
1. 引入 Hook
2. 删除旧的列控制逻辑(`loadSavedConfig``handleColumnChange` 等)
3. 使用 Hook 返回值替换原有变量
4. 测试功能是否正常
---
## 十二、更新日志
### v1.0.0 (2024-01-XX)
- ✅ 初始版本发布
- ✅ 支持列显示/隐藏控制
- ✅ 支持列排序管理
- ✅ 支持本地存储和服务器同步
- ✅ 支持自动加载配置
---
## 十三、相关资源
- **Hook 源码**`src/hooks/tableColumn.ts`
- **API 接口**`src/api/admin/usertable.ts`
- **组件源码**`src/components/TableColumnControl/index.vue`
- **示例页面**`src/views/stuwork/classmasterresume/index.vue`
---
## 十四、贡献指南
如果发现 bug 或有改进建议,请:
1. 提交 Issue 描述问题
2. 提交 Pull Request 提供修复方案
3. 更新本文档说明变更
---
**最后更新2024-01-XX**