xml
复制代码
<template>
<!-- 无内部 el-form 嵌套,适配外部 el-form 校验 -->
<div class="number-range-input" ref="rangeInputRef">
<div class="input-wrap" :style="{ width: inputWidth }">
<!-- 最小值输入框:强制数字输入,绑定内部值 -->
<el-input
v-model="innerMin"
placeholder="最小值"
@blur="handleSyncValue"
@input="handleMinInput"
@clear="handleSyncValue"
:clearable="clearable"
type="number"
style="width: calc(50% - 6px);"
size="mini"
:disabled="disabled"
></el-input>
<!-- 视觉分隔符 -->
<span class="separator">-</span>
<!-- 最大值输入框:强制数字输入,绑定内部值 -->
<el-input
v-model="innerMax"
placeholder="最大值"
@blur="handleSyncValue"
@input="handleMaxInput"
@clear="handleSyncValue"
:clearable="clearable"
type="number"
style="width: calc(50% - 6px);"
size="mini"
:disabled="disabled"
></el-input>
</div>
</div>
</template>
<script>
export default {
name: 'NumberRangeInput',
// Vue2 语法:props 接收外部配置(适配 el-form 校验和回显)
props: {
// 双向绑定值:"1,20" 格式字符串(支持回显和 el-form 校验)
value: {
type: String,
default: ''
},
// 输入框整体宽度
inputWidth: {
type: String,
default: '300px'
},
// 是否显示清除按钮
clearable: {
type: Boolean,
default: true
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 最大值不能小于最小值的提示文本
rangeTip: {
type: String,
default: '最小值不能大於最大值'
}
},
data() {
return {
// 内部缓存值(隔离外部 props,避免直接修改,保留字符串格式)
innerMin: '',
innerMax: ''
};
},
watch: {
// 1. 监听外部 value 变化,实现数据回显(支持 "1,20" 格式回显)
value: {
immediate: true,
handler(val) {
if (!val || typeof val !== 'string') {
this.innerMin = '';
this.innerMax = '';
return;
}
// 分割 "1,20" 格式字符串,解析为最小值和最大值
const [minStr, maxStr] = val.split('&&').map(item => item.trim());
const minNum = Number(minStr);
const maxNum = Number(maxStr);
// 校验解析结果有效性,有效则填充输入框
if (!isNaN(minNum) && !isNaN(maxNum) && minStr && maxStr) {
this.innerMin = String(Math.min(minNum, maxNum));
this.innerMax = String(Math.max(minNum, maxNum));
} else {
this.innerMin = '';
this.innerMax = '';
}
}
}
},
methods: {
/**
* 最小值输入监听:联动最大值,确保最小值 ≤ 最大值(保留字符串格式)
*/
handleMinInput() {
const { innerMin, innerMax } = this;
// 转换为数字判断大小,避免字符串比较误差
if (innerMin && innerMax && !isNaN(Number(innerMin)) && !isNaN(Number(innerMax))) {
const minNum = Number(innerMin);
const maxNum = Number(innerMax);
if (minNum > maxNum) {
this.innerMax = innerMin;
this.$message?.warning(this.rangeTip);
}
}
},
/**
* 最大值输入监听:联动最小值,确保最大值 ≥ 最小值(保留字符串格式)
*/
handleMaxInput() {
const { innerMin, innerMax } = this;
// 转换为数字判断大小,避免字符串比较误差
if (innerMin!=='' && innerMax!=='' && !isNaN(Number(innerMin)) && !isNaN(Number(innerMax))) {
const minNum = Number(innerMin);
const maxNum = Number(innerMax);
if (maxNum < minNum) {
this.innerMin =innerMax;
this.$message?.warning(this.rangeTip);
}
}
},
/**
* 核心:同步内部值到外部,生成 "1,20" 格式字符串(实现双向绑定)
*/
handleSyncValue() {
const { innerMin, innerMax } = this;
console.log("innerMin, innerMax ",innerMin, innerMax )
console.log("!isNaN(Number(innerMin) ",isNaN(Number(innerMin)))
console.log("!isNaN(Number(innerMax) ",isNaN(Number(innerMax)))
let result = '';
// 校验内部值有效性(均为有效数字且非空)
if (innerMin!=='' && innerMax!=='' && !isNaN(Number(innerMin)) && !isNaN(Number(innerMax))) {
const minNum = Number(innerMin);
const maxNum = Number(innerMax);
// 生成 "最小值,最大值" 格式字符串,保证顺序正确
const minStr = String(Math.min(minNum, maxNum));
const maxStr = String(Math.max(minNum, maxNum));
result = `${minStr}&&${maxStr}`;
}else{
console.log("不滿足")
}
// Vue2 双向绑定核心:$emit('input') 同步值到外部 v-model
this.$emit('input', result);
// 额外发射事件,方便外部监听详情
this.$emit('rangeChange', result);
},
/**
* 外部调用:校验方法(适配 el-form 自定义校验,返回是否有效)
* @returns {Boolean} 校验结果(有效返回 true,无效返回 false)
*/
validate() {
const { innerMin, innerMax } = this;
// 校验规则:两个值均为有效数字且非空
return innerMin && innerMax && !isNaN(Number(innerMin)) && !isNaN(Number(innerMax));
},
/**
* 外部调用:重置组件(清空输入框,返回空字符串)
*/
reset() {
this.innerMin = '';
this.innerMax = '';
this.handleSyncValue();
}
}
};
</script>
<style scoped>
.number-range-input {
width: 100%;
box-sizing: border-box;
}
.input-wrap {
display: flex;
align-items: center;
justify-content: space-between;
}
.separator {
color: #666;
user-select: none;
}
::v-deep .el-input__inner{
height: 28px !important;
line-height: 28px !important;
padding: 0 3px !important;
font-size: 12px !important;
text-align: center;
}
</style>