创建Vue脚手架项目
使用Vue CLI创建新项目(Vue2或Vue3根据需求选择):
bash
# Vue3
vue create vue3-rbac-demo
# 或Vue2
vue create vue2-rbac-demo
安装必要依赖:
bash
npm install axios vue-router vuex element-ui # Vue2
# 或
npm install axios vue-router@4 vuex@4 element-plus # Vue3
目录结构设计
src/
├── api/ # 接口封装
│ ├── auth.js # 认证相关接口
│ ├── user.js # 用户相关接口
│ └── index.js # 接口统一导出
├── router/ # 路由配置
│ ├── index.js # 路由入口
│ └── permission.js # 路由权限控制
├── store/ # Vuex状态管理
│ ├── modules/ # 模块化store
│ │ ├── auth.js # 认证模块
│ │ └── user.js # 用户模块
│ └── index.js # store入口
├── utils/ # 工具函数
│ ├── auth.js # 权限相关工具
│ └── request.js # axios封装
├── views/ # 页面组件
│ ├── layout/ # 布局组件
│ ├── login/ # 登录页
│ └── ... # 其他业务页面
└── main.js # 应用入口
封装axios请求
utils/request.js 基础配置:
javascript
import axios from 'axios'
import { Message } from 'element-ui' // Vue2
// import { ElMessage } from 'element-plus' // Vue3
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
config => {
const token = localStorage.getItem('token')
if (token) {
config.headers['Authorization'] = `Bearer ${token}`
}
return config
},
error => {
return Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
response => {
const res = response.data
if (res.code !== 200) {
Message.error(res.message || 'Error')
return Promise.reject(new Error(res.message || 'Error'))
} else {
return res
}
},
error => {
Message.error(error.message || 'Error')
return Promise.reject(error)
}
)
export default service
RBAC权限模型实现
store/modules/auth.js Vuex模块:
javascript
const state = {
roles: [],
permissions: []
}
const mutations = {
SET_ROLES: (state, roles) => {
state.roles = roles
},
SET_PERMISSIONS: (state, permissions) => {
state.permissions = permissions
}
}
const actions = {
// 获取用户权限信息
getInfo({ commit }) {
return new Promise((resolve, reject) => {
getUserInfo().then(response => {
const { roles, permissions } = response.data
commit('SET_ROLES', roles)
commit('SET_PERMISSIONS', permissions)
resolve({ roles, permissions })
}).catch(error => {
reject(error)
})
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
路由权限控制
router/permission.js 路由守卫:
javascript
import router from './index'
import store from '../store'
import { getToken } from '../utils/auth'
const whiteList = ['/login'] // 免登录白名单
router.beforeEach(async (to, from, next) => {
const hasToken = getToken()
if (hasToken) {
if (to.path === '/login') {
next({ path: '/' })
} else {
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
const { roles } = await store.dispatch('auth/getInfo')
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
router.addRoutes(accessRoutes)
next({ ...to, replace: true })
} catch (error) {
await store.dispatch('auth/resetToken')
next(`/login?redirect=${to.path}`)
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
动态路由生成
store/modules/permission.js:
javascript
import { asyncRoutes, constantRoutes } from '@/router'
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
// 根据角色过滤路由
function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
权限指令实现
utils/permission.js 自定义指令:
javascript
import store from '@/store'
function checkPermission(el, binding) {
const { value } = binding
const roles = store.getters && store.getters.roles
if (value && value instanceof Array) {
if (value.length > 0) {
const hasPermission = roles.some(role => {
return value.includes(role)
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
}
} else {
throw new Error(`需要指定权限数组,如v-permission="['admin']"`)
}
}
export default {
inserted(el, binding) {
checkPermission(el, binding)
},
update(el, binding) {
checkPermission(el, binding)
}
}
在main.js中注册指令:
javascript
import permission from './utils/permission'
// Vue2
Vue.directive('permission', permission)
// Vue3
app.directive('permission', permission)
使用示例:
html
<button v-permission="['admin']">管理员按钮</button>
接口封装示例
api/user.js:
javascript
import request from '@/utils/request'
export function login(data) {
return request({
url: '/auth/login',
method: 'post',
data
})
}
export function getInfo() {
return request({
url: '/user/info',
method: 'get'
})
}
export function logout() {
return request({
url: '/auth/logout',
method: 'post'
})
}
登录页面实现
views/login/index.vue:
javascript
<template>
<div class="login-container">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules">
<el-form-item prop="username">
<el-input v-model="loginForm.username" placeholder="用户名" />
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" placeholder="密码" />
</el-form-item>
<el-button type="primary" @click="handleLogin">登录</el-button>
</el-form>
</div>
</template>
<script>
import { login } from '@/api/auth'
export default {
name: 'Login',
data() {
return {
loginForm: {
username: '',
password: ''
},
loginRules: {
username: [{ required: true, trigger: 'blur', message: '请输入用户名' }],
password: [{ required: true, trigger: 'blur', message: '请输入密码' }]
}
}
},
methods: {
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
login(this.loginForm).then(response => {
this.$store.commit('auth/SET_TOKEN', response.token)
localStorage.setItem('token', response.token)
this.$router.push({ path: '/' })
}).finally(() => {
this.loading = false
})
}
})
}
}
}
</script>
主布局实现
views/layout/index.vue:
javascript
<template>
<div class="app-wrapper">
<sidebar class="sidebar-container" />
<div class="main-container">
<navbar />
<app-main />
</div>
</div>
</template>
<script>
import { Navbar, Sidebar, AppMain } from './components'
export default {
name: 'Layout',
components: {
Navbar,
Sidebar,
AppMain
},
computed: {
sidebar() {
return this.$store.state.app.sidebar
}
}
}
</script>
权限按钮组件
components/PermissionButton.vue:
javascript
<template>
<div v-if="hasPermission">
<slot />
</div>
</template>
<script>
export default {
name: 'PermissionButton',
props: {
permission: {
type: Array,
required: true
}
},
computed: {
hasPermission() {
return this.permission.some(role => {
return this.$store.getters.roles.includes(role)
})
}
}
}
</script>
使用示例:
javascript
<permission-button :permission="['admin']">
<button>管理员操作</button>
</permission-button>
环境变量配置
.env.development:
bash
VUE_APP_BASE_API = '/api'
.env.production:
bash
VUE_APP_BASE_API = 'https://api.example.com'
完整RBAC流程
- 用户登录获取token并存储
- 获取用户角色和权限信息存入Vuex
- 根据角色生成可访问的路由表
- 路由守卫控制页面访问权限
- 指令/组件控制按钮级权限
- 接口请求携带token进行验证
- token过期或权限变更时重置状态
这个实现方案完整覆盖了RBAC模型在前端应用中的核心功能点,包括路由级权限控制和按钮级权限控制,可以根据实际项目需求进行调整和扩展。