(系列十四)Vue3+WebApi 搭建动态菜单

打印 上一主题 下一主题

主题 827|帖子 827|积分 2481

阐明

    该文章是属于OverallAuth2.0系列文章,每周更新一篇该系列文章(从0到1完成体系开发)。
    该体系文章,我会尽量说的非常具体,做到不管新手、老手都能看懂。
    阐明:OverallAuth2.0 是一个简单、易懂、功能强大的权限+可视化流程管理体系。
友情提醒:本篇文章是属于系列文章,看该文章前,建议先看之前文章,可以更好理解项目结构。
qq群:801913255,进群有什么不懂的只管问,群主都会耐心解答。
有爱好的朋侪,请关注我吧(*^▽^*)。

关注我,学不会你来打我
问题修复
  阐明:不是跟着系列文章搭建体系的请自行忽略,往下看搭建动态菜单的过程。
  问题1:

修改代码如下
路径:framework->index.vue
  1. <el-main>
  2.           <el-affix :offset="60">
  3.             <el-tabs
  4.               v-if="tabsList.length > 0"
  5.               v-model="defaultActive"
  6.               class="demo-tabs"
  7.               @click="tabsClick(defaultActive)"
  8.               @tab-remove="tabRemoveClick"
  9.             >
  10.               <el-tab-pane
  11.                 v-for="item in tabsList"
  12.                 :label="item.name"
  13.                 :name="item.path"
  14.                 :key="item.path"
  15.                 :closable="item.path == '/panel' ? false : true"
  16.                 style="font-size: 16px"
  17.               >
  18.               </el-tab-pane>
  19.             </el-tabs>
  20.           </el-affix>
  21.           <router-view></router-view>
  22.         </el-main>
复制代码
View Code样式.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 功能级权限之一,菜单权限的紧张篇幅。
  1. routes.push(
  2.   {
  3.     path: '/framework',
  4.     component: Framework,
  5.     name: "架构",
  6.   },
  7.   {
  8.     path: '/login',
  9.     component: Login,
  10.     name: "登录页面",
  11.   },
  12.   {
  13.     path: '/panel',
  14.     redirect: '/panel/index',
  15.     meta: { title: '工作空间' },
  16.     name: "工作空间",
  17.     component: Framework,
  18.     children: [
  19.       {
  20.         path: '/panel',
  21.         name: '工作台',
  22.         component: () => import('../../views/panel/index.vue'),
  23.         meta: { title: '工作台', requireAuth: true, affix: true, closable: false },
  24.       }
  25.     ]
  26.   },
  27.   {
  28.     path: '/menu',
  29.     redirect: '/menu/index',
  30.     meta: { title: '菜单管理' },
  31.     name: "菜单管理",
  32.     component: Framework,
  33.     children: [
  34.       {
  35.         path: '/menu',
  36.         name: '菜单',
  37.         component: () => import('../../views/menu/index.vue'),
  38.         meta: { title: '菜单', requireAuth: true, affix: true, closable: false },
  39.       }
  40.     ]
  41.   },
  42.   {
  43.     path: '/user',
  44.     meta: { title: '用户管理' },
  45.     name: "用户管理",
  46.     component: Framework,
  47.     children: [
  48.       {
  49.         path: '/user',
  50.         name: '用户',
  51.         component: () => import('../../views/user/index.vue'),
  52.         meta: { title: '用户' },
  53.       }]
  54.   },
  55. )
复制代码
View Code创建数据库表
   根据菜单格式创建数据库表,并创建初始值。
  1. CREATE TABLE [dbo].[Sys_Menu](
  2.     [Id] [uniqueidentifier] NOT NULL,
  3.     [Pid] [varchar](50) NOT NULL,
  4.     [CorporationKey] [varchar](50) NOT NULL,
  5.     [SystemKey] [varchar](50) NOT NULL,
  6.     [MenuUrl] [varchar](50) NOT NULL,
  7.     [MenuIcon] [varchar](50) NULL,
  8.     [MenuTitle] [nvarchar](50) NOT NULL,
  9.     [Component] [varchar](500) NOT NULL,
  10.     [Sort] [int] NOT NULL,
  11.     [IsOpen] [bit] NOT NULL,
  12.     [CreateTime] [datetime] NOT NULL,
  13.     [CreateUser] [varchar](50) NOT NULL,
  14.     [RequireAuth] [bit] NULL,
  15.     [Redirect] [varchar](500) NULL,
  16. CONSTRAINT [PK_Menu] PRIMARY KEY CLUSTERED
  17. (
  18.     [Id] ASC
  19. )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
  20. ) ON [PRIMARY]
  21. GO
  22. 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)
  23. 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)
  24. 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)
  25. 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)
  26. 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)
  27. 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)
  28. ALTER TABLE [dbo].[Sys_Menu] ADD  CONSTRAINT [DF_Sys_Menu_Sort]  DEFAULT ((0)) FOR [Sort]
  29. GO
  30. 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'
  31. GO
  32. 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'
  33. GO
  34. 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'
  35. GO
  36. 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'
  37. GO
  38. 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'
  39. GO
  40. 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'
  41. GO
  42. 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'
  43. GO
  44. 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'
  45. GO
  46. 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'
  47. GO
  48. 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'
  49. GO
  50. 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'
  51. GO
  52. 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'
  53. GO
  54. 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'
  55. GO
  56. EXEC sys.sp_addextendedproperty @name=N'MS_Description', @value=N'菜单表' , @level0type=N'SCHEMA',@level0name=N'dbo', @level1type=N'TABLE',@level1name=N'Sys_Menu'
  57. GO
复制代码
View Code编写后端代码
  注意:底层仓储我已经搭建好,只需要实现菜单查询的业务逻辑即可,假如需要了解底层仓储的,请查看《dapper搭建底层仓储
  1:创建模子(我的创建路径:Model->DomainModel->Sys)
  1. /// <summary>
  2. /// 菜单表模型
  3. /// </summary>
  4. public class SysMenu
  5. {
  6.     /// <summary>
  7.     /// 菜单主键
  8.     /// </summary>
  9.     public Guid Id { get; set; }
  10.     /// <summary>
  11.     /// 上级菜单
  12.     /// </summary>
  13.     public string? Pid { get; set; }
  14.     /// <summary>
  15.     /// 公司key
  16.     /// </summary>
  17.     public string? CorporationKey { get; set; }
  18.     /// <summary>
  19.     /// 系统Key
  20.     /// </summary>
  21.     public string? SystemKey { get; set; }
  22.     /// <summary>
  23.     /// 菜单路径
  24.     /// </summary>
  25.     public string? MenuUrl { get; set; }
  26.     /// <summary>
  27.     /// 菜单图标
  28.     /// </summary>
  29.     public string? MenuIcon { get; set; }
  30.     /// <summary>
  31.     /// 菜单标题
  32.     /// </summary>
  33.     public string? MenuTitle { get; set; }
  34.     /// <summary>
  35.     /// 菜单模板
  36.     /// </summary>
  37.     public string? Component { get; set; }
  38.     /// <summary>
  39.     /// 是否开启
  40.     /// </summary>
  41.     public bool IsOpen { get; set; }
  42.     /// <summary>
  43.     /// 排序
  44.     /// </summary>
  45.     public int Sort { get; set; }
  46.     /// <summary>
  47.     /// 创建时间
  48.     /// </summary>
  49.     public DateTime CreateTime { get; set; }
  50.     /// <summary>
  51.     /// 创建人员
  52.     /// </summary>
  53.     public string? CreateUser { get; set; }
  54.     /// <summary>
  55.     /// 是否验证
  56.     /// </summary>
  57.     public bool RequireAuth { get; set; }
  58.     /// <summary>
  59.     /// 重定向目录
  60.     /// </summary>
  61.     public string? Redirect { get; set; }
  62. }
复制代码
View Code  2:创建菜单表的仓储(我的创建路径:Infrastructure->IRepository和Repository->Sys)
  1. /// <summary>
  2. /// 系统菜单仓储接口
  3. /// </summary>
  4. public interface ISysMenuRepository : IRepository<SysMenu>
  5. {
  6. }
复制代码
  1. /// <summary>
  2. /// 系统菜单仓储接口实现
  3. /// </summary>
  4. public class SysMenuRepository : Repository<SysMenu>, ISysMenuRepository
  5. {
  6. }
复制代码
  3:创建领域服务(我的创建路径:DomainService->IService和Service->Sys)
  1. /// <summary>
  2. /// 菜单服务接口
  3. /// </summary>
  4. public interface ISysMenuService
  5. {
  6.     /// <summary>
  7.     /// 获取树形菜单
  8.     /// </summary>
  9.     /// <returns></returns>
  10.     List<SysMenuOutPut> GetMenuTreeList();
  11. }
复制代码
  1. /// <summary>
  2. /// 菜单服务实现
  3. /// </summary>
  4. public class SysMenuService : ISysMenuService
  5. {
  6.      #region 构造实例化
  7.      /// <summary>
  8.      /// 菜单仓储接口
  9.      /// </summary>
  10.      private readonly ISysMenuRepository _menuRepository;
  11.      /// <summary>
  12.      /// 构造函数
  13.      /// </summary>
  14.      /// <param name="menuRepository"></param>
  15.      public SysMenuService(ISysMenuRepository menuRepository)
  16.      {
  17.          _menuRepository = menuRepository;
  18.      }
  19.      #endregion
  20.      #region 业务逻辑
  21.      /// <summary>
  22.      /// 获取树形菜单
  23.      /// </summary>
  24.      /// <returns></returns>
  25.      public List<SysMenuOutPut> GetMenuTreeList()
  26.      {
  27.           return new List<SysMenuOutPut>();
  28.      }
  29.      #endregion
  30. }
复制代码
  4:递归获取菜单,呈现上下级关系
  这块主要是把数据库取出的数据,转换成前端能识别的树形结构。要实现该功能,先要建立一个支持树形结构的输出模子(Model->BusinessModel->OutPut)。
  1. /// <summary>
  2. /// 菜单输出模型
  3. /// </summary>
  4. public class SysMenuOutPut
  5. {
  6.     /// <summary>
  7.     /// 菜单主键
  8.     /// </summary>
  9.     public Guid Id { get; set; }
  10.     /// <summary>
  11.     /// 上级菜单
  12.     /// </summary>
  13.     public string? Pid { get; set; }
  14.     /// <summary>
  15.     /// 公司key
  16.     /// </summary>
  17.     public string? CorporationKey { get; set; }
  18.     /// <summary>
  19.     /// 系统Key
  20.     /// </summary>
  21.     public string? SystemKey { get; set; }
  22.     /// <summary>
  23.     /// 菜单路径
  24.     /// </summary>
  25.     public string? Path { get; set; }
  26.     /// <summary>
  27.     /// 菜单图标
  28.     /// </summary>
  29.     public string? MenuIcon { get; set; }
  30.     /// <summary>
  31.     /// 菜单标题
  32.     /// </summary>
  33.     public string? Name { get; set; }
  34.     /// <summary>
  35.     /// 菜单模板
  36.     /// </summary>
  37.     public string? Component { get; set; }
  38.     /// <summary>
  39.     /// 是否开启
  40.     /// </summary>
  41.     public bool IsOpen { get; set; }
  42.     /// <summary>
  43.     /// 排序
  44.     /// </summary>
  45.     public int Sort { get; set; }
  46.     /// <summary>
  47.     /// 创建时间
  48.     /// </summary>
  49.     public DateTime CreateTime { get; set; }
  50.     /// <summary>
  51.     /// 创建人员
  52.     /// </summary>
  53.     public string? CreateUser { get; set; }
  54.     /// <summary>
  55.     /// 是否验证
  56.     /// </summary>
  57.     public bool RequireAuth { get; set; }
  58.     /// <summary>
  59.     /// 重定向目录
  60.     /// </summary>
  61.     public string? Redirect { get; set; }
  62.     /// <summary>
  63.     /// 子节点
  64.     /// </summary>
  65.     public List<SysMenuOutPut>? Children { get; set; }
  66. }
复制代码
View Code  然后我们要创建一个菜单的核心操作类,以便体系后续的使用(CoreDomain->BusinessCore)
  1. /// <summary>
  2. /// 菜单核心
  3. /// </summary>
  4. public static class MenuCore
  5. {
  6.     /// <summary>
  7.     /// 递归获取菜单,组成树形结构
  8.     /// </summary>
  9.     /// <param name="menuList">菜单数据</param>
  10.     /// <returns>返回菜单的树形结构</returns>
  11.     public static List<SysMenuOutPut> GetMenuTreeList(List<SysMenu> menuList)
  12.     {
  13.         List<SysMenuOutPut> list = new();
  14.         List<SysMenuOutPut> menuListDto = new();
  15.         //模型的转换
  16.         foreach (var item in menuList)
  17.         {
  18.             SysMenuOutPut model = new()
  19.             {
  20.                 Id = item.Id,
  21.                 Pid = item.Pid,
  22.                 CorporationKey = item.CorporationKey,
  23.                 SystemKey = item.SystemKey,
  24.                 Path = item.MenuUrl,
  25.                 Name = item.MenuTitle,
  26.                 MenuIcon = item.MenuIcon,
  27.                 Component = item.Component,
  28.                 IsOpen = item.IsOpen,
  29.                 Sort = item.Sort,
  30.                 RequireAuth = item.RequireAuth,
  31.                 Redirect = item.Redirect,
  32.                 CreateTime = item.CreateTime,
  33.                 CreateUser = item.CreateUser,
  34.             };
  35.             list.Add(model);
  36.         }
  37.         //递归所有父级菜单
  38.         foreach (var data in list.Where(f => f.Pid == "0" && f.IsOpen))
  39.         {
  40.             var childrenList = GetChildrenMenu(list, data.Id).OrderBy(f => f.Sort).ToList();
  41.             data.Children = childrenList.Count == 0 ? null : childrenList;
  42.             menuListDto.Add(data);
  43.         }
  44.         return menuListDto;
  45.     }
  46.     /// <summary>
  47.     /// 实现递归
  48.     /// </summary>
  49.     /// <param name="moduleOutput">菜单数据</param>
  50.     /// <param name="id">菜单ID</param>
  51.     /// <returns></returns>
  52.     private static List<SysMenuOutPut> GetChildrenMenu(List<SysMenuOutPut> moduleOutput, Guid id)
  53.     {
  54.         List<SysMenuOutPut> sysShowTempMenus = new();
  55.         //得到子菜单
  56.         var info = moduleOutput.Where(w => w.Pid == id.ToString() && w.IsOpen).ToList();
  57.         //循环
  58.         foreach (var sysMenuInfo in info)
  59.         {
  60.             var childrenList = GetChildrenMenu(moduleOutput, sysMenuInfo.Id);
  61.             //把子菜单放到Children集合里
  62.             sysMenuInfo.Children = childrenList.Count == 0 ? null : childrenList;
  63.             //添加父级菜单
  64.             sysShowTempMenus.Add(sysMenuInfo);
  65.         }
  66.         return sysShowTempMenus;
  67.     }
  68. }
复制代码
View Code  在领域服务中实现接口GetMenuTreeList();
  1. /// <summary>
  2. /// 获取树形菜单
  3. /// </summary>
  4. /// <returns></returns>
  5. public List<SysMenuOutPut> GetMenuTreeList()
  6. {
  7.     var menuList = _menuRepository.GetAll(BaseSqlRepository.sysMenu_selectAllSql);
  8.     var menuTreeList = MenuCore.GetMenuTreeList(menuList);
  9.     return menuTreeList;
  10. }
复制代码
注意:BaseSqlRepository.sysMenu_selectAllSql 是查询sql的语句,我放在了一个底子sql仓储中,统一管理
  5:编写接口(Controllers->Sys)
  1. /// <summary>
  2. /// 系统模块
  3. /// </summary>
  4. [ApiController]
  5. [Route("api/[controller]/[action]")]
  6. [ApiExplorerSettings(GroupName = nameof(ModeuleGroupEnum.SysMenu))]
  7. public class SysMenuController : BaseController
  8. {
  9.      #region 构造实列化
  10.      /// <summary>
  11.      /// 菜单服务服务
  12.      /// </summary>
  13.      public ISysMenuService _sysMenuService;
  14.      /// <summary>
  15.      /// 构造函数
  16.      /// </summary>
  17.      /// <param name="sysMenuService"></param>
  18.      public SysMenuController(ISysMenuService sysMenuService)
  19.      {
  20.          _sysMenuService = sysMenuService;
  21.      }
  22.      #endregion
  23.      #region 菜单接口
  24.      /// <summary>
  25.      /// 获取树形菜单
  26.      /// </summary>
  27.      /// <returns></returns>
  28.      [HttpGet]
  29.      public ReceiveStatus<SysMenuOutPut> GetMenuTreeList()
  30.      {
  31.          ReceiveStatus<SysMenuOutPut> receiveStatus = new();
  32.          var list = _sysMenuService.GetMenuTreeList();
  33.          receiveStatus.data = list;
  34.          return receiveStatus;
  35.      }
  36.      #endregion
  37. }
复制代码
View Code  6:测试接口

编写前端代码
  后端接口已经预备停当,那么接下来就要编写前端的代码,把静态的json数据转换成接口返回的动态数据。
 第一步:修改静态路由
把base-routes.ts文件中的路由换成
  1. routes.push(
  2.   {
  3.     path: '/framework',
  4.     component: frameWork,
  5.     name: "架构",
  6.   },
  7.   {
  8.     path: '/login',
  9.     component: Login,
  10.     name: "登录页面",
  11.   },
  12.   {
  13.     path: '/panel',
  14.     redirect: '/panel/index',
  15.     meta: { title: '工作空间' },
  16.     name: "工作空间",
  17.     component: frameWork,
  18.     children: [
  19.       {
  20.         path: '/panel',
  21.         name: '工作台',
  22.         component: () => import('../../views/panel/index.vue'),
  23.         meta: { title: '工作台', requireAuth: true, affix: true, closable: false },
  24.       }
  25.     ]
  26.   })
复制代码
View Code只留下固定的路由菜单,把菜单管理、用户管理等菜单去掉,我们做动态获取。
修改路由守卫如下
  1. router.beforeEach((to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
  2.   NProgress.start();
  3.   const userStore = useUserStore();
  4.   const endTime = new Date(userStore.expiresDate);
  5.   const currentTime = new Date();
  6.   to.path = to.path;
  7.   if (to.meta.requireAuth && endTime < currentTime) {
  8.     router.push('/login')
  9.   }
  10.   if (to.meta.requireAuth) {
  11.     next();
  12.   } else if (to.matched.length == 0) {
  13.     next({ path: '/panel' })
  14.   } else {
  15.     next();
  16.   }
  17. })
复制代码
这块对比前次的代码,是把next({ path: '/login' })换成了next({ path: '/panel' })。
第二步:添加接口
新建文件api->menu->index.ts
  1. import Http from '../http';
  2. export const getMenuTreeData = async function() {
  3.     return await Http.get('/api/SysMenu/GetMenuTreeList');
  4. }<br>
复制代码
该接口是上面我们编写的获取菜单接口
第三步:递归菜单,并动态添加到路由中
  在store->user.ts文件如下,添加如下代码
  该代码是把后端获取的树形菜单数据,转换成路由能认识的菜单。
  1. const defineRouteComponents: Record<string, any> = {
  2.   frameWork: () => import('@/views/frameWork/index.vue')
  3. };
  4. const defineRouteComponentKeys = Object.keys(defineRouteComponents);
  5. export const setMenuData = (
  6.   routeMap: any[],
  7. ) => {
  8.   return routeMap
  9.     .map(item => {
  10.       const pathArray = item.component.split('/');
  11.       const url = ref<any>();
  12.       if (pathArray.length > 0) {
  13.         if (pathArray.length === 3)
  14.           url.value = import(`../${pathArray[1]}/${pathArray[2]}.vue`);
  15.         if (pathArray.length === 4)
  16.           url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}.vue`);
  17.         if (pathArray.length === 5)
  18.           url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}/${pathArray[4]}.vue`);
  19.       };
  20.       const { name, requireAuth, id } = item || {};
  21.       const currentRouter: RouteRecordRaw = {
  22.         // 如果路由设置了 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace
  23.         path: item.path,
  24.         // 路由名称,建议唯一
  25.         //name: `${item.id}`,
  26.         // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
  27.         meta: {
  28.           name,
  29.           requireAuth,
  30.           id
  31.         },
  32.         name: item.name,
  33.         children: [],
  34.         // 该路由对应页面的 组件 (动态加载 @/views/ 下面的路径文件)
  35.         component: item.component && defineRouteComponentKeys.includes(item.component)
  36.           ? defineRouteComponents[item.component]
  37.           : () => url.value,
  38.       };
  39.       // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
  40.       if (!currentRouter.path.startsWith('http')) {
  41.         currentRouter.path = currentRouter.path.replace('//', '/');
  42.       }
  43.       // 重定向
  44.       item.redirect && (currentRouter.redirect = item.redirect);
  45.       if (item.children != null) {
  46.         // 子菜单,递归处理
  47.         currentRouter.children = setMenuData(item.children);
  48.       }
  49.       if (currentRouter.children === undefined || currentRouter.children.length <= 0) {
  50.         currentRouter.children;
  51.       }
  52.       return currentRouter;
  53.     })
  54.     .filter(item => item);
  55. };
复制代码
然后在defineStore的actions中添加如下方法
  1.   actions: {
  2.     //获取菜单数据,并递归实现动态路由菜单
  3.     async loadMenus() {
  4.       new Promise<any>(async (resolve, reject) => {
  5.         const { data, code, msg } = await getMenuTreeData();
  6.         if (code == 200) {
  7.           this.menus = data;
  8.           var menuList = setMenuData(data) as RouteRecordRaw[]
  9.           menuList.map(d => {
  10.             router.addRoute(d);
  11.           })
  12.           resolve(menuList);
  13.         }
  14.         else {
  15.           this.menus = [];
  16.           ElMessage({
  17.             message: msg,
  18.             type: "error",
  19.           });
  20.         }
  21.       });
  22.     },
  23.   },
复制代码
user.ts完备代码如下(动态路由菜单的核心)
  1. 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: {
  2.     //获取菜单数据,并递归实现动态路由菜单
  3.     async loadMenus() {
  4.       new Promise<any>(async (resolve, reject) => {
  5.         const { data, code, msg } = await getMenuTreeData();
  6.         if (code == 200) {
  7.           this.menus = data;
  8.           var menuList = setMenuData(data) as RouteRecordRaw[]
  9.           menuList.map(d => {
  10.             router.addRoute(d);
  11.           })
  12.           resolve(menuList);
  13.         }
  14.         else {
  15.           this.menus = [];
  16.           ElMessage({
  17.             message: msg,
  18.             type: "error",
  19.           });
  20.         }
  21.       });
  22.     },
  23.   },  persist: {    enabled: true,    strategies: [      {        // 可以是localStorage或sessionStorage        storage: localStorage,        // 指定需要持久化的属性        paths: ['token', 'expiresDate', 'userInfo', 'menus']      }    ]  },})const defineRouteComponents: Record<string, any> = {
  24.   frameWork: () => import('@/views/frameWork/index.vue')
  25. };
  26. const defineRouteComponentKeys = Object.keys(defineRouteComponents);
  27. export const setMenuData = (
  28.   routeMap: any[],
  29. ) => {
  30.   return routeMap
  31.     .map(item => {
  32.       const pathArray = item.component.split('/');
  33.       const url = ref<any>();
  34.       if (pathArray.length > 0) {
  35.         if (pathArray.length === 3)
  36.           url.value = import(`../${pathArray[1]}/${pathArray[2]}.vue`);
  37.         if (pathArray.length === 4)
  38.           url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}.vue`);
  39.         if (pathArray.length === 5)
  40.           url.value = import(`../${pathArray[1]}/${pathArray[2]}/${pathArray[3]}/${pathArray[4]}.vue`);
  41.       };
  42.       const { name, requireAuth, id } = item || {};
  43.       const currentRouter: RouteRecordRaw = {
  44.         // 如果路由设置了 path,则作为默认 path,否则 路由地址 动态拼接生成如 /dashboard/workplace
  45.         path: item.path,
  46.         // 路由名称,建议唯一
  47.         //name: `${item.id}`,
  48.         // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
  49.         meta: {
  50.           name,
  51.           requireAuth,
  52.           id
  53.         },
  54.         name: item.name,
  55.         children: [],
  56.         // 该路由对应页面的 组件 (动态加载 @/views/ 下面的路径文件)
  57.         component: item.component && defineRouteComponentKeys.includes(item.component)
  58.           ? defineRouteComponents[item.component]
  59.           : () => url.value,
  60.       };
  61.       // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
  62.       if (!currentRouter.path.startsWith('http')) {
  63.         currentRouter.path = currentRouter.path.replace('//', '/');
  64.       }
  65.       // 重定向
  66.       item.redirect && (currentRouter.redirect = item.redirect);
  67.       if (item.children != null) {
  68.         // 子菜单,递归处理
  69.         currentRouter.children = setMenuData(item.children);
  70.       }
  71.       if (currentRouter.children === undefined || currentRouter.children.length <= 0) {
  72.         currentRouter.children;
  73.       }
  74.       return currentRouter;
  75.     })
  76.     .filter(item => item);
  77. };
复制代码
View Code编写完以上代码,我们已经获取到后端的菜单,并且已添加到动态路由中。接下来只需要在登录时,调用loadMenus()方法即可。
第四步:登录后获取动态路由菜单
如图

第五步:传入token
  当你辛劳完成以上步骤后,你迫不及待的想查看下效果。但是体系给你泼了一盆冷水,提示接口401错误。
没错,会出现错误,因为我们体系使用了jwt鉴权,所以我们需要把token传给后端,然后举行验证。只有通过后才气访问接口。
那么要怎样做才气把token传给后端呢。
在我们之前写好的请求拦截中,参加如下代码
ps:不清楚请求拦截的,请观看(系列十一)Vue3框架中路由守卫及请求拦截(实现前后端交互)
  1. /* 请求拦截 */
  2.         this.service.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  3.             //可以在这里做请求拦截处理   如:请求接口前,需要传入的token
  4.             const userInfoStore = useUserStore();
  5.             if (userInfoStore.token) {
  6.                 (config.headers as AxiosRequestHeaders).token = userInfoStore.token as string
  7.                 config.headers["Authorization"] = "Bearer " + userInfoStore.token;
  8.             } else {
  9.                 if (router.currentRoute.value.path !== '/login') {
  10.                     router.push('/login');
  11.                 }
  12.             }
  13.             return config
  14.         }, (error: any) => {
  15.             ElMessage({
  16.                 message: "接口调用失败",
  17.                 type: "error",
  18.             });
  19.             return error.message;
  20.             //return Promise.reject(error);
  21.         })
复制代码
然后运行项目,你会发现,你的菜单实现了动态路由,全部由数据库获取。
以上就是本篇文章的全部内容,感谢耐心观看
后端WebApi 预览地址:http://139.155.137.144:8880/swagger/index.html
前端vue 预览地址:http://139.155.137.144:8881
关注公众号:发送【权限】,获取前后端代码
有爱好的朋侪,请关注我微信公众号吧(*^▽^*)。

关注我:一个全栈多端的宝藏博主,定时分享技能文章,不定时分享开源项目。关注我,带你认识不一样的程序世界

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

灌篮少年

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表