# Hooks 使用指南 本文档详细说明了项目中所有 Hooks 的使用方法,帮助开发者快速上手和正确使用。 ## 目录 1. [useDict - 字典数据获取](#1-usedict---字典数据获取-hook) 2. [useParam - 参数数据获取](#2-useparam---参数数据获取-hook) 3. [useMessage - 消息提示](#3-usemessage---消息提示) 4. [useMessageBox - 消息确认框](#4-usemessagebox---消息确认框) 5. [useTable - 表格数据管理](#5-usetable---表格数据管理-hook) --- ## 1. useDict - 字典数据获取 Hook **文件位置:** `src/hooks/dict.ts` ### 功能说明 用于获取字典数据,支持多个字典类型同时获取,具有自动缓存机制,避免重复请求。 ### 基本用法 ```typescript import { useDict } from '/@/hooks/dict'; // 在 setup 中使用 const { dictType1, dictType2 } = useDict('dictType1', 'dictType2'); ``` ### 完整示例 ```vue ``` ### 特点 - ✅ 支持传入多个字典类型参数 - ✅ 自动缓存机制,避免重复请求 - ✅ 返回响应式引用对象(使用 `toRefs`) - ✅ 字典数据包含:`label`、`value`、`elTagType`、`elTagClass` ### 返回数据结构 ```typescript { label: string, // 显示文本 value: string, // 值 elTagType: string, // 标签类型(用于 el-tag) elTagClass: string // 标签样式类(用于 el-tag) } ``` ### 注意事项 - 字典类型参数为字符串类型 - 返回的对象是响应式的,可以直接在模板中使用 - 首次请求会从服务器获取,后续会使用缓存 --- ## 2. useParam - 参数数据获取 Hook **文件位置:** `src/hooks/param.ts` ### 功能说明 用于获取系统参数值,支持缓存机制,避免重复请求。 ### 基本用法 ```typescript import { useParam } from '/@/hooks/param'; // 在 setup 中使用 const paramValue = useParam('paramType'); ``` ### 完整示例 ```vue ``` ### 特点 - ✅ 单个参数类型获取 - ✅ 自动缓存机制 - ✅ 返回响应式引用(`ref`) - ✅ 参数值类型为字符串 ### 注意事项 - 每次调用只能获取一个参数类型 - 如需获取多个参数,需要多次调用 - 返回的是响应式引用,在模板中会自动解包 --- ## 3. useMessage - 消息提示 **文件位置:** `src/hooks/message.ts` ### 功能说明 统一的消息提示功能,封装了 Element Plus 的 `ElMessage`,提供统一的配置和样式。 ### 基本用法 ```typescript import { useMessage } from '/@/hooks/message'; // 在 setup 中使用 const message = useMessage(); ``` ### 方法列表 | 方法 | 说明 | 显示时间 | | ------------------------ | -------- | -------- | | `info(title: string)` | 普通提示 | 3 秒 | | `warning(title: string)` | 警告提示 | 3 秒 | | `success(title: string)` | 成功提示 | 3 秒 | | `error(title: string)` | 错误提示 | 2 秒 | ### 完整示例 ```vue ``` ### 实际应用场景 ```typescript // 保存成功后提示 const handleSave = async () => { try { await saveData(formData); message.success('保存成功!'); } catch (error) { message.error('保存失败:' + error.message); } }; // 删除操作提示 const handleDelete = async () => { try { await deleteData(id); message.success('删除成功!'); } catch (error) { message.error('删除失败:' + error.message); } }; ``` ### 默认配置 - **显示时间:** 普通/警告/成功 3 秒,错误 2 秒 - **显示关闭按钮:** 是 - **距离顶部偏移:** 20px ### 注意事项 - 消息提示会自动消失 - 可以同时显示多条消息 - 错误提示显示时间较短,便于快速查看 --- ## 4. useMessageBox - 消息确认框 **文件位置:** `src/hooks/message.ts` ### 功能说明 统一的消息确认框功能,封装了 Element Plus 的 `ElMessageBox`,支持提示框、确认框和输入框。 ### 基本用法 ```typescript import { useMessageBox } from '/@/hooks/message'; // 在 setup 中使用 const messageBox = useMessageBox(); ``` ### 方法列表 | 方法 | 说明 | 返回值 | | ---------------------- | ---------- | ---------------------------- | | `info(msg: string)` | 普通提示框 | `void` | | `warning(msg: string)` | 警告提示框 | `void` | | `success(msg: string)` | 成功提示框 | `void` | | `error(msg: string)` | 错误提示框 | `void` | | `confirm(msg: string)` | 确认对话框 | `Promise` | | `prompt(msg: string)` | 输入对话框 | `Promise<{ value: string }>` | ### 完整示例 #### 4.1 提示框(Alert) ```vue ``` #### 4.2 确认对话框(Confirm) ```vue ``` #### 4.3 输入对话框(Prompt) ```vue ``` ### 实际应用场景 ```typescript // 删除确认 const handleDelete = async (id: number) => { try { await messageBox.confirm('确定要删除这条记录吗?'); await deleteApi(id); message.success('删除成功!'); getDataList(); } catch { // 用户取消,不执行任何操作 } }; // 批量删除确认 const handleBatchDelete = async () => { if (selectedIds.length === 0) { message.warning('请先选择要删除的记录'); return; } try { await messageBox.confirm(`确定要删除选中的 ${selectedIds.length} 条记录吗?`); await batchDeleteApi(selectedIds); message.success('批量删除成功!'); getDataList(); } catch { // 用户取消 } }; // 重置密码确认 const handleResetPassword = async (userId: number) => { try { await messageBox.confirm('确定要重置该用户的密码吗?'); const result = await resetPasswordApi(userId); messageBox.info(`重置后密码为:${result.data.password}`); } catch { // 用户取消 } }; ``` ### 特点 - ✅ 支持国际化(自动使用 i18n) - ✅ `confirm` 和 `prompt` 返回 Promise,便于链式调用 - ✅ 统一的按钮文本(确认/取消) - ✅ 自动处理用户取消操作 ### 注意事项 - `confirm` 和 `prompt` 返回 Promise,需要使用 `async/await` 或 `.then()/.catch()` 处理 - 用户点击取消会触发 Promise 的 `reject`,需要在 `catch` 中处理 - 提示框方法(`info`、`warning`、`success`、`error`)不返回 Promise --- ## 5. useTable - 表格数据管理 Hook **文件位置:** `src/hooks/table.ts` ### 功能说明 提供完整的表格数据管理功能,包括数据加载、分页、排序、文件下载等,简化表格相关开发。 ### 基本用法 ```typescript import { useTable } from '/@/hooks/table'; import { fetchList } from '/@/api/your-api'; const { getDataList, // 获取数据列表方法 currentChangeHandle, // 页码改变处理方法 sizeChangeHandle, // 每页条数改变处理方法 sortChangeHandle, // 排序改变处理方法 downBlobFile, // 下载文件方法 tableStyle, // 表格样式 } = useTable({ pageList: fetchList, queryForm: {}, }); ``` ### 配置选项 | 属性 | 类型 | 默认值 | 说明 | | --------------- | ------------ | ------------------------------------------ | ------------------------ | | `createdIsNeed` | `boolean` | `true` | 是否在创建时自动加载数据 | | `isPage` | `boolean` | `true` | 是否需要分页 | | `queryForm` | `any` | `{}` | 查询条件表单对象 | | `pageList` | `Function` | - | 数据列表查询接口(必填) | | `pagination` | `Pagination` | 见下方 | 分页配置 | | `props` | `object` | `{ item: 'records', totalCount: 'total' }` | 数据属性映射 | | `validate` | `Function` | - | 验证函数 | | `onLoaded` | `Function` | - | 数据加载完成回调 | | `onCascaded` | `Function` | - | 级联数据回调 | ### 分页配置(Pagination) ```typescript { current: 1, // 当前页码 size: 10, // 每页显示条数 total: 0, // 总条数 pageSizes: [10, 20, 50, 100], // 每页条数选择器选项 layout: 'total, sizes, prev, pager, next, jumper' // 分页组件布局 } ``` ### 完整示例 ```vue ``` ### 返回的方法说明 #### getDataList(refresh?) 获取数据列表方法。 **参数:** - `refresh` (可选): `boolean` - 是否刷新并跳转到第一页,默认为 `true` **使用示例:** ```typescript // 刷新并跳转到第一页(默认) getDataList(); // 刷新但保持当前页 getDataList(false); ``` #### currentChangeHandle(val) 页码改变处理方法,用于分页组件的 `@currentChange` 事件。 **参数:** - `val`: `number` - 新的页码 **使用示例:** ```vue ``` #### sizeChangeHandle(val) 每页条数改变处理方法,用于分页组件的 `@sizeChange` 事件。 **参数:** - `val`: `number` - 新的每页条数 **使用示例:** ```vue ``` #### sortChangeHandle(column) 排序改变处理方法,用于表格的 `@sort-change` 事件。 **参数:** - `column`: `object` - 排序列信息 - `prop`: 列属性名 - `order`: 排序方式(`ascending` | `descending` | `null`) **使用示例:** ```vue ``` #### downBlobFile(url, query, fileName) 下载文件方法。 **参数:** - `url`: `string` - 文件下载地址 - `query`: `object` - 请求参数 - `fileName`: `string` - 文件名 **使用示例:** ```typescript const handleExport = () => { downBlobFile('/api/export', { type: 'excel' }, '数据导出.xlsx'); }; ``` #### tableStyle 表格样式配置对象。 **结构:** ```typescript { cellStyle: { textAlign: 'center' }, headerCellStyle: { textAlign: 'center', background: 'var(--el-table-row-hover-bg-color)', color: 'var(--el-text-color-primary)' } } ``` **使用示例:** ```vue ``` ### State 对象说明 `useTable` 返回的 `state` 对象包含以下属性: ```typescript { dataList: [], // 表格数据列表 loading: false, // 加载状态 pagination: { // 分页信息 current: 1, size: 10, total: 0 }, dataListSelections: [], // 选中的数据 queryForm: {}, // 查询表单 // ... 其他配置属性 } ``` ### 实际应用场景 #### 场景 1:带验证的查询 ```typescript const { getDataList, state } = useTable({ queryForm: searchForm, pageList: fetchList, validate: async (state) => { if (!searchForm.name && !searchForm.status) { message.warning('请至少输入一个查询条件'); return false; } return true; }, }); ``` #### 场景 2:数据加载后处理 ```typescript const { getDataList, state } = useTable({ queryForm: searchForm, pageList: fetchList, onLoaded: async (state) => { // 数据加载完成后,处理字典转换 state.dataList = state.dataList.map((item) => { // 转换状态字典 const statusItem = statusDict.find((d) => d.value === item.status); item.statusName = statusItem?.label || ''; return item; }); }, }); ``` #### 场景 3:无分页表格 ```typescript const { getDataList, state } = useTable({ queryForm: searchForm, pageList: fetchList, isPage: false, // 禁用分页 createdIsNeed: true, }); ``` #### 场景 4:自定义数据字段映射 ```typescript const { getDataList, state } = useTable({ queryForm: searchForm, pageList: fetchList, props: { item: 'data.list', // 后端返回的数据列表路径 totalCount: 'data.total', // 后端返回的总数路径 }, }); ``` ### 弹窗编辑后刷新表格 在弹窗编辑/新增完成后,需要刷新表格数据。有两种方式: #### 方式一:使用 `getDataList(false)`(推荐) **父组件(列表页):** ```vue ``` **子组件(弹窗表单):** ```vue ``` **说明:** - `getDataList(false)` - 刷新数据但**不跳转到第一页**,保持当前页 - `getDataList()` 或 `getDataList(true)` - 刷新数据并**跳转到第一页** #### 方式二:手动管理(不使用 useTable) **父组件:** ```vue ``` ### 刷新时机的选择 | 场景 | 推荐方法 | 说明 | | -------------- | -------------------- | ---------------------------------------- | | **编辑后刷新** | `getDataList(false)` | 保持当前页,用户可以看到刚才编辑的数据 | | **新增后刷新** | `getDataList()` | 跳转到第一页,因为新数据通常在第一页 | | **删除后刷新** | `getDataList(false)` | 保持当前页,如果当前页没数据了会自动调整 | | **查询后刷新** | `getDataList()` | 跳转到第一页,显示查询结果 | ### 完整示例 ```vue ``` ### 注意事项 - `pageList` 方法是必填的,需要传入数据查询接口函数 - `queryForm` 会在请求时自动合并到请求参数中 - `state.loading` 会自动管理,无需手动设置 - 排序功能会自动将驼峰命名转换为下划线命名(如 `userName` → `user_name`) - 错误会自动通过 `ElMessage.error` 提示 - **弹窗提交成功后,记得 `emit('refresh')` 触发刷新事件** - **根据业务场景选择合适的刷新方式**(是否跳转第一页) --- ## 6. useTable vs 正常使用 Table 对比 ### 概述 `useTable` Hook 是对表格数据管理的封装,提供了统一的状态管理和方法,相比手动管理表格状态,具有明显的优势。 ### 对比表格 | 对比项 | 使用 useTable Hook | 正常使用 Table(手动管理) | | ---------------- | ------------------------------- | ---------------------------------------------- | | **代码量** | 少,配置化 | 多,需要手动编写大量代码 | | **状态管理** | 自动管理(分页、loading、数据) | 需要手动管理所有状态 | | **初始化** | 自动在 `onMounted` 时加载数据 | 需要手动在 `onMounted` 中调用 | | **分页处理** | 自动处理页码和每页条数变化 | 需要手动实现 `currentChange` 和 `sizeChange` | | **排序处理** | 自动处理排序,自动转换命名 | 需要手动实现排序逻辑 | | **错误处理** | 自动错误提示 | 需要手动 try-catch 和错误提示 | | **Loading 状态** | 自动管理 | 需要手动设置 `tableLoading.value = true/false` | | **数据刷新** | 统一方法 `getDataList()` | 需要手动调用 `getList(page)` | | **代码复用** | 高度复用,配置即可 | 每个页面都要重复编写 | | **维护性** | 集中维护,修改一处即可 | 分散在各处,修改困难 | | **统一性** | 所有表格行为一致 | 每个页面可能实现不同 | ### 代码对比示例 #### 使用 useTable Hook(推荐) ```vue ``` **代码行数:约 30 行** #### 正常使用 Table(手动管理) ```vue ``` **代码行数:约 80+ 行** ### 主要优势总结 #### 1. **代码量减少 60%+** - useTable:约 30 行代码 - 手动管理:约 80+ 行代码 - **减少约 50 行重复代码** #### 2. **自动状态管理** - ✅ 自动管理 `loading` 状态 - ✅ 自动管理分页状态(`current`、`size`、`total`) - ✅ 自动管理数据列表 - ✅ 自动处理错误提示 #### 3. **自动初始化** ```typescript // useTable 自动在 onMounted 时加载数据 onMounted(() => { if (state.createdIsNeed) { query(); // 自动调用 } }); ``` #### 4. **统一的方法接口** - `getDataList()` - 统一的数据刷新方法 - `currentChangeHandle()` - 统一的分页处理 - `sizeChangeHandle()` - 统一的每页条数处理 - `sortChangeHandle()` - 统一的排序处理(自动转换命名) #### 5. **统一的表格样式** ```typescript // 所有使用 useTable 的表格样式一致 tableStyle: { cellStyle: { textAlign: 'center' }, headerCellStyle: { textAlign: 'center', ... } } ``` #### 6. **更好的维护性** - 修改 Hook 代码,所有使用的地方自动生效 - 统一的错误处理逻辑 - 统一的 API 响应结构处理 #### 7. **扩展功能** - ✅ 自动排序字段转换(驼峰 → 下划线) - ✅ 支持验证函数 - ✅ 支持数据加载回调 - ✅ 支持文件下载方法 - ✅ 支持无分页模式 ### 使用建议 #### ✅ 推荐使用 useTable 的场景 1. **标准 CRUD 页面** - 列表、分页、查询、排序 2. **数据展示页面** - 需要分页的数据表格 3. **需要统一风格的表格** - 保持项目一致性 4. **快速开发** - 减少重复代码,提高开发效率 #### ⚠️ 可以考虑手动管理的场景 1. **特殊业务逻辑** - 分页逻辑与标准差异很大 2. **性能优化需求** - 需要精细控制加载时机 3. **复杂的数据处理** - 需要大量自定义数据处理逻辑 4. **遗留代码** - 已有大量手动管理的代码,迁移成本高 ### 迁移建议 如果现有代码使用手动管理,建议逐步迁移到 `useTable`: 1. **新页面**:直接使用 `useTable` 2. **旧页面**:在重构时逐步迁移 3. **复杂页面**:可以先迁移基础功能,保留特殊逻辑 ### 总结 `useTable` Hook 通过封装常见的表格管理逻辑,显著减少了代码量,提高了开发效率和代码质量。对于大多数标准表格场景,**强烈推荐使用 `useTable`**。 --- ## 综合使用示例 以下是一个完整的页面示例,展示如何综合使用所有 Hooks: ```vue ``` --- ## 最佳实践 ### 1. 字典数据使用 - ✅ 在 `setup` 顶层调用 `useDict`,确保响应式 - ✅ 字典数据可以直接在模板中使用 - ✅ 使用 `el-tag` 时可以利用 `elTagType` 和 `elTagClass` ### 2. 消息提示使用 - ✅ 操作成功使用 `message.success()` - ✅ 操作失败使用 `message.error()` - ✅ 需要用户确认的操作使用 `messageBox.confirm()` - ✅ 删除等危险操作必须使用确认框 ### 3. 表格管理使用 - ✅ 查询条件变化时调用 `getDataList()` 刷新数据 - ✅ 删除、新增等操作后调用 `getDataList(false)` 保持当前页 - ✅ 使用 `validate` 进行查询条件验证 - ✅ 使用 `onLoaded` 处理数据加载后的逻辑 ### 4. 错误处理 - ✅ 所有异步操作使用 `try-catch` 处理错误 - ✅ 使用 `message.error()` 提示错误信息 - ✅ 用户取消操作不需要提示错误 --- ## 更新记录 - **2024-XX-XX**: 创建 Hooks 使用指南文档 - 文档版本:v1.0 --- **维护者:** 前端开发团队 **最后更新:** 2024 年