import { InputGroupComponent } from '@farris/ui-input-group'; import { ChangeDetectorRef } from '@angular/core'; /* * @Author: 疯狂秀才(Lucas Huang) * @Date: 2019-07-10 11:44:49 * @LastEditors: 疯狂秀才(Lucas Huang) * @LastEditTime: 2019-11-05 11:44:18 * @QQ: 1055818239 * @Version: v0.0.1 */ import { Component, OnInit, ViewChild, ElementRef, OnChanges, Input, SimpleChanges, ContentChild, TemplateRef, QueryList, AfterContentInit, AfterViewInit, ContentChildren, Output, EventEmitter, ViewEncapsulation, OnDestroy, Inject, NgZone } from '@angular/core'; import { Subscription, Observable, of } from 'rxjs'; import PerfectScrollbar from 'perfect-scrollbar'; import { PerfectScrollbarComponent } from '@farris/ui-perfect-scrollbar'; import { PaginationInstance, PaginationControlsComponent } from '@farris/ui-pagination'; import { IdService } from '@farris/ui-common'; import { DataColumn } from '@farris/ui-common/column'; import { deepCopy } from './datatable-column'; import { ColumnDirective } from './datatable-column.component'; import { DataTableService } from './datatable.service'; import { DataTableHeaderComponent } from './table/datatable-header.component'; @Component({ selector: 'farrisui-datatable,farris-datatable', templateUrl: './datatable.component.html', styleUrls: ['./datatable.scss'], encapsulation: ViewEncapsulation.None, providers: [ DataTableService ] }) export class DataTableComponent implements OnInit, OnChanges, OnDestroy, AfterContentInit, AfterViewInit { searchButtonText = ''; @Input() keydownEnterEdit = false; @Input() id: string; @Input() size: string; @Input() allColumnsTitle = '所有列'; // table 尺寸 @Input() width; // 组件级高度包括过滤条高度 @Input() height; tableHeight: number; // 数据表高度 // 是否填充 @Input() fill = false; // 默认分页 @Input() pagination = true; @Input() pagerOnServer = true; @Input() pageSize = 10; @Input() pageIndex = 1; @Input() showPageInfo = true; @Input() showPageNumber = true; @Input() showPageList = true; @Input() pagerViewMode = 'default'; /** 显示表头 */ @Input() showHeader = true; private _pageList = [10, 20, 30, 50, 100]; get pageList() { return this._pageList; } @Input() set pageList(val) { this._pageList = val; if (this.pager) { this.pager.setPageList(val); } } @Input() total = 0; // 列 数据 @Input() columns: DataColumn[]; @Input() searchFields: { label: string, value: string }[]; // 可筛选 @Input() showFilterBar = false; // table 数据 _data = []; @Input() get data() { return this._data; } set data(data: Array) { this._data = data; } // 深拷贝data 数据 copyData: any; @Input() remote = 'client'; /** 启用远端排序 */ @Input() remoteSort = true; /** 排序字段 */ @Input() sortName: string; /** 排序方式 asc | desc */ @Input() sortOrder: string; /** 允许多列排序 */ @Input() multiSort = false; @Input() beforeSortColumn: (field: string, order: string) => Observable; // 多选 单选 @Input() singleSelect = true; @Input() idField = 'id'; // 显示鼠标悬停高亮 @Input() hover = true; // 斑马线 @Input() striped: boolean; // 边框 @Input() bordered: boolean; // 支持添加行 单元格 类样式 @Input() rowClassName: (row: any, index: number) => string; @Input() cellClassName: (value: any, col: any) => string; @Input() rowStyler: (args: any) => any; @Input() cellStyler: (val: any) => any; @Input() set selections(v) { this.dtBody.selections = v ? v : {}; } get selections() { return this.dtBody.selections; } // 滚动条引用 @ViewChild('scorllableBody') scorllableBody: ElementRef; @ViewChild('tableHeader') tableHeader: ElementRef; @ViewChild('tablePager') tablePager: ElementRef; @ViewChild('dtHeader') dtHeader: DataTableHeaderComponent; @ViewChild('dtBody') dtBody: any; @ViewChild('dtLeftBody') dtLeftBody: any; @ViewChild('dtRightBody') dtRightBody: any; @ViewChild('dtLeftFixed') dtLeftFixed: ElementRef; @ViewChild('dtRightFixed') dtRightFixed: ElementRef; @ViewChild('pager') pager: PaginationControlsComponent; @ViewChild('inputgroup') inputGroup: InputGroupComponent; // 分页事件 @Output() pageChanged = new EventEmitter(); @Output() pageSizeChanged = new EventEmitter(); @Output() search = new EventEmitter<{ field: string, value: string }>(); @Output() columnSorted = new EventEmitter(); @Output() rowDblClick = new EventEmitter(); @Output() selectedRow = new EventEmitter(); // 兼容 @Output() selectRows = new EventEmitter(); @Output() unSelectRow = new EventEmitter(); @Output() cellClick = new EventEmitter(); @Output() clearSearchValue = new EventEmitter(); @Output() checkAll = new EventEmitter(); @Output() sortChange = new EventEmitter(); @ContentChildren(ColumnDirective) columnsRef: QueryList; // 表尾 @ContentChild('footer') footer: TemplateRef; // 表格可拖拽宽度系列 // 拖拽线 @ViewChild('dragLine') dragLine: ElementRef; // 是否可拖拽 默认可以 @Input() resizableColumns = true; /** 针对同一条记录,单选时,多次单击后不取消选中。 */ @Input() keepSelect = true; // 是否有行模板 hasRowTepml = false; // 用户获取表头+表格内容的高度 宽度 等 datatableContainer: HTMLDivElement; // 拖拽线初始化位置 dragLineX: number; // 设置拖拽停止器 moveable = false; // currentColumn: any; // 设置左固定列 hasFixed: boolean; fixedLeftWidth: string; // 设置右固定列 fixedRightWidth: string; searchData = { field: '*', value: '' }; // 事件订阅存储 便于销毁 subscription: Subscription[] = []; // 固定列时 同一行的tr hover事件 headerTr: any; leftFixedHeaderTr: any; rightFixedHeaderTr: any; // 原数据 public filter = ''; public maxSize = 7; public directionLinks = true; public autoHide = false; public responsive = true; public paginationOptions: PaginationInstance = { id: 'Farris-DataTable-Pagination', itemsPerPage: this.pageSize, currentPage: this.pageIndex, pageList: this.pageList, totalItems: this.total, remote: this.pagerOnServer }; public labels: any = { previousLabel: ' ', nextLabel: ' ', screenReaderPaginationLabel: 'Pagination', screenReaderPageLabel: 'page', screenReaderCurrentLabel: `You're on page` }; private _currentRowIndex = -1; get currentRowIndex(): number { return this._currentRowIndex; } private _currentRow = undefined; get currentRow() { return this._currentRow; } @ViewChild('perfectScrollbar') perfectScrollbar: PerfectScrollbarComponent; scorllableBodyHeight: number; constructor(private dataService: DataTableService, private idService: IdService, private el: ElementRef, public cd: ChangeDetectorRef, private ngZone: NgZone) { this.dataService.selectedRow.subscribe((e: any) => { if (this.singleSelect) { this._currentRowIndex = e.rowIndex; this._currentRow = e.rowData; } else { if (this.selections) { this.dtHeader.isCheckAll = Object.keys(this.selections).length === this.data.length; } } }); this.dataService.unSelectedRow.subscribe((e: any) => { if (this.singleSelect) { this._currentRow = undefined; this._currentRowIndex = -1; } else { this.dtHeader.isCheckAll = false; } }); } private ps: PerfectScrollbar; ngOnInit() { setTimeout(() => { this.setBodyHeight(); this.ps = this.perfectScrollbar.directiveRef.ps(); }); if (!this.id) { this.id = `datatable_${this.idService.generate()}`; } this.paginationOptions.id = this.paginationOptions.id + this.id; this.copyData = deepCopy(this.data); if (!this.beforeSortColumn) { this.beforeSortColumn = () => of(true); } } private setBodyHeight() { this.tableHeight = this.height; if (this.showFilterBar) { this.tableHeight = this.height - 46; } if (this.showHeader) { this.scorllableBodyHeight = this.tableHeight - this.tableHeader.nativeElement.clientHeight; } if (this.pagination) { this.scorllableBodyHeight = this.scorllableBodyHeight - 50; } if (this.cd && !this.cd['destroyed']) { this.cd.detectChanges(); } } onClearSearchValue() { this.searchData.value = ''; this.cd.detectChanges(); this.clearSearchValue.emit(); } trackByRows = (index: number, row: any) => { return row[this.idField]; } ngOnChanges(changes: SimpleChanges) { if (changes.height && !changes.height.isFirstChange()) { this.setBodyHeight(); } if (changes.total && !changes.total.isFirstChange()) { this.paginationOptions.totalItems = changes.total.currentValue; } if (changes.pageSize) { this.paginationOptions.itemsPerPage = changes.pageSize.currentValue; } if (changes.pageIndex && !changes.pageIndex.isFirstChange()) { this.paginationOptions.currentPage = changes.pageIndex.currentValue; } if (changes.data && !changes.data.isFirstChange()) { const rows = changes.data.currentValue; this.updateCheckboxState(rows); this.paginationOptions = { ...this.paginationOptions }; this.dataService.loadSuccess.next(changes.data.currentValue); } } ngAfterContentInit() { // 支持列组件写入 if (!this.columns) { if (this.columnsRef && this.columnsRef.length) { this.columns = this.columnsRef.map(col => { return { width: col.width, title: col.title, field: col.field, align: col.align, fixed: col.fixed, className: col.className, multipleFilter: col.multipleFilter, filter: col.filter, media: col.media, sortable: col.sortable, edit: col.edit }; }); } } } ngOnDestroy() { this.subscription.forEach(sub => { sub.unsubscribe(); }); this.subscription = []; this.pager = null; } ngAfterViewInit() { // 获取表格容器 即表格 this.datatableContainer = this.el.nativeElement.querySelector('.farris-datatable'); // this.headerTr = this.tableHeader.nativeElement.querySelectorAll('tr'); } updateCheckboxState(rows: any[]) { const updateCheckAllStatus = (f) => { if (this.showHeader && this.dtHeader) { this.dtHeader.isCheckAll = f; } }; if (rows && rows.length) { if (this.selections) { const keys = Object.keys(this.dtBody._selections); if (keys.length) { let count = 0; const ids = rows.map((row: any) => { return row[this.idField].toString(); }); keys.forEach(id => { if (ids.indexOf(id) > -1) { count++; } }); updateCheckAllStatus(ids.length === count); } else { updateCheckAllStatus(false); } } else { updateCheckAllStatus(false); } } else { updateCheckAllStatus(false); } } /** * 获取表格容器的位置 距离左边视口和上边视口的距离 如果页面有滚动条 需要加上滚动条滚动的数值 */ getContainerOffset() { const rect = this.datatableContainer.getBoundingClientRect(); return { left: rect.left + document.body.scrollLeft, top: rect.top + document.body.scrollTop, right: rect.right, bottom: rect.bottom, }; } /** * 鼠标按下 开始记录拖拽线的位置 拖拽线到达当前鼠标位置 * @param e 鼠标对象 */ beginDrag(e) { this.dragLineX = e.pageX; event.preventDefault(); } /** * 鼠标移动 移动拖拽线位置变动 * @param e 鼠标对象 */ moveDrag(e) { // 获取表格的左边距离 const containerLeft = this.getContainerOffset().left; // 设置拖拽线的高度 即获取表头+表内容+表尾的高度 此表格结构包含了分页 因此要去掉分页的高度 if (this.tablePager) { this.dragLine.nativeElement.style.height = this.datatableContainer.offsetHeight - this.tablePager.nativeElement.offsetHeight + 'px'; } else { // const headerHeight = this.el.nativeElement.querySelector('.farris-table-header'); // const bodyHeight = this.el.nativeElement.querySelector('.ps-content'); this.dragLine.nativeElement.style.height = this.datatableContainer.offsetHeight + 'px'; } // 设置拖拽线的高度 拖拽线相对于表格relative定位是absolute,因此是0 this.dragLine.nativeElement.style.top = 0 + 'px'; // 鼠标移动时,拖拽线相对于表格的位置 this.dragLine.nativeElement.style.left = (e.pageX - containerLeft) + 'px'; // 鼠标移动 设置拖拽线总是可见 this.dragLine.nativeElement.style.display = 'block'; } // 重新计算表格宽度 /** * 鼠标抬起 重新计算单元格宽度 * 鼠标对象 */ stopDrag(e, column) { this.resizeColumn(e, column); } resizeColumn(e, column) { if (this.isCheckBox(column, 'dt-checkbox-cell')) { this.dragLine.nativeElement.style.display = 'none'; return; } // 偏移量 const delta = e.pageX - this.dragLineX; // 拖拽前列宽 const columnWidth = column.offsetWidth; // 拖拽后列宽 let newColumnWidth = columnWidth + delta; // 最小宽度 const minWidth = column.style.minWidth || 30; // 新宽度大于或等于最小宽度时 newColumnWidth = newColumnWidth > minWidth ? newColumnWidth : minWidth; // 重新设置宽度 let colIndex = -1; const othersCol = []; const cols = this.tableHeader.nativeElement.querySelectorAll('th'); // 父节点宽度 const parentWidth = column.parentElement.offsetWidth; // 判断是不是有checkbox const firstElement = column.parentElement.firstElementChild; const checkBoxWidth = this.isCheckBox(firstElement, 'dt-checkbox-cell') ? firstElement.offsetWidth : 0; // 其他列宽度 let othersWidth = parentWidth - columnWidth - checkBoxWidth; let newOthersWidth = parentWidth - newColumnWidth - checkBoxWidth; for (let i = 0; i < cols.length; i++) { if (cols[i] === column) { colIndex = i; } else { if (this.isCheckBox(cols[i], 'dt-checkbox-cell')) { continue; } const rate = cols[i].offsetWidth / othersWidth; if (rate > 1) { console.log(cols[i]); } let colWidth = newOthersWidth * rate; if (newOthersWidth * rate > 15) { colWidth = newOthersWidth * rate; } else { colWidth = 15; othersWidth = othersWidth - colWidth; newOthersWidth = newOthersWidth - colWidth; newColumnWidth = newColumnWidth - 15 + newOthersWidth * rate; } othersCol.push({ colIndex: i, colWidth }); } } this.resizeColGroup(this.dtHeader.el.nativeElement, colIndex, newColumnWidth, othersCol); this.resizeColGroup(this.dtBody.el.nativeElement, colIndex, newColumnWidth, othersCol); // 计算宽度完毕 设置拖拽线隐藏 this.dragLine.nativeElement.style.display = 'none'; } isCheckBox(elements, cName) { return !!elements.className.match(new RegExp('(\\s|^)' + cName + '(\\s|$)')); } resizeColGroup(table, resizeColumnIndex, newColumnWidth, othersColumn) { if (table) { // 此处要视不同的表格结构来确定 本组件中 header和body结构相同 const colGroup = table.childNodes[0].children[0].nodeName === 'COLGROUP' ? table.childNodes[0].children[0] : null; if (colGroup) { const col = colGroup.children[resizeColumnIndex]; col.style.width = newColumnWidth + 'px'; for (let i = 0; i < othersColumn.length; i++) { const otherCol = colGroup.children[othersColumn[i].colIndex]; if (otherCol && othersColumn[i].colWidth) { otherCol.style.width = othersColumn[i].colWidth + 'px'; } } } else { throw new Error('Scrollable tables require a colgroup to support resizable columns'); } } } onScrollX(e: any) { // 横向滚动 非固定表头滚动 const x = e.srcElement.scrollLeft; this.tableHeader.nativeElement.scrollTo(x, 0); } /** * 滚动条纵向滚动 */ onScrollY(e: any) { if (!this.hasFixed) { return; } const y = e.srcElement.scrollTop; this.dtLeftFixed.nativeElement.style.top = -y + 'px'; this.dtRightFixed.nativeElement.style.top = -y + 'px'; } onPageChange(page: { pageIndex: number, pageSize: number }) { if (this.pageIndex !== page.pageIndex) { this.pageIndex = page.pageIndex; this.paginationOptions.currentPage = page.pageIndex; this.pageChanged.emit({ pageInfo: page, search: this.searchData, sortName: this.sortName, sortOrder: this.sortOrder }); } } onPageSizeChange(pageSize: number) { if (this.pageSize !== pageSize && this.total) { this.paginationOptions.itemsPerPage = pageSize; this.pageSize = pageSize; const total = this.total; let pageLength = Math.floor(total / pageSize); if (total % pageSize > 0) { pageLength += 1; } if (pageLength && this.pageIndex > pageLength) { this.pageIndex = pageLength; this.paginationOptions.currentPage = this.pageIndex; } this.pageSizeChanged.emit({ pageInfo: { pageIndex: this.pageIndex, pageSize }, search: this.searchData, sortName: this.sortName, sortOrder: this.sortOrder }); } } onSearch($event) { if ($event.originalEvent) { $event.originalEvent.stopPropagation(); } this.search.emit(this.searchData); } onCheckAll(state: boolean) { this.dataService.selectedAll.next(state); this.checkAll.emit(state); } onSelectedRow(e: any) { this.selectedRow.emit({ data: e.rowData, index: e.rowIndex }); // 兼容 this.selectRows.emit({ data: e.rowData, index: e.rowIndex }); } // tslint:disable-next-line:no-shadowed-variable resize(size: { width: number, height: number }) { this.width = size.width; this.height = size.height; this.setBodyHeight(); } loadData(e: { pageSize: number, total: number, data: any, pageIndex: number }) { this.data = e.data; if (this.pagination) { const { total = 0, pageSize = 20, pageIndex = 1 } = { ...e }; this.paginationOptions.totalItems = total; this.paginationOptions.itemsPerPage = pageSize; this.paginationOptions.currentPage = pageIndex; this.paginationOptions.pageList = this.pageList; this.total = total; this.pageSize = pageSize; this.pageIndex = pageIndex; } this.cd.detectChanges(); this.updateCheckboxState(this.data); } /* */ onCellClick(e) { this.cellClick.emit(e); } /* 添加行 */ addRows(dataItem) { this.data = this.data.concat(dataItem); } /* 删除行 */ removeRows() { const SELECTIONS = this.selections; if (this.singleSelect) { for (let i = 0; i < this.data.length; i++) { if (this.data[i] === SELECTIONS) { this.data.splice(i, 1); } } } else { for (let i = 0; i < SELECTIONS.length; i++) { for (let j = 0; j < this.data.length; j++) { if (SELECTIONS[i][this.idField] === this.data[j][this.idField]) { this.data.splice(j, 1); } } } } } checkRow(rid: any, emit = false) { let rowIndex = -1; const rowData = this.data.find((n, i) => { if (n[this.idField] == rid) { rowIndex = i; return true; } }); if (!rowData) { return; } if (!this.selections) { this.selections = { [rid]: rowData }; } else { this.selections = { [rid]: rowData, ...this.selections.reduce((r, n) => { r[n[this.idField]] = n; return r; }, {}) }; } this.dataService.selectedRow.next({ rowIndex, rowData }); if (emit) { const p = { data: rowData, index: rowIndex }; this.selectedRow.emit(p); // 兼容 this.selectRows.emit(p); } } unCheckRow(rid: any, emit = false) { let rowIndex = -1; const rowData = this.data.find((n, i) => { if (n[this.idField] == rid) { rowIndex = i; return true; } }); if (this.selections) { this.selections = this.selections.filter(n => n[this.idField] != rid).reduce((r, n) => { r[n[this.idField]] = n; return r; }, {}); this.dataService.unSelectedRow.next({ rowIndex, rowData }); if (emit) { this.unSelectRow.emit({ data: rowData, index: rowIndex }); } } } clearSelections() { } onKeydownEvent($event) { if (!$event || !this.singleSelect || !this.data || !this.data.length) { return; } if (!['ArrowDown', 'ArrowUp'].includes($event.code)) { return; } let newIdx; if ($event.code === 'ArrowDown') { newIdx = this._currentRowIndex + 1; if (newIdx >= this.data.length) { newIdx = 0; } } else if ($event.code === 'ArrowUp') { if (this._currentRowIndex !== -1) { newIdx = this._currentRowIndex - 1; } } if (newIdx >= 0) { this.dtBody.selectedRow($event, newIdx, this.data[newIdx]); } setTimeout(() => { // scroll intoView const trDoms = this.dtBody.el.nativeElement.querySelectorAll('.ui-table-tbody tr'); if (trDoms && trDoms[this._currentRowIndex]) { trDoms[this._currentRowIndex].scrollIntoView({ block: 'nearest' }); } }); } }