Bläddra i källkod

perf: 优化动态路由

kailong321200875 1 år sedan
förälder
incheckning
879358821d

+ 1 - 5
README.md

@@ -31,11 +31,7 @@ If you need a basic template, please switch to the `mini` branch. `mini` simply
 - [vue-element-plus-admin](https://element-plus-admin.cn/) - Full version of the github site
 - [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - Full version of the gitee site
 
-account: **admin/admin test/test**
-
-`admin` account is used to simulate the control permission of the server, and render whatever the server returns
-
-`test` account is used to simulate the front-end control authority. The server only returns the menu key to be displayed, and the front-end performs matching rendering
+account: **admin/admin**
 
 Online examples do not apply to menu filtering by default, but directly use Static routing
 

+ 1 - 5
README.zh-CN.md

@@ -31,11 +31,7 @@ vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模
 - [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点
 - [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点
 
-帐号:**admin/admin test/test**
-
-`admin` 帐号用于模拟服务端控制权限,服务端返回什么就渲染什么
-
-`test` 帐号用于模拟前端控制权限,服务端只返回需要显示的菜单 key,前端进行匹配渲染
+帐号:**admin/admin**
 
 在线例子默认不适用菜单过滤,而是直接使用静态路由表
 

+ 32 - 3
mock/role/index.ts

@@ -1070,12 +1070,41 @@ export default [
     url: '/role/list',
     method: 'get',
     timeout,
-    response: ({ query }) => {
-      const { roleName } = query
+    response: () => {
+      return {
+        data: {
+          code: code,
+          data: adminList
+        }
+      }
+    }
+  },
+  {
+    url: '/role/table',
+    method: 'get',
+    timeout,
+    response: () => {
+      return {
+        data: {
+          code: code,
+          data: {
+            list: List,
+            total: 4
+          }
+        }
+      }
+    }
+  },
+  // 列表接口
+  {
+    url: '/role/list2',
+    method: 'get',
+    timeout,
+    response: () => {
       return {
         data: {
           code: code,
-          data: roleName === 'admin' ? adminList : testList
+          data: testList
         }
       }
     }

+ 1 - 1
src/api/login/index.ts

@@ -30,5 +30,5 @@ export const getAdminRoleApi = (
 }
 
 export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
-  return request.get({ url: '/role/list', params })
+  return request.get({ url: '/role/list2', params })
 }

+ 13 - 0
src/components/Setting/src/components/InterfaceDisplay.vue

@@ -115,6 +115,14 @@ const dynamicRouterChange = (show: boolean) => {
   appStore.setDynamicRouter(show)
 }
 
+// 服务端动态路由
+const serverDynamicRouter = ref(appStore.getServerDynamicRouter)
+
+const serverDynamicRouterChange = (show: boolean) => {
+  ElMessage.info(t('setting.reExperienced'))
+  appStore.setServerDynamicRouter(show)
+}
+
 // 固定菜单
 const fixedMenu = ref(appStore.getFixedMenu)
 
@@ -206,6 +214,11 @@ watch(
       <ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
     </div>
 
+    <div class="flex justify-between items-center">
+      <span class="text-14px">{{ t('setting.serverDynamicRouter') }}</span>
+      <ElSwitch v-model="serverDynamicRouter" @change="serverDynamicRouterChange" />
+    </div>
+
     <div class="flex justify-between items-center">
       <span class="text-14px">{{ t('setting.fixedMenu') }}</span>
       <ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />

+ 22 - 7
src/hooks/web/useStorage.ts

@@ -1,15 +1,21 @@
-import { isArray, isObject } from '@/utils/is'
+// 获取传入的值的类型
+const getValueType = (value: any) => {
+  const type = Object.prototype.toString.call(value)
+  return type.slice(8, -1)
+}
 
 export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionStorage') => {
   const setStorage = (key: string, value: any) => {
-    window[type].setItem(key, isArray(value) || isObject(value) ? JSON.stringify(value) : value)
+    const valueType = getValueType(value)
+    window[type].setItem(key, JSON.stringify({ type: valueType, value }))
   }
 
   const getStorage = (key: string) => {
     const value = window[type].getItem(key)
-    try {
-      return JSON.parse(value || '')
-    } catch (error) {
+    if (value) {
+      const { value: val } = JSON.parse(value)
+      return val
+    } else {
       return value
     }
   }
@@ -18,8 +24,17 @@ export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionSto
     window[type].removeItem(key)
   }
 
-  const clear = () => {
-    window[type].clear()
+  const clear = (excludes?: string[]) => {
+    // 获取排除项
+    const keys = Object.keys(window[type])
+    const defaultExcludes = ['dynamicRouter', 'serverDynamicRouter']
+    const excludesArr = excludes ? [...excludes, ...defaultExcludes] : defaultExcludes
+    const excludesKeys = excludesArr ? keys.filter((key) => !excludesArr.includes(key)) : keys
+    // 排除项不清除
+    excludesKeys.forEach((key) => {
+      window[type].removeItem(key)
+    })
+    // window[type].clear()
   }
 
   return {

+ 3 - 1
src/locales/en.ts

@@ -93,7 +93,9 @@ export default {
     footer: 'Footer',
     uniqueOpened: 'Unique opened',
     tagsViewIcon: 'Tags view icon',
-    dynamicRouter: 'Dynamic router',
+    // 开启动态路由
+    dynamicRouter: 'Enable dynamic router',
+    serverDynamicRouter: 'Server dynamic router',
     reExperienced: 'Please exit the login experience again',
     fixedMenu: 'Fixed menu'
   },

+ 2 - 1
src/locales/zh-CN.ts

@@ -93,7 +93,8 @@ export default {
     footer: '页脚',
     uniqueOpened: '菜单手风琴',
     tagsViewIcon: '标签页图标',
-    dynamicRouter: '动态路由',
+    dynamicRouter: '开启动态路由',
+    serverDynamicRouter: '服务端动态路由',
     reExperienced: '请重新退出登录体验',
     fixedMenu: '固定菜单'
   },

+ 4 - 17
src/permission.ts

@@ -5,16 +5,12 @@ import type { RouteRecordRaw } from 'vue-router'
 import { useTitle } from '@/hooks/web/useTitle'
 import { useNProgress } from '@/hooks/web/useNProgress'
 import { usePermissionStoreWithOut } from '@/store/modules/permission'
-// import { useDictStoreWithOut } from '@/store/modules/dict'
 import { usePageLoading } from '@/hooks/web/usePageLoading'
-// import { getDictApi } from '@/api/common'
 
 const permissionStore = usePermissionStoreWithOut()
 
 const appStore = useAppStoreWithOut()
 
-// const dictStore = useDictStoreWithOut()
-
 const { getStorage } = useStorage()
 
 const { start, done } = useNProgress()
@@ -30,14 +26,6 @@ router.beforeEach(async (to, from, next) => {
     if (to.path === '/login') {
       next({ path: '/' })
     } else {
-      // if (!dictStore.getIsSetDict) {
-      //   // 获取所有字典
-      //   const res = await getDictApi()
-      //   if (res) {
-      //     dictStore.setDictObj(res.data)
-      //     dictStore.setIsSetDict(true)
-      //   }
-      // }
       if (permissionStore.getIsAddRouters) {
         next()
         return
@@ -45,15 +33,14 @@ router.beforeEach(async (to, from, next) => {
 
       // 开发者可根据实际情况进行修改
       const roleRouters = getStorage('roleRouters') || []
-      const userInfo = getStorage(appStore.getUserInfo)
 
       // 是否使用动态路由
       if (appStore.getDynamicRouter) {
-        userInfo.role === 'admin'
-          ? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
-          : await permissionStore.generateRoutes('test', roleRouters as string[])
+        appStore.serverDynamicRouter
+          ? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
+          : await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
       } else {
-        await permissionStore.generateRoutes('none')
+        await permissionStore.generateRoutes('static')
       }
 
       permissionStore.getAddRouters.forEach((route) => {

+ 12 - 4
src/store/modules/app.ts

@@ -21,6 +21,7 @@ interface AppState {
   fixedHeader: boolean
   greyMode: boolean
   dynamicRouter: boolean
+  serverDynamicRouter: boolean
   pageLoading: boolean
   layout: LayoutType
   title: string
@@ -42,7 +43,6 @@ export const useAppStore = defineStore('app', {
       mobile: false, // 是否是移动端
       title: import.meta.env.VITE_APP_TITLE, // 标题
       pageLoading: false, // 路由跳转loading
-
       breadcrumb: true, // 面包屑
       breadcrumbIcon: true, // 面包屑图标
       collapse: false, // 折叠菜单
@@ -57,11 +57,12 @@ export const useAppStore = defineStore('app', {
       fixedHeader: true, // 固定toolheader
       footer: true, // 显示页脚
       greyMode: false, // 是否开始灰色模式,用于特殊悼念日
-      dynamicRouter: getStorage('dynamicRouter') || true, // 是否动态路由
-      fixedMenu: getStorage('fixedMenu') || false, // 是否固定菜单
+      dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
+      serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
+      fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
 
       layout: getStorage('layout') || 'classic', // layout布局
-      isDark: getStorage('isDark') || false, // 是否是暗黑模式
+      isDark: getStorage('isDark'), // 是否是暗黑模式
       currentSize: getStorage('default') || 'default', // 组件尺寸
       theme: getStorage('theme') || {
         // 主题色
@@ -138,6 +139,9 @@ export const useAppStore = defineStore('app', {
     getDynamicRouter(): boolean {
       return this.dynamicRouter
     },
+    getServerDynamicRouter(): boolean {
+      return this.serverDynamicRouter
+    },
     getFixedMenu(): boolean {
       return this.fixedMenu
     },
@@ -216,6 +220,10 @@ export const useAppStore = defineStore('app', {
       setStorage('dynamicRouter', dynamicRouter)
       this.dynamicRouter = dynamicRouter
     },
+    setServerDynamicRouter(serverDynamicRouter: boolean) {
+      setStorage('serverDynamicRouter', serverDynamicRouter)
+      this.serverDynamicRouter = serverDynamicRouter
+    },
     setFixedMenu(fixedMenu: boolean) {
       setStorage('fixedMenu', fixedMenu)
       this.fixedMenu = fixedMenu

+ 0 - 34
src/store/modules/dict.ts

@@ -1,34 +0,0 @@
-import { defineStore } from 'pinia'
-import { store } from '../index'
-
-export interface DictState {
-  isSetDict: boolean
-  dictObj: Recordable
-}
-
-export const useDictStore = defineStore('dict', {
-  state: (): DictState => ({
-    isSetDict: false,
-    dictObj: {}
-  }),
-  getters: {
-    getDictObj(): Recordable {
-      return this.dictObj
-    },
-    getIsSetDict(): boolean {
-      return this.isSetDict
-    }
-  },
-  actions: {
-    setDictObj(dictObj: Recordable) {
-      this.dictObj = dictObj
-    },
-    setIsSetDict(isSetDict: boolean) {
-      this.isSetDict = isSetDict
-    }
-  }
-})
-
-export const useDictStoreWithOut = () => {
-  return useDictStore(store)
-}

+ 10 - 6
src/store/modules/permission.ts

@@ -1,6 +1,10 @@
 import { defineStore } from 'pinia'
 import { asyncRouterMap, constantRouterMap } from '@/router'
-import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
+import {
+  generateRoutesByFrontEnd,
+  generateRoutesByServer,
+  flatMultiLevelRoutes
+} from '@/utils/routerHelper'
 import { store } from '../index'
 import { cloneDeep } from 'lodash-es'
 
@@ -34,17 +38,17 @@ export const usePermissionStore = defineStore('permission', {
   },
   actions: {
     generateRoutes(
-      type: 'admin' | 'test' | 'none',
+      type: 'server' | 'frontEnd' | 'static',
       routers?: AppCustomRouteRecordRaw[] | string[]
     ): Promise<unknown> {
       return new Promise<void>((resolve) => {
         let routerMap: AppRouteRecordRaw[] = []
-        if (type === 'admin') {
+        if (type === 'server') {
           // 模拟后端过滤菜单
-          routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[])
-        } else if (type === 'test') {
+          routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
+        } else if (type === 'frontEnd') {
           // 模拟前端过滤菜单
-          routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])
+          routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
         } else {
           // 直接读取静态路由表
           routerMap = cloneDeep(asyncRouterMap)

+ 10 - 6
src/utils/routerHelper.ts

@@ -38,7 +38,7 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
 }
 
 // 前端控制路由生成
-export const generateRoutesFn1 = (
+export const generateRoutesByFrontEnd = (
   routes: AppRouteRecordRaw[],
   keys: string[],
   basePath = '/'
@@ -69,7 +69,7 @@ export const generateRoutesFn1 = (
       if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
         data = Object.assign({}, route)
       } else {
-        const routePath = onlyOneChild ?? pathResolve(basePath, route.path)
+        const routePath = (onlyOneChild ?? pathResolve(basePath, route.path)).trim()
         if (routePath === item || meta.followRoute === item) {
           data = Object.assign({}, route)
         }
@@ -78,7 +78,11 @@ export const generateRoutesFn1 = (
 
     // recursive child routes
     if (route.children && data) {
-      data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path))
+      data.children = generateRoutesByFrontEnd(
+        route.children,
+        keys,
+        pathResolve(basePath, data.path)
+      )
     }
     if (data) {
       res.push(data as AppRouteRecordRaw)
@@ -88,7 +92,7 @@ export const generateRoutesFn1 = (
 }
 
 // 后端控制路由生成
-export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
+export const generateRoutesByServer = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
   const res: AppRouteRecordRaw[] = []
 
   for (const route of routes) {
@@ -111,7 +115,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
     }
     // recursive child routes
     if (route.children) {
-      data.children = generateRoutesFn2(route.children)
+      data.children = generateRoutesByServer(route.children)
     }
     res.push(data as AppRouteRecordRaw)
   }
@@ -121,7 +125,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
 export const pathResolve = (parentPath: string, path: string) => {
   if (isUrl(path)) return path
   const childPath = path.startsWith('/') || !path ? path : `/${path}`
-  return `${parentPath}${childPath}`.replace(/\/\//g, '/')
+  return `${parentPath}${childPath}`.replace(/\/\//g, '/').trim()
 }
 
 // 路由降级

+ 7 - 8
src/views/Login/components/LoginForm.vue

@@ -220,7 +220,7 @@ const signIn = async () => {
           if (appStore.getDynamicRouter) {
             getRole()
           } else {
-            await permissionStore.generateRoutes('none').catch(() => {})
+            await permissionStore.generateRoutes('static').catch(() => {})
             permissionStore.getAddRouters.forEach((route) => {
               addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
             })
@@ -241,17 +241,16 @@ const getRole = async () => {
   const params = {
     roleName: formData.username
   }
-  // admin - 模拟后端过滤菜单
-  // test - 模拟前端过滤菜单
   const res =
-    formData.username === 'admin' ? await getAdminRoleApi(params) : await getTestRoleApi(params)
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await getAdminRoleApi(params)
+      : await getTestRoleApi(params)
   if (res) {
     const routers = res.data || []
     setStorage('roleRouters', routers)
-
-    formData.username === 'admin'
-      ? await permissionStore.generateRoutes('admin', routers).catch(() => {})
-      : await permissionStore.generateRoutes('test', routers).catch(() => {})
+    appStore.getDynamicRouter && appStore.getServerDynamicRouter
+      ? await permissionStore.generateRoutes('server', routers).catch(() => {})
+      : await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
 
     permissionStore.getAddRouters.forEach((route) => {
       addRoute(route as RouteRecordRaw) // 动态添加可访问路由表