快速解决Vue 2路由典型问题:编程式路由跳转报错、新页面不自动滚动等常见疑难杂症

定义静态路由 

// 定义静态路由
const routes = [
  {
    path: '/',
    isHidden: true, // 不在导航列表中显示
    redirect: '/layout'
  },
  {
    path: '/login',
    isHidden: true, // 不在导航列表中显示
    // 使用 路由✨懒加载
    component: () => import('@/views/login/comLogin.vue')
  },
  {
    path: '/register',
    isHidden: true, // 不在导航列表中显示
    component: () => import('@/views/register/comRegister.vue')
  },
  {
    path: '/layout',
    component: layout,
    redirect: '/layout/home', // 当路由未匹配时重定向,可作初始化显示页面设置
    children: [
      {
        path: 'home',
        meta: {
          title: '首页',
          icon: ''
        },
        component: () => import('@/components/home.vue')
      }
    ]
  },
  {
    path: '/layout',
    leaf: true, // 有二级路由
    component: layout,
    name: '/layout/zi',
    meta: {
      title: '个人中心',
      icon: ''
    },
    children: [
      {
        path: 'user-info',
        meta: {
          title: '基本资料',
          icon: ''
        },
        component: () => import('@/components/person-center/userInfo.vue')
      },
      {
        path: 'user-avatar',
        meta: {
          title: '更换头像',
          icon: ''
        },
        component: () => import('@/components/person-center/changePic.vue')
      },
      {
        path: 'user-pwd',
        meta: {
          title: '重置密码',
          icon: ''
        },
        component: () => import('@/components/person-center/resetPassword.vue')
      }
    ]
  }

]

服务器获取的路由数据格式

  {
        path: '/layout',
        leaf: true, // 有二级路由
        component: 'layout',
        name: 'articleManagement',
        meta: {
          title: '文章管理',
          icon: ''
        },
        children: [
          {
            path: 'art-cate',
            meta: {
              title: '文章分类',
              icon: ''
            },
            component: '/article-manage/articleCase'
          },
          {
            path: 'art-list',
            meta: {
              title: '文章列表',
              icon: ''
            },
            component: '/article-manage/articleList'
          }
        ]
      }

定义处理组件路径的函数

function filterAsyncRouter() {
  // 用深拷贝处理一下赋值操作, JSON.parse(JSON.stringify 这一步不写也可以
  const router = JSON.parse(JSON.stringify($store.state.router)) // vuex 的路由信息
  router.forEach(item => {
    // 处理布局数组件的路径
    item.component = () => import('@/views/layout/comLayout.vue')
    if (item.children) {
      item.children.forEach(it => {
        const path = it.component
        // webpack4的问题,不能直接使用字符变量作为引用,而且 it.component 不能直接写在路径里面
        // 我也不知道原因直接写会找不到模块,必须用一个变量先接收,可能有其他方法我还不清楚
        it.component = (resolve) => require([`@/components${path}.vue`], resolve)
      })
    }
  })
  return router
}

定义动态添加路由的函数

// 定义404页面 对于用户自己手动的用输入链接访问路由表中不存在的路由组件,那么就直接提示无权访问.
const touter_404 = {
  path: '*',
  name: '404',
  isHidden: true, // 不在导航列表中显示
  component: () => import('@/views/error-page/404.vue')
}

/**
 * 添加路由
 * @param {Object} to 要跳转的path
 * @param {function} next 下一步
 *
 */
function routerGo(to, next) {
  const getRouter = filterAsyncRouter() // 过滤路由转换路由的 component
  // 把404页面放到路由表的最后,避免动态路由刷新404
  getRouter.push(touter_404)
  // router.options.routes 静态路由
  const routerArr = router.options.routes // 获取全部路由(静态加动态)
  // 循环添加路由
  getRouter.forEach(item => {
    router.addRoute(item)
    routerArr.push(item)
  })
  hasRoles = false // 添加完路由赋值为 false 下次不会再添加路由,页面刷新时又会变成true
  // 把路由数据添加到Vuex 生成菜单
  $store.commit('updateRouterArr', routerArr)
  next({ ...to, replace: true })
}

在导航守卫内动态添加路由

// 记录路由 页面刷新之后这里会冲新定义,解决刷新页面动态路由丢失的问题
let hasRoles = true

// 发生页面跳转时触发
router.beforeEach(async (to, from, next) => {
  const token = $store.state.token
  // 权限访问控制
  if (token) {
    // 如果有token, 证明已登录
    if (!$store.state.userInfo.username) {
      // 有token但是没有用户信息, 才去请求用户信息保存到vuex里
      // 调用actions里方法请求数据
      $store.dispatch('initUserInfo') // 下次切换页面vuex里有用户信息数据就不会重复请求用户信息
      await $store.dispatch('initRouter') // 获取路由信息
    }
    if (hasRoles) { // 添加动态路由
      routerGo(to, next)
    } else { // 没有动态路由的情况
      next()
    }
  } else {
    // 如果无token
    // 【因为登录、注册页面也没有token,根据全局前置守卫的定义不做白名单处理,会一直处于跳转——触发——跳转循环中】
    // 如果去的是白名单✨页面, 则放行
    // includes 可以判断一个数组中是否包含某一个元素,并返回true 或者false
    if (whiteList.includes(to.path)) {
      next()
    } else {
      // 如果其他页面请强制拦截并跳转到登录页面
      next('/login')
    }
  }
})

完整代码:

import Vue from 'vue'
import VueRouter from 'vue-router'

import $store from '@/store/index'

import layout from '@/views/layout/comLayout.vue' // 页面布局组件

Vue.use(VueRouter)

// 解决编程式路由往同一地址跳转时会报错的情况🍗
const originalPush = VueRouter.prototype.push
const originalReplace = VueRouter.prototype.replace

// push
VueRouter.prototype.push = function push(location, onResolve, onReject) {
  // if (onResolve || onReject) {
  //   return originalPush.call(this, location, onResolve, onReject)
  // }
  return originalPush.call(this, location).catch((err) => err)
}

// replace
VueRouter.prototype.replace = function push(location, onResolve, onReject) {
  // if (onResolve || onReject) {
  //   return originalReplace.call(this, location, onResolve, onReject)
  // }
  return originalReplace.call(this, location).catch((err) => err)
}

// 定义静态路由
const routes = [
  {
    path: '/',
    isHidden: true, // 不在导航列表中显示
    redirect: '/layout'
  },
  {
    path: '/login',
    isHidden: true, // 不在导航列表中显示
    // 使用 路由✨懒加载
    component: () => import('@/views/login/comLogin.vue')
  },
  {
    path: '/register',
    isHidden: true, // 不在导航列表中显示
    component: () => import('@/views/register/comRegister.vue')
  },
  {
    path: '/layout',
    component: layout,
    redirect: '/layout/home', // 当路由未匹配时重定向,可作初始化显示页面设置
    children: [
      {
        path: 'home',
        meta: {
          title: '首页',
          icon: ''
        },
        component: () => import('@/components/home.vue')
      }
    ]
  },
  {
    path: '/layout',
    leaf: true, // 有二级路由
    component: layout,
    name: '/layout/zi',
    meta: {
      title: '个人中心',
      icon: ''
    },
    children: [
      {
        path: 'user-info',
        meta: {
          title: '基本资料',
          icon: ''
        },
        component: () => import('@/components/person-center/userInfo.vue')
      },
      {
        path: 'user-avatar',
        meta: {
          title: '更换头像',
          icon: ''
        },
        component: () => import('@/components/person-center/changePic.vue')
      },
      {
        path: 'user-pwd',
        meta: {
          title: '重置密码',
          icon: ''
        },
        component: () => import('@/components/person-center/resetPassword.vue')
      }
    ]
  }
]

const router = new VueRouter({
  // 解决跳页面跳转后 新页面没有位于顶部的问题
  scrollBehavior: () => ({ y: 0 }),
  routes
})

// 定义白名单
const whiteList = ['/login', '/register']

// 定义404页面 对于用户自己手动的用输入链接访问路由表中不存在的路由组件,那么就直接提示无权访问.
const touter_404 = {
  path: '*',
  name: '404',
  isHidden: true, // 不在导航列表中显示
  component: () => import('@/views/error-page/404.vue')
}

// 记录路由 页面刷新之后这里会冲新定义,解决刷新页面动态路由丢失的问题
let hasRoles = true

// 发生页面跳转时触发
router.beforeEach(async (to, from, next) => {
  const token = $store.state.token
  // 权限访问控制
  if (token) {
    // 如果有token, 证明已登录
    if (!$store.state.userInfo.username) {
      // 有token但是没有用户信息, 才去请求用户信息保存到vuex里
      // 调用actions里方法请求数据
      $store.dispatch('initUserInfo') // 下次切换页面vuex里有用户信息数据就不会重复请求用户信息
      await $store.dispatch('initRouter') // 获取路由信息
    }
    if (hasRoles) { // 添加动态路由
      routerGo(to, next)
    } else { // 没有动态路由的情况
      next()
    }
  } else {
    // 如果无token
    // 【因为登录、注册页面也没有token,根据全局前置守卫的定义不做白名单处理,会一直处于跳转——触发——跳转循环中】
    // 如果去的是白名单✨页面, 则放行
    // includes 可以判断一个数组中是否包含某一个元素,并返回true 或者false
    if (whiteList.includes(to.path)) {
      next()
    } else {
      // 如果其他页面请强制拦截并跳转到登录页面
      next('/login')
    }
  }
})

/**
 * 添加路由
 * @param {Object} to 要跳转的path
 * @param {function} next 下一步
 *
 */
function routerGo(to, next) {
  const getRouter = filterAsyncRouter() // 过滤路由转换路由的 component
  // 把404页面放到路由表的最后,避免动态路由刷新404
  getRouter.push(touter_404)
  // router.options.routes 静态路由
  const routerArr = router.options.routes // 获取全部路由(静态加动态)
  // 循环添加路由
  getRouter.forEach(item => {
    router.addRoute(item)
    routerArr.push(item)
  })
  hasRoles = false // 添加完路由赋值为 false 下次不会再添加路由,页面刷新时又会变成true
  // 把路由数据添加到Vuex 生成菜单
  $store.commit('updateRouterArr', routerArr)
  next({ ...to, replace: true })
}

function filterAsyncRouter() {
  // 用深拷贝处理一下赋值操作
  const router = JSON.parse(JSON.stringify($store.state.router)) // vuex 的路由信息
  router.forEach(item => {
    // 处理布局数组件的路径
    item.component = () => import('@/views/layout/comLayout.vue')
    if (item.children) {
      item.children.forEach(it => {
        const path = it.component
        // webpack4的问题,不能直接使用字符变量作为引用,而且 it.component 不能直接写在路径里面
        // 我也不知道原因直接写会找不到模块,必须用一个变量先接收,可能有其他方法我还不清楚
        it.component = (resolve) => require([`@/components${path}.vue`], resolve)
      })
    }
  })
  return router
}

export default router

侧边导航栏的数据遍历,这里的 html结构是使用 element-ui 的

 <!-- 侧边栏区域 -->
        <el-aside width="200px">
          <div class="user-box">
            <img v-if="user_pic" :src="baseURL + user_pic" alt="" />
            <img v-else src="../../assets/images/logo.png" alt="" />
            <span>欢迎 {{ nickname || username }}</span>
          </div>
          <!-- 侧边栏导航部分 -->
          <el-menu
            :default-active="$route.path"
            class="el-menu-vertical-demo"
            @open="handleOpen"
            @close="handleClose"
            background-color="#23262E"
            text-color="#fff"
            active-text-color="#409EFF"
            unique-opened
            router
          >
            <!-- 外部嵌套template标签 -->
            <!-- ① 进行数据的一次循环 -->
            <template
              v-for="item in newRouterArr || this.$router.options.routes"
            >
              <template v-if="!item.isHidden">
                <!-- 一级菜单 -->
                <el-menu-item
                  v-if="
                    !item.leaf && !item.leafThree && item.children.length === 1
                  "
                  :index="'/layout/' + item.children[0].path"
                  :key="'/layout/' + item.children[0].path"
                >
                  <i :class="item.children[0].meta.icon"></i>
                  <span slot="title">{{ item.children[0].meta.title }}</span>
                </el-menu-item>

                <!-- 二级菜单 -->
                <el-submenu
                  v-else-if="item.leaf"
                  :index="'/layout/' + item.meta.title"
                  :key="'/layout/' + item.meta.title"
                >
                  <template slot="title">
                    <i :class="item.meta.icon"></i>
                    <span>{{ item.meta.title }}</span>
                  </template>
                  <!-- ② 进行数据的二次循环 -->
                  <el-menu-item
                    v-for="subItem in item.children"
                    :key="'/layout/' + subItem.path"
                    :index="'/layout/' + subItem.path"
                  >
                    <i :class="subItem.meta.icon"></i>{{ subItem.meta.title }}
                  </el-menu-item>
                </el-submenu>
                <!-- 三级菜单 -->
                <el-submenu
                  :key="'/layout/' + item.indexPath"
                  :index="index + ''"
                  v-else-if="item.leafThree"
                >
                  <template slot="title">
                    <div class="nav-first">
                      <i :class="item.meta.icon"></i>
                      <span>{{ item.meta.title }}</span>
                    </div>
                  </template>
                  <el-submenu
                    v-for="child in item.children"
                    :index="child.path"
                    :key="child.path"
                  >
                    <template v-if="child.leaf">
                      <span slot="title"
                        ><span class="dot"></span>{{ child.meta.title }}</span
                      >
                      <el-menu-item
                        v-for="childThree in child.children"
                        :index="childThree.path"
                        :key="childThree.path"
                      >
                        <span>{{ childThree.meta.title }}</span>
                      </el-menu-item>
                    </template>
                  </el-submenu>
                  <el-menu-item
                    v-for="child in item.children"
                    :index="child.path"
                    :key="child.path"
                  >
                    <template v-if="!child.isHidden && !child.leaf">
                      <span slot="title"
                        ><span class="dot"></span>{{ child.meta.title }}</span
                      >
                    </template>
                  </el-menu-item>
                </el-submenu>
              </template>
            </template>
          </el-menu>
        </el-aside>

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Vue二级联动下拉菜单的代码可以分为两个部分:数据部分和视图部分。以下是一个简单的示例代码: ``` <template> <div> <select v-model="selectedFirst" @change="onChangeFirst"> <option value="">请选择一级分类</option> <option v-for="(item, index) in firstList" :key="index" :value="item">{{ item.name }}</option> </select> <select v-model="selectedSecond" @change="onChangeSecond"> <option value="">请选择二级分类</option> <option v-for="(item, index) in secondList" :key="index" :value="item">{{ item.name }}</option> </select> </div> </template> <script> export default { data() { return { firstList: [ { id: 1, name: '分类1' }, { id: 2, name: '分类2' }, { id: 3, name: '分类3' }, ], secondList: [], selectedFirst: '', selectedSecond: '', }; }, methods: { onChangeFirst() { if (this.selectedFirst) { this.secondList = this.selectedFirst.children; this.selectedSecond = ''; } else { this.secondList = []; this.selectedSecond = ''; } }, onChangeSecond() { // do something }, }, }; </script> ``` 在这个示例,我们定义了两个下拉菜单,一个用于选择一级分类,另一个用于选择二级分类。当用户选择一级分类时,我们会根据这个分类的子分类列表更新二级下拉菜单的选项。当用户选择二级分类时,我们会执行相应的逻辑。 以上仅为示例代码,具体实现会因应用场景和需求而有所不同。如果您还有其他问题或需要更详细的说明,请告诉我。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

狗蛋的博客之旅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值