惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
T
The Blog of Author Tim Ferriss
The GitHub Blog
The GitHub Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
A
About on SuperTechFans
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
腾讯CDC
爱范儿
爱范儿
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 【当耐特】
V
Visual Studio Blog
有赞技术团队
有赞技术团队
U
Unit 42
D
Docker
小众软件
小众软件
F
Full Disclosure
I
Intezer
Scott Helme
Scott Helme
P
Privacy International News Feed
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
Google DeepMind News
Google DeepMind News
B
Blog
Martin Fowler
Martin Fowler
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Vercel News
Vercel News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
S
Security Affairs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
月光博客
月光博客
C
Cisco Blogs
云风的 BLOG
云风的 BLOG
Schneier on Security
Schneier on Security
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Threat Research - Cisco Blogs
量子位
Hacker News - Newest:
Hacker News - Newest: "LLM"
H
Heimdal Security Blog
N
Netflix TechBlog - Medium
H
Hacker News: Front Page
P
Proofpoint News Feed
G
GRAHAM CLULEY
V
Vulnerabilities – Threatpost
S
Schneier on Security

博客园 - 冯叶青

OpenAI vs Anthropic API 对比:响应体、请求体、消息格式与工具调用 GitHub Actions 自动部署流程 Git分支自动合并脚本:基于时间戳的冲突解决方案 Next.js lingui.js 多语言自动提取翻译键 - ats-node React实现短信验证码输入组件 Next.js 中优雅地使用 Lottie 动画 Next.js 路由参数更新最佳实践:从 replaceState 到 nextReplaceState Jenkins构建时SWR模块导入报错解决方案 解决 Jenkins 环境下 Lingui 构建报错 "btoa is not defined" git 提交 实现大图自动压缩功能 React lingui.js 多语言自动提取翻译键 - ast node html2canvas 解决截图空白问题 react 实现前端发版监测 JS根据文件名获取文件类型 JS获取本机IP地址 git 内容提交 实现大图拦截功能 JS实现视频截图 next.js 利用中间件(middleware.ts)实现PC与移动路由无缝切换 Android生成签名文件及对apk进行签名 JS-SDK 配置,实现微信分享功能
适用于react、vue菜单格式化工具函数
冯叶青 · 2024-07-12 · via 博客园 - 冯叶青

场景

在一个动态菜单场景中,你向接口获取树形菜单,但最后拿到的树未能达到你的预期,这个时候就需要手写递归重新处理这颗树

适用于react、vue菜单格式化工具函数

包含功能

1. 当前路由是否存在返回按钮

判断逻辑:只要存在左侧可点击的菜单都不具备返回按钮,其他则具有返回按钮

2. 错误路由过滤提醒

假设有这么一个菜单

const router = [
  {
    path: '/test',
    children: [
      {
        path: '/test/page2',
      },
      {
        path: '/otest/page2',
      },
    ],
  },
];

可以看到`/otest/page2`属于无效路由,在react中,这可是致命错误,将破环整个项的运行

这个工具则可以过滤出这种错误路由,并且给出对应提示

3. 递归获取当前菜单下的可用路由

解释一下这种布局,顶部一级菜单,左侧二级菜单

当用户点击顶部菜单时,展示左侧菜单并选中第一个二级可用菜单,正常情况下其实用户点击了顶部菜单是无法知道二级菜单哪个菜单是可用的

因为二级里面还包含二级,正常应该获取二级菜单里面存在组件的菜单

上工具代码

/**
 * @desc 获取动态菜单中第一项可用菜单
 * 正常情况第一项可用菜单就是路由第一个路由中的二层嵌套的第一项菜单
 * */

export const firstAvailableRouter = (routes) => {
  const [router] = routes;
  if (!router?.component && router?.children?.length && !router.redirect) {
    return firstAvailableRouter(router.children);
  } else {
    return router;
  }
};

/**
 *  格式化菜单树
 * @param tree {Array} 树形菜单
 * @param parent {Array} 递归时做为临时存储用
 * */
export const formatTreeMenu = ({
  tree = [],
  parent = []
}: {
  tree: Record<string, any>[];
  parent?: Record<string, any>;
}) => {
  if (!tree || !Array.isArray(tree)) {
    return [];
  }
  /** 先检查所有子集是否都满足要求 */
  const filteredTree = tree
    .filter((item) => item.path)
    .filter((item) => {
      const parentPath = parent.path || '/';
      const hasErrorRouter = parentPath && !item.path.startsWith(parentPath);
      if (hasErrorRouter) {
        console.warn(`路由:'${item.path}'配置错误,已被过滤`);
      }
      return !hasErrorRouter;
    });
  return filteredTree.map((node) => {
    const {
      title,
      layout = undefined,
      routes,
      hideInMenu = false,
      path,
      icon,
      component,
      id
    } = node;
    const hasBack = parent.path === path ? parent.hideInMenu : hideInMenu;
    const firstUsefulRoute =
      firstAvailableRouter({ routes, childrenKye: 'routes' }) || {};
    const formattedNode: Record<string, any> = {
      path,
      name: title,
      layoutWrap: layout,
      hideInMenu,
      roles: ['admin'],
      hasBack,
      component,
      id,
      firstUsefulRoute //第一个有用路由
    };
    if (icon) {
      formattedNode.icon = icon;
    }
    if (Array.isArray(routes) && routes.length > 0) {
      formattedNode.children = formatTreeMenu({
        tree: routes,
        parent: node
      });
    }
    return formattedNode;
  });
};

测试数据

const router = [
  {
    name: 'portalRegist',
    path: '/portalRegist',
    hideInMenu: true,
    layout: false,
    component: './pages/Portal/regist',
    children: [
      {
        path: './',
        redirect: 'readme',
      },
      {
        path: '/portalRegist/:id',
        component: './pages/Portal/regist/readme',
      },
      {
        name: 'portalReadme',
        path: '/portalRegist/readme/:id',
        component: './pages/Portal/regist/readme',
      },
      {
        name: 'portalAuth',
        path: '/portalRegist/auth/:id',
        component: './pages/Portal/regist/auth',
      },
    ],
  },
  {
    path: '/carbon/greenCompliance/energyUsage',
    name: 'xx',
    component: './pages/GreenCompliance/EnergyUsage',
    hideInMenu: true,
  },
];