(系列十四)Vue3+WebApi 搭建動態菜單
說明
該文章是屬于OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。
該系統文章,我會盡量說的非常詳細,做到不管新手、老手都能看懂。
說明:OverallAuth2.0 是一個簡單、易懂、功能強大的權限+可視化流程管理系統。
友情提醒:本篇文章是屬于系列文章,看該文章前,建議先看之前文章,可以更好理解項目結構。
qq群:801913255,進群有什么不懂的盡管問,群主都會耐心解答。
有興趣的朋友,請關注我吧(*^▽^*)。

關注我,學不會你來打我
問題修復
說明:不是跟著系列文章搭建系統的請自行忽略,往下看搭建動態菜單的過程。
問題1:

修改代碼如下
路徑:framework->index.vue
<el-main> <el-affix :offset="60"> <el-tabs v-if="tabsList.length > 0" v-model="defaultActive" class="demo-tabs" @click="tabsClick(defaultActive)" @tab-remove="tabRemoveClick" > <el-tab-pane v-for="item in tabsList" :label="item.name" :name="item.path" :key="item.path" :closable="item.path == '/panel' ? false : true" style="font-size: 16px" > </el-tab-pane> </el-tabs> </el-affix> <router-view></router-view> </el-main>
樣式.demo-tabs中加入白色背景樣式:background-color: white;
問題2:
中國地圖中,波紋會隨著各省的數值變大而變大

路徑:echarts.ts
把value: chinaGeoCoordMap[dataItem[0].name].concat([dataItem[0].value])修改成value: chinaGeoCoordMap[dataItem[0].name].concat(0)
實現功能
把以下菜單換成動態菜單
不是跟著系列走的朋友,可直接觀看動態路由菜單的關鍵代碼 ,在后面的第三步中!!!
注意:該篇文章是實現OverallAuth2.0 功能級權限之一,菜單權限的重要篇幅。
routes.push( { path: '/framework', component: Framework, name: "架構", }, { path: '/login', component: Login, name: "登錄頁面", }, { path: '/panel', redirect: '/panel/index', meta: { title: '工作空間' }, name: "工作空間", component: Framework, children: [ { path: '/panel', name: '工作臺', component: () => import('../../views/panel/index.vue'), meta: { title: '工作臺', requireAuth: true, affix: true, closable: false }, } ] }, { path: '/menu', redirect: '/menu/index', meta: { title: '菜單管理' }, name: "菜單管理", component: Framework, children: [ { path: '/menu', name: '菜單', component: () => import('../../views/menu/index.vue'), meta: { title: '菜單', requireAuth: true, affix: true, closable: false }, } ] }, { path: '/user', meta: { title: '用戶管理' }, name: "用戶管理", component: Framework, children: [ { path: '/user', name: '用戶', component: () => import('../../views/user/index.vue'), meta: { title: '用戶' }, }] }, )
創建數據庫表
根據菜單格式創建數據庫表,并創建初始值。
CREATE TABLE [dbo].[Sys_Menu]( [Id] [uniqueidentifier] NOT NULL, [Pid] [varchar](50) NOT NULL, [CorporationKey] [varchar](50) NOT NULL, [SystemKey] [varchar](50) NOT NULL, [MenuUrl] [varchar](50) NOT NULL, [MenuIcon] [varchar](50) NULL, [MenuTitle] [nvarchar](50) NOT NULL, [Component] [varchar](500) NOT NULL, [Sort] [int] NOT NULL, [IsOpen] [bit] NOT NULL, [CreateTime] [datetime] NOT NULL, [CreateUser] [varchar](50) NOT NULL, [RequireAuth] [bit] NULL, [Redirect] [varchar](500) NULL, CONSTRAINT [PK_Menu] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO INSERT [dbo].[Sys_Menu] ([Id], [Pid], [CorporationKey], [SystemKey], [MenuUrl], [MenuIcon], [MenuTitle], [Component], [Sort], [IsOpen], [CreateTime], [CreateUser], [RequireAuth], [Redirect]) VALUES (N'380ca40b-8b62-4ebe-86d7-91ae48292f43', N'0', N'6a75ec49-2093-4b89-950f-65e6e72746da', N'6e746ed0-12e9-4002-9887-d84a19142304', N'/panel', N'layui-icon-engine', N'工作空間', N'frameWork', 1, 1, CAST(N'2024-12-05T00:00:00.000' AS DateTime), N'1', 1, NULL) INSERT [dbo].[Sys_Menu] ([Id], [Pid], [CorporationKey], [SystemKey], [MenuUrl], [MenuIcon], [MenuTitle], [Component], [Sort], [IsOpen], [CreateTime], [CreateUser], [RequireAuth], [Redirect]) VALUES (N'380ca40b-8b62-4ebe-86d7-91ae48292f44', N'380ca40b-8b62-4ebe-86d7-91ae48292f43', N'6a75ec49-2093-4b89-950f-65e6e72746da', N'6e746ed0-12e9-4002-9887-d84a19142304', N'/panel', N'layui-icon-engine', N'工作臺', N'../views/panel/index', 1, 1, CAST(N'2024-12-05T00:00:00.000' AS DateTime), N'1', 1, NULL) INSERT [dbo].[Sys_Menu] ([Id], [Pid], [CorporationKey], [SystemKey], [MenuUrl], [MenuIcon], [MenuTitle], [Component], [Sort], [IsOpen], [CreateTime], [CreateUser], [RequireAuth], [Redirect]) VALUES (N'380ca40b-8b62-4ebe-86d7-91ae48292f45', N'0', N'6a75ec49-2093-4b89-950f-65e6e72746da', N'6e746ed0-12e9-4002-9887-d84a19142304', N'/menu', N'layui-icon-engine', N'菜單管理', N'frameWork', 1, 1, CAST(N'2024-12-05T00:00:00.000' AS DateTime), N'1', 1, NULL) INSERT [dbo].[Sys_Menu] ([Id], [Pid], [CorporationKey], [SystemKey], [MenuUrl], [MenuIcon], [MenuTitle], [Component], [Sort], [IsOpen], [CreateTime], [CreateUser], [RequireAuth], [Redirect]) VALUES (N'380ca40b-8b62-4ebe-86d7-91ae48292f46', N'380ca40b-8b62-4ebe-86d7-91ae48292f45', N'6a75ec49-2093-4b89-950f-65e6e72746da', N'6e746ed0-12e9-4002-9887-d84a19142304', N'/menu', N'layui-icon-engine', N'菜單', N'../views/menu/index', 1, 1, CAST(N'2024-12-05T00:00:00.000' AS DateTime), N'1', 1, NULL) INSERT [dbo].[Sys_Menu] ([Id], [Pid], [CorporationKey], [SystemKey], [MenuUrl], [MenuIcon], [MenuTitle], [Component], [Sort], [IsOpen], [CreateTime], [CreateUser], [RequireAuth], [Redirect]) VALUES (N'380ca40b-8b62-4ebe-86d7-91ae48292f47', N'0', N'6a75ec49-2093-4b89-950f-65e6e72746da', N'6e746ed0-12e9-4002-9887-d84a19142304', N'/user', N'layui-icon-engine', N'用戶管理', N'frameWork', 1, 1, CAST(N'2024-12-05T00:00:00.000' AS DateTime), N'1', 1, NULL) INSERT [dbo].[Sys_Menu] ([Id], [Pid], [CorporationKey], [SystemKey], [MenuUrl], [MenuIcon], [MenuTitle], [Component], [Sort], [IsOpen], [CreateTime], [CreateUser], [RequireAuth], [Redirect]) VALUES (N'380ca40b-8b62-4ebe-86d7-91ae48292f48', N'380ca40b-8b62-4ebe-86d7-91ae48292f47', N'6a75ec49-2093-4b89-950f-65e6e72746da', N'6e746ed0-12e9-4002-9887-d84a19142304', N'/user', N'layui-icon-engine', N'用戶', N'../views/user/index', 1, 1, CAST(N'2024-12-05T00:00:00.000' AS DateTime), N'1', 1, NULL) ALTER TABLE [dbo].[Sys_Menu] ADD CONSTRAINT [DF_Sys_Menu_Sort] DEFAULT ((0)) FOR [Sort] GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜單id' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'Id' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'父級id' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'Pid' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'公司Key' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'CorporationKey' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'系統Key' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'SystemKey' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜單路徑' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'MenuUrl' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜單圖標' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'MenuIcon' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜單標題' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'MenuTitle' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'排序' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'Sort' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'是否開啟菜單' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'IsOpen' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'創建時間' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'CreateTime' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'創建人員' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'CreateUser' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'是否驗證' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'RequireAuth' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'重定向' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu', @level2type=N'COLUMN',@level2name=N'Redirect' GO EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜單表' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu' GO
編寫后端代碼
注意:底層倉儲我已經搭建好,只需要實現菜單查詢的業務邏輯即可,如果需要了解底層倉儲的,請查看《dapper搭建底層倉儲》
1:創建模型(我的創建路徑:Model->DomainModel->Sys)
/// <summary> /// 菜單表模型 /// </summary> public class SysMenu { /// <summary> /// 菜單主鍵 /// </summary> public Guid Id { get; set; } /// <summary> /// 上級菜單 /// </summary> public string? Pid { get; set; } /// <summary> /// 公司key /// </summary> public string? CorporationKey { get; set; } /// <summary> /// 系統Key /// </summary> public string? SystemKey { get; set; } /// <summary> /// 菜單路徑 /// </summary> public string? MenuUrl { get; set; } /// <summary> /// 菜單圖標 /// </summary> public string? MenuIcon { get; set; } /// <summary> /// 菜單標題 /// </summary> public string? MenuTitle { get; set; } /// <summary> /// 菜單模板 /// </summary> public string? Component { get; set; } /// <summary> /// 是否開啟 /// </summary> public bool IsOpen { get; set; } /// <summary> /// 排序 /// </summary> public int Sort { get; set; } /// <summary> /// 創建時間 /// </summary> public DateTime CreateTime { get; set; } /// <summary> /// 創建人員 /// </summary> public string? CreateUser { get; set; } /// <summary> /// 是否驗證 /// </summary> public bool RequireAuth { get; set; } /// <summary> /// 重定向目錄 /// </summary> public string? Redirect { get; set; } }
2:創建菜單表的倉儲(我的創建路徑:Infrastructure->IRepository和Repository->Sys)
/// <summary> /// 系統菜單倉儲接口 /// </summary> public interface ISysMenuRepository : IRepository<SysMenu> { }
/// <summary> /// 系統菜單倉儲接口實現 /// </summary> public class SysMenuRepository : Repository<SysMenu>, ISysMenuRepository { }
3:創建領域服務(我的創建路徑:DomainService->IService和Service->Sys)
/// <summary> /// 菜單服務接口 /// </summary> public interface ISysMenuService { /// <summary> /// 獲取樹形菜單 /// </summary> /// <returns></returns> List<SysMenuOutPut> GetMenuTreeList(); }
/// <summary> /// 菜單服務實現 /// </summary> public class SysMenuService : ISysMenuService { #region 構造實例化 /// <summary> /// 菜單倉儲接口 /// </summary> private readonly ISysMenuRepository _menuRepository; /// <summary> /// 構造函數 /// </summary> /// <param name="menuRepository"></param> public SysMenuService(ISysMenuRepository menuRepository) { _menuRepository = menuRepository; } #endregion #region 業務邏輯 /// <summary> /// 獲取樹形菜單 /// </summary> /// <returns></returns> public List<SysMenuOutPut> GetMenuTreeList() { return new List<SysMenuOutPut>(); } #endregion }
4:遞歸獲取菜單,呈現上下級關系
這塊主要是把數據庫取出的數據,轉換成前端能識別的樹形結構。要實現該功能,先要建立一個支持樹形結構的輸出模型(Model->BusinessModel->OutPut)。
/// <summary> /// 菜單輸出模型 /// </summary> public class SysMenuOutPut { /// <summary> /// 菜單主鍵 /// </summary> public Guid Id { get; set; } /// <summary> /// 上級菜單 /// </summary> public string? Pid { get; set; } /// <summary> /// 公司key /// </summary> public string? CorporationKey { get; set; } /// <summary> /// 系統Key /// </summary> public string? SystemKey { get; set; } /// <summary> /// 菜單路徑 /// </summary> public string? Path { get; set; } /// <summary> /// 菜單圖標 /// </summary> public string? MenuIcon { get; set; } /// <summary> /// 菜單標題 /// </summary> public string? Name { get; set; } /// <summary> /// 菜單模板 /// </summary> public string? Component { get; set; } /// <summary> /// 是否開啟 /// </summary> public bool IsOpen { get; set; } /// <summary> /// 排序 /// </summary> public int Sort { get; set; } /// <summary> /// 創建時間 /// </summary> public DateTime CreateTime { get; set; } /// <summary> /// 創建人員 /// </summary> public string? CreateUser { get; set; } /// <summary> /// 是否驗證 /// </summary> public bool RequireAuth { get; set; } /// <summary> /// 重定向目錄 /// </summary> public string? Redirect { get; set; } /// <summary> /// 子節點 /// </summary> public List<SysMenuOutPut>? Children { get; set; } }
然后我們要創建一個菜單的核心操作類,以便系統后續的使用(CoreDomain->BusinessCore)
/// <summary> /// 菜單核心 /// </summary> public static class MenuCore { /// <summary> /// 遞歸獲取菜單,組成樹形結構 /// </summary> /// <param name="menuList">菜單數據</param> /// <returns>返回菜單的樹形結構</returns> public static List<SysMenuOutPut> GetMenuTreeList(List<SysMenu> menuList) { List<SysMenuOutPut> list = new(); List<SysMenuOutPut> menuListDto = new(); //模型的轉換 foreach (var item in menuList) { SysMenuOutPut model = new() { Id = item.Id, Pid = item.Pid, CorporationKey = item.CorporationKey, SystemKey = item.SystemKey, Path = item.MenuUrl, Name = item.MenuTitle, MenuIcon = item.MenuIcon, Component = item.Component, IsOpen = item.IsOpen, Sort = item.Sort, RequireAuth = item.RequireAuth, Redirect = item.Redirect, CreateTime = item.CreateTime, CreateUser = item.CreateUser, }; list.Add(model); } //遞歸所有父級菜單 foreach (var data in list.Where(f => f.Pid == "0" && f.IsOpen)) { var childrenList = GetChildrenMenu(list, data.Id).OrderBy(f => f.Sort).ToList(); data.Children = childrenList.Count == 0 ? null : childrenList; menuListDto.Add(data); } return menuListDto; } /// <summary> /// 實現遞歸 /// </summary> /// <param name="moduleOutput">菜單數據</param> /// <param name="id">菜單ID</param> /// <returns></returns> private static List<SysMenuOutPut> GetChildrenMenu(List<SysMenuOutPut> moduleOutput, Guid id) { List<SysMenuOutPut> sysShowTempMenus = new(); //得到子菜單 var info = moduleOutput.Where(w => w.Pid == id.ToString() && w.IsOpen).ToList(); //循環 foreach (var sysMenuInfo in info) { var childrenList = GetChildrenMenu(moduleOutput, sysMenuInfo.Id); //把子菜單放到Children集合里 sysMenuInfo.Children = childrenList.Count == 0 ? null : childrenList; //添加父級菜單 sysShowTempMenus.Add(sysMenuInfo); } return sysShowTempMenus; } }
在領域服務中實現接口GetMenuTreeList();
/// <summary> /// 獲取樹形菜單 /// </summary> /// <returns></returns> public List<SysMenuOutPut> GetMenuTreeList() { var menuList = _menuRepository.GetAll(BaseSqlRepository.sysMenu_selectAllSql); var menuTreeList = MenuCore.GetMenuTreeList(menuList); return menuTreeList; }
注意:BaseSqlRepository.sysMenu_selectAllSql 是查詢sql的語句,我放在了一個基礎sql倉儲中,統一管理
5:編寫接口(Controllers->Sys)
/// <summary> /// 系統模塊 /// </summary> [ApiController] [Route("api/[controller]/[action]")] [ApiExplorerSettings(GroupName = nameof(ModeuleGroupEnum.SysMenu))] public class SysMenuController : BaseController { #region 構造實列化 /// <summary> /// 菜單服務服務 /// </summary> public ISysMenuService _sysMenuService; /// <summary> /// 構造函數 /// </summary> /// <param name="sysMenuService"></param> public SysMenuController(ISysMenuService sysMenuService) { _sysMenuService = sysMenuService; } #endregion #region 菜單接口 /// <summary> /// 獲取樹形菜單 /// </summary> /// <returns></returns> [HttpGet] public ReceiveStatus<SysMenuOutPut> GetMenuTreeList() { ReceiveStatus<SysMenuOutPut> receiveStatus = new(); var list = _sysMenuService.GetMenuTreeList(); receiveStatus.data = list; return receiveStatus; } #endregion }
6:測試接口

編寫前端代碼
后端接口已經準備就緒,那么接下來就要編寫前端的代碼,把靜態的json數據轉換成接口返回的動態數據。
第一步:修改靜態路由
把base-routes.ts文件中的路由換成
routes.push( { path: '/framework', component: frameWork, name: "架構", }, { path: '/login', component: Login, name: "登錄頁面", }, { path: '/panel', redirect: '/panel/index', meta: { title: '工作空間' }, name: "工作空間", component: frameWork, children: [ { path: '/panel', name: '工作臺', component: () => import('../../views/panel/index.vue'), meta: { title: '工作臺', requireAuth: true, affix: true, closable: false }, } ] })
只留下固定的路由菜單,把菜單管理、用戶管理等菜單去掉,我們做動態獲取。
修改路由守衛如下
router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { NProgress.start(); const userStore = useUserStore(); const endTime = new Date(userStore.expiresDate); const currentTime = new Date(); to.path = to.path; if (to.meta.requireAuth && endTime < currentTime) { router.push('/login') } if (to.meta.requireAuth) { next(); } else if (to.matched.length == 0) { next({ path: '/panel' }) } else { next(); } })
這塊對比上次的代碼,是把next({ path: '/login' })換成了next({ path: '/panel' })。
第二步:添加接口
新建文件api->menu->index.ts
import Http from '../http'; export const getMenuTreeData = async function() { return await Http.get('/api/SysMenu/GetMenuTreeList'); }
該接口是上面我們編寫的獲取菜單接口
第三步:遞歸菜單,并動態添加到路由中
在store->user.ts文件如下,添加如下代碼
該代碼是把后端獲取的樹形菜單數據,轉換成路由能認識的菜單。
const defineRouteComponents: Record<string, any> = { frameWork: () => import('@/views/frameWork/index.vue') }; const defineRouteComponentKeys = Object.keys(defineRouteComponents); export const setMenuData = ( routeMap: any[], ) => { return routeMap .map(item => { const pathArray = item.component.split('/'); const url = ref<any>(); if (pathArray.length > 0) { if (pathArray.length === 3) url.value = import(`../${pathArray[1]}/${pathArray[2]}.vue`); if (pathArray.length === 4) url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}.vue`); if (pathArray.length === 5) url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}/${pathArray[4]}.vue`); }; const { name, requireAuth, id } = item || {}; const currentRouter: RouteRecordRaw = { // 如果路由設置了 path,則作為默認 path,否則 路由地址 動態拼接生成如 /dashboard/workplace path: item.path, // 路由名稱,建議唯一 //name: `${item.id}`, // meta: 頁面標題, 菜單圖標, 頁面權限(供指令權限用,可去掉) meta: { name, requireAuth, id }, name: item.name, children: [], // 該路由對應頁面的 組件 (動態加載 @/views/ 下面的路徑文件) component: item.component && defineRouteComponentKeys.includes(item.component) ? defineRouteComponents[item.component] : () => url.value, }; // 為了防止出現后端返回結果不規范,處理有可能出現拼接出兩個 反斜杠 if (!currentRouter.path.startsWith('http')) { currentRouter.path = currentRouter.path.replace('//', '/'); } // 重定向 item.redirect && (currentRouter.redirect = item.redirect); if (item.children != null) { // 子菜單,遞歸處理 currentRouter.children = setMenuData(item.children); } if (currentRouter.children === undefined || currentRouter.children.length <= 0) { currentRouter.children; } return currentRouter; }) .filter(item => item); };
然后在defineStore的actions中添加如下方法
actions: { //獲取菜單數據,并遞歸實現動態路由菜單 async loadMenus() { new Promise<any>(async (resolve, reject) => { const { data, code, msg } = await getMenuTreeData(); if (code == 200) { this.menus = data; var menuList = setMenuData(data) as RouteRecordRaw[] menuList.map(d => { router.addRoute(d); }) resolve(menuList); } else { this.menus = []; ElMessage({ message: msg, type: "error", }); } }); }, },
user.ts完整代碼如下(動態路由菜單的核心)
import { getMenuTreeData } from '@/api/menu';
import router from '@/router';
import { ElMessage } from 'element-plus';
import { defineStore } from 'pinia'
import { ref } from 'vue';
import { RouteRecordRaw } from 'vue-router';
export const useUserStore = defineStore(
'user', {
state: () => ({
token: '',
expiresDate: '',
userInfo: {},
menus: [] as any,
}),
actions: {
//獲取菜單數據,并遞歸實現動態路由菜單
async loadMenus() {
new Promise<any>(async (resolve, reject) => {
const { data, code, msg } = await getMenuTreeData();
if (code == 200) {
this.menus = data;
var menuList = setMenuData(data) as RouteRecordRaw[]
menuList.map(d => {
router.addRoute(d);
})
resolve(menuList);
}
else {
this.menus = [];
ElMessage({
message: msg,
type: "error",
});
}
});
},
},
persist: {
enabled: true,
strategies: [
{
// 可以是localStorage或sessionStorage
storage: localStorage,
// 指定需要持久化的屬性
paths: ['token', 'expiresDate', 'userInfo', 'menus']
}
]
},
})
const defineRouteComponents: Record<string, any> = {
frameWork: () => import('@/views/frameWork/index.vue')
};
const defineRouteComponentKeys = Object.keys(defineRouteComponents);
export const setMenuData = (
routeMap: any[],
) => {
return routeMap
.map(item => {
const pathArray = item.component.split('/');
const url = ref<any>();
if (pathArray.length > 0) {
if (pathArray.length === 3)
url.value = import(`../${pathArray[1]}/${pathArray[2]}.vue`);
if (pathArray.length === 4)
url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}.vue`);
if (pathArray.length === 5)
url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}/${pathArray[4]}.vue`);
};
const { name, requireAuth, id } = item || {};
const currentRouter: RouteRecordRaw = {
// 如果路由設置了 path,則作為默認 path,否則 路由地址 動態拼接生成如 /dashboard/workplace
path: item.path,
// 路由名稱,建議唯一
//name: `${item.id}`,
// meta: 頁面標題, 菜單圖標, 頁面權限(供指令權限用,可去掉)
meta: {
name,
requireAuth,
id
},
name: item.name,
children: [],
// 該路由對應頁面的 組件 (動態加載 @/views/ 下面的路徑文件)
component: item.component && defineRouteComponentKeys.includes(item.component)
? defineRouteComponents[item.component]
: () => url.value,
};
// 為了防止出現后端返回結果不規范,處理有可能出現拼接出兩個 反斜杠
if (!currentRouter.path.startsWith('http')) {
currentRouter.path = currentRouter.path.replace('//', '/');
}
// 重定向
item.redirect && (currentRouter.redirect = item.redirect);
if (item.children != null) {
// 子菜單,遞歸處理
currentRouter.children = setMenuData(item.children);
}
if (currentRouter.children === undefined || currentRouter.children.length <= 0) {
currentRouter.children;
}
return currentRouter;
})
.filter(item => item);
};
編寫完以上代碼,我們已經獲取到后端的菜單,并且已添加到動態路由中。接下來只需要在登錄時,調用loadMenus()方法即可。
第四步:登錄后獲取動態路由菜單
如圖

注意:替換frameWork文件夾下index.vue 文件中的代碼片段,如下圖

第五步:傳入token
當你辛苦完成以上步驟后,你迫不及待的想查看下效果。但是系統給你潑了一盆冷水,提示接口401錯誤。
沒錯,會出現錯誤,因為我們系統使用了jwt鑒權,所以我們需要把token傳給后端,然后進行驗證。只有通過后才能訪問接口。
那么要如何做才能把token傳給后端呢。
在我們之前寫好的請求攔截中,加入如下代碼
ps:不清楚請求攔截的,請觀看(系列十一)Vue3框架中路由守衛及請求攔截(實現前后端交互)
/* 請求攔截 */ this.service.interceptors.request.use((config: InternalAxiosRequestConfig) => { //可以在這里做請求攔截處理 如:請求接口前,需要傳入的token const userInfoStore = useUserStore(); if (userInfoStore.token) { (config.headers as AxiosRequestHeaders).token = userInfoStore.token as string config.headers["Authorization"] = "Bearer " + userInfoStore.token; } else { if (router.currentRoute.value.path !== '/login') { router.push('/login'); } } return config }, (error: any) => { ElMessage({ message: "接口調用失敗", type: "error", }); return error.message; //return Promise.reject(error); })
然后運行項目,你會發現,你的菜單實現了動態路由,全部由數據庫獲取。
以上就是本篇文章的全部內容,感謝耐心觀看
后端WebApi 預覽地址:http://139.155.137.144:8880/swagger/index.html
前端vue 預覽地址:http://139.155.137.144:8881
關注公眾號:發送【權限】,獲取前后端代碼
有興趣的朋友,請關注我微信公眾號吧(*^▽^*)。

關注我:一個全棧多端的寶藏博主,定時分享技術文章,不定時分享開源項目。關注我,帶你認識不一樣的程序世界

浙公網安備 33010602011771號