根据权限路由配置侧边栏菜单(vue3+ts)

一.配置权限路由
  1. 创建一个router文件夹,在router文件夹里创建一个index.ts文件,在这里主要放的是路由守卫的逻辑判断,代码如下:
    
    import { routes } from './router'
    import { createRouter, createWebHashHistory } from 'vue-router'
    
    const router = createRouter({
      routes,
      history: createWebHashHistory(),
    })
    
    router.beforeEach((to, from,next) => {
      let isToken = localStorage.getItem("token")
      let isDeptToken = sessionStorage.getItem("deptToken")
    
      /**有token或者在login页面下通行*/
      if ( (isToken && isDeptToken) || to.path === '/login' ) {
        next();
      } else {
        next('/login');
      }
    
      const title = to.meta.title as string
      sessionStorage.setItem('beforePath', from.fullPath)
      sessionStorage.setItem('authCode', JSON.stringify(to.meta.authCode))
      document.title = title ? `${title} - 值ERP` : '值ERP'
      return true
    }),
    
    router.afterEach((to) => {
      const route: any = { path: to.path, meta: to.meta, params: to.params, query: to.query }
      sessionStorage.setItem('route', JSON.stringify(route))
    })
    
    export { router }
    
    

    2.在router文件夹下面创建一个router.ts文件,此文件主要是用来配置公共路由和没有权限限制的路由。代码如下:

    import { RouteRecordRaw } from 'vue-router'
    
    /**配置路由*/ 
    const routes: Array<RouteRecordRaw>= [
      {
        path:'/',
        redirect:'/menu'
      },
      {
        path:'/login',
        name:'login',
        meta:{
          title:'登录'
        },
        component: () => import( '@/design/login/index.vue' )
      }, 
      {
        name: 'notFound',
        path: '/:pathMatch(.*)*',
        meta: { title: 'notFound' },
        component: () => import('@/design/error/notFound.vue'),
      },
    ]
    
    
    export {routes} 

    3.在router文件夹下面创建一个auth.ts,此文件主要配置侧边栏的路由,后面我们获取到的有权限的路由就会动态添加到这里的children里面。代码如下:

     export const authRouter : any = {
      path:'/',
      name:'layout',
      meta:{title:'系统设置'},
      component: () => import( '@/design/layout/index.vue' ),
      redirect:'/menu',
      children : []
    }

二、进行权限路由的匹配
  1.  我们需要创建一个api文件夹,在api文件夹下面创建一个login文件夹,在login文件夹下面创建一个index.ts文件。这里主要是进行权限路由的匹配。(这个文件创建在哪里可以自己决定,我这里  是因为多处用到且获取部门的时候获取的权限,所以选择放在这里,大家可以根据自己项目自己决定)。代码如下:

        注意:authList 对象必须是 权限的唯一标识 必须是唯一值

                   authList  是一个包含所有权限路由的对象

import { http } from '@/utils'
import { authRouter } from '@/router/auth'
import { router } from '@/router/index'

/**这里是一个包含所有权限路由的对象,用后端返回的路由的唯一标识做为键
   原理是用 路由的唯一标识来匹配当前角色 所拥有的权限 从而找到 当前角色应该看
   到的菜单内容。
 */
const authList = {
  "menu": {
    name: 'menu',
    path: '/menu',
    meta:{},
    component: () => import('@/views/menu/index.vue')
  },
  "dept": {
    name: 'dept',
    path: '/dept',
    meta:{},
    component: () => import('@/views/department/index.vue')
  },
  "approve": {
    name: 'approve',
    path: '/approve',
    meta:{},
    component: () => import('@/views/approve/index.vue')
  },
  "notice": {
    name: 'notice',
    path: '/notice',
    meta:{},
    component: () => import('@/views/notice/index.vue')
  },
  "dictionary": {
    name: 'dictionary',
    path: '/dictionary',
    meta:{},
    component: () => import('@/views/dictionary/index.vue')
  },
}

/**生成权限菜单列表 */
export const menuArr = (list) => {

  const bbb = list.filter((item) => {
    return item.type < 2
  })

  const initTree = (parent_id: number): Array<any> => {
    const result = bbb.filter(item => item.pId == parent_id)
    const data = result.map(item => ({
      title: item.title,
      icon: item.icon,
      key: item.id,
      sort:item.sort,
      path: item.assembly ? authList[item.assembly]?.path : null,
      children: initTree(item.id)
    }))

    data.sort((a, b) => {
      return a.sort - b.sort;
    });

    return data
  }
  // 首先调用initTree方法查找所有parent_id为-1的(-1认为是第一级)
  const tree = initTree(1)
  return tree
}  

/**生成权限路由列表 */
export const routeArr = (data) => {
  const initTree = (parent_id: number): Array<any> => {
    const result = data.filter(item => item.pId == parent_id)
    return result.map(item => ({
      ...item,
      children: initTree(item.id)
    }))
  }

  data.filter((item) => {
    return item.type == 1
  }).forEach((item) => {
    const aa = item.assembly ? authList[item.assembly] : null
    const tree = initTree(item.id)
    if(aa){
      aa.meta.title = item.title
      aa.meta.key = item.id
      aa.meta.parentKey = item.pId
      aa.meta.authCode = tree.map((item) => {
        return item.authCode
      })
      
    } 
    
  })

  return Object.values(authList)

}

/**获取权限原始列表 */
export async function getAuthMenu( dept_id : number ){
  const res = await http.post(`/User/login-dept/${dept_id}`)
  sessionStorage.setItem('deptToken',res.deptToken)
  const list = menuArr(res.menus)
  sessionStorage.setItem("menus",JSON.stringify(list))
  const routeList = routeArr(res.menus)
  authRouter.children = routeList
  router.addRoute(authRouter)

  return routeList 

}


 三、实现侧边栏菜单栏的渲染
  1. 在你的渲染侧边栏的组件里取出经过路由权限匹配生成的菜单,然后渲染到组件上。代码如下:

        我这里用的是antdesign,大家可以根据自己的实际情况做更改

<template>
  <div class="slider">
    <div class="flex py-6 pl-[60px]"><span class="text-white text-2xl">ERP</span></div>
    <a-menu class="menu" id="menu" mode="inline" theme="dark" 
      @click="handleClick" 
      v-model:openKeys="state.openKeys"
      v-model:selectedKeys="state.selectedKeys"
      >
      <template v-for="v in authList">
        <template v-if="!v.children || v.children.length == 0">
          <a-menu-item :key="v.key" :path="v.path" >
            <span>{{ v.title }}</span>
          </a-menu-item>
        </template>
        <template v-else >
          <a-sub-menu :key="v.key" :title="v.title">
            <a-menu-item v-for="chi in v.children" class="menu-item" :key="chi.key" :path="chi.path" >
              <span>{{ chi.title }}</span>
            </a-menu-item>
          </a-sub-menu>
        </template>
      </template>
    </a-menu>
  </div>
</template>
    
<script lang="ts" setup>
  import { ref } from 'vue'
  import { app } from '@/utils'

  /**我把处理过的菜单 存到了本地 所以我从本地取出 */
  const authList = JSON.parse(sessionStorage.getItem('menus') || '')
  const state = ref({
    collapsed: false,
    selectedKeys: [app.route.meta.key],
    openKeys: [app.route.meta.parentKey],
  });

  /**切换菜单 */
  const handleClick = ({ item }) => {
    state.value.selectedKeys = [item.key]
    app.router.push(item.path);
  };

</script>
    
<style scoped lang='scss'>
  .slider{
    height: 100vh;
  }
  :deep(.ant-menu-dark .ant-menu-item-selected){
    background-color: rgba(235,241,255,0.5);
  }
  :deep(.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item:not(.ant-menu-item-selected):hover){
    background-color: rgba(235,241,255,0.2);
  }
</style>
四、具体使用(获取后端不同角色返回的不同权限)

这里我是在登录之后,选择部门的时候。会有不同的权限数据(此时的数据是扁平化的,如果此时的数据已经是处理过的完美的json数据,那么 在api文件里 就不需要对数据进行处理了,可以直接进行匹配。)。那么,我在这里需要根据不同的部门id,调用api文件里的getAuthMenu方法进行初始化权限菜单(getAuthMenu方法的具体逻辑请看标题二)。我的login.vue文件里,代码如下:

<template>
  <div class="login flex flex-col">
    <div class="m-6 text-2xl font-normal" style="color: #333333;">
      ERP管理系统
    </div>
    <div class="flex items-center h-full justify-center ">
      <div class=" w-[40%] h-[78%]">
        <img src="@/assets/images/login/picture_icon.png" alt="" class="w-full h-full">
      </div>

      <div class="w-[25%] h-[60%] ml-32 bg-white shadow-lg rounded-xl relative">

        <div class="flex flex-col items-center justify-around h-full" v-if="!store.loginInfo.token" >
          <div class="flex flex-col items-center justify-center">
            <div class="text-lg font-medium">钉钉扫码登录</div>
            <div class="w-[72px] h-[2px] bg-[#306FFF] my-2"></div>
          </div>
          <div class="">
            <div id="self_defined_element" class="self-defined-classname absolute z-10 ml-4 mt-3 flex justify-center items-center">
              <a-spin :spinning="true"></a-spin>
            </div>
            <img src="@/assets/images/login/fillet_corne_icon.png" alt="" class="w-[330px] h-[330px] relative">
          </div>
          <div class="text-[#cccccc]">
            钉钉扫码进入
          </div>
        </div>

        <div class="flex flex-col items-center mt-4" v-if="store.loginInfo.token">
          <div class="flex flex-col items-center">
            <div class="text-lg font-medium">请选择你的部门</div>
            <div class="w-[72px] h-[2px] bg-[#306FFF] my-2"></div>
          </div>
          <div class="btn flex flex-col">
            <a-card 
              :class="['w-[320px] h-[60px] mt-8 relative', item.disable?'cursor-not-allowed':'cursor-pointer hover:border-[#306FFF]']"
              v-for="item in departmentList"
              @click="Enter(item)"
              >
              <span :class="['text-base absolute top-4', item.disable?'text-[#cccccc] ':'']">
                {{ item.title }}
              </span>
              <img src="@/assets/images/login/entrance_icon.png" class="w-[20px] h-[20px] absolute right-4 top-5">
            </a-card>

          </div>
          <div class="absolute bottom-6 text-base">
            <a-button type="link" @click="reLogin">重新扫码登录</a-button>
          </div>
        </div>
      
      </div>
    </div>
    
  </div>
</template>
    
<script lang="ts" setup>
  import { ref, onMounted } from 'vue'
  import { app, http } from "@/utils"
  import { getDept } from './index'
  import { useLogin } from '@/store/index'
  import { message } from 'ant-design-vue'
  import { getAuthMenu } from '@/api'
  import { router } from '@/router/index'
  import { authRouter } from '@/router/auth'

  const store = useLogin()
  const departmentList = ref()
  const spinning = ref<boolean>(true);

  /**选择部门进入 */
  const Enter = ( department : any ) => {
    sessionStorage.setItem('deptId',JSON.stringify(department.id))
    if( department.disable ){
      message.warning('抱歉,您无法进入该部门');
      return ;
    }
    getAuthMenu( department.id ).then( data => {
      authRouter.children = data
      router.addRoute(authRouter)
      app.router.push(`${data[0].path}`)

    })

  }

  /**重新登录 */ 
  const reLogin = () => {
    localStorage.removeItem('token')
    app.router.go(0)
  }

  /**获取钉钉第三方登录的二维码 */
  const scanCode = () => {
    console.log(window.location.origin);
    
    // @ts-ignore
    window.DTFrameLogin(
      {
        id: 'self_defined_element',
        width: 300,
        height: 300,
      },
      {
        scope: 'openid',
        prompt: 'consent',
        response_type: 'code',
        client_id: 'dingdr49nwgpgfsuibds',
        redirect_uri: encodeURIComponent(window.location.origin),
      }, loginResult => {
        const {redirectUrl, authCode, state} = loginResult
        http.post("/User/scan-code-login" , { authCode })
        .then( (res ) => {
          localStorage.setItem("token" , res.token )
          localStorage.setItem("userInfo" , JSON.stringify(res))
          store.addInfo(res.token)
          getDept()
        })
      }, errorMsg => {
        // 这里一般需要展示登录失败的具体原因
        console.log(`Login Error: ${errorMsg}`)
      }
    )

    spinning.value = false;
  }

  /**第三方钉钉扫码登录 */
  onMounted( () => {
    scanCode()
  })

  if( localStorage.getItem('token') ){
    getDept()
  }

  /**订阅状态发生变化 */
  store.$subscribe((mutation, state) => {
    departmentList.value = store.loginInfo.deptList
  })
  
</script>
    
<style scoped lang='scss'>
  .login{
    width: 100vw;
    height: 100vh;
    background-image: url('@/assets/images/login/Background image_icon.png');
    background-size: 100vw 100vh;
  }

  .self-defined-classname {
    width: 300px;
    height: 300px;
  }

</style>
五、如何解决刷新权限路由丢失的问题
  1. 可以在项目初始化的进行权限路由的匹配添加
  2. 具体实现,在main.ts里面再此调用api文件的getAuthMenu进行权限路由的添加。
  3. main.ts文件的代码如下:
    import './style.css'
    import dayjs from 'dayjs'
    import 'dayjs/locale/zh-cn'
    import App from './App.vue'
    import { createApp } from 'vue'
    import { router } from './router'
    import antdv from 'ant-design-vue'
    import { createPinia } from 'pinia'
    import { auth } from './directive/auth'
    import { authRouter } from '@/router/auth'
    import { getAuthMenu } from '@/api'
    
    const app = createApp(App)
    const pinia = createPinia()
    
    app.use(pinia)
    app.use(antdv)
    app.directive('auth', auth)
    
    dayjs.locale('zh-cn')
    
    /** 因为是异步的 所以必须放在bootstrap里面*/
    async function bootstrap(){
      if(sessionStorage.getItem('deptId')){
        const dept_id = JSON.parse(sessionStorage.getItem('deptId') || '' )
        const isToken = localStorage.getItem("token")
        const isDeptToken = sessionStorage.getItem("deptToken")
        if ( (isToken && isDeptToken) ) {
          const data = await getAuthMenu( dept_id )
          authRouter.children = data
          router.addRoute(authRouter) 
    
        }
      }
      
    
      app.use(router)
      app.mount('#app')
    }
    
    bootstrap()
    

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值