<template>
|
|
<el-input v-bind="$attrs" v-model="internalValue" @input="handleInput" @blur="handleBlur" :placeholder="placeholder"
|
|
:disabled="disabled" type="text">
|
|
|
|
<template slot="prepend" v-if="prepend">
|
|
{{ prepend }}
|
|
</template>
|
|
</el-input>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'DecimalInput',
|
|
props: {
|
|
value: {
|
|
type: [Number, String],
|
|
default: ''
|
|
},
|
|
decimalDigits: {
|
|
type: Number,
|
|
default: 6
|
|
},
|
|
placeholder: {
|
|
type: String,
|
|
default: '请输入'
|
|
},
|
|
disabled: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
prepend: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
},
|
|
data() {
|
|
return {
|
|
internalValue: this.value !== null && this.value !== undefined ? String(this.value) : ''
|
|
};
|
|
},
|
|
watch: {
|
|
value(newVal) {
|
|
// 外部值变化时同步到内部(但不做格式化,避免干扰用户输入)
|
|
if (newVal === '' || newVal == null) {
|
|
this.internalValue = '';
|
|
} else {
|
|
// 如果当前值是 "NA" 或 "20/30/40" 格式,保持不变
|
|
if ((this.internalValue && this.internalValue.toUpperCase() === 'NA') || (this.internalValue && /^\d+(\/\d+)*$/.test(this.internalValue))) {
|
|
return;
|
|
}
|
|
// 检查是否为 "NA" 或其输入过程,或类似 "20/30/40" 的格式
|
|
if (String(newVal).toUpperCase() === 'NA' || /^[Nn]?[Aa]?$/.test(String(newVal)) || /^\d+(\/\d*)*$/.test(String(newVal))) {
|
|
this.internalValue = String(newVal);
|
|
} else {
|
|
this.internalValue = this.handleDecimalDigits(String(newVal));
|
|
}
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
handleInput(val) {
|
|
if (val === '') {
|
|
this.internalValue = '';
|
|
this.$emit('input', '');
|
|
return;
|
|
}
|
|
|
|
// 检查是否为 "NA" 或其输入过程
|
|
if (val.toUpperCase() === 'NA' || /^[Nn]?[Aa]?$/.test(val)) {
|
|
this.internalValue = val;
|
|
this.$emit('input', val);
|
|
return;
|
|
}
|
|
|
|
// 检查是否为 "20/30/40" 或其输入过程
|
|
if (/^\d+(\/\d*)*$/.test(val)) {
|
|
this.internalValue = val;
|
|
this.$emit('input', val);
|
|
return;
|
|
}
|
|
|
|
// 如果当前值是 "NA" 或 "20/30/40" 格式,不允许输入其他字符
|
|
if ((this.internalValue && this.internalValue.toUpperCase() === 'NA') || (this.internalValue && /^\d+(\/\d+)*$/.test(this.internalValue))) {
|
|
// 保持当前值不变
|
|
this.$emit('input', this.internalValue);
|
|
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';
|
|
}
|
|
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 {
|
|
const num = parseFloat(cleaned);
|
|
this.$emit('input', isNaN(num) ? '' : num);
|
|
}
|
|
},
|
|
handleDecimalDigits(val) {
|
|
const actVal = val || this.internalValue;
|
|
let finalValue = actVal.trim();
|
|
|
|
// 检查是否为 "NA" 或类似 "20/30/40" 的格式
|
|
if (finalValue.toUpperCase() === 'NA' || /^\d+(\/\d+)+$/.test(finalValue)) {
|
|
return finalValue;
|
|
}
|
|
|
|
if (finalValue === '' || finalValue === '-') {
|
|
this.internalValue = '';
|
|
this.$emit('input', '');
|
|
return;
|
|
}
|
|
|
|
const num = parseFloat(finalValue);
|
|
if (isNaN(num)) {
|
|
this.internalValue = '';
|
|
this.$emit('input', '');
|
|
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);
|
|
formatted = num.toFixed(displayDecimalDigits).replace(/\.?0*$/, '');
|
|
}
|
|
return formatted;
|
|
},
|
|
|
|
handleBlur() {
|
|
// 检查是否为 "NA" 或类似 "20/30/40" 的格式
|
|
if ((this.internalValue && this.internalValue.toUpperCase() === 'NA') || (this.internalValue && /^\d+(\/\d+)*$/.test(this.internalValue))) {
|
|
this.$emit('input', this.internalValue);
|
|
this.$emit('blur', this.internalValue);
|
|
return;
|
|
}
|
|
|
|
let formatted = this.handleDecimalDigits(this.internalValue);
|
|
this.internalValue = formatted;
|
|
// emit 数字类型(也可 emit 字符串,根据需求)
|
|
this.$emit('input', parseFloat(formatted));
|
|
this.$emit('blur', parseFloat(formatted));
|
|
}
|
|
}
|
|
};
|
|
</script>
|