从JSON结构生成导航菜单项

如何解决从JSON结构生成导航菜单项

我有一个JSON示例,我想在其中存储Angular项目的导航菜单:

{
  "menus": [{
    "name": "nav-menu","style": "nav navbar-toggler","items": [{
      "id": "1","name": "Navigation menu","parent_id": null,"style": "btn btn-default w-100"
    },{
      "id": "2","name": "Home and garden","parent_id": "1",{
      "id": "3","name": "Cookers","parent_id": "2",{
      "id": "4","name": "Microwave ovens",{
      "id": "5","name": "Fridges",{
      "id": "6","name": "PC peripherials",{
      "id": "7","name": "Head phones","parent_id": "6",{
      "id": "8","name": "Monitors",{
      "id": "9","name": "Network",{
      "id": "10","name": "Laptop bags",{
      "id": "11","name": "Web Cams",{
      "id": "12","name": "Remote cameras","parent_id": "11",{
      "id": "13","name": "Laptops",{
      "id": "14","name": "15' Laptops","parent_id": "13",{
      "id": "15","name": "17' Laptops","style": "btn btn-default w-100"
    }]
  }]
}

这个想法是在需要时编辑JSON并根据此数据生成导航菜单。 如何实现呢?

解决方法

这是一个可能的解决方案: https://stackblitz.com/edit/dashjoin-ddx71w

使用常规的角度树(https://v9.material.angular.io/components/tree/overview)实现菜单。 该树使用嵌套的JSON结构,而不是您建议的具有id / parent_id的扁平结构。

如果我们直接采用并编辑此结构,则JSON模式(https://json-schema.org/)是编辑树模型的良好基础。在应用程序组件中检出“ schema”变量。它是树模型结构的简单JSON模式表示形式:

  schema: Schema = {
    type: "object",properties: {
      name: {
        type: "string"
      },style: {
        type: "string"
      },children: {
        type: "array",...

该示例中的架构仅支持三个嵌套级别。您还可以使用$ ref机制来支持任意嵌套级别。

然后,我正在使用一个JSON模式表单组件,该组件显示基于模型和模式的表单:

<lib-json-schema-form [value]="value" (valueChange)="apply($event)" [schema]="schema"></lib-json-schema-form>

apply($event)通过首先删除模型,然后将其设置为从表单组件发出的新值来使材料树重绘。

将表单中的样式(应该称为类)应用于树节点,如下所示:

<span [ngClass]="node.style">{{node.name}}</span>

总而言之,我认为这是一个非常优雅的解决方案,只需很少的代码。

,

因此,首先您需要将平面列表转换为树状结构。

    function unflatten(arr) {
      var tree = [],mappedArr = {},arrElem,mappedElem;

      // First map the nodes of the array to an object -> create a hash table.
      for(var i = 0,len = arr.length; i < len; i++) {
        arrElem = arr[i];
        mappedArr[arrElem.id] = arrElem;
        mappedArr[arrElem.id]['children'] = [];
      }


      for (var id in mappedArr) {
        if (mappedArr.hasOwnProperty(id)) {
          mappedElem = mappedArr[id];
          // If the element is not at the root level,add it to its parent array of children.
          mappedElem.displayName = mappedElem.name;
          mappedElem.icon = '';
          if (mappedElem.parent_id) {
            mappedArr[mappedElem['parent_id']]['children'].push(mappedElem);
          }
          // If the element is at the root level,add it to first level elements array.
          else {
            tree.push(mappedElem);
          }
        }
      }
      return tree;
    }

var arr = [{
      "id": "1","name": "Navigation menu","parent_id": null,"style": "btn btn-default w-100"
    },{
      "id": "2","name": "Home and garden","parent_id": "1",{
      "id": "3","name": "Cookers","parent_id": "2",{
      "id": "4","name": "Microwave ovens",{
      "id": "5","name": "Fridges",{
      "id": "6","name": "PC peripherials",{
      "id": "7","name": "Head phones","parent_id": "6",{
      "id": "8","name": "Monitors",{
      "id": "9","name": "Network",{
      "id": "10","name": "Laptop bags",{
      "id": "11","name": "Web Cams",{
      "id": "12","name": "Remote cameras","parent_id": "11",{
      "id": "13","name": "Laptops",{
      "id": "14","name": "15' Laptops","parent_id": "13",{
      "id": "15","name": "17' Laptops","style": "btn btn-default w-100"
    }]
var tree = unflatten(arr);
console.log(tree);

为了支持Material UI,在上面的代码中,我添加了额外的字段。 1.displayName 2.icon

一旦获得嵌套结构,就可以在组件的模板中使用它。 其余的Angular实现将在stackblitz

中给出 ,

我在新项目中执行此操作,这是部分代码供您参考。 我们在这里用作JSON文件来存储菜单,在应用统计信息完成后使用加载程序加载菜单。然后在我们的html中使用加载的菜单。

我没有使用您的代码或json引用,因此您可以采用这种想法并以自己的方式实现。


sidenav-menu.json

将此文件放在asset/config/sidenav-menu.json

 [
  {
    "icon": "dashboard","name": "Dashboard","isShow": false,"userRole": [
      "ROLE_USER","ROLE_ADMIN"
    ],"href": "dashboard","isChildAvailable": true,"child": [
      {
        "userRole": [
           "ROLE_USER","ROLE_ADMIN"
        ],"icon": "cog","name": "Config","href": "admin/dashboard/config",}
    ]
  }]

menu-loader.service.ts

将此文件放在Shared / Services / loader / MenuLoader.service.ts下

    import { HttpClient } from '@angular/common/http';
    import { Injectable } from '@angular/core';
    
    @Injectable({
  providedIn: 'root'
})
    export class MenuLoaderService {
      sidenavMenu: any[];
      constructor(
    private http: HttpClient) {
  }

  load(): Promise<any> {
    console.log('MenuLoaderService',`getting menu details`);
    const promise = this.http.get('../assets/data/config/sidenav-menu.json')
      .toPromise()
      .then((menus: any[]) => {
        this.sidenavMenu = menus;
        return menus;
      }).catch(this.handleError());

    return promise;
  }

  private handleError(data?: any) {
    return (error: any) => {
      console.log(error);
    };
  }

  getMenu() {
   return this.sidenavMenu;
  }
}

将MenuLoader服务添加到构造器并从中获取菜单

app.component.ts

export class AppComponent implements OnInit {
  constructor(
   // other inject modules
    private menuLoader: MenuLoaderService
  ) {
    this.getMenuList();
  }

  ngOnInit(): void {
   }

  getMenuList() {
    this.menuLoader.load();
  }
}

HTML部分

my-component.component.html 对于显示菜单,我使用了Material design

<!-- Other codes -->
<mat-nav-list style="overflow-y: auto; height: 70.5vh;">
            <div *ngFor="let link of sidenavMenu" [@slideInOut]="link.show ? 'out' : 'in'">
              <span *ngIf="checkUrlAccessibleOrNot(link?.userRole,link?.loginType)">
                <ng-container *ngIf="link.isChildAvailable; else elseTemplate">
                  <mat-list-item role="link" #links [ngClass]="{'bg-amber-gradient': routeIsActive(link?.href)}"
                    (click)="showHideNavMenu(link)">
                    <mat-icon class="component-viewer-nav" matListIcon svgIcon="{{link.icon}}"></mat-icon>
                    <a matLine [matTooltip]="link.name" matTooltipPosition="after">
                      {{ link.name }}</a>
                    <mat-icon *ngIf="!link.isShow">add</mat-icon>
                    <mat-icon *ngIf="link.isShow">remove</mat-icon>
                  </mat-list-item>
                  <mat-divider></mat-divider>
                  <span *ngIf="link.isShow">
                    <div *ngFor="let clink of link.child; let last = last;">
                      <mat-list-item *ngIf="checkUrlAccessibleOrNot(clink?.userRole,link?.loginType)" role="link" #links
                        routerLinkActive="active-link" routerLink="{{clink.href}}" (click)="onLinkClick()" class="p-l-10">
                        <mat-icon class="component-viewer-nav" matListIcon svgIcon="{{clink.icon}}"></mat-icon>
                        <a matLine [matTooltip]="clink.name" matTooltipPosition="after">
                          {{ clink.name}}</a>
                      </mat-list-item>
                      <mat-divider></mat-divider>
                    </div>
                  </span>
                </ng-container>
                <ng-template #elseTemplate>
                  <mat-list-item role="link" #links routerLinkActive="active-link" routerLink="{{link.href}}"
                    (click)="onLinkClick()">
                    <mat-icon class="component-viewer-nav" matListIcon svgIcon="{{link.icon}}"></mat-icon>
                    <a matLine [matTooltip]="link.name" matTooltipPosition="after">
                      {{ link.name }}</a>
                  </mat-list-item>
                  <mat-divider></mat-divider>
                </ng-template>
              </span>
        </div>
              </mat-nav-list>
<!-- Other html codes -->

my-component.component.ts

export class MyComponentComponent implements OnInit {

this.sidenavMenu = []; 构造函数(

    menuLoader: MenuLoaderService
  ) {
      this.sidenavMenu = menuLoader.getMenu();
  }

  ngOnInit() {
    
  }


  showHideNavMenu(link: any) {
    this.sidenavMenu.forEach(indLink => {
      if (indLink.name !== link.name) {
        indLink.isShow = false;
      }
    });
    this.sidenavMenu[this.sidenavMenu.indexOf(link)].isShow =
      !this.sidenavMenu[this.sidenavMenu.indexOf(link)].isShow;
  }

 
  routeIsActive(routePath: string) {
    const mainUrl = this.router.url;
    const splitUrls = mainUrl.split('/');
    return splitUrls[1] === routePath;
  }

  onLinkClick(): void {
    if (this.isMobileView) {
      this.menuSidenav.close();
    }
  }

  checkUrlAccessibleOrNot(roleList: string[],loginType: 'USER' | 'ADMIN'): boolean {
    // Implement it in your own way
  }

  
}

编辑

动画代码:

animations: [
    trigger('slideInOut',[
      state('in',style({
        transform: 'translate3d(0,0)'
      })),state('out',style({
        transform: 'translate3d(100%,transition('in => out',animate('400ms ease-in-out')),transition('out => in',animate('400ms ease-in-out'))
    ]),trigger('detailExpand',[
      state('collapsed',style({ height: '0px',minHeight: '0',display: 'none' })),state('expanded',style({ height: '*' })),transition('expanded <=> collapsed',animate('225ms cubic-bezier(0.4,0.0,0.2,1)')),]),]

我在这里放置了一小段代码,因此您可以以自己的方式实现它,这是一个想法,您也可以实现它,但我没有添加任何演示。如果您在理解代码方面遇到任何问题,请在注释中加以解决。 快乐编码...:D

,

我需要有关您项目的更多数据,但现在……您可以创建一个接口和一个对象类。需要时,您可以随时将数据添加到对象中。

如果需要对API端点进行POST,则可以发送此对象。实现预定义接口的想法是随时编辑此对象。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-