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

306 lines
9.1 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. oldValue: null,
  38. oldPattern: null,
  39. isNA: this.value !== null && this.value !== undefined ? /^NA$/i.test(String(this.value).toUpperCase()) : false,
  40. patternRules: [
  41. { name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i },
  42. { name: 'FRACTION', pattern: /^\d+(\/\d+)*$/, inputPattern: /^(\d+\/?)*$/ }
  43. ]
  44. };
  45. },
  46. watch: {
  47. value: {
  48. handler(newVal) {
  49. if (newVal === '' || newVal == null) {
  50. this.internalValue = '';
  51. this.oldValue = null;
  52. this.oldPattern = null;
  53. this.isNA = false;
  54. } else {
  55. const strVal = String(newVal);
  56. this.internalValue = strVal;
  57. this.isNA = /^NA$/i.test(strVal.toUpperCase());
  58. this.updateOldValue(strVal);
  59. }
  60. },
  61. immediate: true
  62. }
  63. },
  64. methods: {
  65. updateOldValue(val) {
  66. for (const rule of this.patternRules) {
  67. if (rule.pattern.test(val)) {
  68. this.oldValue = val;
  69. this.oldPattern = rule;
  70. return true;
  71. }
  72. }
  73. return false;
  74. },
  75. getMatchingRule(val) {
  76. for (const rule of this.patternRules) {
  77. if (rule.inputPattern.test(val)) {
  78. return rule;
  79. }
  80. }
  81. return null;
  82. },
  83. handleInput(val) {
  84. if (val === '') {
  85. this.internalValue = '';
  86. this.isNA = false;
  87. this.$emit('input', '');
  88. return;
  89. }
  90. // 检查是否已经输入了完整的NA
  91. if (this.isNA) {
  92. // 如果已经输入了NA,无论输入什么,都保持NA不变
  93. this.internalValue = 'NA';
  94. this.$emit('input', 'NA');
  95. return;
  96. }
  97. const upperVal = val.toUpperCase();
  98. let cleaned = val;
  99. let matchedRule = null;
  100. // 检查是否匹配特殊模式(NA或FRACTION)
  101. // 优先检查完整的NA模式
  102. if (/^NA$/i.test(upperVal)) {
  103. matchedRule = { name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i };
  104. cleaned = upperVal;
  105. this.isNA = true;
  106. } else if (upperVal === 'N' || upperVal === 'A') {
  107. // 部分匹配NA模式
  108. matchedRule = { name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i };
  109. cleaned = upperVal;
  110. } else {
  111. // 检查FRACTION模式
  112. matchedRule = this.getMatchingRule(val);
  113. }
  114. if (matchedRule) {
  115. if (matchedRule.name === 'FRACTION') {
  116. cleaned = val.replace(/[^\d/]/g, '');
  117. const parts = cleaned.split('/');
  118. const validParts = [];
  119. for (let i = 0; i < parts.length; i++) {
  120. if (parts[i] !== '' || i === parts.length - 1) {
  121. validParts.push(parts[i]);
  122. }
  123. }
  124. cleaned = validParts.join('/');
  125. } else {
  126. cleaned = upperVal;
  127. }
  128. } else {
  129. // 处理数字和小数点输入
  130. cleaned = val
  131. .replace(/[^\d.-]/g, '')
  132. .replace(/^(-)\1+/, '$1');
  133. // 确保只有一个小数点
  134. const firstDotIndex = cleaned.indexOf('.');
  135. if (firstDotIndex !== -1) {
  136. const before = cleaned.slice(0, firstDotIndex);
  137. const after = cleaned.slice(firstDotIndex + 1).replace(/\./g, '');
  138. cleaned = before + '.' + after;
  139. }
  140. // 处理小数点位数限制
  141. if (this.decimalDigits > 0 && cleaned.includes('.')) {
  142. const [intPart, decPart = ''] = cleaned.split('.');
  143. cleaned = intPart + '.' + decPart.slice(0, this.decimalDigits);
  144. } else if (this.decimalDigits === 0) {
  145. cleaned = cleaned.split('.')[0];
  146. }
  147. // 处理以小数点开头的情况
  148. if (cleaned === '.') cleaned = '0.';
  149. else if (cleaned === '-.') cleaned = '-0.';
  150. else if (cleaned.startsWith('.')) cleaned = '0' + cleaned;
  151. else if (cleaned.startsWith('-.')) cleaned = '-0.' + cleaned.slice(2);
  152. // 处理整数部分的前导零
  153. if (cleaned.includes('.')) {
  154. const [int, dec] = cleaned.split('.');
  155. let newInt = int;
  156. if (/^-?0+\d/.test(int)) {
  157. newInt = int.replace(/^-?0+(\d)/, '$1');
  158. } else if (int === '' || int === '-') {
  159. newInt = int + '0';
  160. } else if (int === '00' || /^-00+$/.test(int)) {
  161. newInt = int.startsWith('-') ? '-0' : '0';
  162. }
  163. cleaned = newInt + '.' + dec;
  164. } else {
  165. if (/^-?0+\d/.test(cleaned)) {
  166. cleaned = cleaned.replace(/^-?0+(\d)/, '$1');
  167. } else if (cleaned === '00' || /^-00+$/.test(cleaned)) {
  168. cleaned = cleaned.startsWith('-') ? '-0' : '0';
  169. }
  170. }
  171. }
  172. this.internalValue = cleaned;
  173. // 处理emit值
  174. if (cleaned === '' || cleaned === '-') {
  175. this.$emit('input', cleaned === '-' ? '-' : '');
  176. } else if (matchedRule && matchedRule.name !== 'FRACTION') {
  177. this.$emit('input', cleaned);
  178. } else if (cleaned.includes('/')) {
  179. this.$emit('input', cleaned);
  180. } else if (cleaned.includes('.')) {
  181. // 对于包含小数点的情况,先emit字符串以保持小数点
  182. this.$emit('input', cleaned);
  183. } else if (matchedRule && matchedRule.name === 'FRACTION') {
  184. // 对于FRACTION模式的整数,保持字符串形式以支持后续输入斜杠
  185. this.$emit('input', cleaned);
  186. } else {
  187. // 对于纯数字,转换为数字
  188. const num = parseFloat(cleaned);
  189. this.$emit('input', isNaN(num) ? '' : num);
  190. }
  191. },
  192. handleDecimalDigits(val) {
  193. const actVal = val || this.internalValue;
  194. let finalValue = actVal.trim();
  195. if (finalValue === '' || finalValue === '-') {
  196. return '';
  197. }
  198. const num = parseFloat(finalValue);
  199. if (isNaN(num)) {
  200. return '';
  201. }
  202. let formatted = String(num);
  203. if (!finalValue.includes('.')) {
  204. formatted = String(Math.floor(num));
  205. } else {
  206. const decPart = finalValue.split('.')[1];
  207. const actualDecimalDigits = decPart.length;
  208. const displayDecimalDigits = Math.min(actualDecimalDigits, this.decimalDigits);
  209. formatted = num.toFixed(displayDecimalDigits).replace(/\.?0*$/, '');
  210. }
  211. return formatted;
  212. },
  213. handleBlur() {
  214. const val = this.internalValue.trim();
  215. if (val === '') {
  216. this.oldValue = null;
  217. this.oldPattern = null;
  218. this.isNA = false;
  219. this.$emit('input', '');
  220. this.$emit('blur', '');
  221. return;
  222. }
  223. const upperVal = val.toUpperCase();
  224. // 检查是否匹配特殊模式
  225. for (const rule of this.patternRules) {
  226. if (rule.pattern.test(upperVal)) {
  227. this.oldValue = upperVal;
  228. this.oldPattern = rule;
  229. this.internalValue = upperVal;
  230. this.isNA = /^NA$/i.test(upperVal);
  231. this.$emit('input', upperVal);
  232. this.$emit('blur', upperVal);
  233. return;
  234. }
  235. }
  236. // 处理分数
  237. if (val.includes('/')) {
  238. const parts = val.split('/');
  239. const validParts = parts.filter(part => part !== '');
  240. if (validParts.length === 0) {
  241. if (this.oldValue) {
  242. this.internalValue = this.oldValue;
  243. this.$emit('input', this.oldValue);
  244. this.$emit('blur', this.oldValue);
  245. } else {
  246. this.internalValue = '';
  247. this.$emit('input', '');
  248. this.$emit('blur', '');
  249. }
  250. return;
  251. }
  252. if (validParts.length === 1) {
  253. const result = validParts[0];
  254. this.oldValue = result;
  255. this.oldPattern = this.patternRules.find(r => r.name === 'FRACTION');
  256. this.internalValue = result;
  257. this.$emit('input', result);
  258. this.$emit('blur', result);
  259. return;
  260. }
  261. const formattedValue = validParts.join('/');
  262. this.oldValue = formattedValue;
  263. this.oldPattern = this.patternRules.find(r => r.name === 'FRACTION');
  264. this.internalValue = formattedValue;
  265. this.$emit('input', formattedValue);
  266. this.$emit('blur', formattedValue);
  267. return;
  268. }
  269. // 处理数字
  270. let formatted = this.handleDecimalDigits(val);
  271. this.internalValue = formatted;
  272. const num = parseFloat(formatted);
  273. this.$emit('input', isNaN(num) ? '' : num);
  274. this.$emit('blur', isNaN(num) ? '' : num);
  275. }
  276. }
  277. };
  278. </script>