refactor: use tanstack query to refactor theme-related fetching (#3588)

#### 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. 测试主题管理列表的数据是否加载正常。
2. 测试预览主题弹框中设置数据是否加载正常。

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/3576/head^2
Ryan Wang 2023-03-27 15:56:17 +08:00 committed by GitHub
parent bd9f590c1e
commit 5b3b473cb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 69 additions and 93 deletions

View File

@ -17,11 +17,11 @@ import LazyImage from "@/components/image/LazyImage.vue";
import ThemePreviewModal from "./preview/ThemePreviewModal.vue"; import ThemePreviewModal from "./preview/ThemePreviewModal.vue";
import ThemeUploadModal from "./ThemeUploadModal.vue"; import ThemeUploadModal from "./ThemeUploadModal.vue";
import ThemeListItem from "./components/ThemeListItem.vue"; import ThemeListItem from "./components/ThemeListItem.vue";
import { computed, ref, watch } from "vue"; import { computed, ref } from "vue";
import type { Theme } from "@halo-dev/api-client"; import type { Theme } from "@halo-dev/api-client";
import { apiClient } from "@/utils/api-client"; import { apiClient } from "@/utils/api-client";
import { onBeforeRouteLeave } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useQuery } from "@tanstack/vue-query";
const { t } = useI18n(); const { t } = useI18n();
@ -44,11 +44,8 @@ const emit = defineEmits<{
}>(); }>();
const activeTab = ref("installed"); const activeTab = ref("installed");
const themes = ref<Theme[]>([] as Theme[]);
const loading = ref(false);
const themeUploadVisible = ref(false); const themeUploadVisible = ref(false);
const creating = ref(false); const creating = ref(false);
const refreshInterval = ref();
const modalTitle = computed(() => { const modalTitle = computed(() => {
return activeTab.value === "installed" return activeTab.value === "installed"
@ -56,51 +53,36 @@ const modalTitle = computed(() => {
: t("core.theme.list_modal.titles.not_installed_themes"); : t("core.theme.list_modal.titles.not_installed_themes");
}); });
const handleFetchThemes = async (options?: { mute?: boolean }) => { const {
try { data: themes,
clearInterval(refreshInterval.value); isLoading,
isFetching,
if (!options?.mute) { refetch,
loading.value = true; } = useQuery<Theme[]>({
} queryKey: ["themes", activeTab],
queryFn: async () => {
const { data } = await apiClient.theme.listThemes({ const { data } = await apiClient.theme.listThemes({
page: 0, page: 0,
size: 0, size: 0,
uninstalled: activeTab.value !== "installed", uninstalled: activeTab.value !== "installed",
}); });
themes.value = data.items; return data.items;
},
refetchOnWindowFocus: false,
refetchInterval(data) {
if (activeTab.value !== "installed") { if (activeTab.value !== "installed") {
return; return false;
} }
const deletedThemes = themes.value.filter( const deletingThemes = data?.filter(
(theme) => !!theme.metadata.deletionTimestamp (theme) => !!theme.metadata.deletionTimestamp
); );
if (deletedThemes.length) { return deletingThemes?.length ? 3000 : false;
refreshInterval.value = setInterval(() => { },
handleFetchThemes({ mute: true }); enabled: computed(() => props.visible),
}, 3000);
}
} catch (e) {
console.error("Failed to fetch themes", e);
} finally {
loading.value = false;
}
};
onBeforeRouteLeave(() => {
clearInterval(refreshInterval.value);
}); });
watch(
() => activeTab.value,
() => {
handleFetchThemes();
}
);
const handleCreateTheme = async (theme: Theme) => { const handleCreateTheme = async (theme: Theme) => {
try { try {
creating.value = true; creating.value = true;
@ -120,7 +102,7 @@ const handleCreateTheme = async (theme: Theme) => {
console.error("Failed to create theme", error); console.error("Failed to create theme", error);
} finally { } finally {
creating.value = false; creating.value = false;
handleFetchThemes(); refetch();
} }
}; };
@ -137,19 +119,8 @@ const handleSelectTheme = (theme: Theme) => {
onVisibleChange(false); onVisibleChange(false);
}; };
watch(
() => props.visible,
(visible) => {
if (visible) {
handleFetchThemes();
} else {
clearInterval(refreshInterval.value);
}
}
);
defineExpose({ defineExpose({
handleFetchThemes, handleFetchThemes: refetch,
}); });
// preview // preview
@ -193,15 +164,15 @@ const handleOpenInstallModal = () => {
:label="$t('core.theme.list_modal.tabs.installed')" :label="$t('core.theme.list_modal.tabs.installed')"
class="-mx-[16px]" class="-mx-[16px]"
> >
<VLoading v-if="loading" /> <VLoading v-if="isLoading" />
<Transition v-else-if="!themes.length" appear name="fade"> <Transition v-else-if="!themes?.length" appear name="fade">
<VEmpty <VEmpty
:message="$t('core.theme.list_modal.empty.message')" :message="$t('core.theme.list_modal.empty.message')"
:title="$t('core.theme.list_modal.empty.title')" :title="$t('core.theme.list_modal.empty.title')"
> >
<template #actions> <template #actions>
<VSpace> <VSpace>
<VButton :loading="loading" @click="handleFetchThemes()"> <VButton :loading="isFetching" @click="refetch()">
{{ $t("core.common.buttons.refresh") }} {{ $t("core.common.buttons.refresh") }}
</VButton> </VButton>
<VButton <VButton
@ -233,7 +204,7 @@ const handleOpenInstallModal = () => {
:is-selected=" :is-selected="
theme.metadata.name === selectedTheme?.metadata?.name theme.metadata.name === selectedTheme?.metadata?.name
" "
@reload="handleFetchThemes({ mute: true })" @reload="refetch"
@preview="handleOpenPreview(theme)" @preview="handleOpenPreview(theme)"
@upgrade="handleOpenUpgradeModal(theme)" @upgrade="handleOpenUpgradeModal(theme)"
/> />
@ -246,14 +217,14 @@ const handleOpenInstallModal = () => {
:label="$t('core.theme.list_modal.tabs.not_installed')" :label="$t('core.theme.list_modal.tabs.not_installed')"
class="-mx-[16px]" class="-mx-[16px]"
> >
<VLoading v-if="loading" /> <VLoading v-if="isLoading" />
<Transition v-else-if="!themes.length" appear name="fade"> <Transition v-else-if="!themes?.length" appear name="fade">
<VEmpty <VEmpty
:title="$t('core.theme.list_modal.not_installed_empty.title')" :title="$t('core.theme.list_modal.not_installed_empty.title')"
> >
<template #actions> <template #actions>
<VSpace> <VSpace>
<VButton :loading="loading" @click="handleFetchThemes"> <VButton :loading="isFetching" @click="refetch">
{{ $t("core.common.buttons.refresh") }} {{ $t("core.common.buttons.refresh") }}
</VButton> </VButton>
</VSpace> </VSpace>
@ -372,7 +343,7 @@ const handleOpenInstallModal = () => {
v-if="visible" v-if="visible"
v-model:visible="themeUploadVisible" v-model:visible="themeUploadVisible"
:upgrade-theme="themeToUpgrade" :upgrade-theme="themeToUpgrade"
@close="handleFetchThemes" @close="refetch"
/> />
<ThemePreviewModal <ThemePreviewModal

View File

@ -26,6 +26,7 @@ import {
import { storeToRefs } from "pinia"; import { storeToRefs } from "pinia";
import { computed, markRaw, ref, watch } from "vue"; import { computed, markRaw, ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useQuery } from "@tanstack/vue-query";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
@ -55,33 +56,34 @@ interface SettingTab {
const { activatedTheme } = storeToRefs(useThemeStore()); const { activatedTheme } = storeToRefs(useThemeStore());
const previewFrame = ref<HTMLIFrameElement | null>(null); const previewFrame = ref<HTMLIFrameElement | null>(null);
const themes = ref<Theme[]>([] as Theme[]);
const themesVisible = ref(false); const themesVisible = ref(false);
const switching = ref(false); const switching = ref(false);
const selectedTheme = ref<Theme>(); const selectedTheme = ref<Theme>();
const handleFetchThemes = async () => { const { data: themes } = useQuery<Theme[]>({
try { queryKey: ["themes"],
queryFn: async () => {
const { data } = await apiClient.theme.listThemes({ const { data } = await apiClient.theme.listThemes({
page: 0, page: 0,
size: 0, size: 0,
uninstalled: false, uninstalled: false,
}); });
themes.value = data.items; return data.items;
} catch (e) { },
console.error("Failed to fetch themes", e); refetchOnWindowFocus: false,
} enabled: computed(() => props.visible),
}; });
watch( watch(
() => props.visible, () => props.visible,
(visible) => { (visible) => {
if (visible) { if (visible) {
handleFetchThemes();
selectedTheme.value = props.theme || activatedTheme?.value; selectedTheme.value = props.theme || activatedTheme?.value;
} else { } else {
themesVisible.value = false; setTimeout(() => {
settingsVisible.value = false; themesVisible.value = false;
settingsVisible.value = false;
}, 200);
} }
} }
); );
@ -121,39 +123,42 @@ const modalTitle = computed(() => {
}); });
// theme settings // theme settings
const setting = ref<Setting>();
const configMap = ref<ConfigMap>();
const saving = ref(false); const saving = ref(false);
const settingTabs = ref<SettingTab[]>([] as SettingTab[]); const settingTabs = ref<SettingTab[]>([] as SettingTab[]);
const activeSettingTab = ref(""); const activeSettingTab = ref("");
const settingsVisible = ref(false); const settingsVisible = ref(false);
const { data: setting, refetch: handleFetchSettings } = useQuery<Setting>({
queryKey: ["theme-setting", selectedTheme],
queryFn: async () => {
const { data } = await apiClient.theme.fetchThemeSetting({
name: selectedTheme?.value?.metadata.name as string,
});
return data;
},
refetchOnWindowFocus: false,
enabled: computed(() => !!selectedTheme.value?.spec.settingName),
});
const { data: configMap, refetch: handleFetchConfigMap } = useQuery<ConfigMap>({
queryKey: ["theme-configMap", selectedTheme],
queryFn: async () => {
const { data } = await apiClient.theme.fetchThemeConfig({
name: selectedTheme?.value?.metadata.name as string,
});
return data;
},
refetchOnWindowFocus: false,
enabled: computed(() => !!selectedTheme.value?.spec.configMapName),
});
const { formSchema, configMapFormData, convertToSave } = useSettingFormConvert( const { formSchema, configMapFormData, convertToSave } = useSettingFormConvert(
setting, setting,
configMap, configMap,
activeSettingTab activeSettingTab
); );
const handleFetchSettings = async () => {
if (!selectedTheme?.value) return;
const { data } = await apiClient.theme.fetchThemeSetting({
name: selectedTheme?.value?.metadata.name,
});
setting.value = data;
};
const handleFetchConfigMap = async () => {
if (!selectedTheme?.value) return;
const { data } = await apiClient.theme.fetchThemeConfig({
name: selectedTheme?.value?.metadata.name,
});
configMap.value = data;
};
const handleSaveConfigMap = async () => { const handleSaveConfigMap = async () => {
saving.value = true; saving.value = true;
@ -164,7 +169,7 @@ const handleSaveConfigMap = async () => {
return; return;
} }
const { data: newConfigMap } = await apiClient.theme.updateThemeConfig({ await apiClient.theme.updateThemeConfig({
name: selectedTheme?.value?.metadata.name, name: selectedTheme?.value?.metadata.name,
configMap: configMapToUpdate, configMap: configMapToUpdate,
}); });
@ -172,7 +177,7 @@ const handleSaveConfigMap = async () => {
Toast.success(t("core.common.toast.save_success")); Toast.success(t("core.common.toast.save_success"));
await handleFetchSettings(); await handleFetchSettings();
configMap.value = newConfigMap; await handleFetchConfigMap();
saving.value = false; saving.value = false;