BCVP.VUE3系列第六课:获取动态菜单接口

科技   科技   2024-10-02 08:15   山西  
BCVP 开发者社区出品

BCVP V3开发

数字化
服务化
绿色化



放假不停歇,趁着假期学习下VUE3相关的内容,一方面是自己保持活力,另一方面也是工作需要,本系列是我的自学教程,如果有从0开始学习VUE3的,可以跟着一起练习下,毕竟前端我也是泥腿子出身,这一系列会使用Vite、TS、Pinia、Element-Plus等新知识点,既是查漏补缺,也是知识分享。

代码地址:

https://github.com/anjoy8/bcvp.vue3.git

这是每篇文章一节课一个分支,方便大家学习,会慢慢的将blog.admin项目进行翻新,使用的后端接口还是BlogCore。

系列文章:

第一课:项目初始化与核心知识点说明

第二课:基于泛型基类封装Axios请求

第三课:封装Axios拦截器

第四课:登录页设计

第五课:获取用户信息



0、本文介绍



本文参考的是开源项目

https://gitee.com/HalseySpicy/Geeker-Admin/tree/template

分步骤讲解登录逻辑,今天说一下获取动态菜单接口,为下一步动态渲染作准备。



1、定义模型,调用接口


还是老规矩,定义一个接口模型,因为这个也是在全局都能用到的,还是定义一个全局,在typings下的global.d.ts文件中,定义相应的接口
/* Menu */declare namespace Menu {  interface MenuOptions {    id: string;    pid: string;    order: number;    name: string;    IsHide: boolean;    IsButton: boolean;    path: string;    Func: string | null;    component?: string | (() => Promise<unknown>);    iconCls: string;    meta: MetaProps;    children?: MenuOptions[] | null;  }  interface MetaProps {    title: string | null;    requireAuth: boolean;    NoTabPage: boolean;    keepAlive: boolean;  }  export interface MenuRequest {    uid: string;  }}



然后,调用动态菜单数据接口,

在loginApi.ts文件中写逻辑,毕竟这个还算是登录模块的

// 获取菜单列表export const getAuthMenuListApi = async (params: Menu.MenuRequest): Promise<BaseResponse<Menu.MenuOptions>> => {  try {    const response = await get<BaseResponse<Menu.MenuOptions>>('/api/permission/GetNavigationBar', {      uid: params.uid,    });    return response;  } catch (error) {    throw new Error('请求失败');  }};



2、注册服务状态管理


因为菜单数据在很多地方都用的到,且大概率还是响应式的,所以还是放到Pinia里实现:

新建文件src\stores\modules\authMenu.ts

import { defineStore } from "pinia";import type { AuthState } from "@/stores/interface";import { getAuthMenuListApi } from "@/api/loginApi";import { getFlatMenuList, getShowMenuList, getAllBreadcrumbList } from "@/utils";
export const useAuthMenuStore = defineStore({ id: "blogvue3-auth", state: (): AuthState => ({ // 按钮权限列表 authButtonList: {}, // 菜单权限列表 authMenuList: [], // 当前页面的 router name,用来做按钮权限筛选 routeName: "" }), getters: { // 按钮权限列表 authButtonListGet: state => state.authButtonList, // 菜单权限列表 ==> 这里的菜单没有经过任何处理 authMenuListGet: state => state.authMenuList, // 菜单权限列表 ==> 左侧菜单栏渲染,需要剔除 isHide == true showMenuListGet: state => getShowMenuList(state.authMenuList), // 菜单权限列表 ==> 扁平化之后的一维数组菜单,主要用来添加动态路由 flatMenuListGet: state => getFlatMenuList(state.authMenuList), // 递归处理后的所有面包屑导航列表 breadcrumbListGet: state => getAllBreadcrumbList(state.authMenuList) }, actions: { // Get AuthMenuList async getAuthMenuList(params: Menu.MenuRequest) { const { response } = await getAuthMenuListApi(params); this.authMenuList = response.children ?? []; }, // Set RouteName async setRouteName(name: string) { this.routeName = name; } }});


这里涉及到了一个公共模型,在stores/interfacse/index.ts中添加,


/* AuthState */export interface AuthState { routeName: string; authButtonList: { [key: string]: string[]; }; authMenuList: Menu.MenuOptions[];}


然后还需要一个util的帮助类,对接口返回的原始数据进行预处理,比如做一个过滤,或者数据扁平化处理,当然这块逻辑可以写到当前文件authMenu.ts中,不过抽离出来放到公共帮助类更好,其他地方肯定也能用到:

在src下建一个utils文件夹,新增一个index.ts文件


/** * @description 使用递归扁平化菜单,方便添加动态路由 * @param {Array} menuList 菜单列表 * @returns {Array} */export function getFlatMenuList(menuList: Menu.MenuOptions[]): Menu.MenuOptions[] { let newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList)); return newMenuList.flatMap(item => [item, ...(item.children ? getFlatMenuList(item.children) : [])]);}
/** * @description 使用递归过滤出需要渲染在左侧菜单的列表 (需剔除 isHide == true 的菜单) * @param {Array} menuList 菜单列表 * @returns {Array} * */export function getShowMenuList(menuList: Menu.MenuOptions[]) { let newMenuList: Menu.MenuOptions[] = JSON.parse(JSON.stringify(menuList)); return newMenuList.filter(item => { item.children?.length && (item.children = getShowMenuList(item.children)); return !item.IsHide; });}
/** * @description 使用递归找出所有面包屑存储到 pinia/vuex 中 * @param {Array} menuList 菜单列表 * @param {Array} parent 父级菜单 * @param {Object} result 处理后的结果 * @returns {Object} */export const getAllBreadcrumbList = (menuList: Menu.MenuOptions[], parent = [], result: { [key: string]: any } = {}) => { for (const item of menuList) { result[item.path] = [...parent, item]; if (item.children) getAllBreadcrumbList(item.children, result[item.path], result); } return result;};
/** * @description 使用递归处理路由菜单 path,生成一维数组 (第一版本地路由鉴权会用到,该函数暂未使用) * @param {Array} menuList 所有菜单列表 * @param {Array} menuPathArr 菜单地址的一维数组 ['**','**'] * @returns {Array} */export function getMenuListPath(menuList: Menu.MenuOptions[], menuPathArr: string[] = []): string[] { for (const item of menuList) { if (typeof item === "object" && item.path) menuPathArr.push(item.path); if (item.children?.length) getMenuListPath(item.children, menuPathArr); } return menuPathArr;}
/** * @description 递归查询当前 path 所对应的菜单对象 (该函数暂未使用) * @param {Array} menuList 菜单列表 * @param {String} path 当前访问地址 * @returns {Object | null} */export function findMenuByPath(menuList: Menu.MenuOptions[], path: string): Menu.MenuOptions | null { for (const item of menuList) { if (item.path === path) return item; if (item.children) { const res = findMenuByPath(item.children, path); if (res) return res; } } return null;}


3、动态路由数据初始化



新建文件src\router\modules\dynamicRouter.ts,增加内容

import router from "@/router/index";import type { RouteRecordRaw } from "vue-router";import { ElNotification } from "element-plus";import { useAuthStore } from "@/stores/auth";import { useAuthMenuStore } from "@/stores/modules/authMenu";
// 引入 views 文件夹下所有 vue 文件const modules = import.meta.glob("@/views/**/*.vue");
/** * @description 初始化动态路由 */export const initDynamicRouter = async (params: Menu.MenuRequest) => { const userStore = useAuthStore(); const authStore = useAuthMenuStore();
try { // 1.获取菜单列表 && 按钮权限列表 await authStore.getAuthMenuList(params); // await authStore.getAuthButtonList();
// 2.判断当前用户有没有菜单权限 if (!authStore.authMenuListGet.length) { ElNotification({ title: "无权限访问", message: "当前账号无任何菜单权限,请联系系统管理员!", type: "warning", duration: 3000 }); userStore.setToken(""); router.replace("/login"); return Promise.reject("No permission"); }
console.log(authStore.flatMenuListGet);
// 3.添加动态路由 authStore.flatMenuListGet.forEach(item => { item.children && delete item.children; if (!item.IsButton && item.path && typeof item.path == "string" && item.path != ' ' && item.path != '-') { item.component = modules["/src/views" + item.path + ".vue"]; router.addRoute("layout", item as unknown as RouteRecordRaw); } }); } catch (error) { // 当按钮 || 菜单请求出错时,重定向到登陆页 userStore.setToken(""); router.replace("/login"); return Promise.reject(error); }};


主要就是封装一下菜单权限数据,根据当前人的ID,获取对应的菜单,进行逻辑处理,并交给状态管理器。


4、登录时候初始化路由



还是在登录逻辑里,登录完成后,获取用户信息后,根据用户id,进行初始化菜单路由数据动作:

import { initDynamicRouter } from '@/router/modules/dynamicRouter';const menuReq: Menu.MenuRequest = { uid: userInfoRes.response.uID };  // 2.添加动态路由await initDynamicRouter(menuReq);


重新登录,就能看到控制台打印的效果了,当然目前只有数据,还没有真正的和VUE进行页面数据绑定,和页面渲染,只不过数据已经准备好了,



下篇文章我们就真正的来到了VUE3+BlogCore的第一个重头戏——页面左侧菜单渲染,敬请期待。

BCVP代码创新社
专注于 NetCore 相关技术栈的推广,致力于前后端之间的完全分离,从壹开始,让每一个程序员都能从这里学有所成。
 最新文章