使用AI一步一步实现若依(23)

news2025/3/26 8:22:09

功能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
在这里插入图片描述
侧边栏显示正常。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2321841.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第一天学爬虫

阅读提示&#xff1a;我今天才开始尝试爬虫&#xff0c;写的不好请见谅。 一、准备工具 requests库&#xff1a;发送HTTP请求并获取网页内容。BeautifulSoup库&#xff1a;解析HTML页面并提取数据。pandas库&#xff1a;保存抓取到的数据到CSV文件中。 二、爬取步骤 发送请求…

W、M、C练题笔记(持续更新中)

web here are the flag 点击&#xff0c;页面跳转404.php&#xff0c;用bp抓包访问/flag.php页面&#xff0c;得到flag用base64解码 TryToFindFlag 打开后查看源代码 发现是robots协议&#xff0c;访问robots.txt 访问flllaaa......&#xff0c;得到空白页面&#xff0c;查看…

CVE-2021-45232未授权接口练习笔记

CVE-2021-45232 是 Apache APISIX Dashboard 中的一个严重权限漏洞&#xff0c;类似于攻击者无需密码即可拿到整个网关系统的“万能钥匙”。攻击者利用此漏洞&#xff0c;可直接操控网关流量转发规则&#xff0c;甚至远程执行代码&#xff0c;引发服务器沦陷。 默认账户密码导致…

贪心算法——c#

贪心算法通俗解释 贪心算法是一种"每一步都选择当前最优解"的算法策略。它不关心全局是否最优&#xff0c;而是通过局部最优的累积来逼近最终解。优点是简单高效&#xff0c;缺点是可能无法得到全局最优解。 一句话秒懂 自动售货机找零钱&#xff1a;用最少数量的…

Retrofit中scalars转换html为字符串

简介 在Retrofit中&#xff0c;如果你想直接获取HTML或其他文本格式的响应内容而不是将其映射到一个模型类&#xff0c;ScalarsConverterFactory 就派上用场了。ScalarsConverterFactory 是一个转换器工厂&#xff0c;它能够将响应体转换为Java基本类型如String、Integer或Byte…

【微服务架构】SpringCloud(七):配置中心 Spring Cloud Config

文章目录 配置中心为什么需要配置中心配置中心介绍 服务搭建基于GITHUB1.创建仓库2.新建微服务作为配置中心服务3.启动测试拉取 匹配规则分支读取 客户端配置配置文件引入依赖使用远程配置 刷新配置手动配置热更新自动刷新erlang安装RabbitMQ安装环境变量管理界面服务配置测试 …

Linux学习笔记(应用篇二)

基于I.MX6ULL.MINI开发板 开发板与电脑相互通信电脑与开发板互传文件 开发板与电脑相互通信 用网线将电脑与开发板连接 本人使用的是Ubuntu系统&#xff0c;不是虚拟机 一般来说刚开始电脑和开发板是ping不通的 首先查看电脑的 IP WinR&#xff0c;cmd调出终端 我使用的是…

记录一次部署k3s后,服务404 page not found,nginx显示正常

服务部署k3s后&#xff0c;正常入口端怎么返回都是80&#xff0c;且返回错误 TRAEFIK DEFAULT CERT ERR_CERT_AUTHORITY_INVALID ngnix显示也是正常&#xff0c;怎么找也找不到问题 后来通过 iptables -L -n -t nat|grep 80 发现入口端流量被DNAT转到新的服务 而k3s中&#…

mac上安装nvm及nvm的基本语法使用!!

种一棵树&#xff0c;最好是十年前&#xff0c;其次是现在&#xff01;想要改变&#xff0c;从此刻开始&#xff0c;一切都不晚&#xff01; 目录 nvm是什么&#xff1f;前提条件&#xff1a;安装homebrew如果系统已经有node版本&#xff1a;在mac上安装nvm&#xff1a;用nvm安…

(基本常识)C++中const与引用——面试常问

作者&#xff1a;求一个demo 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 内容通俗易懂&#xff0c;没有废话&#xff0c;文章最后是面试常问内容&#xff08;建议通过标题目录学习&#xff09; 废话不多…

dfs(深度优先)——太抽象了

1. 两种方法 #include<bits/stdc.h> using namespace std; //void dfs(int index,int n,vector<int> current) //{ // if(index>n){ // for(int i0;i<current.size();i){ // cout<<current[i]<<" "; // } // cout<<endl;…

python --face_recognition(人脸识别,检测,特征提取,绘制鼻子,眼睛,嘴巴,眉毛)/活体检测

dlib 安装方法 之前博文 https://blog.csdn.net/weixin_44634704/article/details/141332644 环境: python3.8 opencv-python4.11.0.86 face_recognition1.3.0 dlib19.24.6人脸检测 import cv2 import face_recognition# 读取人脸图片 img cv2.imread(r"C:\Users\123\…

redis解决缓存穿透/击穿/雪崩

文章目录 1.缓存穿透1.1 概念1.2 解决方案1.2.1 缓存空对象1.2.2 布隆过滤 1.2 店铺查询使用缓存穿透解决方案1.2.1 流程 2.缓存雪崩2.1 什么是缓存雪崩&#xff1f;2.2 雪崩解决方案 3.缓存击穿3.1 什么是缓存击穿&#xff1f;3.2解决方案3.2.1 基于互斥锁解决缓存击穿问题&am…

《TCP/IP网络编程》学习笔记 | Chapter 22:重叠 I/O 模型

《TCP/IP网络编程》学习笔记 | Chapter 22&#xff1a;重叠 I/O 模型 《TCP/IP网络编程》学习笔记 | Chapter 22&#xff1a;重叠 I/O 模型理解重叠 I/O 模型重叠 I/O本章讨论的重叠 I/O 的重点不在于 I/O 创建重叠 I/O 套接字执行重叠 I/O 的 WSASend 函数进行重叠 I/O 的 WSA…

python每日十题(10)

在Python语言中&#xff0c;源文件的扩展名&#xff08;后缀名&#xff09;一般使用.py。 保留字&#xff0c;也称关键字&#xff0c;是指被编程语言内部定义并保留使用的标识符。Python 3.x有35个关键字&#xff0c;分别为&#xff1a;and&#xff0c;as&#xff0c;assert&am…

LabVIEW液压振动锤控制系统

在现代工程机械领域&#xff0c;液压振动锤的高效与精准控制日益显得重要。本文通过LabVIEW软件&#xff0c;展开液压振动锤启停共振控制技术的研究与应用&#xff0c;探讨如何通过改进控制系统来优化液压振动锤的工作性能&#xff0c;确保其在复杂工况下的稳定性与效率。 ​ …

简单介绍My—Batis

1.什么是My—Batis&#xff1f; My—Batis是一个持久层框架&#xff0c;提供了sql映射功能&#xff0c;能方便的将数据库表和java对象进行映射&#xff0c;通过My—Batis可以将项目中的数据存储在数据库中&#xff0c;以便我们进行调用。值得注意的是My—Batis和spring不是一回…

ALTER TABLE SHRINK SPACE及MOVE的区别与适用场景

以下是 ‌Oracle 数据库‌中三个收缩表空间命令的对比&#xff1a; 1. ALTER TABLE table_name SHRINK SPACE;‌ ‌作用‌&#xff1a;直接重组表数据并移动高水位线&#xff08;HWM&#xff09;&#xff0c;释放未使用的空间到表空间‌。 影响‌&#xff1a; 会锁表&#…

docker远程debug

1. 修改 Java 启动命令 在 Docker 容器中启动 Java 程序时&#xff0c;需要添加 JVM 调试参数&#xff0c;jdk8以上版本 java -agentlib:jdwptransportdt_socket,servery,suspendn,address*:5005 -jar your-app.jar jdk8及以下版本&#xff1a; java -Xdebug -Xrunjdwp:tra…

rosbag|ROS中.bag数据包转换为matlab中.mat数据类型

代码见代码 msg_dict中设置自定义消息类型 test_config中设置需要记录的具体的值 test_config中topic_name以及message_type照搬plotjuggler打开时的参数 最后生成.mat文件在matlab中进行使用