mirror of https://github.com/halo-dev/halo
feat: add empty state support for core modules
Signed-off-by: Ryan Wang <i@ryanc.cc>pull/3445/head
parent
02997e1d55
commit
a65649b1cd
|
@ -16,3 +16,4 @@ export * from "./components/switch";
|
|||
export * from "./components/dialog";
|
||||
export * from "./components/pagination";
|
||||
export * from "./components/codemirror";
|
||||
export * from "./components/empty";
|
||||
|
|
|
@ -90,6 +90,6 @@ describe("Empty", () => {
|
|||
expect(attributes.src).not.toEqual("/src/components/empty/Empty.svg");
|
||||
|
||||
expect(attributes.src).toEqual("./empty");
|
||||
expect(attributes.alt).toEqual("Empty Status")
|
||||
expect(attributes.alt).toEqual("Empty Status");
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
IconSettings,
|
||||
VButton,
|
||||
VCard,
|
||||
VEmpty,
|
||||
VPageHeader,
|
||||
VPagination,
|
||||
VSpace,
|
||||
|
@ -78,7 +79,24 @@ useExtensionPointsState("PAGES", pagesPublicState);
|
|||
></VTabbar>
|
||||
</template>
|
||||
<div v-if="activeId === 'functional'">
|
||||
<VEmpty
|
||||
v-if="!pagesPublicState.functionalPages.length"
|
||||
message="当前没有功能页面,功能页面通常由各个插件提供,你可以尝试安装新插件以获得支持"
|
||||
title="当前没有功能页面"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton :route="{ name: 'Plugins' }" type="primary">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
安装插件
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconAddCircle,
|
||||
IconListSettings,
|
||||
useDialog,
|
||||
VButton,
|
||||
VCard,
|
||||
VEmpty,
|
||||
VPageHeader,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
|
@ -28,6 +30,7 @@ const menuItems = ref<MenuItem[]>([] as MenuItem[]);
|
|||
const menuTreeItems = ref<MenuTreeItem[]>([] as MenuTreeItem[]);
|
||||
const selectedMenu = ref<Menu | undefined>();
|
||||
const selectedMenuItem = ref<MenuItem | null>(null);
|
||||
const loading = ref(false);
|
||||
const menuListRef = ref();
|
||||
const menuItemEditingModal = ref();
|
||||
|
||||
|
@ -35,6 +38,8 @@ const dialog = useDialog();
|
|||
|
||||
const handleFetchMenuItems = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
if (!selectedMenu.value?.spec.menuItems) {
|
||||
return;
|
||||
}
|
||||
|
@ -52,6 +57,8 @@ const handleFetchMenuItems = async () => {
|
|||
menuTreeItems.value = buildMenuItemsTree(data.items);
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch menu items", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -181,7 +188,25 @@ const handleDelete = async (menuItem: MenuTreeItem) => {
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<VEmpty
|
||||
v-if="!menuItems.length && !loading"
|
||||
message="你可以尝试刷新或者新建菜单项"
|
||||
title="当前没有菜单项"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchMenuItems"> 刷新</VButton>
|
||||
<VButton type="primary" @click="menuItemEditingModal = true">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新增菜单项
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<MenuItemListItem
|
||||
v-else
|
||||
:menu-tree-items="menuTreeItems"
|
||||
@change="handleUpdateInBatch"
|
||||
@delete="handleDelete"
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
useDialog,
|
||||
VButton,
|
||||
VCard,
|
||||
VEmpty,
|
||||
VSpace,
|
||||
} from "@halo-dev/components";
|
||||
import MenuEditingModal from "./MenuEditingModal.vue";
|
||||
|
@ -27,6 +28,7 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const menus = ref<Menu[]>([] as Menu[]);
|
||||
const loading = ref(false);
|
||||
const selectedMenuToUpdate = ref<Menu | null>(null);
|
||||
const menuEditingModal = ref<boolean>(false);
|
||||
|
||||
|
@ -35,6 +37,8 @@ const dialog = useDialog();
|
|||
const handleFetchMenus = async () => {
|
||||
selectedMenuToUpdate.value = null;
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const { data } = await apiClient.extension.menu.listv1alpha1Menu();
|
||||
menus.value = data.items;
|
||||
|
||||
|
@ -49,6 +53,8 @@ const handleFetchMenus = async () => {
|
|||
}
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch menus", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -114,6 +120,17 @@ defineExpose({
|
|||
@close="handleFetchMenus"
|
||||
/>
|
||||
<VCard :bodyClass="['!p-0']" title="菜单">
|
||||
<VEmpty
|
||||
v-if="!menus.length && !loading"
|
||||
message="你可以尝试刷新或者新建菜单"
|
||||
title="当前没有菜单"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton size="sm" @click="handleFetchMenus"> 刷新</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
<div class="divide-y divide-gray-100 bg-white">
|
||||
<div
|
||||
v-for="(menu, index) in menus"
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
<script lang="ts" setup>
|
||||
import {
|
||||
IconAddCircle,
|
||||
IconGitHub,
|
||||
IconMore,
|
||||
useDialog,
|
||||
VButton,
|
||||
VEmpty,
|
||||
VModal,
|
||||
VSpace,
|
||||
VTag,
|
||||
|
@ -33,17 +35,21 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const themes = ref<Theme[]>([]);
|
||||
const loading = ref(false);
|
||||
const themeInstall = ref(false);
|
||||
|
||||
const dialog = useDialog();
|
||||
|
||||
const handleFetchThemes = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
const { data } =
|
||||
await apiClient.extension.theme.listthemeHaloRunV1alpha1Theme();
|
||||
themes.value = data.items;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch themes", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -91,7 +97,25 @@ defineExpose({
|
|||
title="已安装的主题"
|
||||
@update:visible="handleVisibleChange"
|
||||
>
|
||||
<ul class="flex flex-col divide-y divide-gray-100" role="list">
|
||||
<VEmpty
|
||||
v-if="!themes.length && !loading"
|
||||
message="当前没有已安装的主题,你可以尝试刷新或者安装新主题"
|
||||
title="当前没有已安装的主题"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchThemes"> 刷新</VButton>
|
||||
<VButton type="primary" @click="themeInstall = true">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
新增菜单项
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
|
||||
<ul v-else class="flex flex-col divide-y divide-gray-100" role="list">
|
||||
<li
|
||||
v-for="(theme, index) in themes"
|
||||
:key="index"
|
||||
|
|
|
@ -5,6 +5,7 @@ import { apiClient } from "@halo-dev/admin-shared";
|
|||
import { useDialog } from "@halo-dev/components";
|
||||
|
||||
interface useThemeLifeCycleReturn {
|
||||
loading: Ref<boolean>;
|
||||
activatedTheme: Ref<Theme>;
|
||||
isActivated: ComputedRef<boolean>;
|
||||
handleActiveTheme: () => void;
|
||||
|
@ -12,6 +13,7 @@ interface useThemeLifeCycleReturn {
|
|||
|
||||
export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn {
|
||||
const activatedTheme = ref<Theme>({} as Theme);
|
||||
const loading = ref(false);
|
||||
|
||||
const isActivated = computed(() => {
|
||||
return activatedTheme.value?.metadata?.name === theme.value?.metadata?.name;
|
||||
|
@ -21,6 +23,8 @@ export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn {
|
|||
|
||||
const handleFetchActivatedTheme = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const { data } = await apiClient.extension.configMap.getv1alpha1ConfigMap(
|
||||
"system"
|
||||
);
|
||||
|
@ -40,6 +44,8 @@ export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn {
|
|||
activatedTheme.value = themeData;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch active theme", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -76,6 +82,7 @@ export function useThemeLifeCycle(theme: Ref<Theme>): useThemeLifeCycleReturn {
|
|||
onMounted(handleFetchActivatedTheme);
|
||||
|
||||
return {
|
||||
loading,
|
||||
activatedTheme,
|
||||
isActivated,
|
||||
handleActiveTheme,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
// core libs
|
||||
import type { ComputedRef, Ref } from "vue";
|
||||
import { computed, provide, ref, watch, watchEffect } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
|
||||
|
@ -8,7 +9,9 @@ import cloneDeep from "lodash.clonedeep";
|
|||
|
||||
// hooks
|
||||
import { useThemeLifeCycle } from "../composables/use-theme";
|
||||
import { useSettingForm } from "@halo-dev/admin-shared";
|
||||
// types
|
||||
import type { FormKitSettingSpec } from "@halo-dev/admin-shared";
|
||||
import { BasicLayout, useSettingForm } from "@halo-dev/admin-shared";
|
||||
|
||||
// components
|
||||
import {
|
||||
|
@ -17,16 +20,12 @@ import {
|
|||
IconPalette,
|
||||
VButton,
|
||||
VCard,
|
||||
VEmpty,
|
||||
VPageHeader,
|
||||
VSpace,
|
||||
VTabbar,
|
||||
} from "@halo-dev/components";
|
||||
import ThemeListModal from "../components/ThemeListModal.vue";
|
||||
import { BasicLayout } from "@halo-dev/admin-shared";
|
||||
|
||||
// types
|
||||
import type { FormKitSettingSpec } from "@halo-dev/admin-shared";
|
||||
import type { ComputedRef, Ref } from "vue";
|
||||
import type { Theme } from "@halo-dev/api-client";
|
||||
|
||||
interface ThemeTab {
|
||||
|
@ -53,7 +52,7 @@ const selectedTheme = ref<Theme>({} as Theme);
|
|||
const themesModal = ref(false);
|
||||
const activeTab = ref("");
|
||||
|
||||
const { isActivated, activatedTheme, handleActiveTheme } =
|
||||
const { loading, isActivated, activatedTheme, handleActiveTheme } =
|
||||
useThemeLifeCycle(selectedTheme);
|
||||
|
||||
const settingName = computed(() => selectedTheme.value.spec?.settingName);
|
||||
|
@ -171,19 +170,45 @@ watch(
|
|||
</VPageHeader>
|
||||
|
||||
<div class="m-0 md:m-4">
|
||||
<VCard :body-class="['!p-0']">
|
||||
<template #header>
|
||||
<VTabbar
|
||||
v-model:active-id="activeTab"
|
||||
:items="tabs"
|
||||
class="w-full !rounded-none"
|
||||
type="outline"
|
||||
@change="handleTabChange"
|
||||
></VTabbar>
|
||||
<VEmpty
|
||||
v-if="
|
||||
!selectedTheme.metadata?.name &&
|
||||
!activatedTheme.metadata?.name &&
|
||||
!loading
|
||||
"
|
||||
message="当前没有已激活或者选择的主题,你可以切换主题或者安装新主题"
|
||||
title="当前没有已激活或已选择的主题"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="themesModal = true">
|
||||
安装主题
|
||||
</VButton>
|
||||
<VButton type="primary" @click="themesModal = true">
|
||||
<template #icon>
|
||||
<IconExchange class="h-full w-full" />
|
||||
</template>
|
||||
切换主题
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VCard>
|
||||
<div>
|
||||
<RouterView :key="activeTab" />
|
||||
</VEmpty>
|
||||
|
||||
<div v-else>
|
||||
<VCard :body-class="['!p-0']">
|
||||
<template #header>
|
||||
<VTabbar
|
||||
v-model:active-id="activeTab"
|
||||
:items="tabs"
|
||||
class="w-full !rounded-none"
|
||||
type="outline"
|
||||
@change="handleTabChange"
|
||||
></VTabbar>
|
||||
</template>
|
||||
</VCard>
|
||||
<div>
|
||||
<RouterView :key="activeTab" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BasicLayout>
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
IconPlug,
|
||||
VButton,
|
||||
VCard,
|
||||
VEmpty,
|
||||
VPageHeader,
|
||||
VPagination,
|
||||
VSpace,
|
||||
|
@ -17,11 +18,14 @@ import { apiClient } from "@halo-dev/admin-shared";
|
|||
import type { Plugin } from "@halo-dev/api-client";
|
||||
|
||||
const plugins = ref<Plugin[]>([] as Plugin[]);
|
||||
const loading = ref(false);
|
||||
const pluginInstall = ref(false);
|
||||
const keyword = ref("");
|
||||
|
||||
const handleFetchPlugins = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
|
||||
const fieldSelector: Array<string> = [];
|
||||
|
||||
if (keyword.value) {
|
||||
|
@ -38,6 +42,8 @@ const handleFetchPlugins = async () => {
|
|||
plugins.value = data.items;
|
||||
} catch (e) {
|
||||
console.error("Fail to fetch plugins", e);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -211,7 +217,34 @@ onMounted(handleFetchPlugins);
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
|
||||
|
||||
<VEmpty
|
||||
v-if="!plugins.length && !loading"
|
||||
message="当前没有已安装的插件,你可以尝试刷新或者安装新插件"
|
||||
title="当前没有已安装的插件"
|
||||
>
|
||||
<template #actions>
|
||||
<VSpace>
|
||||
<VButton @click="handleFetchPlugins">刷新</VButton>
|
||||
<VButton
|
||||
v-permission="['system:plugins:manage']"
|
||||
type="secondary"
|
||||
@click="pluginInstall = true"
|
||||
>
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
安装插件
|
||||
</VButton>
|
||||
</VSpace>
|
||||
</template>
|
||||
</VEmpty>
|
||||
|
||||
<ul
|
||||
v-else
|
||||
class="box-border h-full w-full divide-y divide-gray-100"
|
||||
role="list"
|
||||
>
|
||||
<li v-for="(plugin, index) in plugins" :key="index">
|
||||
<PluginListItem :plugin="plugin" />
|
||||
</li>
|
||||
|
|
Loading…
Reference in New Issue