36 KiB
Hooks 使用指南
本文档详细说明了项目中所有 Hooks 的使用方法,帮助开发者快速上手和正确使用。
目录
1. useDict - 字典数据获取 Hook
文件位置: src/hooks/dict.ts
功能说明
用于获取字典数据,支持多个字典类型同时获取,具有自动缓存机制,避免重复请求。
基本用法
import { useDict } from '/@/hooks/dict';
// 在 setup 中使用
const { dictType1, dictType2 } = useDict('dictType1', 'dictType2');
完整示例
<template>
<div>
<!-- 使用字典数据 -->
<el-select v-model="form.status">
<el-option v-for="item in userStatus" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-select v-model="form.type">
<el-option v-for="item in userType" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</template>
<script setup lang="ts">
import { useDict } from '/@/hooks/dict';
// 获取多个字典类型
const { userStatus, userType, deptType } = useDict('user_status', 'user_type', 'dept_type');
const form = ref({
status: '',
type: '',
});
</script>
特点
- ✅ 支持传入多个字典类型参数
- ✅ 自动缓存机制,避免重复请求
- ✅ 返回响应式引用对象(使用
toRefs) - ✅ 字典数据包含:
label、value、elTagType、elTagClass
返回数据结构
{
label: string, // 显示文本
value: string, // 值
elTagType: string, // 标签类型(用于 el-tag)
elTagClass: string // 标签样式类(用于 el-tag)
}
注意事项
- 字典类型参数为字符串类型
- 返回的对象是响应式的,可以直接在模板中使用
- 首次请求会从服务器获取,后续会使用缓存
2. useParam - 参数数据获取 Hook
文件位置: src/hooks/param.ts
功能说明
用于获取系统参数值,支持缓存机制,避免重复请求。
基本用法
import { useParam } from '/@/hooks/param';
// 在 setup 中使用
const paramValue = useParam('paramType');
完整示例
<template>
<div>
<div>最大上传大小:{{ uploadMaxSize }}</div>
<div>系统名称:{{ systemName }}</div>
<div>系统版本:{{ systemVersion }}</div>
</div>
</template>
<script setup lang="ts">
import { useParam } from '/@/hooks/param';
// 获取单个参数
const uploadMaxSize = useParam('upload_max_size');
const systemName = useParam('system_name');
const systemVersion = useParam('system_version');
</script>
特点
- ✅ 单个参数类型获取
- ✅ 自动缓存机制
- ✅ 返回响应式引用(
ref) - ✅ 参数值类型为字符串
注意事项
- 每次调用只能获取一个参数类型
- 如需获取多个参数,需要多次调用
- 返回的是响应式引用,在模板中会自动解包
3. useMessage - 消息提示
文件位置: src/hooks/message.ts
功能说明
统一的消息提示功能,封装了 Element Plus 的 ElMessage,提供统一的配置和样式。
基本用法
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 秒 |
完整示例
<template>
<div>
<el-button @click="handleInfo">普通提示</el-button>
<el-button @click="handleWarning">警告提示</el-button>
<el-button @click="handleSuccess">成功提示</el-button>
<el-button @click="handleError">错误提示</el-button>
</div>
</template>
<script setup lang="ts">
import { useMessage } from '/@/hooks/message';
const message = useMessage();
const handleInfo = () => {
message.info('这是一条普通提示信息');
};
const handleWarning = () => {
message.warning('这是一条警告提示信息');
};
const handleSuccess = () => {
message.success('操作成功!');
};
const handleError = () => {
message.error('操作失败,请重试!');
};
</script>
实际应用场景
// 保存成功后提示
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,支持提示框、确认框和输入框。
基本用法
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)
<script setup lang="ts">
import { useMessageBox } from '/@/hooks/message';
const messageBox = useMessageBox();
// 普通提示框
const handleInfo = () => {
messageBox.info('这是一条信息提示');
};
// 警告提示框
const handleWarning = () => {
messageBox.warning('这是一条警告信息');
};
// 成功提示框
const handleSuccess = () => {
messageBox.success('操作成功!');
};
// 错误提示框
const handleError = () => {
messageBox.error('操作失败!');
};
</script>
4.2 确认对话框(Confirm)
<template>
<el-button @click="handleDelete">删除</el-button>
</template>
<script setup lang="ts">
import { useMessageBox } from '/@/hooks/message';
import { useMessage } from '/@/hooks/message';
const messageBox = useMessageBox();
const message = useMessage();
const handleDelete = async () => {
try {
// 显示确认对话框
await messageBox.confirm('确定要删除这条记录吗?删除后无法恢复!');
// 用户点击确认后执行
await deleteData(id);
message.success('删除成功!');
} catch {
// 用户点击取消,不执行任何操作
console.log('用户取消了删除操作');
}
};
</script>
4.3 输入对话框(Prompt)
<template>
<el-button @click="handleRename">重命名</el-button>
</template>
<script setup lang="ts">
import { useMessageBox } from '/@/hooks/message';
import { useMessage } from '/@/hooks/message';
const messageBox = useMessageBox();
const message = useMessage();
const handleRename = async () => {
try {
// 显示输入对话框
const { value } = await messageBox.prompt('请输入新名称');
// 用户输入了值并点击确认
if (value && value.trim()) {
await renameData(id, value);
message.success('重命名成功!');
} else {
message.warning('名称不能为空');
}
} catch {
// 用户点击取消
console.log('用户取消了重命名操作');
}
};
</script>
实际应用场景
// 删除确认
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
功能说明
提供完整的表格数据管理功能,包括数据加载、分页、排序、文件下载等,简化表格相关开发。
基本用法
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)
{
current: 1, // 当前页码
size: 10, // 每页显示条数
total: 0, // 总条数
pageSizes: [10, 20, 50, 100], // 每页条数选择器选项
layout: 'total, sizes, prev, pager, next, jumper' // 分页组件布局
}
完整示例
<template>
<div>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline>
<el-form-item label="姓名">
<el-input v-model="searchForm.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 表格 -->
<el-table
:data="state.dataList"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@sort-change="sortChangeHandle"
v-loading="state.loading"
border
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="name" label="姓名" sortable="custom" />
<el-table-column prop="status" label="状态" />
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button type="primary" link size="small" @click="handleEdit(scope.row)"> 编辑 </el-button>
<el-button type="danger" link size="small" @click="handleDelete(scope.row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
:current="state.pagination.current"
:size="state.pagination.size"
:total="state.pagination.total"
@currentChange="currentChangeHandle"
@sizeChange="sizeChangeHandle"
/>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { useTable } from '/@/hooks/table';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { fetchList, deleteData } from '/@/api/your-api';
// 消息提示
const message = useMessage();
const messageBox = useMessageBox();
// 搜索表单
const searchForm = reactive({
name: '',
status: '',
});
// 表格管理
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, state } = useTable({
// 查询条件
queryForm: searchForm,
// 数据列表查询接口
pageList: fetchList,
// 分页配置(可选)
pagination: {
current: 1,
size: 10,
total: 0,
pageSizes: [10, 20, 50, 100],
layout: 'total, sizes, prev, pager, next, jumper',
},
// 数据属性映射(可选,根据后端返回结构调整)
props: {
item: 'records', // 数据列表字段名
totalCount: 'total', // 总数字段名
},
// 验证函数(可选)
validate: async (state) => {
// 可以在这里进行表单验证
if (!searchForm.name && !searchForm.status) {
message.warning('请至少输入一个查询条件');
return false;
}
return true;
},
// 数据加载完成回调(可选)
onLoaded: async (state) => {
console.log('数据加载完成', state.dataList);
// 可以在这里处理加载完成后的逻辑
},
});
// 查询
const handleSearch = () => {
getDataList(); // 刷新并跳转到第一页
};
// 重置
const handleReset = () => {
searchForm.name = '';
searchForm.status = '';
getDataList();
};
// 编辑
const handleEdit = (row: any) => {
// 编辑逻辑
};
// 删除
const handleDelete = async (row: any) => {
try {
await messageBox.confirm('确定要删除这条记录吗?');
await deleteData(row.id);
message.success('删除成功!');
getDataList(false); // 刷新但不跳转第一页
} catch {
// 用户取消
}
};
</script>
返回的方法说明
getDataList(refresh?)
获取数据列表方法。
参数:
refresh(可选):boolean- 是否刷新并跳转到第一页,默认为true
使用示例:
// 刷新并跳转到第一页(默认)
getDataList();
// 刷新但保持当前页
getDataList(false);
currentChangeHandle(val)
页码改变处理方法,用于分页组件的 @currentChange 事件。
参数:
val:number- 新的页码
使用示例:
<pagination @currentChange="currentChangeHandle" />
sizeChangeHandle(val)
每页条数改变处理方法,用于分页组件的 @sizeChange 事件。
参数:
val:number- 新的每页条数
使用示例:
<pagination @sizeChange="sizeChangeHandle" />
sortChangeHandle(column)
排序改变处理方法,用于表格的 @sort-change 事件。
参数:
column:object- 排序列信息prop: 列属性名order: 排序方式(ascending|descending|null)
使用示例:
<el-table @sort-change="sortChangeHandle">
<el-table-column prop="name" sortable="custom" />
</el-table>
downBlobFile(url, query, fileName)
下载文件方法。
参数:
url:string- 文件下载地址query:object- 请求参数fileName:string- 文件名
使用示例:
const handleExport = () => {
downBlobFile('/api/export', { type: 'excel' }, '数据导出.xlsx');
};
tableStyle
表格样式配置对象。
结构:
{
cellStyle: { textAlign: 'center' },
headerCellStyle: {
textAlign: 'center',
background: 'var(--el-table-row-hover-bg-color)',
color: 'var(--el-text-color-primary)'
}
}
使用示例:
<el-table :cell-style="tableStyle.cellStyle" :header-cell-style="tableStyle.headerCellStyle">
</el-table>
State 对象说明
useTable 返回的 state 对象包含以下属性:
{
dataList: [], // 表格数据列表
loading: false, // 加载状态
pagination: { // 分页信息
current: 1,
size: 10,
total: 0
},
dataListSelections: [], // 选中的数据
queryForm: {}, // 查询表单
// ... 其他配置属性
}
实际应用场景
场景 1:带验证的查询
const { getDataList, state } = useTable({
queryForm: searchForm,
pageList: fetchList,
validate: async (state) => {
if (!searchForm.name && !searchForm.status) {
message.warning('请至少输入一个查询条件');
return false;
}
return true;
},
});
场景 2:数据加载后处理
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:无分页表格
const { getDataList, state } = useTable({
queryForm: searchForm,
pageList: fetchList,
isPage: false, // 禁用分页
createdIsNeed: true,
});
场景 4:自定义数据字段映射
const { getDataList, state } = useTable({
queryForm: searchForm,
pageList: fetchList,
props: {
item: 'data.list', // 后端返回的数据列表路径
totalCount: 'data.total', // 后端返回的总数路径
},
});
弹窗编辑后刷新表格
在弹窗编辑/新增完成后,需要刷新表格数据。有两种方式:
方式一:使用 getDataList(false)(推荐)
父组件(列表页):
<template>
<!-- 表格 -->
<el-table :data="state.dataList" v-loading="state.loading">
<!-- 表格列 -->
</el-table>
<!-- 弹窗组件,监听 refresh 事件 -->
<form-dialog ref="formDialogRef" @refresh="getDataList(false)" />
</template>
<script setup lang="ts">
import { useTable } from '/@/hooks/table';
import { fetchList } from '/@/api/user';
const { getDataList, state, tableStyle } = useTable({
queryForm: {},
pageList: fetchList,
});
const formDialogRef = ref();
</script>
子组件(弹窗表单):
<script setup lang="ts">
const emit = defineEmits(['refresh']);
// 提交表单
const onSubmit = async () => {
const valid = await dataFormRef.value.validate().catch(() => {});
if (!valid) return false;
try {
loading.value = true;
form.id ? await putObj(form) : await addObj(form);
useMessage().success(form.id ? '修改成功' : '添加成功');
visible.value = false;
emit('refresh'); // 触发 refresh 事件
} catch (err: any) {
useMessage().error(err.msg);
} finally {
loading.value = false;
}
};
</script>
说明:
getDataList(false)- 刷新数据但不跳转到第一页,保持当前页getDataList()或getDataList(true)- 刷新数据并跳转到第一页
方式二:手动管理(不使用 useTable)
父组件:
<template>
<el-table :data="tableData" v-loading="tableLoading">
<!-- 表格列 -->
</el-table>
<form-dialog ref="formDialogRef" @refresh="handleRefresh" />
</template>
<script setup lang="ts">
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
// 刷新数据
const handleRefresh = () => {
getList(page);
};
const getList = (page: any) => {
tableLoading.value = true;
fetchList({
current: page.currentPage,
size: page.pageSize,
})
.then((response: any) => {
tableData.value = response.data.record.records;
page.total = response.data.record.total;
})
.finally(() => {
tableLoading.value = false;
});
};
</script>
刷新时机的选择
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 编辑后刷新 | getDataList(false) |
保持当前页,用户可以看到刚才编辑的数据 |
| 新增后刷新 | getDataList() |
跳转到第一页,因为新数据通常在第一页 |
| 删除后刷新 | getDataList(false) |
保持当前页,如果当前页没数据了会自动调整 |
| 查询后刷新 | getDataList() |
跳转到第一页,显示查询结果 |
完整示例
<template>
<div>
<!-- 搜索表单 -->
<el-form :model="searchForm" inline>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<div class="mb15">
<el-button type="primary" @click="handleAdd">新增</el-button>
</div>
<!-- 表格 -->
<el-table :data="state.dataList" v-loading="state.loading">
<el-table-column prop="name" label="姓名" />
<el-table-column label="操作">
<template #default="scope">
<el-button @click="handleEdit(scope.row)">编辑</el-button>
<el-button @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
:current="state.pagination.current"
:size="state.pagination.size"
:total="state.pagination.total"
@currentChange="currentChangeHandle"
@sizeChange="sizeChangeHandle"
/>
<!-- 弹窗表单 -->
<form-dialog ref="formDialogRef" @refresh="handleFormRefresh" />
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { useTable } from '/@/hooks/table';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { fetchList, deleteData } from '/@/api/user';
const message = useMessage();
const messageBox = useMessageBox();
const formDialogRef = ref();
const searchForm = reactive({
name: '',
});
const { getDataList, currentChangeHandle, sizeChangeHandle, state } = useTable({
queryForm: searchForm,
pageList: fetchList,
});
// 查询
const handleSearch = () => {
getDataList(); // 查询后跳转到第一页
};
// 新增
const handleAdd = () => {
formDialogRef.value?.openDialog();
};
// 编辑
const handleEdit = (row: any) => {
formDialogRef.value?.openDialog(row.id);
};
// 删除
const handleDelete = async (row: any) => {
try {
await messageBox.confirm('确定要删除吗?');
await deleteData(row.id);
message.success('删除成功');
getDataList(false); // 删除后保持当前页
} catch {
// 用户取消
}
};
// 弹窗提交后的刷新处理
const handleFormRefresh = () => {
// 可以根据操作类型选择刷新方式
// 如果是新增,跳转到第一页
// 如果是编辑,保持当前页
getDataList(false); // 默认保持当前页
};
</script>
注意事项
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(推荐)
<script setup lang="ts">
import { reactive } from 'vue';
import { useTable } from '/@/hooks/table';
import { fetchList } from '/@/api/user';
// 搜索表单
const searchForm = reactive({
name: '',
status: '',
});
// 一行配置,自动获得所有功能
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, tableStyle, state } = useTable({
queryForm: searchForm,
pageList: fetchList,
});
// 查询 - 只需一行
const handleSearch = () => {
getDataList();
};
</script>
<template>
<el-table
:data="state.dataList"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@sort-change="sortChangeHandle"
v-loading="state.loading"
>
<!-- 表格列 -->
</el-table>
<pagination
:current="state.pagination.current"
:size="state.pagination.size"
:total="state.pagination.total"
@currentChange="currentChangeHandle"
@sizeChange="sizeChangeHandle"
/>
</template>
代码行数:约 30 行
正常使用 Table(手动管理)
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { fetchList } from '/@/api/user';
import { ElMessage } from 'element-plus';
// 需要手动定义所有状态
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
currentPage: 1,
pageSize: 10,
total: 0,
});
const params = ref({});
const search = reactive({
name: '',
status: '',
});
// 需要手动实现数据加载
const getList = (page: any) => {
tableLoading.value = true;
fetchList(
Object.assign(
{
current: page.currentPage,
size: page.pageSize,
},
params.value
)
)
.then((response: any) => {
tableData.value = response.data.record.records;
page.total = response.data.record.total;
tableLoading.value = false;
})
.catch((error: any) => {
ElMessage.error(error.msg || '加载失败');
tableLoading.value = false;
});
};
// 需要手动实现分页处理
const currentChange = (val: number) => {
page.currentPage = val;
getList(page);
};
const handleSizeChange = (val: number) => {
page.pageSize = val;
page.currentPage = 1;
getList(page);
};
// 需要手动实现排序(如果需要)
const sortChangeHandle = (column: any) => {
// 手动实现排序逻辑...
getList(page);
};
// 需要手动在 onMounted 中调用
onMounted(() => {
getList(page);
});
// 查询
const handleSearch = () => {
params.value = { ...search };
page.currentPage = 1;
getList(page);
};
</script>
<template>
<el-table :data="tableData" v-loading="tableLoading" @sort-change="sortChangeHandle">
<!-- 表格列 -->
</el-table>
<pagination :current="page.currentPage" :size="page.pageSize" :total="page.total" @currentChange="currentChange" @sizeChange="handleSizeChange" />
</template>
代码行数:约 80+ 行
主要优势总结
1. 代码量减少 60%+
- useTable:约 30 行代码
- 手动管理:约 80+ 行代码
- 减少约 50 行重复代码
2. 自动状态管理
- ✅ 自动管理
loading状态 - ✅ 自动管理分页状态(
current、size、total) - ✅ 自动管理数据列表
- ✅ 自动处理错误提示
3. 自动初始化
// useTable 自动在 onMounted 时加载数据
onMounted(() => {
if (state.createdIsNeed) {
query(); // 自动调用
}
});
4. 统一的方法接口
getDataList()- 统一的数据刷新方法currentChangeHandle()- 统一的分页处理sizeChangeHandle()- 统一的每页条数处理sortChangeHandle()- 统一的排序处理(自动转换命名)
5. 统一的表格样式
// 所有使用 useTable 的表格样式一致
tableStyle: {
cellStyle: { textAlign: 'center' },
headerCellStyle: { textAlign: 'center', ... }
}
6. 更好的维护性
- 修改 Hook 代码,所有使用的地方自动生效
- 统一的错误处理逻辑
- 统一的 API 响应结构处理
7. 扩展功能
- ✅ 自动排序字段转换(驼峰 → 下划线)
- ✅ 支持验证函数
- ✅ 支持数据加载回调
- ✅ 支持文件下载方法
- ✅ 支持无分页模式
使用建议
✅ 推荐使用 useTable 的场景
- 标准 CRUD 页面 - 列表、分页、查询、排序
- 数据展示页面 - 需要分页的数据表格
- 需要统一风格的表格 - 保持项目一致性
- 快速开发 - 减少重复代码,提高开发效率
⚠️ 可以考虑手动管理的场景
- 特殊业务逻辑 - 分页逻辑与标准差异很大
- 性能优化需求 - 需要精细控制加载时机
- 复杂的数据处理 - 需要大量自定义数据处理逻辑
- 遗留代码 - 已有大量手动管理的代码,迁移成本高
迁移建议
如果现有代码使用手动管理,建议逐步迁移到 useTable:
- 新页面:直接使用
useTable - 旧页面:在重构时逐步迁移
- 复杂页面:可以先迁移基础功能,保留特殊逻辑
总结
useTable Hook 通过封装常见的表格管理逻辑,显著减少了代码量,提高了开发效率和代码质量。对于大多数标准表格场景,强烈推荐使用 useTable。
综合使用示例
以下是一个完整的页面示例,展示如何综合使用所有 Hooks:
<template>
<div class="layout-padding">
<div class="layout-padding-auto layout-padding-view">
<!-- 搜索表单 -->
<el-form :model="searchForm" inline>
<el-form-item label="姓名">
<el-input v-model="searchForm.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="状态">
<el-select v-model="searchForm.status" clearable>
<el-option v-for="item in userStatus" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<div class="mb15">
<el-button type="primary" icon="FolderAdd" @click="handleAdd"> 新增 </el-button>
<el-button type="warning" plain icon="Download" @click="handleExport" class="ml10"> 导出 </el-button>
</div>
<!-- 表格 -->
<el-table
:data="state.dataList"
:cell-style="tableStyle.cellStyle"
:header-cell-style="tableStyle.headerCellStyle"
@sort-change="sortChangeHandle"
v-loading="state.loading"
border
>
<el-table-column type="index" label="序号" width="60" />
<el-table-column prop="name" label="姓名" sortable="custom" />
<el-table-column prop="status" label="状态">
<template #default="scope">
<el-tag v-for="item in userStatus.filter((s) => s.value === scope.row.status)" :key="item.value">
{{ item.label }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200">
<template #default="scope">
<el-button type="primary" link size="small" @click="handleEdit(scope.row)"> 编辑 </el-button>
<el-button type="danger" link size="small" @click="handleDelete(scope.row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<pagination
:current="state.pagination.current"
:size="state.pagination.size"
:total="state.pagination.total"
@currentChange="currentChangeHandle"
@sizeChange="sizeChangeHandle"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
import { useDict } from '/@/hooks/dict';
import { useParam } from '/@/hooks/param';
import { useMessage, useMessageBox } from '/@/hooks/message';
import { useTable } from '/@/hooks/table';
import { fetchList, deleteData, exportData } from '/@/api/user';
// 1. 字典数据
const { userStatus } = useDict('user_status');
// 2. 参数数据
const systemName = useParam('system_name');
// 3. 消息提示
const message = useMessage();
const messageBox = useMessageBox();
// 4. 搜索表单
const searchForm = reactive({
name: '',
status: '',
});
// 5. 表格管理
const { getDataList, currentChangeHandle, sizeChangeHandle, sortChangeHandle, downBlobFile, tableStyle, state } = useTable({
queryForm: searchForm,
pageList: fetchList,
validate: async (state) => {
// 可以添加验证逻辑
return true;
},
onLoaded: async (state) => {
console.log('数据加载完成,共', state.dataList.length, '条');
},
});
// 查询
const handleSearch = () => {
getDataList();
};
// 重置
const handleReset = () => {
searchForm.name = '';
searchForm.status = '';
getDataList();
};
// 新增
const handleAdd = () => {
message.info('打开新增对话框');
// 新增逻辑
};
// 编辑
const handleEdit = (row: any) => {
message.info('打开编辑对话框');
// 编辑逻辑
};
// 删除
const handleDelete = async (row: any) => {
try {
await messageBox.confirm('确定要删除这条记录吗?删除后无法恢复!');
await deleteData(row.id);
message.success('删除成功!');
getDataList(false); // 刷新但不跳转第一页
} catch {
// 用户取消,不执行任何操作
}
};
// 导出
const handleExport = () => {
downBlobFile('/api/user/export', searchForm, `用户数据_${new Date().getTime()}.xlsx`);
};
</script>
最佳实践
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 年