|
|
- <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) : '',
- oldValue: null,
- oldPattern: null,
- isNA: this.value !== null && this.value !== undefined ? /^NA$/i.test(String(this.value).toUpperCase()) : false,
- patternRules: [
- { name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i },
- { name: 'FRACTION', pattern: /^\d+(\/\d+)*$/, inputPattern: /^(\d+\/?)*$/ }
- ]
- };
- },
- watch: {
- value: {
- handler(newVal) {
- if (newVal === '' || newVal == null) {
- this.internalValue = '';
- this.oldValue = null;
- this.oldPattern = null;
- this.isNA = false;
- } else {
- const strVal = String(newVal);
- this.internalValue = strVal;
- this.isNA = /^NA$/i.test(strVal.toUpperCase());
- 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 = '';
- this.isNA = false;
- this.$emit('input', '');
- return;
- }
-
- // 检查是否已经输入了完整的NA
- if (this.isNA) {
- // 如果已经输入了NA,无论输入什么,都保持NA不变
- this.internalValue = 'NA';
- this.$emit('input', 'NA');
- return;
- }
-
- const upperVal = val.toUpperCase();
- let cleaned = val;
- let matchedRule = null;
-
- // 检查是否匹配特殊模式(NA或FRACTION)
- // 优先检查完整的NA模式
- if (/^NA$/i.test(upperVal)) {
- matchedRule = { name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i };
- cleaned = upperVal;
- this.isNA = true;
- } else if (upperVal === 'N' || upperVal === 'A') {
- // 部分匹配NA模式
- matchedRule = { name: 'NA', pattern: /^NA$/i, inputPattern: /^N?A?$/i };
- cleaned = upperVal;
- } else {
- // 检查FRACTION模式
- 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] !== '' || i === parts.length - 1) {
- 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';
- }
- }
- }
-
- 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 if (cleaned.includes('.')) {
- // 对于包含小数点的情况,先emit字符串以保持小数点
- this.$emit('input', cleaned);
- } else if (matchedRule && matchedRule.name === 'FRACTION') {
- // 对于FRACTION模式的整数,保持字符串形式以支持后续输入斜杠
- 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 === '-') {
- return '';
- }
-
- const num = parseFloat(finalValue);
- if (isNaN(num)) {
- 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() {
- const val = this.internalValue.trim();
-
- if (val === '') {
- this.oldValue = null;
- this.oldPattern = null;
- this.isNA = false;
- 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.isNA = /^NA$/i.test(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;
- }
-
- // 处理数字
- let formatted = this.handleDecimalDigits(val);
- this.internalValue = formatted;
- const num = parseFloat(formatted);
- this.$emit('input', isNaN(num) ? '' : num);
- this.$emit('blur', isNaN(num) ? '' : num);
- }
- }
- };
- </script>
|