說明
該文章是屬於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()方法即可。
第四步:登入後獲取動態路由選單
如圖
第五步:傳入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
關注公眾號:傳送【許可權】,獲取前後端程式碼
有興趣的朋友,請關注我微信公眾號吧(*^▽^*)。
關注我:一個全棧多端的寶藏博主,定時分享技術文章,不定時分享開源專案。關注我,帶你認識不一樣的程式世界