❝
还在为每个页面写重复的增删查改代码而烦恼吗? 还在复制粘贴 currentPage、pageSize、loading 等状态吗? 一个 Hook 帮你解决所有分页痛点,减少90%重复代码
❞
背景与痛点
在后台管理系统开发中,分页列表查询非常常见,我们通常需要处理:
- 当前页、页大小、总数等分页状态
- 加载中、错误处理等请求状态
- 搜索、刷新、翻页等分页操作
- 数据缓存和重复请求处理
这些重复逻辑分散在各个组件中,维护起来很麻烦。
为了解决这个烦恼,我专门封装了分页数据管理 Hook。现在只需要几行代码,就能轻松实现分页查询,省时又高效,减少了大量重复劳动
使用前提 - 接口格式约定
查询接口返回的数据格式:
js
{
list: [ // 当前页数据数组
{ id: 1, name: 'user1' },
{ id: 2, name: 'user2' }
],
total: 100 // 数据总条数
}
这里一般都是这样定义的。除了这些主要定义增删查改接口API的 url、method的约定 将url分成几部:前缀(prefix)、页面名字(pageName)、后缀(suffix),后缀和method是可以约定好的,我们就可以封装到hooks中。
-
条件筛选 suffix:'pageList', method: 'get'
-
新增 suffix:'add', method: 'post'
-
删除 suffix:'delete', method: 'delete'
-
修改 suffix:'update', method: 'put'
-
详情查看 suffix:'get', method: 'get'
这些约定好就不用每次都写了
先看效果:一个页面就一百行代码
js
<template>
<Page>
<SPageSearch :formConfig="searchConfig"> </SPageSearch>
<div class="py-[13px] px-[15px]">
<SButtonGroup @add="open('add')"></SButtonGroup>
</div>
<SPageContent :contentConfig="pageConfig">
<template #action="{ row }">
<div class="flex justify-center">
<SButtonGroup size="small" @edit="open('edit', row.id)" @del="deleteRow(row.id)" :order="['edit', 'del']" />
</div>
</template>
</SPageContent>
<SPageDialog width="40vw" :formConfig="dialogFormConfig"> </SPageDialog>
</Page>
</template>
<script setup lang="ts">
const searchConfig = $defineFormConfig(() => {
return {
formItems: [
{
field: 'customer',
label: $t('basicInformation.customer'),
type: 'input',
config: {
placeholder: $t('basicInformation.customerPlaceholder'),
},
},
],
}
})
const { deleteRow, open } = $usePageHook(() => {
return {
title: $t('basicInformation.customerInformation'),
pageName: 'customer',
prefix: 'basic',
}
})
const pageConfig = $defineSTableConfig(() => {
return {
columns: [
{ type: 'seq', width: 70 },
{ field: 'customerName', title: $t('basicInformation.customerName') },
{ field: 'customerCode', title: $t('basicInformation.customerCode') },
{ field: 'remark', title: $t('basicInformation.describe') },
{ field: 'createUserName', title: $t('basicInformation.creator') },
{ field: 'createTime', title: $t('basicInformation.creationTime') },
{
field: 'action',
title: $t('common.operate'),
width: 200,
slots: { default: 'action' },
fixed: 'right',
},
],
}
})
const dialogFormConfig = $defineFormConfig(() => {
return {
colLayout: { span: 24 },
formItems: [
{
field: 'customerName',
label: $t('basicInformation.customerName'),
type: 'input',
formItemConfig: {
rules: [{ required: true, message: $t('basicInformation.customerName') + $t('common.rulehHint'), trigger: 'change' }],
},
},
{
field: 'customerCode',
label: $t('basicInformation.customerCode'),
type: 'input',
formItemConfig: {
rules: [{ required: true, message: $t('basicInformation.customerCode') + $t('common.rulehHint'), trigger: 'change' }],
},
},
{
field: 'remark',
label: $t('basicInformation.describe'),
type: 'textarea',
layout: { span: 24 },
},
],
}
})
</script>
<style scoped lang="scss"></style>

使用hooks
kotlin
const { deleteRow, open } = $usePageHook(() => {
return {
title: $t('basicInformation.customerInformation'),
pageName: 'customer',
prefix: 'basic',
}
})
这么几行代码完成了这个页面的所有的业务逻辑,在hook中使用注入的方式,再配合封装的组件,爽歪歪!
核心设计思想
1. 配置驱动设计
typescript
css
// 通过配置对象定义页面行为
const pageConfig = {
search: { /* 搜索配置 */ },
add: { /* 新增配置 */ },
edit: { /* 编辑配置 */ },
delete: { /* 删除配置 */ }
}
2. 约定优于配置
typescript
arduino
// 默认的 URL 路径约定
const defaultSuffix = {
search: 'pageList',
add: 'add',
edit: 'update',
delete: 'delete',
look: 'get'
}
架构分层
第一层:数据状态管理
typescript
csharp
// 核心响应式数据
const searchFormData = ref({}) // 搜索表单数据
const tableData = ref([]) // 表格数据
const formDialogData = ref({}) // 弹窗表单数据
const pagerConfig = reactive({}) // 分页配置
const dialogVisible = ref(false) // 弹窗显示状态
const loading = ref(false) // 加载状态
第二层:业务操作封装
typescript
scss
// 封装所有 CRUD 操作
const operations = {
search(), // 搜索
add(), // 新增
edit(), // 编辑
deleteRow(), // 删除
look(), // 查看详情
download() // 导出
}
第三层:生命周期和依赖注入
typescript
scss
// 提供依赖给子组件
provide('searchFormProvide', searchFormProvide)
provide('contentProvide', contentProvide)
provide('dialogProvide', dialogProvide)
关键技术实现
1. 智能的 Loading 管理
typescript
javascript
// 支持多种 loading 控制方式
const loadingProxy = new Proxy(() => {}, {
get: () => loadingCom.value, // loading.value 获取值
set: () => loadingInner.value = newValue, // loading.value = 1 设置值
apply: () => loadingProxy(1) // loading(1) 函数调用
})
// 计数式 loading,避免并发请求问题
function startLoading() { loadingNum.value += 1 }
function endLoading() { loadingNum.value -= 1 }
2. 灵活的请求封装
typescript
csharp
// 统一的请求创建函数
function creakSearchRequest(params, dataIn) {
return async (paramsData) => {
return await request[method](url, paramsData)
}
}
// 支持自定义 API 和默认 URL 生成
3. 钩子函数扩展点
typescript
javascript
// 前置处理钩子
before: (params) => {
// 可以修改请求参数
return modifiedParams
}
// 后置处理钩子
after: (res) => {
// 可以处理响应数据
return processedData
}
4. 类型安全的泛型设计
typescript
php
interface PageHookData {
prefix?: string
pageName?: string
search?: unitType & {
isValidate?: boolean
list?: string
total?: string
}
// ... 其他操作配置
}
使用方式
基本使用
typescript
javascript
const page = usePageHook(() => ({
pageName: 'user',
prefix: 'system',
search: {
after: (res) => {
return res.data.records
}
}
}))
复杂场景
typescript
javascript
const page = usePageHook(({ searchFormData, tableData }) => ({
// 自定义初始数据
searchFormData: { status: 1 },
tableData: [],
// 操作配置
search: {
before: (params) => {
// 请求前处理
return [modifiedParams]
},
after: (res) => {
// 响应后处理
return [res.data.list, res.data.total]
}
},
// 树形数据
tree: {
getData: async (inputValue) => {
return await getTreeData(inputValue)
}
}
}))
设计优势总结
这套分页管理 Hook 的优势:
1. 高度可配置
- 支持自定义 URL、方法、参数处理
- 灵活的钩子函数扩展
- 多种 loading 控制方式
2. 状态管理清晰
- 明确的数据流:搜索 → 请求 → 更新表格
- 统一的 loading 状态管理
- 弹窗状态与数据分离
3. 组件通信便捷
typescript
arduino
// 子组件通过 inject 获取功能
const { search, tableData } = inject('pageProvide')
4. 类型安全
完整的 TypeScript 类型定义,提供良好的开发体验。
5. 复用性强
通过配置即可适配不同的业务页面,减少重复代码。