Browse Source

feat: add `noBasicLayout` in route meta (#5386)

所有菜单数据无需配置component为BasicLayout,它们将会默认使用基础布局,也可以通过meta.noBasicLayout来阻止这一行为
Netfan 2 months ago
parent
commit
1ad54561b0

+ 0 - 2
apps/backend-mock/utils/mock-data.ts

@@ -53,7 +53,6 @@ export const MOCK_CODES = [
 
 const dashboardMenus = [
   {
-    component: 'BasicLayout',
     meta: {
       order: -1,
       title: 'page.dashboard.title',
@@ -116,7 +115,6 @@ const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
 
   return [
     {
-      component: 'BasicLayout',
       meta: {
         icon: 'ic:baseline-view-in-ar',
         keepAlive: true,

+ 11 - 1
apps/web-antd/src/router/routes/core.ts

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout } from '#/layouts';
+import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 import Login from '#/views/_core/authentication/login.vue';
 
@@ -21,13 +21,23 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
 
 /** 基本路由,这些路由是必须存在的 */
 const coreRoutes: RouteRecordRaw[] = [
+  /**
+   * 根路由
+   * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
+   * 此路由必须存在,且不应修改
+   */
   {
+    component: BasicLayout,
     meta: {
+      hideInBreadcrumb: true,
+      hideInMenu: true,
+      hideInTab: true,
       title: 'Root',
     },
     name: 'Root',
     path: '/',
     redirect: DEFAULT_HOME_PATH,
+    children: [],
   },
   {
     component: AuthPageLayout,

+ 0 - 2
apps/web-antd/src/router/routes/modules/dashboard.ts

@@ -1,11 +1,9 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'lucide:layout-dashboard',
       order: -1,

+ 0 - 2
apps/web-antd/src/router/routes/modules/demos.ts

@@ -1,11 +1,9 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'ic:baseline-view-in-ar',
       keepAlive: true,

+ 12 - 12
apps/web-antd/src/router/routes/modules/vben.ts

@@ -8,30 +8,20 @@ import {
   VBEN_NAIVE_PREVIEW_URL,
 } from '@vben/constants';
 
-import { BasicLayout, IFrameView } from '#/layouts';
+import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       badgeType: 'dot',
       icon: VBEN_LOGO_URL,
-      order: 9999,
+      order: 9998,
       title: $t('demos.vben.title'),
     },
     name: 'VbenProject',
     path: '/vben-admin',
     children: [
-      {
-        name: 'VbenAbout',
-        path: '/vben-admin/about',
-        component: () => import('#/views/_core/about/index.vue'),
-        meta: {
-          icon: 'lucide:copyright',
-          title: $t('demos.vben.about'),
-        },
-      },
       {
         name: 'VbenDocument',
         path: '/vben-admin/document',
@@ -76,6 +66,16 @@ const routes: RouteRecordRaw[] = [
       },
     ],
   },
+  {
+    name: 'VbenAbout',
+    path: '/vben-admin/about',
+    component: () => import('#/views/_core/about/index.vue'),
+    meta: {
+      icon: 'lucide:copyright',
+      title: $t('demos.vben.about'),
+      order: 9999,
+    },
+  },
 ];
 
 export default routes;

+ 11 - 1
apps/web-ele/src/router/routes/core.ts

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout } from '#/layouts';
+import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 import Login from '#/views/_core/authentication/login.vue';
 
@@ -21,13 +21,23 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
 
 /** 基本路由,这些路由是必须存在的 */
 const coreRoutes: RouteRecordRaw[] = [
+  /**
+   * 根路由
+   * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
+   * 此路由必须存在,且不应修改
+   */
   {
+    component: BasicLayout,
     meta: {
+      hideInBreadcrumb: true,
+      hideInMenu: true,
+      hideInTab: true,
       title: 'Root',
     },
     name: 'Root',
     path: '/',
     redirect: DEFAULT_HOME_PATH,
+    children: [],
   },
   {
     component: AuthPageLayout,

+ 0 - 2
apps/web-ele/src/router/routes/modules/dashboard.ts

@@ -1,11 +1,9 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'lucide:layout-dashboard',
       order: -1,

+ 0 - 2
apps/web-ele/src/router/routes/modules/demos.ts

@@ -1,11 +1,9 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'ic:baseline-view-in-ar',
       keepAlive: true,

+ 12 - 12
apps/web-ele/src/router/routes/modules/vben.ts

@@ -9,30 +9,20 @@ import {
 } from '@vben/constants';
 import { SvgAntdvLogoIcon } from '@vben/icons';
 
-import { BasicLayout, IFrameView } from '#/layouts';
+import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       badgeType: 'dot',
       icon: VBEN_LOGO_URL,
-      order: 9999,
+      order: 9998,
       title: $t('demos.vben.title'),
     },
     name: 'VbenProject',
     path: '/vben-admin',
     children: [
-      {
-        name: 'VbenAbout',
-        path: '/vben-admin/about',
-        component: () => import('#/views/_core/about/index.vue'),
-        meta: {
-          icon: 'lucide:copyright',
-          title: $t('demos.vben.about'),
-        },
-      },
       {
         name: 'VbenDocument',
         path: '/vben-admin/document',
@@ -77,6 +67,16 @@ const routes: RouteRecordRaw[] = [
       },
     ],
   },
+  {
+    name: 'VbenAbout',
+    path: '/vben-admin/about',
+    component: () => import('#/views/_core/about/index.vue'),
+    meta: {
+      icon: 'lucide:copyright',
+      title: $t('demos.vben.about'),
+      order: 9999,
+    },
+  },
 ];
 
 export default routes;

+ 11 - 1
apps/web-naive/src/router/routes/core.ts

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout } from '#/layouts';
+import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 import Login from '#/views/_core/authentication/login.vue';
 
@@ -21,13 +21,23 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
 
 /** 基本路由,这些路由是必须存在的 */
 const coreRoutes: RouteRecordRaw[] = [
+  /**
+   * 根路由
+   * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
+   * 此路由必须存在,且不应修改
+   */
   {
+    component: BasicLayout,
     meta: {
+      hideInBreadcrumb: true,
+      hideInMenu: true,
+      hideInTab: true,
       title: 'Root',
     },
     name: 'Root',
     path: '/',
     redirect: DEFAULT_HOME_PATH,
+    children: [],
   },
   {
     component: AuthPageLayout,

+ 0 - 2
apps/web-naive/src/router/routes/modules/dashboard.ts

@@ -1,11 +1,9 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'lucide:layout-dashboard',
       order: -1,

+ 0 - 2
apps/web-naive/src/router/routes/modules/demos.ts

@@ -1,11 +1,9 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'ic:baseline-view-in-ar',
       keepAlive: true,

+ 12 - 12
apps/web-naive/src/router/routes/modules/vben.ts

@@ -9,30 +9,20 @@ import {
 } from '@vben/constants';
 import { SvgAntdvLogoIcon } from '@vben/icons';
 
-import { BasicLayout, IFrameView } from '#/layouts';
+import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       badgeType: 'dot',
       icon: VBEN_LOGO_URL,
-      order: 9999,
+      order: 9998,
       title: $t('demos.vben.title'),
     },
     name: 'VbenProject',
     path: '/vben-admin',
     children: [
-      {
-        name: 'VbenAbout',
-        path: '/vben-admin/about',
-        component: () => import('#/views/_core/about/index.vue'),
-        meta: {
-          icon: 'lucide:copyright',
-          title: $t('demos.vben.about'),
-        },
-      },
       {
         name: 'VbenDocument',
         path: '/vben-admin/document',
@@ -77,6 +67,16 @@ const routes: RouteRecordRaw[] = [
       },
     ],
   },
+  {
+    name: 'VbenAbout',
+    path: '/vben-admin/about',
+    component: () => import('#/views/_core/about/index.vue'),
+    meta: {
+      icon: 'lucide:copyright',
+      title: $t('demos.vben.about'),
+      order: 9999,
+    },
+  },
 ];
 
 export default routes;

+ 0 - 3
docs/src/en/guide/essentials/route.md

@@ -73,7 +73,6 @@ import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       badgeType: 'dot',
       badgeVariants: 'destructive',
@@ -124,7 +123,6 @@ import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'ic:baseline-view-in-ar',
       keepAlive: true,
@@ -249,7 +247,6 @@ import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'mdi:home',
       title: $t('page.home.title'),

+ 11 - 7
docs/src/guide/essentials/route.md

@@ -62,12 +62,10 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { VBEN_LOGO_URL } from '@vben/constants';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       badgeType: 'dot',
       badgeVariants: 'destructive',
@@ -103,7 +101,6 @@ export default routes;
 
 ::: tip
 
-- 多级路由的父级路由无需设置 `component` 属性,只需设置 `children` 属性即可。除非你真的需要在父级路由嵌套下显示内容。
 - 如果没有特殊情况,父级路由的 `redirect` 属性,不需要指定,默认会指向第一个子路由。
 
 :::
@@ -113,12 +110,10 @@ export default routes;
 ```ts
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'ic:baseline-view-in-ar',
       keepAlive: true,
@@ -238,12 +233,10 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { VBEN_LOGO_URL } from '@vben/constants';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'mdi:home',
       title: $t('page.home.title'),
@@ -400,6 +393,10 @@ interface RouteMeta {
    * 菜单可以看到,但是访问会被重定向到403
    */
   menuVisibleWithForbidden?: boolean;
+  /**
+   * 当前路由不使用基础布局(仅在顶级生效)
+   */
+  noBasicLayout?: boolean;
   /**
    * 在新窗口打开
    */
@@ -584,6 +581,13 @@ _注意:_ 排序仅针对一级菜单有效,二级菜单的排序需要在对
 
 用于配置页面的菜单参数,会在菜单中传递给页面。
 
+### noBasicLayout
+
+- 类型:`boolean`
+- 默认值:`false`
+
+用于配置当前路由不使用基础布局,仅在顶级时生效。默认情况下,所有的路由都会被包裹在基础布局中(包含顶部以及侧边等导航部件),如果你的页面不需要这些部件,可以设置 `noBasicLayout` 为 `true`。
+
 ## 路由刷新
 
 路由刷新方式如下:

+ 4 - 0
packages/@core/base/typings/src/vue-router.d.ts

@@ -97,6 +97,10 @@ interface RouteMeta {
    * 菜单可以看到,但是访问会被重定向到403
    */
   menuVisibleWithForbidden?: boolean;
+  /**
+   * 不使用基础布局(仅在顶级生效)
+   */
+  noBasicLayout?: boolean;
   /**
    * 在新窗口打开
    */

+ 19 - 1
packages/effects/access/src/accessible.ts

@@ -22,11 +22,29 @@ async function generateAccessible(
   // 生成路由
   const accessibleRoutes = await generateRoutes(mode, options);
 
+  const root = router.getRoutes().find((item) => item.path === '/');
+
   // 动态添加到router实例内
   accessibleRoutes.forEach((route) => {
-    router.addRoute(route);
+    if (root && !route.meta?.noBasicLayout) {
+      // 为了兼容之前的版本用法,如果包含子路由,则将component移除,以免出现多层BasicLayout
+      // 如果你的项目已经跟进了本次修改,移除了所有自定义菜单首级的BasicLayout,可以将这段if代码删除
+      if (route.children && route.children.length > 0) {
+        delete route.component;
+      }
+      root.children?.push(route);
+    } else {
+      router.addRoute(route);
+    }
   });
 
+  if (root) {
+    if (root.name) {
+      router.removeRoute(root.name);
+    }
+    router.addRoute(root);
+  }
+
   // 生成菜单
   const accessibleMenus = await generateMenus(accessibleRoutes, options.router);
 

+ 11 - 1
playground/src/router/routes/core.ts

@@ -2,7 +2,7 @@ import type { RouteRecordRaw } from 'vue-router';
 
 import { DEFAULT_HOME_PATH, LOGIN_PATH } from '@vben/constants';
 
-import { AuthPageLayout } from '#/layouts';
+import { AuthPageLayout, BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 import Login from '#/views/_core/authentication/login.vue';
 
@@ -21,13 +21,23 @@ const fallbackNotFoundRoute: RouteRecordRaw = {
 
 /** 基本路由,这些路由是必须存在的 */
 const coreRoutes: RouteRecordRaw[] = [
+  /**
+   * 根路由
+   * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。
+   * 此路由必须存在,且不应修改
+   */
   {
+    component: BasicLayout,
     meta: {
+      hideInBreadcrumb: true,
+      hideInMenu: true,
+      hideInTab: true,
       title: 'Root',
     },
     name: 'Root',
     path: '/',
     redirect: DEFAULT_HOME_PATH,
+    children: [],
   },
   {
     component: AuthPageLayout,

+ 1 - 3
playground/src/router/routes/modules/dashboard.ts

@@ -1,18 +1,16 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'lucide:layout-dashboard',
       order: -1,
       title: $t('page.dashboard.title'),
     },
     name: 'Dashboard',
-    path: '/',
+    path: '/dashboard',
     children: [
       {
         name: 'Analytics',

+ 1 - 2
playground/src/router/routes/modules/demos.ts

@@ -1,11 +1,10 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout, IFrameView } from '#/layouts';
+import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'ic:baseline-view-in-ar',
       keepAlive: true,

+ 0 - 2
playground/src/router/routes/modules/examples.ts

@@ -1,11 +1,9 @@
 import type { RouteRecordRaw } from 'vue-router';
 
-import { BasicLayout } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       icon: 'ion:layers-outline',
       keepAlive: true,

+ 12 - 12
playground/src/router/routes/modules/vben.ts

@@ -10,30 +10,20 @@ import {
 } from '@vben/constants';
 import { SvgAntdvLogoIcon } from '@vben/icons';
 
-import { BasicLayout, IFrameView } from '#/layouts';
+import { IFrameView } from '#/layouts';
 import { $t } from '#/locales';
 
 const routes: RouteRecordRaw[] = [
   {
-    component: BasicLayout,
     meta: {
       badgeType: 'dot',
       icon: VBEN_LOGO_URL,
-      order: 9999,
+      order: 9998,
       title: $t('demos.vben.title'),
     },
     name: 'VbenProject',
     path: '/vben-admin',
     children: [
-      {
-        name: 'VbenAbout',
-        path: '/vben-admin/about',
-        component: () => import('#/views/_core/about/index.vue'),
-        meta: {
-          icon: 'lucide:copyright',
-          title: $t('demos.vben.about'),
-        },
-      },
       {
         name: 'VbenDocument',
         path: '/vben-admin/document',
@@ -89,6 +79,16 @@ const routes: RouteRecordRaw[] = [
       },
     ],
   },
+  {
+    component: () => import('#/views/_core/about/index.vue'),
+    meta: {
+      icon: 'lucide:copyright',
+      order: 9999,
+      title: $t('demos.vben.about'),
+    },
+    name: 'VbenAbout',
+    path: '/vben-admin/about',
+  },
 ];
 
 export default routes;