diff --git a/package.json b/package.json index 0c3e8838..1ef33aef 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@halo-dev/components": "workspace:*", "@halo-dev/console-shared": "workspace:*", "@halo-dev/richtext-editor": "0.0.0-alpha.19", + "@tanstack/vue-query": "^4.24.10", "@tiptap/extension-character-count": "^2.0.0-beta.202", "@uppy/core": "^3.0.4", "@uppy/dashboard": "^3.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b9faaff..99a48ce5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,6 +22,7 @@ importers: '@rushstack/eslint-patch': ^1.2.0 '@tailwindcss/aspect-ratio': ^0.4.2 '@tailwindcss/container-queries': ^0.1.0 + '@tanstack/vue-query': ^4.24.10 '@tiptap/extension-character-count': ^2.0.0-beta.202 '@types/jsdom': ^20.0.1 '@types/lodash.clonedeep': 4.5.7 @@ -115,6 +116,7 @@ importers: '@halo-dev/components': link:packages/components '@halo-dev/console-shared': link:packages/shared '@halo-dev/richtext-editor': 0.0.0-alpha.19_oau5uimcdfyintci7kxkcutjea + '@tanstack/vue-query': 4.24.10_vue@3.2.45 '@tiptap/extension-character-count': 2.0.0-beta.202_f4ffqkgz5d3wev7su7t7l2rrua '@uppy/core': 3.0.4 '@uppy/dashboard': 3.2.0_@uppy+core@3.0.4 @@ -3168,6 +3170,33 @@ packages: tailwindcss: 3.2.4_postcss@8.4.19 dev: true + /@tanstack/match-sorter-utils/8.7.6: + resolution: {integrity: sha512-2AMpRiA6QivHOUiBpQAVxjiHAA68Ei23ZUMNaRJrN6omWiSFLoYrxGcT6BXtuzp0Jw4h6HZCmGGIM/gbwebO2A==} + engines: {node: '>=12'} + dependencies: + remove-accents: 0.4.2 + dev: false + + /@tanstack/query-core/4.24.10: + resolution: {integrity: sha512-2QywqXEAGBIUoTdgn1lAB4/C8QEqwXHj2jrCLeYTk2xVGtLiPEUD8jcMoeB2noclbiW2mMt4+Fq7fZStuz3wAQ==} + dev: false + + /@tanstack/vue-query/4.24.10_vue@3.2.45: + resolution: {integrity: sha512-K9mtij3WpQquySsaNhyN5ZQT3oO6Y69J+OT2/NYQYvYCI1AL1S5/sQ1n2FtpyslgrJn9khSerFB7icbYcbcubA==} + peerDependencies: + '@vue/composition-api': ^1.1.2 + vue: ^2.5.0 || ^3.0.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + '@tanstack/match-sorter-utils': 8.7.6 + '@tanstack/query-core': 4.24.10 + '@vue/devtools-api': 6.4.5 + vue: 3.2.45 + vue-demi: 0.13.11_vue@3.2.45 + dev: false + /@tiptap/core/2.0.0-beta.209_ev2av5mpakmaqws3kqtlepbpdy: resolution: {integrity: sha512-DOOzfo2XKD5Qt2oEGW33/6ugwSnvpl4WbxtlKdPadLoApk6Kja3K1Eps3pihBgIGmo4tkctkCzmj8wNWS7KeWg==} peerDependencies: @@ -4726,7 +4755,7 @@ packages: /axios/0.21.4_debug@4.3.2: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} dependencies: - follow-redirects: 1.15.2_debug@4.3.2 + follow-redirects: 1.15.2 transitivePeerDependencies: - debug dev: true @@ -4742,7 +4771,7 @@ packages: /axios/0.27.2: resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} dependencies: - follow-redirects: 1.15.2_debug@4.3.2 + follow-redirects: 1.15.2 form-data: 4.0.0 transitivePeerDependencies: - debug @@ -5549,6 +5578,7 @@ packages: optional: true dependencies: ms: 2.1.2 + dev: true /debug/4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -6870,6 +6900,15 @@ packages: vue-resize: 2.0.0-alpha.1_vue@3.2.45 dev: false + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + /follow-redirects/1.15.2_debug@4.3.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} @@ -6880,6 +6919,7 @@ packages: optional: true dependencies: debug: 4.3.2 + dev: true /foreground-child/2.0.0: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} @@ -8434,6 +8474,7 @@ packages: /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + dev: true /ms/2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -9307,6 +9348,10 @@ packages: engines: {node: '>= 0.10'} dev: true + /remove-accents/0.4.2: + resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==} + dev: false + /request-progress/3.0.0: resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} dependencies: diff --git a/src/main.ts b/src/main.ts index 7944ecaa..061e9460 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,6 +21,7 @@ import { useSystemStatesStore } from "./stores/system-states"; import { useUserStore } from "./stores/user"; import { useSystemConfigMapStore } from "./stores/system-configmap"; import i18n from "./locales"; +import { VueQueryPlugin } from "@tanstack/vue-query"; const app = createApp(App); @@ -28,6 +29,7 @@ setupComponents(app); app.use(createPinia()); app.use(i18n); +app.use(VueQueryPlugin); function registerModule(pluginModule: PluginModule, core: boolean) { if (pluginModule.components) { diff --git a/src/modules/system/plugins/PluginDetail.vue b/src/modules/system/plugins/PluginDetail.vue index 581471db..e92c910b 100644 --- a/src/modules/system/plugins/PluginDetail.vue +++ b/src/modules/system/plugins/PluginDetail.vue @@ -1,13 +1,14 @@ diff --git a/src/modules/system/plugins/PluginList.vue b/src/modules/system/plugins/PluginList.vue index 79155f50..ed098301 100644 --- a/src/modules/system/plugins/PluginList.vue +++ b/src/modules/system/plugins/PluginList.vue @@ -14,95 +14,15 @@ import { } from "@halo-dev/components"; import PluginListItem from "./components/PluginListItem.vue"; import PluginUploadModal from "./components/PluginUploadModal.vue"; -import { computed, onMounted, ref } from "vue"; +import { computed, ref } from "vue"; import { apiClient } from "@/utils/api-client"; -import type { PluginList } from "@halo-dev/api-client"; import { usePermission } from "@/utils/permission"; -import { onBeforeRouteLeave } from "vue-router"; import FilterTag from "@/components/filter/FilterTag.vue"; import FilteCleanButton from "@/components/filter/FilterCleanButton.vue"; import { getNode } from "@formkit/core"; +import { useQuery } from "@tanstack/vue-query"; +import type { Plugin } from "@halo-dev/api-client"; -const { currentUserHasPermission } = usePermission(); - -const plugins = ref({ - page: 1, - size: 20, - total: 0, - items: [], - first: true, - last: false, - hasNext: false, - hasPrevious: false, - totalPages: 0, -}); -const loading = ref(false); -const pluginInstall = ref(false); -const keyword = ref(""); -const refreshInterval = ref(); - -const handleFetchPlugins = async (options?: { - mute?: boolean; - page?: number; -}) => { - try { - clearInterval(refreshInterval.value); - - if (!options?.mute) { - loading.value = true; - } - - if (options?.page) { - plugins.value.page = options.page; - } - - const { data } = await apiClient.plugin.listPlugins({ - page: plugins.value.page, - size: plugins.value.size, - keyword: keyword.value, - enabled: selectedEnabledItem.value?.value, - sort: [selectedSortItem.value?.value].filter( - (item) => !!item - ) as string[], - }); - - plugins.value = data; - - const deletedPlugins = plugins.value.items.filter( - (plugin) => !!plugin.metadata.deletionTimestamp - ); - - if (deletedPlugins.length) { - refreshInterval.value = setInterval(() => { - handleFetchPlugins({ mute: true }); - }, 3000); - } - } catch (e) { - console.error("Failed to fetch plugins", e); - } finally { - loading.value = false; - } -}; - -onBeforeRouteLeave(() => { - clearInterval(refreshInterval.value); -}); - -const handlePaginationChange = ({ - page, - size, -}: { - page: number; - size: number; -}) => { - plugins.value.page = page; - plugins.value.size = size; - handleFetchPlugins(); -}; - -onMounted(handleFetchPlugins); - -// Filters interface EnabledItem { label: string; value?: boolean; @@ -113,6 +33,16 @@ interface SortItem { value: string; } +const { currentUserHasPermission } = usePermission(); + +const pluginInstall = ref(false); + +const keyword = ref(""); +const page = ref(1); +const size = ref(20); +const total = ref(0); + +// Filters const EnabledItems: EnabledItem[] = [ { label: "全部", @@ -144,12 +74,12 @@ const selectedSortItem = ref(); function handleEnabledItemChange(enabledItem: EnabledItem) { selectedEnabledItem.value = enabledItem; - handleFetchPlugins({ page: 1 }); + page.value = 1; } function handleSortItemChange(sortItem?: SortItem) { selectedSortItem.value = sortItem; - handleFetchPlugins({ page: 1 }); + page.value = 1; } function handleKeywordChange() { @@ -157,12 +87,12 @@ function handleKeywordChange() { if (keywordNode) { keyword.value = keywordNode._value as string; } - handleFetchPlugins({ page: 1 }); + page.value = 1; } function handleClearKeyword() { keyword.value = ""; - handleFetchPlugins({ page: 1 }); + page.value = 1; } const hasFilters = computed(() => { @@ -177,14 +107,47 @@ function handleClearFilters() { selectedEnabledItem.value = undefined; selectedSortItem.value = undefined; keyword.value = ""; - handleFetchPlugins({ page: 1 }); + page.value = 1; } + +const { data, isLoading, isFetching, refetch } = useQuery({ + queryKey: [ + "plugins", + page, + size, + keyword, + selectedEnabledItem, + selectedSortItem, + ], + queryFn: async () => { + const { data } = await apiClient.plugin.listPlugins({ + page: page.value, + size: size.value, + keyword: keyword.value, + enabled: selectedEnabledItem.value?.value, + sort: [selectedSortItem.value?.value].filter(Boolean) as string[], + }); + + total.value = data.total; + + return data.items; + }, + refetchOnWindowFocus: false, + keepPreviousData: true, + refetchInterval: (data) => { + const deletingPlugins = data?.filter( + (plugin) => !!plugin.metadata.deletionTimestamp + ); + + return deletingPlugins?.length ? 3000 : false; + }, +}); @@ -302,11 +265,11 @@ function handleClearFilters() { @@ -317,16 +280,16 @@ function handleClearFilters() { - + - + - 刷新 + 刷新 - - + + @@ -356,11 +319,10 @@ function handleClearFilters() {