Skip to content

Commit

Permalink
refactor(abc:reuse-tab): refactor reuse-tab component
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk committed Mar 13, 2018
1 parent edaea09 commit e25d2b6
Show file tree
Hide file tree
Showing 20 changed files with 1,338 additions and 389 deletions.
2 changes: 1 addition & 1 deletion src/app/layout.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { MenuService, SettingsService, Menu } from '@delon/theme';
</div>
</div>
<section class="content">
<reuse-tab debug></reuse-tab>
<reuse-tab></reuse-tab>
<router-outlet></router-outlet>
</section>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/routes/abc/ellipsis/ellipsis.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ export class DemoEllipsisComponent {
}

remove() {
this.srv.remove(this.router.url);
this.srv.close(this.router.url);
}
}
2 changes: 1 addition & 1 deletion src/app/routes/acl/jwt/jwt.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ export class DEMOJWTComponent {
}

remove() {
this.srv.remove(this.router.url);
this.srv.close(this.router.url);
}
}
38 changes: 36 additions & 2 deletions src/core/abc/reuse-tab/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,16 +102,50 @@ export class DemoReuseTabEditComponent implements OnInit {

## API

### reuse-tab
### ReuseTabService 接口

**属性**

参数 | 说明 | 类型 | 默认值
----|------|-----|------
max | 允许最多复用多少个页面 | `number` | `10`
mode | 设置匹配模式 | `ReuseTabMatchMode` | `0`
debug | 是否Debug模式 | `boolean` | `false`
excludes | 排除规则,限 `mode=URL` | `RegExp[]` | -
items | 获取已缓存的路由 | `ReuseTabCached[]` | -
count | 获取当前缓存的路由总数 | `number` | -
change | 订阅缓存变更通知 | `Observable<ReuseTabNotify>` | -
title | 自定义当前标题 | `string` | -
closable | 自定义当前 `closable` 状态 | `boolean` | -

**方法**

方法名 | 说明 | 返回类型
----|------|-----
index(url) | 获取指定路径缓存所在位置,`-1` 表示无缓存 | `number`
exists(url) | 获取指定路径缓存是否存在 | `boolean`
get(url) | 获取指定路径缓存 | `boolean`
getTitle(url, route?: ActivatedRouteSnapshot) | 获取标题 | `string`
clearTitleCached() | 清空自定义标题数据 | `void`
getClosable(url, route?: ActivatedRouteSnapshot) | 获取 `closable` 状态 | `string`
clearClosableCached() | 清空 `closable` 缓存 | `void`
remove(url) | 根据URL移除标签,同时触 `change` remove事件 | `void`
move(url, position) | 移动缓存数据,同时触 `change` move事件 | `void`
clear() | 清除所有缓存,同时触 `change` clear事件 | `void`
refresh() | 无任何动作,但会触 `change` refresh事件 | `void`

### reuse-tab 组件

参数 | 说明 | 类型 | 默认值
----|------|-----|------
i18n | 右击菜单国际化,支持HTML | `ReuseContextI18n` | -
mode | 设置匹配模式 | `ReuseTabMatchMode` | `0`
debug | 是否Debug模式 | `boolean` | `false`
max | 允许最多复用多少个页面 | `number` | `10`
excludes | 排除规则,限 `mode=URL` | `RegExp[]` | -
allowClose | 允许关闭 | `boolean` | `true`
showCurrent | 总是显示当前页 | `boolean` | `true`
fixed | 是否固定 | `boolean` | `true`
change | 切换时回调 | `EventEmitter` | -
close | 关闭回调 | `EventEmitter` | -
change | 切换时回调,接收的参数至少包含:`active``list` 两个参数 | `EventEmitter` | -

2 changes: 1 addition & 1 deletion src/core/abc/reuse-tab/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export { ReuseTabComponent } from './reuse-tab.component';
export { ReuseTabService } from './reuse-tab.service';
export { ReuseTabStrategy } from './reuse-tab.strategy';
export { AdReuseTabModule } from './reuse-tab.module';
export * from './interface';
export { ReuseTabMatchMode, ReuseTabCached, ReuseTabNotify, ReuseItem, ReuseContextI18n } from './interface';
34 changes: 34 additions & 0 deletions src/core/abc/reuse-tab/interface.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ActivatedRouteSnapshot } from '@angular/router';
import { ReuseTabContextComponent } from './reuse-tab-context.component';

/**
* 复用匹配模式
Expand Down Expand Up @@ -37,6 +38,9 @@ export interface ReuseTabCached {

url: string;

/** 是否可关闭,默认:`true` */
closable?: boolean;

_snapshot: ActivatedRouteSnapshot;

_handle: any;
Expand All @@ -45,7 +49,37 @@ export interface ReuseTabCached {
}

export interface ReuseTabNotify {
/** 事件类型 */
active: string;

[key: string]: any;
}

export interface ReuseItem {
url: string;
title: string;
closable: boolean;
index: number;
active: boolean;
last: boolean;
}

export interface ReuseContextEvent {
event: MouseEvent;
item: ReuseItem;
comp?: ReuseTabContextComponent;
}

export type CloseType = 'close' | 'closeOther' | 'closeRight' | 'clear' | null;

export interface ReuseContextCloseEvent {
type: CloseType;
item: ReuseItem;
}

export interface ReuseContextI18n {
close?: string;
closeOther?: string;
closeRight?: string;
clear?: string;
}
50 changes: 50 additions & 0 deletions src/core/abc/reuse-tab/reuse-tab-context-menu.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Component, Input, EventEmitter, Output, HostListener } from '@angular/core';

import { ReuseContextI18n, ReuseContextCloseEvent, ReuseItem, CloseType } from './interface';

@Component({
selector: 'reuse-tab-context-menu',
template: `
<ul nz-menu>
<li nz-menu-item (click)="click($event, 'close')" data-type="close" [nzDisable]="!item.closable" [innerHTML]="i18n.close"></li>
<li nz-menu-item (click)="click($event, 'closeOther')" data-type="closeOther" [innerHTML]="i18n.closeOther"></li>
<li nz-menu-item (click)="click($event, 'closeRight')" data-type="closeRight" [nzDisable]="item.last" [innerHTML]="i18n.closeRight"></li>
<li nz-menu-item (click)="click($event, 'clear')" data-type="clear" [innerHTML]="i18n.clear"></li>
</ul>`,
preserveWhitespaces: false
})
export class ReuseTabContextMenuComponent {

private _i18n: ReuseContextI18n;
@Input()
set i18n(value: ReuseContextI18n) {
this._i18n = Object.assign({
close: '关闭标签',
closeOther: '关闭其它标签',
closeRight: '关闭右侧标签',
clear: '清空'
}, value);
}
get i18n() {
return this._i18n;
}

@Input() item: ReuseItem;

@Output() close = new EventEmitter<ReuseContextCloseEvent>();

click(e: MouseEvent, type: CloseType) {
e.preventDefault();
e.stopPropagation();
if (type === 'close' && !this.item.closable) return;
if (type === 'closeRight' && this.item.last) return;
this.close.next({ type, item: this.item });
}

@HostListener('document:click', ['$event'])
@HostListener('document:contextmenu', ['$event'])
closeMenu(event: MouseEvent): void {
if (event.type === 'click' && event.button === 2) return;
this.close.next({ type: null, item: null });
}
}
33 changes: 33 additions & 0 deletions src/core/abc/reuse-tab/reuse-tab-context.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Component, Input, Output, EventEmitter, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';

import { ReuseContextI18n, ReuseContextCloseEvent } from './interface';
import { ReuseTabContextService } from './reuse-tab-context.service';

@Component({
selector: 'reuse-tab-context',
template: ``,
preserveWhitespaces: false
})
export class ReuseTabContextComponent implements OnDestroy {

private sub$: Subscription = new Subscription();

@Input()
set i18n(value: ReuseContextI18n) {
this.srv.i18n = value;
}

@Output() change = new EventEmitter<ReuseContextCloseEvent>();

constructor(
private srv: ReuseTabContextService
) {
this.sub$.add(srv.show.subscribe(context => this.srv.open(context)));
this.sub$.add(srv.close.subscribe(res => this.change.emit(res)));
}

ngOnDestroy(): void {
this.sub$.unsubscribe();
}
}
24 changes: 24 additions & 0 deletions src/core/abc/reuse-tab/reuse-tab-context.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Directive, HostListener, Input } from '@angular/core';

import { ReuseTabContextService } from './reuse-tab-context.service';
import { ReuseItem } from './interface';

@Directive({
selector: '[context-menu]'
})
export class ReuseTabContextDirective {

@Input('context-menu') item: ReuseItem;

constructor(private srv: ReuseTabContextService) { }

@HostListener('contextmenu', ['$event'])
onContextMenu(event: MouseEvent): void {
this.srv.show.next({
event,
item: this.item
});
event.preventDefault();
event.stopPropagation();
}
}
77 changes: 77 additions & 0 deletions src/core/abc/reuse-tab/reuse-tab-context.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Injectable } from '@angular/core';
import { Overlay, OverlayRef, ScrollStrategyOptions } from '@angular/cdk/overlay';
import { ComponentPortal } from '@angular/cdk/portal';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';

import { ReuseContextEvent, ReuseContextI18n, ReuseContextCloseEvent } from './interface';
import { ReuseTabContextMenuComponent } from './reuse-tab-context-menu.component';

@Injectable()
export class ReuseTabContextService {
private ref: OverlayRef;
i18n: ReuseContextI18n;

show: Subject<ReuseContextEvent> = new Subject<ReuseContextEvent>();
close: Subject<ReuseContextCloseEvent> = new Subject<ReuseContextCloseEvent>();

constructor(private overlay: Overlay) {}

remove() {
if (!this.ref) return;
this.ref.detach();
this.ref.dispose();
this.ref = null;
}

open(context: ReuseContextEvent) {
this.remove();
const { event, item } = context;
const fakeElement = {
getBoundingClientRect: (): ClientRect => ({
bottom: event.clientY,
height: 0,
left: event.clientX,
right: event.clientX,
top: event.clientY,
width: 0,
})
};
const positionStrategy = this.overlay.position().connectedTo(
{ nativeElement: fakeElement },
{ originX: 'start', originY: 'bottom' },
{ overlayX: 'start', overlayY: 'top' })
.withFallbackPosition(
{ originX: 'start', originY: 'top' },
{ overlayX: 'start', overlayY: 'bottom' })
.withFallbackPosition(
{ originX: 'end', originY: 'top' },
{ overlayX: 'start', overlayY: 'top' })
.withFallbackPosition(
{ originX: 'start', originY: 'top' },
{ overlayX: 'end', overlayY: 'top' })
.withFallbackPosition(
{ originX: 'end', originY: 'center' },
{ overlayX: 'start', overlayY: 'center' })
.withFallbackPosition(
{ originX: 'start', originY: 'center' },
{ overlayX: 'end', overlayY: 'center' })
;
this.ref = this.overlay.create({
positionStrategy,
panelClass: 'reuse-tab-cm',
scrollStrategy: this.overlay.scrollStrategies.close()
});
const comp = this.ref.attach(new ComponentPortal(ReuseTabContextMenuComponent));
const instance = comp.instance;
instance.i18n = this.i18n;
instance.item = item;

const sub$ = new Subscription();
sub$.add(instance.close.subscribe((res: ReuseContextCloseEvent) => {
this.close.next(res);
this.remove();
}));
comp.onDestroy(() => sub$.unsubscribe());
}
}
16 changes: 5 additions & 11 deletions src/core/abc/reuse-tab/reuse-tab.component.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
<nz-tabset [(nzSelectedIndex)]="_pos" [nzAnimated]="false" (nzSelectedIndexChange)="to($event)" [nzTabBarExtraTemplate]="opsContent">
<nz-tab *ngFor="let i of _list; let index = index">
<nz-tabset [nzSelectedIndex]="pos" [nzAnimated]="false">
<nz-tab *ngFor="let i of list; let index = index">
<ng-template #nzTabHeading>
{{i.title}}
<i *ngIf="allowClose" class="anticon anticon-close op" (click)="remove(index)"></i>
<span [context-menu]="i" (click)="to(index)" class="name">{{i.title}}</span>
<i *ngIf="i.closable" class="anticon anticon-close op" (click)="_close(index)"></i>
</ng-template>
</nz-tab>
<ng-template #opsContent>
<nz-popconfirm *ngIf="allowClose && srv.count" [nzTitle]="'确定清空吗?'" (nzOnConfirm)="clear()">
<button nz-popconfirm nz-button [nzType]="'dashed'" [nzShape]="'circle'">
<i class="anticon anticon-delete"></i>
</button>
</nz-popconfirm>
</ng-template>
</nz-tabset>
<reuse-tab-context [i18n]="i18n" (change)="cmChange($event)"></reuse-tab-context>
Loading

0 comments on commit e25d2b6

Please sign in to comment.