在vue-element-admin V4.0之前的版本,根目录下存在2个文件夹:
通常地,各种基础设置都能在这2个文件夹下的配置文件中完成。
然而,V4.0版本变化较大:
其中,.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屏蔽也不起作用。有两种解决方法:
然而在采用这两种方法的同时也修改了其他的设置,且上述问题后续没有再遇到,故而个人不太确定是否是这两种解决方案起作用了。
同时,不知道是否与运行环境为VS Code有关。
每次运行npm run dev
,会启动默认浏览器并打开2个页面。
解决方法为:
devServer.open
默认为true
,更改为false
。scripts.dev
默认为vue-cli-service serve
,将其修改为:vue-cli-service serve --open
。一开始使用Mock定义了所有的接口。随着服务端开放部分接口,这部分接口就需要改为访问服务端。此时就需要同时访问服务端和Mock。
同时访问服务端和Mock有两种思路:
使用同一个request有2种方式:
VUE_APP_BASE_API
,拦截服务端接口并将其更改为服务端地址。VUE_APP_BASE_API
,拦截Mock接口并将其更改为Mock地址。VUE_APP_BASE_API
默认地,VUE_APP_BASE_API = '/dev-api'
。这个不需要更改。
打开vue.config.js,修改其devServer,添加proxy属性:
proxy: {
'/login': {
target: 'http://192.168.15.98:/template',
changeOrigin: true,
pathRewrite: {
['^' + process.env.VUE_APP_BASE_API]: ''
}
}
}
如上,会拦截带有/login
的接口,将其基本地址更改为http://192.168.15.98:/template
。
然而,更改地址仅仅是更改了ip+端口,/dev-api
依然被保留。也就是说:
http://localhost:9527/dev-api/login
http://192.168.15.98:/template/dev-api/login
但目标地址为:http://192.168.15.98:/template/login
。故而需要设置pathRewrite
将/dev-api
替换为空字符串。
这样做可以实现。然而,这样意味着服务端每次开放接口都需要修改此代理来适配新的接口。随着服务端开放的接口越来越多,这个设置也会变得越来越复杂。可以创建一个单独的配置文件来存放这些API。
当然可以为所有的服务端接口添加一个统一的前缀,这样该处设置就不需要反复修改。但这样就对服务端的接口名有要求,考虑到实际开发,并不推荐这样做。
VUE_APP_BASE_API
Mock数据都是存放在本地的,因此所有Mock接口是本地定义的,不需要服务端干涉。故而Mock接口可以添加统一前缀。当服务端对应接口开放,删掉相应Mock接口并修改对应访问即可。
于是将服务端地址设为VUE_APP_BASE_API
,然后拦截Mock的接口。还要修改/mock/mock-server.js
中的responseFake
,其中用到的url更改为Mock的地址。
理论来说与前一种方式相同。但实际会遇到跨域的问题。
首先明确本机是localhost。
前一种方式访问的地址是本地,访问服务端接口时,代理将对localhost的访问转为对服务端的访问,也就是说是从localhost访问localhost,然后代理转给服务端,这样是没问题的。
然而这一种方式的访问的地址是服务端地址,这就意味着访问Mock端口时,需要代理将对服务端的访问转为对本地的访问,即从localhost访问服务端,然后代理将服务端地址转为localhost。这样就跨域了。
不推荐使用此方式。
请求都是由/src/utils/request.js
负责。因此复制一个request.js
,改名为requestMock.js
,将其中axios.create()
时所调用的地址更改为Mock的地址。
然后找到/src/api/
下所有需要访问Mock的文件,将其中:
import request from '@/utils/request'
更改为:
import request from '@/utils/requestMock'
然后还要修改/mock/mock-server.js
中的responseFake
,其中用到的url更改为Mock的地址。
这样,2个request互不影响,问题解决。
打开 /src/store/modules/user.js,其中actions
下的login()
为调用用户自定义登录接口并进行回调处理。
在这里获取到的response即为服务端返回的数据。从中即可获取token值。
在下方会调用commit()
和setToken()
来将这个token值保存起来。
打开 /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的动态路由流程为:
login()
接口获取token并缓存。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)
})
})
},
getInfo()
接口获取用户信息并缓存,其中包含roles属性。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)
})
})
}
router
中。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来获取一个路由数据数组:
login()
接口获取token并缓存。getInfo()
接口获取用户信息并缓存,其中包含roleId属性。router
上。其中第3和第4步的逻辑应在 /src/permission.js 的try
中实现。提供一段参考代码:
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 中。
需要注意的一点是,需要将 /src/router/index.js 中constantRoutes
的最后两个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
)和首页(/
)。
动态路由即从服务端获取到路由数据后,拼装出的路由数组。
路由的结构官方文档有详细说明,一个简单的路由结构为:
{
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个:
而package.json中主要的运行和打包命令也有3个:
这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
这样可以针对不同的环境创建多个配置文件,然后运行对应的命令即可使用对应的配置来运行/打包,而不需要每次都修改配置文件。
将vue.config.js的publicPath属性更改为./
即可。