input 欄位輸入數字時自動插入千分位逗點

最近在改一個舊專案的 input 欄位的千分位問題,它一般可以正常運作,也就是輸入到超過千位時,每三位數自動加上千分位點。但是有時候壞掉,就會變成出現千分位點之後,輸入的數字會重複,我檢查之後發現是一次輸入會觸發兩次 input 事件。

前輩看過之後表示,這個 input 元件寫得太肥,props 傳入許多用不到的參數,很難測出是哪個環節出了問題,不如重寫一個。所以我就去研究了 input 欄位判斷千分位點的作法。

先假設有一個父元件會打 API 接收一個之前填入的數值,用 props 傳入設有 <input>輸入框的子元件,當作預設的數值。

<template>
  <div id="app">
    <inputNumber :base="baseValue"></inputNumber>
  </div>
</template>

<script>
	import inputNumber from './components/InputNumber.vue';
	export default {
	  components: {
	    inputNumber,
	  },
	  data() {
	    return {
	      baseValue: 223,
	    };
	  },
	};
</script>

子元件的部分,用base 這個 props 承接父元件傳入的 baseValue。

因為單向資料流的關係,我們不能直接去修改父元件傳入的資料,所以在 mounted 用 value

這個變數承接 base 的資料。

<template>
  <div>
    <input v-model="formatterValue" />
  </div>
</template>

<script>
// <https://codesandbox.io/p/sandbox/vue-template-s6jo9?file=%2Fsrc%2FApp.vue%3A1%2C1-61%2C1>
// <https://stackoverflow.com/questions/63305785/vuejs-add-thousands-separator-to-input>

export default {
  props: {
    base: {
      type: Number,
      default: Infinity,
    },
  },
  data() {
    return {
      value: '',
    };
  }, 
  mounted() {
    this.value = this.base;
  },
};
</script>

然後在 methods 設計一個加上千分位點的 formatValue(num) 函式及 parseValue(text) 去除千分位點的函式。

 methods: {
    formatValue(num) {
      // 加上千分位
      return Number(num)
        .toString()
        .replace(/\\B(?=(\\d{3})+(?!\\d))/g, ',');
      // `${value}`.replace(/\\B(?=(\\d{3})+(?!\\d))/g, ','
      // /(\\d)(?=(\\d\\d\\d)+(?!\\d))/g, '$1,'
    },
    parseValue(text) {
      // 去除千分位
      return Number(text.replace(/,/g, ''));
    },
  },

然後在 computed 中設計一個 getter,偵測 input 輸入的數值,加上千分位點。setter 的部分則是去除千分位點,寫回 value。

computed: {
    formatterValue: {
      // getter 加上千分位
      get: function () {
        if (this.value !== '') {
          return this.formatValue(this.value);
        }
      },
      // setter 複寫 value
      set: function (newValue) {
        this.value = this.parseValue(newValue);
      },
    },
  },

然後在 <input> 用 v-model 綁上 computed 的 formatterValue。

<input v-model="formatterValue" />

這樣就大功告成了,但是如果輸入文字,input欄位就會出現 NaN,這部分要再想想怎麼解決。

子元件原始碼:

<template>
  <div id="app">
    <h4>外部傳入{{ base }}</h4>
    <h3>內部承接{{ value }}</h3>

    <input v-model="formatterValue" />
  </div>
</template>

<script>
export default {
  props: {
    base: {
      type: Number,
      default: Infinity,
    },
  },
  data() {
    return {
      value: '',
    };
  },
  computed: {
    formatterValue: {
      // getter 加上千分位
      get: function () {
        if (this.value !== '') {
          return this.formatValue(this.value);
        }
      },
      // setter 複寫 value
      set: function (newValue) {
        this.value = this.parseValue(newValue);
      },
    },
  },
  methods: {
    formatValue(num) {
      // 加上千分位
      return Number(num)
        .toString()
        .replace(/\B(?=(\d{3})+(?!\d))/g, ',');      
    },
    parseValue(text) {
      // 去除千分位
      return Number(text.replace(/,/g, ''));
    },
  },
  mounted() {
    this.value = this.base;
  },
};
</script>

範例程式碼

參考資料:

Leave a Reply

Your email address will not be published. Required fields are marked *