You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 4中Mat Input货币小数样式及失焦小数上标实现方案

嘿,我来帮你搞定这个Angular 4里Mat Input的货币格式问题!首先得说,Material Chip确实不适合这个场景——它是用来展示标签/选择项的,和输入框的编辑逻辑完全不搭,咱们换个思路来实现你要的效果:输入时显示常规数字,失焦后小数部分用上标呈现。

推荐方案:自定义货币输入组件

因为Mat Input本质是原生input的封装,没法直接渲染富文本(比如<sup>上标标签),所以咱们做一个“视觉切换”的组件:失焦时显示格式化后的文本,聚焦时切换回可编辑的输入框,同时保证表单值的正确性。

1. 组件模板(currency-input.component.html)

<div class="currency-input-container" (click)="focusInput()">
  <!-- 失焦时展示的格式化文本 -->
  <span *ngIf="!isFocused" class="display-text">
    {{ integerPart }}<sup>{{ decimalPart }}</sup>
  </span>
  <!-- 聚焦时显示的Mat Input -->
  <mat-form-field *ngIf="isFocused" class="input-field">
    <input matInput [(ngModel)]="value" (focus)="onInputFocus()" (blur)="onInputBlur()">
  </mat-form-field>
</div>

2. 组件逻辑(currency-input.component.ts)

import { Component, Input, Output, EventEmitter, OnInit, OnChanges } from '@angular/core';

@Component({
  selector: 'app-currency-input',
  templateUrl: './currency-input.component.html',
  styleUrls: ['./currency-input.component.scss']
})
export class CurrencyInputComponent implements OnInit, OnChanges {
  @Input() value: string = '';
  @Output() valueChange = new EventEmitter<string>();
  
  isFocused: boolean = false;
  integerPart: string = '0';
  decimalPart: string = '00';
  private readonly decimalPlaces: number = 2;

  ngOnInit() {
    this.formatDisplayValue();
  }

  ngOnChanges() {
    // 外部值变化时同步更新显示
    this.formatDisplayValue();
  }

  onInputFocus() {
    this.isFocused = true;
  }

  onInputBlur() {
    this.isFocused = false;
    // 输入完成后同步值到父组件,并重新格式化显示
    this.valueChange.emit(this.value);
    this.formatDisplayValue();
  }

  focusInput() {
    if (!this.isFocused) {
      // 点击显示文本时切换到输入框并自动聚焦
      this.isFocused = true;
      setTimeout(() => {
        const inputEl = document.querySelector('.input-field input') as HTMLInputElement;
        inputEl?.focus();
      }, 0);
    }
  }

  private formatDisplayValue() {
    if (!this.value) {
      this.integerPart = '0';
      this.decimalPart = '0'.repeat(this.decimalPlaces);
      return;
    }

    const numValue = parseFloat(this.value);
    if (isNaN(numValue)) {
      this.integerPart = '0';
      this.decimalPart = '0'.repeat(this.decimalPlaces);
      return;
    }

    // 拆分整数和小数部分
    const fixedValue = numValue.toFixed(this.decimalPlaces);
    const [integer, decimal] = fixedValue.split('.');
    this.integerPart = integer;
    this.decimalPart = decimal;
  }
}

3. 组件样式(currency-input.component.scss)

.currency-input-container {
  display: inline-block;
  cursor: text;
  font-size: 16px;
  min-width: 120px;

  .display-text {
    padding: 8px 12px;
    display: inline-block;

    sup {
      font-size: 0.7em;
      vertical-align: super;
      margin-left: 2px;
    }
  }

  .input-field {
    margin: 0;
    width: 100%;

    mat-form-field {
      width: 100%;
      font-size: 16px;
    }
  }
}

如何使用这个组件

在父组件的模板里直接用双向绑定就行,和普通Mat Input的用法几乎一样:

<app-currency-input [(ngModel)]="productPrice"></app-currency-input>
备选方案:指令+CSS视觉模拟(适合简单场景)

如果不想写完整组件,也可以用指令配合CSS来模拟效果,但这个方案只是视觉层面的,需要额外处理文本同步,不如组件方案可靠:

  1. 先写一个监听焦点的指令:
import { Directive, ElementRef, HostListener, Renderer2 } from '@angular/core';

@Directive({
  selector: '[appCurrencySuperscript]'
})
export class CurrencySuperscriptDirective {
  private decimalSpan: HTMLElement;

  constructor(private el: ElementRef, private renderer: Renderer2) {
    // 创建用来显示小数上标的span
    this.decimalSpan = this.renderer.createElement('span');
    this.renderer.addClass(this.decimalSpan, 'decimal-sup');
    this.renderer.appendChild(this.el.nativeElement.parentElement, this.decimalSpan);
  }

  @HostListener('focus') onFocus() {
    this.renderer.addClass(this.el.nativeElement.parentElement, 'focused');
  }

  @HostListener('blur') onBlur() {
    this.renderer.removeClass(this.el.nativeElement.parentElement, 'focused');
    this.updateDecimalText();
  }

  @HostListener('input') onInput() {
    this.updateDecimalText();
  }

  private updateDecimalText() {
    const value = this.el.nativeElement.value;
    if (!value) {
      this.decimalSpan.textContent = '00';
      return;
    }
    const numValue = parseFloat(value);
    if (isNaN(numValue)) {
      this.decimalSpan.textContent = '00';
      return;
    }
    const decimalPart = numValue.toFixed(2).split('.')[1];
    this.decimalSpan.textContent = decimalPart;
  }
}
  1. 模板和CSS:
<div class="currency-wrapper">
  <input matInput appCurrencySuperscript [(ngModel)]="price">
</div>
.currency-wrapper {
  position: relative;
  display: inline-block;

  input {
    padding-right: 30px;
    transition: color 0.2s;
  }

  .decimal-sup {
    position: absolute;
    right: 10px;
    top: 4px;
    font-size: 0.7em;
    opacity: 0;
    transition: opacity 0.2s;
  }

  &:not(.focused) input {
    color: transparent;
  }

  &:not(.focused) .decimal-sup {
    opacity: 1;
  }
}
为啥Material Chip没用?

Chip组件的定位是展示可选择/可移除的标签项,它没有输入框的编辑能力,也没法和表单值做双向绑定,所以完全不适合你要的“输入时编辑、失焦时格式化”的交互场景,咱们还是得围绕表单输入控件来定制才行。

内容的提问来源于stack exchange,提问作者dhana

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 07:37:19