(一)项目配置
**项目配置策略**
1.基础配置:指定应用上下文,端口号。vue.config.js
const port=7070;
module.export={
publicPath:"/best-practice",//部署应用包时的基础
devServer:{
port
}
}
2.配置webpack:configureWebpack
1)设置一个组件存放路径的别名。vue.config.js
const path=require('path')
module.exports={
resolve:{
alias:{
comps:path.join("_dirname",'src/components')
}
}
}
2)设置⼀个webpack配置项⽤于⻚⾯title,vue.config.js
module.exports = {
configureWebpack: {
name: "vue项⽬最佳实践"
}
};
在宿主⻚⾯使⽤lodash插值语法使⽤它,./public/index.html
<title><%= webpackConfig.name %></title>
3)基于环境有条件地配置,vue.config.js
// 传递⼀个函数给configureWebpack
// 可以直接修改,或返回⼀个⽤于合并的配置对象
configureWebpack:config=>{
config.resolve.alias.comps=path.join(_dirname,"src/components");
if(process.en.NODE_ENV==='development'){
config.name='vue项目最佳实践'
}else{
config.name=‘Vue Best Practice'
}
}
4)chainWebpack称为链式操作,可以更细粒度控制webpack内部配置。
4.1 下载图标 ,存⼊src/icons/svg中;
4.2 安装依赖:svg-sprite-loader(npm i svg-sprite-loader -D)
4.3 修改规则和新增规则,vue.config.js
// resolve定义⼀个绝对路径获取函数
const path = require('path');
function resolve(dir) {
return path.join(__dirname, dir)
}
chainWebpack(config) {
// 配置svg规则排除icons⽬录中svg⽂件处理
// ⽬标给svg规则增加⼀个排除选项exclude:['path/to/icon']
config.module.rule("svg")
.exclude.add(resolve("src/icons"))
// 新增icons规则,设置svg-sprite-loader处理icons⽬录中的svg
config.module.rule('icons')
.test(/\.svg$/)
.include.add(resolve('./src/icons')).end()
.use('svg-sprite-loader')
.loader('svg-sprite-loader')
.options({symbolId: 'icon-[name]'})
}
4.4使⽤图标,App.vue
<template>
<svg>
<use xlink:href="#icon-wx" />
</svg>
</template>
<script>
import '@/icons/svg/wx.svg'
</script>
4.5做图标的自动导入
#创建icons/index.js
const req = require.context('./svg', false, /\.svg$/)
req.keys().map(req);
#创建SvgIcon组件,components/SvgIcon.vue
<template>
<svg :class="svgClass" v-on="$listeners">
<use :xlink:href="iconName" />
</svg>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
iconClass: {
type: String,
required: true
},
className: {
type: String,
default: ''
}
},
computed: {
iconName() {
return `#icon-${this.iconClass}`
},
svgClass() {
if (this.className) {
return 'svg-icon ' + this.className
} else {
return 'svg-icon'
}
</script>
<style scoped>
.svg-icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
</style>
5)环境变量和模式
如果想给多种环境做不同的配置,可以利用vue-cli提供的模式。默认有developement,production,test三种模式,对应的配置文件的形式是.env.development
5.1定义一个开发时间可用的配置项,创建.env.dev
# 只能⽤于服务端
foo=bar
# 可⽤于客户端
VUE_APP_DONG=dong
修改mode选项覆盖模式名称,package.json
"serve": "vue-cli-service serve --mode dev"
(二)权限
路由分为两种: constantRoutes 和 asyncRoutes ,前者是默认路由可直接访问,后者中定义的路由 需要先登录,获取⻆⾊并过滤后动态加⼊到Router中。
1.
1)路由定义,router/index.js
2)创建用户登录页面,views/Login.vue
3)路由守卫:创建./src/permission.js,并在main.js中引入
4)维护用户登录状态:路由守卫=》用户登录=》获取token并缓存
import router from './router'
import store from './store'
const whiteList = ['/login'] // 无需令牌白名单
router.beforeEach(async (to, from, next) => {
// 获取令牌判断用户是否登录
const hasToken = localStorage.getItem('token')
// 已登录
if (hasToken) {
if (to.path === '/login') {
// 若已登录没有必要显示登录页,重定向至首页
next('/')
} else {
const hasRoles = store.getters.roles && store.getters.roles.length > 0;
if (hasRoles) {
// 说明用户已获取过角色信息,放行
next()
} else {
try {
// 先请求获取用户信息
const { roles } = await store.dispatch('user/getInfo')
// 根据当前用户角色过滤出可访问路由
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// 添加至路由器
router.addRoutes(accessRoutes)
// 继续路由切换,确保addRoutes完成
next({ ...to, replace: true })
} catch (error) {
// 出错需重置令牌并重新登录(令牌过期、网络错误等原因)
await store.dispatch('user/resetToken')
next(`/login?redirect=${to.path}`)
alert(error || '未知错误')
}
}
}
} else {// 未登录
if (whiteList.indexOf(to.path) !== -1) {
// 白名单中路由放过
next()
} else {
// 重定向至登录页
next(`/login?redirect=${to.path}`)
}
}
})
2.异步获取路由表。可以当⽤户登录后向后端请求可访问的路由表,从⽽动态⽣成可访问⻚⾯,操作和原来是相同的,这⾥多了⼀步将后端返回路由表中组件名称和本地的组件映射步骤:
// 前端组件名和组件映射表
const map = {
//xx: require('@/views/xx.vue').default // 同步的⽅式
xx: () => import('@/views/xx.vue') // 异步的⽅式
}
// 服务端返回的asyncRoutes
const asyncRoutes = [
{ path: '/xx', component: 'xx',... }
]
// 遍历asyncRoutes,将component替换为map[component]
function mapComponent(asyncRoutes) {
asyncRoutes.forEach(route => {
route.component = map[route.component];
if(route.children) {
route.children.map(child => mapComponent(child))
}
})
}
mapComponent(asyncRoutes)
3.按钮权限
⻚⾯中某些按钮、链接有时候需要更细粒度权限控制,这时候可以封装⼀个指令v-permission,放在需 要控制的按钮上,从⽽实现按钮级别权限控制
3.1创建指令,src/directives/permission.js
import store from "@/store";
const permission = {
inserted(el, binding) {
// 获取指令的值:按钮要求的角色数组
const { value:pRoles } = binding;
// 获取用户角色
const roles = store.getters && store.getters.roles;
if (pRoles && pRoles instanceof Array && pRoles.length > 0) {
// 判断用户角色中是否有按钮要求的角色
const hasPermission = roles.some(role => {
return pRoles.includes(role);
});
// 如果没有权限则删除当前dom
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(`需要指定按钮要求角色数组,如v-permission="['admin','editor']"`);
}
}
};
export default permission;
3.1指令只能删除挂载指令的元素,对于那些额外⽣成的和指令⽆关的元素⽆能为⼒.此时只能通过v-if来实现。
<template>
<el-tab-pane v-if="checkPermission(['admin'])">
</template>
<script>
export default {
methods: {
checkPermission(permissionRoles) {
return roles.some(role => {
return permissionRoles.includes(role);
});
}
}
}
</script>