UNPKG

bixi

Version:

企业级中后台前端解决方案

339 lines (305 loc) 9.12 kB
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output, TemplateRef, ViewChild, ViewEncapsulation } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormControl, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; import { BixiI18nService, IEditableI18n } from '@bixi/core/i18n'; import { log } from '@bixi/core/utils'; import cloneDeep from 'lodash.clonedeep'; import { NzSafeAny } from 'ng-zorro-antd/core/types/any'; import { NzSelectComponent } from 'ng-zorro-antd/select'; import { Observable, of, Subscription } from 'rxjs'; import { IEditableCol, IEditableRow, IEditableSelectCol, IEditableSelectOption, IEditableSelectOptions, IValidatorError } from './editable-table.type'; import { EditableTemplateService } from './editable-template.service'; export type IOnTouchedType = () => NzSafeAny; export type IOnChangeType = (value: NzSafeAny) => void; @Component({ selector: 'bixi-editable-table', exportAs: 'bixiEditableTable', host: { '[class.bixi-editable-table]': 'true' }, templateUrl: 'editable-table.component.html', providers: [ EditableTemplateService, { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => BixiEditableTableComponent), multi: true }], encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush }) export class BixiEditableTableComponent implements ControlValueAccessor, OnInit, OnDestroy { isValid: boolean = true; form: FormGroup; editedRows: IEditableRow[] = []; editRow: IEditableRow; cacheRow: IEditableRow | null = null; editRowIndex: number | null = null; // 当前编辑行数 _rows: IEditableRow[] = []; addText: string; i18nSubscription = new Subscription(); editTranstions: IEditableI18n; @Input() get rows() { return this._rows; } set rows(value: IEditableRow[]) { this._rows = cloneDeep(value); } _cols: IEditableCol[] = []; @Input() get cols() { return this._cols; } set cols(cols: IEditableCol[]) { this._cols = cols.map((col) => { col = col as IEditableSelectCol; const { options } = col; if (options) { let _options: IEditableSelectOptions; if (!(options instanceof Observable)) { _options = of(options); } else { _options = options; } col.options = _options; } return col; }); } @Input() scroll = { y: '300px' }; @Input() dragable = false; @Input() footer?: TemplateRef<{}> | string | null; @Input() validate: (fg: FormGroup, rows: IEditableRow[], row: IEditableRow | null) => { key: string; message: string }[]; @Output() deleteRow: EventEmitter<IEditableRow> = new EventEmitter<IEditableRow>(); @Output() addRow: EventEmitter<FormGroup> = new EventEmitter<FormGroup>(); @Output() rowsChange: EventEmitter<IEditableRow[]> = new EventEmitter<IEditableRow[]>(); @Output() dirtyRowsChange: EventEmitter<IEditableRow[]> = new EventEmitter<IEditableRow[]>(); @ViewChild('editContent') firstEditingEle: ElementRef | NzSelectComponent; // tslint:disable-next-line: no-empty onTableChange: IOnChangeType = () => { }; // tslint:disable-next-line: no-empty onTableTouched: IOnTouchedType = () => { }; constructor( private fb: FormBuilder, private i18n: BixiI18nService, private cdr: ChangeDetectorRef ) { } getSelectOptionName(options: IEditableSelectOption[], key: string) { const target = options.find((option) => option.value === key); if (target) { return target.name; } return key; } ngOnInit() { this.form = this.fb.group({}); this.collectFormGroup(); if (!this.editTranstions) { this.i18nSubscription = this.i18n.localeChange.subscribe(() => this.setTranslation()); } } private setTranslation() { this.editTranstions = this.i18n.getLocaleData('editableTable') || {}; this.addText = this.editTranstions.addText; this.cdr.markForCheck(); } get isEditing() { return this.editRowIndex !== null; } private collectFormGroup() { if (this.cols) { this.cols.forEach((col) => { this.form.addControl(col.key, new FormControl(null, col.validators)); }); } } private checkValidity() { this.cols.forEach((col) => { if (col.editable !== false) { const control = this.form.get(col.key); if (control) { control.markAsDirty(); control.updateValueAndValidity(); } } }); if (this.validate) { const compose = this.validate(this.form, this._rows, (this.isEditing) ? { ...this.editRow, ...this.form.value } : null); if (!compose) return; compose.forEach((item: IValidatorError) => { const control = this.form.controls[item.key]; if (!control) { log('bixi-editable-table', `校验 ${item.key} 未找到相对于控制器, 请查看校验返回数据是否和列配置对应!`); } control.setErrors({ message: item.message }); }); } } private startEdit(row: IEditableRow, index: number): void { if (this.isEditing && this.editDirty) { return; // 不可多行编辑 } this.form.reset(); this.cacheRow = { ...row }; this.editRow = { ...row }; this.editRowIndex = index; this.form.patchValue(row); setTimeout(() => { if (this.firstEditingEle) { if (this.firstEditingEle instanceof NzSelectComponent) { this.firstEditingEle.focus(); } else { (this.firstEditingEle.nativeElement as HTMLInputElement).focus(); } } }); } get editDirty() { return this.form.dirty; } reset() { this.editRow = {}; this.cacheRow = {}; this.editedRows = []; this.stopEdit(); } private stopEdit(): void { this.editRowIndex = null; } onConfirm() { if (!this.isEditing) return; this.checkValidity(); if (!this.form.valid) { return; } if (this.isEditing) { const saveRow = { ...this.editRow, ...this.form.value }; this.rows.splice(this.editRowIndex || 0, 1, saveRow); this.collectRowChange(saveRow); this.comfirmChange(); this.onTableChange(this.rows); } this.stopEdit(); } onCancel() { if (this.isEditing) { if (this.cacheRow) { this.rows.splice(this.editRowIndex || 0, 1, this.cacheRow); } else { // cacheRow 为空,表示此次取消是新建内容取消 this.rows.splice(this.editRowIndex || 0, 1); } this.stopEdit(); } } onClear(input: HTMLInputElement, key: string) { input.value = ''; this.form.controls[key].markAsDirty(); this.form.patchValue({ [key]: '' }); this.form.controls[key].updateValueAndValidity(); this.onChange(input.value, key); } onChange(value: string, key: string) { // 无效的 valueChange(patchValue) 不触发 if (typeof this.editRow === 'object' && !this.rowEqual(this.editRow)) { this.editRow[key] = value; if (!value) return; const changedCol = this.cols.find(col => col.key === key); if (changedCol && changedCol.onChange) { changedCol.onChange(this.form); } } } private rowEqual(change: { [key: string]: NzSafeAny; }): boolean { let rtn = true; if (Object.keys(change).length !== Object.keys(this.form.value).length) return false; Object.keys(change).forEach((key) => { if (this.form.get(key)?.value !== change[key]) { rtn = false; } }); return rtn; } onDelete(index: number) { if (this.isEditing) { return; } this.collectRowChange({ ...this.rows[index], __deleted: true }); const row = this.rows.splice(index, 1); this.comfirmChange(); this.deleteRow.emit(row); } private collectRowChange(row: IEditableRow) { if (row) { this.editedRows.push(row); } } comfirmChange() { this.rowsChange.emit(this.rows); this.dirtyRowsChange.emit(this.editedRows); } drop(event: CdkDragDrop<IEditableRow[]>): void { moveItemInArray(this._rows, event.previousIndex, event.currentIndex); this.comfirmChange(); } onAddRow() { if (this.isEditing) { return; } this._rows = [ ...this.rows, {} ]; this.startEdit({}, this._rows.length - 1); this.cacheRow = null; setTimeout(() => { // 因为 rows 变化渲染过程中会改变 form 中的值,这里等渲染完成后再赋值 this.addRow.emit(this.form); }); } writeValue(value: IEditableRow[]): void { this.rows = value; this.cdr.markForCheck(); } registerOnChange(fn: IOnChangeType): void { this.onTableChange = fn; } registerOnTouched(fn: IOnTouchedType): void { this.onTableTouched = fn; } ngOnDestroy() { this.i18nSubscription.unsubscribe(); } }