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