mirror of https://github.com/halo-dev/halo-admin
perf: improve theme detail and settings page
parent
6c71424421
commit
2da147b3bf
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'"
|
||||
|
|
Loading…
Reference in New Issue