addRoutes для внешнего управления полномочиями динамически загружает маршруты и шаги в яме

vue-router
addRoutes для внешнего управления полномочиями динамически загружает маршруты и шаги в яме

Укажите источник(nuggets.capable/post/684490…)

В последние несколько дней я разрабатывал разрешения маршрутизации фоновой системы управления.Прежде чем начать это делать, я проверил много информации и обнаружил, что управление разрешениями для разделения внешнего и внутреннего интерфейса в основном является следующими двумя способами:

  1. После того, как серверная часть сгенерирует соответствующий маршрут для текущего пользователя, внешний интерфейс (используя API, предоставленный Vue Router)addRoutesДинамически загружать маршруты.
  2. Фронтенд записывает все маршруты, бэкенд возвращает роль текущего пользователя, а затем назначает маршруты ролей согласно тому, какие маршруты есть у каждой роли заранее.

Разница между двумя методами

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

  • Второй, по сравнению с первым, интерфейс относительно свободен, но если права доступа меняются, интерфейс и сервер нужно модифицировать вместе, а если некоторые (технические) пользователи изменяют свои права доступа на front-end, они могут пройти Маршрутизатор видит некоторые страницы, которые не разрешены к просмотру.Хотя данные не могут быть получены, некоторые страницы все равно не хотят, чтобы их видели посторонние люди.

Далее я в основном рассказываю о практике первого метода и некоторых ямах, на которые я наступил.

Формат данных, требуемый addRoutes

официальная документация:

router.addRoutes

подпись функции:

router.addRoutes(routes: Array<RouteConfig>)

Динамически добавляйте дополнительные правила маршрутизации. Аргумент должен быть массивом, соответствующим требованиям опции route.

Маршрутизация внешней инициализации

На мой взглядaddRoutesЭто можно понимать как добавление нового маршрута к существующему маршруту, поэтому вaddRoutesПрежде нам нужно инициализировать некоторые страницы маршрутизации, не требующие разрешений, такие как страница входа, домашняя страница, страница 404 и т. д. Этот процесс очень прост, просто добавьте статические маршруты в файл маршрутизации, и я не буду вдаваться в подробности. подробности здесь.

Следующим шагом является разработка внутренней таблицы маршрутизации и определение формата данных интерфейсных и внутренних взаимодействий.

Разработка внутренней таблицы маршрутизации

имя поля инструкция
*id id
*pid идентификатор родителя
*path путь маршрутизации
name название маршрута
*component путь компонента маршрута
redirect путь перенаправления
hidden скрывать ли
meta логотип

пояс*обязательное поле

Получение маршрутов, сгенерированных серверной частью, и их анализ

Через таблицу маршрутизации, разработанную выше, можно обнаружить, что маршруты между маршрутами проходят черезpidЧтобы определить верхний и нижний уровни, поэтому, когда мы получаем данные маршрутизации от бэкэнда, нам нужно разобрать их наaddRoutesФормат входного параметра.

После получения маршрута, сгенерированного бэкэндом, используются следующие функции для его парсинга в соответствующий формат:

parse_routes.js

import Router from '@/router'

/**
 * @desc: 解析原始路由信息(路由之间通过pid确定上下级)并动态添加路由及跳转页面
 * @param {Array} menus - (从后端获取的)菜单路由信息
 * @param {String} to - 解析成功后需要跳转的路由路径
 * @example
 * // 引入parse_routes
 * const menus = [ // 由后端传入
 *  { "id": 1, "pid": 0, "path": "/receipt", "name": "", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": "" },
 *  { "id": 2, "pid": 1, "path": "index", "name": "Receipt", "component": "receipt/index", "redirect": "","hidden": "false", "meta": "{\"title\": \"收款管理\", \"icon\": \"receipt\"}" },
 *  { "id": 3, "pid": 0, "path": "/payment", "name": "", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": "" },
 *  { "id": 4, "pid": 3, "path": "index", "name": "Payment", "component": "payment/index", "redirect": "", "hidden": "false", "meta": "{\"title\": \"付款管理\", \"icon\": \"payment\"}" },
 *  { "id": 5, "pid": 0, "path": "/crm", "name":"", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": "" },
 *  { "id": 6, "pid": 5, "path": "index","name": "Crm", "component": "crm/index", "redirect": "","hidden": "false", "meta": "{\"title\": \"客户管理\", \"icon\": \"people\"}" },
 *  { "id": 7, "pid": 0, "path": "/upload_product", "name":"", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": ""},
 *  { "id": 8, "pid": 7, "path": "index","name": "productUpload", "component": "productUpload/index", "redirect": "","hidden": "false", "meta": "{\"title\": \"测评商品上传\", \"icon\": \"upload\"}" }
 * ]
 * ParseRoutes(menus, '/payment/index')
 */
 
export default (menus, to = '/') => {
  // 初始路由
  const defRoutes = [
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/',
      component: () => import('@/views/layout/Layout'),
      redirect: '/dashboard',
      name: 'Dashboard',
      children: [
        {
          path: 'dashboard',
          meta: { title: '首页', icon: 'home' },
          component: () => import('@/views/dashboard/index')
        }
      ]
    },
    {
      path: '/404',
      name: '404',
      component: () => import('@/views/404'),
      hidden: true
    },
    {
      path: '*',
      redirect: '/404',
      hidden: true
    }
  ]

  // 初始化路由信息对象
  const menusMap = {}
  menus.map(v => {
    const { path, name, component, redirect, hidden, meta } = v
    // 重新构建路由对象
    const item = {
      path,
      name,
      component: () => import(`@/views/${component}`),
      redirect,
      hidden: JSON.parse(hidden)
    }
    meta.length !== 0 && (item.meta = JSON.parse(meta))
    // 判断是否为根节点
    if (v.pid === 0) {
      menusMap[v.id] = item
    } else {
      !menusMap[v.pid].children && (menusMap[v.pid].children = [])
      menusMap[v.pid].children.push(item)
    }
  })

  // 将生成数组树结构的菜单
  const routes = Object.values(menusMap)
  // 默认路由拼接生成的路由(注意顺序)
  const integralRoutes = defRoutes.concat(routes)

  Router.options.routes = integralRoutes
  Router.addRoutes(routes)
  Router.push({ path: to })
}

Рендеринг бокового меню

После успешного синтаксического анализа данных необходимо отобразить боковую панель. Здесь я имею в виду большого парня (PanJiaChen).element-ui-admin, вы можете обратиться к коду большого парня за подробностями, которые не будут повторяться здесь.

Если вы настаиваете на том, чтобы увидеть это, то поздравляю, вы можете пройтиaddRoutesМаршруты загружаются динамически.

Далее, давайте поговорим о том, что я используюaddRoutesНекоторые ямки, встречающиеся в процессе. (Сердце читателя ос: ммп, наконец-то добрались до сути~)

Ключ сложности 1: 404 после перехода на страницу

После того, как мы успешно добавим маршрут динамически, изменим адресную строку или обновим страницу, вы обнаружите, что страница переходит на 404.

В соответствии с нашей конфигурацией маршрутизации выше:

[
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/',
      component: () => import('@/views/layout/Layout'),
      redirect: '/dashboard',
      name: 'Dashboard',
      children: [
        {
          path: 'dashboard',
          meta: { title: '首页', icon: 'home' },
          component: () => import('@/views/dashboard/index')
        }
      ]
    },
    {
      path: '/404',
      name: '404',
      component: () => import('@/views/404'),
      hidden: true
    },
    {
      path: '*',
      redirect: '/404',
      hidden: true
    }
  ]

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

Есть много решений, мы упомянем здесь только одно.

решение

Это не инициализация маршрута 404 при инициализации маршрута, аОбъединение маршрутов при разборе полученных маршрутных данныхрешить проблему.

parse_routes.js

...
// 将生成数组树结构的菜单并拼接404路由
  const routes = Object.values(menusMap).concat(notFoundRoutes)

Ключевая сложность 2: Неверный маршрут обновления страницы

После решения проблемы 404 повторное обновление страницы обнаружит, что страница становится пустой, поскольку обновление экземпляра маршрутизатора страниц приведет к повторной инициализации в исходное состояние.

решение

Когда мы получаем внутренние данные, мы сохраняем их в vuex икеш браузера(Я использую sessionStorage). Обратите внимание, что полученные данные хранятся непосредственно здесь, потому что sessionStorage может хранить только строки, и нам нужно парсить определенные поля в процессе преобразования формата, такие какcomponent, hiddenЖдать.

actions.js

...
const menus = data.data.menus
// 将获取到的数据存入 sessionStorage 和 vuex 中
sessionStorage.setItem('_c_unparseRoutes', JSON.stringify(menus))
commit('GET_ROUTES', menus) // 解析函数
ParseRoutes(menus)

Затем функция ловушки в App.vuecreated()илиmounted()Определите, пусты ли данные в vuex и есть ли сохраненные данные в sessionStorage, и отслеживайте обновление страницы.

App.vue

...
created() {
  const unparseRoutes = JSON.parse(sessionStorage.getItem('_c_unparseRoutes'))
  if (this.localRoutes.length === 0 && unparseRoutes) {
    const toPath = sessionStorage.getItem('_c_lastPath')
    ParseRoutes(unparseRoutes, toPath) // 解析函数
  }
  // 监听页面刷新
  window.addEventListener('beforeunload', () => {
    sessionStorage.setItem('_c_lastPath', this.$router.currentRoute.path)
  })
}

Аналитические функции (полная версия)

import Router from '@/router'

/**
 * @desc: 解析原始路由信息(路由之间通过pid确定上下级)并动态添加路由及跳转页面
 * @param {Array} menus - (从后端获取的)菜单路由信息
 * @param {String} to - 解析成功后需要跳转的路由路径
 * @example
 * // 引入parse_routes
 * const menus = [ // 由后端传入
 *  { "id": 1, "pid": 0, "path": "/receipt", "name": "", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": "" },
 *  { "id": 2, "pid": 1, "path": "index", "name": "Receipt", "component": "receipt/index", "redirect": "","hidden": "false", "meta": "{\"title\": \"收款管理\", \"icon\": \"receipt\"}" },
 *  { "id": 3, "pid": 0, "path": "/payment", "name": "", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": "" },
 *  { "id": 4, "pid": 3, "path": "index", "name": "Payment", "component": "payment/index", "redirect": "", "hidden": "false", "meta": "{\"title\": \"付款管理\", \"icon\": \"payment\"}" },
 *  { "id": 5, "pid": 0, "path": "/crm", "name":"", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": "" },
 *  { "id": 6, "pid": 5, "path": "index","name": "Crm", "component": "crm/index", "redirect": "","hidden": "false", "meta": "{\"title\": \"客户管理\", \"icon\": \"people\"}" },
 *  { "id": 7, "pid": 0, "path": "/upload_product", "name":"", "component": "layout/Layout", "redirect": "", "hidden": "false", "meta": ""},
 *  { "id": 8, "pid": 7, "path": "index","name": "productUpload", "component": "productUpload/index", "redirect": "","hidden": "false", "meta": "{\"title\": \"测评商品上传\", \"icon\": \"upload\"}" }
 * ]
 * ParseRoutes(menus, '/payment/index')
 */
 
export default (menus, to = '/') => {
  // 初始路由
  const defRoutes = [
    {
      path: '/login',
      name: 'Login',
      component: () => import('@/views/login/index'),
      hidden: true
    },
    {
      path: '/',
      component: () => import('@/views/layout/Layout'),
      redirect: '/dashboard',
      name: 'Dashboard',
      children: [
        {
          path: 'dashboard',
          meta: { title: '首页', icon: 'home' },
          component: () => import('@/views/dashboard/index')
        }
      ]
    }
  ]
  // 404路由
  const notFoundRoutes = [
    { path: '/404', name: '404', component: () => import('@/views/404'), hidden: true },
    { path: '*', redirect: '/404', hidden: true }
  ]
  // 初始化路由信息对象
  const menusMap = {}
  menus.map(v => {
    const { path, name, component, redirect, hidden, meta } = v
    // 重新构建路由对象
    const item = {
      path,
      name,
      component: () => import(`@/views/${component}`),
      redirect,
      hidden: JSON.parse(hidden)
    }
    meta.length !== 0 && (item.meta = JSON.parse(meta))
    // 判断是否为根节点
    if (v.pid === 0) {
      menusMap[v.id] = item
    } else {
      !menusMap[v.pid].children && (menusMap[v.pid].children = [])
      menusMap[v.pid].children.push(item)
    }
  })

  // 将生成数组树结构的菜单并拼接404路由
  const routes = Object.values(menusMap).concat(notFoundRoutes)
  // 默认路由拼接生成的路由(注意顺序)
  const integralRoutes = defRoutes.concat(routes)

  Router.options.routes = integralRoutes
  Router.addRoutes(routes)
  Router.push({ path: to })
}

Написанное в конце, выше приведены некоторые из ям, с которыми я столкнулся за последние два дня, когда я использовал addRoutes для динамической загрузки маршрутов при написании управления разрешениями.

Впервые пишу такую ​​длинную статью, если что-то не так с содержанием, пожалуйста, посмотрите на Haihan и укажите на это! Если у вас есть лучшие предложения, пожалуйста, укажите их! !

Если у вас есть любимый старый утюг, не забудьте дважды щелкнуть, чтобы поставить лайк~ (шучу)