diff --git a/docs/tableHook使用方式.md b/docs/tableHook使用方式.md new file mode 100644 index 0000000..591cf47 --- /dev/null +++ b/docs/tableHook使用方式.md @@ -0,0 +1,663 @@ +# useTableColumnControl Hook 使用文档 + +## 一、简介 + +`useTableColumnControl` 是一个用于统一管理表格列显示/隐藏和排序功能的 Vue 3 Composition API Hook。它封装了列配置的加载、保存、同步等逻辑,让开发者只需几行代码即可实现完整的表格列控制功能。 + +### 主要功能 +- ✅ 列显示/隐藏控制 +- ✅ 列排序管理 +- ✅ 配置自动保存到本地存储 +- ✅ 配置同步到后端服务器 +- ✅ 自动加载已保存的配置 +- ✅ 类型安全支持 + +--- + +## 二、安装和引入 + +### 文件位置 +``` +src/hooks/tableColumn.ts +``` + +### 引入方式 +```typescript +import { useTableColumnControl, type TableColumn } from '/@/hooks/tableColumn' +``` + +--- + +## 三、基础使用 + +### 1. 引入公共样式(推荐) + +首先在 ` +``` + +### 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 + +``` + +--- + +## 四、完整示例 + +```vue + + + +``` + +--- + +## 五、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` | 当前可见的列 key 数组 | +| `columnOrder` | `Ref` | 列排序顺序数组 | +| `visibleColumnsSorted` | `ComputedRef` | 排序后的可见列配置数组 | +| `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 + +``` + +--- + +## 七、注意事项 + +### 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>({}) + + 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([]) + + 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 +) { + 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 + set: (key: string, value: any) => Promise + } +) { + // 实现自定义存储逻辑 +} +``` + +--- + +## 九、常见问题 + +### 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([]) +const columnOrder = ref([]) +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** + diff --git a/src/assets/styles/modern-page.scss b/src/assets/styles/modern-page.scss new file mode 100644 index 0000000..f6b0ee9 --- /dev/null +++ b/src/assets/styles/modern-page.scss @@ -0,0 +1,252 @@ +/** + * 现代化页面布局公共样式 + * 用于统一的卡片式布局和表格样式 + * + * 使用方法: + * @import '/@/assets/styles/modern-page.scss'; + * + * 然后在模板中使用以下类名: + * - .modern-page-container: 页面容器 + * - .page-wrapper: 页面包装器 + * - .search-card: 搜索表单卡片 + * - .content-card: 内容卡片 + * - .modern-table: 现代化表格 + * - .search-form: 搜索表单 + */ + +// 页面容器 +.modern-page-container { + padding: 20px; + background: #f5f7fa; + min-height: calc(100vh - 84px); +} + +// 页面包装器 +.page-wrapper { + display: flex; + flex-direction: column; + gap: 20px; +} + +// 搜索卡片 +.search-card { + border-radius: 12px; + border: none; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04); + transition: all 0.3s ease; + + &:hover { + box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.08); + } + + :deep(.el-card__header) { + padding: 16px 20px; + border-bottom: 1px solid #f0f2f5; + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + } + + :deep(.el-card__body) { + padding: 20px; + } +} + +// 内容卡片 +.content-card { + border-radius: 12px; + border: none; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.04); + transition: all 0.3s ease; + + &:hover { + box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.08); + } + + :deep(.el-card__header) { + padding: 16px 20px; + border-bottom: 1px solid #f0f2f5; + background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%); + } + + :deep(.el-card__body) { + padding: 20px; + } +} + +// 卡片头部 +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.card-title { + display: flex; + align-items: center; + font-size: 16px; + font-weight: 600; + color: #303133; + + .title-icon { + margin-right: 8px; + color: var(--el-color-primary); + font-size: 18px; + } +} + +.header-actions { + display: flex; + align-items: center; + gap: 10px; +} + +// 搜索表单 - 保持原有宽度 +.search-form { + :deep(.el-form-item) { + margin-bottom: 18px; + margin-right: 20px; + } + + :deep(.el-form-item__label) { + font-weight: 500; + color: #606266; + } + + // 确保表单项宽度保持200px + :deep(.el-select), + :deep(.el-input) { + width: 200px !important; + } +} + +// 现代化表格 +.modern-table { + border-radius: 8px; + overflow: hidden; + + :deep(.el-table__header) { + th { + background: linear-gradient(135deg, #fafbfc 0%, #f5f7fa 100%); + color: #606266; + font-weight: 600; + border-bottom: 2px solid #e4e7ed; + padding: 14px 0; + } + } + + :deep(.el-table__body) { + tr { + transition: all 0.2s ease; + + &:hover { + background-color: #f5f7ff; + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + } + } + + td { + border-bottom: 1px solid #f0f2f5; + padding: 16px 0; + } + } + + :deep(.el-table__row) { + &:nth-child(even) { + background-color: #fafbfc; + } + } +} + +// 分页包装器 +.pagination-wrapper { + margin-top: 20px; + display: flex; + justify-content: flex-end; + padding-top: 16px; + border-top: 1px solid #f0f2f5; +} + +// 响应式设计 +@media (max-width: 768px) { + .modern-page-container { + padding: 12px; + } + + .page-wrapper { + gap: 12px; + } + + .search-card, + .content-card { + :deep(.el-card__header) { + padding: 12px 16px; + } + + :deep(.el-card__body) { + padding: 16px; + } + } + + .card-header { + flex-direction: column; + align-items: flex-start; + gap: 12px; + } + + .header-actions { + width: 100%; + justify-content: space-between; + } + + // 移动端保持表单宽度 + .search-form { + :deep(.el-form-item) { + margin-right: 0; + width: 100%; + } + + :deep(.el-select), + :deep(.el-input) { + width: 100% !important; + } + } +} + +// 动画效果 +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.search-card, +.content-card { + animation: fadeIn 0.3s ease-out; +} + +// 按钮样式优化 +:deep(.el-button) { + border-radius: 6px; + transition: all 0.2s ease; + + &:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + } + + &:active { + transform: translateY(0); + } +} + +// 表格空状态优化 +:deep(.el-empty) { + padding: 40px 0; +} + diff --git a/src/hooks/tableColumn.ts b/src/hooks/tableColumn.ts new file mode 100644 index 0000000..324ec98 --- /dev/null +++ b/src/hooks/tableColumn.ts @@ -0,0 +1,220 @@ +import { ref, computed, onMounted, nextTick } from 'vue' +import { useRoute } from 'vue-router' +import { getTableConfigFromLocal, saveTableConfigToLocal, updateUserTableConfig } from '/@/api/admin/usertable' + +/** + * 表格列配置类型 + */ +export 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 +} + +/** + * useTableColumnControl Hook + * + * 用于统一管理表格列的显示/隐藏和排序功能 + * + * @param tableColumns 表格列配置数组 + * @param options 可选配置项 + * @returns 返回列控制相关的状态和方法 + * + * @example + * ```ts + * const tableColumns = [ + * { prop: 'name', label: '姓名', icon: User }, + * { prop: 'age', label: '年龄', icon: Calendar } + * ] + * + * const { + * visibleColumns, + * visibleColumnsSorted, + * checkColumnVisible, + * handleColumnChange, + * handleColumnOrderChange + * } = useTableColumnControl(tableColumns) + * ``` + */ +export function useTableColumnControl( + tableColumns: TableColumn[], + options?: { + /** 是否在挂载时自动加载配置,默认为 true */ + autoLoad?: boolean + /** 自定义存储 key,默认使用路由路径 */ + storageKey?: string + } +) { + const route = useRoute() + const { autoLoad = true, storageKey: customStorageKey } = options || {} + + // 生成存储 key + const getStorageKey = () => { + if (customStorageKey) { + return customStorageKey + } + const routePath = route.path.replace(/^\//, '').replace(/\//g, '-') + return `table-columns-${routePath}` + } + + // 当前显示的列 + const visibleColumns = ref([]) + // 列排序顺序 + const columnOrder = ref([]) + + /** + * 从本地统一存储加载配置 + */ + const loadSavedConfig = () => { + const storageKey = getStorageKey() + const savedConfig = getTableConfigFromLocal(storageKey) + + // 获取所有有效的列 key + const validColumns = tableColumns + .filter(col => !col.alwaysShow && !col.fixed) + .map(col => col.prop || col.label || '') + .filter(Boolean) + + // 加载可见列配置 + if (savedConfig && savedConfig.visibleColumns) { + const filteredSaved = savedConfig.visibleColumns.filter((col: string) => validColumns.includes(col)) + visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns + } else { + visibleColumns.value = validColumns + } + + // 加载列排序配置 + if (savedConfig && savedConfig.columnOrder) { + columnOrder.value = savedConfig.columnOrder.filter((key: string) => validColumns.includes(key)) + // 补充缺失的列 + validColumns.forEach(key => { + if (!columnOrder.value.includes(key)) { + columnOrder.value.push(key) + } + }) + } else { + columnOrder.value = validColumns + } + } + + /** + * 列显示控制函数 + * @param prop 列的 prop 或 label + * @returns 是否可见 + */ + const checkColumnVisible = (prop: string): boolean => { + if (visibleColumns.value.length === 0) { + return true + } + return visibleColumns.value.includes(prop) + } + + /** + * 列变化处理 + * @param value 新的可见列数组 + */ + const handleColumnChange = (value: string[]) => { + visibleColumns.value = value + const storageKey = getStorageKey() + + // 过滤掉 alwaysShow 和 fixed 的列 + const selectableColumns = value.filter(col => { + const column = tableColumns.find(c => (c.prop || c.label) === col) + return column && !column.alwaysShow && !column.fixed + }) + + // 保存到本地统一存储 + saveTableConfigToLocal(storageKey, { visibleColumns: selectableColumns }) + // 异步保存到后端 + updateUserTableConfig(storageKey, { visibleColumns: selectableColumns }).catch(() => {}) + } + + /** + * 列排序变化处理 + * @param order 新的列排序数组 + */ + const handleColumnOrderChange = (order: string[]) => { + columnOrder.value = order + const storageKey = getStorageKey() + + // 保存到本地统一存储 + saveTableConfigToLocal(storageKey, { columnOrder: order }) + // 异步保存到后端 + updateUserTableConfig(storageKey, { columnOrder: order }).catch(() => {}) + } + + /** + * 排序后的表格列 + * 根据 visibleColumns 和 columnOrder 计算最终显示的列 + */ + const visibleColumnsSorted = computed(() => { + // 过滤出可见的列 + const columns = tableColumns.filter(col => { + const key = col.prop || col.label || '' + return visibleColumns.value.includes(key) + }) + + // 如果有排序配置,按排序顺序排列 + if (columnOrder.value.length > 0) { + const orderedColumns: TableColumn[] = [] + const unorderedColumns: TableColumn[] = [] + + // 先按保存的顺序添加列 + columnOrder.value.forEach(key => { + const col = columns.find(c => (c.prop || c.label) === key) + if (col) { + orderedColumns.push(col) + } + }) + + // 添加未在排序列表中的列(新增的列) + columns.forEach(col => { + const key = col.prop || col.label || '' + if (!columnOrder.value.includes(key)) { + unorderedColumns.push(col) + } + }) + + return [...orderedColumns, ...unorderedColumns] + } + + return columns + }) + + // 自动加载配置 + if (autoLoad) { + loadSavedConfig() + onMounted(() => { + nextTick(() => { + if (visibleColumns.value.length === 0) { + loadSavedConfig() + } + }) + }) + } + + return { + /** 当前可见的列 key 数组 */ + visibleColumns, + /** 列排序顺序 */ + columnOrder, + /** 排序后的可见列配置数组 */ + visibleColumnsSorted, + /** 检查列是否可见 */ + checkColumnVisible, + /** 列变化处理函数 */ + handleColumnChange, + /** 列排序变化处理函数 */ + handleColumnOrderChange, + /** 手动加载配置 */ + loadSavedConfig + } +} + diff --git a/src/views/basic/basicclass/index.vue b/src/views/basic/basicclass/index.vue index 1916da7..c2011f5 100644 --- a/src/views/basic/basicclass/index.vue +++ b/src/views/basic/basicclass/index.vue @@ -1,9 +1,17 @@ - + - - + +
+ +
+ @@ -262,8 +282,9 @@ import { useMessage, useMessageBox } from "/@/hooks/message"; import { fetchList as getRuleList } from "/@/api/stuwork/entrancerule"; import { downBlobFile, adaptationUrl } from "/@/utils/other"; import TableColumnControl from '/@/components/TableColumnControl/index.vue' -import { List, OfficeBuilding, Grid, Document, UserFilled, Phone, User, Lock, CircleCheck, TrendCharts, Setting, Menu } from '@element-plus/icons-vue' -import { getTableConfigFromLocal, saveTableConfigToLocal, updateUserTableConfig } from '/@/api/admin/usertable' +import { List, OfficeBuilding, Grid, Document, UserFilled, Phone, User, Lock, CircleCheck, TrendCharts, Setting, Menu, Search } from '@element-plus/icons-vue' +import { useTableColumnControl } from '/@/hooks/tableColumn' +import '/@/styles/modern-page.scss' import { defineAsyncComponent as defineStatusTag } from 'vue' const StatusTag = defineStatusTag(() => import('/@/components/StatusTag/index.vue')) @@ -299,94 +320,14 @@ const tableColumns = [ { prop: 'stuLoseRate', label: '流失率', icon: TrendCharts } ] -// 当前显示的列 -const visibleColumns = ref([]) -// 列排序顺序 -const columnOrder = ref([]) - -// 从本地统一存储加载配置 -const loadSavedConfig = () => { - const routePath = route.path.replace(/^\//, '').replace(/\//g, '-') - const storageKey = `table-columns-${routePath}` - const savedConfig = getTableConfigFromLocal(storageKey) - - if (savedConfig && savedConfig.visibleColumns) { - const validColumns = tableColumns.map(col => col.prop || col.label) - const filteredSaved = savedConfig.visibleColumns.filter((col: string) => validColumns.includes(col)) - visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns - } else { - visibleColumns.value = tableColumns.map(col => col.prop || col.label) - } - - if (savedConfig && savedConfig.columnOrder) { - const validColumns = tableColumns.map(col => col.prop || col.label) - columnOrder.value = savedConfig.columnOrder.filter((key: string) => validColumns.includes(key)) - validColumns.forEach(key => { - if (!columnOrder.value.includes(key)) { - columnOrder.value.push(key) - } - }) - } else { - columnOrder.value = tableColumns.map(col => col.prop || col.label) - } -} - -// 列变化处理 -const handleColumnChange = (value: string[]) => { - visibleColumns.value = value - const routePath = route.path.replace(/^\//, '').replace(/\//g, '-') - const storageKey = `table-columns-${routePath}` - const selectableColumns = value.filter(col => { - const column = tableColumns.find(c => (c.prop || c.label) === col) - return column && !column.alwaysShow && !column.fixed - }) - // 保存到本地统一存储 - saveTableConfigToLocal(storageKey, { visibleColumns: selectableColumns }) - // 异步保存到后端 - updateUserTableConfig(storageKey, { visibleColumns: selectableColumns }).catch(() => {}) -} - -// 列排序变化处理 -const handleColumnOrderChange = (order: string[]) => { - columnOrder.value = order - const routePath = route.path.replace(/^\//, '').replace(/\//g, '-') - const storageKey = `table-columns-${routePath}` - // 保存到本地统一存储 - saveTableConfigToLocal(storageKey, { columnOrder: order }) - // 异步保存到后端 - updateUserTableConfig(storageKey, { columnOrder: order }).catch(() => {}) -} - -// 排序后的表格列 -const visibleColumnsSorted = computed(() => { - const columns = tableColumns.filter(col => { - const key = col.prop || col.label - return visibleColumns.value.includes(key) - }) - - if (columnOrder.value.length > 0) { - const orderedColumns: any[] = [] - const unorderedColumns: any[] = [] - - columnOrder.value.forEach(key => { - const col = columns.find(c => (c.prop || c.label) === key) - if (col) { - orderedColumns.push(col) - } - }) - - columns.forEach(col => { - const key = col.prop || col.label - if (!columnOrder.value.includes(key)) { - unorderedColumns.push(col) - } - }) - - return [...orderedColumns, ...unorderedColumns] - } - - return columns -}) +// 使用表格列控制hook +const { + visibleColumns, + visibleColumnsSorted, + checkColumnVisible, + handleColumnChange, + handleColumnOrderChange +} = useTableColumnControl(tableColumns) // 搜索表单 const searchForm = reactive({ @@ -583,7 +524,6 @@ const getRuleListData = async () => { } // 初始化 -loadSavedConfig() onMounted(() => { getDeptListData() getClassListData() diff --git a/src/views/basic/basicstudent/index.vue b/src/views/basic/basicstudent/index.vue index 4a34d82..2cffe8c 100644 --- a/src/views/basic/basicstudent/index.vue +++ b/src/views/basic/basicstudent/index.vue @@ -1,9 +1,17 @@ - + - - + +
+ +
+ @@ -117,8 +137,9 @@ import { BasicTableProps, useTable } from "/@/hooks/table"; import { fetchList } from "/@/api/basic/basicstudentavatar"; import { getClassListByRole } from "/@/api/basic/basicclass"; import TableColumnControl from '/@/components/TableColumnControl/index.vue' -import { Picture, List, Document, UserFilled, Grid, Menu } from '@element-plus/icons-vue' -import { getTableConfigFromLocal, saveTableConfigToLocal, updateUserTableConfig } from '/@/api/admin/usertable' +import { Picture, List, Document, UserFilled, Grid, Menu, Search } from '@element-plus/icons-vue' +import { useTableColumnControl } from '/@/hooks/tableColumn' +import '/@/styles/modern-page.scss' // 定义变量内容 const route = useRoute() @@ -135,94 +156,14 @@ const tableColumns = [ { prop: 'headImg', label: '头像', icon: Picture, width: 120 } ] -// 当前显示的列 -const visibleColumns = ref([]) -// 列排序顺序 -const columnOrder = ref([]) - -// 从本地统一存储加载配置 -const loadSavedConfig = () => { - const routePath = route.path.replace(/^\//, '').replace(/\//g, '-') - const storageKey = `table-columns-${routePath}` - const savedConfig = getTableConfigFromLocal(storageKey) - - if (savedConfig && savedConfig.visibleColumns) { - const validColumns = tableColumns.map(col => col.prop || col.label) - const filteredSaved = savedConfig.visibleColumns.filter((col: string) => validColumns.includes(col)) - visibleColumns.value = filteredSaved.length > 0 ? filteredSaved : validColumns - } else { - visibleColumns.value = tableColumns.map(col => col.prop || col.label) - } - - if (savedConfig && savedConfig.columnOrder) { - const validColumns = tableColumns.map(col => col.prop || col.label) - columnOrder.value = savedConfig.columnOrder.filter((key: string) => validColumns.includes(key)) - validColumns.forEach(key => { - if (!columnOrder.value.includes(key)) { - columnOrder.value.push(key) - } - }) - } else { - columnOrder.value = tableColumns.map(col => col.prop || col.label) - } -} - -// 列变化处理 -const handleColumnChange = (value: string[]) => { - visibleColumns.value = value - const routePath = route.path.replace(/^\//, '').replace(/\//g, '-') - const storageKey = `table-columns-${routePath}` - const selectableColumns = value.filter(col => { - const column = tableColumns.find(c => (c.prop || c.label) === col) - return column && !column.alwaysShow && !column.fixed - }) - // 保存到本地统一存储 - saveTableConfigToLocal(storageKey, { visibleColumns: selectableColumns }) - // 异步保存到后端 - updateUserTableConfig(storageKey, { visibleColumns: selectableColumns }).catch(() => {}) -} - -// 列排序变化处理 -const handleColumnOrderChange = (order: string[]) => { - columnOrder.value = order - const routePath = route.path.replace(/^\//, '').replace(/\//g, '-') - const storageKey = `table-columns-${routePath}` - // 保存到本地统一存储 - saveTableConfigToLocal(storageKey, { columnOrder: order }) - // 异步保存到后端 - updateUserTableConfig(storageKey, { columnOrder: order }).catch(() => {}) -} - -// 排序后的表格列 -const visibleColumnsSorted = computed(() => { - const columns = tableColumns.filter(col => { - const key = col.prop || col.label - return visibleColumns.value.includes(key) - }) - - if (columnOrder.value.length > 0) { - const orderedColumns: any[] = [] - const unorderedColumns: any[] = [] - - columnOrder.value.forEach(key => { - const col = columns.find(c => (c.prop || c.label) === key) - if (col) { - orderedColumns.push(col) - } - }) - - columns.forEach(col => { - const key = col.prop || col.label - if (!columnOrder.value.includes(key)) { - unorderedColumns.push(col) - } - }) - - return [...orderedColumns, ...unorderedColumns] - } - - return columns -}) +// 使用表格列控制hook +const { + visibleColumns, + visibleColumnsSorted, + checkColumnVisible, + handleColumnChange, + handleColumnOrderChange +} = useTableColumnControl(tableColumns) // 搜索表单 const searchForm = reactive({ @@ -309,7 +250,6 @@ const getClassListData = async () => { } // 初始化 -loadSavedConfig() onMounted(() => { getClassListData() }) diff --git a/src/views/stuwork/activityawards/index.vue b/src/views/stuwork/activityawards/index.vue index 47980bf..2e76bae 100644 --- a/src/views/stuwork/activityawards/index.vue +++ b/src/views/stuwork/activityawards/index.vue @@ -1,112 +1,133 @@