1、背景:本来angular,vue,react是专门做单页应用的,奈何客户觉得单页面不行要传统的多tab

2、现状:项目已经基于多模块和嵌套路由完成

3、实现思路:1、路由复用策略。2、路由结合动态组件

3.1 最先尝试的路由复用策略,此方案最终被放弃

1、首先是路由复用策略不是很稳定的一个特性
2、路由复用和模块懒加载有冲突
3、最主要的路由复用的页面如果嵌入了iframe,那么每次打开该页面iframe都会重新加载,这不是客户希望看到的结果。

3.2 路由结合动态组件

项目已经是基于路由,只需稍稍改动就行

1、新增一个多tab组件,

具体tab实现采用阿里ng-zorro,封装一下

<div class="dsptab" #dsptabs>
  <p-contextMenu [model]="items" class="tree-context-menu-tab"></p-contextMenu>
  <nz-tabset
  [nzType]="'card'"
  [nzAnimated]="true"
  [nzSelectedIndex]="currentIndex"
  (nzSelectChange)="nzSelectChange($event)"
  >

      <nz-tab *ngFor="let tab of menuList; let idx=index" >

      <ng-template #nzTabHeading>
      <div class="dsp-anticon" (contextmenu)="contextmenu($event)">
      {{tab.title}}
      <i *ngIf="tab.close" class="fa fa-times" (click)="closeUrl(tab.url)"></i>
      </div>
      </ng-template>

      <ng-container *ngComponentOutlet="tab.component"></ng-container>
      </nz-tab>
  </nz-tabset>
</div>
import {AfterViewChecked, Component, HostListener, Injector, OnInit, ReflectiveInjector, ViewChild} from '@angular/core';
import {ActivatedRoute, NavigationEnd, Router} from '@angular/router';
import {Title} from '@angular/platform-browser';
import {PagesService} from '../../pages.service';
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import {API_URL} from '../../pages.const';
import {ContextMenu} from 'primeng/components/contextmenu/contextmenu';

@Component({
selector: 'app-multi-tabs',
templateUrl: './multi-tabs.component.html',
styleUrls: ['./multi-tabs.component.css']
})
    export class MultiTabsComponent implements OnInit, AfterViewChecked {

    private apiUrl: string = API_URL;

    @ViewChild(ContextMenu)contexMenu: ContextMenu;


    // 路由列表
    menuList = [];
    // 当前选择的tab index
    currentIndex: number = -1;
    items = [];

    constructor(private router: Router,
    private inj: Injector,
    private pagesService: PagesService,
    private activatedRoute: ActivatedRoute,
    private titleService: Title) {



    // 路由事件
    this.router.events.filter(event => event instanceof NavigationEnd)
    .map(() => this.activatedRoute)
    .map(route => {
      while (route.firstChild) {
          route = route.firstChild;
      }
      return route;
    })
    .filter(route => route.outlet === 'primary')
    .subscribe((event) => {

        this.pagesService.hideMask();

        const menu: any = {};
        const title = event.queryParams['value'].title;
        const component = event.component;
        menu.title = title;
        menu.url = this.router.url;

        menu.data = event.params['_value'];



        if (title !== '首页') {
            menu.close = true;
        }
        menu.component = component;
        if ( (!this.menuList || this.menuList.length === 0) && title !== '首页' ) {
          this.router.navigate(['/pages/monitor/welcome'], {queryParams: {title: '首页'}});
          return;
        }

        if ( /\/pages\/leaderView\/*/.test(this.router.url) ) {
          const url = this.apiUrl + 'reportUrlManager/getUrl?URLCode=' + menu.data.page + '&timeStamp=' + (new Date()).getTime();
          window.open(url, '_blank');
          const cm = this.menuList[this.currentIndex];
          return;
        }

        if (title && component) {
          const idx = this.menuList.findIndex(value => value.url === menu.url);
          if (idx >= 0) {
              this.currentIndex = idx;
          }else {
            this.menuList.push(menu);
            this.currentIndex = this.menuList.length - 1;
          }

        }

        });
    }

    ngOnInit(): void {
      this.items = [
      {label: '关闭所有', icon: 'fa-window-close', command: (event) => this.closeAllTab()},
      {label: '关闭当前', icon: 'fa-remove', command: (event) => this.closeCurrTab()}
      ];
    }

    closeAllTab() {
      this.currentIndex = 0;
      this.menuList.forEach(value => {
      if (value.title !== '首页') {
      }
      });
      this.menuList.splice(1);
      const menu = this.menuList[0];
      this.router.navigate([menu.url.split('?')[0]], {queryParams: {title: menu.title}});
      }

      closeCurrTab() {
      const tab = this.menuList[this.currentIndex];
      if (tab.title !== '首页') {
          this.closeUrl(tab.url);
      }
    }

    createInjector(tab) {

    if (!tab.data) {
        return null;
    }

    const inputProviders = Object.keys(tab.data).map(
    (inputName) => {
    return {
    provide: inputName, useValue: tab.data[inputName]
    };
    });
    const resolvedInputs = ReflectiveInjector.resolve(inputProviders);
    const injector = ReflectiveInjector.resolve(inputProviders);
    return injector;
    }

    // 关闭选项标签
    closeUrl(tab: any) {
      // 当前关闭的是第几个路由
      const index = this.menuList.findIndex(p => p.url === tab);
      // 如果只有一个不可以关闭
      if (this.menuList.length === 1) {
          return;
      }
      this.menuList.splice(index, 1);
      // 如果当前删除的对象是当前选中的,那么需要跳转
      if (this.currentIndex === index) {
        // 显示上一个选中
        let menu = this.menuList[index - 1];
        if (!menu) {// 如果上一个没有下一个选中
            menu = this.menuList[index];
        }
      }
    }

    /**
    * tab发生改变
    */
    nzSelectChange(e) {
      if (e.tab) {
        this.currentIndex = e.index;
        const menu = this.menuList[this.currentIndex];
        this.router.navigateByUrl(menu.url);
      }
    }

    ngAfterViewChecked(): void {
    }

    contextmenu(event) {
      event.preventDefault();
      this.contexMenu.toggle(event);
    }

    @HostListener('contextmenu', ['$event']) ban(event) {}

}

4、将封装的组件放入内容展示区

<ba-sidebar></ba-sidebar>
<ba-page-top></ba-page-top>
<div class="al-main"
[ngClass]="{'collapsed':!_sideIsCollapsed}">
  <div class="al-content">
  <app-multi-tabs>
      <router-outlet></router-outlet>
  </app-multi-tabs>
  </div>
</div> 

5、在内容展示区所在模块引入各个子模块,在各个子模块中将视图组件(即完整的页面组件)放入该子模块的entryComponents

upload successful

upload successful

6、按以上步骤修改其他子模块,修改完成完美实现 angular 多窗口 ,iframe也不会每次刷新

upload successful



前端      angular4 多tab

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!