bixi
Version:
企业级中后台前端解决方案
339 lines (305 loc) • 9.12 kB
text/typescript
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;
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;
get rows() {
return this._rows;
}
set rows(value: IEditableRow[]) {
this._rows = cloneDeep(value);
}
_cols: IEditableCol[] = [];
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;
});
}
scroll = { y: '300px' };
dragable = false;
footer?: TemplateRef<{}> | string | null;
validate: (fg: FormGroup, rows: IEditableRow[], row: IEditableRow | null) => { key: string; message: string }[];
deleteRow: EventEmitter<IEditableRow> = new EventEmitter<IEditableRow>();
addRow: EventEmitter<FormGroup> = new EventEmitter<FormGroup>();
rowsChange: EventEmitter<IEditableRow[]> = new EventEmitter<IEditableRow[]>();
dirtyRowsChange: EventEmitter<IEditableRow[]> = new EventEmitter<IEditableRow[]>();
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();
}
}