Skip to content

Commit

Permalink
feat: usePaging and useInfiniteScroll
Browse files Browse the repository at this point in the history
  • Loading branch information
bowencool committed Sep 10, 2022
1 parent 6811ee3 commit 7a9994b
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 2 deletions.
2 changes: 2 additions & 0 deletions packages/index.ts
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;
53 changes: 53 additions & 0 deletions packages/use-infinite-scroll/demo-el.vue
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>
47 changes: 47 additions & 0 deletions packages/use-infinite-scroll/demo.vue
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>
84 changes: 84 additions & 0 deletions packages/use-infinite-scroll/index.ts
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),
};
}
21 changes: 21 additions & 0 deletions packages/use-infinite-scroll/readme.md
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>
23 changes: 23 additions & 0 deletions packages/use-paging/demo.vue
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>
57 changes: 57 additions & 0 deletions packages/use-paging/index.tsx
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,
};
}
21 changes: 21 additions & 0 deletions packages/use-paging/readme.md
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>
21 changes: 21 additions & 0 deletions packages/use-paging/service.ts
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);
});
19 changes: 19 additions & 0 deletions packages/use-paging/types.tsx
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>>;
}
4 changes: 2 additions & 2 deletions website/.vitepress/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ const hooks = [
text: 'Composition API',
children: [
{ text: 'useAsyncState 异步状态管理', link: '/hooks/use-async-state/readme' },
// { text: 'usePaging 分页器状态管理', link: '/hooks/use-paging/readme' },
// { text: 'useInfiniteScroll 无限滚动状态管理', link: '/hooks/use-infinite-scroll/readme' },
{ text: 'usePaging 分页器状态管理', link: '/hooks/use-paging/readme' },
{ text: 'useInfiniteScroll 无限滚动状态管理', link: '/hooks/use-infinite-scroll/readme' },
{ text: 'useCopy 复制交互', link: '/hooks/use-copy/readme' },
{ text: 'useCountDown 倒计时交互', link: '/hooks/use-count-down/readme' },
],
Expand Down

0 comments on commit 7a9994b

Please sign in to comment.