ホームページ  >  記事  >  ウェブフロントエンド  >  Vue3で動的メニューを読み込む方法

Vue3で動的メニューを読み込む方法

PHPz
PHPz転載
2023-05-10 13:58:062106ブラウズ

1. 全体的な考え方

まず、全体的な実装の考え方を整理します まず第一に、全体的な考え方は vhr とまったく同じです。

vhr のフロントエンド動的メニューの実装アイデアを忘れてしまった友人もいるかもしれないことを考慮して、この記事ではそれを一緒に分析します。

すべての .vue ファイルでメニュー データにアクセスできるようにするために、メニュー データを vuex に保存することを選択します。vuex は、vue でデータを保存するための一般的な場所です。すべての .vue ファイルは vuex からデータを読み取ることができます。 vuex に保存されているデータは基本的にメモリ上に保存されているため、ブラウザが F5 キーを押して更新するとデータが消えてしまう特性があります。したがって、ページ ジャンプが発生した場合、ユーザーがページ上のメニュー ボタンをクリックした後にページ ジャンプが発生したのか、それともユーザーがブラウザの更新ボタンをクリックした (または F5 を押した) 後にページ ジャンプが発生したのかを区別する必要があります。

これを実現するには、vue でルート ナビゲーション ガード関数を使用する必要があります。私たち Java エンジニアにとって、これらは少し馴染みのないものに聞こえるかもしれませんが、Java ではフィルターとして扱うことができます。実際、私たちはビデオの中で友達に説明するときにこのたとえを使いました。新しいものと、すでに頭の中で見慣れているものを比較すると、理解しやすくなります。

vue のナビゲーション ガードはモニターに似ています。すべてのページ ジャンプを監視できます。ページ ジャンプ中に、vuex のメニュー データがまだ存在するかどうかを判断できます。まだ存在する場合は、それを意味します。ユーザーがページ上のメニュー ボタンをクリックしてジャンプを完了しました。メニュー ボタンが存在しない場合は、ユーザーがブラウザの更新ボタンをクリックするか、F5 キーを押してページを更新したことを意味します。このとき、すぐにサーバーにアクセスする必要があります。 . メニューデータを再読み込みします。

---xxxxxxxxxxxxxxxxxxxxx---

全体的な実装アイデアは次のようになります。次に、具体的な実装の詳細をいくつか見てみましょう。

2. 実装の詳細

2.1 読み込みの詳細

最初に読み込みの詳細を見てみましょう。

ご存知のとおり、単一ページ プロジェクトへの入り口は main.js で、ルートによって読み込まれるコンテンツは src/permission.js ファイルにあります。これについては、 で紹介されています。 main.js. src/permission.js のフロント ナビゲーション ガードの内容は次のとおりです:

router.beforeEach((to, from, next) => {
  NProgress.start()
  if (getToken()) {
    to.meta.title && useSettingsStore().setTitle(to.meta.title)
    /* has token*/
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      if (useUserStore().roles.length === 0) {
        isRelogin.show = true
        // 判断当前用户是否已拉取完user_info信息
        useUserStore().getInfo().then(() => {
          isRelogin.show = false
          usePermissionStore().generateRoutes().then(accessRoutes => {
            // 根据roles权限生成可访问的路由表
            accessRoutes.forEach(route => {
              if (!isHttp(route.path)) {
                router.addRoute(route) // 动态添加可访问路由表
              }
            })
            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
          })
        }).catch(err => {
          useUserStore().logOut().then(() => {
            ElMessage.error(err)
            next({ path: '/' })
          })
        })
      } else {
        next()
      }
    }
  } else {
    // 没有token
    if (whiteList.indexOf(to.path) !== -1) {
      // 在免登录白名单,直接进入
      next()
    } else {
      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
      NProgress.done()
    }
  }
})

このフロント ナビゲーション ガードのアイデアについて説明します:

  • 最初に getToken メソッドを呼び出します。このメソッドは実際に Cookie から認証トークンを取得します。これは、ログイン成功後にバックエンドがフロントエンドに返す JWT 文字列です。

  • getToken メソッドに戻り値がある場合は、ユーザーがログインしていることを意味し、if 分岐に入ります。getToken が値を取得しない場合は、ユーザーがログインしていることを意味します。ログインしていない場合は、次の 2 つの状況に分けられます: i: アクセスされたターゲット アドレスがログイン不要のホワイトリストに含まれている場合は、直接アクセスできます; ii: アクセスされたターゲット アドレスはホワイトリストに含まれていないため、直接アクセスできます。この時点でログイン ページにジャンプします。ジャンプすると同時に、リダイレクト パラメーターが含まれるため、ログインに成功した後、訪問したターゲット ページに戻るのに便利です。ログイン不要のアクセス用のこのホワイトリストは、src/permission.js ファイルで定義された変数です。デフォルトのパスは 4 つあり、['/login'、'/auth-redirect'、'/bind'、'/登録する']###。

  • getToken が値を取得した場合、ユーザーがログインしたことを意味します。このとき、さまざまな状況が考えられます。ユーザーがアクセスするパスがログイン ページの場合、ユーザーは次のようにします。プロジェクトのホームページにリダイレクトされます (つまり、ユーザーがすでにログインしている場合、ユーザーはログイン ページに再度アクセスすることはできません)。ユーザーがアクセスするパスがログイン ページではない場合は、最初にロールがvuex にはまだ値がありますか?値がある場合は、ユーザーがメニュー ボタンをクリックしてジャンプしたことを意味するため、直接ジャンプします。値がない場合は、ユーザーがブラウザの更新ボタンまたは F5 ボタンを押してページを更新したことを意味します。 getInfo メソッド (src/store/modules/user.js ファイルにある) を呼び出して、現在のユーザーの基本情報、ロール情報、および権限情報をサーバーに再ロードしてから、generateRoutes メソッド (src/store/modules にある) を呼び出します。 /permission.js ファイル) サーバーにアクセスしてルーティング情報をロードし、ロードされたルーティング情報をルーター オブジェクトに入力します (このルーティング オブジェクトが http リンクではなく、通常のルーティング アドレスである場合)。

これは、動的ルーティングの読み込みに関する全体的な考え方です。

3 番目のステップでは、2 つのメソッドが関係します。1 つは getInfo で、もう 1 つは GenerateRoutes です。これら 2 つのメソッドも重要です。詳しく見てみましょう。

2.2 getInfo

まず、ユーザー情報をロードするこのメソッドは、

src/store/modules/user.js ファイルにあります。これらのユーザーの基本情報は読み込まれ、vuex に保存されます。ブラウザを更新すると、これらのデータは失われます。

getInfo() {
  return new Promise((resolve, reject) => {
    getInfo().then(res => {
      const user = res.user
      const avatar = (user.avatar == "" || user.avatar == null) ? defAva : import.meta.env.VITE_APP_BASE_API + user.avatar;
      if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
        this.roles = res.roles
        this.permissions = res.permissions
      } else {
        this.roles = ['ROLE_DEFAULT']
      }
      this.name = user.userName
      this.avatar = avatar;
      resolve(res)
    }).catch(error => {
      reject(error)
    })
  })
},

メソッドのロジックは、実際にはあまり言う必要はありません。JSON 形式との組み合わせサーバーから返されたものは理解しやすいはずです (部分的な JSON):

{
    "permissions":[
        "*:*:*"
    ],
    "roles":[
        "admin"
    ],
    "user":
        "userName":"admin",
        "nickName":"TienChin健身",
        "avatar":"",
    }
}

另外再强调下,之前在 vhr 中,我们是将请求封装成了一个 api.js 文件,里边有常用的 get、post、put 以及 delete 请求等,然后在需要使用的地方,直接去调用这些方法发送请求即可,但是在 TienChin 中,脚手架的封装是将所有的请求都提前统一封装好,在需要的时候直接调用封装好的方法,连请求地址都不用传递了(封装的时候就已经写死了),所以小伙伴们看上面的 getInfo 方法只有方法调用,没有传递路径参数等。

2.3 generateRoutes

generateRoutes 方法则位于 src/store/modules/permission.js 文件中,这里值得说道的地方就比较多了:

generateRoutes(roles) {
  return new Promise(resolve => {
    // 向后端请求路由数据
    getRouters().then(res => {
      const sdata = JSON.parse(JSON.stringify(res.data))
      const rdata = JSON.parse(JSON.stringify(res.data))
      const defaultData = JSON.parse(JSON.stringify(res.data))
      const sidebarRoutes = filterAsyncRouter(sdata)
      const rewriteRoutes = filterAsyncRouter(rdata, false, true)
      const defaultRoutes = filterAsyncRouter(defaultData)
      const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
      asyncRoutes.forEach(route => { router.addRoute(route) })
      this.setRoutes(rewriteRoutes)
      this.setSidebarRouters(constantRoutes.concat(sidebarRoutes))
      this.setDefaultRoutes(sidebarRoutes)
      this.setTopbarRoutes(defaultRoutes)
      resolve(rewriteRoutes)
    })
  })
}

首先大家看到,服务端返回的动态菜单数据解析了三次,分别拿到了三个对象,这三个对象都是将来要用的,只不过使用的场景不同,下面结合页面的显示跟大家细说。

  • 首先是调用 filterAsyncRouter 方法,这个方法的核心作用就是将服务端返回的 component 组件动态加载为一个 component 对象。不过这个方法在调用的过程中,后面还有两个参数,第二个是 lastRouter 在该方法中并无实质性作用;第三个参数则主要是说是否需要对 children 的 path 进行重写。小伙伴们知道,服务端返回的动态菜单的 path 属性都是只有一层的,例如一级菜单系统管理的 path 是 system,二级菜单用户管理的 path 则是 user,那么用户管理最终访问的 path 就是 system/path,如果第三个参数为 true,则会进行 path 的重写,将 path 最终设置正确。

  • 所以这里的 sidebarRoutes 和 defaultRoutes 只是能用于菜单渲染(因为这两个里边的菜单 path 不对),而最终的页面跳转要通过 rewriteRoutes 才可以实现。

  • 除了服务端返回的动态菜单,前端本身也定义了一些基础菜单,前端的基础菜单分为两大类,分别是 constantRoutes 和 dynamicRoutes,其中 constantRoutes 是固定菜单,也就是一些跟用户权限无关的菜单,例如 404 页面、首页等;dynamicRoutes 是动态菜单,也就是也根据用户权限来决定是否展示的菜单,例如分配用户、字典数据、调度日志等等。

  • filterDynamicRoutes 方法则是将前端提前定义好的 dynamicRoutes 菜单进行过滤,找出那些符合当前用户权限的菜单将之添加到路由中(这些菜单都不需要在菜单栏渲染出来)。

  • 接下来涉及到四个不同的保存路由数据的变量,分别是 routes、addRoutes(经松哥分析,这个变量并无实际作用,可以删除之)、defaultRoutes、topbarRouters 以及 sidebarRouters,四个路由变量的作用各有不同:

routes:

routes 中保存的是 constantRoutes 以及服务端返回的动态路由数据,并且这个动态路由数据中的 path 已经完成了重写,所以这个 routes 主要用在两个地方:

首页的搜索上:首页的搜索也可以按照路径去搜索,所以需要用到这个 routes,如下图:

Vue3で動的メニューを読み込む方法

用在 TagsView,这个地方也需要根据页面渲染不同的菜单,也是用的 routes:

Vue3で動的メニューを読み込む方法

sidebarRouters:

这个就是大家所熟知的侧边栏菜单了,具体展示是 constantRoutes+服务端返回的菜单,不过这些 constantRoutes 基本上 hidden 属性都是 false,渲染的时候是不会被渲染出来的。

Vue3で動的メニューを読み込む方法

topbarRouters:

这个是用在 TopNav 组件中,这个是将系统的一级菜单在头部显示出来的,如下图:

Vue3で動的メニューを読み込む方法

一级菜单在顶部显示,左边显示的都是二级三级菜单,那么顶部菜单的渲染,用的就是这个 topbarRouters。

defaultRoutes:

想要开启顶部菜单,需要在 src/layout/components/Settings/index.vue 组件中设置,如下图:

Vue3で動的メニューを読み込む方法

开启顶部菜单之后,点击顶部菜单,左边菜单栏会跟着切换,此时就是从 defaultRoutes 中遍历出相关的菜单设置给 sidebarRouters。

わかりました。これがこれら 4 つのルート変数の役割です。正直に言うと、足場のこの部分のコード設計は少しわかりにくいです。それほど多くの変数を用意する必要はありません。ブラザー ソングに時間をかけてもらいましょう。誰にとっても最適化します。

generateRoutes メソッドは、最終的に rewriteRoutes 変数を前述のフロント ナビゲーション ガードに返し、最後にフロント ナビゲーション ガードがルーターにデータを追加します。

メニューの描画は src/layout/components/Sidebar/index.vue で行われており、見てみると正常な動作なので言うことはありません。

以上がVue3で動的メニューを読み込む方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。