🔶实际效果

在开发复杂表单和筛选功能时,我们经常需要用户输入一个数值区间,例如价格区间、日期区间,或者像这里的"磨损区间"。今天我分享一个小组件 ------ WearRangeSelector,用来高效、优雅地处理最小值和最大值的输入和校验。
🔶背景与需求
在实际业务中,我们常遇到这样的需求:
- 用户需要输入一个范围,例如
minWear和maxWear。 - 输入值必须为数字。
minWear不能大于maxWear。- 用户按回车时,立即触发搜索或查询。
- 样式上输入框紧密相连,中间用"至"分隔。
这些需求看似简单,但如果没有一个统一的组件,往往每次都会重复写输入校验逻辑,非常不利于维护。
🔶组件功能概览
WearRangeSelector 的核心功能:
- 双输入框,分别绑定最小值和最大值。
- 支持
.sync双向绑定父组件的数据。 - 自动校验输入是否合法:
- 是否为空或非数字
- 最小值不能大于最大值
- 按回车时触发父组件回调。
- 优雅的样式,让两个输入框视觉上连成一个整体。
🔥组件实现
👉完整组件
vue
<template>
<div class="wear-range-selector">
<el-input
@blur="handleBlur"
v-model.trim="internalMinWear"
clearable
placeholder="磨损区间起始值"
style="width: 150px; margin-right: -10px;"
class="filter-item min-wear-input"
@keyup.enter.native="handleEnter"
/>
<span class="wear-separator">至</span>
<el-input
@blur="handleBlur"
v-model.trim="internalMaxWear"
clearable
placeholder="磨损区间最终值"
style="width: 150px;"
class="filter-item max-wear-input"
@keyup.enter.native="handleEnter"
/>
</div>
</template>
<script>
export default {
name: 'WearRangeSelector',
props: {
minWear: {
type: [Number, String],
default: null
},
maxWear: {
type: [Number, String],
default: null
}
},
data() {
return {
internalMinWear: this.minWear,
internalMaxWear: this.maxWear
}
},
watch: {
minWear(val) {
this.internalMinWear = val
},
maxWear(val) {
this.internalMaxWear = val
},
internalMinWear(val) {
this.$emit('update:minWear', val)
},
internalMaxWear(val) {
this.$emit('update:maxWear', val)
}
},
methods: {
handleEnter(e) {
// 1️⃣ 手动让当前输入框失焦
e.target.blur()
// 2️⃣ 确保执行同步逻辑(你原来的 blur 逻辑)
this.handleBlur()
// 3️⃣ 再通知父组件
this.$emit('enter')
},
handleBlur() {
let min = this.internalMinWear
let max = this.internalMaxWear
min = min === '' ? null : min
max = max === '' ? null : max
// 验证并同步数据
if (min != null && isNaN(min)) {
this.$message.error('最小磨损必须为数字')
this.internalMinWear = null
this.$emit('update:minWear', null)
this.$emit('update:maxWear', null)
return
}
if (max != null && isNaN(max)) {
this.$message.error('最大磨损必须为数字')
this.internalMaxWear = null
this.$emit('update:minWear', null)
this.$emit('update:maxWear', null)
return
}
if (min != null && max != null && min > max) {
this.$message.error('最小磨损不能大于最大磨损')
this.internalMinWear = null
this.internalMaxWear = null
this.$emit('update:minWear', null)
this.$emit('update:maxWear', null)
return
}
// 同步数据给父组件
this.$emit('update:minWear', min)
this.$emit('update:maxWear', max)
this.$emit('change', { min, max })
}
}
}
</script>
<style lang="scss" scoped>
.wear-range-selector {
display: inline-flex;
align-items: center;
gap: 0;
vertical-align: middle;
}
.wear-separator {
margin-top: -10px;
color: #999;
padding: 0 12px;
height: 30.5px;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid #dcdfe6;
border-bottom: 1px solid #dcdfe6;
font-size: 14px;
}
// 起始值输入框去除右边框
::v-deep .min-wear-input .el-input__inner {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
}
// 最终值输入框去除左边框
::v-deep .max-wear-input .el-input__inner {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: none;
}
</style>
1️⃣ 模板部分
vue
<template>
<div class="wear-range-selector">
<el-input
@blur="handleBlur"
v-model.trim="internalMinWear"
clearable
placeholder="磨损区间起始值"
style="width: 150px; margin-right: -10px;"
class="filter-item min-wear-input"
@keyup.enter.native="handleEnter"
/>
<span class="wear-separator">至</span>
<el-input
@blur="handleBlur"
v-model.trim="internalMaxWear"
clearable
placeholder="磨损区间最终值"
style="width: 150px;"
class="filter-item max-wear-input"
@keyup.enter.native="handleEnter"
/>
</div>
</template>
- 使用了 Element Plus 的
el-input。 - 左右输入框分别绑定
internalMinWear和internalMaxWear。 - 中间加了一个 "至" 分隔符,让用户直观地理解这是一个区间。
.trim确保输入值去掉首尾空格。
2️⃣ 脚本逻辑
vue
<script>
export default {
name: 'WearRangeSelector',
props: {
minWear: { type: [Number, String], default: null },
maxWear: { type: [Number, String], default: null }
},
data() {
return {
internalMinWear: this.minWear,
internalMaxWear: this.maxWear
}
},
watch: {
minWear(val) { this.internalMinWear = val },
maxWear(val) { this.internalMaxWear = val },
internalMinWear(val) { this.$emit('update:minWear', val) },
internalMaxWear(val) { this.$emit('update:maxWear', val) }
},
methods: {
handleEnter(e) {
e.target.blur() // 手动失焦,触发 blur 校验
this.handleBlur()
this.$emit('enter') // 通知父组件按回车事件
},
handleBlur() {
let min = this.internalMinWear === '' ? null : this.internalMinWear
let max = this.internalMaxWear === '' ? null : this.internalMaxWear
if (min != null && isNaN(min)) {
this.$message.error('最小磨损必须为数字')
this.internalMinWear = null
this.$emit('update:minWear', null)
return
}
if (max != null && isNaN(max)) {
this.$message.error('最大磨损必须为数字')
this.internalMaxWear = null
this.$emit('update:maxWear', null)
return
}
if (min != null && max != null && min > max) {
this.$message.error('最小磨损不能大于最大磨损')
this.internalMinWear = null
this.internalMaxWear = null
this.$emit('update:minWear', null)
this.$emit('update:maxWear', null)
return
}
this.$emit('update:minWear', min)
this.$emit('update:maxWear', max)
this.$emit('change', { min, max })
}
}
}
</script>
关键点:
- 双向绑定通过
internalMinWear和internalMaxWear实现。 - 使用
watch监听 props 与内部值同步,确保.sync能正确工作。 - 校验逻辑严格,防止非数字输入和最小值大于最大值。
3️⃣ 样式美化
vue
<style lang="scss" scoped>
.wear-range-selector {
display: inline-flex;
align-items: center;
}
.wear-separator {
margin-top: -10px;
color: #999;
padding: 0 12px;
height: 30.5px;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid #dcdfe6;
border-bottom: 1px solid #dcdfe6;
}
::v-deep .min-wear-input .el-input__inner {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: none;
}
::v-deep .max-wear-input .el-input__inner {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: none;
}
</style>
- 去掉了左右输入框相邻的边框,视觉上像一个整体。
- 中间的 "至" 通过上下边框和居中对齐,让它自然融入输入框。
4️⃣ 小技巧与优化
- 清空输入框时同步 null
防止空字符串传给后端 API 导致错误。 - 回车自动失焦
保证按回车触发的逻辑和 blur 校验逻辑一致,用户体验更统一。 - 可扩展性强
可以扩展单位(如"%"或"kg"),或增加范围限制(如最大值不能超过 100)。
👉组件使用示例
- 引入组件
vue
import WearRangeSelector from '@/components/WearRangeSelector'
- 导入组件
vue
components: {WearRangeSelector}
- 使用组件
vue
<wear-range-selector
:min-wear.sync="query.minWear"
:max-wear.sync="query.maxWear"
@enter="crud.toQuery"
/>
- 父组件通过
.sync获取和更新minWear和maxWear。 - 按回车时触发
crud.toQuery方法执行查询。 - 可以在父组件中通过
@change监听区间变化实时响应。
⚠总结
WearRangeSelector 是一个简单却实用的区间选择组件,封装了常见的输入校验、样式处理和交互逻辑,减少了重复代码,也提升了用户体验。
无论是价格区间、分数区间还是磨损区间,这个组件都能直接复用,非常适合在企业级后台管理系统中使用。