/* * @Author: 疯狂秀才(Lucas Huang) * @Date: 2019-08-05 10:26:50 * @LastEditors: 疯狂秀才(Lucas Huang) * @LastEditTime: 2020-11-16 11:43:34 * @QQ: 1055818239 * @Version: v0.0.1 */ import { Component, Input, Output, EventEmitter, forwardRef, OnInit, ViewChild, ElementRef, Injector, AfterViewInit, Renderer2, OnDestroy, OnChanges, SimpleChanges, HostBinding, ChangeDetectorRef, HostListener } from '@angular/core'; import { ControlValueAccessor, FormControl, NgControl, NgModel, NG_VALUE_ACCESSOR } from '@angular/forms'; import { BigNumber } from 'bignumber.js'; import { LocaleService } from '@farris/ui-locale'; import { CommonUtils } from '@farris/ui-common'; export interface NumberFormatter { /** 前置符号 */ prefix?: string; /** 后缀 */ suffix?: string; /** 小数点 */ decimalSeparator?: string; /** 千分位符号 */ groupSeparator?: string; /** 千分位分组 */ groupSize?: number; } @Component({ selector: 'farris-number-spinner', template: `
`, styleUrls: ['./number.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NumberSpinnerComponent), multi: true } ] }) export class NumberSpinnerComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges, OnDestroy { @HostBinding('class.f-cmp-number-spinner') hostClass = true; @Input() disabled = false; @Input() readonly = false; @Input() editable = true; // formatter 和 parser 必须同时存在 @Input() formatter: (val: number) => string; @Input() parser: (val: string | number) => number; // 空白提示文本 @Input() placeholder = ''; // up or down 步长 @Input() step = 1; // 最大值 @Input() max: any; // 最小值 @Input() min: any; /** 启用大数支持 */ @Input() bigNumber = false; // 是否显示加减按钮 @Input() showButton = true; // 是否使用千分值 @Input() useThousands = true; // 文本方向 @Input() textAlign = 'left'; // 自动补全小数 @Input() autoDecimal = true; // 允许为空 @Input() canNull = false; // 精度 @Input() precision = 0; // 前缀 @Input() prefix = ''; // 后缀 @Input() suffix = ''; /** 小数点符号 */ @Input() decimalSeparator = '.'; /** 千分位符号 */ @Input() groupSeparator = ','; /** 使用千分位时,每组显示的字符数 */ @Input() groupSize = 3; private _value = ''; @Input() get value() { return this._value; } set value(val: any) { this._value = val; } /** 显示0值 */ @Input() showZero = true; @Output() valueChange = new EventEmitter(); @Output() blur = new EventEmitter(); @Output() focus = new EventEmitter(); @ViewChild('input') input: ElementRef; isFocus = false; popValue = ''; isActiveTip = false; formatOptions: NumberFormatter = {}; private _realValue = null; cdRef: ChangeDetectorRef; commonUtils: CommonUtils; private _ngControl: NgControl; private showtipTimer = null; _updateOn = 'change'; localeService: LocaleService = null; onTouchedCallback: () => void = () => { }; onChangeCallback: (_: any) => void = () => { }; constructor(public el: ElementRef, public render: Renderer2, public injector: Injector) { this.cdRef = this.injector.get(ChangeDetectorRef, null); this.localeService = this.injector.get(LocaleService, null); this.commonUtils = this.injector.get(CommonUtils, new CommonUtils()); } ngOnInit(): void { this.formatOptions = this.buildFormatOptions(); this._ngControl = this.injector.get(NgControl, null); if (this.localeService) { const defaultPlaceHolder = this.localeService.getValue('numberSpinner.placeholder') || '请输入数字'; if (!this.placeholder) { this.placeholder = defaultPlaceHolder; } } } ngAfterViewInit() { if (this._ngControl && this._ngControl.control) { this._updateOn = this._ngControl.control.updateOn; } this.listenInputPasteEvent(); } ngOnChanges(changes: SimpleChanges) { if (changes.value && !changes.value.isFirstChange()) { this._realValue = this.getRealValue(changes.value.currentValue); this.value = this.format(this._realValue); } if (changes.showZero && !changes.showZero.isFirstChange()) { this.value = this.format(this._realValue); } if (changes.precision && !changes.precision.isFirstChange()) { this.onOptionsChanged(); } if (changes.useThousands && !changes.useThousands.isFirstChange()) { this.onOptionsChanged(); } if (changes.prefix && !changes.prefix.isFirstChange()) { this.onOptionsChanged(); } if (changes.suffix && !changes.suffix.isFirstChange()) { this.onOptionsChanged(); } } private onOptionsChanged() { this.formatOptions = this.buildFormatOptions(); this.value = this.format(this._realValue); } ngOnDestroy() {} // 支持粘贴带格式的数据 listenInputPasteEvent() { this.input.nativeElement.addEventListener('paste', event => { event.preventDefault(); const clipboardData = event.clipboardData || window['clipboardData']; const pasteValue = clipboardData.getData('text'); const val = this.cleanNumString(pasteValue); if (this.isEmpty(val)) { return; } const target = event.target; const start = (target as any).selectionStart; const end = (target as any).selectionEnd; if (this.isEmpty(val)) { target.value = ''; } else { target.value = target.value.slice(0, start) + val + target.value.slice(end); } if (this._updateOn === 'change') { this._realValue = this.getRealValue(target.value); this.value = this._realValue; this._modelChanged(this._realValue); } }); } onBlur($event, type: string = '') { if (this.readonly || this.disabled) { return; } if (this._updateOn === 'blur') { const val = this.cleanNumString($event.value); this._realValue = this.getRealValue(val); } this.value = this.format(this._realValue); this.input.nativeElement.value = this.value; this.isFocus = false; this.onModelChange(this._realValue, 'blur'); this.blur.emit({ event: $event, formatted: this.value, value: this._realValue, instance: this }); } onClick($event) { $event.stopPropagation(); } onFocus($event) { if (this.readonly || this.disabled) { this.isFocus = false; return; } this.value = this.isEmpty(this._realValue) ? '' : ((!this.showZero && this._realValue == '0') ? '' : this._realValue ); this.isFocus = true; this.focus.emit({ event: $event, formatted: this.value, value: this._realValue, instance: this }); } onMouseEnter($event) { if (this.value) { this.popValue = this.value; this.isActiveTip = this.isShowPopover(); } } onKeyDown(e: KeyboardEvent) { if (e.key === 'ArrowDown') { e.preventDefault(); this.down(e); e.stopPropagation(); } if (e.key === 'ArrowUp') { e.preventDefault(); this.up(e); e.stopPropagation(); } } private _modelChanged(realVal) { this._realValue = realVal; this.onChangeCallback(realVal); this.onTouchedCallback(); this.valueChange.emit(realVal); } onModelChange(realVal: any, updateOn = 'change') { // this._realValue = this.getRealValue(val); let _realValue = realVal; if (updateOn === 'change') { _realValue = this.getRealValue(realVal); } this.isActiveTip = false; this.popValue = this.format(_realValue); if (this._updateOn === updateOn) { this._modelChanged(_realValue); } } up(e: Event, type = null) { this.compute('up'); e.stopPropagation(); } down(e: Event, type = null) { this.compute('down'); e.stopPropagation(); } compute(tye = 'up') { if (this.isDisableOfBtn(tye)) { let _resultValue; const realBigNum = new BigNumber(this._realValue || 0); if (tye === 'up') { _resultValue = realBigNum.plus(Number(this.step)); } else { _resultValue = realBigNum.minus(Number(this.step)); } const s = _resultValue.toFixed(); if (!this.isFocus) { this.value = this.format(s); } else { this.value = s; } this.input.nativeElement.value = this.value; // this.onModelChange(this._getRealValue(_resultValue), 'blur'); this._modelChanged(this.getRealValue(_resultValue)); } } isDisableOfBtn(type: string, value?: any) { if (value === undefined) { value = this._realValue; } value = new BigNumber(value); if (type === 'up' && !(new BigNumber(this.max)).isNaN() && value.gte(this.max)) { return false; } if (type === 'down' && !(new BigNumber(this.min)).isNaN() && value.lte(this.min)) { return false; } return true; } isShowPopover() { const width = this.input.nativeElement.clientWidth; const { paddingLeft, paddingRight } = window.getComputedStyle(this.input.nativeElement); const _width = width - (Number(paddingLeft.replace('px', '')) || 0) - (Number(paddingRight.replace('px', '')) || 0); const txtWidth = this.commonUtils.getTextWidth(this.value, window.getComputedStyle(this.input.nativeElement).font); if (_width && _width < txtWidth) { return true; } return false; } _getPrecision() { return Number(this.precision || 0); } toFixed(n: BigNumber | number) { let _n = n; if (!BigNumber.isBigNumber(n)) { _n = new BigNumber(n); } if (this.precision !== null && this.precision !== undefined) { return n.toFixed(this._getPrecision()); } return n.toFixed(); } _getRealValue(_n: BigNumber) { const n = this.toFixed(_n); return this.bigNumber ? n : Number(n); } getRealValue(val: any) { if (this.parser) { if (!isNaN(Number(val))) { return val; } else { return this.parser(val); } } let _n = this.validInterval(new BigNumber(val)); if (_n.isNaN()) { if (this.canNull) { return null; } else { const minBigNum = new BigNumber('' + this.min); const maxBigNum = new BigNumber('' + this.max); if (!minBigNum.isNaN()) { _n = minBigNum; } else if (!maxBigNum.isNaN()) { _n = maxBigNum; } else { return 0; } } // if (this.canNull || minBigNum.isNaN()) { // return null; // } else { // _n = minBigNum; // } } return this._getRealValue(_n); } private buildFormatOptions() { return { prefix: this.prefix, suffix: this.suffix, decimalSeparator: this.decimalSeparator, groupSeparator: this.useThousands ? this.groupSeparator : '', groupSize: this.groupSize }; } isEmpty(val: any) { return isNaN(val) || val === null || val === undefined || val === ''; } validInterval(bn: BigNumber) { let _bnVal = bn; if (!this.isEmpty(this.max)) { const _maxBigNum = new BigNumber('' + this.max); if (bn.gt(_maxBigNum) ) { _bnVal = _maxBigNum; const _realValue = this._getRealValue(_maxBigNum); this._modelChanged(_realValue); } } if (!this.isEmpty(this.min)) { const _minBigNum = new BigNumber('' + this.min); if (bn.lt(_minBigNum)) { _bnVal = _minBigNum; const _realValue = this._getRealValue(_minBigNum); this._modelChanged(_realValue); } } return _bnVal; } format(val: any) { val = this.cleanNumString(val); const bigVal = new BigNumber(val); const _bgNum = this.validInterval(bigVal); if (_bgNum.valueOf() == '0' && !this.showZero) { return ''; } if (this.canNull && bigVal.isNaN()) { return ''; } else { if (_bgNum.isNaN()) { return ''; } } if (this.formatter) { return this.formatter(_bgNum.toNumber()); } else { if (!Object.keys(this.formatOptions).length) { this.formatOptions = this.buildFormatOptions(); } return this._toFormat(_bgNum, this.formatOptions); } } _toFormat(_bgNum: BigNumber, fmt: NumberFormatter) { if (this.precision !== null && this.precision !== undefined) { return _bgNum.toFormat(this._getPrecision(), fmt); } else { return _bgNum.toFormat(fmt); } } cleanNumString(val: any) { val = (val === null || val === undefined || val === '') ? '' : String(val); val = val.replace(new RegExp(this.prefix, 'g'), '') .replace(new RegExp(this.suffix, 'g'), '').replace(/\,/g, ''); if (this.groupSeparator && this.groupSeparator !== ',') { val = val.replace(new RegExp(`\\${this.groupSeparator}`, 'g'), ''); } if (this.decimalSeparator && this.decimalSeparator !== '.') { val = val.replace(new RegExp(`\\${this.decimalSeparator}`, 'g'), '.'); } return val; } updateValue(val) { val = this.cleanNumString(val); this._realValue = this.getRealValue(val); this.value = this.format(this._realValue); this.el.nativeElement.value = this.value; } private updateControlValue() { let _val = this.cleanNumString(this.value); if (this.parser) { _val = this.parser(this.value); } if (this.isEmpty(this._realValue) && this.isEmpty(_val)) { return; } const rv = new BigNumber(this._realValue); const cv = new BigNumber(_val); // display value if (rv.isNaN() && cv.isNaN()) { return; } // if (!rv.eq(cv)) { // } if (this._ngControl) { const formgroup = this._ngControl['formDirective']; if (formgroup) { let ctrl = formgroup.control.get(this._ngControl.name); if (!ctrl && formgroup.control.controls) { ctrl = formgroup.control.controls[this._ngControl.name]; } if (ctrl) { if (ctrl.value !== this._realValue) { ctrl.setValue(this._realValue); } } } } } writeValue(val: any): void { this._realValue = val; this.value = this.format(this._realValue); this.updateControlValue(); } registerOnChange(fn: any): void { this.onChangeCallback = fn; } registerOnTouched(fn: any): void { this.onTouchedCallback = fn; } setDisabledState?(isDisabled: boolean): void { this.disabled = isDisabled; } }