Проверка прав входа в систему vue-element-admin и логика реализации динамической маршрутизации

Vue.js

предисловие

Чувствоватьпроект с открытым исходным кодом vue-element-adminЛогика повторного использования проверки разрешений на вход и динамической маршрутизации по-прежнему очень сильна и может использоваться непосредственно в ваших собственных проектах. Чтобы углубить память, напишите статью, чтобы записать конспекты исследования.

Статья в основном записывает логику в качестве мастера, затем сотрудничать с некоторым ключевым кодом, полный код может загрузить исходный код проекта напрямую, или я лично организуюЛайт(удалите другие коды, сохраните только функцию входа в систему)

Реализация динамической маршрутизации

логика

  1. Настройте два массива маршрутов:
    • один публичный,无需权限都可以加载, такие как домашняя страница, страница входа, страница 404 и т. д.;
    • Один динамичный,配置角色权限, чтобы динамически выбирать, отображать ли;
  2. После нажатия кнопки «Войти» будет возвращена информация о разрешениях пользователя в сочетании с разрешениями ролей массива динамической маршрутизации, а маршруты, к которым может получить доступ пользователь, будут отфильтрованы.
  3. Наконец, через vueaddRoutesМетод может динамически добавлять отфильтрованный массив к фактическому объекту маршрутизации.

Шаг 1. Настройте два массива маршрутов

Каталог: src/router/index.js

// 该 js 实现了逻辑的第一步,配置两个路由

// 公共的路由
export const constantRoutes = [
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true  // 因为无需在侧边栏显示,可以用这个字段来控制隐藏 
  },
  {
    path: '/',
    component: Layout,  // 这是一个框架组件,顶部和侧边栏会固定加载
    redirect: '/dashboard',  // 重定向的就是中间的内容部分
    children: [
      {
        path: 'dashboard', 
        component: () => import('@/views/dashboard/index'),
        name: 'Dashboard',
        meta: { title: 'Dashboard', icon: 'dashboard'}
      }
    ]
  }
]

// 动态的路由 (最后通过 addRoutes 添加进去)
export const asyncRoutes = [
  {
    path: '/user',
    component: Layout, 
    redirect: '/user/create', 
    children: [
      {
        path: 'create',
        name: 'userCreate',
        component: () => import('@/views/create/create'),
        meta: { roles: ['admin'] } // 只有管理员才能访问
      },
      {
        path: 'list',
        name: 'userList',
        component: () => import('@/views/user/list'),
       meta: { roles: ['admin','editor'] } // 管理员和编辑可以访问
      },
    ]
  }
]

// 先把公共路由添加进路由实例,动态的路由手动添加
const createRouter = () => new Router({
  // mode: 'history', // 是否使用 history 模式
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

Второй и третий этапы: реализация на маршрутизаторе

После нажатия входа вы войдете до того, как страница перейдет к路由守卫.

  • nprogress
  • Первый вход фактически发送两次请求, первый раз — получить токен при нажатии на вход, а второй раз — в роутгарде获取用户拥有的权限;
  • После получения текущих разрешений пользователя он будет фильтровать настроенный нами массив динамической маршрутизации, а затем использоватьaddRoutesдобавлять динамически;
  • Ситуация перенаправления маршрута: когда нет токена и вы хотите перейти на другие маршруты, такие как «список пользователей», вы можете сначала назначить маршрут «список пользователей» параметру Реализовать переходы перенаправления.

Каталог: src/promission.js (будет представлен в main.js)

// 省略 import 引入的代码哦!

NProgress.configure({ showSpinner: false }) // 进度条设置,把转圈圈关掉

const whiteList = ['/login'] // 设置白名单

// 路由守卫
router.beforeEach(async (to, from, next) => {
  // 进度条开始
  NProgress.start()

  // 设置标题
  document.title = getPageTitle(to.meta.title)

  // 第一次登录后会把 token 存在 cookie 中,此处就是通过 cookie 拿 token
  const hasToken = getToken()

  if (hasToken) {
 /* 有 token,证明已成功登录 */
 
    if (to.path === '/login') {
      next({ path: '/' })    // 如果要去登录页,会自动跳转到首页,然后会再次进入这个路由守卫
      NProgress.done()
    } else {
    
      // 在 vuex 中获取用户权限,因为第一次登录时会把请求回来的用户权限存在 vuex 中
      const hasRoles = store.getters.roles && store.getters.roles.length > 0 
      
      if (hasRoles) {
        next()  // 如果有 token 并且有权限,直接跳转
      } else {
        try {
          // 有 token 无权限,这就是用户第一次登录的情况,需要发送请求获取
          const { roles } = await store.dispatch('user/getInfo')

          // 筛选动态路由数组
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

          // 动态添加筛选后的路由数组
          router.addRoutes(accessRoutes)

         // replace: true 表示不会记录到浏览器 history
          next({ ...to, replace: true })
          
        } catch (error) {
          // 获取用户权限报错会要求重新登录,并且删掉 token
          await store.dispatch('user/resetToken')
          
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)  // 重定向
          NProgress.done()
        }
      }
    }
  } else {
    /*没有 token,证明 token 过期了或者未登录,*/

    if (whiteList.indexOf(to.path) !== -1) {
      next()  // 如果是白名单,直接跳转
    } else {
      // 要去其他路由,先把路由地址赋值给跳转参数,登陆成功后拿出来跳转。
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // 结束进度条
  NProgress.done()
})

Перенаправление страницы входа

Как было сказано выше, может быть ситуация, что срок действия токена истек или вы не вошли в систему, но переходите на внутреннюю страницу по другим ссылкам, вам необходимо авторизоваться, прежде чем вы сможете перейти;
В этом случае он будет переходить на адрес маршрутизации в качестве параметра на страницу входа в систему, логин первым определяет, что нет параметра Redirect.

Каталог: src/views/login/index.vue

methods:{

   // 点击登录调用的方法
   handleLogin() {
   // 字段校验(element-ui的东西)
    this.$refs.loginForm.validate(valid => {   
      if (valid) {
        this.loading = true  // 登录时按钮里面会有个圈在转
        
        this.$store.dispatch('user/login', this.loginForm)  // 发送登录请求获取 token
          .then(() => {
            // 重定向,先判断有没有redirect参数
            this.$router.push({ path: this.redirect || '/', query: this.otherQuery })
            this.loading = false
          })
          .catch(() => {
            this.loading = false
          })
      } else {
        console.log('error submit!!')
        return false
      }
    })
  }
}

На данный момент реализована вся динамическая маршрутизация и проверка разрешений на вход, а что касается того, как фильтровать и объединять массивы маршрутизации в vuex, то логика кода отправки запросов здесь обсуждаться не будет.

На самом деле проверка разрешений и добавление динамической маршрутизации были реализованы и раньше, а потом уже с рендерингом представления,把我们筛选合并后的路由数组渲染到菜单上Готово.

рендеринг меню

Рендеринг меню реализован с использованием компонента боковой панели element-ui, но детали все еще довольно сложны. Также обратитесь к документации курса на MOOC.com.История управления "Сяому Чтение"

Каталог: src\layout\components\Sidebar\SidebarItem.vue

// 该组件就是负责渲染侧边栏的每一个目录

<template>
  <div v-if="!item.hidden">

    <!-- template 部分渲染没有子目录的目录 -->
    <!-- hasOneShowingChild:判断是否只有一个需要显示的子路由 -->
    <!-- !onlyOneChild.children||onlyOneChild.noShowingChildren:判断需要展示的子菜单是否包含 children 属性,
    如果包含,则说明子菜单可能存在孙子菜单,此时则需要再判断 noShowingChildren 属性 -->
    <!-- !item.alwaysShow:判断路由中是否存在 alwaysShow 属性,只要配置了 alwaysShow 属性就会直接进入下面的那部分-->

    <template v-if="hasOneShowingChild(item.children,item) && (!onlyOneChild.children||onlyOneChild.noShowingChildren)&&!item.alwaysShow">
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item :index="resolvePath(onlyOneChild.path)" :class="{'submenu-title-noDropdown':!isNest}">
          <item :icon="onlyOneChild.meta.icon||(item.meta&&item.meta.icon)" :title="onlyOneChild.meta.title" />
        </el-menu-item>
      </app-link>
    </template>

    <!-- el-submenu 部分是渲染含有子目录的目录 -->
    <el-submenu v-else ref="subMenu" :index="resolvePath(item.path)" popper-append-to-body>
      <template slot="title">
        <item v-if="item.meta" :icon="item.meta && item.meta.icon" :title="item.meta.title" />
      </template>
      <!-- 子目录是嵌套本组件实现,所以可以理解为整个组件只会渲染一个目录,子目录是递归整个组件产生 -->
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu>
  </div>
</template>

<script>
import path from 'path'
import { isExternal } from '@/utils/validate'
import Item from './Item'
import AppLink from './Link'
import FixiOSBug from './FixiOSBug'

export default {
  name: 'SidebarItem',
  components: { Item, AppLink },
  mixins: [FixiOSBug],
  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ''
    }
  },
  data() {
    this.onlyOneChild = null
    return {}
  },
  methods: {
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter(item => {
        // 如果 children 中的路由包含 hidden 属性,则返回 false
        if (item.hidden) {
          return false
        } else {
          // 将子路由赋值给 onlyOneChild,用于只包含一个路由时展示
          this.onlyOneChild = item
          return true
        }
      })

      // 如果过滤后,只包含展示一个路由,则返回 true
      if (showingChildren.length === 1) {
        return true
      }

      // 如果没有子路由需要展示,则将 onlyOneChild 的 path 设置空路由,并添加 noShowingChildren 属性,表示虽然有子路由,但是不需要展示子路由
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ... parent, path: '', noShowingChildren: true }
        return true
      }

      // 返回 false,表示不需要展示子路由,或者超过一个需要展示的子路由
      return false
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath
      }
      if (isExternal(this.basePath)) {
        return this.basePath
      }
      return path.resolve(this.basePath, routePath)
    }
  }
}
</script>

Суммировать

Поместите документ курса MOOCИстория управления "Сяому Чтение"Внутри заканчивая изображение, хотя я думаю, что также посмотрите на код простой точки ... но после прочтения кода, чтобы посмотреть на этот рисунок, должно быть гораздо более глубокое понимание.

Логика кода защитной маршрутизации FIG:

Ссылаться на

Опыт управления "Xiaomu Reading"