perf: improve plugin detail and settings page

pull/3445/head
Ryan Wang 2022-09-10 17:23:51 +08:00
parent bcd997231a
commit 088196e6ae
7 changed files with 80 additions and 58 deletions

View File

@ -1,3 +1,2 @@
export { default as BlankLayout } from "./BlankLayout.vue"; export { default as BlankLayout } from "./BlankLayout.vue";
export { default as BasicLayout } from "./BasicLayout.vue"; export { default as BasicLayout } from "./BasicLayout.vue";
export { default as PluginLayout } from "./PluginLayout.vue";

View File

@ -8,7 +8,7 @@ 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";
const plugin = inject<Ref<Plugin>>("plugin", ref({} as Plugin)); const plugin = inject<Ref<Plugin | undefined>>("plugin");
const { changeStatus, isStarted } = usePluginLifeCycle(plugin); const { changeStatus, isStarted } = usePluginLifeCycle(plugin);
interface RoleTemplateGroup { interface RoleTemplateGroup {
@ -23,7 +23,7 @@ const handleFetchRoles = async () => {
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; pluginRoleTemplates.value = data.items;
} catch (e) { } catch (e) {
@ -51,7 +51,7 @@ const pluginRoleTemplateGroups = computed<RoleTemplateGroup[]>(() => {
}); });
watchEffect(() => { watchEffect(() => {
if (plugin.value.metadata?.name) { if (plugin?.value) {
handleFetchRoles(); handleFetchRoles();
} }
}); });
@ -62,7 +62,7 @@ watchEffect(() => {
<div> <div>
<h3 class="text-lg font-medium leading-6 text-gray-900">插件信息</h3> <h3 class="text-lg font-medium leading-6 text-gray-900">插件信息</h3>
<p class="mt-1 flex max-w-2xl items-center gap-2 text-sm text-gray-500"> <p class="mt-1 flex max-w-2xl items-center gap-2 text-sm text-gray-500">
<span>{{ plugin?.spec?.version }}</span> <span>{{ plugin?.spec.version }}</span>
<VTag> <VTag>
{{ isStarted ? "已启用" : "未启用" }} {{ isStarted ? "已启用" : "未启用" }}
</VTag> </VTag>
@ -79,7 +79,7 @@ watchEffect(() => {
> >
<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-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ plugin?.spec?.displayName }} {{ plugin?.spec.displayName }}
</dd> </dd>
</div> </div>
<div <div
@ -87,7 +87,7 @@ watchEffect(() => {
> >
<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-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ plugin?.spec?.version }} {{ plugin?.spec.version }}
</dd> </dd>
</div> </div>
<div <div
@ -95,7 +95,7 @@ watchEffect(() => {
> >
<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-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
{{ plugin?.spec?.requires }} {{ plugin?.spec.requires }}
</dd> </dd>
</div> </div>
<div <div
@ -103,8 +103,8 @@ watchEffect(() => {
> >
<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-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<a :href="plugin?.spec?.homepage" target="_blank"> <a :href="plugin?.spec.homepage" target="_blank">
{{ plugin?.spec?.author }} {{ plugin?.spec.author }}
</a> </a>
</dd> </dd>
</div> </div>
@ -114,7 +114,7 @@ watchEffect(() => {
<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-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<ul <ul
v-if="plugin?.spec?.license && plugin?.spec?.license.length" v-if="plugin?.spec.license && plugin?.spec.license.length"
class="list-inside list-disc" class="list-inside list-disc"
> >
<li v-for="(license, index) in plugin.spec.license" :key="index"> <li v-for="(license, index) in plugin.spec.license" :key="index">
@ -132,7 +132,7 @@ watchEffect(() => {
class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6" class="bg-white px-4 py-5 hover:bg-gray-50 sm:grid sm:grid-cols-6 sm:gap-4 sm:px-6"
> >
<dt class="text-sm font-medium text-gray-900">模型定义</dt> <dt class="text-sm font-medium text-gray-900">模型定义</dt>
<dd class="mt-1 sm:col-span-2 sm:mt-0"> <dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<span></span> <span></span>
</dd> </dd>
</div> </div>

View File

@ -1,22 +1,24 @@
<script lang="ts" setup> <script lang="ts" setup>
// core libs // core libs
import { computed, inject, ref, watchEffect } from "vue"; import { computed, ref } from "vue";
// hooks // hooks
import { useSettingForm } from "@halo-dev/admin-shared"; import { apiClient, useSettingForm } from "@halo-dev/admin-shared";
// components // components
import { VButton } from "@halo-dev/components"; import { VButton } from "@halo-dev/components";
// types // types
import type { Ref } from "vue";
import type { Plugin } from "@halo-dev/api-client"; import type { Plugin } from "@halo-dev/api-client";
import { useRouteParams } from "@vueuse/router";
const plugin = inject<Ref<Plugin>>("plugin", ref({} as Plugin)); const name = useRouteParams<string>("name");
const group = inject<Ref<string | undefined>>("activeTab"); const group = useRouteParams<string>("group");
const settingName = computed(() => plugin.value.spec?.settingName); const plugin = ref<Plugin | undefined>();
const configMapName = computed(() => plugin.value.spec?.configMapName);
const settingName = computed(() => plugin?.value?.spec.settingName);
const configMapName = computed(() => plugin?.value?.spec.configMapName);
const { const {
settings, settings,
@ -31,16 +33,28 @@ const formSchema = computed(() => {
if (!settings?.value?.spec) { if (!settings?.value?.spec) {
return; return;
} }
return settings.value.spec.find((item) => item.group === group?.value) return settings.value.spec.find((item) => item.group === group.value)
?.formSchema; ?.formSchema;
}); });
watchEffect(async () => { const handleFetchPlugin = async () => {
try {
const { data } =
await apiClient.extension.plugin.getpluginHaloRunV1alpha1Plugin({
name: name.value,
});
plugin.value = data;
if (settingName.value && configMapName.value) { if (settingName.value && configMapName.value) {
await handleFetchSettings(); await handleFetchSettings();
await handleFetchConfigMap(); await handleFetchConfigMap();
} }
}); } catch (e) {
console.error("Failed to fetch plugin and settings", e);
}
};
await handleFetchPlugin();
</script> </script>
<template> <template>
<div class="bg-white p-4 sm:px-6"> <div class="bg-white p-4 sm:px-6">

View File

@ -13,10 +13,10 @@ import { formatDatetime } from "@/utils/date";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
plugin: Plugin | null; plugin?: Plugin;
}>(), }>(),
{ {
plugin: null, plugin: undefined,
} }
); );

View File

@ -12,18 +12,18 @@ interface usePluginLifeCycleReturn {
} }
export function usePluginLifeCycle( export function usePluginLifeCycle(
plugin: Ref<Plugin | null> plugin?: Ref<Plugin | undefined>
): usePluginLifeCycleReturn { ): usePluginLifeCycleReturn {
const dialog = useDialog(); const dialog = useDialog();
const isStarted = computed(() => { const isStarted = computed(() => {
return ( return (
plugin.value?.status?.phase === "STARTED" && plugin.value?.spec.enabled plugin?.value?.status?.phase === "STARTED" && plugin.value?.spec.enabled
); );
}); });
const changeStatus = () => { const changeStatus = () => {
if (!plugin.value) return; if (!plugin?.value) return;
const pluginToUpdate = cloneDeep(plugin.value); const pluginToUpdate = cloneDeep(plugin.value);
@ -46,7 +46,7 @@ export function usePluginLifeCycle(
}; };
const uninstall = () => { const uninstall = () => {
if (!plugin.value) return; if (!plugin?.value) return;
const { enabled } = plugin.value.spec; const { enabled } = plugin.value.spec;

View File

@ -1,23 +1,23 @@
<script lang="ts" setup> <script lang="ts" setup>
// core libs // core libs
import { computed, onMounted, provide, ref, watch } from "vue"; import { computed, nextTick, onMounted, provide, ref, watch } from "vue";
import { RouterView, useRoute, useRouter } from "vue-router"; import { RouterView, useRoute, useRouter } from "vue-router";
import { apiClient } from "../utils/api-client"; import { apiClient } from "@halo-dev/admin-shared";
// libs // libs
import cloneDeep from "lodash.clonedeep"; import cloneDeep from "lodash.clonedeep";
// hooks // hooks
import { useSettingForm } from "../composables"; import { useSettingForm } from "@halo-dev/admin-shared";
// components // components
import { VButton, VCard, VPageHeader, VTabbar } from "@halo-dev/components"; import { VButton, VCard, VPageHeader, VTabbar } from "@halo-dev/components";
import { BasicLayout } from "../layouts"; import { BasicLayout } from "@halo-dev/admin-shared";
// types // types
import type { Ref } from "vue"; import type { Ref } from "vue";
import type { Plugin } from "@halo-dev/api-client"; import type { Plugin } from "@halo-dev/api-client";
import type { FormKitSettingSpec } from "../types/formkit"; import type { FormKitSettingSpec } from "@halo-dev/admin-shared";
interface PluginTab { interface PluginTab {
id: string; id: string;
@ -41,15 +41,15 @@ const initialTabs: PluginTab[] = [
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const plugin = ref<Plugin>({} as Plugin); const plugin = ref<Plugin>();
const tabs = ref<PluginTab[]>(cloneDeep(initialTabs)); const tabs = ref<PluginTab[]>(cloneDeep(initialTabs));
const activeTab = ref<string>(); const activeTab = ref<string>();
provide<Ref<Plugin>>("plugin", plugin); provide<Ref<Plugin | undefined>>("plugin", plugin);
provide<Ref<string | undefined>>("activeTab", activeTab); provide<Ref<string | undefined>>("activeTab", activeTab);
const settingName = computed(() => plugin.value.spec?.settingName); const settingName = computed(() => plugin.value?.spec.settingName);
const configMapName = computed(() => plugin.value.spec?.configMapName); const configMapName = computed(() => plugin.value?.spec.configMapName);
const { settings, handleFetchSettings } = useSettingForm( const { settings, handleFetchSettings } = useSettingForm(
settingName, settingName,
@ -71,15 +71,16 @@ const handleFetchPlugin = async () => {
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);
} }
}; };
const onTabChange = (routeName: string) => { const handleTriggerTabChange = () => {
if (routeName === "PluginSetting") { if (route.name === "PluginSetting") {
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
); );
}); });
@ -87,7 +88,7 @@ const onTabChange = (routeName: string) => {
activeTab.value = tab.id; activeTab.value = tab.id;
return; return;
} }
router.push({ name: "PluginDetail" }); handleTabChange(tabs.value[0].id);
return; return;
} }
const tab = tabs.value.find((tab) => tab.route.name === route.name); const tab = tabs.value.find((tab) => tab.route.name === route.name);
@ -99,6 +100,7 @@ onMounted(async () => {
await handleFetchSettings(); await handleFetchSettings();
tabs.value = cloneDeep(initialTabs); tabs.value = cloneDeep(initialTabs);
if (settings.value && settings.value.spec) { if (settings.value && settings.value.spec) {
tabs.value = [ tabs.value = [
...tabs.value, ...tabs.value,
@ -115,16 +117,16 @@ onMounted(async () => {
}; };
}), }),
] as PluginTab[]; ] as PluginTab[];
onTabChange(route.name as string);
} }
await nextTick();
handleTriggerTabChange();
}); });
watch( watch([() => route.name, () => route.params], () => {
() => route.name, handleTriggerTabChange();
async (newRouteName) => { });
onTabChange(newRouteName as string);
}
);
</script> </script>
<template> <template>
<BasicLayout> <BasicLayout>
@ -150,7 +152,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>
</BasicLayout> </BasicLayout>

View File

@ -1,9 +1,5 @@
import { import { BasicLayout, BlankLayout, definePlugin } from "@halo-dev/admin-shared";
BasicLayout, import PluginLayout from "./layouts/PluginLayout.vue";
BlankLayout,
definePlugin,
PluginLayout,
} from "@halo-dev/admin-shared";
import PluginList from "./PluginList.vue"; import PluginList from "./PluginList.vue";
import PluginSetting from "./PluginSetting.vue"; import PluginSetting from "./PluginSetting.vue";
import PluginDetail from "./PluginDetail.vue"; import PluginDetail from "./PluginDetail.vue";