文章目录
权限
1、后端返回角色(前端去配置)
2、后端返回数据
第一种方法后端返回数据(前端配置)
一、mock模拟的对比数据
推荐 https://www.fastmock.site/ 网站 模拟接口的
{
"code": "200",
"message": "登录成功",
"meunList": [{
"name": "home"
},
{
"name":"detail"
},
{
"name": "form",
"children": [{
"name": "form1"
}]
},
{
"name": "transition",
"children": [{
"name": "transition1"
},
{
"name": "transition2"
}
]
},
{
"name": "test",
"children": [{
"name": "test1"
},
{
"name": "test2"
}
]
},
]
}
二、 layout
- silder.vue
利用的vuex里面的数据
<template>
<!-- 侧边栏 -->
<div class="sliber-container" style="width: 256px">
<a-menu
:selectedKeys="[$route.path]" // 让其手动刷新的时候还能定位到原来的url
mode="inline"
theme="dark"
:inline-collapsed="$store.state.collapsed"
@select="handleSelect"
>
<template v-for="item in $store.state.oneRouter.silderbarMeun">
<a-menu-item v-if="!item.children" :key="'/'+item.name">
<a-icon type="mail" />
<span>
{{item.meta.title}}
</span>
</a-menu-item>
<a-sub-menu v-else :key="item.name">
<span slot="title">
<a-icon type="mail" />
<span>{{item.meta.title}}</span>
</span>
<a-menu-item v-for="$1 in item.children" :key="'/'+item.name + '/' + $1.name">
{{$1.meta.title}}
</a-menu-item>
</a-sub-menu>
</template>
</a-menu>
</div>
</template>
- header.vue
<template>
<!-- 进入首页后我们要使用localStorage
把登陆时存储的用户名取出来显示在右边登录名的地方,
当用户点击注销图标的时候,清除localStorage,
并跳转回登陆页面。 -->
<!-- 头部 -->
<div class="header_container">
<div class="header_left">
<span>恓惶后台系统</span>
</div>
<div class="header_right">
<a-avatar icon="user" />
<span class="user-name" @click="exitFn">登出</span>
</div>
</div>
</template>
<script>
export default {
data() {
return {};
},
methods: {
exitFn() {
// 退出的时候得撤销token
localStorage.removeItem("token");
this.$store.commit('LOGIN_OUT')
// this.$store.commit('oneRouter/CLEAR_PERMISSION', {}, {root: true})
// this.$store.commit('oneRouter/CLEAR_MEUN', {}, {root: true})
window.location.reload() // 有这个就相当于刷新就不需要上面两个清楚步骤了
this.$router.push("/login");
},
},
};
</script>
三、vuex
- 目录
- index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import way from './modules/index'
export default new Vuex.Store({
state: {
// 这样写的目的是获取的时候直接获取,设置的时候直接设置()
/**
* 为什么要这样设置呢,因为vuex是存在内存中的这样读取的更快
*/
get UserToken(){
return localStorage.getItem('token')
},
set UserToken(value){
localStorage.setItem('token', value)
}
},
mutations: {
LOGIN_IN(state, token){
state.UserToken = token
},
LOGIN_OUT(state){
state.UserToken = ""
}
},
actions: {},
modules: {
oneRouter: way.oneRouter
},
});
- 模块中的index.js
/**
* @params oneRouter 路由权限的方法一(前端比对)
*/
import oneRouter from './oneRouter.js'
export default {
oneRouter
}
- oneRouters.js
import { getVipData, getMainData } from '@/api/nav.js' // 接口一个数据就类似上面那种配置
import dynamicRouter from '@/router/dynamic_router' // 拿来对比做权限的全部路由(数据在下方)
import { recursionRouter, setDefaultRouter} from '../../utils/recursion' // 两个转换方法(数据在下方)
import router, { DynamicRoutes} from '@/router/index'
import store from '@/store'
export default {
namespaced: true,
state:{
permissionList: null, // 最終所有的路由用来在导航守卫判断用的
silderbarMeun:[], // 导航菜单 用来渲染的
},
getters:{},
mutations:{
SET_PERMISSION(state,routes){
state.permissionList = routes
},
CLEAR_PERMISSION(state){
state.permissionList = null
},
SET_MEUN(state,meun){
state.silderbarMeun = meun
}, // 设置菜单的
CLEAR_MEUN(state,meun){
state.silderbarMeun = []
} // 清楚菜单的
},
// 异步访问
actions: {
async FETCH_PERMISSION({commit, state}){
let permissionList = null
if(store.state.UserToken == 'vip'){
permissionList = await getVipData()
}else{
permissionList = await getMainData()
}
// 筛选
const routes = recursionRouter(permissionList.data.meunList, dynamicRouter)
const Main = DynamicRoutes.find(el => el.name === 'layout')
Main.children = [] // 防止退出的时候这里面的东西还存在(如果退出直接用window.location.reload()这个步骤可有可无)
const children = Main.children
children.push(...routes)
// 生成菜单
commit('SET_MEUN', children)
// 设置默认路由
setDefaultRouter([Main]) // 可有可无我是在全部定义中已经定义好重定向了
// 初始化路由
const initRoute = router.options.routes
router.addRoutes(DynamicRoutes)
commit('SET_PERMISSION',[...initRoute, ...DynamicRoutes])
}
}
}
四、router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import store from "@/store";
Vue.use(VueRouter);
// 引入自己写的layout
import Layout from "@/layout";
// 防止多点 重写router的push方法
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
}
// 初始化路由
const routes = [
{
path: "/login",
name: "login",
component: () => import("@/views/login.vue"),
meta: { requiresAuth: true, title: "登录" },
}
];
/**
* 动态路由
*/
// 动态加载路由
export const DynamicRoutes = [
{
path: "/",
name:'layout',
component: Layout,
meta: { requiresAuth: true, title: "首页" },
children: [
],
},
//匹配404页面 匹配上面所有的就到这个页面
{
path: "*",
name: "404",
component: () => import("@/views/404"),
},
]
const router = new VueRouter({
routes,
});
// 设置路由白名单(其实网上很多做法用meta的requiresAuth就是跟白名单一样的)
function inWhiteList(toPath) {
const whiteList = ['/login','/404'];
const bool = whiteList.some(el => el == toPath) // 有一个正确就正确
return bool
}
// 导航守卫
router.beforeEach(async (to, from, next) => {
window.document.title = to.meta.title // 改变浏览器标题
// 路由守卫(可以做权限),根据路由元信息设置文档标题
const token = store.state.UserToken;
// 1.检查to.path是否存在于免登陆白名单
if (inWhiteList(to.path)){
// 判断存在token的时候并且是login的时候直接跳转
if (to.path === '/login' && token) {
// 避免重复登录
next({ path: '/'})
} else {
next()
}
return
}
// 2.登录逻辑
if (!token) {
// 用户未登录
next({
path: '/login'
})
} else {
// 用户登录了
if(!store.state.oneRouter.permissionList){
// router.matcher = new VueRouter({routes}) // 请求路由里面的纪录 要保留之前静态的路径
store.dispatch("oneRouter/FETCH_PERMISSION").then(() => {
// 防止刷新
next({path: to.path})
})
}else{
next()
}
}
});
export default router;
- 全部数据(dynamic_router.js)
/**
* 全部router数据 用来和返回的数据进行对比
*/
const DynamicRoutes = [
{
path:'/home',
name:'home',
component: () => import('@/views/home/home.vue'),
meta: { title: "首页" },
},
{
path:'/detail',
name:'detail',
component: () => import('@/views/detail/detail.vue'),
meta: { title: "详情" },
},
{
path:'/form',
name:'form',
component: () => import('@/views/form/index.vue'),
meta: { title: "表单" },
children: [
{
path:'form1',
name:'form1',
component: () => import('@/views/form/form1.vue'),
meta: { title: "表单1" },
}
]
},
{
path:'/transition',
name:'transition',
component: () => import('@/views/transition/index.vue'),
meta: { title: "过渡" },
children: [
{
path:'transition1',
name:'transition1',
component: () => import('@/views/transition/transition1.vue'),
meta: { title: "过渡1" },
},
{
path:'transition2',
name:'transition2',
component: () => import('@/views/transition/transition2.vue'),
meta: { title: "过渡1" },
}
]
},
{
path:'/test',
name:'test',
component: () => import('@/views/test/index.vue'),
meta: { title: "测试" },
children: [
{
path:'test1',
name:'test1',
component: () => import('@/views/test/test.vue'),
meta: { title: "测试1" },
},
{
path:'test2',
name:'test2',
component: () => import('@/views/test/test1.vue'),
meta: { title: "测试2" },
}
]
},
]
export default DynamicRoutes
- recursionRouter, setDefaultRouter两种方法
/**
* 这个是对比发的文件
* 方法一:比对路由权限
* 方法二:指定返回的重定向默认
*/
/**
* @param {Array} userRouter 后端返回的数据
* @param {Array} allRouter 前端自己配置好的路由
*/
export function recursionRouter(userRouter = [], allRouter = []){
let resultRouter = []
allRouter.forEach((v,i) => {
userRouter.forEach((item,index) => {
if(item.name === v.name){
if(item.children && item.children.length > 0){
v.children = recursionRouter(item.children, v.children)
}
resultRouter.push(v)
}
})
})
return resultRouter
}
export function setDefaultRouter(routes){
routes.forEach((v,i) => {
if(v.children && v.children.length > 0){
v.redirect = {name: v.children[0].name}
setDefaultRouter(v.children)
}
})
}
五、封装request.js
import axios from 'axios'
const http = axios.create({
baseURL: 'https://www.fastmock.site/mock/772b8327e1a2bc2488c1e8329f0e917b/main',
timeout: 10000,
});
// 添加请求拦截器
http.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
http.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
export default http
注意最好在addroute后(效果有用),在把router.option.routes中也得添加下动态数据router,避免在组件中获取不到里面的数据
六、Bug
-
使用导航菜单重复点击报错解决方法
在router下的index.js下 在new VueRouter(之前定义) const resolveOriginal = VueRouter.prototype.push VueRouter.prototype.push = function push(location) { return resolveOriginal.call(this, location).catch(err => err) }
七、额外的知识点
设置浏览器的标题和图标
- 标题的改变
- 将.ico格式的图片放到public路径下面 之后自己取名字就好
八、登录
1.登录的流程
还有一点注意的就是之后在自己封装axios的时候要在请求拦截器中
并且登录的时候我们还要将token存到vuex里面去,这样取出来的时候方便
2.跨域的问题
-
解决方法一 vue.config.js配置 devServer: { open: true, //配置后自动启动浏览器 // proxy: 'http://localhost:8080' // 配置跨域处理,只是一个代理 proxy: { //配置多个跨域 "/api": { // 拦截以/api开头接口 target: "https://www.bilibili.com/", //目标 changeOrigin: true, // //这里true表示实现跨域 ws: true, //websocket支持 secure: false, pathRewrite: { "^/api": "", }, }, }, }, 组件调用直接 /api/index/ding.json