關于 vue3 + vite + ts + pinia 解決權限問題方案
1.目標
使用 pinia 保存服務器返回的權限列表生成路由列表數據和左側導航列表數據
2.問題描述
在使用?vue3 + pinia 實現(xiàn)此功能時,遇到了很多坑,如:
1.登錄后獲取權限添加到路由,但還未添加好路由時實際上已經出發(fā)了 next(),就跳轉到 404 頁面?
2.在 router.ts 中使用 pinia,pinia 報錯,后面查了才知道是路由初始化時 pinia 還未完成初始化??
3.路由請求成功并保存在本地后刷新頁面 頁面路由丟失然后就 404,是因為刷新后路由不會保存,需要重新拿到本地保存的權限列表添加到路由然后再next() ,但此處不能直接 next(),如果直接next(),還是會執(zhí)行到 404,因為當你刷新時進入到404,此時的 to.path 就是 404,就算你添加了路由,
next()還是會進入 404,所以就需要保存除404以外最后一次跳轉的路徑,即代碼中的?next({ path: menuStore.currentPath })
4.登錄后如果權限成功設置,如果此時我們退出登錄并且不刷新頁面,那么路由就會保存在本地,當我們換一個權限不同的用戶登錄時,路由里面則會添加個用戶的權限,就可以通過 url 訪問別人的權限,那么我們不可能強制刷新,這樣會影響用戶體驗,所以只能靜態(tài)刪除權限,退出時遍歷本地權限列表,刪除路由中的權限??router.removeRoute(‘此處是權限的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'
/* 導入路由文件模板 */
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) {
? ? ? ? // 此處添加初始化字段重置,因為如果退出后不刷新然后登錄不同的用戶,權限不一樣,會直接添加進路由,這樣新的用戶
? ? ? ? // 就可以通過 url 訪問前一個用戶的權限,所以退出時必須清空路由,此處重置字段用于重新獲取權限數據
? ? ? ? initAddRoute = true
? ? ? ? next()
? ? } else {
? ? ? ? if (!token) {
? ? ? ? ? ? ElNotification({
? ? ? ? ? ? ? ? title: '登錄提示',
? ? ? ? ? ? ? ? duration: 2000,
? ? ? ? ? ? ? ? message: '登錄過期,請重新登錄111!',
? ? ? ? ? ? ? ? type: 'warning',
? ? ? ? ? ? })
? ? ? ? ? ? next(LOGIN_NAME)
? ? ? ? } else {
? ? ? ? ? ? const menuStore = useMenuStore()
? ? ? ? ? ? // 如果當前跳轉的不是 404 頁面,則保存要跳轉的頁面名稱
? ? ? ? ? ? if (to.path !== "/404") {
? ? ? ? ? ? ? ? menuStore.setCurrentPath(to.path)
? ? ? ? ? ? }
? ? ? ? ? ? // 獲取保存的路由菜單并生成符合路由數據結構的列表的
? ? ? ? ? ? let routeList: Menu.MenuOptions[] = getRouterList(menuStore.routeList)
? ? ? ? ? ? // 登錄時獲取權限
? ? ? ? ? ? if (initAddRoute && from.name === "login") {
? ? ? ? ? ? ? ? let list = await menuStore.getPermiList()// 獲取權限
? ? ? ? ? ? ? ? routeList = await getRouterList(list)// 格式化權限
? ? ? ? ? ? }
? ? ? ? ? ? // 判斷當前是否是 初始化/刷新 =》添加路由,如果是并且本地保存了路由菜單,則添加進路由
? ? ? ? ? ? if (initAddRoute && routeList.length > 0) {
? ? ? ? ? ? ? ? routeList.forEach((item: any) => {
? ? ? ? ? ? ? ? ? ? router.addRoute(item)
? ? ? ? ? ? ? ? })
? ? ? ? ? ? ? ? initAddRoute = false
? ? ? ? ? ? ? ? // 初次進入時路由還未初始化好,如果直接執(zhí)行 next(),會跳轉到本地的路由 404,
? ? ? ? ? ? ? ? // 所以登錄或刷新時判斷當前是登錄并且跳轉的是 404,則重新觸發(fā)一次路由守衛(wèi),此時路由已初始化完,
? ? ? ? ? ? ? ? // 然后再執(zhí)行 next()
? ? ? ? ? ? ? ? if (from.name === "login" && to.name === "404") {
? ? ? ? ? ? ? ? ? ? next({ path: './configAdmin' })// 重新觸發(fā)導航守衛(wèi)并保存路徑
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? next({ path: menuStore.currentPath })// 重新觸發(fā)導航守衛(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
? ? ? ? },
? ? ? ? // 獲取權限列表
? ? ? ? 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
數據格式化方法,可根據自己業(yè)務的數據結構自行更改 :
// 獲取本地所有權限列表
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)獲取到的權限 數據結構 => 路由
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
? ? })
? ? // 生成最終權限路由列表
? ? routeList = routeList.map(item => {
? ? ? ? item.children = item.children?.filter(child => {
? ? ? ? ? ? return child.hasPermission
? ? ? ? })
? ? ? ? return item
? ? })
? ? return routeList
}
