637 lines
15 KiB
Markdown
637 lines
15 KiB
Markdown
# 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**
|