perf: improve theme detail and settings page

pull/609/head
Ryan Wang 2022-09-10 23:11:15 +08:00
parent 6c71424421
commit 2da147b3bf
7 changed files with 125 additions and 103 deletions

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
// core libs // core libs
import { computed, inject, ref } from "vue"; import { inject } from "vue";
import { RouterLink } from "vue-router"; import { RouterLink } from "vue-router";
// components // components
@ -10,37 +10,30 @@ import { VAlert, VSpace, VTag } from "@halo-dev/components";
import type { ComputedRef, Ref } from "vue"; import type { ComputedRef, Ref } from "vue";
import type { Theme } from "@halo-dev/api-client"; import type { Theme } from "@halo-dev/api-client";
const selectedTheme = inject<Ref<Theme>>( const selectedTheme = inject<Ref<Theme | undefined>>("selectedTheme");
"selectedTheme", const isActivated = inject<ComputedRef<boolean>>("isActivated");
ref<Theme>({} as Theme)
);
const isActivated = inject<ComputedRef<boolean>>(
"isActivated",
computed(() => false)
);
</script> </script>
<template> <template>
<div class="bg-white px-4 py-4 sm:px-6"> <div class="bg-white px-4 py-4 sm:px-6">
<div class="flex flex-row gap-3"> <div class="flex flex-row gap-3">
<div v-if="selectedTheme.spec?.logo">
<div <div
class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm" class="h-12 w-12 overflow-hidden rounded border bg-white hover:shadow-sm"
> >
<img <img
:alt="selectedTheme.spec?.displayName" :key="selectedTheme?.metadata.name"
:src="selectedTheme.spec?.logo" :alt="selectedTheme?.spec.displayName"
:src="selectedTheme?.spec.logo"
class="h-full w-full" class="h-full w-full"
/> />
</div> </div>
</div>
<div> <div>
<h3 class="text-lg font-medium leading-6 text-gray-900"> <h3 class="text-lg font-medium leading-6 text-gray-900">
{{ selectedTheme.spec?.displayName }} {{ selectedTheme?.spec.displayName }}
</h3> </h3>
<p class="mt-1 flex max-w-2xl items-center gap-2"> <p class="mt-1 flex max-w-2xl items-center gap-2">
<span class="text-sm text-gray-500"> <span class="text-sm text-gray-500">
{{ selectedTheme.spec?.version }} {{ selectedTheme?.spec.version }}
</span> </span>
<VTag> <VTag>
{{ isActivated ? "当前启用" : "未启用" }} {{ isActivated ? "当前启用" : "未启用" }}
@ -56,7 +49,7 @@ const isActivated = inject<ComputedRef<boolean>>(
> >
<dt class="text-sm font-medium text-gray-900">ID</dt> <dt class="text-sm font-medium text-gray-900">ID</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ selectedTheme.metadata?.name }} {{ selectedTheme?.metadata.name }}
</dd> </dd>
</div> </div>
<div <div
@ -64,7 +57,7 @@ const isActivated = inject<ComputedRef<boolean>>(
> >
<dt class="text-sm font-medium text-gray-900">作者</dt> <dt class="text-sm font-medium text-gray-900">作者</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ selectedTheme.spec?.author?.name }} {{ selectedTheme?.spec.author.name }}
</dd> </dd>
</div> </div>
<div <div
@ -72,8 +65,8 @@ const isActivated = inject<ComputedRef<boolean>>(
> >
<dt class="text-sm font-medium text-gray-900">网站</dt> <dt class="text-sm font-medium text-gray-900">网站</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
<a :href="selectedTheme.spec?.website" target="_blank"> <a :href="selectedTheme?.spec.website" target="_blank">
{{ selectedTheme.spec?.website }} {{ selectedTheme?.spec.website }}
</a> </a>
</dd> </dd>
</div> </div>
@ -82,8 +75,8 @@ const isActivated = inject<ComputedRef<boolean>>(
> >
<dt class="text-sm font-medium text-gray-900">源码仓库</dt> <dt class="text-sm font-medium text-gray-900">源码仓库</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
<a :href="selectedTheme.spec?.repo" target="_blank"> <a :href="selectedTheme?.spec.repo" target="_blank">
{{ selectedTheme.spec?.repo }} {{ selectedTheme?.spec.repo }}
</a> </a>
</dd> </dd>
</div> </div>
@ -92,7 +85,7 @@ const isActivated = inject<ComputedRef<boolean>>(
> >
<dt class="text-sm font-medium text-gray-900">当前版本</dt> <dt class="text-sm font-medium text-gray-900">当前版本</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ selectedTheme.spec?.version }} {{ selectedTheme?.spec.version }}
</dd> </dd>
</div> </div>
<div <div
@ -100,7 +93,7 @@ const isActivated = inject<ComputedRef<boolean>>(
> >
<dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt> <dt class="text-sm font-medium text-gray-900">Halo 版本要求</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-3 sm:mt-0">
{{ selectedTheme.spec?.require }} {{ selectedTheme?.spec.require }}
</dd> </dd>
</div> </div>
<div <div
@ -118,7 +111,7 @@ const isActivated = inject<ComputedRef<boolean>>(
<ul class="mt-2 space-y-2"> <ul class="mt-2 space-y-2">
<li> <li>
<div <div
class="inline-flex w-96 cursor-pointer flex-row flex-col gap-y-3 rounded border p-5 hover:border-primary" class="inline-flex w-96 cursor-pointer flex-col gap-y-3 rounded border p-5 hover:border-primary"
> >
<RouterLink <RouterLink
:to="{ :to="{
@ -138,7 +131,7 @@ const isActivated = inject<ComputedRef<boolean>>(
</li> </li>
<li> <li>
<div <div
class="inline-flex w-96 cursor-pointer flex-row flex-col gap-y-3 rounded border p-5 hover:border-primary" class="inline-flex w-96 cursor-pointer flex-col gap-y-3 rounded border p-5 hover:border-primary"
> >
<span class="font-medium hover:text-blue-400"> <span class="font-medium hover:text-blue-400">
run.halo.plugins.photos run.halo.plugins.photos

View File

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
// core libs // core libs
import { computed, inject, onMounted, ref } from "vue"; import { computed, inject, watch } from "vue";
// components // components
import { VButton } from "@halo-dev/components"; import { VButton } from "@halo-dev/components";
@ -11,12 +11,15 @@ import type { Theme } from "@halo-dev/api-client";
// hooks // hooks
import { useSettingForm } from "@halo-dev/admin-shared"; import { useSettingForm } from "@halo-dev/admin-shared";
import { useRouteParams } from "@vueuse/router";
const selectedTheme = inject<Ref<Theme>>("selectedTheme", ref({} as Theme)); const group = useRouteParams<string>("group");
const group = inject<Ref<string | undefined>>("activeTab");
const selectedTheme = inject<Ref<Theme | undefined>>("selectedTheme");
const settingName = computed(() => selectedTheme?.value?.spec.settingName);
const configMapName = computed(() => selectedTheme?.value?.spec.configMapName);
const settingName = computed(() => selectedTheme.value.spec?.settingName);
const configMapName = computed(() => selectedTheme.value.spec?.configMapName);
const { const {
settings, settings,
configMapFormData, configMapFormData,
@ -34,10 +37,16 @@ const formSchema = computed(() => {
?.formSchema; ?.formSchema;
}); });
onMounted(() => { await handleFetchSettings();
await handleFetchConfigMap();
watch(
() => selectedTheme?.value,
() => {
handleFetchSettings(); handleFetchSettings();
handleFetchConfigMap(); handleFetchConfigMap();
}); }
);
</script> </script>
<template> <template>
<div class="bg-white p-4 sm:px-6"> <div class="bg-white p-4 sm:px-6">

View File

@ -64,7 +64,7 @@ onMounted(() => {
<div class="flex h-screen"> <div class="flex h-screen">
<div class="flex-1"> <div class="flex-1">
<div <div
class="grid h-16 grid-cols-2 items-center bg-white py-2 px-4 drop-shadow-sm sm:grid-cols-3" class="grid h-14 grid-cols-2 items-center bg-white px-4 drop-shadow-sm sm:grid-cols-3"
> >
<div> <div>
<h2 class="truncate text-xl font-bold text-gray-800"> <h2 class="truncate text-xl font-bold text-gray-800">
@ -80,7 +80,7 @@ onMounted(() => {
</div> </div>
<div class="flex justify-end"> <div class="flex justify-end">
<VSpace> <VSpace>
<VButton type="default" @click="settingVisible = true"> <VButton size="sm" type="default" @click="settingVisible = true">
<template #icon> <template #icon>
<IconSettings class="h-full w-full" /> <IconSettings class="h-full w-full" />
</template> </template>

View File

@ -11,11 +11,11 @@ import {
VTag, VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import ThemeInstallModal from "./ThemeInstallModal.vue"; import ThemeInstallModal from "./ThemeInstallModal.vue";
import { onMounted, ref } from "vue"; import { ref, watch } from "vue";
import type { Theme } from "@halo-dev/api-client"; import type { Theme } from "@halo-dev/api-client";
import { apiClient } from "@halo-dev/admin-shared"; import { apiClient } from "@halo-dev/admin-shared";
withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
visible: boolean; visible: boolean;
selectedTheme: Theme | null; selectedTheme: Theme | null;
@ -32,6 +32,7 @@ const emit = defineEmits<{
(event: "update:visible", visible: boolean): void; (event: "update:visible", visible: boolean): void;
(event: "close"): void; (event: "close"): void;
(event: "update:selectedTheme", theme: Theme | null): void; (event: "update:selectedTheme", theme: Theme | null): void;
(event: "select", theme: Theme | null): void;
}>(); }>();
const themes = ref<Theme[]>([]); const themes = ref<Theme[]>([]);
@ -71,7 +72,7 @@ const handleUninstall = async (theme: Theme) => {
}); });
}; };
const handleVisibleChange = (visible: boolean) => { const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible); emit("update:visible", visible);
if (!visible) { if (!visible) {
emit("close"); emit("close");
@ -80,10 +81,18 @@ const handleVisibleChange = (visible: boolean) => {
const handleSelectTheme = (theme: Theme) => { const handleSelectTheme = (theme: Theme) => {
emit("update:selectedTheme", theme); emit("update:selectedTheme", theme);
handleVisibleChange(false); emit("select", theme);
onVisibleChange(false);
}; };
onMounted(handleFetchThemes); watch(
() => props.visible,
(visible) => {
if (visible) {
handleFetchThemes();
}
}
);
defineExpose({ defineExpose({
handleFetchThemes, handleFetchThemes,
@ -95,7 +104,7 @@ defineExpose({
:visible="visible" :visible="visible"
:width="888" :width="888"
title="已安装的主题" title="已安装的主题"
@update:visible="handleVisibleChange" @update:visible="onVisibleChange"
> >
<VEmpty <VEmpty
v-if="!themes.length && !loading" v-if="!themes.length && !loading"
@ -212,7 +221,7 @@ defineExpose({
<VButton type="secondary" @click="themeInstall = true"> <VButton type="secondary" @click="themeInstall = true">
安装主题 安装主题
</VButton> </VButton>
<VButton @click="handleVisibleChange(false)"></VButton> <VButton @click="onVisibleChange(false)"></VButton>
</VSpace> </VSpace>
</template> </template>
</VModal> </VModal>

View File

@ -6,17 +6,19 @@ import { useDialog } from "@halo-dev/components";
interface useThemeLifeCycleReturn { interface useThemeLifeCycleReturn {
loading: Ref<boolean>; loading: Ref<boolean>;
activatedTheme: Ref<Theme>; activatedTheme: Ref<Theme | undefined>;
isActivated: ComputedRef<boolean>; isActivated: ComputedRef<boolean>;
handleActiveTheme: () => void; handleActiveTheme: () => void;
} }
export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn { export function useThemeLifeCycle(
const activatedTheme = ref<Theme>({} as Theme); theme: Ref<Theme | undefined>
): useThemeLifeCycleReturn {
const activatedTheme = ref<Theme | undefined>();
const loading = ref(false); const loading = ref(false);
const isActivated = computed(() => { const isActivated = computed(() => {
return activatedTheme.value?.metadata?.name === theme.value?.metadata?.name; return activatedTheme.value?.metadata.name === theme.value?.metadata.name;
}); });
const dialog = useDialog(); const dialog = useDialog();
@ -54,7 +56,7 @@ export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn {
const handleActiveTheme = async () => { const handleActiveTheme = async () => {
dialog.info({ dialog.info({
title: "是否确认启用当前主题", title: "是否确认启用当前主题",
description: theme.value.spec.displayName, description: theme.value?.spec.displayName,
onConfirm: async () => { onConfirm: async () => {
try { try {
const { data: systemConfigMap } = const { data: systemConfigMap } =
@ -66,7 +68,7 @@ export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn {
const themeConfigToUpdate = JSON.parse( const themeConfigToUpdate = JSON.parse(
systemConfigMap.data?.theme || "{}" systemConfigMap.data?.theme || "{}"
); );
themeConfigToUpdate.active = theme.value?.metadata?.name; themeConfigToUpdate.active = theme.value?.metadata.name;
systemConfigMap.data["theme"] = JSON.stringify(themeConfigToUpdate); systemConfigMap.data["theme"] = JSON.stringify(themeConfigToUpdate);
await apiClient.extension.configMap.updatev1alpha1ConfigMap({ await apiClient.extension.configMap.updatev1alpha1ConfigMap({

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
// core libs // core libs
import type { ComputedRef, Ref } from "vue"; import { nextTick, type ComputedRef, type Ref } from "vue";
import { computed, provide, ref, watch, watchEffect } from "vue"; import { computed, provide, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
// libs // libs
@ -48,25 +48,23 @@ const initialTabs: ThemeTab[] = [
]; ];
const tabs = ref<ThemeTab[]>(cloneDeep(initialTabs)); const tabs = ref<ThemeTab[]>(cloneDeep(initialTabs));
const selectedTheme = ref<Theme>({} as Theme); const selectedTheme = ref<Theme | undefined>();
const themesModal = ref(false); const themesModal = ref(false);
const activeTab = ref(""); const activeTab = ref("");
const { loading, isActivated, activatedTheme, handleActiveTheme } = const { loading, isActivated, activatedTheme, handleActiveTheme } =
useThemeLifeCycle(selectedTheme); useThemeLifeCycle(selectedTheme);
const settingName = computed(() => selectedTheme.value.spec?.settingName); const settingName = computed(() => selectedTheme.value?.spec.settingName);
const configMapName = computed(() => selectedTheme.value.spec?.configMapName); const configMapName = computed(() => selectedTheme.value?.spec.configMapName);
const { settings, handleFetchSettings } = useSettingForm( const { settings, handleFetchSettings } = useSettingForm(
settingName, settingName,
configMapName configMapName
); );
provide<Ref<Theme>>("activatedTheme", activatedTheme); provide<Ref<Theme | undefined>>("selectedTheme", selectedTheme);
provide<Ref<Theme>>("selectedTheme", selectedTheme);
provide<ComputedRef<boolean>>("isActivated", isActivated); provide<ComputedRef<boolean>>("isActivated", isActivated);
provide<Ref<string | undefined>>("activeTab", activeTab);
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -74,11 +72,14 @@ const router = useRouter();
const handleTabChange = (id: string) => { const handleTabChange = (id: string) => {
const tab = tabs.value.find((item) => item.id === id); const tab = tabs.value.find((item) => item.id === id);
if (tab) { if (tab) {
activeTab.value = tab.id;
router.push(tab.route); router.push(tab.route);
} }
}; };
watchEffect(async () => { watch(
() => selectedTheme.value,
async () => {
if (selectedTheme.value) { if (selectedTheme.value) {
// reset tabs // reset tabs
tabs.value = cloneDeep(initialTabs); tabs.value = cloneDeep(initialTabs);
@ -100,16 +101,20 @@ watchEffect(async () => {
}; };
}), }),
] as ThemeTab[]; ] as ThemeTab[];
onTabChange(route.name as string);
} }
}
});
const onTabChange = (routeName: string) => { await nextTick();
if (routeName === "ThemeSetting") {
handleTriggerTabChange();
}
}
);
const handleTriggerTabChange = () => {
if (route.name === "ThemeSetting") {
const tab = tabs.value.find((tab) => { const tab = tabs.value.find((tab) => {
return ( return (
tab.route.name === routeName && tab.route.name === route.name &&
tab.route.params?.group === route.params.group tab.route.params?.group === route.params.group
); );
}); });
@ -117,7 +122,7 @@ const onTabChange = (routeName: string) => {
activeTab.value = tab.id; activeTab.value = tab.id;
return; return;
} }
router.push({ name: "ThemeDetail" }); handleTabChange(tabs.value[0].id);
return; return;
} }
@ -125,12 +130,9 @@ const onTabChange = (routeName: string) => {
activeTab.value = tab ? tab.id : tabs.value[0].id; activeTab.value = tab ? tab.id : tabs.value[0].id;
}; };
watch( watch([() => route.name, () => route.params], async () => {
() => route.name, handleTriggerTabChange();
async (newRouteName) => { });
onTabChange(newRouteName as string);
}
);
</script> </script>
<template> <template>
<BasicLayout> <BasicLayout>
@ -139,7 +141,7 @@ watch(
v-model:selected-theme="selectedTheme" v-model:selected-theme="selectedTheme"
v-model:visible="themesModal" v-model:visible="themesModal"
/> />
<VPageHeader :title="selectedTheme.spec?.displayName"> <VPageHeader :title="selectedTheme?.spec.displayName">
<template #icon> <template #icon>
<IconPalette class="mr-2 self-center" /> <IconPalette class="mr-2 self-center" />
</template> </template>
@ -171,11 +173,7 @@ watch(
<div class="m-0 md:m-4"> <div class="m-0 md:m-4">
<VEmpty <VEmpty
v-if=" v-if="!selectedTheme && !loading"
!selectedTheme.metadata?.name &&
!activatedTheme.metadata?.name &&
!loading
"
message="当前没有已激活或者选择的主题,你可以切换主题或者安装新主题" message="当前没有已激活或者选择的主题,你可以切换主题或者安装新主题"
title="当前没有已激活或已选择的主题" title="当前没有已激活或已选择的主题"
> >
@ -205,7 +203,18 @@ watch(
</template> </template>
</VCard> </VCard>
<div> <div>
<RouterView :key="activeTab" /> <RouterView :key="activeTab" v-slot="{ Component }">
<template v-if="Component">
<Suspense>
<component :is="Component"></component>
<template #fallback>
<div class="flex h-32 w-full justify-center bg-white">
<span class="text-sm text-gray-600">加载中...</span>
</div>
</template>
</Suspense>
</template>
</RouterView>
</div> </div>
</div> </div>
</div> </div>

View File

@ -76,7 +76,7 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
</div> </div>
<div class="flex"> <div class="flex">
<div <div
class="inline-flex flex-col flex-col-reverse items-end gap-4 sm:flex-row sm:items-center sm:gap-6" class="inline-flex flex-col items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
> >
<FloatingTooltip <FloatingTooltip
v-if="plugin?.status?.phase === 'FAILED'" v-if="plugin?.status?.phase === 'FAILED'"