mirror of https://github.com/halo-dev/halo-admin
refactor: use tanstack query to refactor plugins fetching (#876)
#### What type of PR is this? /kind improvement #### What this PR does / why we need it: 使用 [TanStack Query](https://github.com/TanStack/query) 重构插件管理列表的数据请求相关逻辑。 #### Which issue(s) this PR fixes: Ref https://github.com/halo-dev/halo/issues/3360 #### Special notes for your reviewer: 测试方式: 1. 需要 `pnpm install` 2. 插件管理页面,安装若干插件。 3. 测试分页、条件筛选等逻辑是否正常。 #### Does this PR introduce a user-facing change? ```release-note None ```pull/868/head^2
parent
c03ea64bf2
commit
816feb9937
|
@ -45,6 +45,7 @@
|
||||||
"@halo-dev/components": "workspace:*",
|
"@halo-dev/components": "workspace:*",
|
||||||
"@halo-dev/console-shared": "workspace:*",
|
"@halo-dev/console-shared": "workspace:*",
|
||||||
"@halo-dev/richtext-editor": "0.0.0-alpha.19",
|
"@halo-dev/richtext-editor": "0.0.0-alpha.19",
|
||||||
|
"@tanstack/vue-query": "^4.24.10",
|
||||||
"@tiptap/extension-character-count": "^2.0.0-beta.202",
|
"@tiptap/extension-character-count": "^2.0.0-beta.202",
|
||||||
"@uppy/core": "^3.0.4",
|
"@uppy/core": "^3.0.4",
|
||||||
"@uppy/dashboard": "^3.2.0",
|
"@uppy/dashboard": "^3.2.0",
|
||||||
|
|
|
@ -22,6 +22,7 @@ importers:
|
||||||
'@rushstack/eslint-patch': ^1.2.0
|
'@rushstack/eslint-patch': ^1.2.0
|
||||||
'@tailwindcss/aspect-ratio': ^0.4.2
|
'@tailwindcss/aspect-ratio': ^0.4.2
|
||||||
'@tailwindcss/container-queries': ^0.1.0
|
'@tailwindcss/container-queries': ^0.1.0
|
||||||
|
'@tanstack/vue-query': ^4.24.10
|
||||||
'@tiptap/extension-character-count': ^2.0.0-beta.202
|
'@tiptap/extension-character-count': ^2.0.0-beta.202
|
||||||
'@types/jsdom': ^20.0.1
|
'@types/jsdom': ^20.0.1
|
||||||
'@types/lodash.clonedeep': 4.5.7
|
'@types/lodash.clonedeep': 4.5.7
|
||||||
|
@ -115,6 +116,7 @@ importers:
|
||||||
'@halo-dev/components': link:packages/components
|
'@halo-dev/components': link:packages/components
|
||||||
'@halo-dev/console-shared': link:packages/shared
|
'@halo-dev/console-shared': link:packages/shared
|
||||||
'@halo-dev/richtext-editor': 0.0.0-alpha.19_oau5uimcdfyintci7kxkcutjea
|
'@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
|
'@tiptap/extension-character-count': 2.0.0-beta.202_f4ffqkgz5d3wev7su7t7l2rrua
|
||||||
'@uppy/core': 3.0.4
|
'@uppy/core': 3.0.4
|
||||||
'@uppy/dashboard': 3.2.0_@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
|
tailwindcss: 3.2.4_postcss@8.4.19
|
||||||
dev: true
|
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:
|
/@tiptap/core/2.0.0-beta.209_ev2av5mpakmaqws3kqtlepbpdy:
|
||||||
resolution: {integrity: sha512-DOOzfo2XKD5Qt2oEGW33/6ugwSnvpl4WbxtlKdPadLoApk6Kja3K1Eps3pihBgIGmo4tkctkCzmj8wNWS7KeWg==}
|
resolution: {integrity: sha512-DOOzfo2XKD5Qt2oEGW33/6ugwSnvpl4WbxtlKdPadLoApk6Kja3K1Eps3pihBgIGmo4tkctkCzmj8wNWS7KeWg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -4726,7 +4755,7 @@ packages:
|
||||||
/axios/0.21.4_debug@4.3.2:
|
/axios/0.21.4_debug@4.3.2:
|
||||||
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.2_debug@4.3.2
|
follow-redirects: 1.15.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -4742,7 +4771,7 @@ packages:
|
||||||
/axios/0.27.2:
|
/axios/0.27.2:
|
||||||
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.2_debug@4.3.2
|
follow-redirects: 1.15.2
|
||||||
form-data: 4.0.0
|
form-data: 4.0.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
|
@ -5549,6 +5578,7 @@ packages:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/debug/4.3.4:
|
/debug/4.3.4:
|
||||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||||
|
@ -6870,6 +6900,15 @@ packages:
|
||||||
vue-resize: 2.0.0-alpha.1_vue@3.2.45
|
vue-resize: 2.0.0-alpha.1_vue@3.2.45
|
||||||
dev: false
|
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:
|
/follow-redirects/1.15.2_debug@4.3.2:
|
||||||
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
|
@ -6880,6 +6919,7 @@ packages:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.3.2
|
debug: 4.3.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/foreground-child/2.0.0:
|
/foreground-child/2.0.0:
|
||||||
resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==}
|
resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==}
|
||||||
|
@ -8434,6 +8474,7 @@ packages:
|
||||||
|
|
||||||
/ms/2.1.2:
|
/ms/2.1.2:
|
||||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ms/2.1.3:
|
/ms/2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
@ -9307,6 +9348,10 @@ packages:
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/remove-accents/0.4.2:
|
||||||
|
resolution: {integrity: sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/request-progress/3.0.0:
|
/request-progress/3.0.0:
|
||||||
resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==}
|
resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
|
@ -21,6 +21,7 @@ import { useSystemStatesStore } from "./stores/system-states";
|
||||||
import { useUserStore } from "./stores/user";
|
import { useUserStore } from "./stores/user";
|
||||||
import { useSystemConfigMapStore } from "./stores/system-configmap";
|
import { useSystemConfigMapStore } from "./stores/system-configmap";
|
||||||
import i18n from "./locales";
|
import i18n from "./locales";
|
||||||
|
import { VueQueryPlugin } from "@tanstack/vue-query";
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ setupComponents(app);
|
||||||
|
|
||||||
app.use(createPinia());
|
app.use(createPinia());
|
||||||
app.use(i18n);
|
app.use(i18n);
|
||||||
|
app.use(VueQueryPlugin);
|
||||||
|
|
||||||
function registerModule(pluginModule: PluginModule, core: boolean) {
|
function registerModule(pluginModule: PluginModule, core: boolean) {
|
||||||
if (pluginModule.components) {
|
if (pluginModule.components) {
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { VSwitch, VTag } from "@halo-dev/components";
|
import { VSwitch, VTag } from "@halo-dev/components";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, inject, ref, watchEffect } from "vue";
|
import { computed, inject } from "vue";
|
||||||
import { apiClient } from "@/utils/api-client";
|
import { apiClient } from "@/utils/api-client";
|
||||||
import type { Plugin, Role } from "@halo-dev/api-client";
|
import type { Plugin, Role } from "@halo-dev/api-client";
|
||||||
import { pluginLabels } from "@/constants/labels";
|
import { pluginLabels } from "@/constants/labels";
|
||||||
import { rbacAnnotations } from "@/constants/annotations";
|
import { rbacAnnotations } from "@/constants/annotations";
|
||||||
import { usePluginLifeCycle } from "./composables/use-plugin";
|
import { usePluginLifeCycle } from "./composables/use-plugin";
|
||||||
import { formatDatetime } from "@/utils/date";
|
import { formatDatetime } from "@/utils/date";
|
||||||
|
import { useQuery } from "@tanstack/vue-query";
|
||||||
|
|
||||||
const plugin = inject<Ref<Plugin | undefined>>("plugin");
|
const plugin = inject<Ref<Plugin | undefined>>("plugin");
|
||||||
const { changeStatus, isStarted } = usePluginLifeCycle(plugin);
|
const { changeStatus, isStarted } = usePluginLifeCycle(plugin);
|
||||||
|
@ -17,24 +18,22 @@ interface RoleTemplateGroup {
|
||||||
roles: Role[];
|
roles: Role[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginRoleTemplates = ref<Role[]>([]);
|
const { data: pluginRoleTemplates } = useQuery({
|
||||||
|
queryKey: ["plugin-roles", plugin?.value?.metadata.name],
|
||||||
const handleFetchRoles = async () => {
|
queryFn: async () => {
|
||||||
try {
|
|
||||||
const { data } = await apiClient.extension.role.listv1alpha1Role({
|
const { data } = await apiClient.extension.role.listv1alpha1Role({
|
||||||
page: 0,
|
page: 0,
|
||||||
size: 0,
|
size: 0,
|
||||||
labelSelector: [`${pluginLabels.NAME}=${plugin?.value?.metadata.name}`],
|
labelSelector: [`${pluginLabels.NAME}=${plugin?.value?.metadata.name}`],
|
||||||
});
|
});
|
||||||
pluginRoleTemplates.value = data.items;
|
|
||||||
} catch (e) {
|
return data.items;
|
||||||
console.error(e);
|
},
|
||||||
}
|
});
|
||||||
};
|
|
||||||
|
|
||||||
const pluginRoleTemplateGroups = computed<RoleTemplateGroup[]>(() => {
|
const pluginRoleTemplateGroups = computed<RoleTemplateGroup[]>(() => {
|
||||||
const groups: RoleTemplateGroup[] = [];
|
const groups: RoleTemplateGroup[] = [];
|
||||||
pluginRoleTemplates.value.forEach((role) => {
|
pluginRoleTemplates.value?.forEach((role) => {
|
||||||
const group = groups.find(
|
const group = groups.find(
|
||||||
(group) =>
|
(group) =>
|
||||||
group.module === role.metadata.annotations?.[rbacAnnotations.MODULE]
|
group.module === role.metadata.annotations?.[rbacAnnotations.MODULE]
|
||||||
|
@ -50,12 +49,6 @@ const pluginRoleTemplateGroups = computed<RoleTemplateGroup[]>(() => {
|
||||||
});
|
});
|
||||||
return groups;
|
return groups;
|
||||||
});
|
});
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
if (plugin?.value) {
|
|
||||||
handleFetchRoles();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -14,95 +14,15 @@ import {
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
import PluginListItem from "./components/PluginListItem.vue";
|
import PluginListItem from "./components/PluginListItem.vue";
|
||||||
import PluginUploadModal from "./components/PluginUploadModal.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 { apiClient } from "@/utils/api-client";
|
||||||
import type { PluginList } from "@halo-dev/api-client";
|
|
||||||
import { usePermission } from "@/utils/permission";
|
import { usePermission } from "@/utils/permission";
|
||||||
import { onBeforeRouteLeave } from "vue-router";
|
|
||||||
import FilterTag from "@/components/filter/FilterTag.vue";
|
import FilterTag from "@/components/filter/FilterTag.vue";
|
||||||
import FilteCleanButton from "@/components/filter/FilterCleanButton.vue";
|
import FilteCleanButton from "@/components/filter/FilterCleanButton.vue";
|
||||||
import { getNode } from "@formkit/core";
|
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<PluginList>({
|
|
||||||
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 {
|
interface EnabledItem {
|
||||||
label: string;
|
label: string;
|
||||||
value?: boolean;
|
value?: boolean;
|
||||||
|
@ -113,6 +33,16 @@ interface SortItem {
|
||||||
value: string;
|
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[] = [
|
const EnabledItems: EnabledItem[] = [
|
||||||
{
|
{
|
||||||
label: "全部",
|
label: "全部",
|
||||||
|
@ -144,12 +74,12 @@ const selectedSortItem = ref<SortItem>();
|
||||||
|
|
||||||
function handleEnabledItemChange(enabledItem: EnabledItem) {
|
function handleEnabledItemChange(enabledItem: EnabledItem) {
|
||||||
selectedEnabledItem.value = enabledItem;
|
selectedEnabledItem.value = enabledItem;
|
||||||
handleFetchPlugins({ page: 1 });
|
page.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSortItemChange(sortItem?: SortItem) {
|
function handleSortItemChange(sortItem?: SortItem) {
|
||||||
selectedSortItem.value = sortItem;
|
selectedSortItem.value = sortItem;
|
||||||
handleFetchPlugins({ page: 1 });
|
page.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeywordChange() {
|
function handleKeywordChange() {
|
||||||
|
@ -157,12 +87,12 @@ function handleKeywordChange() {
|
||||||
if (keywordNode) {
|
if (keywordNode) {
|
||||||
keyword.value = keywordNode._value as string;
|
keyword.value = keywordNode._value as string;
|
||||||
}
|
}
|
||||||
handleFetchPlugins({ page: 1 });
|
page.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClearKeyword() {
|
function handleClearKeyword() {
|
||||||
keyword.value = "";
|
keyword.value = "";
|
||||||
handleFetchPlugins({ page: 1 });
|
page.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasFilters = computed(() => {
|
const hasFilters = computed(() => {
|
||||||
|
@ -177,14 +107,47 @@ function handleClearFilters() {
|
||||||
selectedEnabledItem.value = undefined;
|
selectedEnabledItem.value = undefined;
|
||||||
selectedSortItem.value = undefined;
|
selectedSortItem.value = undefined;
|
||||||
keyword.value = "";
|
keyword.value = "";
|
||||||
handleFetchPlugins({ page: 1 });
|
page.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { data, isLoading, isFetching, refetch } = useQuery<Plugin[]>({
|
||||||
|
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;
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<PluginUploadModal
|
<PluginUploadModal
|
||||||
v-if="currentUserHasPermission(['system:plugins:manage'])"
|
v-if="currentUserHasPermission(['system:plugins:manage'])"
|
||||||
v-model:visible="pluginInstall"
|
v-model:visible="pluginInstall"
|
||||||
@close="handleFetchPlugins()"
|
@close="refetch()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VPageHeader title="插件">
|
<VPageHeader title="插件">
|
||||||
|
@ -302,11 +265,11 @@ function handleClearFilters() {
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<div
|
<div
|
||||||
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
class="group cursor-pointer rounded p-1 hover:bg-gray-200"
|
||||||
@click="handleFetchPlugins()"
|
@click="refetch()"
|
||||||
>
|
>
|
||||||
<IconRefreshLine
|
<IconRefreshLine
|
||||||
v-tooltip="`刷新`"
|
v-tooltip="`刷新`"
|
||||||
:class="{ 'animate-spin text-gray-900': loading }"
|
:class="{ 'animate-spin text-gray-900': isFetching }"
|
||||||
class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
|
class="h-4 w-4 text-gray-600 group-hover:text-gray-900"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -317,16 +280,16 @@ function handleClearFilters() {
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<VLoading v-if="loading" />
|
<VLoading v-if="isLoading" />
|
||||||
|
|
||||||
<Transition v-else-if="!plugins.total" appear name="fade">
|
<Transition v-else-if="!data?.length" appear name="fade">
|
||||||
<VEmpty
|
<VEmpty
|
||||||
message="当前没有已安装的插件,你可以尝试刷新或者安装新插件"
|
message="当前没有已安装的插件,你可以尝试刷新或者安装新插件"
|
||||||
title="当前没有已安装的插件"
|
title="当前没有已安装的插件"
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<VSpace>
|
<VSpace>
|
||||||
<VButton @click="handleFetchPlugins()">刷新</VButton>
|
<VButton :loading="isFetching" @click="refetch()">刷新</VButton>
|
||||||
<VButton
|
<VButton
|
||||||
v-permission="['system:plugins:manage']"
|
v-permission="['system:plugins:manage']"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
|
@ -347,8 +310,8 @@ function handleClearFilters() {
|
||||||
class="box-border h-full w-full divide-y divide-gray-100"
|
class="box-border h-full w-full divide-y divide-gray-100"
|
||||||
role="list"
|
role="list"
|
||||||
>
|
>
|
||||||
<li v-for="(plugin, index) in plugins.items" :key="index">
|
<li v-for="(plugin, index) in data" :key="index">
|
||||||
<PluginListItem :plugin="plugin" @reload="handleFetchPlugins()" />
|
<PluginListItem :plugin="plugin" @reload="refetch()" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Transition>
|
</Transition>
|
||||||
|
@ -356,11 +319,10 @@ function handleClearFilters() {
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
<div class="bg-white sm:flex sm:items-center sm:justify-end">
|
||||||
<VPagination
|
<VPagination
|
||||||
:page="plugins.page"
|
v-model:page="page"
|
||||||
:size="plugins.size"
|
v-model:size="size"
|
||||||
:total="plugins.total"
|
:total="total"
|
||||||
:size-options="[20, 30, 50, 100]"
|
:size-options="[10, 20, 30, 50, 100]"
|
||||||
@change="handlePaginationChange"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue