feat: add empty state support for core modules

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/3445/head
Ryan Wang 2022-08-22 16:49:07 +08:00
parent 02997e1d55
commit a65649b1cd
9 changed files with 172 additions and 22 deletions

View File

@ -16,3 +16,4 @@ export * from "./components/switch";
export * from "./components/dialog";
export * from "./components/pagination";
export * from "./components/codemirror";
export * from "./components/empty";

View File

@ -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");
});
});

View File

@ -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"
>

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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,

View File

@ -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>

View File

@ -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>