第二节:深度剖析菜单权限、按钮权限、keepAlive缓存的设计

一. 剖析菜单权限

(一). 登录成功后,调用initBackEndControlRoutes方法,初始化路由

 1. 初始化用户信息(含基础信息、菜单权限、按钮权限),存放vuex中, store.dispatch('userInfos/setUserInfos');

 

 2. 获取路由全部菜单

      (1). 请求接口,获取所有的路由信息 (注意:这里不是特定权限哦)

      (2). 将路由信息存放到vuex中的requestOldRoutes里

      (3). 将获取的路由数据转换一下,即把 "component": "home/index"  转换成 component: () => import('/@/views/home/index.vue'),

   最终赋值给 dynamicRoutes[0].children = await backEndComponent(res.data); 后面的setAddRoute方法中将使用赋值后的dynamicRouters

   注意:此时dynamicRoute并没有注册到vue-router中

 

 3. 将用户具有权限的菜单对应路由注册到vue-router中,详见 setAddRoute 方法  

   (1). 获取当前用户所具有权限标识的数组 setFilterRouteEnd()

      A. 将多级路由嵌套处理成一维数组 formatFlatteningRoutes(dynamicRoutes) 【难点!】

      B. 再一维数组处理成多级嵌套数组,返回 dynamicRoutes 的格式,但这里只处理两级即可。 详见:formatTwoStageRoutes 解释

      【主要目的是处理keep-alive缓存,将所有需要缓存的菜单name存放到vuex中的keepAliveNames数组中】

      C. 获取当前用户权限标识的路由数组 setFilterRoute(filterRouteEnd[0].children)

      【已经改造成meta.roles数组中只存一个值,且和path值相同的情况了】

      D. 将pathMatch(未匹配上统一跳转404)的路由和C中获取的路由合并,统一返回

   (2). 遍历数组进行路由的注册

       根据routeName值判断路由中是否已经存在,不存在则通过 router.addRoute(route) 进行添加

 

4. 准备左侧菜单所需要的数据 + tagView需要的数据 详见setFilterMenuAndCacheTagsViewRoutes方法

  (1).获取用户所具有权限的左侧(或横向)菜单数据,赋值给vuex中的routesList

    A. 通过setFilterHasRolesMenu方法递归遍历dynamicRoutes[0].children的数据, 利用hasRoles方法判断是否具有这项菜单权限,最后返回具有权限的菜单数据

    【重点:这里是在dynamicRoutes[0].children上处理,如果没有权限,删掉其中的那部分,但是格式还是形如 dynamicRoutes[0].children的格式】

    B. 赋值给vuex中的routesList,即菜单所需要的数据  

  (2).将用户具有权限的菜单数据对应的路由向vuex中的tagsViewRoutes存放,供tagView使用 详见setCacheTagsViewRoutes方法

    A.  获取用户具有权限的菜单数据   详见setFilterHasRolesMenu

    B.  将路由多级嵌套处理成一维数组  详见formatFlatteningRoutes

    C.  一维数组处理成多级嵌套数组    详见 formatTwoStageRoutes

    D.  将具有权限的菜单存放到Vuex中的tagsViewRoutes

  注:此处的步骤和上面步骤3中的路由注册有点重复,考虑精简一下  

 

(二) 相关菜单组件的显示

  封装了两个菜单组件,水平方向和竖直方向的,分别是:

  1. layout/navMenu/horizontal.vue 水平方向的菜单

      调用位置:layout/navBars/breadcrumb/index.vue, 传递的参数 store.state.routesList.routesList

  2. layout/navMenu/vertical.vue 竖直方向的菜单

      调用位置:layout/component/asize.vue,传递参数 store.state.routesList.routesList

  总结:最终传递的数据都是vuex中的store.state.routesList.routesList

 

二. 剖析按钮权限

1.  思考

(1). 项目中总共有几类情况用到按钮权限?

  【页面的按钮是一类,表格的查询权限是一类】

(2). 按钮权限的定义规则和存放问题?

  【形如:/system/user/search,最后一部分为按钮的含义;用户所具有的按钮权限以数组的形式存放在VueX中的authBtnList中】

(3). 页面上如何判断是否有该按钮权限呢?

  本质:就是判断Vuex中的authBtnList中是否包含这个按钮名字,包含则显示,反之则销毁。这里封装三种形式判断有无:

    a. 函数形式:单个权限和多个(满足1个 or 全部满足)权限验证,详见/utils/authFunction 

查看代码
import { store } from '/@/store/index';
import { judementSameArr } from '/@/utils/arrayOperation';

/**
 * 单个权限验证
 * @param value 权限值
 * @returns 有权限,返回 `true`,反之则反
 */
export function auth(value) {
	return store.state.userInfos.userInfos.authBtnList.some((v) => v === value);
}

/**
 * 多个权限验证,满足一个则为 true
 * @param value 权限值
 * @returns 有权限,返回 `true`,反之则反
 */
export function auths(value) {
	let flag = false;
	store.state.userInfos.userInfos.authBtnList.map((val) => {
		value.map((v) => {
			if (val === v) flag = true;
		});
	});
	return flag;
}

/**
 * 多个权限验证,全部满足则为 true
 * @param value 权限值
 * @returns 有权限,返回 `true`,反之则反
 */
export function authAll(value) {
	return judementSameArr(value, store.state.userInfos.userInfos.authBtnList);
}

/**
 * 判断两数组是否相同(忽略顺序)
 * @param news 新数据
 * @param old 源数据
 * @returns 两数组相同返回 `true`,反之则反
 */
export function judementSameArr(news, old) {
	let count = 0;
	const leng = old.length;
	// for in也可以遍历数组,通常用它来遍历对象
	for (let i in old) {
		for (let j in news) {
			if (old[i] === news[j]) count++;
		}
	}
	return count === leng ? true : false;
}

    b. 指令形式:单个权限和多个权限验证,定义详见/utils/directive/authDirective,在main.js进行全局指令的注册

查看代码
 import { store } from '/@/store/index';
import { judementSameArr } from '/@/utils/arrayOperation';

/**
 * 用户权限指令
 * @directive 单个权限验证(v-auth="xxx")
 * @directive 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
 * @directive 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
 */
export function authDirective(app) {
	// 单个权限验证(v-auth="xxx")
	app.directive('auth', {
		mounted(el, binding) {
			if (!store.state.userInfos.userInfos.authBtnList.some(v => v === binding.value)) el.parentNode.removeChild(el);
		},
	});
	// 多个权限验证,满足一个则显示(v-auths="[xxx,xxx]")
	app.directive('auths', {
		mounted(el, binding) {
			let flag = false;
			store.state.userInfos.userInfos.authBtnList.map(val => {
				binding.value.map(v => {
					if (val === v) flag = true;
				});
			});
			if (!flag) el.parentNode.removeChild(el);
		},
	});
	// 多个权限验证,全部满足则显示(v-auth-all="[xxx,xxx]")
	app.directive('auth-all', {
		mounted(el, binding) {
			const flag = judementSameArr(binding.value, store.state.userInfos.userInfos.authBtnList);
			if (!flag) el.parentNode.removeChild(el);
		},
	});
}

    c. 组件形式:详见components/auth文件夹下的三个组件

auth.vue

<template>
    <slot v-if="getUserAuthBtnList" />
</template>

<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
    name: 'auth',
    props: {
        value: {
            type: String,
            default: () => '',
        },
    },
    setup(props) {
        const store = useStore();
        // 获取 vuex 中的用户权限
        const getUserAuthBtnList = computed(() => {
            return store.state.userInfos.userInfos.authBtnList.some(v => v === props.value);
        });
        return {
            getUserAuthBtnList,
        };
    },
};
</script>
View Code

authAll.vue

<template>
    <slot v-if="getUserAuthBtnList" />
</template>

<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
import { judementSameArr } from '/@/utils/arrayOperation';
export default {
    name: 'authAll',
    props: {
        value: {
            type: Array,
            default: () => [],
        },
    },
    setup(props) {
        const store = useStore();
        // 获取 vuex 中的用户权限
        const getUserAuthBtnList = computed(() => {
            return judementSameArr(props.value, store.state.userInfos.userInfos.authBtnList);
        });
        return {
            getUserAuthBtnList,
        };
    },
};
</script>
View Code

auths.vue

<template>
    <slot v-if="getUserAuthBtnList" />
</template>

<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
    name: 'auths',
    props: {
        value: {
            type: Array,
            default: () => [],
        },
    },
    setup(props) {
        const store = useStore();
        // 获取 vuex 中的用户权限
        const getUserAuthBtnList = computed(() => {
            let flag = false;
            store.state.userInfos.userInfos.authBtnList.map(val => {
                props.value.map(v => {
                    if (val === v) flag = true;
                });
            });
            return flag;
        });
        return {
            getUserAuthBtnList,
        };
    },
};
</script>
View Code

用法:

<Auth :value="authList.add"> <el-button size="mini" type="success" @click="onOpenAddDialog" v-auth="authList.add"> 新增 </el-button> </Auth>

2. 实操

   A. 登录的时候从接口中获取按钮权限,存放到Vuex中的 userInfos.authBtnList

   B. 页面上先把该页面用到的所有按钮权限名称定义出来,如下

const authList = reactive({
	search: '/system/user/search',
	add: '/system/user/add',
	edit: '/system/user/edit',
	delOne: '/system/user/delOne',
	delMany: '/system/user/delMany',
	excel: '/system/user/excel',
	arrange: '/system/user/arrange',
});

   C. 使用 v-auth指令的模式绑定按钮

<template #handler="myInfo">
        <el-button size="small" type="text" @click="onOpenEditDialog(myInfo.row1)" v-auth="authList.edit">修改</el-button>
        <el-button size="small" type="text" @click="deleteObjs(myInfo.row1.id)" v-auth="authList.delOne">删除</el-button>
 </template>

   D. 使用 auth方法处理表格是否有查询权限

import { auth } from '/@/utils/authFunction';
/**
 * 初始化表格数据
 */
const initTableData = async () => {
	if (!auth(authList.search)) {
		ElMessage.error('您没有查询权限');
		return;
	}
	//将string类型的对象去空格
	let myTrimData = TrimObjStrPros(formData.value);
	const { status, data } = await myAxios({
		url: proxy.$url.GetUserInforByConditionUrl,
		data: { ...tableData.param, ...myTrimData },
	});
	if (status == 'ok') {
		tableData.tableRows = data.tableRows;
		tableData.total = data.total;
	}
};

 

三. keepAlive缓存设计

1. 思考

(1). 菜单路由中的参数 meta对象中的 isKeepAlive 作用是什么?

   【答:当这个菜单对应的isKeepAlive=true时,表示该菜单需要缓存,会存放到vuex中的keepAliveNames数组,详见router/index.js/formatTwoStageRoutes方法】

 

(2). 菜单存在上下级问题,如果上级isKeepAlive为false,下级的isKeepAlive设为true,有效吗?

   【答:有效! 但不建议这么设置,建议下级只要有1个isKeepAlive设为true,就将他的父级isKeepAlive设为true    详见router/index.js/formatTwoStageRoutes方法

      分析该方法,这里只分两级,第一级是指 / 这种路径,这个路径的isKeepAlive必须设置为true,而对于菜单的上下级,比如/system/role 和 /system 是同一级的。

      该方法的判断条件是:if (newArr[0].meta.isKeepAlive && v.meta.isKeepAlive) 说明他这个第一级是指 / 的这种路径   】

 

(3). Vuex中的keepAliveNames数组,作用是什么?

    【答:形如:["home","system","systemUser","systemRole"],存放的是需要缓存菜单的名称,最后放在parent.vue页面中 <keep-alive> 组件的includes属性中】

   

(4). Vuex中的 tagsViewRoutes 数组,作用是什么?

    【答:这里存放的是具有权限菜单的路由数据,与isKeepAlive无关 】

 

(5). 点击左侧的菜单,是如何与右侧的tageView相关联的?

     【答:以vertical.vue竖向菜单为例,el-menu添加的router属性,表示启用vue-router模式,启用该模式会在激活导航时以index的值作为path进行路由跳转】

 

(6). <keep-alive>组件所在的页面 parent.vue 中的 v-slot="{ Component }" 怎么理解? 

   <router-view v-slot="{ Component }">
      <transition :name="setTransitionName" mode="out-in">
        <keep-alive :include="state.keepAliveNameList">
          <component :is="Component" :key="state.refreshRouterViewKey" class="w100" />
        </keep-alive>
      </transition>
    </router-view>

  【参考官网:https://router.vuejs.org/zh/api/#router-view-的-v-slot】

  【答:通过路由跳转,比如跳转路径为 /system/user, router-view组件中Component将恢自动解析为 /system/user 路径对应的组件,从而赋值给 :is="Component" 】

   

(7). 每个菜单对应的Vue页面为什么有个name值?这个值为什么需要和路由菜单中name值相同才能实现页面缓存呢?

    【参考官网:https://v3.cn.vuejs.org/api/built-in-components.html#keep-alive 】

    【答:

         A. 观察parent.vue中的<keep-alive>组件,include里存放的是Vuex中keepAliveNames数组,而该数组存放的就是需要缓存的菜单路由的name值

        B. <component>组件匹配的时候,首先检查组件自身的 name 选项,所以要定义菜单页面的name值   】

   

(8). 设置中的开启tagView缓存的作用是什么?

     【答:可以缓存多个tagView,刷新页面不消失】

(9).  设置中的tagView公用的作用是什么?

     【】

 

2. 分析流程

 肯定是使用keepAlive,使用方法:每个页面都必须加一个name属性,这个name属性必须和路由菜单中的name值相同,才能实现keepAlive缓存。

 原理:需要全局搜索一下 keep-alive,找到相应文件

  <keep-alive> → parent.vue → main.vue → [classic.vue] [columns.vue] [defaults.vue] [transverse.vue]

  (1). <keep-alive> 组件在 parent.vue 中使用 (layout\routerView\parent.vue),keep-alive中存放<component>动态组件

     【:include里传入的值是vuex中的store.state.keepAliveNames.keepAliveNames】

  (2). parent.vue组件在 main.vue 中使用 (\layout\component\main.vue)

  (3). main.vue组件在 /layout/main下的四个组件中调用,分别是:[classic.vue] [columns.vue] [defaults.vue] [transverse.vue]

现象: 开启缓存后,页面关闭,下次打开仍然缓存,实际我不想这样

 

3. tagsView组件分析

  【组件详见:/@/layout/navBars/tagsView/tagsView.vue'】

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-12-23 16:20  Yaopengfei  阅读(155)  评论(1编辑  收藏  举报