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
6、按以上步骤修改其他子模块,修改完成完美实现 angular 多窗口 ,iframe也不会每次刷新
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!