-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: usePaging and useInfiniteScroll
- Loading branch information
Showing
11 changed files
with
350 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
export * from './use-async-state'; | ||
export * from './use-copy'; | ||
export * from './use-count-down'; | ||
export * from './use-paging'; | ||
export * from './use-infinite-scroll'; | ||
|
||
export const version = process.env.npm_package_version; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
<script lang="ts" setup> | ||
import { useInfiniteScroll } from '@bowencool/vhooks'; | ||
import { ElEmpty, ElInfiniteScroll as vInfiniteScroll } from 'element-plus'; | ||
import { fakePagingService } from '../use-paging/service'; | ||
import { error2String } from '../use-async-state/demo/error2String'; | ||
const { list, loading, loadingMore, error, done, empty, loadMore, refreshing, refresh } = useInfiniteScroll( | ||
fakePagingService, | ||
{ | ||
defaultPageSize: 5, | ||
}, | ||
); | ||
</script> | ||
|
||
<template> | ||
<section | ||
v-infinite-scroll="loadMore" | ||
:infinite-scroll-disabled="done" | ||
:infinite-scroll-distance="50" | ||
:class="$style.infiniteList" | ||
> | ||
<div v-if="refreshing" class="text-center p-4">更新中...</div> | ||
<template v-if="list"> | ||
<div v-for="d in list" :key="d.id" :class="$style.infiniteListItem">{{ d.id }}</div> | ||
</template> | ||
<div class="text-center text-gray-666 py-4"> | ||
<div v-if="loadingMore">加载中...</div> | ||
<div v-else-if="error" key="error"> | ||
<span class="text-alert">{{ error2String(error) }}</span> | ||
</div> | ||
<ElEmpty v-else-if="empty" description="暂无数据"></ElEmpty> | ||
<span v-else-if="done">没有更多了</span> | ||
</div> | ||
</section> | ||
</template> | ||
|
||
<style module> | ||
.infiniteList { | ||
height: 300px; | ||
overflow: auto; | ||
overflow: overlay; | ||
} | ||
.infiniteListItem { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
height: 50px; | ||
background: var(--el-color-primary-light-9); | ||
margin: 10px; | ||
color: var(--el-color-primary); | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<script lang="ts" setup> | ||
import { useInfiniteScroll } from '@bowencool/vhooks'; | ||
import { fakePagingService } from '../use-paging/service'; | ||
const defaultPageSize = 5; | ||
const { list, loading, loadingMore, error, done, empty, loadMore, refreshing, refresh } = useInfiniteScroll( | ||
fakePagingService, | ||
{ | ||
defaultPageSize, | ||
isDone(res) { | ||
const currentQuantity: number = list.value?.length || 0; | ||
// 当前数量达到 total | ||
return currentQuantity >= res.total; | ||
// 当前响应数据不足一页 | ||
// return res.list.length < defaultPageSize; | ||
// 接口告诉你了 | ||
// return res.hasNextPage; | ||
// 默认 (不足一页 || 数量达到 total) | ||
}, | ||
}, | ||
); | ||
</script> | ||
|
||
<template> | ||
<button :disabled="loading" @click="loadMore">加载下一页</button> | ||
<button :disabled="loading" @click="refresh">刷新</button> | ||
<div v-if="refreshing">刷新中...</div> | ||
<template v-if="list"> | ||
<div v-for="d in list" :key="d.id" :class="$style.infiniteListItem">{{ d.id }}</div> | ||
</template> | ||
<div v-if="loadingMore">加载中...</div> | ||
<div v-else-if="error">{{ error }}</div> | ||
<div v-else-if="empty">暂无数据</div> | ||
<div v-else-if="done">没有更多了</div> | ||
</template> | ||
|
||
<style module> | ||
.infiniteListItem { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
height: 50px; | ||
background: var(--el-color-primary-light-9); | ||
margin: 10px; | ||
color: var(--el-color-primary); | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { computed, onMounted, reactive, ref } from 'vue'; | ||
import { throttleAsyncResult } from '@bowencool/async-utilities'; | ||
import type { PagingParamsDTO, PagingResultDTO, PagingService, UseAsyncStateOptions } from '@bowencool/vhooks'; | ||
import { useAsyncState } from '../use-async-state'; | ||
|
||
export type UseInfiniteScrollOptions<R> = UseAsyncStateOptions<PagingResultDTO<R>, [PagingParamsDTO /* & P */]> & { | ||
defaultPageSize?: number; | ||
isDone?: (res: PagingResultDTO<R>) => boolean; | ||
}; | ||
|
||
export function useInfiniteScroll<RowType>( | ||
pagingService: PagingService<RowType>, | ||
{ defaultPageSize: pageSize = 10, isDone, ...others }: UseInfiniteScrollOptions<RowType> = {}, | ||
) { | ||
const refreshing = ref(false); | ||
const done = ref(false); | ||
const list = ref<RowType[]>(); | ||
const { data, error, loading, run } = useAsyncState(pagingService, { | ||
// debounceResult: true, // 这个 data 没有用到,所以不需要 | ||
debounceInterval: 100, | ||
...others, | ||
manual: true, | ||
}); | ||
const state = reactive({ | ||
pageNumber: 1, | ||
}); | ||
|
||
// todo 正在加载下一页时,此时 refresh() 会失效,需要改进 | ||
const loadData = throttleAsyncResult(async function (isRefresh = false) { | ||
if (done.value && !isRefresh) return; | ||
const oldList = list.value || []; | ||
const offset = isRefresh ? 0 : oldList.length; | ||
if (isRefresh) { | ||
state.pageNumber = 1; | ||
refreshing.value = true; | ||
} | ||
|
||
const payload = { | ||
pageSize, | ||
pageNumber: state.pageNumber, | ||
offset, | ||
count: pageSize, | ||
}; | ||
console.log(`loadData${isRefresh ? '(refresh)' : ''}`, payload); | ||
|
||
try { | ||
const res = await run(payload); | ||
const newList = isRefresh ? res.list : oldList.concat(res.list); | ||
list.value = newList; | ||
console.log(`\tpage ${state.pageNumber}: (${offset}+${res.list.length})/${res.total}`, res); | ||
const isDoneF = isDone || ((r) => r.list.length < pageSize || newList.length >= r.total); | ||
done.value = isDoneF(res); | ||
state.pageNumber += 1; | ||
} finally { | ||
refreshing.value = false; | ||
} | ||
}); | ||
|
||
onMounted(() => { | ||
loadData(); | ||
}); | ||
return { | ||
/** 原始响应 */ | ||
rawData: data, | ||
/** 合并后的列表 */ | ||
list, | ||
/** 正在刷新第一页的数据 */ | ||
refreshing, | ||
/** 正在加载下一页的数据 */ | ||
loadingMore: computed(() => loading.value && !refreshing.value), | ||
/** 正在请求接口 */ | ||
loading, | ||
/** 接口错误 */ | ||
error, | ||
/** 加载下一页的数据 */ | ||
loadMore: () => loadData(), | ||
/** “暂无数据” */ | ||
empty: computed(() => list.value?.length === 0 && !loading.value && done.value), | ||
/** 是否已经加载全部 */ | ||
done, | ||
/** 刷新到第一页 */ | ||
refresh: () => loadData(true), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
## Intro | ||
|
||
无限滚动分页逻辑 | ||
|
||
## Demos | ||
|
||
<demo src="./demo.vue" file="../use-paging/service" /> | ||
|
||
<demo src="./demo-el.vue" file="../use-paging/service" title="和 el-infinite-scroll 一起使用" /> | ||
|
||
<!-- todo https://github.com/element-plus/element-plus/issues/6033 --> | ||
<!-- <demo src="./demo-el-bar.vue" file="../use-paging/service" title="和 el-infinite-scroll、 el-scroll-bar 一起使用" /> --> | ||
|
||
## Api | ||
|
||
<details> | ||
<summary>index.d.ts</summary> | ||
|
||
<<< es/use-infinite-scroll/index.d.ts | ||
|
||
</details> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
<script lang="ts"> | ||
import { ElPagination } from 'element-plus'; | ||
import { defineComponent } from 'vue'; | ||
import { usePaging } from '@bowencool/vhooks'; | ||
import { fakePagingService } from './service'; | ||
export default defineComponent({ | ||
components: { ElPagination }, | ||
setup() { | ||
return usePaging(fakePagingService); | ||
}, | ||
}); | ||
</script> | ||
|
||
<template> | ||
<ElPagination v-bind="paginationState" background layout="total, sizes, prev, pager, next, jumper"></ElPagination> | ||
<pre>list: | ||
<el-icon v-if="loading"><icon-loading /></el-icon> | ||
<span v-else-if="error">{{ error }}</span> | ||
<span v-else-if="list"><div v-for="d in list" :key="d.id">{{ d.id }}</div></span> | ||
<span v-else>empty</span> | ||
</pre> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { promiseTimeout } from '@vueuse/core'; | ||
import { computed, onMounted, reactive } from 'vue'; | ||
import type { UseAsyncStateOptions } from '../use-async-state'; | ||
import { useAsyncState } from '../use-async-state'; | ||
|
||
export * from './types'; | ||
|
||
import type { PagingParamsDTO, PagingResultDTO, PagingService } from './types'; | ||
|
||
export type UsePagingOption<R> = UseAsyncStateOptions<PagingResultDTO<R>, [PagingParamsDTO /* & P */]> & { | ||
defaultPageSize?: number; | ||
}; | ||
|
||
export function usePaging<RowType /* P extends Record<string, any> */>( | ||
pagingService: PagingService<RowType /* P */>, | ||
{ defaultPageSize = 10, ...others }: UsePagingOption<RowType> = {}, | ||
) { | ||
const { data, error, loading, run } = useAsyncState(pagingService, { | ||
debounceResult: true, | ||
debounceInterval: 100, | ||
...others, | ||
manual: true, | ||
}); | ||
const paginationState = reactive({ | ||
total: 0, | ||
currentPage: 1, | ||
pageSize: defaultPageSize, | ||
'onUpdate:currentPage': async function (pageNumber: number) { | ||
const { pageSize } = paginationState; | ||
const rez = await run({ pageSize, pageNumber, offset: pageSize * (pageNumber - 1), count: pageSize }); | ||
paginationState.currentPage = pageNumber; | ||
paginationState.pageSize = pageSize; | ||
paginationState.total = rez.total; | ||
}, | ||
'onUpdate:pageSize': async function (pageSize: number) { | ||
const rez = await run({ pageSize, pageNumber: 1, offset: 0, count: pageSize }); | ||
paginationState.currentPage = 1; | ||
paginationState.pageSize = pageSize; | ||
paginationState.total = rez.total ?? 0; | ||
}, | ||
}); | ||
|
||
// after effects | ||
const goPage = (n: number) => promiseTimeout(0).then(() => paginationState['onUpdate:currentPage'](n)); | ||
onMounted(() => { | ||
goPage(1); | ||
}); | ||
|
||
return { | ||
list: computed(() => data.value?.list), | ||
paginationState, | ||
goPage, | ||
rawData: data, | ||
loading, | ||
error, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
## Intro | ||
|
||
分页逻辑 | ||
|
||
<demo src="./demo.vue" file="./service" /> | ||
|
||
## Api | ||
|
||
<details> | ||
<summary>index.d.ts</summary> | ||
|
||
<<< es/use-paging/index.d.ts | ||
|
||
</details> | ||
|
||
<details> | ||
<summary>types.d.ts</summary> | ||
|
||
<<< es/use-paging/types.d.ts | ||
|
||
</details> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { PagingService } from './types'; | ||
|
||
type ItemDTO = { id: number }; | ||
|
||
export const fakePagingService: PagingService<ItemDTO> = (query) => | ||
new Promise((r, j) => { | ||
setTimeout(() => { | ||
try { | ||
const total = 33; | ||
const list: ItemDTO[] = []; | ||
const start = (query.pageNumber - 1) * query.pageSize + 1; | ||
for (let i = start; i < start + query.pageSize; i++) { | ||
if (i > total) break; | ||
list.push({ id: i }); | ||
} | ||
r({ total, list }); | ||
} catch (er) { | ||
j(er); | ||
} | ||
}, 300); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// export type SortOrder = 'ascending' | 'descending'; | ||
|
||
export interface PagingParamsDTO { | ||
pageNumber: number; | ||
pageSize: number; | ||
offset: number; | ||
count: number; | ||
// sortField?: string; | ||
// sortOrder?: SortOrder; | ||
[k: string]: any; | ||
} | ||
export type PagingResultDTO<R> = { | ||
total: number; | ||
list: R[]; | ||
[k: string]: any; | ||
}; | ||
export interface PagingService<R /* , P extends Record<string, any> = Record<string, any> */> { | ||
(p: Record<string, any> /* PagingParamsDTO */ /* & P */): Promise<PagingResultDTO<R>>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters