华西海圻ELN前端工程
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
4.9 KiB

  1. <template>
  2. <el-input v-bind="$attrs" v-model="internalValue" @input="handleInput" @blur="handleBlur" :placeholder="placeholder"
  3. :disabled="disabled" type="text">
  4. <template slot="prepend" v-if="prepend">
  5. {{ prepend }}
  6. </template>
  7. </el-input>
  8. </template>
  9. <script>
  10. export default {
  11. name: 'DecimalInput',
  12. props: {
  13. value: {
  14. type: [Number, String],
  15. default: ''
  16. },
  17. decimalDigits: {
  18. type: Number,
  19. default: 6
  20. },
  21. placeholder: {
  22. type: String,
  23. default: '请输入'
  24. },
  25. disabled: {
  26. type: Boolean,
  27. default: false
  28. },
  29. prepend: {
  30. type: String,
  31. default: ''
  32. },
  33. },
  34. data() {
  35. return {
  36. internalValue: this.value !== null && this.value !== undefined ? String(this.value) : ''
  37. };
  38. },
  39. watch: {
  40. value(newVal) {
  41. // 外部值变化时同步到内部(但不做格式化,避免干扰用户输入)
  42. if (newVal === '' || newVal == null) {
  43. this.internalValue = '';
  44. } else {
  45. this.internalValue = this.handleDecimalDigits(String(newVal));
  46. }
  47. }
  48. },
  49. methods: {
  50. handleInput(val) {
  51. if (val === '') {
  52. this.internalValue = '';
  53. this.$emit('input', '');
  54. return;
  55. }
  56. // 1. 只保留数字、小数点、开头的负号
  57. let cleaned = val
  58. .replace(/[^\d.-]/g, '')
  59. .replace(/^(-)\1+/, '$1'); // 合并多个负号
  60. // 2. 只保留第一个小数点
  61. const firstDotIndex = cleaned.indexOf('.');
  62. if (firstDotIndex !== -1) {
  63. const before = cleaned.slice(0, firstDotIndex);
  64. const after = cleaned.slice(firstDotIndex + 1).replace(/\./g, '');
  65. cleaned = before + '.' + after;
  66. }
  67. // 3. 限制小数位数(仅当允许小数时)
  68. if (this.decimalDigits > 0 && cleaned.includes('.')) {
  69. const [intPart, decPart = ''] = cleaned.split('.');
  70. cleaned = intPart + '.' + decPart.slice(0, this.decimalDigits);
  71. } else if (this.decimalDigits === 0) {
  72. cleaned = cleaned.split('.')[0]; // 移除小数部分
  73. }
  74. // 4. 处理以 . 或 -. 开头
  75. if (cleaned === '.') cleaned = '0.';
  76. else if (cleaned === '-.') cleaned = '-0.';
  77. else if (cleaned.startsWith('.')) cleaned = '0' + cleaned;
  78. else if (cleaned.startsWith('-.')) cleaned = '-0.' + cleaned.slice(2);
  79. // 5. 【关键】安全去除前导零,但保留单个 0
  80. if (cleaned.includes('.')) {
  81. // 有小数点:处理整数部分
  82. const [int, dec] = cleaned.split('.');
  83. // 整数部分:-0012 → -12,00 → 0,0 → 0,-0 → 0(或保留 -0)
  84. let newInt = int;
  85. if (/^-?0+\d/.test(int)) {
  86. newInt = int.replace(/^-?0+(\d)/, '$1');
  87. } else if (int === '' || int === '-') {
  88. newInt = int + '0';
  89. } else if (int === '00' || /^-00+$/.test(int)) {
  90. newInt = int.startsWith('-') ? '-0' : '0';
  91. }
  92. cleaned = newInt + '.' + dec;
  93. } else {
  94. // 无小数点
  95. if (/^-?0+\d/.test(cleaned)) {
  96. cleaned = cleaned.replace(/^-?0+(\d)/, '$1');
  97. } else if (cleaned === '00' || /^-00+$/.test(cleaned)) {
  98. cleaned = cleaned.startsWith('-') ? '-0' : '0';
  99. }
  100. // 注意:不要把单独的 '0' 变成 ''
  101. }
  102. this.internalValue = cleaned;
  103. // emit
  104. if (cleaned === '' || cleaned === '-') {
  105. this.$emit('input', cleaned === '-' ? '-' : '');
  106. } else {
  107. const num = parseFloat(cleaned);
  108. this.$emit('input', isNaN(num) ? '' : num);
  109. }
  110. },
  111. handleDecimalDigits(val) {
  112. const actVal = val || this.internalValue;
  113. let finalValue = actVal.trim();
  114. if (finalValue === '' || finalValue === '-') {
  115. this.internalValue = '';
  116. this.$emit('input', '');
  117. return;
  118. }
  119. const num = parseFloat(finalValue);
  120. if (isNaN(num)) {
  121. this.internalValue = '';
  122. this.$emit('input', '');
  123. return;
  124. }
  125. // 只保留用户输入的有效小数位数,不强制补零
  126. let formatted = String(num);
  127. // 如果用户输入的是整数,直接显示整数
  128. if (!finalValue.includes('.')) {
  129. formatted = String(Math.floor(num));
  130. } else {
  131. // 保留用户输入的小数位数,但不超过设定的最大值
  132. const decPart = finalValue.split('.')[1];
  133. const actualDecimalDigits = decPart.length;
  134. const displayDecimalDigits = Math.min(actualDecimalDigits, this.decimalDigits);
  135. formatted = num.toFixed(displayDecimalDigits).replace(/\.?0*$/, '');
  136. }
  137. return formatted;
  138. },
  139. handleBlur() {
  140. let formatted = this.handleDecimalDigits(this.internalValue);
  141. this.internalValue = formatted;
  142. // emit 数字类型(也可 emit 字符串,根据需求)
  143. this.$emit('input', parseFloat(formatted));
  144. this.$emit('blur', parseFloat(formatted));
  145. }
  146. }
  147. };
  148. </script>