功能23:从后端获取路由/菜单数据
功能22:用户管理
功能21:使用axios发送请求
功能20:使用分页插件
功能19:集成MyBatis-Plus
功能18:创建后端工程
功能17:菜单管理
功能16:角色管理
功能15:用户管理
功能14:使用本地SVG图标库
功能13:侧边栏加入Logo
功能12:折叠/展开侧边栏
功能11:实现面包屑功能
功能10:添加首页菜单项
功能9:退出登录功能
功能8:页面权限控制
功能7:路由全局前置守卫
功能6:动态添加路由记录
功能5:侧边栏菜单动态显示
功能4:首页使用Layout布局
功能3:点击登录按钮实现页面跳转
功能2:静态登录界面
功能1:创建前端项目
前言
drop table if exists sys_menu;
create table sys_menu (
menu_id bigint(20) not null auto_increment comment '菜单ID',
menu_name varchar(50) not null comment '菜单名称',
parent_id bigint(20) default 0 comment '父菜单ID',
order_num int(4) default 0 comment '显示顺序',
path varchar(200) default '' comment '路由地址',
component varchar(255) default null comment '组件路径',
query varchar(255) default null comment '路由参数',
route_name varchar(50) default '' comment '路由名称',
is_frame int(1) default 1 comment '是否为外链(0是 1否)',
is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)',
menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)',
visible char(1) default 0 comment '菜单状态(0显示 1隐藏)',
status char(1) default 0 comment '菜单状态(0正常 1停用)',
perms varchar(100) default null comment '权限标识',
icon varchar(100) default '#' comment '菜单图标',
create_by varchar(64) default '' comment '创建者',
create_time datetime comment '创建时间',
update_by varchar(64) default '' comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) default '' comment '备注',
primary key (menu_id)
) engine=innodb auto_increment=2000 comment = '菜单权限表';
一.操作步骤(后端)
1.SysLoginController
src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
监听接口/getRouters
@RestController
public class SysLoginController {
@Autowired
private ISysMenuService menuService;
/**
* 获取路由信息
*
* @return 路由信息
*/
@GetMapping("getRouters")
public AjaxResult getRouters() {
return AjaxResult.success(menuService.buildMenus(1L));
}
}
2.Service接口和实现
src/main/java/com/ruoyi/system/service/ISysMenuService.java
public interface ISysMenuService extends IService<SysMenu> {
List<RouterVo> buildMenus(Long userId);
}
src/main/java/com/ruoyi/system/service/impl/SysMenuServiceImpl.java
复杂的逻辑将数据库表中的数据进行转换后返回(具体逻辑到后续菜单管理功能也实现后再统一梳理)
@Service
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu>
implements ISysMenuService {
@Override
public List<RouterVo> buildMenus(Long userId) {
List<SysMenu> menus = list();
List<SysMenu> menuTree = getChildPerms(menus, 0);
return menu2Router(menuTree);
}
private List<RouterVo> menu2Router(List<SysMenu> menus) {
List<RouterVo> routers = new LinkedList<>();
for (SysMenu menu : menus) {
RouterVo router = buildBaseRouter(menu);
handleChildrenRoutes(menu, router);
routers.add(router);
}
return routers;
}
/**
* 根据父ID获取所有子菜单(递归构建树形结构)
*
* @param list 菜单列表(平铺结构)
* @param parentId 目标父菜单ID(需与菜单ID类型一致,此处假设为long)
* @return 树形结构子菜单列表
*/
private List<SysMenu> getChildPerms(List<SysMenu> list, long parentId) {
// 1. 预构建父节点到子节点的映射(提升查找效率)
Map<Long, List<SysMenu>> parentIdToChildrenMap = list.stream()
.collect(Collectors.groupingBy(SysMenu::getParentId));
// 2. 直接获取父节点的子节点列表,并递归构建树
List<SysMenu> returnList = new ArrayList<>();
for (SysMenu menu : list) {
if (menu.getParentId() == parentId) {
buildTree(parentIdToChildrenMap, menu);
returnList.add(menu);
}
}
return returnList;
}
/**
* 递归构建菜单树形结构(基于预生成的映射)
*
* @param parentMap 父节点到子节点的映射
* @param currentNode 当前处理节点
*/
private void buildTree(Map<Long, List<SysMenu>> parentMap, SysMenu currentNode) {
List<SysMenu> childList = parentMap.getOrDefault(currentNode.getMenuId(), new ArrayList<>());
currentNode.setChildren(childList);
for (SysMenu child : childList) {
buildTree(parentMap, child); // 递归处理子节点
}
}
/**
* 构建基础路由信息
*/
private RouterVo buildBaseRouter(SysMenu menu) {
RouterVo router = new RouterVo();
router.setHidden("1".equals(menu.getVisible()));
router.setName(getRouteName(menu));
router.setPath(getRouterPath(menu));
router.setComponent(getComponent(menu));
router.setQuery(menu.getQuery());
router.setMeta(buildMetaData(menu));
return router;
}
/**
* 处理子路由的特殊情况
*/
private void handleChildrenRoutes(SysMenu menu, RouterVo router) {
List<SysMenu> childMenus = menu.getChildren();
// 处理目录类型
if (isDirectoryWithChildren(menu, childMenus)) {
router.setAlwaysShow(true);
router.setRedirect("noRedirect");
router.setChildren(menu2Router(childMenus));
return;
}
// 处理根菜单
if (isRootMenu(menu)) {
handleRootMenu(menu, router);
return;
}
// 处理根菜单的内部链接
if (isRootInnerLink(menu)) {
handleRootInnerLink(menu, router);
}
}
/**
* 构建元数据信息
*/
private MetaVo buildMetaData(SysMenu menu) {
return new MetaVo(
menu.getMenuName(),
menu.getIcon(),
"1".equals(menu.getIsCache()),
menu.getPath()
);
}
/**
* 处理菜单框架的特殊情况
*/
private void handleRootMenu(SysMenu menu, RouterVo router) {
router.setMeta(null);
RouterVo child = buildBaseRouter(menu);
child.setPath(menu.getPath()); // 覆盖路径
child.setComponent(menu.getComponent());
child.setName(getRouteName(menu.getRouteName(), menu.getPath()));
router.setChildren(Collections.singletonList(child));
}
/**
* 处理内部链接的特殊情况
*/
private void handleRootInnerLink(SysMenu menu, RouterVo router) {
router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon()));
router.setPath("/");
RouterVo child = new RouterVo();
child.setPath(innerLinkReplaceEach(menu.getPath()));
child.setComponent(UserConstants.INNER_LINK);
child.setName(getRouteName(menu.getRouteName(), child.getPath()));
child.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
router.setChildren(Collections.singletonList(child));
}
/* 以下为辅助方法 */
/**
* 判断是否为目录类型且包含子菜单
*/
private boolean isDirectoryWithChildren(SysMenu menu, List<SysMenu> childMenus) {
return CollectionUtils.isNotEmpty(childMenus)
&& UserConstants.TYPE_DIR.equals(menu.getMenuType());
}
/**
* 判断是否为根菜单
*/
private boolean isRootMenu(SysMenu menu) {
return isRoot(menu)
&& UserConstants.TYPE_MENU.equals(menu.getMenuType())
&& UserConstants.NO_FRAME.equals(menu.getIsFrame());
}
/**
* 判断是否为根内部链接
*/
private boolean isRootInnerLink(SysMenu menu) {
return isRoot(menu) && isInnerLink(menu);
}
/**
* 判断是否内部展示的地址
*/
private boolean isInnerLink(SysMenu menu) {
return UserConstants.NO_FRAME.equals(menu.getIsFrame())
&& StringUtils.ishttp(menu.getPath());
}
/**
* 判断是否为根菜单(parentId为0)
*/
private boolean isRoot(SysMenu menu) {
return menu.getParentId() != null && menu.getParentId().intValue() == 0;
}
/**
* 获取路由名称(优先使用配置的路由名)
*/
private String getRouteName(SysMenu menu) {
return isRootMenu(menu)
? StringUtils.EMPTY
: getRouteName(menu.getRouteName(), menu.getPath());
}
/**
* 生成最终路由名称(首字母大写)
*/
private String getRouteName(String routeName, String path) {
return StringUtils.capitalize(StringUtils.defaultIfEmpty(routeName, path));
}
/**
* 获取路由路径(处理特殊路径情况)
*/
private String getRouterPath(SysMenu menu) {
if (isNonRootInnerLink(menu)) {
return innerLinkReplaceEach(menu.getPath());
}
if (isRootDirectoryFrame(menu)) {
return "/" + menu.getPath();
}
return isRootMenu(menu) ? "/" : menu.getPath();
}
/**
* 判断非根菜单的内部链接
*/
private boolean isNonRootInnerLink(SysMenu menu) {
return !isRoot(menu) && isInnerLink(menu);
}
/**
* 判断是否为根目录框架
*/
private boolean isRootDirectoryFrame(SysMenu menu) {
return isRoot(menu)
&& UserConstants.TYPE_DIR.equals(menu.getMenuType())
&& UserConstants.NO_FRAME.equals(menu.getIsFrame());
}
/**
* 获取组件路径(智能处理不同情况)
*/
private String getComponent(SysMenu menu) {
if (StringUtils.isNotEmpty(menu.getComponent()) && !isRootMenu(menu)) {
return menu.getComponent();
}
if (isInnerLinkComponent(menu)) {
return UserConstants.INNER_LINK;
}
if (isParentView(menu)) {
return UserConstants.PARENT_VIEW;
}
return UserConstants.LAYOUT;
}
private boolean isInnerLinkComponent(SysMenu menu) {
return StringUtils.isEmpty(menu.getComponent())
&& menu.getParentId().intValue() != 0
&& isInnerLink(menu);
}
/**
* 判断是否需要父视图组件
*/
private boolean isParentView(SysMenu menu) {
return !isRoot(menu) && UserConstants.TYPE_DIR.equals(menu.getMenuType());
}
/**
* 处理内部链接路径格式转换(使用正则表达式优化)
*/
private String innerLinkReplaceEach(String path) {
return path.replaceAll("^(http|https|www)([.:])+", "")
.replaceAll("[.:]", "/");
}
}
3.Mapper
src/main/java/com/ruoyi/system/mapper/SysMenuMapper.java
public interface SysMenuMapper extends BaseMapper<SysMenu> {
}
4.实体类SysMenu
src/main/java/com/ruoyi/common/core/domain/entity/SysMenu.java
@TableName("sys_menu")
public class SysMenu {
/**
* 菜单ID
*/
@TableId(type = IdType.AUTO) // 主键自增
private Long menuId;
/**
* 菜单名称
*/
private String menuName;
/**
* 父菜单名称
*/
@TableField(exist = false)
private String parentName;
/**
* 父菜单ID
*/
private Long parentId;
/**
* 显示顺序
*/
private Integer orderNum;
/**
* 路由地址
*/
private String path;
/**
* 组件路径
*/
private String component;
/**
* 路由参数
*/
private String query;
/**
* 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义)
*/
private String routeName;
/**
* 是否为外链(0是 1否)
*/
private String isFrame;
/**
* 是否缓存(0缓存 1不缓存)
*/
private String isCache;
/**
* 类型(M目录 C菜单 F按钮)
*/
private String menuType;
/**
* 显示状态(0显示 1隐藏)
*/
private String visible;
/**
* 菜单状态(0正常 1停用)
*/
private String status;
/**
* 权限字符串
*/
private String perms;
/**
* 菜单图标
*/
private String icon;
/**
* 创建者
*/
private String createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private String updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 备注
*/
private String remark;
/**
* 子菜单
*/
@TableField(exist = false)
private List<SysMenu> children = new ArrayList<>();
public List<SysMenu> getChildren() {
return children;
}
public void setChildren(List<SysMenu> children) {
this.children = children;
}
public Long getMenuId() {
return menuId;
}
public void setMenuId(Long menuId) {
this.menuId = menuId;
}
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Integer getOrderNum() {
return orderNum;
}
public void setOrderNum(Integer orderNum) {
this.orderNum = orderNum;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public String getRouteName() {
return routeName;
}
public void setRouteName(String routeName) {
this.routeName = routeName;
}
public String getIsFrame() {
return isFrame;
}
public void setIsFrame(String isFrame) {
this.isFrame = isFrame;
}
public String getIsCache() {
return isCache;
}
public void setIsCache(String isCache) {
this.isCache = isCache;
}
public String getMenuType() {
return menuType;
}
public void setMenuType(String menuType) {
this.menuType = menuType;
}
public String getVisible() {
return visible;
}
public void setVisible(String visible) {
this.visible = visible;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getPerms() {
return perms;
}
public void setPerms(String perms) {
this.perms = perms;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getCreateBy() {
return createBy;
}
public void setCreateBy(String createBy) {
this.createBy = createBy;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getUpdateBy() {
return updateBy;
}
public void setUpdateBy(String updateBy) {
this.updateBy = updateBy;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
5.工具类StringUtils
src/main/java/com/ruoyi/common/utils/StringUtils.java
public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 空字符串
*/
private static final String NULLSTR = "";
/**
* * 判断一个字符串是否为空串
*
* @param str String
* @return true:为空 false:非空
*/
public static boolean isEmpty(String str) {
return isNull(str) || NULLSTR.equals(str.trim());
}
/**
* * 判断一个字符串是否为非空串
*
* @param str String
* @return true:非空串 false:空串
*/
public static boolean isNotEmpty(String str) {
return !isEmpty(str);
}
/**
* * 判断一个对象是否为空
*
* @param object Object
* @return true:为空 false:非空
*/
public static boolean isNull(Object object) {
return object == null;
}
/**
* 是否为http(s)://开头
*
* @param link 链接
* @return 结果
*/
public static boolean ishttp(String link) {
return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS);
}
}
6.VO
src/main/java/com/ruoyi/system/domain/vo/RouterVo.java
public class RouterVo {
/**
* 路由名字
*/
private String name;
/**
* 路由地址
*/
private String path;
/**
* 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现
*/
private boolean hidden;
/**
* 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
*/
private String redirect;
/**
* 组件地址
*/
private String component;
/**
* 路由参数:如 {"id": 1, "name": "ry"}
*/
private String query;
/**
* 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
*/
private Boolean alwaysShow;
/**
* 其他元素
*/
private MetaVo meta;
/**
* 子路由
*/
private List<RouterVo> children;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public boolean getHidden() {
return hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public String getRedirect() {
return redirect;
}
public void setRedirect(String redirect) {
this.redirect = redirect;
}
public String getComponent() {
return component;
}
public void setComponent(String component) {
this.component = component;
}
public String getQuery() {
return query;
}
public void setQuery(String query) {
this.query = query;
}
public Boolean getAlwaysShow() {
return alwaysShow;
}
public void setAlwaysShow(Boolean alwaysShow) {
this.alwaysShow = alwaysShow;
}
public MetaVo getMeta() {
return meta;
}
public void setMeta(MetaVo meta) {
this.meta = meta;
}
public List<RouterVo> getChildren() {
return children;
}
public void setChildren(List<RouterVo> children) {
this.children = children;
}
}
src/main/java/com/ruoyi/system/domain/vo/MetaVo.java
public class MetaVo {
/**
* 设置该路由在侧边栏和面包屑中展示的名字
*/
private String title;
/**
* 设置该路由的图标,对应路径src/assets/icons/svg
*/
private String icon;
/**
* 设置为true,则不会被 <keep-alive>缓存
*/
private boolean noCache;
/**
* 内链地址(http(s)://开头)
*/
private String link;
public MetaVo() {
}
public MetaVo(String title, String icon) {
this.title = title;
this.icon = icon;
}
public MetaVo(String title, String icon, boolean noCache) {
this.title = title;
this.icon = icon;
this.noCache = noCache;
}
public MetaVo(String title, String icon, String link) {
this.title = title;
this.icon = icon;
this.link = link;
}
public MetaVo(String title, String icon, boolean noCache, String link) {
this.title = title;
this.icon = icon;
this.noCache = noCache;
if (StringUtils.ishttp(link)) {
this.link = link;
}
}
public boolean isNoCache() {
return noCache;
}
public void setNoCache(boolean noCache) {
this.noCache = noCache;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getLink() {
return link;
}
public void setLink(String link) {
this.link = link;
}
}
7.pom
引入工具依赖
<!--常用工具类 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
8.配置文件
src/main/resources/application.yml
spring:
jackson:
default-property-inclusion: NON_EMPTY
9.常量类
src/main/java/com/ruoyi/common/constant/Constants.java
public class Constants {
/**
* http请求
*/
public static final String HTTP = "http://";
/**
* https请求
*/
public static final String HTTPS = "https://";
}
src/main/java/com/ruoyi/common/constant/UserConstants.java
public class UserConstants {
public static final String YES_FRAME = "0";
public static final String NO_FRAME = "1";
/**
* 菜单类型(目录)
*/
public static final String TYPE_DIR = "M";
/**
* 菜单类型(菜单)
*/
public static final String TYPE_MENU = "C";
/**
* 菜单类型(按钮)
*/
public static final String TYPE_BUTTON = "F";
/**
* Layout组件标识
*/
public final static String LAYOUT = "Layout";
/**
* ParentView组件标识
*/
public final static String PARENT_VIEW = "ParentView";
/**
* InnerLink组件标识
*/
public final static String INNER_LINK = "InnerLink";
}
操作步骤(前端)
10.API
src\api\login.js
import request from '@/utils/request'
// 获取路由
export const getRouters = () => {
return request({
url: '/getRouters',
method: 'get'
})
}
11.前置守卫
src\permission.js
import router from './router'
import { getToken } from '@/utils/auth'
import usePermissionStore from '@/stores/permission'
const whiteList = ['/login']
const whiteListDict = whiteList.reduce((acc, cur) => {
acc[cur] = true;
return acc;
}, {});
router.beforeEach(async (to, from, next) => {
if (getToken()) {
if (whiteListDict[to.path]) {
next({ path: '/' })
} else {
if (router.getRoutes().length <= 3) {
try {
const newRouteRecord = await usePermissionStore().generateRoutes()
newRouteRecord.forEach(route => {
router.addRoute(route) // 动态添加可访问路由表
})
next({ ...to, replace: true })
} catch (error) {
console.error(error)
}
} else {
next()
}
}
} else {
// 没有token
if (whiteListDict[to.path]) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
}
}
})
12.permissionStore
src\stores\permission.js
调用新增的API接口,并处理返回结果。
import { ref } from 'vue'
import { defineStore } from 'pinia'
import Layout from '@/layout/index.vue'
import { constantRoutes } from '@/router'
import { getRouters } from '@/api/login'
import { isHttp } from '@/utils/ruoyi'
// 匹配views里面所有的.vue文件
const modules = import.meta.glob('@/views/**/*.vue')
const usePermissionStore = defineStore('permission', () => {
const arrayForMenu = ref([])
const arrayForRouter = ref([])
// 异步操作
const generateRoutes = async () => {
const res = await getRouters()
const menuData = JSON.parse(JSON.stringify(res.data))
const routeData = JSON.parse(JSON.stringify(res.data))
arrayForMenu.value = constantRoutes.concat(menuData)
const routeDataFilter = filterAsyncRouter(routeData)
arrayForRouter.value = routeDataFilter
return routeDataFilter
}
return {
arrayForMenu,
arrayForRouter,
generateRoutes
}
})
/**
* 异步路由过滤器 - 核心路由配置处理器
* 功能:
* 1. 递归处理路由配置树,动态加载Vue组件
* 2. 特殊处理Layout组件和ParentView结构
* 3. 规范化路由配置结构
*
* @param {Array} asyncRouterArr - 原始异步路由配置数组
* @returns {Array} 处理后的标准化路由配置数组
*
* 处理逻辑:
* 1. 遍历路由配置,处理子路由配置
* 2. 动态加载组件(转换字符串路径为真实组件)
* 3. 递归处理嵌套子路由
* 4. 清理空children和redirect属性
*/
const filterAsyncRouter = (asyncRouterArr) => {
return asyncRouterArr.filter(routeMap => {
if (isHttp(routeMap.path)) {
return false;
}
// 处理子路由
if (routeMap.children) {
routeMap.children = filterChildrenForRouter(routeMap.children);
}
if (routeMap.component) {
// Layout 组件特殊处理
if (routeMap.component === 'Layout') {
routeMap.component = Layout
} else {
routeMap.component = loadView(routeMap.component)
}
}
// 递归处理子路由
if (routeMap.children?.length) {
filterAsyncRouter(routeMap.children);
} else {
delete routeMap.children;
delete routeMap.redirect;
}
return true;
});
}
/**
* 子路由结构转换器 - 路由层级扁平化处理器
* 功能:
* 1. 处理ParentView类型的路由结构
* 2. 合并嵌套子路由路径
* 3. 将多级路由转换为扁平结构
*
* @param {Array} childrenArr - 原子路由配置数组
* @returns {Array} 转换后的扁平化子路由数组
*
* 处理逻辑:
* 1. 当遇到ParentView组件时,将其子路由提升到当前层级
* 2. 合并父级路径到子路由path
* 3. 保留普通路由配置
*/
const filterChildrenForRouter = (childrenArr) => {
let children = [];
childrenArr.forEach(el => {
if (el.children?.length && el.component === 'ParentView') {
children.push(...el.children.map(c => ({
...c,
path: `${el.path}/${c.path}`
})));
return;
}
children.push(el);
});
return children;
}
/**
* 动态组件加载器 - 模块解析器
* 功能:
* 根据组件路径字符串动态加载Vue组件
*
* @param {string} view - 组件路径字符串(例: "system/user/index")
* @returns {Component} Vue组件
*
* 处理逻辑:
* 1. 遍历预编译的模块集合(modules)
* 2. 匹配views目录下的对应组件文件
* 3. 返回组件异步加载函数
*/
const loadView = (view) => {
let res;
for (const path in modules) {
const dir = path.split('views/')[1].split('.vue')[0];
if (dir === view) {
res = () => modules[path]();
}
}
return res;
}
export default usePermissionStore
13.工具类
src\utils\ruoyi.js
/**
* 判断url是否是http或https
* @param {string} url
* @returns {Boolean}
*/
export function isHttp(url) {
return typeof url === 'string' && url.startsWith('http');
}
二.功能验证
运行项目,浏览器访问http://localhost:5173/index
侧边栏显示正常。