關(guān)于 vue3 + vite + ts + pinia 解決權(quán)限問題方案
1.目標(biāo)
使用 pinia 保存服務(wù)器返回的權(quán)限列表生成路由列表數(shù)據(jù)和左側(cè)導(dǎo)航列表數(shù)據(jù)
2.問題描述
在使用?vue3 + pinia 實現(xiàn)此功能時产场,遇到了很多坑伯复,如:
1.登錄后獲取權(quán)限添加到路由帖鸦,但還未添加好路由時實際上已經(jīng)出發(fā)了 next()漆弄,就跳轉(zhuǎn)到 404 頁面?
2.在 router.ts 中使用 pinia,pinia 報錯噪漾,后面查了才知道是路由初始化時 pinia 還未完成初始化??
3.路由請求成功并保存在本地后刷新頁面 頁面路由丟失然后就 404,是因為刷新后路由不會保存,需要重新拿到本地保存的權(quán)限列表添加到路由然后再next() ,但此處不能直接 next(),如果直接next()凄杯,還是會執(zhí)行到 404,因為當(dāng)你刷新時進入到404秉宿,此時的 to.path 就是 404戒突,就算你添加了路由,
next()還是會進入 404描睦,所以就需要保存除404以外最后一次跳轉(zhuǎn)的路徑膊存,即代碼中的?next({ path: menuStore.currentPath })
4.登錄后如果權(quán)限成功設(shè)置,如果此時我們退出登錄并且不刷新頁面忱叭,那么路由就會保存在本地隔崎,當(dāng)我們換一個權(quán)限不同的用戶登錄時,路由里面則會添加個用戶的權(quán)限韵丑,就可以通過 url 訪問別人的權(quán)限爵卒,那么我們不可能強制刷新,這樣會影響用戶體驗撵彻,所以只能靜態(tài)刪除權(quán)限技潘,退出時遍歷本地權(quán)限列表,刪除路由中的權(quán)限??router.removeRoute(‘此處是權(quán)限的name’)
以下是我的解決方案
router.ts
import "element-plus/theme-chalk/el-notification.css"
import { ElNotification } from "element-plus"
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"
import { useMenuStore } from '@/store/modules/menu'
import {
? ? getAllPremission,
? ? getRouterList
} from "@/utils/tools"
import errorRouter from './modules/error'
const LOGIN_NAME = '/login'
/* 導(dǎo)入路由文件模板 */
export let routerArray: RouteRecordRaw[] = errorRouter
// export let routerArray: RouteRecordRaw[] = getAllPremission()
const routes: Array<RouteRecordRaw> = [
? ? {
? ? ? ? path: '/login',
? ? ? ? name: 'login',
? ? ? ? component: () => import('@/view/login/index.vue')
? ? },
? ? { path: '/', redirect: { name: 'login' } },
? ? ...routerArray,
? ? {
? ? ? ? // 找不到路由重定向到404頁面
? ? ? ? name: "NotFont",
? ? ? ? path: "/:pathMatch(.*)",
? ? ? ? redirect: { name: "404" }
? ? }
]
const router = createRouter({
? ? history: createWebHashHistory(),
? ? routes,
? ? strict: false,
? ? // 切換頁面千康,滾動到最頂部
? ? scrollBehavior: () => ({ left: 0, top: 0 })
})
let initAddRoute = true // 是否是初始化添加路由
// 路由攔截
router.beforeEach(async (to, from, next) => {
? ? const token = localStorage.getItem('token')
? ? if (to.path === LOGIN_NAME) {
? ? ? ? // 此處添加初始化字段重置,因為如果退出后不刷新然后登錄不同的用戶铲掐,權(quán)限不一樣拾弃,會直接添加進路由,這樣新的用戶
? ? ? ? // 就可以通過 url 訪問前一個用戶的權(quán)限摆霉,所以退出時必須清空路由豪椿,此處重置字段用于重新獲取權(quán)限數(shù)據(jù)
? ? ? ? initAddRoute = true
? ? ? ? next()
? ? } else {
? ? ? ? if (!token) {
? ? ? ? ? ? ElNotification({
? ? ? ? ? ? ? ? title: '登錄提示',
? ? ? ? ? ? ? ? duration: 2000,
? ? ? ? ? ? ? ? message: '登錄過期,請重新登錄111携栋!',
? ? ? ? ? ? ? ? type: 'warning',
? ? ? ? ? ? })
? ? ? ? ? ? next(LOGIN_NAME)
? ? ? ? } else {
? ? ? ? ? ? const menuStore = useMenuStore()
? ? ? ? ? ? // 如果當(dāng)前跳轉(zhuǎn)的不是 404 頁面搭盾,則保存要跳轉(zhuǎn)的頁面名稱
? ? ? ? ? ? if (to.path !== "/404") {
? ? ? ? ? ? ? ? menuStore.setCurrentPath(to.path)
? ? ? ? ? ? }
? ? ? ? ? ? // 獲取保存的路由菜單并生成符合路由數(shù)據(jù)結(jié)構(gòu)的列表的
? ? ? ? ? ? let routeList: Menu.MenuOptions[] = getRouterList(menuStore.routeList)
? ? ? ? ? ? // 登錄時獲取權(quán)限
? ? ? ? ? ? if (initAddRoute && from.name === "login") {
? ? ? ? ? ? ? ? let list = await menuStore.getPermiList()// 獲取權(quán)限
? ? ? ? ? ? ? ? routeList = await getRouterList(list)// 格式化權(quán)限
? ? ? ? ? ? }
? ? ? ? ? ? // 判斷當(dāng)前是否是 初始化/刷新 =》添加路由,如果是并且本地保存了路由菜單婉支,則添加進路由
? ? ? ? ? ? if (initAddRoute && routeList.length > 0) {
? ? ? ? ? ? ? ? routeList.forEach((item: any) => {
? ? ? ? ? ? ? ? ? ? router.addRoute(item)
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? initAddRoute = false
? ? ? ? ? ? ? ? // 初次進入時路由還未初始化好鸯隅,如果直接執(zhí)行 next(),會跳轉(zhuǎn)到本地的路由 404,
? ? ? ? ? ? ? ? // 所以登錄或刷新時判斷當(dāng)前是登錄并且跳轉(zhuǎn)的是 404蝌以,則重新觸發(fā)一次路由守衛(wèi)炕舵,此時路由已初始化完,
? ? ? ? ? ? ? ? // 然后再執(zhí)行 next()
? ? ? ? ? ? ? ? if (from.name === "login" && to.name === "404") {
? ? ? ? ? ? ? ? ? ? next({ path: './configAdmin' })// 重新觸發(fā)導(dǎo)航守衛(wèi)并保存路徑
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? next({ path: menuStore.currentPath })// 重新觸發(fā)導(dǎo)航守衛(wèi)一次
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? next()
? ? ? ? ? ? }
? ? ? ? ? ? // next()
? ? ? ? }
? ? }
})
export default router
menu.ts
import { defineStore } from "pinia"
import { MenuState } from '../interface'
import piniaPersistConfig from '@/config/piniaPersist'
import {
? ? getPermissionList,
} from '@/api/public'
import {
? ? getMenuList
} from "@/utils/tools"
// useMenuStore
export const useMenuStore = defineStore({
? ? id: "MenuState",
? ? state: (): MenuState => ({
? ? ? ? // menu collapse
? ? ? ? isCollapse: false,
? ? ? ? // routeList
? ? ? ? routeList: [],
? ? ? ? // menuList
? ? ? ? menuList: [],
? ? ? ? currentPath: ''
? ? }),
? ? getters: {},
? ? actions: {
? ? ? ? setCollapse() {
? ? ? ? ? ? this.isCollapse = !this.isCollapse
? ? ? ? },
? ? ? ? setRouteList(routeList: any) {
? ? ? ? ? ? this.routeList = routeList
? ? ? ? },
? ? ? ? setMenuList(menuList: any) {
? ? ? ? ? ? this.menuList = menuList
? ? ? ? },
? ? ? ? setCurrentPath(currentPath: string) {
? ? ? ? ? ? this.currentPath = currentPath
? ? ? ? },
? ? ? ? // 獲取權(quán)限列表
? ? ? ? async getPermiList() {
? ? ? ? ? ? const res: any = await getPermissionList()
? ? ? ? ? ? if (res.code === 200) {
? ? ? ? ? ? ? ? this.routeList = res.data
? ? ? ? ? ? ? ? this.menuList = getMenuList(res.data)
? ? ? ? ? ? }
? ? ? ? ? ? return res.data
? ? ? ? },
? ? },
? ? persist: piniaPersistConfig("MenuState")
})
utils/tools.ts
數(shù)據(jù)格式化方法跟畅,可根據(jù)自己業(yè)務(wù)的數(shù)據(jù)結(jié)構(gòu)自行更改 :
// 獲取本地所有權(quán)限列表
export const getAllPremission = () => {
? ? const metaRouters = import.meta.glob("../router/modules/*.ts", { import: 'default', eager: true })
? ? let routerArray: any[] = []
? ? Object.values(metaRouters).forEach((item: any) => {
? ? ? ? item.map((val: any) => {
? ? ? ? ? ? routerArray.push(val)
? ? ? ? })
? ? })
? ? return routerArray
}
// 封裝 動態(tài)獲取到的權(quán)限 數(shù)據(jù)結(jié)構(gòu) => 路由
export const getRouterList = (list: Menu.RequestRouteItem[]) => {
? ? if (!list.length) return []
? ? let routeList: Menu.MenuOptions[] = getAllPremission().map(routeItem => {
? ? ? ? routeItem.children?.forEach((childrenItem: any) => {
? ? ? ? ? ? childrenItem.hasPermission = list.some((listItem: Menu.RequestRouteItem) => {
? ? ? ? ? ? ? ? if (listItem.children.length) {
? ? ? ? ? ? ? ? ? ? return listItem.children.some(child => {
? ? ? ? ? ? ? ? ? ? ? ? return childrenItem.meta!.title == child.permissionName
? ? ? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? return childrenItem.meta!.title == listItem.permissionName
? ? ? ? ? ? ? ? }
? ? ? ? ? ? })
? ? ? ? ? ? if (childrenItem.meta!.title === '測試模版') {
? ? ? ? ? ? ? ? childrenItem.hasPermission = true
? ? ? ? ? ? }
? ? ? ? })
? ? ? ? return routeItem
? ? })
? ? // 生成最終權(quán)限路由列表
? ? routeList = routeList.map(item => {
? ? ? ? item.children = item.children?.filter(child => {
? ? ? ? ? ? return child.hasPermission
? ? ? ? })
? ? ? ? return item
? ? })
? ? return routeList
}