华西海圻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.

202 lines
6.6 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. // 如果当前值是 "NA" 或 "20/30/40" 格式,保持不变
  46. if ((this.internalValue && this.internalValue.toUpperCase() === 'NA') || (this.internalValue && /^\d+(\/\d+)*$/.test(this.internalValue))) {
  47. return;
  48. }
  49. // 检查是否为 "NA" 或其输入过程,或类似 "20/30/40" 的格式
  50. if (String(newVal).toUpperCase() === 'NA' || /^[Nn]?[Aa]?$/.test(String(newVal)) || /^\d+(\/\d*)*$/.test(String(newVal))) {
  51. this.internalValue = String(newVal);
  52. } else {
  53. this.internalValue = this.handleDecimalDigits(String(newVal));
  54. }
  55. }
  56. }
  57. },
  58. methods: {
  59. handleInput(val) {
  60. if (val === '') {
  61. this.internalValue = '';
  62. this.$emit('input', '');
  63. return;
  64. }
  65. // 检查是否为 "NA" 或其输入过程
  66. if (val.toUpperCase() === 'NA' || /^[Nn]?[Aa]?$/.test(val)) {
  67. this.internalValue = val;
  68. this.$emit('input', val);
  69. return;
  70. }
  71. // 检查是否为 "20/30/40" 或其输入过程
  72. if (/^\d+(\/\d*)*$/.test(val)) {
  73. this.internalValue = val;
  74. this.$emit('input', val);
  75. return;
  76. }
  77. // 如果当前值是 "NA" 或 "20/30/40" 格式,不允许输入其他字符
  78. if ((this.internalValue && this.internalValue.toUpperCase() === 'NA') || (this.internalValue && /^\d+(\/\d+)*$/.test(this.internalValue))) {
  79. // 保持当前值不变
  80. this.$emit('input', this.internalValue);
  81. return;
  82. }
  83. // 1. 只保留数字、小数点、开头的负号
  84. let cleaned = val
  85. .replace(/[^\d.-]/g, '')
  86. .replace(/^(-)\1+/, '$1'); // 合并多个负号
  87. // 2. 只保留第一个小数点
  88. const firstDotIndex = cleaned.indexOf('.');
  89. if (firstDotIndex !== -1) {
  90. const before = cleaned.slice(0, firstDotIndex);
  91. const after = cleaned.slice(firstDotIndex + 1).replace(/\./g, '');
  92. cleaned = before + '.' + after;
  93. }
  94. // 3. 限制小数位数(仅当允许小数时)
  95. if (this.decimalDigits > 0 && cleaned.includes('.')) {
  96. const [intPart, decPart = ''] = cleaned.split('.');
  97. cleaned = intPart + '.' + decPart.slice(0, this.decimalDigits);
  98. } else if (this.decimalDigits === 0) {
  99. cleaned = cleaned.split('.')[0]; // 移除小数部分
  100. }
  101. // 4. 处理以 . 或 -. 开头
  102. if (cleaned === '.') cleaned = '0.';
  103. else if (cleaned === '-.') cleaned = '-0.';
  104. else if (cleaned.startsWith('.')) cleaned = '0' + cleaned;
  105. else if (cleaned.startsWith('-.')) cleaned = '-0.' + cleaned.slice(2);
  106. // 5. 【关键】安全去除前导零,但保留单个 0
  107. if (cleaned.includes('.')) {
  108. // 有小数点:处理整数部分
  109. const [int, dec] = cleaned.split('.');
  110. // 整数部分:-0012 → -12,00 → 0,0 → 0,-0 → 0(或保留 -0)
  111. let newInt = int;
  112. if (/^-?0+\d/.test(int)) {
  113. newInt = int.replace(/^-?0+(\d)/, '$1');
  114. } else if (int === '' || int === '-') {
  115. newInt = int + '0';
  116. } else if (int === '00' || /^-00+$/.test(int)) {
  117. newInt = int.startsWith('-') ? '-0' : '0';
  118. }
  119. cleaned = newInt + '.' + dec;
  120. } else {
  121. // 无小数点
  122. if (/^-?0+\d/.test(cleaned)) {
  123. cleaned = cleaned.replace(/^-?0+(\d)/, '$1');
  124. } else if (cleaned === '00' || /^-00+$/.test(cleaned)) {
  125. cleaned = cleaned.startsWith('-') ? '-0' : '0';
  126. }
  127. // 注意:不要把单独的 '0' 变成 ''
  128. }
  129. this.internalValue = cleaned;
  130. // emit
  131. if (cleaned === '' || cleaned === '-') {
  132. this.$emit('input', cleaned === '-' ? '-' : '');
  133. } else {
  134. const num = parseFloat(cleaned);
  135. this.$emit('input', isNaN(num) ? '' : num);
  136. }
  137. },
  138. handleDecimalDigits(val) {
  139. const actVal = val || this.internalValue;
  140. let finalValue = actVal.trim();
  141. // 检查是否为 "NA" 或类似 "20/30/40" 的格式
  142. if (finalValue.toUpperCase() === 'NA' || /^\d+(\/\d+)+$/.test(finalValue)) {
  143. return finalValue;
  144. }
  145. if (finalValue === '' || finalValue === '-') {
  146. this.internalValue = '';
  147. this.$emit('input', '');
  148. return;
  149. }
  150. const num = parseFloat(finalValue);
  151. if (isNaN(num)) {
  152. this.internalValue = '';
  153. this.$emit('input', '');
  154. return;
  155. }
  156. // 只保留用户输入的有效小数位数,不强制补零
  157. let formatted = String(num);
  158. // 如果用户输入的是整数,直接显示整数
  159. if (!finalValue.includes('.')) {
  160. formatted = String(Math.floor(num));
  161. } else {
  162. // 保留用户输入的小数位数,但不超过设定的最大值
  163. const decPart = finalValue.split('.')[1];
  164. const actualDecimalDigits = decPart.length;
  165. const displayDecimalDigits = Math.min(actualDecimalDigits, this.decimalDigits);
  166. formatted = num.toFixed(displayDecimalDigits).replace(/\.?0*$/, '');
  167. }
  168. return formatted;
  169. },
  170. handleBlur() {
  171. // 检查是否为 "NA" 或类似 "20/30/40" 的格式
  172. if ((this.internalValue && this.internalValue.toUpperCase() === 'NA') || (this.internalValue && /^\d+(\/\d+)*$/.test(this.internalValue))) {
  173. this.$emit('input', this.internalValue);
  174. this.$emit('blur', this.internalValue);
  175. return;
  176. }
  177. let formatted = this.handleDecimalDigits(this.internalValue);
  178. this.internalValue = formatted;
  179. // emit 数字类型(也可 emit 字符串,根据需求)
  180. this.$emit('input', parseFloat(formatted));
  181. this.$emit('blur', parseFloat(formatted));
  182. }
  183. }
  184. };
  185. </script>