angular+ 自定义popover指令

自定义popover

需求背景

项目基于ng-zorro-antd框架实现。实现卡片结合popover组件交互效果。点击一个popover组件的“...”按钮,关闭其他popover组件,打开当前的popover组件。如下图:

ng-zorro-antd提供的popover组件是基于angular/cdk的overlay组件构建的。而overlay组件是有背景层,交互方式是打开一个弹出层,要先关闭才能在打开另一个overlay组件。所以ng-zorro-antd的popover组件交互也是如此。

方案演进

1、静态页面

card-list.component.html:

<div nz-row [nzGutter]="8">
  <div nz-col [nzSpan]="8" *ngFor="let item of cardlist.items">
      <nz-card  nzTitle="{{item.roleName}}" style="margin-bottom:10px;" [nzExtra]="extraTemplate" (click)="openDetails(item)">
        <p>{{item.description}}</p>
        <p>{{item.createAt}}</p>
      </nz-card>
  </div>
</div>
<ng-template #extraTemplate>
    <span
      (click)="tabPopover($event)" style="padding:5px;">
      <i class="anticon anticon-ellipsis"></i>
    </span>
    <div class="popover bottom" (click)="stopP($event)">
      <div class="arrow"></div>
      <h3 class="popover-title" *ngIf="title != null">{{title}}
      </h3>
      <div class="popover-content">
        <ul class="popover-itemsBox">
          <li class="popover-item">
              <a routerLink="../roleDetails" class="popover-item-link">
                  <i class="anticon anticon-edit"></i>编辑
              </a>
          </li>
          <li class="popover-item">
              <a (click)="showDeleteConfirm()" class="popover-item-link">
                  <i class="anticon anticon-delete"></i>删除
              </a>
           </li>
        </ul>
      </div>
    </div>
</ng-template>

card-list.component.css:

.popover {
    position: absolute;
    top: 36px;
    right: -3px;
    z-index: 1060;
    display: none;
    max-width: 276px;
    padding: 1px;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-style: normal;
    font-weight: normal;
    letter-spacing: normal;
    line-break: auto;
    line-height: 1.42857143;
    text-align: left;
    text-align: start;
    text-decoration: none;
    text-shadow: none;
    text-transform: none;
    white-space: normal;
    word-break: normal;
    word-spacing: normal;
    word-wrap: normal;
    font-size: 14px;
    background-color: #ffffff;
    -webkit-background-clip: padding-box;
            background-clip: padding-box;
    border-radius: 4px;
    -webkit-box-shadow: 0 2px 8px rgba(0,0,0,.15);
    box-shadow: 0 2px 8px rgba(0,0,0,.15);
  }
  .popover.bottom {
    margin-top: 10px;
  }
  .popover-title {
    margin: 0;
    padding: 8px 14px;
    font-size: 14px;
    border-bottom: 1px solid #e8e8e8;
  }
  .popover-content {
    padding: 9px 14px;
  }
  .popover > .arrow,
  .popover > .arrow:after {
    position: absolute;
    display: block;
    width: 0;
    height: 0;
    border-color: transparent;
    border-style: solid;
    
  }
  .popover > .arrow {
    border-width: 11px;
  }
  .popover > .arrow:after {
    border-width: 10px;
    content: "";
  }
  .popover.bottom > .arrow {
    left: 50%;
    margin-left: -11px;
    border-top-width: 0;
    border-bottom-color: rgba(0,0,0,.07);
    top: -11px;
  }
  .popover.bottom > .arrow:after {
    content: " ";
    top: 1px;
    margin-left: -10px;
    border-top-width: 0;
    border-bottom-color: #ffffff;
  }
  .clearfix:before,
  .clearfix:after {
    content: " ";
    display: table;
  }
  .clearfix:after {
    clear: both;
  }
  .pull-right {
    float: right !important;
  }
  .pull-left {
    float: left !important;
  }
  .hide {
    display: none !important;
  }
  .show {
    display: block !important;
  }
  .popover-itemsBox {
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .popover-item {
    color:#888;
    padding: 5px 0px;
  }
  .popover-item-link {
    color:#888;
  }
  .popover-item-link:hover {
    color:#1890ff;
  }
  .popover-item-link .anticon{
    margin-right:5px;
  }

card-list.component.ts:

import { Component, Renderer2, ElementRef, OnInit, OnDestroy } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { NzModalService } from 'ng-zorro-antd';

class Point {
  constructor(public x: number, public y: number) {}
}
@Component({
  selector: 'app-card-list',
  templateUrl: './card-list.component.html',
  styleUrls: ['./card-list.component.css']
})
export class CardListComponent implements OnInit, OnDestroy {

  cardlist;
  unlistenGlobal;
  constructor(
    private renderer: Renderer2,
    private elRef: ElementRef,
    public router: Router,
    public activatedRoute: ActivatedRoute,
    private modalService: NzModalService
  ) { }
  hasClass(el, cls) {
    let clsArr = [], isHasClass = false;
    clsArr = el.className.split(/\s+/);
    for (let i = 0; i < clsArr.length; i++) {
      if (clsArr[i] === cls) {
        isHasClass = true;
        break;
      }
    }
    return isHasClass;
  }
  closePopover() {
    const element = this.elRef.nativeElement.querySelectorAll('.popover'); 
    for (let i = 0; i < element.length; i++) {
      this.renderer.removeClass(element[i], 'show');
    }
  }
  stopP($event) {
    $event.stopPropagation();
  }
  tabPopover($event) {
    $event.stopPropagation();
    const popTarget = this.renderer.parentNode($event.currentTarget).querySelector('.popover');
    if (this.hasClass(popTarget, 'show')) {
      this.closePopover();
    } else {
      this.closePopover();
      this.renderer.addClass(popTarget, 'show');

    }
  }

  openDetails(item) {
    this.router.navigate(['../roleDetails'], { relativeTo: this.activatedRoute });

  }
  showDeleteConfirm(): void {
    this.closePopover();
    this.modalService.confirm({
      nzTitle: 'Are you sure delete this task?',
      nzContent: '<b style="color: red;">Some descriptions</b>',
      nzOkText: 'Yes',
      nzOkType: 'danger',
      nzOnOk: () => console.log('OK'),
      nzCancelText: 'No',
      nzOnCancel: () => console.log('Cancel')
    });
  }
  ngOnInit() {
    this.unlistenGlobal = 			this.renderer.listen('document', 'click', (evt) => {
      this.closePopover();
    });
    this.cardlist = {
      "items": [
        {
          "createAt": "2018-03-28 11:35:15",
          "description": "拥有平台全部功能的权限",
          "id": 4,
          "isShare": 1,
          "roleName": "系统管理员",
          "tenantID": null,
          "updateAt": null
        },
        {
          "createAt": "2018-03-28 11:35:15",
          "description": "能管理设备、产品、分组等信息",
          "id": 5,
          "isShare": 1,
          "roleName": "设备管理员",
          "tenantID": null,
          "updateAt": null
        },
        {
          "createAt": "2018-03-28 11:35:15",
          "description": "仅能查看所有资源信息",
          "id": 6,
          "isShare": 1,
          "roleName": "普通用户",
          "tenantID": null,
          "updateAt": null
        },
        {
          "createAt": "2018-08-28 20:08:33",
          "description": null,
          "id": 23,
          "isShare": 0,
          "roleName": "分组用户",
          "tenantID": "Cb81t4UWN",
          "updateAt": null
        },
        {
          "createAt": "2018-08-29 13:07:45",
          "description": null,
          "id": 24,
          "isShare": 0,
          "roleName": "产品经理",
          "tenantID": "Cb81t4UWN",
          "updateAt": null
        },
        {
          "createAt": "2018-09-03 15:32:12",
          "description": "系统管理员",
          "id": 25,
          "isShare": 0,
          "roleName": "admin",
          "tenantID": "Cb81t4UWN",
          "updateAt": null
        },
        {
          "createAt": "2018-09-05 09:16:03",
          "description": "test",
          "id": 27,
          "isShare": 0,
          "roleName": "melin",
          "tenantID": "Cb81t4UWN",
          "updateAt": null
        },
        {
          "createAt": "2018-09-06 09:00:42",
          "description": "test",
          "id": 28,
          "isShare": 0,
          "roleName": "melin02",
          "tenantID": "Cb81t4UWN",
          "updateAt": null
        },
        {
          "createAt": "2018-09-25 13:46:54",
          "description": "test",
          "id": 29,
          "isShare": 0,
          "roleName": "设备管理员",
          "tenantID": "Cb81t4UWN",
          "updateAt": null
        }
      ],
      "meta": {
        "count": 9,
        "limit": 10,
        "page": 1
      }
    };
  }

  ngOnDestroy(): void {
    this.unlistenGlobal();
  }

}

2、可复用-组件化

card.component.html:

<nz-card  nzTitle="{{popoverData.roleName}}" style="margin-bottom:10px;" [nzExtra]="extraTemplate">
  <ng-content></ng-content>
</nz-card>
<ng-template #extraTemplate >
  <span
    (click)="tabPopover($event)" style="padding:5px;">
    <i class="anticon anticon-ellipsis"></i>
  </span>
  <div class="popover bottom " (click)="stopP($event)">
    <div class="arrow"></div>
    <h3 class="popover-title" *ngIf="title != null">{{title}}</h3>
    <div class="popover-content">
      <ul class="popover-itemsBox">
        <li class="popover-item"><a routerLink="../roleDetails" class="popover-item-link"><i class="anticon anticon-edit"></i>编辑</a></li>
        <li class="popover-item"><a (click)="showDeleteConfirm()" class="popover-item-link"><i class="anticon anticon-delete"></i>删除</a></li>
      </ul>
    </div>
  </div>
  </ng-template>

card.component.css:

.popover {
    position: absolute;
    top: 36px;
    right: -3px;
    z-index: 1060;
    display: none;
    max-width: 276px;
    padding: 1px;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-style: normal;
    font-weight: normal;
    letter-spacing: normal;
    line-break: auto;
    line-height: 1.42857143;
    text-align: left;
    text-align: start;
    text-decoration: none;
    text-shadow: none;
    text-transform: none;
    white-space: normal;
    word-break: normal;
    word-spacing: normal;
    word-wrap: normal;
    font-size: 14px;
    background-color: #ffffff;
    -webkit-background-clip: padding-box;
            background-clip: padding-box;
    border-radius: 4px;
    -webkit-box-shadow: 0 2px 8px rgba(0,0,0,.15);
    box-shadow: 0 2px 8px rgba(0,0,0,.15);
  }
  .popover.bottom {
    margin-top: 10px;
  }
  .popover-title {
    margin: 0;
    padding: 8px 14px;
    font-size: 14px;
    border-bottom: 1px solid #e8e8e8;
  }
  .popover-content {
    padding: 9px 14px;
  }
  .popover > .arrow,
  .popover > .arrow:after {
    position: absolute;
    display: block;
    width: 0;
    height: 0;
    border-color: transparent;
    border-style: solid;
    
  }
  .popover > .arrow {
    border-width: 11px;
  }
  .popover > .arrow:after {
    border-width: 10px;
    content: "";
  }
  .popover.bottom > .arrow {
    left: 50%;
    margin-left: -11px;
    border-top-width: 0;
    border-bottom-color: rgba(0,0,0,.07);
    top: -11px;
  }
  .popover.bottom > .arrow:after {
    content: " ";
    top: 1px;
    margin-left: -10px;
    border-top-width: 0;
    border-bottom-color: #ffffff;
  }
  .clearfix:before,
  .clearfix:after {
    content: " ";
    display: table;
  }
  .clearfix:after {
    clear: both;
  }
  .pull-right {
    float: right !important;
  }
  .pull-left {
    float: left !important;
  }
  .hide {
    display: none !important;
  }
  .show {
    display: block !important;
  }
  .popover-itemsBox {
    list-style: none;
    margin: 0;
    padding: 0;
  }
  .popover-item {
    color:#888;
    padding: 5px 0px;
  }
  .popover-item-link {
    color:#888;
  }
  .popover-item-link:hover {
    color:#1890ff;
  }
  .popover-item-link .anticon{
    margin-right:5px;
  }

card.component.ts:

import { Component, OnInit, Input, Renderer2, ElementRef, OnDestroy, ViewContainerRef, Inject } from '@angular/core';
import { DOCUMENT } from '@angular/platform-browser';

@Component({
  selector: 'app-popover',
  templateUrl: './popover.component.html',
  styleUrls: ['./popover.component.css']
})
export class PopoverComponent implements OnInit, OnDestroy {
  @Input() popoverData;
  unlistenGlobal;
  constructor(
    private renderer: Renderer2,
    @Inject(DOCUMENT) private _document: any
  ) { }

  ngOnInit() {
    this.unlistenGlobal = this.renderer.listen('document', 'click', (evt) => {
      this.closePopover();
    });
  }
  ngOnDestroy(): void {
    this.unlistenGlobal();
  }
  hasClass(el, cls) {
    let clsArr = [], isHasClass = false;
    clsArr = el.className.split(/\s+/);
    for (let i = 0; i < clsArr.length; i++) {
      if (clsArr[i] === cls) {
        isHasClass = true;
        break;
      }
    }
    return isHasClass;
  }
  closePopover() {
    const element = this._document.querySelectorAll('.popover'); 
    if (element.length > 0) {
      for (let i = 0; i < element.length; i++) {
        this.renderer.removeClass(element[i], 'show');
      }
    }
  }
  stopP($event) {
    $event.stopPropagation();
  }
  tabPopover($event) {
    $event.stopPropagation();
    const popTarget = this.renderer.parentNode($event.currentTarget).querySelector('.popover');
    if (this.hasClass(popTarget, 'show')) {
      this.closePopover();
    } else {
      this.closePopover();
      this.renderer.addClass(popTarget, 'show');
    }
  }
  showDeleteConfirm(): void {
    this.closePopover();
  }
}

其他组件使用card组件(要在module注入card组件):

<div nz-row [nzGutter]="8">
  <div nz-col [nzSpan]="8" *ngFor="let item of cardlist.items">
      <app-popover style="margin-bottom:10px;" [popoverData]="item">
          <p>{{item.description}}</p>
          <p>{{item.createAt}}</p>
      </app-popover>
  </div>
</div>

3、封装成自定义指令

Popover文件结构:

popover
    popover.component.css

    popover.component.html

    popover.component.ts

    popover.directive.ts

    popover.module.ts

具体代码实现如下:

popover.module.ts

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SsPopover } from './popover.directive';
import { PopoverComponent } from './popover.component';

@NgModule({
  declarations: [SsPopover, PopoverComponent],
  exports: [SsPopover],
  imports: [CommonModule],
  entryComponents: [PopoverComponent]
})
export class PopoverModule { }

popover.directive.ts

import { Directive, Input, TemplateRef, ViewContainerRef, Output, EventEmitter, OnInit, OnDestroy, HostListener, ElementRef, ComponentRef, ComponentFactoryResolver, Renderer2, Inject, ViewRef, Injector } from '@angular/core';
import { PopoverComponent } from './popover.component';
import { DOCUMENT } from '@angular/platform-browser';

export class ContentRef {
  constructor(public nodes: any[], public viewRef?: ViewRef, public componentRef?: ComponentRef<any>) { }
}
class Point {
  constructor(public x: number, public y: number) { }
}

@Directive({
  // tslint:disable-next-line:directive-selector
  selector: '[ssPopover]',
  exportAs: 'ssPopover'
})
// tslint:disable-next-line:directive-class-suffix
export class SsPopover implements OnInit, OnDestroy {
  private element: HTMLElement;
  private _mypopoverRef: ComponentRef<PopoverComponent>;
  private unlistenGlobal;
  private unlistenPopover;
  private _contentRef: ContentRef;
  constructor(
    private render: Renderer2,
    private elementRef: ElementRef,
    private viewContainer: ViewContainerRef,
    private _injector: Injector,
    private componentFactoryResolver: ComponentFactoryResolver,
    @Inject(DOCUMENT) private _document: any
  ) { }

  /**
   * Content to be displayed as popover. If title and content are empty, the popover won't open.
   */
  @Input() ssPopover: string | TemplateRef<any>;
  /**
   * Title of a popover. If title and content are empty, the popover won't open.
   */
  @Input() popoverTitle: string | TemplateRef<any>;

  @HostListener('click', ['$event'])
  onClick(event: any) {
    event.stopPropagation();
    const btnElement = this._document.querySelectorAll('.ss-popover-btn');
    if (btnElement.length > 0) {
      if (this.hasClass(this.element, 'ss-popover-btn')) {
        this.removePopover();
      } else {
        this.removePopover();
        this.showPopover();
      }
    } else {
      this.showPopover();
    }
  }
  hasClass(el, cls) {
    let clsArr = [], isHasClass = false;
    clsArr = el.className.split(/\s+/);
    for (let i = 0; i < clsArr.length; i++) {
      if (clsArr[i] === cls) {
        isHasClass = true;
        break;
      }
    }
    return isHasClass;
  }

  showPopover() {
    this._mypopoverRef = this.createPopover(this.popoverTitle);
    const popoverEl = this._mypopoverRef.location.nativeElement.querySelector('.popover');
    const popoverContentEl = this._mypopoverRef.location.nativeElement.querySelector('.popover-content');
    const targetPos = this.getTargetLocation();
    this.render.setStyle(popoverEl, 'right', targetPos.x + 'px');
    this.render.setStyle(popoverEl, 'top', targetPos.y + 'px');
    this.render.addClass(popoverEl, 'show');
    this.render.addClass(this.element, 'ss-popover-btn');
    this.render.appendChild(this.render.parentNode(this.element), this._mypopoverRef.location.nativeElement);
    this.unlistenPopover = this.render.listen(popoverContentEl, 'click', (evt) => {
      this.removePopover();
    });
  }

  hidePopover() {
    const popoverEl = this._mypopoverRef.location.nativeElement.querySelector('.popover');
    if (this._mypopoverRef) {
      this.viewContainer.remove(this.viewContainer.indexOf(this._mypopoverRef.hostView));
      this.render.removeClass(popoverEl, 'show');
      this._mypopoverRef = null;
    }
  }
  removePopover() {
    const btnElement = this._document.querySelectorAll('.ss-popover-btn');
    if (btnElement.length > 0) {
      for (let i = 0; i < btnElement.length; i++) {
        this.render.removeClass(btnElement[i], 'ss-popover-btn');
        this.render.removeChild(this.render.parentNode(btnElement[i]), this.render.parentNode(btnElement[i]).querySelector('.popover'));
      }
    }
  }

  private createPopover(title): ComponentRef<PopoverComponent> {
    this.viewContainer.clear();
    this._contentRef = this._getContentRef(this.ssPopover);
    const PopoverComponentFactory =
      this.componentFactoryResolver.resolveComponentFactory(PopoverComponent);
    const PopoverComponentRef = this.viewContainer.createComponent(PopoverComponentFactory, 0, this._injector,
      this._contentRef.nodes);
    PopoverComponentRef.instance.title = title;

    return PopoverComponentRef;
  }
  private _getContentRef(content: string | TemplateRef<any>, context?: any): ContentRef {
    if (!content) {
      return new ContentRef([]);
    } else if (content instanceof TemplateRef) {
      const viewRef = this.viewContainer.createEmbeddedView(content);
      return new ContentRef([viewRef.rootNodes], viewRef);
    } else {
      return new ContentRef([[this.render.createText(`${content}`)]]);
    }
  }
  private getTargetLocation(): Point {
    const box = this.element.getBoundingClientRect();
    return new Point(this.element.offsetParent.clientWidth - this.element.offsetLeft - box.width, this.element.offsetTop + box.height / 2);
  }
  ngOnInit(): void {
    this.element = this.elementRef.nativeElement;
    this.unlistenGlobal = this.render.listen('document', 'click', (evt) => {
      this.removePopover();
    });
  }

  ngOnDestroy() {
    this.unlistenGlobal();
    if (this.unlistenPopover) {
      this.unlistenPopover();
    }

  }
}


popover.component.ts

import { Component, OnInit, Input, TemplateRef } from '@angular/core';

@Component({
  selector: 'app-popover',
  templateUrl: './popover.component.html',
  styleUrls: ['./popover.component.css']
})
export class PopoverComponent implements OnInit {
  @Input() title: undefined | string | TemplateRef<any>;
  constructor() { }

  ngOnInit() {
  }

  stopP($event) {
    $event.stopPropagation();
  }
}

popover.component.html

<div class="popover bottom animated pulse" (click)="stopP($event)">
  <h3 class="popover-title" *ngIf="title != null">{{title}}</h3>
  <div class="popover-content">
    <ng-content></ng-content>
  </div>
</div>

popover.component.css

.popover {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1060;
  display: none;
  max-width: 276px;
  padding: 1px;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-style: normal;
  font-weight: normal;
  letter-spacing: normal;
  line-break: auto;
  line-height: 1.42857143;
  text-align: left;
  text-align: start;
  text-decoration: none;
  text-shadow: none;
  text-transform: none;
  white-space: normal;
  word-break: normal;
  word-spacing: normal;
  word-wrap: normal;
  font-size: 14px;
  cursor:default;
  background-color: #ffffff;
    -webkit-background-clip: padding-box;
            background-clip: padding-box;
    border-radius: 4px;
    -webkit-box-shadow: 0 2px 8px rgba(0,0,0,.15);
    box-shadow: 0 2px 8px rgba(0,0,0,.15);
}
  .popover.bottom {
    margin-top: 10px;
  }
  .popover-title {
    margin: 0;
    padding: 8px 14px;
    font-size: 14px;
    border-bottom: 1px solid #e8e8e8;
  }
  .popover-content {
    padding: 9px 14px;
  }
  .popover > .arrow,
  .popover > .arrow:after {
    position: absolute;
    display: block;
    width: 0;
    height: 0;
    border-color: transparent;
    border-style: solid;
    
  }
  .popover > .arrow {
    border-width: 6px;
  }
  .popover > .arrow:after {
    border-width: 5px;
    content: "";
  }
  .popover.bottom > .arrow {
    left: 30%;
    margin-left: -16px;
    border-top-width: 0;
    border-bottom-color: rgba(0,0,0,.07);
    top: -6px;
  }
  .popover.bottom > .arrow:after {
    content: " ";
    top: 1px;
    margin-left: -5px;
    border-top-width: 0;
    border-bottom-color: #ffffff;
  }

  .clearfix:before,
  .clearfix:after {
    content: " ";
    display: table;
  }
  .clearfix:after {
    clear: both;
  }
  .center-block {
    display: block;
    margin-left: auto;
    margin-right: auto;
  }
  .pull-right {
    float: right !important;
  }
  .pull-left {
    float: left !important;
  }
  .hide {
    display: none !important;
  }
  .show {
    display: block !important;
  }
  .invisible {
    visibility: hidden;
  }
  .text-hide {
    font: 0/0 a;
    color: transparent;
    text-shadow: none;
    background-color: transparent;
    border: 0;
  }
  .hidden {
    display: none !important;
  }
  .affix {
    position: fixed;
  }

例如,在role模块使用自定义popover:

1、在role模块引入PopoverModule。

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NgZorroAntdModule } from 'ng-zorro-antd';
import { CapellaRoleRoutes } from './capella-role.routing';
import { CapellaRoleComponent } from './capella-role.component';
import { RoleListComponent } from './role-list/role-list.component';
import { RoleDetailsComponent } from './role-details/role-details.component';
import { PopoverModule } from '../common/popover/popover.module';

@NgModule({
  imports: [
    CommonModule,
    NgZorroAntdModule,
    CapellaRoleRoutes,
    PopoverModule
  ],
  declarations: [
    CapellaRoleComponent,
    RoleListComponent,
    RoleDetailsComponent,
  ]
})
export class CapellaRoleModule { }

2、以指令方式使用。

<div nz-row [nzGutter]="8">
  <div nz-col [nzSpan]="8" *ngFor="let item of cardlist.items">
      <nz-card  nzTitle="{{item.roleName}}" class="ss-card-box" [nzExtra]="extraTemplate" (click)="openDetails(item)">
        <p class="ss-card-content">{{item.description}}</p>
        <p>{{item.createAt}}</p>
      </nz-card>
  </div>
</div>
<ng-template #extraTemplate >
  <span class="ss-popover-icon" [ssPopover]="extraPopover">
    <i class="anticon anticon-ellipsis"></i>
  </span>
</ng-template>
<ng-template #extraPopover >
    <ul class="ss-popover-itemsBox">
        <li class="ss-popover-item"><a routerLink="../roleDetails" class="ss-popover-item-link"><i class="anticon anticon-edit"></i>编辑</a></li>
        <li class="ss-popover-item"><a (click)="showDeleteConfirm()" class="ss-popover-item-link"><i class="anticon anticon-delete"></i>删除</a></li>
      </ul>
</ng-template>
posted @ 2018-10-10 15:49  MiyaMiya  阅读(2518)  评论(0编辑  收藏  举报