<template>
<el-input
:model-value="displayValue"
placeholder="请输入"
:max="maxNumber"
:min="minNumber"
@input="handleInput"
@change="handleInputChange"
clearable
/>
</template>
<script setup>
// vue3 组合式API自定义限制小数位数字输入框组件
// <InputNumLimit v-model="num" :precision="2" />
import { computed, reactive, watch } from "vue";
const max = 9999999999;
const min = -9999999999;
const props = defineProps({
modelValue: {
type: [Number, null, String],
},
maxNumber: {
type: Number,
default: max,
},
minNumber: {
type: Number,
default: min,
},
isInterval: {
type: Boolean,
default: false, // 是否是区间范围
},
precision: {
type: Number,
default: 2, // 小数点位数
},
});
const emit = defineEmits(["update:modelValue"]);
const data = reactive({
currentValue: props.modelValue,
userInput: null,
});
const displayValue = computed(() => {
if (data.userInput !== null) {
return data.userInput;
}
return data.currentValue;
});
watch(
() => props.modelValue,
(value, oldValue) => {
// console.log("value=", value, oldValue);
let newVal = [null, undefined, ""].includes(value) ? value : Number(value);
if (![null, undefined, ""].includes(newVal)) {
if (isNaN(newVal)) {
return;
}
newVal = toPrecision(newVal);
}
// if (newVal >= props.maxNumber) newVal = props.maxNumber;
// if (newVal <= props.minNumber) newVal = props.minNumber;
if (data.userInput === null && newVal !== oldValue) {
data.currentValue = newVal; // 初始赋值
emit("update:modelValue", newVal);
}
},
{ immediate: true }
);
const toPrecision = (num) => {
const { precision } = props;
return parseFloat(
Math.trunc((num * Math.pow(10, precision + 1)) / 10) /
Math.pow(10, precision)
);
};
function setCurrentValue(newVal) {
const oldVal = data.currentValue;
if (props.isInterval) {
if (typeof newVal === "number") {
newVal = toPrecision(newVal);
}
if (newVal >= props.maxNumber) newVal = props.maxNumber;
if (newVal <= props.minNumber) newVal = props.minNumber;
}
if (oldVal === newVal) return;
data.userInput = null;
emit("update:modelValue", newVal);
data.currentValue = newVal;
}
function handleInput(val) {
if (isNaN(val) && val !== "-") {
val = val.replace(/\,|\,/g, ""); // 复制时过滤千分符
if (isNaN(val)) {
val = val.slice(0, -1);
val = val.replace(/[^\-\d^\.]+/g, "");
}
}
if (val) {
const valArr = val.split(".");
if (props.precision === 0) {
val = valArr[0];
} else if (valArr[1] && valArr[1].length > props.precision) {
val = valArr[0] + "." + valArr[1].substr(0, props.precision);
}
}
// 最大、最小值限制 - 区间范围数值不能在此限制,如查询时的金额范围
if (!props.isInterval) {
if (val >= props.maxNumber) val = props.maxNumber;
if (val <= props.minNumber) val = props.minNumber;
} else {
if (val >= max) val = max;
if (val <= min) val = min;
}
data.userInput = val;
}
function handleInputChange(val) {
const newVal = val === "" ? undefined : Number(val);
if (!isNaN(newVal) || val === "") {
setCurrentValue(newVal);
}
data.userInput = null;
}
</script>
<template>
<div>
<el-input
ref="inputRef"
:value="displayValue"
:placeholder="placeholder"
:max="maxNum"
:min="minNum"
@input="handleInput"
@change="handleInputChange"
clearable
/>
</div>
</template>
<script>
// vue3 组合式API自定义输入框组件如何在父组件设置v-model
// <CustomInputNumber v-model="inpNum" />
export default {
// name: "CustomInputNumber", // 自定义数字输入框
props: {
value: {}, // 表单输入元素上创建双向数据绑定,使用 emit 来触发事件更新这个值
precision: {
type: Number,
default: () => undefined, // 小数点位
},
},
data() {
return {
currentValue: null,
userInput: null,
maxNum: 1000000,
minNum: -9999,
placeholder: "请输入",
};
},
computed: {
displayValue() {
if (this.userInput !== null) {
return this.userInput;
}
return this.currentValue;
},
},
watch: {
value: {
immediate: true,
handler(value) {
let newVal = [null, undefined].includes(value) ? value : Number(value);
if (![null, undefined].includes(newVal)) {
if (isNaN(newVal)) {
return;
}
if (this.precision !== undefined) {
newVal = this.toPrecision(newVal, this.precision);
}
}
// if (newVal >= this.maxNum) newVal = this.maxNum;
// if (newVal <= this.minNum) newVal = this.minNum;
this.currentValue = newVal; // 初始赋值
this.userInput = null;
this.$emit("input", newVal);
},
},
},
methods: {
toPrecision(num, precision = 2) {
return parseFloat(
Math.trunc((num * Math.pow(10, precision + 1)) / 10) /
Math.pow(10, precision)
);
},
setCurrentValue(newVal) {
const oldVal = this.currentValue;
if (typeof newVal === "number" && this.precision !== undefined) {
newVal = this.toPrecision(newVal, this.precision);
}
if (newVal >= this.maxNum) newVal = this.maxNum;
if (newVal <= this.minNum) newVal = this.minNum;
if (oldVal === newVal) return;
this.userInput = null;
this.$emit("input", newVal);
// this.$emit("change", newVal, oldVal);
this.currentValue = newVal;
},
handleInput(val) {
if (isNaN(val) && val !== "-") {
val = val.replace(/\,|\,/g, ""); // 复制时过滤千分符
if (isNaN(val)) {
val = val.slice(0, -1);
val = val.replace(/[^\-\d^\.]+/g, "");
}
}
if (this.precision !== undefined && val) {
const valArr = val.split(".");
if (this.precision === 0) {
val = valArr[0];
} else if (valArr[1] && valArr[1].length > this.precision) {
val = valArr[0] + "." + valArr[1].substr(0, this.precision);
}
}
// 最大、最小值限制
if (val >= this.maxNum) val = this.maxNum;
if (val <= this.minNum) val = this.minNum;
this.userInput = val;
},
handleInputChange(val) {
const newVal = val === "" ? undefined : Number(val);
if (!isNaN(newVal) || val === "") {
this.setCurrentValue(newVal);
}
this.userInput = null;
},
},
mounted() {
// let innerInput = this.$refs.inputRef.$refs.input;
// innerInput.setAttribute("role", "spinbutton");
// innerInput.setAttribute("aria-valuemax", this.maxNum);
// innerInput.setAttribute("aria-valuemin", this.minNum);
// innerInput.setAttribute("aria-valuenow", this.currentValue); // 输入时限制
},
updated() {
// if (!this.$refs || !this.$refs.inputRef) return;
// const innerInput = this.$refs.inputRef.$refs.input;
// innerInput.setAttribute("aria-valuenow", this.currentValue);
},
};
</script>
/**
* instructions.js
*
* 配合 el-input-number 限制输入框只能输入n位小数
* <el-input-number v-model="num" :controls="false" v-input-number-limit="2"
* @input.native="inputNumEvt"
@change="handleNumberChange" :min="1" :max="10" :precision="2"></el-input-number>
inputNumEvt(nVal) {
// 如果有最大、最小数值限制,InputNumber计数器 实时计算需要配合 @change 使用
// console.log('nVal==', nVal.target.value)
}
handleNumberChange(value) {
console.log(value);
}
*/
export const inputNumberLimt = {
bind(el, binding = {}) {
const { value } = binding
if (value === null || value === undefined) {
return
}
const target = el instanceof HTMLInputElement ? el : el.querySelector("input");
target.addEventListener("input", e => {
let val = e.target.value ? e.target.value.toString() : ''
if (Number(val) != val && val !== '-') {
val = val.slice(0,-1)
val = val.replace(/[^\d^\.]+/g, '')
} else {
const decLen = typeof value === 'number' ? value : value.value
// 小数点 - 长度限制
if (typeof decLen !== 'undefined' && val) {
const valArr = val.split('.')
if (decLen === 0) {
val = valArr[0]
} else if (valArr[1] && valArr[1].length > decLen) {
val = valArr[0] + '.' + valArr[1].substr(0, decLen)
}
}
}
const max_num = value.maxNumber ? typeConversion(value.maxNumber, 'number') : 1000000000000
// const max_num = 1000000000000
// 最大值限制
if(val > max_num) {
val = max_num
}
e.target.value = val; // 不能在此转Number类型,否则无法输入小数点
})
}
}
/**
* main.js 注入自定义指令
*/
import * as instructions from '@/utils/instructions' // 小数点限制指令
Vue.directive('inputNumberLimit', instructions.inputNumberLimt)