背景
产品说表格里的某一列使用场景很高,但是宽度不够,总是需要悬浮才能查看全部内容,能不能做成可拖拽的列????
网上看了一圈,有人实现了,但是我在尝试的时候总是有各种问题。。。。。
以下是指令代码
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;
}
}
链接: 参考地址