Modify the reuse-tab tag of the delon library so that it is associated with the hidden dynamic menu Menu and the scheme that the parameters will not be lost when the tab is switched

  I have recently used the erupt-web project, and I am very grateful to the author for providing a powerful, easy-to-use, and flexible configuration tool: erupt project

  When using its routing reuse label reuse-tab, I found that when the tab is switched back and forth, the parameters attached to the url will be lost. Looking through the official documentation, it is clearly stated that queryString cannot be used as a parameter: ng-alain==>reuse-
tab

QueryString query parameters are not supported.
Reuse uses URLs to distinguish whether the same page is used, and QueryString query parameters are prone to repeated misuse, so query parameters are not supported, and the QueryString part will be forcibly ignored during the reuse process.

  This is troublesome!

  Although it is a background management platform, in our plan, each page can jump to each other, and the menu generally enters the list page, and clicks on the data of each row and column on the list page to perform inter-page and pop-up layers. interaction.

  For example, the "User List" menu under the "User Management" menu, after clicking on it, the content area displays a row of summary data for each user, and clicking the operation menu (such as viewing details) at the end of the list row will open a new operation content tab page. On this page, various detailed information of the user is displayed.

  This design must require the newly opened tab page to pass parameters from the page that triggered the click event, and the operation parameters such as tab switching back and forth, browser refresh, etc. must not be lost!

  So I started to study reuse-tab!

  The delon library used by erupt-web is version 8.8.0. Although the version is a bit old, it is better than stable. I refer to the blogger's article: [NG-Alain] The past and present of the component Reuse- tab , which mentions:

Routing parameter passing after reuse-tab is enabled
  In any single-page web application, items related to [routing] must be passed in strict accordance with the method recommended by the framework.
  In ng-alain, when reuse-tab is enabled (even before it is enabled), the URL should not continue to use the parameter passing method with [?], no matter whether [?] is before or after [#]. This rule is a mandatory requirement, and if it is not followed, an error will inevitably occur, which means that the parameters will be lost after the page is switched or refreshed.

  It mentioned a method of passing parameters called: matrix parameters (matrix parameters), in simple terms, it is to replace the ?/& in the url with ; to pass parameters, so I tried it:

let menu: Menu = this.menuSrv.getItem(menuCode);
let queryParams = {
    
    id:1,name:"张三"};
let link = menu.link;
for (let p in queryParams) {
    
    
    link += ';' + (p + '=' + queryParams[p])
}
console.log('url:', link);
this.router.navigateByUrl(link);

  The routing is indeed redirected successfully, and the parameters will not be lost when the tab is switched back and forth. Just read the location.href in the redirected page and parse the parameters by yourself.

  But there is a new problem: the newly opened tab has no name, and the url is used as the tab name. The boss also mentioned:

When displaying the title of a multi-tab tab, the title set by ReuseTabService.title and stored in the ReuseTabService._titleCached array is preferred, and then the title of the data object of the route is used. Otherwise, the text attribute of the menu is used, and finally the URL is displayed directly.
According to the last two lines of code of this method, if the value of mode is set to ReuseTabMatchModel.URL, one of the first two conditions must be met, otherwise only the URL will be displayed.

  In order to allow all dynamic URLs to be reused, I did set the model value of the menu to ReuseTabMatchModel.URL. So I had to manually set ReuseTabService.title = 'Title' to set the tab title.

  But even this is not enough. One is that the menu on the left is not connected, and the other is that the breadcrumb navigation bar at the top of the page is blank! Moreover, the method of manually setting the tab name is not reliable. After all, erupt-web will eventually be packaged and compiled into the erupt project. There is no place to manually set the tab name in the java code!

  So instead, I adopt the simplest method: it is known that every newly opened tab is a menu, and the route to jump from the list page is just a matrix parameter attached to the link of the menu, so if I open a new tab before, to A hidden menu is dynamically added to the menu service, and the link is set as a link with matrix parameters. Doesn’t the newly opened tab open a menu in disguise? Doesn’t the associated expansion menu and navigation bar all exist?

  So - first set the various details page menu items as hidden menus:

function generateTree(menus, pid): Menu[] {
    
    
    let result: Menu[] = [];
    menus.forEach((menu) => {
    
    
        if (menu.type === MenuTypeEnum.button || menu.type === MenuTypeEnum.api) {
    
    
            return;
        }
        if (menu.pid == pid) {
    
    
            let option: Menu = {
    
    
                text: menu.name,
                key: menu.code, 
                i18n: menu.name,
                hide: menu.status === MenuStatusEnum.HIDE,
                hideInBreadcrumb: false, //隐藏菜单时隐藏面包屑等:false
                icon: menu.icon || {
    
    
                    type: "icon",
                    value: "unordered-list"
                },
                link: generateMenuPath(menu.type, menu.value),
                children: generateTree(menus, menu.id)
            };
            result.push(option);
        }
    });
    return result;
}

  Good idea, but many problems! The menu of this version of delon-8.8.0 has a bug. When all submenus under the menu are hide, the parent menu on the page menu bar still appears as a menu group, click to expand (although there is no any visible submenu), and then click Yes to close. And we expect the parent menu (actually the "User Management List" menu) to jump directly to the list page after clicking.

  The later version has officially fixed this bug: refactor(theme:menu): refactor MenuService #1507

  According to this PR, when LayoutDefaultNavComponent is initialized, the menu is set and filtered for a hide attribute:

 ngOnInit(): void {
    
    
	 menuSrv.change.pipe(takeUntil(destroy$)).subscribe(data => {
    
    
	   menuSrv.visit(data, (i: Nav, _p, depth) => {
    
    
	     ...
	   });
	   //如果所有的子菜单都hide,则将父菜单也一并hide
	   this.fixHide(data);
	   //过滤隐藏的菜单
	   this.list = data.filter((w: Nav) => w._hidden !== true);
	   cdr.detectChanges();
	 });
	 
  private fixHide(ls: Nav[]): void {
    
    
    const inFn = (list: Nav[]): void => {
    
    
      for (const item of list) {
    
    
        if (item.children && item.children.length > 0) {
    
    
          inFn(item.children);
          if (!item._hidden) {
    
    
            item._hidden = item.children.every((v: Nav) => v._hidden);
          }
        }
      }
    };

    inFn(ls);
  }
    

  Therefore, like me, "User Management" - "User List" is full of hidden menus, and the interface does not even display the "User List", so upgrading the version is not the solution to this problem!

  After pulling down the source code of delon 8.8.0 and trying to modify it several times ( ng-alain/delon/8.8.0 ), I noticed this source code:

<ng-template #item let-i>
  <!-- link -->
  <a *ngIf="i._type <= 2" (click)="to(i)" [attr.data-id]="i.__id" class="sidebar-nav__item-link"
    [ngClass]="{'sidebar-nav__item-disabled': i.disabled}">
    <ng-container *ngIf="i._needIcon">
      <ng-container *ngIf="!collapsed">
        <ng-template [ngTemplateOutlet]="icon" [ngTemplateOutletContext]="{$implicit: i.icon}"></ng-template>
      </ng-container>
      <span *ngIf="collapsed" nz-tooltip nzTooltipPlacement="right" [nzTooltipTitle]="i.text">
        <ng-template [ngTemplateOutlet]="icon" [ngTemplateOutletContext]="{$implicit: i.icon}"></ng-template>
      </span>
    </ng-container>
    <span class="sidebar-nav__item-text" [innerHTML]="i._text"></span>
  </a>
  <!-- has children link -->
  <a *ngIf="i._type === 3" (click)="toggleOpen(i)" (mouseenter)="showSubMenu($event, i)" class="sidebar-nav__item-link">
    <ng-template [ngTemplateOutlet]="icon" [ngTemplateOutletContext]="{$implicit: i.icon}"></ng-template>
    <span class="sidebar-nav__item-text" [innerHTML]="i._text"></span>
    <i class="sidebar-nav__sub-arrow"></i>
  </a>
  <!-- badge -->
  <div *ngIf="i.badge" [attr.title]="i.badge" class="badge badge-{
     
     {i.badgeStatus}}" [class.badge-dot]="i.badgeDot">
    <em>{
   
   {i.badge}}</em>
  </div>
</ng-template>

  The problem now is actually to enter the i._type === 3 branch, so if I directly add the source code here to judge whether all the submenus are hidden, and then repackage and compile, the function will definitely be realized , but this method is too reluctant.

  Observe the source code and find that the assignment of the _type attribute is only in the resume method, as follows:

resume(callback?: (item: Menu, parentMenum: Menu | null, depth?: number) => void) {
    
    
    let i = 1;
    const shortcuts: Menu[] = [];
    this.visit(this.data, (item, parent, depth) => {
    
    
      ...

      item._type = item.externalLink ? 2 : 1;
      if (item.children && item.children.length > 0) {
    
    
        item._type = 3;
      }

      ...

      if (callback) callback(item, parent, depth);
    });
	...
  }

  The resume method will only be called when adding and switching the i18n internationalization scheme. Then I plan to traverse the menu again after the menu add is completed, and force the _type of the parent menu with all submenus to hide to be 2, so that the interface When rendering, do not enter the branch of i._type === 3, the code is in the default.component.ts of erupt-web, roughly as follows:

ngOnInit() {
    
    
        ...
        this.data.getMenu().subscribe(res => {
    
    
        	...
        	function generateTree(menus, pid): Menu[] {
    
    
        		...
        	}
        	...
        	let rootMenu: Menu[] = [{
    
    
                group: false,
                hideInBreadcrumb: true, //不生成导航栏
                text: "~",
                shortcutRoot: true,
                children: generateTree(res, null)
            }];
            this.menuSrv.add(rootMenu);
            console.log('菜单初始化完成!', this.menuSrv.menus)
            
            this.menuSrv.menus.forEach((m) => {
    
    
                this.resumeMenuType(m.children)
            });
            console.log('重设type之后的菜单:', this.menuSrv.menus)
        	
	})

private resumeMenuType(ls: Menu[]): void {
    
    
    const inFn = (list: Menu[]): void => {
    
    
        for (const item of list) {
    
    
            if (item.children && item.children.length > 0) {
    
    
                inFn(item.children);
                if (!item._hidden) {
    
    
                    let h = item.children.every((v: Menu) => v._hidden);
                    if (h === true) {
    
    
                    	//所有子菜单都是hidden,但菜单本身link不为空,则设置type=2
                        if (item.link && item.link.trim().length > 0) {
    
    
                            item._type = 2;
                        } else {
    
    
                            item._type = 3
                        }
                    }
                }
            }
        }
    };

    inFn(ls);
}

  The above operation can be regarded as painstakingly bypassing the bug of delon-8.8.0. The project started, and finally saw the expected menu, I was ecstatic!

  With an excited look, continue to the second step of the plan: before opening a new tab, dynamically add a hidden menu to the menu service, and set the link to a link with matrix parameters. The main code is as follows:

newTabForMenu(menuCode: string, menuTitle: string, data: any, eruptName: string, primaryKeyName: string, primaryKeyVal: any) {
    
    
        let menu: Menu = this.menuSrv.getItem(menuCode);
        if (!menu) {
    
    
            menu = this.menuSrv.getHit(this.menuSrv.menus, menuTitle);
        }
        if (!menu) {
    
    
            this.msg.warning("无法找到此路由,请检查!");
            return;
        }
		
		//根据自身业务,构建矩阵参数
		// ...
		let queryParams = {
    
    id:1,name:'张三',age:30};
        let link = menu.link;
        for (let p in queryParams) {
    
    
            link += ';' + (p + '=' + queryParams[p])
        }
        //在当前menu对象的同级生成一个隐藏菜单,防止router无法找到路由
        const newHideMenuKey = '_hide_' + menu.key + '_' + new Date().getTime();
        let newHideMenu: Menu = {
    
    
            text: menu.text,
            key: newHideMenuKey,
            hideInBreadcrumb: false,
            i18n: menu.text,
            hide: true,
            link: link,
            __parent: menu.__parent
        };
        if (menu.__parent) {
    
    
            menu.__parent.children.push(newHideMenu);
        }
        this.menuSrv.setItem(newHideMenuKey, newHideMenu);
        console.log('新增隐藏菜单之后:', this.menuSrv.menus);
        
        //跳转到菜单路由
        this.router.navigate([newHideMenu.link]);

    }

  All in one go! Start, test, fail - characters such as ;= in the url are automatically encoded:

xx%2Fxxx%3Bid%3D1%3Bname%3D%E5%BC%A0%E4%B8%89%3Bage%3D30

  Of course, this string is far from the original link we set:

xx/xxx;id=1;name=Zhang San;age=30

  So reuse-tab is naturally 404.

  In order to solve this problem, go through various information sites:   this is the solution of custom URL serialization rules,   this is the use of router.navigate(['/xxx/xxx',{k:v,k:v}]) plan

  Finally, prepare to use the second solution and modify the code:

newTabForMenu(menuCode: string, menuTitle: string, data: any, eruptName: string, primaryKeyName: string, primaryKeyVal: any) {
    
    
	...
	let queryParams = {
    
    id:1,name:'张三',age:30};
	...
	//跳转到菜单路由,参数格式为:['/xxx/xxx',{k:v,k:v}]
    this.router.navigate([menu.link, queryParams]);
    
}

  Tried again this time and finally succeeded! ! ! The breadcrumbs in the navigation bar are complete, and the menu bar of the newly opened tab page is also opened. Finally, the original idea is fully realized! ! !

  Tears streaming down my face!

  To sum up this adjustment, in fact, it is the use of matrix parameters + dynamic insertion of hidden menus as two core technical points, but whether it is from the use of reuse-tab, the source code implementation of ng-alain's menu, and the use of angular router, in all aspects, there is no Proficient in everything! Everywhere is the result of slowly grinding out the spirit of perseverance. The so-called never forgetting must have an echo, the so-called more people who walk, there will be a way...

Guess you like

Origin blog.csdn.net/AJian759447583/article/details/128009374