feat: tanstack query demos (#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 <vince292007@gmail.com>pull/48/MERGE
parent
56e66193fc
commit
86ed732ca8
|
@ -26,6 +26,7 @@
|
|||
"#/*": "./src/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tanstack/vue-query": "^5.53.1",
|
||||
"@vben/access": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
|
|
|
@ -5,6 +5,8 @@ import { initStores } from '@vben/stores';
|
|||
import '@vben/styles';
|
||||
import '@vben/styles/antd';
|
||||
|
||||
import { VueQueryPlugin } from '@tanstack/vue-query';
|
||||
|
||||
import { setupI18n } from '#/locales';
|
||||
|
||||
import App from './app.vue';
|
||||
|
@ -25,6 +27,9 @@ async function bootstrap(namespace: string) {
|
|||
// 配置路由及路由守卫
|
||||
app.use(router);
|
||||
|
||||
// 配置@tanstack/vue-query
|
||||
app.use(VueQueryPlugin);
|
||||
|
||||
app.mount('#app');
|
||||
}
|
||||
|
||||
|
|
|
@ -183,6 +183,15 @@ const routes: RouteRecordRaw[] = [
|
|||
title: $t('page.demos.features.clipboard'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'VueQueryDemo',
|
||||
path: '/demos/features/vue-query',
|
||||
component: () =>
|
||||
import('#/views/demos/features/vue-query/index.vue'),
|
||||
meta: {
|
||||
title: 'Tanstack Query',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// 面包屑导航
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<script setup lang="ts">
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Card } from 'ant-design-vue';
|
||||
|
||||
import InfiniteQueries from './infinite-queries.vue';
|
||||
import PaginatedQueries from './paginated-queries.vue';
|
||||
import QueryRetries from './query-retries.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page title="Vue Query示例">
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<Card title="分页查询">
|
||||
<PaginatedQueries />
|
||||
</Card>
|
||||
<Card title="无限滚动">
|
||||
<InfiniteQueries class="h-[300px] overflow-auto" />
|
||||
</Card>
|
||||
<Card title="错误重试">
|
||||
<QueryRetries />
|
||||
</Card>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
|
@ -0,0 +1,58 @@
|
|||
<script setup lang="ts">
|
||||
import type { IProducts } from './typing';
|
||||
|
||||
import { useInfiniteQuery } from '@tanstack/vue-query';
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
const LIMIT = 10;
|
||||
const fetchProducts = async ({ pageParam = 0 }): Promise<IProducts> => {
|
||||
const res = await fetch(
|
||||
`https://dummyjson.com/products?limit=${LIMIT}&skip=${pageParam * LIMIT}`,
|
||||
);
|
||||
return res.json();
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
error,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isError,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
isPending,
|
||||
} = useInfiniteQuery({
|
||||
getNextPageParam: (current, allPages) => {
|
||||
const nextPage = allPages.length + 1;
|
||||
const lastPage = current.skip + current.limit;
|
||||
if (lastPage === current.total) return;
|
||||
return nextPage;
|
||||
},
|
||||
initialPageParam: 0,
|
||||
queryFn: fetchProducts,
|
||||
queryKey: ['products'],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<span v-if="isPending">加载...</span>
|
||||
<span v-else-if="isError">出错了: {{ error }}</span>
|
||||
<div v-else-if="data">
|
||||
<span v-if="isFetching && !isFetchingNextPage">Fetching...</span>
|
||||
<ul v-for="(group, index) in data.pages" :key="index">
|
||||
<li v-for="product in group.products" :key="product.id">
|
||||
{{ product.title }}
|
||||
</li>
|
||||
</ul>
|
||||
<Button
|
||||
:disabled="!hasNextPage || isFetchingNextPage"
|
||||
@click="() => fetchNextPage()"
|
||||
>
|
||||
<span v-if="isFetchingNextPage">加载中...</span>
|
||||
<span v-else-if="hasNextPage">加载更多</span>
|
||||
<span v-else>没有更多了</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,51 @@
|
|||
<script setup lang="ts">
|
||||
import type { IProducts } from './typing';
|
||||
|
||||
import { type Ref, ref } from 'vue';
|
||||
|
||||
import { keepPreviousData, useQuery } from '@tanstack/vue-query';
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
const LIMIT = 10;
|
||||
const fetcher = async (page: Ref<number>): Promise<IProducts> => {
|
||||
const res = await fetch(
|
||||
`https://dummyjson.com/products?limit=${LIMIT}&skip=${(page.value - 1) * LIMIT}`,
|
||||
);
|
||||
return res.json();
|
||||
};
|
||||
|
||||
const page = ref(1);
|
||||
const { data, error, isError, isPending, isPlaceholderData } = useQuery({
|
||||
// The data from the last successful fetch is available while new data is being requested.
|
||||
placeholderData: keepPreviousData,
|
||||
queryFn: () => fetcher(page),
|
||||
queryKey: ['products', page],
|
||||
});
|
||||
const prevPage = () => {
|
||||
page.value = Math.max(page.value - 1, 1);
|
||||
};
|
||||
const nextPage = () => {
|
||||
if (!isPlaceholderData.value) {
|
||||
page.value = page.value + 1;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex gap-4">
|
||||
<Button size="small" @click="prevPage">上一页</Button>
|
||||
<p>当前页: {{ page }}</p>
|
||||
<Button size="small" @click="nextPage">下一页</Button>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<div v-if="isPending">加载中...</div>
|
||||
<div v-else-if="isError">出错了: {{ error }}</div>
|
||||
<div v-else-if="data">
|
||||
<ul>
|
||||
<li v-for="item in data.products" :key="item.id">
|
||||
{{ item.title }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,34 @@
|
|||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useQuery } from '@tanstack/vue-query';
|
||||
import { Button } from 'ant-design-vue';
|
||||
|
||||
const count = ref(-1);
|
||||
async function fetchApi() {
|
||||
count.value += 1;
|
||||
return new Promise((_resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('something went wrong!'));
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
const { error, isFetching, refetch } = useQuery({
|
||||
enabled: false, // Disable automatic refetching when the query mounts
|
||||
queryFn: fetchApi,
|
||||
queryKey: ['queryKey'],
|
||||
retry: 3, // Will retry failed requests 3 times before displaying an error
|
||||
});
|
||||
|
||||
const onClick = async () => {
|
||||
count.value = -1;
|
||||
await refetch();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Button :loading="isFetching" @click="onClick"> 发起错误重试 </Button>
|
||||
<p v-if="count > 0" class="my-3">重试次数{{ count }}</p>
|
||||
<p>{{ error }}</p>
|
||||
</template>
|
|
@ -0,0 +1,18 @@
|
|||
export interface IProducts {
|
||||
limit: number;
|
||||
products: {
|
||||
brand: string;
|
||||
category: string;
|
||||
description: string;
|
||||
discountPercentage: string;
|
||||
id: string;
|
||||
images: string[];
|
||||
price: string;
|
||||
rating: string;
|
||||
stock: string;
|
||||
thumbnail: string;
|
||||
title: string;
|
||||
}[];
|
||||
skip: number;
|
||||
total: number;
|
||||
}
|
|
@ -1156,6 +1156,9 @@ importers:
|
|||
|
||||
playground:
|
||||
dependencies:
|
||||
'@tanstack/vue-query':
|
||||
specifier: ^5.53.1
|
||||
version: 5.54.2(vue@3.5.3(typescript@5.5.4))
|
||||
'@vben/access':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/effects/access
|
||||
|
@ -3976,12 +3979,28 @@ packages:
|
|||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20'
|
||||
|
||||
'@tanstack/match-sorter-utils@8.19.4':
|
||||
resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@tanstack/query-core@5.54.1':
|
||||
resolution: {integrity: sha512-hKS+WRpT5zBFip21pB6Jx1C0hranWQrbv5EJ7qPoiV5MYI3C8rTCqWC9DdBseiPT1JgQWh8Y55YthuYZNiw3Xw==}
|
||||
|
||||
'@tanstack/store@0.5.5':
|
||||
resolution: {integrity: sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==}
|
||||
|
||||
'@tanstack/virtual-core@3.9.0':
|
||||
resolution: {integrity: sha512-Saga7/QRGej/IDCVP5BgJ1oDqlDT2d9rQyoflS3fgMS8ntJ8JGw/LBqK2GorHa06+VrNFc0tGz65XQHJQJetFQ==}
|
||||
|
||||
'@tanstack/vue-query@5.54.2':
|
||||
resolution: {integrity: sha512-GYIYee9WkUbPDD28t1kdNNtLCioiIva0MhKCvODGWoEML5MNONCX4/i4y2GGFi8i9nSbcA8MpvD+nt/tdZ+yJw==}
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.1.2
|
||||
vue: 3.5.3
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
|
||||
'@tanstack/vue-store@0.5.5':
|
||||
resolution: {integrity: sha512-j+CDrxVhtQQNOjWzLmCqJeDwmmTAQGvEaNbLr1uPJ9rxJITodJtFNdBFj7l+Nd5o34v2ayEv64Ugh6+1BtuGNg==}
|
||||
peerDependencies:
|
||||
|
@ -8442,6 +8461,9 @@ packages:
|
|||
resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
remove-accents@0.5.0:
|
||||
resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==}
|
||||
|
||||
repeat-string@1.6.1:
|
||||
resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
@ -12909,10 +12931,24 @@ snapshots:
|
|||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 3.4.10
|
||||
|
||||
'@tanstack/match-sorter-utils@8.19.4':
|
||||
dependencies:
|
||||
remove-accents: 0.5.0
|
||||
|
||||
'@tanstack/query-core@5.54.1': {}
|
||||
|
||||
'@tanstack/store@0.5.5': {}
|
||||
|
||||
'@tanstack/virtual-core@3.9.0': {}
|
||||
|
||||
'@tanstack/vue-query@5.54.2(vue@3.5.3(typescript@5.5.4))':
|
||||
dependencies:
|
||||
'@tanstack/match-sorter-utils': 8.19.4
|
||||
'@tanstack/query-core': 5.54.1
|
||||
'@vue/devtools-api': 6.6.3
|
||||
vue: 3.5.3(typescript@5.5.4)
|
||||
vue-demi: 0.14.10(vue@3.5.3(typescript@5.5.4))
|
||||
|
||||
'@tanstack/vue-store@0.5.5(vue@3.5.3(typescript@5.5.4))':
|
||||
dependencies:
|
||||
'@tanstack/store': 0.5.5
|
||||
|
@ -17933,6 +17969,8 @@ snapshots:
|
|||
|
||||
relateurl@0.2.7: {}
|
||||
|
||||
remove-accents@0.5.0: {}
|
||||
|
||||
repeat-string@1.6.1: {}
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
|
Loading…
Reference in New Issue