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

View File

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

View File

@ -64,7 +64,7 @@ onMounted(() => {
<div class="flex h-screen">
<div class="flex-1">
<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>
<h2 class="truncate text-xl font-bold text-gray-800">
@ -80,7 +80,7 @@ onMounted(() => {
</div>
<div class="flex justify-end">
<VSpace>
<VButton type="default" @click="settingVisible = true">
<VButton size="sm" type="default" @click="settingVisible = true">
<template #icon>
<IconSettings class="h-full w-full" />
</template>

View File

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

View File

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

View File

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

View File

@ -76,7 +76,7 @@ const { isStarted, changeStatus, uninstall } = usePluginLifeCycle(plugin);
</div>
<div class="flex">
<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
v-if="plugin?.status?.phase === 'FAILED'"