useList 通用列表管理hook
概述
useList.hook.ts 是一个用于统一管理列表数据的加载、分页、筛选和搜索功能的通用hook。
核心功能
- 响应式状态管理 :使用
reactive管理筛选条件和分页配置 - 数据请求封装:统一处理 loading 状态、错误捕获和数据赋值
- 追加/替换模式 :通过
isPush参数支持普通分页和滚动追加加载两种场景 - 筛选重置:保存初始筛选条件,支持一键重置
详细代码
typescript
import { reactive, ref, toRaw } from 'vue'
export interface IPage {
pageNum: number
pageSize: number
total?: number
}
export interface GetListFnArgsType<T> {
pagination: IPage
filter: T
}
export default function useList<T, U extends Record<string, any>>(
filterOption: U,
getListFn: (args: GetListFnArgsType<U>) => Promise<InResult<{ records: Array<T>, total: number }>>, // InResult 类型定义请阅读uni.request 二次封装这篇文章
pageOption?: IPage,
) {
const list = ref<Array<T>>([])
const loading = ref(false)
const filter = reactive<U>({ ...filterOption })
const pagination = reactive<IPage>({
...pageOption,
pageSize: pageOption?.pageSize || 10,
pageNum: pageOption?.pageNum || 1,
})
// 请求列表数据
const loadData = async (curPage = pagination.pageNum, isPush = true) => {
pagination.pageNum = curPage
loading.value = true
const req = { pagination, filter: toRaw(filter) as U }
try {
const res = await getListFn(req as GetListFnArgsType<U>)
loading.value = false
if (!isPush) {
list.value = res.data.records as Array<any>
}
else {
list.value = [...list.value, ...res.data.records as Array<any>]
}
pagination.total = res.data.total
}
catch (error) {
loading.value = false
console.log(error)
}
}
// 重置筛选
const filterReset = () => {
Object.assign(filter, filterOption)
loadData(1, false)
}
// 搜索
const filterSearch = () => {
console.log(121212)
return loadData(1, false)
}
// 分页请求
const loadDataPage = (page: number, isPush = true) => {
return loadData(page, isPush)
}
// 分页大小改变
const loadDataPageSize = (pageSize: number) => {
pagination.pageSize = pageSize
loadData(1, false)
}
return {
list,
loading,
filter,
pagination,
loadData,
loadDataPage,
loadDataPageSize,
filterReset,
filterSearch,
}
}
类型定义
IPage
默认分页参数接口:
typescript
interface IPage {
pageNum: number // 当前页码
pageSize: number // 每页条数
total?: number // 总条数(响应数据填充)
}
GetListFnArgsType
列表请求参数类型:
typescript
interface GetListFnArgsType<T> {
pagination: IPage // 分页信息
filter: T // 筛选条件
}
返回值
| 属性 | 类型 | 说明 |
|---|---|---|
list |
Ref<Array<T>> |
列表数据 |
loading |
Ref<boolean> |
加载状态 |
filter |
Reactive<U> |
筛选条件(响应式) |
pagination |
Reactive<IPage> |
分页信息(响应式) |
loadData |
(curPage?: number, isPush?: boolean) => Promise<void> |
加载数据 |
loadDataPage |
(page: number, isPush?: boolean) => Promise<void> |
分页加载 |
loadDataPageSize |
(pageSize: number) => void |
改变每页条数 |
filterReset |
() => void |
重置筛选条件 |
filterSearch |
() => Promise<void> |
搜索(重置页码为1) |
方法详解
loadData(curPage, isPush)
加载列表数据:
- curPage : 当前页码,默认为
pagination.pageNum - isPush : 是否追加数据,
true表示追加(用于滚动加载),false表示替换(默认)
loadDataPage(page, isPush)
分页加载,调用 loadData 的封装。
loadDataPageSize(pageSize)
改变每页条数并重新加载第一页数据。
filterReset()
重置筛选条件为初始值,并重新加载第一页数据。
filterSearch()
执行搜索,重置页码为1并重新加载数据。
使用示例
typescript
import type { IEvent } from '@/api/task/type'
import { getEventList } from '@/api/task'
import useList, { type GetListFnArgsType } from '@/hooks/uesList.hook'
const filterOption = {
keyword: '',
status: '',
startTime: '',
endTime: '',
}
async function getListData(params: GetListFnArgsType<typeof filterOption>) {
const { pagination, filter } = params
const req = {
params: {
pageNo: pagination.pageNum,
pageSize: pagination.pageSize,
},
data: {
startTime: filter.startTime,
endTime: filter.endTime,
},
}
const res = await getEventList(req)
return res
/**
* 这里假设接口返回的数据格式不是 records 和 total
* 你可以在 getListData 中根据实际情况调整 records 和 total 的赋值,如:
* const res = await getEventList(req);
* return {data: { records: res.data.list, total: res.data.totalSize }}
*/
}
const { list, filter, pagination, filterSearch, loadDataPage } = useList<IEvent, typeof filterOption>(filterOption, getListData, { pageNum: 1, pageSize: 20 })
html
<template>
<cus-list v-model:keyword="filter.keyword" :refresh-func="filterSearch" :load-func="loadDataPage" :pagination="pagination" @search="filterSearch" @clear="filterSearch">
<template #serach-left>
<van-dropdown-menu style="--dropdown-menu-title-active-text-color: var(--uni-color-primary);--dropdown-menu-background-color: transparent;--dropdown-menu-title-text-color: var(--uni-text-color); --dropdown-menu-option-active-color: var(--uni-color-primary)" custom-class="min-w-[100rpx]">
<van-dropdown-item v-model:value="filter.siteId" :options="siteIdOptions" @change="onSiteChange" />
</van-dropdown-menu>
</template>
<template #search-filter="{ data }">
<view class="box-border h-full w-full p-2">
<view class="flex items-center gap-2">
<view>开始时间:</view>
<cus-date-picker v-model="filter.startTime" type="date" />
</view>
<view class="mt-2 flex items-center gap-2">
<view>结束时间:</view>
<cus-date-picker v-model="filter.endTime" type="date" />
</view>
<!-- <view>其它过滤条件开发中。。。</view> -->
<view class="mt-5 flex gap-2">
<van-button size="small" block class="flex-1" @click="onFilterReset(data)">
重置
</van-button>
<van-button type="primary" block size="small" class="flex-1" @click="onFilter(data)">
确定
</van-button>
</view>
</view>
</template>
<template #default>
<task-item v-for="item in list" :key="item.id" :record="item" />
</template>
</cus-list>
</template>