|
|
|
@ -35,20 +35,52 @@ export default { |
|
|
|
}, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
internalValue: this.value !== null && this.value !== undefined ? String(this.value) : '' |
|
|
|
internalValue: this.value !== null && this.value !== undefined ? String(this.value) : '', |
|
|
|
oldValue: null, |
|
|
|
oldPattern: null, |
|
|
|
patternRules: [ |
|
|
|
{ name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i }, |
|
|
|
{ name: 'FRACTION', pattern: /^\d+(\/\d+)*$/, inputPattern: /^(\d+\/?)*$/ } |
|
|
|
] |
|
|
|
}; |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
value(newVal) { |
|
|
|
// 外部值变化时同步到内部(但不做格式化,避免干扰用户输入) |
|
|
|
if (newVal === '' || newVal == null) { |
|
|
|
this.internalValue = ''; |
|
|
|
} else { |
|
|
|
this.internalValue = this.handleDecimalDigits(String(newVal)); |
|
|
|
} |
|
|
|
value: { |
|
|
|
handler(newVal) { |
|
|
|
if (newVal === '' || newVal == null) { |
|
|
|
this.internalValue = ''; |
|
|
|
this.oldValue = null; |
|
|
|
this.oldPattern = null; |
|
|
|
} else { |
|
|
|
const strVal = String(newVal); |
|
|
|
this.internalValue = strVal; |
|
|
|
this.updateOldValue(strVal); |
|
|
|
} |
|
|
|
}, |
|
|
|
immediate: true |
|
|
|
} |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
updateOldValue(val) { |
|
|
|
for (const rule of this.patternRules) { |
|
|
|
if (rule.pattern.test(val)) { |
|
|
|
this.oldValue = val; |
|
|
|
this.oldPattern = rule; |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
}, |
|
|
|
|
|
|
|
getMatchingRule(val) { |
|
|
|
for (const rule of this.patternRules) { |
|
|
|
if (rule.inputPattern.test(val)) { |
|
|
|
return rule; |
|
|
|
} |
|
|
|
} |
|
|
|
return null; |
|
|
|
}, |
|
|
|
|
|
|
|
handleInput(val) { |
|
|
|
if (val === '') { |
|
|
|
this.internalValue = ''; |
|
|
|
@ -56,91 +88,142 @@ export default { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 1. 只保留数字、小数点、开头的负号 |
|
|
|
let cleaned = val |
|
|
|
.replace(/[^\d.-]/g, '') |
|
|
|
.replace(/^(-)\1+/, '$1'); // 合并多个负号 |
|
|
|
|
|
|
|
// 2. 只保留第一个小数点 |
|
|
|
const firstDotIndex = cleaned.indexOf('.'); |
|
|
|
if (firstDotIndex !== -1) { |
|
|
|
const before = cleaned.slice(0, firstDotIndex); |
|
|
|
const after = cleaned.slice(firstDotIndex + 1).replace(/\./g, ''); |
|
|
|
cleaned = before + '.' + after; |
|
|
|
} |
|
|
|
|
|
|
|
// 3. 限制小数位数(仅当允许小数时) |
|
|
|
if (this.decimalDigits > 0 && cleaned.includes('.')) { |
|
|
|
const [intPart, decPart = ''] = cleaned.split('.'); |
|
|
|
cleaned = intPart + '.' + decPart.slice(0, this.decimalDigits); |
|
|
|
} else if (this.decimalDigits === 0) { |
|
|
|
cleaned = cleaned.split('.')[0]; // 移除小数部分 |
|
|
|
} |
|
|
|
|
|
|
|
// 4. 处理以 . 或 -. 开头 |
|
|
|
if (cleaned === '.') cleaned = '0.'; |
|
|
|
else if (cleaned === '-.') cleaned = '-0.'; |
|
|
|
else if (cleaned.startsWith('.')) cleaned = '0' + cleaned; |
|
|
|
else if (cleaned.startsWith('-.')) cleaned = '-0.' + cleaned.slice(2); |
|
|
|
|
|
|
|
// 5. 【关键】安全去除前导零,但保留单个 0 |
|
|
|
if (cleaned.includes('.')) { |
|
|
|
// 有小数点:处理整数部分 |
|
|
|
const [int, dec] = cleaned.split('.'); |
|
|
|
// 整数部分:-0012 → -12,00 → 0,0 → 0,-0 → 0(或保留 -0) |
|
|
|
let newInt = int; |
|
|
|
if (/^-?0+\d/.test(int)) { |
|
|
|
newInt = int.replace(/^-?0+(\d)/, '$1'); |
|
|
|
} else if (int === '' || int === '-') { |
|
|
|
newInt = int + '0'; |
|
|
|
} else if (int === '00' || /^-00+$/.test(int)) { |
|
|
|
newInt = int.startsWith('-') ? '-0' : '0'; |
|
|
|
const upperVal = val.toUpperCase(); |
|
|
|
let cleaned = val; |
|
|
|
let matchedRule = null; |
|
|
|
if (this.oldPattern) { |
|
|
|
// 检查是否是旧模式的延续(以oldValue开头) |
|
|
|
if (val.startsWith(this.oldValue)) { |
|
|
|
// 是旧模式的延续,检查是否符合inputPattern |
|
|
|
if (this.oldPattern.inputPattern.test(val)) { |
|
|
|
matchedRule = this.oldPattern; |
|
|
|
cleaned = val; |
|
|
|
} else { |
|
|
|
// 不符合,但如果是FRACTION类型,尝试特殊处理 |
|
|
|
if (this.oldPattern.name === 'FRACTION') { |
|
|
|
cleaned = val.replace(/[^\d/]/g, ''); |
|
|
|
const parts = cleaned.split('/'); |
|
|
|
const validParts = []; |
|
|
|
for (let i = 0; i < parts.length; i++) { |
|
|
|
if (parts[i] !== '') { |
|
|
|
validParts.push(parts[i]); |
|
|
|
} |
|
|
|
} |
|
|
|
cleaned = validParts.join('/'); |
|
|
|
matchedRule = this.oldPattern; |
|
|
|
} else { |
|
|
|
cleaned = this.oldValue || ''; |
|
|
|
this.internalValue = cleaned; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (this.oldPattern.inputPattern.test(val)) { |
|
|
|
// 不是延续,但符合inputPattern(可能是清空后重新输入) |
|
|
|
matchedRule = this.oldPattern; |
|
|
|
cleaned = val; |
|
|
|
} else { |
|
|
|
// 尝试匹配新规则 |
|
|
|
matchedRule = this.getMatchingRule(val); |
|
|
|
if (!matchedRule) { |
|
|
|
cleaned = this.oldValue || ''; |
|
|
|
this.internalValue = cleaned; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
cleaned = newInt + '.' + dec; |
|
|
|
} else { |
|
|
|
// 无小数点 |
|
|
|
if (/^-?0+\d/.test(cleaned)) { |
|
|
|
cleaned = cleaned.replace(/^-?0+(\d)/, '$1'); |
|
|
|
} else if (cleaned === '00' || /^-00+$/.test(cleaned)) { |
|
|
|
cleaned = cleaned.startsWith('-') ? '-0' : '0'; |
|
|
|
matchedRule = this.getMatchingRule(val); |
|
|
|
|
|
|
|
if (matchedRule) { |
|
|
|
if (matchedRule.name === 'FRACTION') { |
|
|
|
cleaned = val.replace(/[^\d/]/g, ''); |
|
|
|
const parts = cleaned.split('/'); |
|
|
|
const validParts = []; |
|
|
|
for (let i = 0; i < parts.length; i++) { |
|
|
|
if (parts[i] !== '') { |
|
|
|
validParts.push(parts[i]); |
|
|
|
} |
|
|
|
} |
|
|
|
cleaned = validParts.join('/'); |
|
|
|
} else { |
|
|
|
cleaned = upperVal; |
|
|
|
} |
|
|
|
} else { |
|
|
|
cleaned = val |
|
|
|
.replace(/[^\d.-]/g, '') |
|
|
|
.replace(/^(-)\1+/, '$1'); |
|
|
|
|
|
|
|
const firstDotIndex = cleaned.indexOf('.'); |
|
|
|
if (firstDotIndex !== -1) { |
|
|
|
const before = cleaned.slice(0, firstDotIndex); |
|
|
|
const after = cleaned.slice(firstDotIndex + 1).replace(/\./g, ''); |
|
|
|
cleaned = before + '.' + after; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.decimalDigits > 0 && cleaned.includes('.')) { |
|
|
|
const [intPart, decPart = ''] = cleaned.split('.'); |
|
|
|
cleaned = intPart + '.' + decPart.slice(0, this.decimalDigits); |
|
|
|
} else if (this.decimalDigits === 0) { |
|
|
|
cleaned = cleaned.split('.')[0]; |
|
|
|
} |
|
|
|
|
|
|
|
if (cleaned === '.') cleaned = '0.'; |
|
|
|
else if (cleaned === '-.') cleaned = '-0.'; |
|
|
|
else if (cleaned.startsWith('.')) cleaned = '0' + cleaned; |
|
|
|
else if (cleaned.startsWith('-.')) cleaned = '-0.' + cleaned.slice(2); |
|
|
|
|
|
|
|
if (cleaned.includes('.')) { |
|
|
|
const [int, dec] = cleaned.split('.'); |
|
|
|
let newInt = int; |
|
|
|
if (/^-?0+\d/.test(int)) { |
|
|
|
newInt = int.replace(/^-?0+(\d)/, '$1'); |
|
|
|
} else if (int === '' || int === '-') { |
|
|
|
newInt = int + '0'; |
|
|
|
} else if (int === '00' || /^-00+$/.test(int)) { |
|
|
|
newInt = int.startsWith('-') ? '-0' : '0'; |
|
|
|
} |
|
|
|
cleaned = newInt + '.' + dec; |
|
|
|
} else { |
|
|
|
if (/^-?0+\d/.test(cleaned)) { |
|
|
|
cleaned = cleaned.replace(/^-?0+(\d)/, '$1'); |
|
|
|
} else if (cleaned === '00' || /^-00+$/.test(cleaned)) { |
|
|
|
cleaned = cleaned.startsWith('-') ? '-0' : '0'; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 注意:不要把单独的 '0' 变成 '' |
|
|
|
} |
|
|
|
|
|
|
|
this.internalValue = cleaned; |
|
|
|
|
|
|
|
// emit |
|
|
|
if (cleaned === '' || cleaned === '-') { |
|
|
|
this.$emit('input', cleaned === '-' ? '-' : ''); |
|
|
|
} else if (matchedRule && matchedRule.name !== 'FRACTION') { |
|
|
|
this.$emit('input', cleaned); |
|
|
|
} else if (cleaned.includes('/')) { |
|
|
|
this.$emit('input', cleaned); |
|
|
|
} else { |
|
|
|
const num = parseFloat(cleaned); |
|
|
|
this.$emit('input', isNaN(num) ? '' : num); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
handleDecimalDigits(val) { |
|
|
|
const actVal = val || this.internalValue; |
|
|
|
let finalValue = actVal.trim(); |
|
|
|
|
|
|
|
if (finalValue === '' || finalValue === '-') { |
|
|
|
this.internalValue = ''; |
|
|
|
this.$emit('input', ''); |
|
|
|
return; |
|
|
|
return ''; |
|
|
|
} |
|
|
|
|
|
|
|
const num = parseFloat(finalValue); |
|
|
|
if (isNaN(num)) { |
|
|
|
this.internalValue = ''; |
|
|
|
this.$emit('input', ''); |
|
|
|
return; |
|
|
|
return ''; |
|
|
|
} |
|
|
|
|
|
|
|
// 只保留用户输入的有效小数位数,不强制补零 |
|
|
|
let formatted = String(num); |
|
|
|
|
|
|
|
// 如果用户输入的是整数,直接显示整数 |
|
|
|
if (!finalValue.includes('.')) { |
|
|
|
formatted = String(Math.floor(num)); |
|
|
|
} else { |
|
|
|
// 保留用户输入的小数位数,但不超过设定的最大值 |
|
|
|
const decPart = finalValue.split('.')[1]; |
|
|
|
const actualDecimalDigits = decPart.length; |
|
|
|
const displayDecimalDigits = Math.min(actualDecimalDigits, this.decimalDigits); |
|
|
|
@ -150,12 +233,80 @@ export default { |
|
|
|
}, |
|
|
|
|
|
|
|
handleBlur() { |
|
|
|
let formatted = this.handleDecimalDigits(this.internalValue); |
|
|
|
const val = this.internalValue.trim(); |
|
|
|
|
|
|
|
if (val === '') { |
|
|
|
this.oldValue = null; |
|
|
|
this.oldPattern = null; |
|
|
|
this.$emit('input', ''); |
|
|
|
this.$emit('blur', ''); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const upperVal = val.toUpperCase(); |
|
|
|
|
|
|
|
for (const rule of this.patternRules) { |
|
|
|
if (rule.pattern.test(upperVal)) { |
|
|
|
this.oldValue = upperVal; |
|
|
|
this.oldPattern = rule; |
|
|
|
this.internalValue = upperVal; |
|
|
|
this.$emit('input', upperVal); |
|
|
|
this.$emit('blur', upperVal); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (val.includes('/')) { |
|
|
|
const parts = val.split('/'); |
|
|
|
const validParts = parts.filter(part => part !== ''); |
|
|
|
|
|
|
|
if (validParts.length === 0) { |
|
|
|
if (this.oldValue) { |
|
|
|
this.internalValue = this.oldValue; |
|
|
|
this.$emit('input', this.oldValue); |
|
|
|
this.$emit('blur', this.oldValue); |
|
|
|
} else { |
|
|
|
this.internalValue = ''; |
|
|
|
this.$emit('input', ''); |
|
|
|
this.$emit('blur', ''); |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (validParts.length === 1) { |
|
|
|
const result = validParts[0]; |
|
|
|
this.oldValue = result; |
|
|
|
this.oldPattern = this.patternRules.find(r => r.name === 'FRACTION'); |
|
|
|
this.internalValue = result; |
|
|
|
this.$emit('input', result); |
|
|
|
this.$emit('blur', result); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
const formattedValue = validParts.join('/'); |
|
|
|
this.oldValue = formattedValue; |
|
|
|
this.oldPattern = this.patternRules.find(r => r.name === 'FRACTION'); |
|
|
|
this.internalValue = formattedValue; |
|
|
|
this.$emit('input', formattedValue); |
|
|
|
this.$emit('blur', formattedValue); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.oldValue && this.oldPattern) { |
|
|
|
const partialMatch = this.oldPattern.inputPattern && this.oldPattern.inputPattern.test(val); |
|
|
|
if (!partialMatch) { |
|
|
|
this.internalValue = this.oldValue; |
|
|
|
this.$emit('input', this.oldValue); |
|
|
|
this.$emit('blur', this.oldValue); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let formatted = this.handleDecimalDigits(val); |
|
|
|
this.internalValue = formatted; |
|
|
|
// emit 数字类型(也可 emit 字符串,根据需求) |
|
|
|
this.$emit('input', parseFloat(formatted)); |
|
|
|
this.$emit('blur', parseFloat(formatted)); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
</script> |
|
|
|
</script> |