ソースを参照

feat: 路由`meta`添加`activePath`可将某个菜单激活,主要用于通过`query`或`params`传参的路由

xiaoxian521 2 年 前
コミット
58cafbc73f

+ 2 - 0
mock/asyncRoutes.ts

@@ -179,6 +179,7 @@ const tabsRouter = {
       meta: {
         // 不在menu菜单中显示
         showLink: false,
+        activePath: "/tabs/index",
         roles: ["admin", "common"]
       }
     },
@@ -190,6 +191,7 @@ const tabsRouter = {
       meta: {
         // 不在menu菜单中显示
         showLink: false,
+        activePath: "/tabs/index",
         roles: ["admin", "common"]
       }
     }

+ 7 - 2
src/layout/components/sidebar/horizontal.vue

@@ -1,8 +1,9 @@
 <script setup lang="ts">
-import { ref, nextTick } from "vue";
 import Search from "../search/index.vue";
 import Notice from "../notice/index.vue";
 import SidebarItem from "./sidebarItem.vue";
+import { isAllEmpty } from "@pureadmin/utils";
+import { ref, nextTick, computed } from "vue";
 import { useNav } from "@/layout/hooks/useNav";
 import { useTranslationLang } from "../../hooks/useTranslationLang";
 import { usePermissionStoreHook } from "@/store/modules/permission";
@@ -27,6 +28,10 @@ const {
   getDropdownItemClass
 } = useNav();
 
+const defaultActive = computed(() =>
+  !isAllEmpty(route.meta?.activePath) ? route.meta.activePath : route.path
+);
+
 nextTick(() => {
   menuRef.value?.handleResize();
 });
@@ -46,7 +51,7 @@ nextTick(() => {
       ref="menuRef"
       mode="horizontal"
       class="horizontal-header-menu"
-      :default-active="route.path"
+      :default-active="defaultActive"
     >
       <sidebar-item
         v-for="route in usePermissionStoreHook().wholeMenus"

+ 4 - 4
src/layout/components/sidebar/mixNav.vue

@@ -2,6 +2,7 @@
 import extraIcon from "./extraIcon.vue";
 import Search from "../search/index.vue";
 import Notice from "../notice/index.vue";
+import { isAllEmpty } from "@pureadmin/utils";
 import { useNav } from "@/layout/hooks/useNav";
 import { transformI18n } from "@/plugins/i18n";
 import { ref, toRaw, watch, onMounted, nextTick } from "vue";
@@ -36,10 +37,9 @@ function getDefaultActive(routePath) {
   const wholeMenus = usePermissionStoreHook().wholeMenus;
   /** 当前路由的父级路径 */
   const parentRoutes = getParentPaths(routePath, wholeMenus)[0];
-  defaultActive.value = findRouteByPath(
-    parentRoutes,
-    wholeMenus
-  )?.children[0]?.path;
+  defaultActive.value = !isAllEmpty(route.meta?.activePath)
+    ? route.meta.activePath
+    : findRouteByPath(parentRoutes, wholeMenus)?.children[0]?.path;
 }
 
 onMounted(() => {

+ 12 - 6
src/layout/components/sidebar/vertical.vue

@@ -5,8 +5,8 @@ import { emitter } from "@/utils/mitt";
 import SidebarItem from "./sidebarItem.vue";
 import leftCollapse from "./leftCollapse.vue";
 import { useNav } from "@/layout/hooks/useNav";
-import { storageLocal } from "@pureadmin/utils";
 import { responsiveStorageNameSpace } from "@/config";
+import { storageLocal, isAllEmpty } from "@pureadmin/utils";
 import { findRouteByPath, getParentPaths } from "@/router/utils";
 import { usePermissionStoreHook } from "@/store/modules/permission";
 import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue";
@@ -32,7 +32,13 @@ const loading = computed(() =>
   pureApp.layout === "mix" ? false : menuData.value.length === 0 ? true : false
 );
 
-function getSubMenuData(path: string) {
+const defaultActive = computed(() =>
+  !isAllEmpty(route.meta?.activePath) ? route.meta.activePath : route.path
+);
+
+function getSubMenuData() {
+  let path = "";
+  path = defaultActive.value;
   subMenuData.value = [];
   // path的上级路由组成的数组
   const parentPathArr = getParentPaths(
@@ -48,18 +54,18 @@ function getSubMenuData(path: string) {
   subMenuData.value = parenetRoute?.children;
 }
 
-getSubMenuData(route.path);
-
 watch(
   () => [route.path, usePermissionStoreHook().wholeMenus],
   () => {
     if (route.path.includes("/redirect")) return;
-    getSubMenuData(route.path);
+    getSubMenuData();
     menuSelect(route.path);
   }
 );
 
 onMounted(() => {
+  getSubMenuData();
+
   emitter.on("logoChange", key => {
     showLogo.value = key;
   });
@@ -87,7 +93,7 @@ onBeforeUnmount(() => {
         mode="vertical"
         class="outer-most select-none"
         :collapse="isCollapse"
-        :default-active="route.path"
+        :default-active="defaultActive"
         :collapse-transition="false"
       >
         <sidebar-item

+ 3 - 3
src/layout/components/tag/index.vue

@@ -166,7 +166,7 @@ function onFresh() {
     path: "/redirect" + fullPath,
     query
   });
-  handleAliveRoute(route as toRouteType, "refresh");
+  handleAliveRoute(route as ToRouteType, "refresh");
 }
 
 function deleteDynamicTag(obj: any, current: any, tag?: string) {
@@ -239,7 +239,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 
 function deleteMenu(item, tag?: string) {
   deleteDynamicTag(item, item.path, tag);
-  handleAliveRoute(route as toRouteType);
+  handleAliveRoute(route as ToRouteType);
 }
 
 function onClickDrop(key, item, selectRoute?: RouteConfigs) {
@@ -287,7 +287,7 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
         length: multiTags.value.length
       });
       router.push(topPath);
-      handleAliveRoute(route as toRouteType);
+      handleAliveRoute(route as ToRouteType);
       break;
     case 6:
       // 整体页面全屏

+ 1 - 1
src/router/index.ts

@@ -101,7 +101,7 @@ const whiteList = ["/login"];
 
 const { VITE_HIDE_HOME } = import.meta.env;
 
-router.beforeEach((to: toRouteType, _from, next) => {
+router.beforeEach((to: ToRouteType, _from, next) => {
   if (to.meta?.keepAlive) {
     handleAliveRoute(to, "add");
     // 页面整体刷新和点击标签页刷新

+ 1 - 1
src/router/utils.ts

@@ -256,7 +256,7 @@ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
 }
 
 /** 处理缓存路由(添加、删除、刷新) */
-function handleAliveRoute({ name }: toRouteType, mode?: string) {
+function handleAliveRoute({ name }: ToRouteType, mode?: string) {
   switch (mode) {
     case "add":
       usePermissionStoreHook().cacheOperate({

+ 0 - 93
types/global.d.ts

@@ -7,7 +7,6 @@ import type {
 import type { ECharts } from "echarts";
 import type { IconifyIcon } from "@iconify/vue";
 import type { TableColumns } from "@pureadmin/table";
-import { type RouteComponent, type RouteLocationNormalized } from "vue-router";
 
 /**
  * 全局类型声明,无需引入直接在 `.vue` 、`.ts` 、`.tsx` 文件使用即可获得类型提示
@@ -166,98 +165,6 @@ declare global {
     tags?: Array<any>;
   }
 
-  /**
-   * `src/router` 文件夹里的类型声明
-   */
-  interface toRouteType extends RouteLocationNormalized {
-    meta: {
-      roles: Array<string>;
-      keepAlive?: boolean;
-      dynamicLevel?: string;
-    };
-  }
-
-  /**
-   * @description 完整子路由配置表
-   */
-  interface RouteChildrenConfigsTable {
-    /** 子路由地址 `必填` */
-    path: string;
-    /** 路由名字(对应不要重复,和当前组件的`name`保持一致)`必填` */
-    name?: string;
-    /** 路由重定向 `可选` */
-    redirect?: string;
-    /** 按需加载组件 `可选` */
-    component?: RouteComponent;
-    meta?: {
-      /** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加) `必填` */
-      title: string;
-      /** 菜单图标 `可选` */
-      icon?: string | FunctionalComponent | IconifyIcon;
-      /** 菜单名称右侧的额外图标 */
-      extraIcon?: string | FunctionalComponent | IconifyIcon;
-      /** 是否在菜单中显示(默认`true`)`可选` */
-      showLink?: boolean;
-      /** 是否显示父级菜单 `可选` */
-      showParent?: boolean;
-      /** 页面级别权限设置 `可选` */
-      roles?: Array<string>;
-      /** 按钮级别权限设置 `可选` */
-      auths?: Array<string>;
-      /** 路由组件缓存(开启 `true`、关闭 `false`)`可选` */
-      keepAlive?: boolean;
-      /** 内嵌的`iframe`链接 `可选` */
-      frameSrc?: string;
-      /** `iframe`页是否开启首次加载动画(默认`true`)`可选` */
-      frameLoading?: boolean;
-      /** 页面加载动画(有两种形式,一种直接采用vue内置的`transitions`动画,另一种是使用`animate.css`写进、离场动画)`可选` */
-      transition?: {
-        /**
-         * @description 当前路由动画效果
-         * @see {@link https://next.router.vuejs.org/guide/advanced/transitions.html#transitions}
-         * @see animate.css {@link https://animate.style}
-         */
-        name?: string;
-        /** 进场动画 */
-        enterTransition?: string;
-        /** 离场动画 */
-        leaveTransition?: string;
-      };
-      // 是否不添加信息到标签页,(默认`false`)
-      hiddenTag?: boolean;
-      /** 动态路由可打开的最大数量 `可选` */
-      dynamicLevel?: number;
-    };
-    /** 子路由配置项 */
-    children?: Array<RouteChildrenConfigsTable>;
-  }
-
-  /**
-   * @description 整体路由配置表(包括完整子路由)
-   */
-  interface RouteConfigsTable {
-    /** 路由地址 `必填` */
-    path: string;
-    /** 路由名字(保持唯一)`可选` */
-    name?: string;
-    /** `Layout`组件 `可选` */
-    component?: RouteComponent;
-    /** 路由重定向 `可选` */
-    redirect?: string;
-    meta?: {
-      /** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加)`必填` */
-      title: string;
-      /** 菜单图标 `可选` */
-      icon?: string | FunctionalComponent | IconifyIcon;
-      /** 是否在菜单中显示(默认`true`)`可选` */
-      showLink?: boolean;
-      /** 菜单升序排序,值越高排的越后(只针对顶级路由)`可选` */
-      rank?: number;
-    };
-    /** 子路由配置项 */
-    children?: Array<RouteChildrenConfigsTable>;
-  }
-
   /**
    * 平台里所有组件实例都能访问到的全局属性对象的类型声明
    */

+ 105 - 0
types/router.d.ts

@@ -0,0 +1,105 @@
+// 全局路由类型声明
+
+import { type RouteComponent, type RouteLocationNormalized } from "vue-router";
+
+declare global {
+  interface ToRouteType extends RouteLocationNormalized {
+    meta: CustomizeRouteMeta;
+  }
+
+  /**
+   * @description 完整子路由的`meta`配置表
+   */
+  interface CustomizeRouteMeta {
+    /** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加) `必填` */
+    title: string;
+    /** 菜单图标 `可选` */
+    icon?: string | FunctionalComponent | IconifyIcon;
+    /** 菜单名称右侧的额外图标 */
+    extraIcon?: string | FunctionalComponent | IconifyIcon;
+    /** 是否在菜单中显示(默认`true`)`可选` */
+    showLink?: boolean;
+    /** 是否显示父级菜单 `可选` */
+    showParent?: boolean;
+    /** 页面级别权限设置 `可选` */
+    roles?: Array<string>;
+    /** 按钮级别权限设置 `可选` */
+    auths?: Array<string>;
+    /** 路由组件缓存(开启 `true`、关闭 `false`)`可选` */
+    keepAlive?: boolean;
+    /** 内嵌的`iframe`链接 `可选` */
+    frameSrc?: string;
+    /** `iframe`页是否开启首次加载动画(默认`true`)`可选` */
+    frameLoading?: boolean;
+    /** 页面加载动画(有两种形式,一种直接采用vue内置的`transitions`动画,另一种是使用`animate.css`写进、离场动画)`可选` */
+    transition?: {
+      /**
+       * @description 当前路由动画效果
+       * @see {@link https://next.router.vuejs.org/guide/advanced/transitions.html#transitions}
+       * @see animate.css {@link https://animate.style}
+       */
+      name?: string;
+      /** 进场动画 */
+      enterTransition?: string;
+      /** 离场动画 */
+      leaveTransition?: string;
+    };
+    // 是否不添加信息到标签页,(默认`false`)
+    hiddenTag?: boolean;
+    /** 动态路由可打开的最大数量 `可选` */
+    dynamicLevel?: number;
+    /** 将某个菜单激活
+     * (主要用于通过`query`或`params`传参的路由,当它们通过配置`showLink: false`后不在菜单中显示,就不会有任何菜单高亮,
+     * 而通过设置`activePath`指定激活菜单即可获得高亮,`activePath`为指定激活菜单的`path`)
+     */
+    activePath?: string;
+  }
+
+  /**
+   * @description 完整子路由配置表
+   */
+  interface RouteChildrenConfigsTable {
+    /** 子路由地址 `必填` */
+    path: string;
+    /** 路由名字(对应不要重复,和当前组件的`name`保持一致)`必填` */
+    name?: string;
+    /** 路由重定向 `可选` */
+    redirect?: string;
+    /** 按需加载组件 `可选` */
+    component?: RouteComponent;
+    meta?: CustomizeRouteMeta;
+    /** 子路由配置项 */
+    children?: Array<RouteChildrenConfigsTable>;
+  }
+
+  /**
+   * @description 整体路由配置表(包括完整子路由)
+   */
+  interface RouteConfigsTable {
+    /** 路由地址 `必填` */
+    path: string;
+    /** 路由名字(保持唯一)`可选` */
+    name?: string;
+    /** `Layout`组件 `可选` */
+    component?: RouteComponent;
+    /** 路由重定向 `可选` */
+    redirect?: string;
+    meta?: {
+      /** 菜单名称(兼容国际化、非国际化,如何用国际化的写法就必须在根目录的`locales`文件夹下对应添加)`必填` */
+      title: string;
+      /** 菜单图标 `可选` */
+      icon?: string | FunctionalComponent | IconifyIcon;
+      /** 是否在菜单中显示(默认`true`)`可选` */
+      showLink?: boolean;
+      /** 菜单升序排序,值越高排的越后(只针对顶级路由)`可选` */
+      rank?: number;
+    };
+    /** 子路由配置项 */
+    children?: Array<RouteChildrenConfigsTable>;
+  }
+}
+
+// https://router.vuejs.org/zh/guide/advanced/meta.html#typescript
+declare module "vue-router" {
+  interface RouteMeta extends CustomizeRouteMeta {}
+}