angular 表格列宽拖拽指令

背景

产品说表格里的某一列使用场景很高,但是宽度不够,总是需要悬浮才能查看全部内容,能不能做成可拖拽的列????

网上看了一圈,有人实现了,但是我在尝试的时候总是有各种问题。。。。。

以下是指令代码

import {
  AfterViewInit,
  Directive,
  ElementRef,
  EventEmitter,
  Inject,
  Input,
  NgZone,
  HostListener,
  OnDestroy,
  Output,
  Renderer2,
} from '@angular/core';
import { DOCUMENT } from '@angular/common';
import { fromEvent, Subscription } from 'rxjs';

@Directive({
  selector: '[appThResize]'
})
export class ThResizeDirective implements AfterViewInit, OnDestroy {
  @Input() tableViewRefElement: ElementRef;

  @Output() resizeEndEvent: EventEmitter<{ width: number }> = new EventEmitter<{ width: number }>();
  @Output() resizeStartEvent: EventEmitter<MouseEvent> = new EventEmitter<MouseEvent>();
  @Output() resizingEvent: EventEmitter<{ width: number }> = new EventEmitter<{ width: number }>();

  private document: Document;
  private resizeNodeEvent: any;
  private mouseenterNodeEvent: any;
  private mouseleaveNodeEvent: any;
  private nextElement: any;
  private element: HTMLElement;
  private resizeHandleElement: HTMLElement;
  private resizeOverlay: HTMLElement;
  private tableElement: HTMLElement;
  private resizeBarRefElement: HTMLElement;

  private resizing = false;
  private minWidth: string | number = 50;
  private maxWidth: string | number;
  private initialWidth: number;
  private totalWidth: number;
  private mouseDownScreenX: number;
  private subscription: Subscription;

  constructor(
    element: ElementRef,
    private renderer2: Renderer2,
    private zone: NgZone,
    @Inject(DOCUMENT) private doc: any,
  ) {
    this.element = element.nativeElement;
    this.document = this.doc;
  }

  ngAfterViewInit(): void {
    this.setResizeHandle();
  }

  @HostListener('mousedown', ['$event'])
  onMousedown(event: MouseEvent): void {
    const isHandle = (<HTMLElement>event.target).classList.contains('resize-handle');

    if (isHandle) {
      this.resizeStartEvent.emit(event);

      this.initialWidth = this.element.clientWidth;
      const initialOffset = this.element.offsetLeft;
      this.mouseDownScreenX = event.clientX;
      event.stopPropagation();
      this.nextElement = this.element.nextElementSibling;
      this.resizing = true;
      this.totalWidth = this.nextElement ? this.initialWidth + this.nextElement.clientWidth : this.initialWidth;

      this.resizeOverlay = this.renderer2.createElement('div');
      this.renderer2.appendChild(this.element.firstElementChild, this.resizeOverlay);
      this.renderer2.addClass(this.resizeOverlay, 'table-resize-overlay');
      this.renderer2.listen(this.resizeOverlay, 'click', (clickEvent: Event) => clickEvent.stopPropagation());

      this.renderer2.addClass(this.tableViewRefElement.nativeElement, 'table-view-selector');

      if (!this.resizeBarRefElement) {
        const resizeBar = this.renderer2.createElement('div');
        this.renderer2.addClass(resizeBar, 'table-resize-bar');

        this.tableElement = this.tableViewRefElement.nativeElement.querySelector('.table-wrap table');
        if (this.tableElement) {
          this.renderer2.appendChild(this.tableElement, resizeBar);
          this.renderer2.setStyle(resizeBar, 'display', 'block');
          this.renderer2.setStyle(resizeBar, 'left', initialOffset + this.initialWidth + 'px');
          this.resizeBarRefElement = resizeBar;
        }
      }

      this.renderer2.addClass(this.element, 'table-hover-bg');

      const mouseup = fromEvent(this.document, 'mouseup');
      this.subscription = mouseup.subscribe((ev: any) => this.onMouseup(ev));

      this.zone.runOutsideAngular(() => {
        this.document.addEventListener('mousemove', this.bindMousemove);
      });
    }
  }

  onMouseup(event: MouseEvent): void {
    this.zone.run(() => {
      const movementX = event.clientX - this.mouseDownScreenX;
      const newWidth = this.initialWidth + movementX;
      const finalWidth = this.getFinalWidth(newWidth);
      this.resizing = false;

      this.renderer2.removeChild(this.element, this.resizeOverlay);
      this.renderer2.removeClass(this.tableViewRefElement.nativeElement, 'table-view-selector');
      this.renderer2.removeClass(this.element, 'table-hover-bg');
      if (this.tableElement) {
        this.renderer2.removeChild(this.tableElement, this.resizeBarRefElement);
      }

      this.resizeEndEvent.emit({ width: finalWidth });
    });
    if (this.subscription && !this.subscription.closed) {
      this._destroySubscription();
    }

    this.document.removeEventListener('mousemove', this.bindMousemove);
  }

  bindMousemove = (e) => {
    this.move(e);
  }

  move(event: MouseEvent): void {
    const movementX = event.clientX - this.mouseDownScreenX;
    const newWidth = this.initialWidth + movementX;

    const finalWidth = this.getFinalWidth(newWidth);
    if (finalWidth <= this.minWidth) {
      this.resizingEvent.emit({ width: finalWidth });
      this.onMouseup(event);
      return;
    }
    const leftWidth = finalWidth + this.element.offsetLeft;
    if (this.resizeBarRefElement) {
      this.renderer2.setStyle(this.resizeBarRefElement, 'left', `${leftWidth}px`);
    }
    this.resizingEvent.emit({ width: finalWidth });
  }

  private getFinalWidth(newWidth: number): number {
    const minWidth = this.handleWidth(this.minWidth);
    const maxWidth = this.handleWidth(this.maxWidth);

    const overMinWidth = !this.minWidth || newWidth >= minWidth;
    const underMaxWidth = !this.maxWidth || newWidth <= maxWidth;

    const finalWidth = !overMinWidth ? minWidth : !underMaxWidth ? maxWidth : newWidth;
    return finalWidth;
  }

  private handleWidth(width: string | number): number {
    if (!width) {
      return;
    }
    if (typeof width === 'number') {
      return width;
    }
    if (width.includes('%')) {
      const tableWidth = this.tableViewRefElement.nativeElement.clientWidth;
      return (tableWidth * parseInt(width, 10)) / 100;
    }
    return parseInt(width.replace(/[^\d]+/, ''), 10);
  }

  private _destroySubscription(): void {
    if (this.subscription) {
      this.subscription.unsubscribe();
      this.subscription = undefined;
    }
  }

  private setResizeHandle(): void {
    this.resizeHandleElement = this.renderer2.createElement('span');
    this.renderer2.addClass(this.resizeHandleElement, 'resize-handle');
    this.renderer2.appendChild(this.element, this.resizeHandleElement);

    this.resizeNodeEvent = this.renderer2.listen(this.resizeHandleElement, 'click', (event) => event.stopPropagation());

    this.mouseenterNodeEvent = this.renderer2.listen(this.resizeHandleElement, 'mouseenter', (event) => {
      this.initialWidth = this.element.clientWidth;
      const initialOffset = this.element.offsetLeft;

      event.stopPropagation();

      const resizeBar = this.renderer2.createElement('div');
      this.renderer2.addClass(resizeBar, 'table-resize-bar');
      console.log('123',this.tableViewRefElement);
      
      this.tableElement = this.tableViewRefElement.nativeElement.querySelector('.table-wrap table');
      if (this.tableElement) {
        this.renderer2.appendChild(this.tableElement, resizeBar);
        this.renderer2.setStyle(resizeBar, 'display', 'block');
        this.renderer2.setStyle(resizeBar, 'left', initialOffset + this.initialWidth + 'px');
        this.resizeBarRefElement = resizeBar;
      }
    });

    this.mouseleaveNodeEvent = this.renderer2.listen(this.resizeHandleElement, 'mouseleave', (event) => {
      event.stopPropagation();

      if (!this.resizing && this.tableElement && this.resizeBarRefElement) {
        this.renderer2.removeChild(this.tableElement, this.resizeBarRefElement);
      }
    });
  }

  ngOnDestroy(): void {
    this._destroySubscription();
    if (this.resizeNodeEvent) {
      this.resizeNodeEvent();
    }
    if (this.mouseenterNodeEvent) {
      this.mouseenterNodeEvent();
    }
    if (this.mouseleaveNodeEvent) {
      this.mouseleaveNodeEvent();
    }
  }
}

导出指令就不展示代码了

页面应用代码

<div class="table-container" #tableView>
  <div class="table-wrap">
    <nz-table #rowSelectionTable nzShowSizeChanger [nzData]="listOfData" nzTableLayout="fixed">
      <thead>
        <tr>
          <th
            *ngFor="let c of columns"
            [nzWidth]="c.width"
            appThResize
            [tableViewRefElement]="tableViewRefElement"
            (resizeEndEvent)="onResize($event)"
            (resizeStartEvent)="onBeginResize($event)"
            (resizingEvent)="onResizing($event, c)">
              {{c.name}}
          </th>
        </tr>
      </thead>
      <tbody>
        <tr *ngFor="let data of rowSelectionTable.data">
          <td>{{ data.name }}</td>
          <td>{{ data.age }}</td>
          <td>{{ data.address }}</td>
        </tr>
      </tbody>
    </nz-table>
  </div>
</div>

ts应用代码

import { Component, OnInit, AfterViewInit, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';

interface Column {
  name: string;
  width?: string;
}

@Component({
  selector: 'app-table',
  templateUrl: './table.component.html',
  styleUrls: ['./table.component.less']
})
export class TableComponent implements OnInit, AfterViewInit {
  @ViewChild('tableView', { static: true }) tableViewRefElement: ElementRef;
  listOfData = [];
  columns: Column[] = [
    {
      name: 'Name',
    },
    {
      name: 'Age',
    },
    {
      name: 'Stress',
    },
  ];

  constructor(
    private el: ElementRef,
    private cdr: ChangeDetectorRef,
  ) { }

  ngOnInit(): void {
    this.listOfData = new Array(200).fill(0).map((_, index) => ({
      id: index,
      name: `Edward King ${index}`,
      age: 32,
      address: `London, Park Lane no. ${index}`
    }));
  }

  ngAfterViewInit(): void {
    // 初始化固定每列的宽度
    setTimeout(() => {
      this.el.nativeElement.querySelectorAll('th').forEach((e: HTMLElement, index: number) => {
        this.columns[index].width = e.offsetWidth + 'px';
      });
    });
  }

  onResize($event): void {
    // console.log($event);
    // console.log('resize事件结束');
  }

  onBeginResize($event): void {
    // console.log($event);
    // console.log('resize事件开始');
  }

  onResizing({ width }, c: Column): void {
    console.log(width);
    // console.log('resize事件进行中');
    c.width = width + 'px';
    this.cdr.detectChanges();
  }
}

:host ::ng-deep {
  .table-resize-overlay {
    position: absolute;
    display: block;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    z-index: 1000;
  }

  .table-view-selector {
    user-select: none;
    cursor: col-resize;

    .ant-table-thead th.ant-table-column-has-sorters {
      user-select: none;
      cursor: col-resize;
    }
  }

  .table-resize-bar {
    top: 0;
    bottom: 0;
    position: absolute;
    cursor: col-resize;
    border-right: 1px dashed #adb0b8;
    z-index: 9999;
    display: none;
  }

  .table-hover-bg {
    background: #F2F2F3;
    pointer-events: none;

    &+th {
      pointer-events: none;
    }
  }

  .resize-handle {
    display: inline-block;
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    width: 5px;
    cursor: col-resize;

    &:hover {
      border-right: 1px dashed #adb0b8;
    }
  }

  .ant-table-thead>tr:first-child>th:last-child {

    .resize-handle {
      display: none;
    }
  }

  .table-wrap {
    width: 100%;
    overflow-x: auto;
    overflow-y: hidden;
  }

  .ant-table tfoot>tr>td,
  .ant-table tfoot>tr>th,
  .ant-table-tbody>tr>td,
  .ant-table-thead>tr>th {
    padding: 10px;
  }
}

链接: 参考地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值