Skip to content

Commit 86ed732

Browse files
feat: tanstack query demos (vbenjs#4276)
* chore(@vben/request): add axios-retry * feat: error retry * feat: paginated queries * feat: infinite queries * chore: update * chore: update * fix: ci error * chore: update * chore: remove axios-retry * chore: update deps * chore: update deps * chore: update deps * chore: update pnpm.lock --------- Co-authored-by: vince <[email protected]>
1 parent 56e6619 commit 86ed732

File tree

9 files changed

+239
-0
lines changed

9 files changed

+239
-0
lines changed

playground/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"#/*": "./src/*"
2727
},
2828
"dependencies": {
29+
"@tanstack/vue-query": "^5.53.1",
2930
"@vben/access": "workspace:*",
3031
"@vben/common-ui": "workspace:*",
3132
"@vben/constants": "workspace:*",

playground/src/bootstrap.ts

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { initStores } from '@vben/stores';
55
import '@vben/styles';
66
import '@vben/styles/antd';
77

8+
import { VueQueryPlugin } from '@tanstack/vue-query';
9+
810
import { setupI18n } from '#/locales';
911

1012
import App from './app.vue';
@@ -25,6 +27,9 @@ async function bootstrap(namespace: string) {
2527
// 配置路由及路由守卫
2628
app.use(router);
2729

30+
// 配置@tanstack/vue-query
31+
app.use(VueQueryPlugin);
32+
2833
app.mount('#app');
2934
}
3035

playground/src/router/routes/modules/demos.ts

+9
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,15 @@ const routes: RouteRecordRaw[] = [
183183
title: $t('page.demos.features.clipboard'),
184184
},
185185
},
186+
{
187+
name: 'VueQueryDemo',
188+
path: '/demos/features/vue-query',
189+
component: () =>
190+
import('#/views/demos/features/vue-query/index.vue'),
191+
meta: {
192+
title: 'Tanstack Query',
193+
},
194+
},
186195
],
187196
},
188197
// 面包屑导航
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<script setup lang="ts">
2+
import { Page } from '@vben/common-ui';
3+
4+
import { Card } from 'ant-design-vue';
5+
6+
import InfiniteQueries from './infinite-queries.vue';
7+
import PaginatedQueries from './paginated-queries.vue';
8+
import QueryRetries from './query-retries.vue';
9+
</script>
10+
11+
<template>
12+
<Page title="Vue Query示例">
13+
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
14+
<Card title="分页查询">
15+
<PaginatedQueries />
16+
</Card>
17+
<Card title="无限滚动">
18+
<InfiniteQueries class="h-[300px] overflow-auto" />
19+
</Card>
20+
<Card title="错误重试">
21+
<QueryRetries />
22+
</Card>
23+
</div>
24+
</Page>
25+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<script setup lang="ts">
2+
import type { IProducts } from './typing';
3+
4+
import { useInfiniteQuery } from '@tanstack/vue-query';
5+
import { Button } from 'ant-design-vue';
6+
7+
const LIMIT = 10;
8+
const fetchProducts = async ({ pageParam = 0 }): Promise<IProducts> => {
9+
const res = await fetch(
10+
`https://dummyjson.com/products?limit=${LIMIT}&skip=${pageParam * LIMIT}`,
11+
);
12+
return res.json();
13+
};
14+
15+
const {
16+
data,
17+
error,
18+
fetchNextPage,
19+
hasNextPage,
20+
isError,
21+
isFetching,
22+
isFetchingNextPage,
23+
isPending,
24+
} = useInfiniteQuery({
25+
getNextPageParam: (current, allPages) => {
26+
const nextPage = allPages.length + 1;
27+
const lastPage = current.skip + current.limit;
28+
if (lastPage === current.total) return;
29+
return nextPage;
30+
},
31+
initialPageParam: 0,
32+
queryFn: fetchProducts,
33+
queryKey: ['products'],
34+
});
35+
</script>
36+
37+
<template>
38+
<div>
39+
<span v-if="isPending">加载...</span>
40+
<span v-else-if="isError">出错了: {{ error }}</span>
41+
<div v-else-if="data">
42+
<span v-if="isFetching && !isFetchingNextPage">Fetching...</span>
43+
<ul v-for="(group, index) in data.pages" :key="index">
44+
<li v-for="product in group.products" :key="product.id">
45+
{{ product.title }}
46+
</li>
47+
</ul>
48+
<Button
49+
:disabled="!hasNextPage || isFetchingNextPage"
50+
@click="() => fetchNextPage()"
51+
>
52+
<span v-if="isFetchingNextPage">加载中...</span>
53+
<span v-else-if="hasNextPage">加载更多</span>
54+
<span v-else>没有更多了</span>
55+
</Button>
56+
</div>
57+
</div>
58+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<script setup lang="ts">
2+
import type { IProducts } from './typing';
3+
4+
import { type Ref, ref } from 'vue';
5+
6+
import { keepPreviousData, useQuery } from '@tanstack/vue-query';
7+
import { Button } from 'ant-design-vue';
8+
9+
const LIMIT = 10;
10+
const fetcher = async (page: Ref<number>): Promise<IProducts> => {
11+
const res = await fetch(
12+
`https://dummyjson.com/products?limit=${LIMIT}&skip=${(page.value - 1) * LIMIT}`,
13+
);
14+
return res.json();
15+
};
16+
17+
const page = ref(1);
18+
const { data, error, isError, isPending, isPlaceholderData } = useQuery({
19+
// The data from the last successful fetch is available while new data is being requested.
20+
placeholderData: keepPreviousData,
21+
queryFn: () => fetcher(page),
22+
queryKey: ['products', page],
23+
});
24+
const prevPage = () => {
25+
page.value = Math.max(page.value - 1, 1);
26+
};
27+
const nextPage = () => {
28+
if (!isPlaceholderData.value) {
29+
page.value = page.value + 1;
30+
}
31+
};
32+
</script>
33+
34+
<template>
35+
<div class="flex gap-4">
36+
<Button size="small" @click="prevPage">上一页</Button>
37+
<p>当前页: {{ page }}</p>
38+
<Button size="small" @click="nextPage">下一页</Button>
39+
</div>
40+
<div class="p-4">
41+
<div v-if="isPending">加载中...</div>
42+
<div v-else-if="isError">出错了: {{ error }}</div>
43+
<div v-else-if="data">
44+
<ul>
45+
<li v-for="item in data.products" :key="item.id">
46+
{{ item.title }}
47+
</li>
48+
</ul>
49+
</div>
50+
</div>
51+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue';
3+
4+
import { useQuery } from '@tanstack/vue-query';
5+
import { Button } from 'ant-design-vue';
6+
7+
const count = ref(-1);
8+
async function fetchApi() {
9+
count.value += 1;
10+
return new Promise((_resolve, reject) => {
11+
setTimeout(() => {
12+
reject(new Error('something went wrong!'));
13+
}, 1000);
14+
});
15+
}
16+
17+
const { error, isFetching, refetch } = useQuery({
18+
enabled: false, // Disable automatic refetching when the query mounts
19+
queryFn: fetchApi,
20+
queryKey: ['queryKey'],
21+
retry: 3, // Will retry failed requests 3 times before displaying an error
22+
});
23+
24+
const onClick = async () => {
25+
count.value = -1;
26+
await refetch();
27+
};
28+
</script>
29+
30+
<template>
31+
<Button :loading="isFetching" @click="onClick"> 发起错误重试 </Button>
32+
<p v-if="count > 0" class="my-3">重试次数{{ count }}</p>
33+
<p>{{ error }}</p>
34+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
export interface IProducts {
2+
limit: number;
3+
products: {
4+
brand: string;
5+
category: string;
6+
description: string;
7+
discountPercentage: string;
8+
id: string;
9+
images: string[];
10+
price: string;
11+
rating: string;
12+
stock: string;
13+
thumbnail: string;
14+
title: string;
15+
}[];
16+
skip: number;
17+
total: number;
18+
}

pnpm-lock.yaml

+38
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)