说明

该文章是属于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. docker安装运行kafka单机版

    这里我们安装一下kafka的单机版,由于kafka是基于zk进行管理的,如果我们没有安装过zk的话,需要进行安装好zk再安装kafka,当然如果已经安装过了, 那就没必要安装了.我们可以执行docke ...

  2. kotlin更多语言结构——>相等性

    Kotlin 中有两种类型的相等性: - 结构相等(用 equals() 检测); - 引用相等(两个引用指向同一对象).   结构相等 结构相等由 ==(以及其否定形式 !=)操作判断.按照惯例,像 ...

  3. python数据结构学习第一章——栈

    在这片文章中,我们使用python3.8自制一个具有基本功能的栈结构,它的功能只有push,pop,peek这三个功能 ` #!/usr/bin/env python # * coding: utf- ...

  4. day02-json字符串和js对象

    Web1.0时代 早期网站的登录,如果失败,需要刷新页面才能重新登录; 如果不点击提交按钮,就不知道自己密码输错了: 现在大多数的网站,都是局部刷新,在不刷新整个页面的情况下,实现页面更新: 注册的时 ...

  5. KubeSphere Helm 应用仓库源码分析

    作者:蔡锡生,LStack 平台研发工程师,近期专注于基于 OAM 的应用托管平台落地. 背景介绍 KubeSphere 应用商店简介 作为一个开源的.以应用为中心的容器平台,KubeSphere 在 ...

  6. 【FAQ】HarmonyOS SDK 闭源开放能力 —Map Kit(3)

    1.问题描述: compatibleSdkVersion升级到5.0.0(12)之后,调用坐标系转换API:map.convertCoordinate(mapCommon.CoordinateType ...

  7. 生成文本聚类java实现3

    由于carrot2对中文的理解很不靠谱,所以参考了网络上的一些资料,现在贡献出来所有代码. 代码的思路就是找字或者词出现的频度,并进行打分,最后按照出现次数和重要性,找出重要的语汇.现在贴出来一些可用 ...

  8. 递推(C语言)

    文章目录 1.斐波那契数列 2.太波那契数列 3.二维递推问题 4.实战 4.1 力扣509 斐波那契数 4.2 力扣70 爬楼梯 4.3 力扣119 杨辉三角|| 递推最通俗的理解就是数列,递推和数 ...

  9. Vmware Workstation的虚拟机如何通过宿主机的无线网卡和外部通信

    今天需要在我的笔记本w10电脑上安装一个linux虚拟机,苦于我的w10是家庭版,没有hyper-v功能,所以安装了 vmware的workstation的软件,然后创建了虚拟机,但是总是搞不定如何让 ...

  10. esp8266+mqtt+继电器 (platformio)

    esp8266+mqtt+继电器 使用mqtt 控制led灯 项目地址 https://gitee.com/zhudachangs/esp8266-mqtt-relay #include <Ar ...