vue-element-adminV4.0版本遇到的一些问题及解决方案

V4.0与之前版本的差异

在vue-element-admin V4.0之前的版本,根目录下存在2个文件夹:

  • build文件夹包含构建相关的build及webpack等文件。
  • config文件夹包含各种环境下的配置。

通常地,各种基础设置都能在这2个文件夹下的配置文件中完成。
然而,V4.0版本变化较大:

  • build文件夹保留,但其下只剩了一个index.js文件。
  • config文件夹去掉。
  • 根目录下多了2种重要文件:.env.xxx,vue.config.js。

其中,.env.xxx为开发/生产环境的配置文件,vue.config.js为整个工程的全局配置文件。

修改服务端地址

要修改服务端地址,打开.env.development,这是开发环境的配置文件。其下有个配置是:

VUE_APP_BASE_API = '/dev-api'

将该处地址修改为正确的服务端地址:

VUE_APP_BASE_API = 'http://192.168.15.98:9098/template'

即可。

服务端地址前被附加http://localhost:9527/

有时,按上述更改设置后请求,会发现在路径前附加了http://localhost:9527/,类似:

http://localhost:9527/192.168.15.98:9098/template/test

曾经一度排查了很久,将Mock屏蔽也不起作用。有两种解决方法:

  1. 修改vue.config.js中的devServer,配置proxy指向服务端。
  2. 从github上重新下载最新的vue-element-admin。

然而在采用这两种方法的同时也修改了其他的设置,且上述问题后续没有再遇到,故而个人不太确定是否是这两种解决方案起作用了。
同时,不知道是否与运行环境为VS Code有关。

启动工程测试时会打开2个页面

每次运行npm run dev,会启动默认浏览器并打开2个页面。
解决方法为:

  1. 打开vue.config.js,其中devServer.open默认为true,更改为false
  2. 打开package.json,其中scripts.dev默认为vue-cli-service serve,将其修改为:vue-cli-service serve --open

自定义token

接收token并保存

打开 /src/store/modules/user.js,其中actions下的login()为调用用户自定义登录接口并进行回调处理。
在这里获取到的response即为服务端返回的数据。从中即可获取token值。
在下方会调用commit()setToken()来将这个token值保存起来。

将token附加到请求的header

打开 /src/utils/request.js,有这样一段代码:

// request interceptor
service.interceptors.request.use(
  config => {
    // do something before request is sent

    if (store.getters.token) {
      // let each request carry token
      // ['X-Token'] is a custom headers key
      // please modify it according to the actual situation
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    // do something with request error
    console.log(error) // for debug
    return Promise.reject(error)
  }
)

其中config.headers['X-Token'] = getToken()这行代码的作用就是取出token值并附加到header的X-Token属性中。若要更改属性名,或者在token值前附加特定的字符串,可以在这里进行修改。
另外,若需要对特定的返回code做拦截,也是在该文件中进行

动态路由

vue-element-admin的动态路由方案是将所有路由都在web端写好,然后根据服务端返回的roles来决定显示哪些。
实际情况往往需要将路由存储在服务端,web端请求到数据后动态生成路由。

思路

vue-element-admin的动态路由流程为:

  1. 用户输入用户名和密码后点击登录,调用login()接口获取token并缓存。
    其代码位于 /store/user.js
login({ commit }, userInfo) {
  const { username, password } = userInfo
  return new Promise((resolve, reject) => {
    login({ username: username.trim(), password: password }).then(response => {
      const { data } = response
      commit('SET_TOKEN', data.token)
      setToken(data.token)
      resolve()
    }).catch(error => {
      reject(error)
    })
  })
},
  1. 调用getInfo()接口获取用户信息并缓存,其中包含roles属性。
    其代码位于 /store/user.js
getInfo({ commit, state }) {
  return new Promise((resolve, reject) => {
    getInfo(state.token).then(response => {
      const { data } = response

      if (!data) {
        reject('Verification failed, please Login again.')
      }

      const { roles, name, avatar, introduction } = data

      // roles must be a non-empty array
      if (!roles || roles.length <= 0) {
        reject('getInfo: roles must be a non-null array!')
      }

      commit('SET_ROLES', roles)
      commit('SET_NAME', name)
      commit('SET_AVATAR', avatar)
      commit('SET_INTRODUCTION', introduction)
      resolve(data)
    }).catch(error => {
      reject(error)
    })
  })
}
  1. 根据roles属性对本地所有的路由进行过滤,只保留允许roles查看的路由,挂到router中。
    其代码位于 /src/permission.js
try {
  // get user info
  // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
  const { roles } = await store.dispatch('user/getInfo')

  // generate accessible routes map based on roles
  const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

  // dynamically add accessible routes
  router.addRoutes(accessRoutes)

  // hack method to ensure that addRoutes is complete
  // set the replace: true, so the navigation will not leave a history record
  next({ ...to, replace: true })
} catch (error) {
  // remove token and go to login page to re-login
  await store.dispatch('user/resetToken')
  Message.error(error || 'Has Error')
  next(`/login?redirect=${to.path}`)
  NProgress.done()
}

可以看到相关逻辑位于try块中。

根据此流程,可知要满足需求,需要更改其中的2和3两步。设登录的账户都有个roleId属性,可通过请求服务端相关接口并传入roleId来获取一个路由数据数组:

  1. 用户输入用户名和密码后点击登录,调用login()接口获取token并缓存。
  2. 调用getInfo()接口获取用户信息并缓存,其中包含roleId属性。
  3. 调用服务端接口并传入roleId,来获取路由数据数组。
  4. 根据路由数据数组来动态生成路由并挂到router上。

其中第3和第4步的逻辑应在 /src/permission.jstry中实现。提供一段参考代码:

try {
  // get user info
  const { roleId } = await store.dispatch('user/getInfo')

  // 根据roleId从服务端获取addRoutes
  const addRoutes = await store.dispatch('myRoute/generateRoutes', { roleId })
  // 必须设置router.options.routes后,router.addRoutes才会生效
  router.options.routes = constantRoutes.concat(addRoutes)
   // 动态添加可访问路由表。注意已有的静态路由不要设置,否则会提示重复
  router.addRoutes(addRoutes)
  // hack method to ensure that addRoutes is complete
  // set the replace: true, so the navigation will not leave a history record
  next({ ...to, replace: true })
} catch (error) {
  // remove token and go to login page to re-login
  await store.dispatch('user/resetToken')
  Message.error(error || '验证失败,请重新登录')
  next(`/login?redirect=${to.path}`)
  NProgress.done()
}

这里在 /src/modules/ 文件夹下自定义了myRoute.js负责路由数据的请求及获取到数据后路由结构的生成,并将myRoute设置到 /store/getter.js/store/index.js 中。

404拦截

需要注意的一点是,需要将 /src/router/index.jsconstantRoutes的最后两个404路由单独导出,加载动态路由的时候,作为动态路由的一部分独立挂到动态路由的最后。

// 404 页面一定要最后加载,如果放在constantRouterMap一同声明了404,后面的所以页面都会被拦截到404
export const errorRouterMap = [
  { path: '/404', component: () => import('@/views/404'), hidden: true },
  { path: '*', redirect: '/404', hidden: true }
]

也就是说,对于动态路由而言,其构成为:

静态路由部分 + 动态路由部分 + 2个404路由

静态路由(constantRouterMap)即无论哪个账号登录都可访问的路由,直接存储在前端,通常至少有2个路由:登录(/login)和首页(/)。
动态路由即从服务端获取到路由数据后,拼装出的路由数组。

webpackEmptyContext:动态加载失败

路由的结构官方文档有详细说明,一个简单的路由结构为:

{
  path: '/icon',
  component: Layout,
  children: [
    {
      path: 'index',
      component: () => import('@/views/icons/index'),
      name: 'Icons',
      meta: { title: 'Icons', icon: 'icon', noCache: true }
    }
  ]
}

其中非常重要的一个属性是component,其设置为() => import('@/views/icons/index')
静态路由这样写是没问题的。然而,若动态路由需要在import()中使用变量,明明组件文件都在,路由地址也对,但浏览器就会提示:

vue-router.esm.js:1921 Error: Cannot find module ‘@/views/icons/index’ at webpackEmptyContext

这是webpack导致的,不能在import()中使用变量。解决方法为将import()更改为require()。即:

// 定义组件变量
const newComponent = 'icons/index'
// 无法运行的写法
component: () => import('@/views/' + newComponent)
// 正常运行的写法
component: resolve => require([`@/views/` + newComponent], resolve)

如上,即可解决。

命令与配置文件

新版本下的配置文件直接放在了根目录下,默认有3个:

  • .env.development
  • .env.prodution
  • .env.staging

而package.json中主要的运行和打包命令也有3个:

  • “dev”: “vue-cli-service serve”
  • “build:prod”: “vue-cli-service build”
  • “build:stage”: “vue-cli-service build --mode staging”

这3个命令,分别对应上面的3个配置文件。
配置文件的格式统一为:

.env. + 名称

引用时,在命令后加:--mode 名称即可。
特殊地,若运行的是serve命令且没有加--mode 名称,则调用 .env.development ;若运行的是build命令且没有加--mode 名称,则调用 .env.prodution
即:
vue-cli-service serve等同于vue-cli-service serve --mode development
vue-cli-service build等同于vue-cli-service build --mode prodution

这样可以针对不同的环境创建多个配置文件,然后运行对应的命令即可使用对应的配置来运行/打包,而不需要每次都修改配置文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章