mirror of https://github.com/halo-dev/halo
Refactor menu generation strategy to support sub-menu items. (#5177)
#### What type of PR is this? /area console /kind feature /milestone 2.12.x #### What this PR does / why we need it: 重构 Console 和 UC 的菜单生成逻辑,支持配置二级菜单项。 <img width="557" alt="图片" src="https://github.com/halo-dev/halo/assets/21301288/0f1717ce-bd30-448b-9625-24bfd5e1c5ae"> 配置方式: ```ts export default definePlugin({ components: {}, routes: [ { parentName: "AttachmentsRoot", route: { name: "S3Link", path: "s3-link", component: markRaw(HomeView), meta: { title: "S3 关联", searchable: true, menu: { name: "S3 关联", icon: markRaw(IconAddCircle), priority: 0, mobile: true, }, }, }, }, ], }); ``` 只需要指定 parentName 并在其下 route 需要配置 meta.menu 即可。 最终文档会补充在:https://github.com/halo-dev/docs/pull/291 #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/4807 #### Special notes for your reviewer: 1. 可以按照上述配置方式测试。 2. 可以安装 [plugin-s3-1.5.0-SNAPSHOT.jar.zip](https://github.com/halo-dev/halo/files/13959977/plugin-s3-1.5.0-SNAPSHOT.jar.zip) 进行测试。 #### Does this PR introduce a user-facing change? ```release-note 重构 Console 和 UC 的菜单生成逻辑,支持配置二级菜单项。 ```pull/5121/head^2
parent
b42e046d54
commit
3ebb45c266
|
@ -12,23 +12,24 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/attachments",
|
||||
name: "AttachmentsRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.attachment.title",
|
||||
permissions: ["system:attachments:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.attachments",
|
||||
group: "content",
|
||||
icon: markRaw(IconFolder),
|
||||
priority: 3,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Attachments",
|
||||
component: AttachmentList,
|
||||
meta: {
|
||||
title: "core.attachment.title",
|
||||
permissions: ["system:attachments:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.attachments",
|
||||
group: "content",
|
||||
icon: markRaw(IconFolder),
|
||||
priority: 3,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -12,24 +12,25 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/comments",
|
||||
name: "CommentsRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.comment.title",
|
||||
searchable: true,
|
||||
permissions: ["system:comments:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.comments",
|
||||
group: "content",
|
||||
icon: markRaw(IconMessage),
|
||||
priority: 2,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Comments",
|
||||
component: CommentList,
|
||||
meta: {
|
||||
title: "core.comment.title",
|
||||
searchable: true,
|
||||
permissions: ["system:comments:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.comments",
|
||||
group: "content",
|
||||
icon: markRaw(IconMessage),
|
||||
priority: 2,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -14,23 +14,24 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/single-pages",
|
||||
name: "SinglePagesRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.page.title",
|
||||
searchable: true,
|
||||
permissions: ["system:singlepages:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.single_pages",
|
||||
group: "content",
|
||||
icon: markRaw(IconPages),
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "SinglePages",
|
||||
component: SinglePageList,
|
||||
meta: {
|
||||
title: "core.page.title",
|
||||
searchable: true,
|
||||
permissions: ["system:singlepages:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.single_pages",
|
||||
group: "content",
|
||||
icon: markRaw(IconPages),
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "deleted",
|
||||
|
|
|
@ -19,24 +19,25 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/posts",
|
||||
name: "PostsRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.post.title",
|
||||
searchable: true,
|
||||
permissions: ["system:posts:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.posts",
|
||||
group: "content",
|
||||
icon: markRaw(IconBookRead),
|
||||
priority: 0,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Posts",
|
||||
component: PostList,
|
||||
meta: {
|
||||
title: "core.post.title",
|
||||
searchable: true,
|
||||
permissions: ["system:posts:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.posts",
|
||||
group: "content",
|
||||
icon: markRaw(IconBookRead),
|
||||
priority: 0,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "deleted",
|
||||
|
|
|
@ -9,23 +9,24 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/menus",
|
||||
name: "MenusRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.menu.title",
|
||||
searchable: true,
|
||||
permissions: ["system:menus:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.menus",
|
||||
group: "interface",
|
||||
icon: markRaw(IconListSettings),
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Menus",
|
||||
component: Menus,
|
||||
meta: {
|
||||
title: "core.menu.title",
|
||||
searchable: true,
|
||||
permissions: ["system:menus:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.menus",
|
||||
group: "interface",
|
||||
icon: markRaw(IconListSettings),
|
||||
priority: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -10,23 +10,24 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/theme",
|
||||
name: "ThemeRoot",
|
||||
component: ThemeLayout,
|
||||
meta: {
|
||||
title: "core.theme.title",
|
||||
searchable: true,
|
||||
permissions: ["system:themes:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.themes",
|
||||
group: "interface",
|
||||
icon: markRaw(IconPalette),
|
||||
priority: 0,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "ThemeDetail",
|
||||
component: ThemeDetail,
|
||||
meta: {
|
||||
title: "core.theme.title",
|
||||
searchable: true,
|
||||
permissions: ["system:themes:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.themes",
|
||||
group: "interface",
|
||||
icon: markRaw(IconPalette),
|
||||
priority: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "settings/:group",
|
||||
|
|
|
@ -9,22 +9,24 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/actuator",
|
||||
name: "OverviewRoot", // fixme: actuator will be renamed to overview in the future
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.actuator.title",
|
||||
searchable: true,
|
||||
permissions: ["system:actuator:manage"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.actuator",
|
||||
group: "system",
|
||||
icon: markRaw(IconTerminalBoxLine),
|
||||
priority: 3,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Actuator",
|
||||
component: Actuator,
|
||||
meta: {
|
||||
title: "core.actuator.title",
|
||||
searchable: true,
|
||||
permissions: ["system:actuator:manage"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.actuator",
|
||||
group: "system",
|
||||
icon: markRaw(IconTerminalBoxLine),
|
||||
priority: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -9,23 +9,24 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/backup",
|
||||
name: "BackupRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.backup.title",
|
||||
searchable: true,
|
||||
permissions: ["system:migrations:manage"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.backup",
|
||||
group: "system",
|
||||
icon: markRaw(IconServerLine),
|
||||
priority: 4,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Backup",
|
||||
component: Backups,
|
||||
meta: {
|
||||
title: "core.backup.title",
|
||||
searchable: true,
|
||||
permissions: ["system:migrations:manage"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.backup",
|
||||
group: "system",
|
||||
icon: markRaw(IconServerLine),
|
||||
priority: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { definePlugin } from "@halo-dev/console-shared";
|
||||
import BasicLayout from "@console/layouts/BasicLayout.vue";
|
||||
import BlankLayout from "@console/layouts/BlankLayout.vue";
|
||||
import PluginList from "./PluginList.vue";
|
||||
import PluginDetail from "./PluginDetail.vue";
|
||||
import { IconPlug } from "@halo-dev/components";
|
||||
|
@ -11,44 +10,33 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/plugins",
|
||||
component: BlankLayout,
|
||||
name: "PluginsRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.plugin.title",
|
||||
searchable: true,
|
||||
permissions: ["system:plugins:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.plugins",
|
||||
group: "system",
|
||||
icon: markRaw(IconPlug),
|
||||
priority: 0,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Plugins",
|
||||
component: PluginList,
|
||||
meta: {
|
||||
title: "core.plugin.title",
|
||||
searchable: true,
|
||||
permissions: ["system:plugins:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.plugins",
|
||||
group: "system",
|
||||
icon: markRaw(IconPlug),
|
||||
priority: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
name: "Plugins",
|
||||
component: PluginList,
|
||||
},
|
||||
{
|
||||
path: ":name",
|
||||
component: BasicLayout,
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "PluginDetail",
|
||||
component: PluginDetail,
|
||||
meta: {
|
||||
title: "core.plugin.detail.title",
|
||||
permissions: ["system:plugins:view"],
|
||||
},
|
||||
},
|
||||
],
|
||||
name: "PluginDetail",
|
||||
component: PluginDetail,
|
||||
meta: {
|
||||
title: "core.plugin.detail.title",
|
||||
permissions: ["system:plugins:view"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -9,22 +9,23 @@ export default definePlugin({
|
|||
routes: [
|
||||
{
|
||||
path: "/settings",
|
||||
name: "SettingsRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.setting.title",
|
||||
permissions: ["system:settings:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.settings",
|
||||
group: "system",
|
||||
icon: markRaw(IconSettings),
|
||||
priority: 2,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "SystemSetting",
|
||||
component: SystemSettings,
|
||||
meta: {
|
||||
title: "core.setting.title",
|
||||
permissions: ["system:settings:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.settings",
|
||||
group: "system",
|
||||
icon: markRaw(IconSettings),
|
||||
priority: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -33,24 +33,25 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
path: "/users",
|
||||
name: "UsersRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.user.title",
|
||||
searchable: true,
|
||||
permissions: ["system:users:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.users",
|
||||
group: "system",
|
||||
icon: markRaw(IconUserSettings),
|
||||
priority: 1,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Users",
|
||||
component: UserList,
|
||||
meta: {
|
||||
title: "core.user.title",
|
||||
searchable: true,
|
||||
permissions: ["system:users:view"],
|
||||
menu: {
|
||||
name: "core.sidebar.menu.items.users",
|
||||
group: "system",
|
||||
icon: markRaw(IconUserSettings),
|
||||
priority: 1,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ":name",
|
||||
|
|
|
@ -73,7 +73,14 @@ function registerModule(app: App, pluginModule: PluginModule, core: boolean) {
|
|||
|
||||
for (const route of pluginModule.routes) {
|
||||
if ("parentName" in route) {
|
||||
router.addRoute(route.parentName, route.route);
|
||||
const parentRoute = router
|
||||
.getRoutes()
|
||||
.find((item) => item.name === route.parentName);
|
||||
if (parentRoute) {
|
||||
router.removeRoute(route.parentName);
|
||||
parentRoute.children = [...parentRoute.children, route.route];
|
||||
router.addRoute(parentRoute);
|
||||
}
|
||||
} else {
|
||||
router.addRoute(route);
|
||||
}
|
||||
|
|
|
@ -18,8 +18,11 @@ declare module "*.vue" {
|
|||
}
|
||||
|
||||
declare module "vue-router" {
|
||||
import type { Component } from "vue";
|
||||
|
||||
interface RouteMeta {
|
||||
title?: string;
|
||||
description?: string;
|
||||
searchable?: boolean;
|
||||
permissions?: string[];
|
||||
core?: boolean;
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
IconFolder,
|
||||
IconMessage,
|
||||
IconPages,
|
||||
IconAddCircle,
|
||||
} from "@/icons/icons";
|
||||
|
||||
const meta: Meta<typeof VMenu> = {
|
||||
|
@ -22,6 +23,7 @@ const meta: Meta<typeof VMenu> = {
|
|||
IconMessage,
|
||||
IconFolder,
|
||||
IconPages,
|
||||
IconAddCircle,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
|
@ -41,6 +43,11 @@ const meta: Meta<typeof VMenu> = {
|
|||
<template #icon>
|
||||
<IconBookRead />
|
||||
</template>
|
||||
<VMenuItem title="新文章">
|
||||
<template #icon>
|
||||
<IconBookRead />
|
||||
</template>
|
||||
</VMenuItem>
|
||||
</VMenuItem>
|
||||
<VMenuItem title="页面">
|
||||
<template #icon>
|
||||
|
|
|
@ -36,7 +36,6 @@ const hasSubmenus = computed(() => {
|
|||
function handleClick() {
|
||||
if (hasSubmenus.value) {
|
||||
open.value = !open.value;
|
||||
return;
|
||||
}
|
||||
emit("select", props.id);
|
||||
}
|
||||
|
@ -83,7 +82,8 @@ function handleClick() {
|
|||
flex
|
||||
select-none
|
||||
relative
|
||||
p-2
|
||||
px-2
|
||||
py-[0.4rem]
|
||||
font-normal
|
||||
rounded-base;
|
||||
|
||||
|
@ -109,17 +109,21 @@ function handleClick() {
|
|||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.submenus-show-enter-active {
|
||||
transition: all 0.1s ease-out;
|
||||
}
|
||||
|
||||
.submenus-show-enter-active,
|
||||
.submenus-show-leave-active {
|
||||
transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.submenus-show-enter-from,
|
||||
.submenus-show-enter-to {
|
||||
transform: translateY(-10px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.sub-menu-items {
|
||||
@apply pl-5 my-1;
|
||||
|
||||
.menu-item-title {
|
||||
@apply p-1.5 text-sm;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -88,7 +88,7 @@ describe("Menu", () => {
|
|||
// has sub menu
|
||||
if (item.props().id === "3") {
|
||||
item.trigger("click");
|
||||
expect(item.emitted().select).toBeUndefined();
|
||||
expect(item.emitted().select).toBeDefined();
|
||||
|
||||
expect(item.vm.open).toBe(false);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const RoutesMenu = defineComponent({
|
|||
function renderIcon(icon: Component | undefined) {
|
||||
if (!icon) return undefined;
|
||||
|
||||
return <icon height="20px" width="20px" />;
|
||||
return <icon />;
|
||||
}
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -48,6 +48,8 @@ const RoutesMenu = defineComponent({
|
|||
v-slots={{
|
||||
icon: () => renderIcon(item.icon),
|
||||
}}
|
||||
onSelect={handleSelect}
|
||||
active={openIds.value.includes(item.path)}
|
||||
>
|
||||
{renderItems(item.children)}
|
||||
</VMenuItem>
|
||||
|
|
|
@ -25,29 +25,67 @@ export function useRouteMenuGenerator(
|
|||
const roleStore = useRoleStore();
|
||||
const { uiPermissions } = roleStore.permissions;
|
||||
|
||||
function flattenRoutes(route: RouteRecordNormalized | RouteRecordRaw) {
|
||||
let routes: (RouteRecordNormalized | RouteRecordRaw)[] = [route];
|
||||
if (route.children) {
|
||||
route.children.forEach((child) => {
|
||||
routes = routes.concat(flattenRoutes(child));
|
||||
});
|
||||
}
|
||||
return routes;
|
||||
}
|
||||
|
||||
function isRouteValid(route?: RouteRecordNormalized) {
|
||||
if (!route) return false;
|
||||
const { meta } = route;
|
||||
if (!meta?.menu) return false;
|
||||
return (
|
||||
!meta.permissions || hasPermission(uiPermissions, meta.permissions, true)
|
||||
);
|
||||
}
|
||||
|
||||
const generateMenus = () => {
|
||||
// sort by menu.priority and meta.core
|
||||
const currentRoutes = sortBy<RouteRecordNormalized>(
|
||||
router.getRoutes().filter((route) => {
|
||||
const { meta } = route;
|
||||
if (!meta?.menu) {
|
||||
return false;
|
||||
}
|
||||
if (meta.permissions) {
|
||||
return hasPermission(
|
||||
uiPermissions,
|
||||
meta.permissions as string[],
|
||||
true
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
// Filter and sort routes based on menu and permissions
|
||||
let currentRoutes = sortBy<RouteRecordNormalized>(
|
||||
router.getRoutes().filter((route) => isRouteValid(route)),
|
||||
[
|
||||
(route: RouteRecordRaw) => !route.meta?.core,
|
||||
(route: RouteRecordRaw) => route.meta?.menu?.priority || 0,
|
||||
]
|
||||
);
|
||||
|
||||
// Flatten and filter child routes
|
||||
currentRoutes.forEach((route) => {
|
||||
if (route.children.length) {
|
||||
const routesMap = new Map(
|
||||
currentRoutes.map((route) => [route.name, route])
|
||||
);
|
||||
|
||||
const flattenedAndValidChildren = route.children
|
||||
.flatMap((child) => flattenRoutes(child))
|
||||
.map((flattenedChild) => {
|
||||
const validRoute = routesMap.get(flattenedChild.name);
|
||||
if (validRoute && isRouteValid(validRoute)) {
|
||||
return validRoute;
|
||||
}
|
||||
})
|
||||
.filter(Boolean); // filters out falsy values
|
||||
|
||||
// Sorting the routes
|
||||
// @ts-ignore children must be RouteRecordRaw[], but it is RouteRecordNormalized[]
|
||||
route.children = sortBy(flattenedAndValidChildren, [
|
||||
(route) => !route?.meta?.core,
|
||||
(route) => route?.meta?.menu?.priority || 0,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
// Remove duplicate routes
|
||||
const allChildren = currentRoutes.flatMap((route) => route.children);
|
||||
currentRoutes = currentRoutes.filter(
|
||||
(route) => !allChildren.find((child) => child.name === route.name)
|
||||
);
|
||||
|
||||
// group by menu.group
|
||||
menus.value = currentRoutes.reduce((acc, route) => {
|
||||
const { menu } = route.meta;
|
||||
|
@ -55,19 +93,20 @@ export function useRouteMenuGenerator(
|
|||
return acc;
|
||||
}
|
||||
const group = acc.find((item) => item.id === menu.group);
|
||||
const childRoute = route.children[0];
|
||||
const childMetaMenu = childRoute?.meta?.menu;
|
||||
const childRoute = route.children;
|
||||
|
||||
const menuChildren: MenuItemType[] = childRoute
|
||||
.map((child) => {
|
||||
if (!child.meta?.menu) return;
|
||||
return {
|
||||
name: child.meta.menu.name,
|
||||
path: child.path,
|
||||
icon: child.meta.menu.icon,
|
||||
mobile: child.meta.menu.mobile,
|
||||
};
|
||||
})
|
||||
.filter(Boolean) as MenuItemType[];
|
||||
|
||||
// only support one level
|
||||
const menuChildren = childMetaMenu
|
||||
? [
|
||||
{
|
||||
name: childMetaMenu.name,
|
||||
path: childRoute.path,
|
||||
icon: childMetaMenu.icon,
|
||||
},
|
||||
]
|
||||
: undefined;
|
||||
if (group) {
|
||||
group.items?.push({
|
||||
name: menu.name,
|
||||
|
|
|
@ -9,24 +9,25 @@ export default definePlugin({
|
|||
ucRoutes: [
|
||||
{
|
||||
path: "/posts",
|
||||
name: "PostsRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.uc_post.title",
|
||||
searchable: true,
|
||||
permissions: ["uc:posts:manage"],
|
||||
menu: {
|
||||
name: "core.uc_sidebar.menu.items.posts",
|
||||
group: "content",
|
||||
icon: markRaw(IconBookRead),
|
||||
priority: 0,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Posts",
|
||||
component: PostList,
|
||||
meta: {
|
||||
title: "core.uc_post.title",
|
||||
searchable: true,
|
||||
permissions: ["uc:posts:manage"],
|
||||
menu: {
|
||||
name: "core.uc_sidebar.menu.items.posts",
|
||||
group: "content",
|
||||
icon: markRaw(IconBookRead),
|
||||
priority: 0,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "editor",
|
||||
|
|
|
@ -8,23 +8,24 @@ export default definePlugin({
|
|||
ucRoutes: [
|
||||
{
|
||||
path: "/notifications",
|
||||
name: "NotificationsRoot",
|
||||
component: BasicLayout,
|
||||
meta: {
|
||||
title: "core.uc_notification.title",
|
||||
searchable: true,
|
||||
menu: {
|
||||
name: "core.uc_sidebar.menu.items.notification",
|
||||
group: "dashboard",
|
||||
icon: markRaw(IconNotificationBadgeLine),
|
||||
priority: 1,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "Notifications",
|
||||
component: Notifications,
|
||||
meta: {
|
||||
title: "core.uc_notification.title",
|
||||
searchable: true,
|
||||
menu: {
|
||||
name: "core.uc_sidebar.menu.items.notification",
|
||||
group: "dashboard",
|
||||
icon: markRaw(IconNotificationBadgeLine),
|
||||
priority: 1,
|
||||
mobile: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -73,7 +73,14 @@ function registerModule(app: App, pluginModule: PluginModule, core: boolean) {
|
|||
|
||||
for (const route of pluginModule.ucRoutes) {
|
||||
if ("parentName" in route) {
|
||||
router.addRoute(route.parentName, route.route);
|
||||
const parentRoute = router
|
||||
.getRoutes()
|
||||
.find((item) => item.name === route.parentName);
|
||||
if (parentRoute) {
|
||||
router.removeRoute(route.parentName);
|
||||
parentRoute.children = [...parentRoute.children, route.route];
|
||||
router.addRoute(parentRoute);
|
||||
}
|
||||
} else {
|
||||
router.addRoute(route);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue