说明

该文章是属于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

关注公众号:发送【权限】,获取前后端代码

有兴趣的朋友,请关注我微信公众号吧(*^▽^*)。

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

(系列十四)Vue3+WebApi 搭建动态菜单的更多相关文章

  1. struts2官方 中文教程 系列十四:主题Theme

    介绍 当您使用一个Struts 2标签时,例如 <s:select ..../>  在您的web页面中,Struts 2框架会生成HTML,它会显示外观并控制select控件的布局.样式和 ...

  2. 学习ASP.NET Core Razor 编程系列十四——文件上传功能(二)

    学习ASP.NET Core Razor 编程系列目录 学习ASP.NET Core Razor 编程系列一 学习ASP.NET Core Razor 编程系列二——添加一个实体 学习ASP.NET ...

  3. 学习ASP.NET Core Blazor编程系列十四——修改

    学习ASP.NET Core Blazor编程系列一--综述 学习ASP.NET Core Blazor编程系列二--第一个Blazor应用程序(上) 学习ASP.NET Core Blazor编程系 ...

  4. 闯祸了,生成环境执行了DDL操作《死磕MySQL系列 十四》

    由于业务随着时间不停的改变,起初的表结构设计已经满足不了如今的需求,这时你是不是想那就加字段呗!加字段也是个艺术活,接下来由本文的主人咔咔给你吹. 试想一下这个场景 事务A在执行一个非常大的查询 事务 ...

  5. MP实战系列(十四)之分页使用

    MyBatis Plus的分页,有插件式的,也有其自带了,插件需要配置,说麻烦也不是特别麻烦,不过觉得现有的MyBatis Plus足以解决,就懒得配置插件了. MyBatis Plus的资料不算是太 ...

  6. 4.14Python数据处理篇之Matplotlib系列(十四)---动态图的绘制

    目录 目录 前言 (一)需求分析 (二)随机数的动态图 1.思路分析: 2.源代码: 2.输出效果: 目录 前言 学习matplotlib已经到了尾声,没有必要再继续深究下去了,现今只是学了一些基础的 ...

  7. 【Qt编程】基于Qt的词典开发系列<十四>自动补全功能

    最近写了一个查单词的类似有道词典的软件,里面就有一个自动补全功能(即当你输入一个字母时,就会出现几个候选项).这个自动补全功能十分常见,百度搜索关键词时就会出现.不过它们这些补全功能都是与你输入的进行 ...

  8. vagrant系列教程(四):vagrant搭建redis与redis的监控程序redis-stat(转)

    上一篇php7环境的搭建 真是火爆,仅仅两天时间,就破了我之前swagger系列的一片文章,看来,大家对搭建环境真是情有独钟. 为了访问量,我今天再来一篇Redis的搭建.当然不能仅仅是redis的搭 ...

  9. shiro实战系列(十四)之配置

    Shiro 被设计成能够在任何环境下工作,从最简单的命令行应用程序到最大的的企业群集应用.由于环境的多样性,使得许多配置机制适用于它的配置. 一. 许多配置选项 Shiro的SecurityManag ...

  10. kubernetes系列(十四) - 存储之PersistentVolume

    1. PersistentVolume(PV)简介 1.1 为什么需要Persistent Volume(PV) 1.2 PersistentVolume(PV)和Volume的区别 1.3 PV和P ...

随机推荐

  1. Fluent Builder 模式

    前言 以前最讨厌设计复杂方法调用, 就是那种需要一堆有逻辑规则的 config 作为参数的方法. 这种 config 通常是一个大对象, 有许多 property, property 之间有存在一些逻 ...

  2. Azure 入门系列 (第一篇 Virtual Machine 和 SQL Server)

    前言 终于有机会自己搞 Azure 了. 以前都是找代理做,近期代理 support 越来越弱了. 看来是 right time 自己搞了. 以前写过一些关于 Azure 的笔记: Azure key ...

  3. .NET 8 + Vue/UniApp 高性能前后端分离框架

    前言 作为一名开发者,我们知道能够简化开发流程.提升工作效率的工具是至关重要的. 推荐一款前后端分离框架 Admin.NET(ZRAdmin),它不仅可以满足项目开发的需求,还应用了一些新的特性,如R ...

  4. 如何理解iowait

    Linux中,%iowait 过高可能是个问题,严重的时候,它能使服务停止, 但问题是,多高才算高? 什么时候应该担心呢? 本文将讨论 iowait 的含义.相关的统计数据.原理以及 iowait的瓶 ...

  5. Java实用小工具系列1---使用StringUtils分割字符串

    经常有这种情况,需要将逗号分割的字符串,比如:aaa, bbb ,ccc,但往往是人工输入的,难免会有多空格逗号情况,比如:aaa, bbb , ccc, ,,这种情况使用split会解析出不正常的结 ...

  6. 密码学承诺之原理和应用 - Kate多项式承诺

    主页 微信公众号:密码应用技术实战 博客园首页:https://www.cnblogs.com/informatics/ GIT地址:https://github.com/warm3snow 简介 多 ...

  7. python 打包 py 文件 为exe

    使用 pyinstaller 来进行打包 pip install pyinstaller 可能需要全局 科学 代理上网 或者 修改 下载源地址 执行命令 图标path:C:\desktop\icon ...

  8. 二、java之面向对象

    面向对象 面向对象编程(Object-Oriented Programming,OOP) 面向对象编程的本质就是:以类的方式组织代码,以对象的组织(封装)数据 三大特性: ◆封装 ◆◆封装的概念 程序 ...

  9. ARM 版 OpenEuler 22.03 部署 KubeSphere v3.4.0 不完全指南

    作者:运维有术 前言 知识点 定级:入门级 KubeKey 安装部署 ARM 版 KubeSphere 和 Kubernetes ARM 版 KubeSphere 和 Kubernetes 常见问题 ...

  10. 9-4 vector对象是如何增长的

    .size():容器中有多少元素 .capacity():不重新分配内存时,可容纳多少元素 .reserve(n):分配至少能容纳n个元素的内存 n>capacity时会分配使得capacity ...