|
|
@@ -665,6 +665,7 @@ const mutation = useMutation({
|
|
|
```
|
|
|
|
|
|
### 3. 分页查询
|
|
|
+#### 标准分页(useQuery)
|
|
|
```typescript
|
|
|
const useUserList = (page: number, pageSize: number = 10) => {
|
|
|
return useQuery({
|
|
|
@@ -680,6 +681,159 @@ const useUserList = (page: number, pageSize: number = 10) => {
|
|
|
}
|
|
|
```
|
|
|
|
|
|
+#### 移动端无限滚动分页(useInfiniteQuery)
|
|
|
+```typescript
|
|
|
+import { useInfiniteQuery } from '@tanstack/react-query'
|
|
|
+
|
|
|
+const useInfiniteUserList = (keyword?: string) => {
|
|
|
+ return useInfiniteQuery({
|
|
|
+ queryKey: ['users-infinite', keyword],
|
|
|
+ queryFn: async ({ pageParam = 1 }) => {
|
|
|
+ const response = await userClient.$get({
|
|
|
+ query: {
|
|
|
+ page: pageParam,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取用户列表失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ },
|
|
|
+ getNextPageParam: (lastPage, allPages) => {
|
|
|
+ const totalPages = Math.ceil(lastPage.pagination.total / lastPage.pagination.pageSize)
|
|
|
+ const nextPage = allPages.length + 1
|
|
|
+ return nextPage <= totalPages ? nextPage : undefined
|
|
|
+ },
|
|
|
+ staleTime: 5 * 60 * 1000,
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// 使用示例
|
|
|
+const {
|
|
|
+ data,
|
|
|
+ isLoading,
|
|
|
+ isFetchingNextPage,
|
|
|
+ fetchNextPage,
|
|
|
+ hasNextPage,
|
|
|
+ refetch
|
|
|
+} = useInfiniteUserList(searchKeyword)
|
|
|
+
|
|
|
+// 合并所有分页数据
|
|
|
+const allUsers = data?.pages.flatMap(page => page.data) || []
|
|
|
+
|
|
|
+// 触底加载更多处理
|
|
|
+const handleScrollToLower = () => {
|
|
|
+ if (hasNextPage && !isFetchingNextPage) {
|
|
|
+ fetchNextPage()
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+#### 移动端分页页面模板
|
|
|
+```typescript
|
|
|
+// mini/src/pages/[无限滚动列表]/index.tsx
|
|
|
+import { View, ScrollView } from '@tarojs/components'
|
|
|
+import { useInfiniteQuery } from '@tanstack/react-query'
|
|
|
+import { goodsClient } from '@/api'
|
|
|
+import { InferResponseType } from 'hono'
|
|
|
+import Taro from '@tarojs/taro'
|
|
|
+
|
|
|
+type GoodsResponse = InferResponseType<typeof goodsClient.$get, 200>
|
|
|
+
|
|
|
+export default function InfiniteGoodsList() {
|
|
|
+ const [searchKeyword, setSearchKeyword] = useState('')
|
|
|
+
|
|
|
+ const {
|
|
|
+ data,
|
|
|
+ isLoading,
|
|
|
+ isFetchingNextPage,
|
|
|
+ fetchNextPage,
|
|
|
+ hasNextPage,
|
|
|
+ refetch
|
|
|
+ } = useInfiniteQuery({
|
|
|
+ queryKey: ['goods-infinite', searchKeyword],
|
|
|
+ queryFn: async ({ pageParam = 1 }) => {
|
|
|
+ const response = await goodsClient.$get({
|
|
|
+ query: {
|
|
|
+ page: pageParam,
|
|
|
+ pageSize: 10,
|
|
|
+ keyword: searchKeyword
|
|
|
+ }
|
|
|
+ })
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取商品失败')
|
|
|
+ }
|
|
|
+ return response.json()
|
|
|
+ },
|
|
|
+ getNextPageParam: (lastPage) => {
|
|
|
+ const { pagination } = lastPage
|
|
|
+ const totalPages = Math.ceil(pagination.total / pagination.pageSize)
|
|
|
+ return pagination.current < totalPages ? pagination.current + 1 : undefined
|
|
|
+ },
|
|
|
+ staleTime: 5 * 60 * 1000,
|
|
|
+ })
|
|
|
+
|
|
|
+ // 合并所有分页数据
|
|
|
+ const allGoods = data?.pages.flatMap(page => page.data) || []
|
|
|
+
|
|
|
+ // 触底加载更多
|
|
|
+ const handleScrollToLower = () => {
|
|
|
+ if (hasNextPage && !isFetchingNextPage) {
|
|
|
+ fetchNextPage()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 下拉刷新
|
|
|
+ const onPullDownRefresh = () => {
|
|
|
+ refetch().finally(() => {
|
|
|
+ Taro.stopPullDownRefresh()
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return (
|
|
|
+ <ScrollView
|
|
|
+ className="h-screen"
|
|
|
+ scrollY
|
|
|
+ onScrollToLower={handleScrollToLower}
|
|
|
+ refresherEnabled
|
|
|
+ refresherTriggered={false}
|
|
|
+ onRefresherRefresh={onPullDownRefresh}
|
|
|
+ >
|
|
|
+ <View className="px-4 py-4">
|
|
|
+ {isLoading ? (
|
|
|
+ <View className="flex justify-center py-10">
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-8 h-8 text-blue-500" />
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ {allGoods.map((item) => (
|
|
|
+ <View key={item.id} className="bg-white rounded-lg p-4 mb-3">
|
|
|
+ <Text>{item.name}</Text>
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+
|
|
|
+ {isFetchingNextPage && (
|
|
|
+ <View className="flex justify-center py-4">
|
|
|
+ <View className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500" />
|
|
|
+ <Text className="ml-2 text-sm text-gray-500">加载更多...</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {!hasNextPage && allGoods.length > 0 && (
|
|
|
+ <View className="text-center py-4 text-sm text-gray-400">
|
|
|
+ 没有更多了
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+ </ScrollView>
|
|
|
+ )
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
## 表单处理规范
|
|
|
|
|
|
### 1. 表单Schema定义
|