Song Geは以前に2つの記事を書き、TienChinプロジェクトのメニューデータの問題を共有しました。まだ読んでいない人は、ここをクリックしてください。
これらの2つの記事では、主に、バックエンドが現在ログインしているユーザーに基づいてメニューJSONを動的に生成する方法について説明しています。
したがって、問題は、フロントエンドがバックエンドから返されたメニューJSONを受け取った後、それをどのようにレンダリングするかということです。これが私たちが現在直面している問題です。
TienChinプロジェクトはRuoYi足場に基づいているため、この記事の分析はRuoYi-Vue3
プロジェクト。
1.全体的なアイデア
まず、全体的な実装のアイデアを整理しましょう。最初に、全体的なアイデアはvhrとまったく同じです。
一部の友人がvhrのフロントエンド動的メニューの実装アイデアを忘れている可能性があることを考慮して、この記事ではそれを分析します。
すべての.vue
ファイル、vueにデータを保存するための一般的な場所であるvuexにメニューデータを保存することを選択し、すべての.vue
ファイルがそこからデータを読み取るvuex
ことができ。に保存されているデータvuex
は基本的にメモリに保存されているため、ブラウザがF5を押して更新した後、データが失われるという機能があります。したがって、ページジャンプが発生した場合、ユーザーがページのメニューボタンをクリックした後、またはユーザーがブラウザの更新ボタンをクリックした後(またはF5キーを押した後)にページジャンプが発生するかどうかを区別する必要があります。
これを実現するには、vueでルーティングおよびナビゲーションガード機能を使用する必要があります。Javaエンジニアにとって、これらは少し馴染みがないように聞こえるかもしれませんが、Javaのフィルターとして理解できます。実際、友人に説明するとビデオでは、これはアナロジーです。新しいものを私たちの心の中にある既存のものと比較することで簡単に理解できます。
vueのナビゲーションガードは監視のようなもので、すべてのページジャンプを監視できます。ページジャンプでは、vuexのメニューデータがまだあるかどうかを判断できます。まだある場合は、ユーザーが[メニュー]ボタンをクリックしたことを意味します。ページ上のジャンプが完了しました。ジャンプが表示されない場合は、ユーザーがブラウザの更新ボタンをクリックするか、F5キーを押してページを更新したことを意味します。このとき、サーバーにすばやく移動してメニューを再読み込みする必要があります。データ。
--- xxxxxxxxxxxxxxxxxx ---
全体的な実装の考え方は次のようになります。次に、具体的な実装の詳細を見てみましょう。
2.実装の詳細
2.1読み込みの詳細
まず、読み込みの詳細を見てみましょう。
ご存知のように、単一ページプロジェクトのエントリはmain.js
、ルートによってロードされたコンテンツがmain.jsで導入されたsrc / permit.jsファイルにあり、ナビゲーション前のガードのコンテンツがsrc/にあることです。 permit.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 / permit.jsファイルで定義されている変数です。デフォルトでは、4つのパスがあります
['/login', '/auth-redirect', '/bind', '/register']
。 - 如果 getToken 拿到了值,说明用户已经登录了,此时又分情况:如果用户访问的路径是登录页面,那么就给他重定向到项目首页(也就是在已经登录的情况下,不允许用户再次访问登录页面);如果用户访问的路径不是登录页面,那么首先判断 vuex 中的 roles 是否还有值?如果有值,说明当前就是用户点击了一个菜单按钮进行跳转的,那么直接跳转就行了;如果没有值,说明用户是按了浏览器的刷新按钮或者是 F5 按钮刷新进行的页面跳转,那么此时首先调用 getInfo 方法(位于 src/store/modules/user.js 文件中)去服务端重新加载当前用户的基本信息、角色信息以及权限信息,然后再调用 generateRoutes 方法(位于 src/store/modules/permission.js 文件中)去服务端加载路由信息,并将加载到的路由信息放入到 router 对象中(前提是这个路由对象不是一个 http 链接,就是普通的路由地址)。
这就是动态路由的加载整体思路。
在第三步骤中,涉及到两个方法,一个是 getInfo 还有一个 generateRoutes,这两个方法也都比较关键,我们再来稍微看下。
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,如下图:
- 用在 TagsView,这个地方也需要根据页面渲染不同的菜单,也是用的 routes:
sidebarRouters:
这个就是大家所熟知的侧边栏菜单了,具体展示是 constantRoutes+服务端返回的菜单,不过这些 constantRoutes 基本上 hidden 属性都是 false,渲染的时候是不会被渲染出来的。
topbarRouters:
这个是用在 TopNav 组件中,这个是将系统的一级菜单在头部显示出来的,如下图:
一级菜单在顶部显示,左边显示的都是二级三级菜单,那么顶部菜单的渲染,用的就是这个 topbarRouters。
defaultRoutes:
トップメニューを開くには、以下に示すように、src / layout / components / Settings/index.vueコンポーネントで設定する必要があります。
トップメニューを開いた後、トップメニューをクリックすると、それに応じて左側のメニューバーが切り替わります。このとき、関連するメニュー設定がdefaultRoutesからsidebarRoutersに移動します。
さて、これがこれら4つのルート変数の役割です。正直なところ、足場のこの部分のコード設計は少し混乱しています。それほど多くの変数を作成する必要はありません。Songgeに時間をかけてすべての人に最適化してもらいましょう。 。
generateRoutesメソッドは、最終的にrewriteRoutes変数を前述のpre-navigationガードに返し、最後にpre-navigationガードがルーターにデータを追加します。
メニューのレンダリングはsrc/layout / components / Sidebar / index.vueで行われます。それを読んだ後、それは通常の操作であり、言うことは何もありません。
3.まとめ
さて、これはRuoYi-Vue3の動的メニューレンダリングロジックです。皆さんはそれを理解しているのでしょうか。ビデオはもうすぐ公開されます。ビデオに興味のある方は、ここをクリックしてください:ビデオをサポートするTienChinプロジェクトはここにあります。
- ナゲッツテクノロジーコミュニティのクリエイター署名プログラムの募集に参加しています。リンクをクリックして登録し、送信してください。