• 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- 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;
         0;
        height: 0;
        border-color: transparent;
        border-style: solid;
        
      }
      .popover > .arrow {
        border- 11px;
      }
      .popover > .arrow:after {
        border- 10px;
        content: "";
      }
      .popover.bottom > .arrow {
        left: 50%;
        margin-left: -11px;
        border-top- 0;
        border-bottom-color: rgba(0,0,0,.07);
        top: -11px;
      }
      .popover.bottom > .arrow:after {
        content: " ";
        top: 1px;
        margin-left: -10px;
        border-top- 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- 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;
         0;
        height: 0;
        border-color: transparent;
        border-style: solid;
        
      }
      .popover > .arrow {
        border- 11px;
      }
      .popover > .arrow:after {
        border- 10px;
        content: "";
      }
      .popover.bottom > .arrow {
        left: 50%;
        margin-left: -11px;
        border-top- 0;
        border-bottom-color: rgba(0,0,0,.07);
        top: -11px;
      }
      .popover.bottom > .arrow:after {
        content: " ";
        top: 1px;
        margin-left: -10px;
        border-top- 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- 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;
         0;
        height: 0;
        border-color: transparent;
        border-style: solid;
        
      }
      .popover > .arrow {
        border- 6px;
      }
      .popover > .arrow:after {
        border- 5px;
        content: "";
      }
      .popover.bottom > .arrow {
        left: 30%;
        margin-left: -16px;
        border-top- 0;
        border-bottom-color: rgba(0,0,0,.07);
        top: -6px;
      }
      .popover.bottom > .arrow:after {
        content: " ";
        top: 1px;
        margin-left: -5px;
        border-top- 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>
    
  • 相关阅读:
    使用密码解密TACACS+的报文
    C9K Stackwise Virtual(三)
    Webhook Configuration Example
    sup-bootflash和bootflash
    WLC5508 license没有500个?
    AAA Server Groups
    关于FlexConnect的Bug!
    Bug搬运工-CSCve57121--Cisco 2800, 3800 and 1560 series APs fail to pass traffic
    Bug搬运工-CSCvb29354-1810 OEAP cannot join vWLC
    阿里云云计算认证ACP模拟考试练习题第1套模拟题分享(共10套)
  • 原文地址:https://www.cnblogs.com/xmyun/p/9766888.html
Copyright © 2020-2023  润新知