Vue3路由权限控制
- 设置路由:静态路由与动态路由
- 静态路由:这些是在应用启动时就已经定义好的路由,通常包括一些不需要权限验证的公共页面,如登录页、404页面等。
- 动态路由:这些路由是根据用户的权限决定的,通常包括需要权限验证的页面。
- 登录获取 token / sessionId 等数据,利用 pinia 进行存储
- 在登录成功后,将 token 或 sessionId 存储到 Pinia 中,并在需要时(如请求后端API获取权限数据时)从 Pinia 中获取。
- 将从后端获取的权限数据和路由页面进行映射
- 登录成功后,从后端获取的权限数据应该与前端定义的路由进行映射(利用 pinia 进行存储),以决定哪些路由对当前用户是可见的。
- 路由权限控制
- 方法一:全局前置守卫
- 在全局前置守卫中,检查用户的权限,并根据权限数据决定是否允许用户访问某个路由。
- 如果用户没有权限访问某个路由,重定向用户到另一个页面(如403页面或登录页面)。
- 方法二:动态添加路由
- 这种方法适用于需要根据用户权限动态添加路由的场景。
- 在登录成功后,根据从后端获取的权限数据,动态地向 Vue Router 添加路由。
- 全局前置守卫在这里主要用于检查用户是否已经登录以及是否有足够的权限访问当前路由。
vue3路由权限控制demo系统 - 商户管理系统 - 涉及的技术栈:Vite
+ Vue3
+ Axios
+ Vue Router 4
+ Pinia
角色分别为admin
/ merchant
/ staff
时,菜单栏如下:
1. 路由配置
Vue Router 4
进行路由管理时,使用createRouter()
函数来创建并配置路由器实例。
createRouter()
函数替代了Vue Router 3
中的new VueRouter()
函数。
// v3.x 示例
// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
// 1. 定义 (路由) 组件。
import Layout from '../views/layout/Layout'
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. 定义路由
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
// ...
]
// 3. 创建 router 实例并导出
const router = new VueRouter({
// history: 地址栏无'#'号,需要配置服务器重写规则,否则刷新页面会404;
// hsah: 地址栏有'#'号,改变 hash 不会重新加载页面。
mode: 'history',
routes // (缩写) 相当于 routes: routes
})
export default router
// 4. 在main.js中挂载
import router from './router'
const app = new Vue({
router
}).$mount('#app')
// v4.x 示例
// 1. 引入createRouter和createWebHistory
import { createMemoryHistory, createRouter } from 'vue-router'
// 2. 引入路由组件
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
// 3. 定义路由
const routes = [
{ path: '/', component: HomeView },
{ path: '/about', component: AboutView },
]
// 4. 创建 router 实例并 export
const router = createRouter({
// 历史模式:
// Hash 模式: createWebHashHistory()
// HTML5 模式: createWebHistory()
// Memory 模式: createMemoryHistory() - 不能前进或后退,适合 Node 环境和 SSR
history: createMemoryHistory(),
routes,
})
export default router
// 5. 在main.js中挂载
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
// 6. 在App.vue中使用RouterView来渲染路由组件。
// RouterView: 可以使 Vue Router 知道你想要在哪里渲染当前 URL 路径对应的路由组件。
<template>
<RouterView></RouterView>
</template>
<script setup>
import { RouterView } from 'vue-router'
</script>
➡️ @/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Login from '../views/login/index.vue'
import Layout from '../views/layout/index.vue'
import { dynamicRoutes } from './dynamic-router'
// 静态路由
export const constantRoutes = [
{
path: '/',
component: Layout,
redirect: '/home',
children: [
{
path: 'home',
name: 'home',
component: () => import('../views/home/index.vue'),
meta: { title: '首页' }
}
// 在此添加更多的动态子路由
]
},
{
path: '/login',
name: 'login',
component: Login
},
{
path: '/404',
name: '404',
component: () => import('../views/error-page/404.vue')
},
{
path: '/403',
name: '403',
component: () => import('../views/error-page/403.vue')
}
]
// 添加动态路由
constantRoutes.find(item => item.path === '/').children.push(...dynamicRoutes)
const router = createRouter({
history: createWebHistory(),
routes: constantRoutes
})
export default router
➡️ @/router/dynamic-router.js
import merchantList from '../views/merchant-management/merchant-list.vue'
// 动态路由,需要配置权限
export const dynamicRoutes = [
{
name: 'merchant',
path: 'merchant',
meta: { title: '商户管理'},
redirect: '/merchant/list',
children: [
{
path: 'list',
name: 'merchant-list',
component: merchantList,
meta: { title: '商户列表'}
},
{
path: 'info',
name: 'merchant-info',
component: () => import('../views/merchant-management/merchant-info.vue'),
meta: { title: '商户详情'}
},
{
path: 'add',
name: 'merchant-add',
component: () => import('../views/merchant-management/merchant-add.vue'),
meta: { title: '新增商户'}
}
]
},
{
name: 'invoice',
path: 'invoice',
meta: { title: '发票管理'},
redirect: '/invoice/list',
children: [
{
path: 'list',
name: 'invoice-list',
component: () => import('../views/invoice-menagement/invoice-list.vue'),
meta: { title: '开票记录'}
},
{
path: 'apply',
name: 'invoice-apply',
component: () => import('../views/invoice-menagement/invoice-apply.vue'),
meta: { title: '申请开票'}
}
]
},
{
name: 'staff',
path: 'staff',
meta: { title: '员工管理' },
redirect: '/staff/list',
children: [
{
path: 'list',
name: 'staff-list',
component: () => import('../views/staff-management/list.vue'),
meta: { title: '员工列表'}
}
]
}
]
2. 基于 Axios 的 http 请求封装
在
vite.config.js
中配置代理服务:当浏览器发出一个请求时,如果请求
URL
的协议、域名或端口与当前页面URL
中的任何一个不同,则该请求被认为是跨域请求。浏览器出于安全考虑,默认会阻止这种跨域请求。在开发环境中,如使用Vite
时,可以通过配置代理服务器来解决跨域问题,即让Vite
作为中间代理,接收来自客户端的请求,并将其转发至目标服务器;服务器响应后,Vite
再将结果返回给客户端。
注意:Vite
反向代理仅在开发环境有用,如果打包了,就不会走反向代理了。这是因为打包后的Vue3
项目会被部署到与后端相同的IP和端口下,此时不会有跨域问题。
➡️vite.config.js
- 配置反向代理
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
host: '0.0.0.0', // 主机ip
https: false, // 是否开启 https
// open: true, // 是否自动在浏览器打开
port: 5173, // 端口号默认值5173
proxy: {
'/api': {
target: 'http://localhost:3000', // 后台接口前缀
changeOrigin: true, // 是否允许跨域
secure: false, // 是否忽略 HTTPS 证书错误
rewrite: (path) => path.replace(/^\/api/, ''),
}
}
},
})
创建
.env.development
和.env.production
文件:
- 开发环境:
- 使用命令
npm run dev
编译.env.development
文件。- 我们可以在
.env.development
文件里定义VITE_BASE_URL = "/api"
。- 然后在接口文件(例如
@/utils/http.js
)中取出VITE_BASE_URL
并设置为baseURL
,这样可以统一给接口地址加上/api
前缀。这样就能和vite.config.js
中的反向代理关键字/api
联系起来,所有以/api
开头的接口都会被代理到所配置的 URL,从而解决跨域问题。- 生产环境:
- 使用命令
npm run build
编译.env.production
文件。- 在生产环境中,我们将
VITE_BASE_URL
设置为接口的 IP 地址。这样接口文件的baseURL
就会得到实际的 IP 地址,生产环境通常不会出现跨域问题,因此不需要走代理。
➡️.env.development
VITE_BASE_URL = '/api'
➡️.env.production
VITE_BASE_URL = 'https://xxxx.com/'
➡️ @/utils/baseUrl.js
export const baseUrl = import.meta.env.VITE_BASE_URL
➡️ @/utils/http.js
import axios from 'axios'
import baseURL from './baseURL'
import { ElMessage } from 'element-plus'
// 创建一个 axios 实例
const instance = axios.create({
timeout: 5000,
baseURL: baseURL,
})
// 请求拦截器
instance.interceptors.request.use(
config => {
// 请求头添加token
// if (store.state.UserToken) {
// config.headers.Authorization = `Bearer ${store.state.UserToken}`
// }
return config
},
error => Promise.reject(error)
)
// 响应拦截器
instance.interceptors.response.use(
response => {
// 这里可以根据需要处理响应数据
return response.data
},
error => {
console.error('API 请求错误:', error)
if (error.response) {
const status = error.response.status
let message = '请求错误'
switch (status) {
case 400:
message = '请求出错'
break
case 401:
ElMessage.warning('授权失败,请重新登录')
// 此处可以更新登录状态
// store.commit('LOGIN_OUT')
// setTimeout(() => {
// window.location.reload()
// }, 1000)
return
case 403:
message = '拒绝访问'
break
case 404:
message = '请求错误,未找到该资源'
break
case 500:
message = '服务器端出错'
break
default:
message = `请求错误 [${status}]`
}
ElMessage.error(message)
} else {
// 处理网络错误
ElMessage.error('连接服务器失败')
}
return Promise.reject(error)
}
)
// 封装 GET 和 POST 方法
const http = {
get: (url, options = {}) => instance.get(url, options),
post: (url, data, options = {}) => instance.post(url, data, options),
// 可以继续添加其他 HTTP 方法,如 put, delete 等
}
export default http
➡️ @/api/index.js
- 封装api
import http from "@/utils/http"
export function loginAPI(data) {
return http.get(`/user/login?username=${data.username}&password=${data.password}`)
}
export function getPermissionRoutesAPI(data) {
return http.post('/user/getpermissionRoutes', data)
}
3. mock.js模拟后端接口数据
➡️ @/mock/index.js
import Mock from 'mockjs'
import user from './modules/user'
import permission from './modules/permission'
const { mock } = Mock // Mock函数
// 拦截命中的请求
mock(RegExp("/api/user/login" + ".*"), 'get', user.loginAPI)
mock("/api/user/getpermissionRoutes", 'post',permission.getPermissionRoutesAPI)
➡️ @/mock/modules/user.js
import { Random } from "mockjs" // 导出随机函数
function loginAPI(req) {
// req是一个请求对象,包含: url,type和body等属性
const url = req.url.split('?')[1]
const username = url.split('&')[0].split('=')[1]
const password = url.split('&')[1].split('=')[1]
let token = ''
let message = '登录成功'
let code = 200
if (username === 'admin') {
token = 'admin-' + Random.guid()
} else if (username === 'merchant') {
token = 'merchant-' + Random.guid()
} else if (username === 'staff'){
token = 'staff-' + Random.guid()
} else {
code = 401
token = ''
message = '账号不存在'
}
const data = {
code: code, // 响应状态码
data: {
username: Random.cname(),
token: token
},
message: message
}
return data
}
export default {
loginAPI,
}
➡️ @/mock/modules/permission.js
const adminPermission = {
"code": 200,
"message": "获取权限成功",
"data": [
{
"title": "首页",
},
{
"title": "商户管理",
"children": [
{
"title": "商户列表"
},
{
"title": "商户详情",
},
{
"title": "新增商户"
}
]
},
{
"title": "员工管理",
"children": [
{
"title": "员工列表"
}
]
},
{
"title": "发票管理",
"children": [
{
"title": "申请开票"
},
{
"title": "开票记录"
}
]
}
]
}
const merchantPermission = {
"code": 200,
"message": "获取权限成功",
"data": [
{
"title": "首页",
},
{
"title": "员工管理",
"children": [
{
"title": "员工列表"
}
]
},
{
"title": "发票管理",
"children": [
{
"title": "申请开票"
},
{
"title": "开票记录"
}
]
}
]
}
const staffPermission = {
"code": 200,
"message": "获取权限成功",
"data": [
{
"title": "首页",
},
{
"title": "发票管理",
"children": [
{
"title": "申请开票"
},
{
"title": "开票记录"
}
]
}
]
}
function getPermissionRoutesAPI(req, res) {
const token = req.body
switch (token.split('-')[0]) {
case 'admin':
return adminPermission
case 'merchant':
return merchantPermission
case 'staff':
return staffPermission
default:
return staffPermission
}
}
export default {
getPermissionRoutesAPI
}
标签:const,title,Vue3,return,router,import,权限,路由
From: https://blog.csdn.net/m0_46714106/article/details/142379910