Explorar el Código

Merge branch 'master' of http://git.cc-lotus.info/Information/technique_admin

zs hace 11 meses
padre
commit
eef059912e

+ 1 - 1
README.md

@@ -3,4 +3,4 @@
 ## 1.菜单设置
 ### 1.1 功能列表
 > 前端:主要是控制角色是否可以显示按钮
-> 服务:主要是用同一编码对接口的使用进行控制
+> 服务:主要是用同一编码对接口的使用进行控制

+ 2 - 0
src/lang/package/zh-cn/common.js

@@ -1,4 +1,5 @@
 export default {
+  route_loading: '页面加载中...',
   opera: '操作',
   back: '返回',
   create: '添加',
@@ -23,6 +24,7 @@ export default {
   cancel: '取消',
   user_confirm: '用户确认',
   re_login: '重新登录',
+  reload: '重新加载',
   opera_success: '操作成功',
   opera_fail: '操作失败',
   token_error: '用户验证失败,请重新登录'

+ 1 - 1
src/layout/parts/Header.vue

@@ -38,7 +38,7 @@ const router = useRouter()
 const logout = () => {
   userStore.logOut()
   tagsViewStore.delAllViews()
-  window.location.href = '/login'
+  window.location.href = `${import.meta.env.VITE_BASE_URL}/login`
 }
 // 个人中心
 const center = () => {

+ 27 - 203
src/router/guard.js

@@ -1,132 +1,10 @@
-import { AxiosWrapper } from '@/utils/axios-wrapper'
-import { UserStore } from '@/store/user'
-import { cloneDeep, get, isArray, omit } from 'lodash-es'
+import { get } from 'lodash-es'
 import NProgress from 'nprogress'
 import 'nprogress/nprogress.css'
-import { ElMessageBox } from 'element-plus'
-import i18n from '@/lang'
-const whiteList = ['/redirect', '/login', '/401', '/404']
-NProgress.configure({ showSpinner: false }) // 进度条
-// 检查路由是否存在
-const hasNecessaryRoute = (to, router) => {
-  // 将默认注册的路由平铺成一维数组
-  const routesOneDimensional = toOneDimensional(router.getRoutes())
-  return routesOneDimensional.find((f) => f.path === to.path)
-}
-// 获取用户信息,返回菜单
-const getUserMeta = async (token) => {
-  const userStore = UserStore()
-  const axios = new AxiosWrapper()
-  const result = await axios.$get(`/token/tokenView`, null, {
-    headers: {
-      token: token
-    }
-  })
-  if (result.errcode === 0) {
-    userStore.setUser(result.data)
-    const resetMenusResult = resetMenus(result.data.menus)
-    const storeMenus = toRaw(userStore.menus)
-    if (storeMenus.length <= 0) {
-      userStore.setMenus(resetMenusResult)
-    }
-    return { menus: result.data.menus, errcode: 0 }
-  }
-  return { errmsg: result.errmsg, errcode: result.errcode }
-}
-/**
- * 将路由数组一维化
- * @param {Array} routes 路由数组
- * @returns 一维路由数组
- */
-const toOneDimensional = (routes) => {
-  const result = []
-  for (const r of routes) {
-    const { children = [], ...others } = r
-    result.push(others)
-    if (children.length > 0) result.push(...toOneDimensional(children))
-  }
-  return result
-}
-// 添加路由
-const addUserRoutes = async (menus, router) => {
-  return new Promise((resolve, reject) => {
-    // 将用户菜单转换成普通对象
-    const menuArr = toRaw(menus)
-    // 将用户菜单平铺成一维数组,并将目录过滤出去.目录不需要注册,不是组件
-    const menuOneDimensional = toOneDimensional(menuArr)
-    // 将默认注册的路由平铺成一维数组
-    const routesOneDimensional = toOneDimensional(router.getRoutes())
-    routesRegister(menuOneDimensional, routesOneDimensional, router)
-    resolve()
-  })
-}
-/**
- * 注册路由
- * @param {Array} menus 一维数组菜单
- * @param {Array} defaultRoutes 默认路由一维数组
- * @param {*} router 路由实例
- */
-const routesRegister = (menus, defaultRoutes, router) => {
-  // 默认注册位置
-  const __def = 'Layout'
-  const loadComponent = import.meta.glob('../views/**/*.vue')
-  for (const route of menus) {
-    const { type, route_name, path, component, parent_id } = route
-    if (!path) {
-      // console.log(route)
-    }
-    // 检查路由是否已存在,存在跳过
-    const hasRoute = defaultRoutes.find((f) => f.path === path)
-    if (hasRoute) continue
-    if (type === '0') {
-      // 目录, 默认使用 `/${路由名称}`
-      const route = {
-        path: `/${route_name}`,
-        name: route_name,
-        meta: { title: route_name, type }
-      }
-      router.addRoute(__def, route)
-    } else if (type === '1' || type === '2') {
-      // 菜单 或 子菜单
-      const route = {
-        path: path,
-        name: route_name,
-        meta: {
-          title: route_name,
-          type
-        },
-        component: loadComponent[`../views${component}.vue`]
-      }
-      if (parent_id) {
-        const parent = menus.find((f) => f._id === parent_id)
-        if (!parent) continue
-        const pos = parent.route_name
-        router.addRoute(pos, route)
-        // try {
-        // } catch (error) {
-        //   console.log(pos, route)
-        //   console.error(error)
-        // }
-      } else {
-        router.addRoute(__def, route)
-      }
-    }
-  }
-}
-
-const resetMenus = (menus) => {
-  if (!isArray(menus) || menus.length <= 0) return []
-  const cMenus = cloneDeep(menus)
-  const result = []
-  for (const m of cMenus) {
-    const mid = omit(m, ['is_use', 'order_num', 'in_admin_frame'])
-    const { children } = mid
-    if (children) mid.children = resetMenus(children)
-    result.push(mid)
-  }
-  return result
-}
+import { UserStore } from '@/store/user'
 
+const whiteList = ['/redirect', '/login', '/401', '/404', '/route/loading']
+NProgress.configure({ showSpinner: false }) // 进度条
 const dontRedirectList = ['/login', '/', '/401', '/404']
 
 // 注册前置守卫
@@ -139,84 +17,30 @@ export const registerBeforeRouter = async (router) => {
       next()
       return
     }
-    if (token) {
-      NProgress.inc()
-      if (to.path === '/login') next()
-      NProgress.inc()
-      try {
-        const { menus, errcode, errmsg } = await getUserMeta(token)
-        // 登录信息有问题
-        if (errcode !== 0) {
-          if (errcode.includes('FRAMEERROR_401')) {
-            await ElMessageBox.alert(errmsg, i18n.global.t('common.user_confirm'), {
-              confirmButtonText: i18n.global.t('common.re_login'),
-              type: 'error'
-            })
-            const fp = from.path
-            const noneed = dontRedirectList.includes(fp)
-            if (noneed) next('/login')
-            else next(`/login?redirect=${from.fullPath}`)
-            return
-          } else {
-            await ElMessageBox.alert(errmsg, i18n.global.t('common.user_confirm'), {
-              confirmButtonText: i18n.global.t('common.re_login'),
-              type: 'error'
-            })
-            // location.reload()
-            const fp = from.path
-            const noneed = dontRedirectList.includes(fp)
-            if (noneed) next('/login')
-            else next(`/login?redirect=${from.fullPath}`)
-          }
-        }
-        // 菜单格式不正确
-        if (!menus) {
-          next('/401')
-          return
-        }
-        // 检查目的地路由是否注册
-        const hasRoute = hasNecessaryRoute(to, router)
-        NProgress.inc()
-        if (hasRoute || to.meta.hidden) {
-          // 注册了直接进入
-          if (get(from, 'query.redirect')) {
-            const redirect = get(from, 'query.redirect')
-            from.query = {}
-            next(redirect)
-            return
-          } else {
-            next()
-            return
-          }
-        } else {
-          // 没注册就先注册再重定向进入直到进入为止
-          await addUserRoutes(menus, router)
-          NProgress.inc()
-          if (get(from, 'query.redirect')) {
-            const redirect = get(from, 'query.redirect')
-            from.query = {}
-            next(redirect)
-          } else {
-            await next(to)
-          }
-
-          // next({ ...to, replace: true })
-        }
-      } catch (error) {
-        await ElMessageBox.alert(i18n.global.t('common.token_error'), i18n.global.t('common.user_confirm'), {
-          confirmButtonText: i18n.global.t('common.re_login'),
-          type: 'error'
-        })
-        const fp = from.path
-        const noneed = dontRedirectList.includes(fp)
-        if (noneed) next('/login')
-        else next(`/login?redirect=${from.fullPath}`)
-      }
+    if (!token) {
+      // 没有登录信息,返回login,并记录当前路由
+      console.log('no token & not in whiteList')
+      const redirectPath = to.fullPath
+      next({ path: '/login', query: { redirectPath } })
+      return
     } else {
-      const fp = from.path
-      const noneed = dontRedirectList.includes(fp)
-      if (noneed) next('/login')
-      else next(`/login?redirect=${from.fullPath}`)
+      const userStore = UserStore()
+      const userMenus = toRaw(userStore.menus)
+      if (!userMenus || userMenus.length <= 0) {
+        console.log('has token but no menus')
+        // 没有菜单,说明路由没有注册,需要注册路由,记录当前去哪里再跳转至路由
+        let redirectPath = to.fullPath
+        next({ path: '/route/loading', query: { redirectPath } })
+        return
+      } else {
+        // 路由注册了,直接gogogo
+        // 检查有没有redirectPath
+        console.log('continue')
+        let redirectPath = get(to, 'query.redirectPath')
+        if (redirectPath) next(redirectPath)
+        next()
+        return
+      }
     }
   })
 }

+ 5 - 0
src/router/index.js

@@ -5,6 +5,11 @@ export const Layout = () => import('@/layout/index.vue')
 import i18n from '@/lang'
 // 静态路由
 export const constantRoutes = [
+  {
+    path: '/route/loading',
+    component: () => import('@/views/route-loading/index.vue'),
+    meta: { hidden: true }
+  },
   {
     path: '/redirect',
     component: Layout,

+ 121 - 0
src/router/register.js

@@ -0,0 +1,121 @@
+import { UserStore } from '@/store/user'
+import { cloneDeep, isArray, omit } from 'lodash-es'
+
+// 获取用户信息,返回菜单
+export const getUserMeta = async (token) => {
+  const userStore = UserStore()
+  const result = await userStore.tokenView(token)
+  if (result.errcode === 0) {
+    userStore.setUser(result.data)
+    const resetMenusResult = resetMenus(result.data.menus)
+    const storeMenus = toRaw(userStore.menus)
+    if (storeMenus.length <= 0) {
+      userStore.setMenus(resetMenusResult)
+    }
+    return { menus: result.data.menus, errcode: 0 }
+  }
+}
+
+// 检查路由是否存在
+export const hasNecessaryRoute = (to, router) => {
+  // 将默认注册的路由平铺成一维数组
+  const routesOneDimensional = toOneDimensional(router.getRoutes())
+  return routesOneDimensional.find((f) => f.path === to.path)
+}
+
+/**
+ * 将路由数组一维化
+ * @param {Array} routes 路由数组
+ * @returns 一维路由数组
+ */
+const toOneDimensional = (routes) => {
+  const result = []
+  for (const r of routes) {
+    const { children = [], ...others } = r
+    result.push(others)
+    if (children.length > 0) result.push(...toOneDimensional(children))
+  }
+  return result
+}
+
+// 添加路由
+export const addUserRoutes = (menus, router) => {
+  return new Promise((resolve, reject) => {
+    // 将用户菜单转换成普通对象
+    const menuArr = toRaw(menus)
+    // 将用户菜单平铺成一维数组,并将目录过滤出去.目录不需要注册,不是组件
+    const menuOneDimensional = toOneDimensional(menuArr)
+    // 将默认注册的路由平铺成一维数组
+    const routesOneDimensional = toOneDimensional(router.getRoutes())
+    routesRegister(menuOneDimensional, routesOneDimensional, router)
+    console.log('route adding complete')
+    resolve()
+  })
+}
+
+/**
+ * 注册路由
+ * @param {Array} menus 一维数组菜单
+ * @param {Array} defaultRoutes 默认路由一维数组
+ * @param {*} router 路由实例
+ */
+const routesRegister = (menus, defaultRoutes, router) => {
+  // 默认注册位置
+  const __def = 'Layout'
+  const loadComponent = import.meta.glob('../views/**/*.vue')
+  for (const route of menus) {
+    const { type, route_name, path, component, parent_id } = route
+    if (!path) {
+      // console.log(route)
+    }
+    // 检查路由是否已存在,存在跳过
+    const hasRoute = defaultRoutes.find((f) => f.path === path)
+    if (hasRoute) continue
+    if (type === '0') {
+      // 目录, 默认使用 `/${路由名称}`
+      const route = {
+        path: `/${route_name}`,
+        name: route_name,
+        meta: { title: route_name, type }
+      }
+      router.addRoute(__def, route)
+    } else if (type === '1' || type === '2') {
+      // 菜单 或 子菜单
+      const route = {
+        path: path,
+        name: route_name,
+        meta: {
+          title: route_name,
+          type
+        },
+        component: loadComponent[`../views${component}.vue`]
+      }
+      if (parent_id) {
+        const parent = menus.find((f) => f._id === parent_id)
+        if (!parent) continue
+        const pos = parent.route_name
+        router.addRoute(pos, route)
+        // try {
+        // } catch (error) {
+        //   console.log(pos, route)
+        //   console.error(error)
+        // }
+      } else {
+        router.addRoute(__def, route)
+      }
+    }
+  }
+}
+
+const resetMenus = (menus) => {
+  if (!isArray(menus) || menus.length <= 0) return []
+  const cMenus = cloneDeep(menus)
+  const result = []
+  for (const m of cMenus) {
+    const mid = omit(m, ['is_use', 'order_num', 'in_admin_frame'])
+    const { children } = mid
+    if (children) mid.children = resetMenus(children)
+    result.push(mid)
+  }
+  return result
+}

+ 12 - 1
src/store/user.js

@@ -1,4 +1,6 @@
 import { defineStore } from 'pinia'
+import { AxiosWrapper } from '@/utils/axios-wrapper'
+const axios = new AxiosWrapper()
 export const UserStore = defineStore('user', () => {
   const user = ref({})
   const menus = ref([])
@@ -19,5 +21,14 @@ export const UserStore = defineStore('user', () => {
   const setMenus = (payload) => {
     menus.value = payload
   }
-  return { user, setUser, logOut, menus, setMenus }
+
+  const tokenView = async (token) => {
+    const result = await axios.$get(`/token/tokenView`, null, {
+      headers: {
+        token: token
+      }
+    })
+    return result
+  }
+  return { user, setUser, logOut, menus, setMenus, tokenView }
 })

+ 63 - 48
src/utils/axios-wrapper.js

@@ -10,7 +10,7 @@ import i18n from '@/lang'
 import * as crypto from './crypto'
 let currentRequests = 0
 const { VITE_APP_BASE_API, VITE_USE_CRYPTO } = import.meta.env
-
+const userErrorCodeList = ['NOT_LOGIN', 'ACCOUNT_HAS_EXPIRED', 'ACCOUNT_LOGGED_IN_ELESWHERE', 'USER_NOT_FOUND', 'USER_IS_DISABLED', 'ROLE_IS_DISABLED']
 export class AxiosWrapper {
   constructor({ baseUrl = VITE_APP_BASE_API, unwrap = true } = {}) {
     this.baseUrl = baseUrl
@@ -79,44 +79,8 @@ export class AxiosWrapper {
         baseURL: this.baseUrl,
         withCredentials: true
       })
-      // #region 加密部分
-      // 加密,需要根据env文件判断是否启用加密
-      if (VITE_USE_CRYPTO) {
-        // 生成随机字符串
-        const reqCode = crypto.getRandomString()
-        axios.interceptors.request.use(async (config) => {
-          // 加密真实使用的加密字符串
-          const deReqCode = crypto.pemEncrypt(reqCode)
-          // 加密数据并替换
-          config.transformRequest = (data) => {
-            if (data) {
-              // 加密 加密字符串
-              const strData = JSON.stringify(data)
-              // 加密数据
-              const enCodeData = crypto.encrypt(deReqCode, strData)
-              // 替换数据位置
-              return { data: enCodeData }
-            }
-            return undefined
-          }
-          // 添加请求头,将 加密的真实使用加密字符串附上
-          config.headers['api-token'] = deReqCode
-          return config
-        })
-        axios.interceptors.response.use(
-          (response) => {
-            if (get(response, 'data.data')) {
-              let data = crypto.decrypt(reqCode, get(response, 'data.data'))
-              const dobj = JSON.parse(data || '{}')
-              const others = pick(get(response, 'data'), ['errcode', 'errmsg'])
-              response.data = { ...others, ...dobj }
-            }
-            return response
-          },
-          (error) => Promise.reject(error)
-        )
-      }
-      // #endregion
+      AxiosWrapper.toCropty(axios)
+
       const token = localStorage.getItem('token')
       if (token) axios.defaults.headers.common['token'] = token
 
@@ -133,15 +97,19 @@ export class AxiosWrapper {
       if (errcode) {
         console.warn(`[${uri}] fail: ${errcode}-${errmsg} ${details}`)
         if (errcode !== 0) {
-          console.log(router)
-          if (errcode.includes('FRAMEERROR_401')) {
-            // await ElMessageBox.alert(errmsg, i18n.global.t('common.user_confirm'), {
-            //   confirmButtonText: i18n.global.t('common.re_login'),
-            //   type: 'error',
-            //   callback: (act) => {
-            //     // router.replace('/login')
-            //   }
-            // })
+          if (userErrorCodeList.includes(errcode)) {
+            const nowRouteFullPath = router.currentRoute.value.fullPath
+            ElMessageBox.confirm(errmsg, i18n.global.t('common.user_confirm'), {
+              confirmButtonText: i18n.global.t('common.re_login'),
+              cancelButtonText: i18n.global.t('common.reload'),
+              type: 'error'
+            })
+              .then(() => {
+                if (nowRouteFullPath !== '/login') window.location.href = `${import.meta.env.VITE_BASE_URL}/login`
+              })
+              .catch(() => {
+                if (nowRouteFullPath !== '/login') location.reload()
+              })
           }
         }
         return returnRes
@@ -175,4 +143,51 @@ export class AxiosWrapper {
       }
     }
   }
+
+  static toCropty(axios) {
+    // #region 加密部分
+    // 加密,需要根据env文件判断是否启用加密
+    if (JSON.parse(VITE_USE_CRYPTO)) {
+      // 生成随机字符串
+      const reqCode = crypto.getRandomString()
+      axios.interceptors.request.use(async (config) => {
+        // 加密真实使用的加密字符串
+        const deReqCode = crypto.pemEncrypt(reqCode)
+        // 加密数据并替换
+        config.transformRequest = (data) => {
+          if (data) {
+            console.group('请求')
+            console.log(`加密串:${reqCode}`)
+            console.log(`原数据:`)
+            // 加密 加密字符串
+            const strData = JSON.stringify(data)
+            console.log(strData)
+            // 加密数据
+            const enCodeData = crypto.encrypt(reqCode, strData)
+            console.log(`加密后数据:`)
+            console.log(enCodeData)
+            // 替换数据位置
+            return JSON.stringify({ data: enCodeData })
+          }
+          return undefined
+        }
+        // 添加请求头,将 加密的真实使用加密字符串附上
+        config.headers['api-token'] = deReqCode
+        return config
+      })
+      axios.interceptors.response.use(
+        (response) => {
+          if (get(response, 'data.data')) {
+            let data = crypto.decrypt(reqCode, get(response, 'data.data'))
+            const dobj = JSON.parse(data || '{}')
+            const others = pick(get(response, 'data'), ['errcode', 'errmsg'])
+            response.data = { ...others, ...dobj }
+          }
+          return response
+        },
+        (error) => Promise.reject(error)
+      )
+    }
+    // #endregion
+  }
 }

+ 4 - 3
src/views/login/index.vue

@@ -52,15 +52,16 @@ const loginData = ref({
 const loginRules = computed(() => {})
 const toLogin = async (data) => {
   const res = await loginStore.login(data)
-  console.log(res)
   if (res.errcode == '0') {
     ElMessage({ message: `登录成功`, type: 'success' })
     localStorage.setItem('token', res.data)
     // 路由
     router.push({ path: '/' })
-  } else {
-    ElMessage({ message: `${res.errmsg}`, type: 'error' })
   }
+  // 如果登录有问题,就会被之前的中断
+  // else {
+  //   ElMessage({ message: `${res.errmsg}`, type: 'error' })
+  // }
   loading.value = false
 }
 /**

+ 34 - 0
src/views/route-loading/index.vue

@@ -0,0 +1,34 @@
+<template>
+  <div id="index" v-loading.fullscreen.lock="fullscreenLoading" :element-loading-text="$t('common.route_loading')"></div>
+</template>
+
+<script setup>
+import { getUserMeta, addUserRoutes } from '@/router/register'
+import { UserStore } from '@/store/user'
+import { onMounted } from 'vue'
+import { useRouter, useRoute } from 'vue-router'
+import { get } from 'lodash-es'
+const fullscreenLoading = ref(true)
+const router = useRouter()
+const route = useRoute()
+const userStore = UserStore()
+// 进了这个页面了.那就是重来注册一遍路由了
+const token = localStorage.getItem('token')
+onMounted(async () => {
+  // 1.获取用户菜单
+  const reqResult = await getUserMeta(token)
+  if (reqResult.errcode !== 0) {
+    // 处理异常
+    return
+  }
+  const menus = get(reqResult, 'menus')
+  // 注册路由
+  await addUserRoutes(menus, router)
+  userStore.setMenus(menus)
+  const rRoute = toRaw(route)
+  let redirectPath = get(rRoute, 'query.redirectPath')
+  if (!redirectPath) redirectPath = '/'
+  router.push(redirectPath)
+})
+</script>
+<style scoped></style>

+ 3 - 0
vite.config.js

@@ -21,6 +21,9 @@ export default defineConfig(({ mode }) => {
     build: {
       outDir: env.VITE_OUT_DIR
     },
+    esbuild: {
+      drop: ['console', 'debugger']
+    },
     server: {
       // 允许IP访问
       host: '0.0.0.0',