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

312 lines
9.3 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. patternRules: [
  40. { name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i },
  41. { name: 'FRACTION', pattern: /^\d+(\/\d+)*$/, inputPattern: /^(\d+\/?)*$/ }
  42. ]
  43. };
  44. },
  45. watch: {
  46. value: {
  47. handler(newVal) {
  48. if (newVal === '' || newVal == null) {
  49. this.internalValue = '';
  50. this.oldValue = null;
  51. this.oldPattern = null;
  52. } else {
  53. const strVal = String(newVal);
  54. this.internalValue = strVal;
  55. this.updateOldValue(strVal);
  56. }
  57. },
  58. immediate: true
  59. }
  60. },
  61. methods: {
  62. updateOldValue(val) {
  63. for (const rule of this.patternRules) {
  64. if (rule.pattern.test(val)) {
  65. this.oldValue = val;
  66. this.oldPattern = rule;
  67. return true;
  68. }
  69. }
  70. return false;
  71. },
  72. getMatchingRule(val) {
  73. for (const rule of this.patternRules) {
  74. if (rule.inputPattern.test(val)) {
  75. return rule;
  76. }
  77. }
  78. return null;
  79. },
  80. handleInput(val) {
  81. if (val === '') {
  82. this.internalValue = '';
  83. this.$emit('input', '');
  84. return;
  85. }
  86. const upperVal = val.toUpperCase();
  87. let cleaned = val;
  88. let matchedRule = null;
  89. if (this.oldPattern) {
  90. // 检查是否是旧模式的延续(以oldValue开头)
  91. if (val.startsWith(this.oldValue)) {
  92. // 是旧模式的延续,检查是否符合inputPattern
  93. if (this.oldPattern.inputPattern.test(val)) {
  94. matchedRule = this.oldPattern;
  95. cleaned = val;
  96. } else {
  97. // 不符合,但如果是FRACTION类型,尝试特殊处理
  98. if (this.oldPattern.name === 'FRACTION') {
  99. cleaned = val.replace(/[^\d/]/g, '');
  100. const parts = cleaned.split('/');
  101. const validParts = [];
  102. for (let i = 0; i < parts.length; i++) {
  103. if (parts[i] !== '') {
  104. validParts.push(parts[i]);
  105. }
  106. }
  107. cleaned = validParts.join('/');
  108. matchedRule = this.oldPattern;
  109. } else {
  110. cleaned = this.oldValue || '';
  111. this.internalValue = cleaned;
  112. return;
  113. }
  114. }
  115. } else if (this.oldPattern.inputPattern.test(val)) {
  116. // 不是延续,但符合inputPattern(可能是清空后重新输入)
  117. matchedRule = this.oldPattern;
  118. cleaned = val;
  119. } else {
  120. // 尝试匹配新规则
  121. matchedRule = this.getMatchingRule(val);
  122. if (!matchedRule) {
  123. cleaned = this.oldValue || '';
  124. this.internalValue = cleaned;
  125. return;
  126. }
  127. }
  128. } else {
  129. matchedRule = this.getMatchingRule(val);
  130. if (matchedRule) {
  131. if (matchedRule.name === 'FRACTION') {
  132. cleaned = val.replace(/[^\d/]/g, '');
  133. const parts = cleaned.split('/');
  134. const validParts = [];
  135. for (let i = 0; i < parts.length; i++) {
  136. if (parts[i] !== '') {
  137. validParts.push(parts[i]);
  138. }
  139. }
  140. cleaned = validParts.join('/');
  141. } else {
  142. cleaned = upperVal;
  143. }
  144. } else {
  145. cleaned = val
  146. .replace(/[^\d.-]/g, '')
  147. .replace(/^(-)\1+/, '$1');
  148. const firstDotIndex = cleaned.indexOf('.');
  149. if (firstDotIndex !== -1) {
  150. const before = cleaned.slice(0, firstDotIndex);
  151. const after = cleaned.slice(firstDotIndex + 1).replace(/\./g, '');
  152. cleaned = before + '.' + after;
  153. }
  154. if (this.decimalDigits > 0 && cleaned.includes('.')) {
  155. const [intPart, decPart = ''] = cleaned.split('.');
  156. cleaned = intPart + '.' + decPart.slice(0, this.decimalDigits);
  157. } else if (this.decimalDigits === 0) {
  158. cleaned = cleaned.split('.')[0];
  159. }
  160. if (cleaned === '.') cleaned = '0.';
  161. else if (cleaned === '-.') cleaned = '-0.';
  162. else if (cleaned.startsWith('.')) cleaned = '0' + cleaned;
  163. else if (cleaned.startsWith('-.')) cleaned = '-0.' + cleaned.slice(2);
  164. if (cleaned.includes('.')) {
  165. const [int, dec] = cleaned.split('.');
  166. let newInt = int;
  167. if (/^-?0+\d/.test(int)) {
  168. newInt = int.replace(/^-?0+(\d)/, '$1');
  169. } else if (int === '' || int === '-') {
  170. newInt = int + '0';
  171. } else if (int === '00' || /^-00+$/.test(int)) {
  172. newInt = int.startsWith('-') ? '-0' : '0';
  173. }
  174. cleaned = newInt + '.' + dec;
  175. } else {
  176. if (/^-?0+\d/.test(cleaned)) {
  177. cleaned = cleaned.replace(/^-?0+(\d)/, '$1');
  178. } else if (cleaned === '00' || /^-00+$/.test(cleaned)) {
  179. cleaned = cleaned.startsWith('-') ? '-0' : '0';
  180. }
  181. }
  182. }
  183. }
  184. this.internalValue = cleaned;
  185. if (cleaned === '' || cleaned === '-') {
  186. this.$emit('input', cleaned === '-' ? '-' : '');
  187. } else if (matchedRule && matchedRule.name !== 'FRACTION') {
  188. this.$emit('input', cleaned);
  189. } else if (cleaned.includes('/')) {
  190. this.$emit('input', cleaned);
  191. } else {
  192. const num = parseFloat(cleaned);
  193. this.$emit('input', isNaN(num) ? '' : num);
  194. }
  195. },
  196. handleDecimalDigits(val) {
  197. const actVal = val || this.internalValue;
  198. let finalValue = actVal.trim();
  199. if (finalValue === '' || finalValue === '-') {
  200. return '';
  201. }
  202. const num = parseFloat(finalValue);
  203. if (isNaN(num)) {
  204. return '';
  205. }
  206. let formatted = String(num);
  207. if (!finalValue.includes('.')) {
  208. formatted = String(Math.floor(num));
  209. } else {
  210. const decPart = finalValue.split('.')[1];
  211. const actualDecimalDigits = decPart.length;
  212. const displayDecimalDigits = Math.min(actualDecimalDigits, this.decimalDigits);
  213. formatted = num.toFixed(displayDecimalDigits).replace(/\.?0*$/, '');
  214. }
  215. return formatted;
  216. },
  217. handleBlur() {
  218. const val = this.internalValue.trim();
  219. if (val === '') {
  220. this.oldValue = null;
  221. this.oldPattern = null;
  222. this.$emit('input', '');
  223. this.$emit('blur', '');
  224. return;
  225. }
  226. const upperVal = val.toUpperCase();
  227. for (const rule of this.patternRules) {
  228. if (rule.pattern.test(upperVal)) {
  229. this.oldValue = upperVal;
  230. this.oldPattern = rule;
  231. this.internalValue = upperVal;
  232. this.$emit('input', upperVal);
  233. this.$emit('blur', upperVal);
  234. return;
  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. if (this.oldValue && this.oldPattern) {
  270. const partialMatch = this.oldPattern.inputPattern && this.oldPattern.inputPattern.test(val);
  271. if (!partialMatch) {
  272. this.internalValue = this.oldValue;
  273. this.$emit('input', this.oldValue);
  274. this.$emit('blur', this.oldValue);
  275. return;
  276. }
  277. }
  278. let formatted = this.handleDecimalDigits(val);
  279. this.internalValue = formatted;
  280. this.$emit('input', parseFloat(formatted));
  281. this.$emit('blur', parseFloat(formatted));
  282. }
  283. }
  284. };
  285. </script>