构建一个 Vue 基于el-input的磨损区间选择器组件 —— WearRangeSelector

🔶实际效果

在开发复杂表单和筛选功能时,我们经常需要用户输入一个数值区间,例如价格区间、日期区间,或者像这里的"磨损区间"。今天我分享一个小组件 ------ WearRangeSelector,用来高效、优雅地处理最小值和最大值的输入和校验。


🔶背景与需求

在实际业务中,我们常遇到这样的需求:

  • 用户需要输入一个范围,例如 minWearmaxWear
  • 输入值必须为数字。
  • minWear 不能大于 maxWear
  • 用户按回车时,立即触发搜索或查询。
  • 样式上输入框紧密相连,中间用"至"分隔。

这些需求看似简单,但如果没有一个统一的组件,往往每次都会重复写输入校验逻辑,非常不利于维护。


🔶组件功能概览

WearRangeSelector 的核心功能:

  1. 双输入框,分别绑定最小值和最大值。
  2. 支持 .sync 双向绑定父组件的数据。
  3. 自动校验输入是否合法:
    • 是否为空或非数字
    • 最小值不能大于最大值
  4. 按回车时触发父组件回调。
  5. 优雅的样式,让两个输入框视觉上连成一个整体。

🔥组件实现

👉完整组件

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
  • 左右输入框分别绑定 internalMinWearinternalMaxWear
  • 中间加了一个 "至" 分隔符,让用户直观地理解这是一个区间。
  • .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>

关键点:

  • 双向绑定通过 internalMinWearinternalMaxWear 实现。
  • 使用 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️⃣ 小技巧与优化

  1. 清空输入框时同步 null
    防止空字符串传给后端 API 导致错误。
  2. 回车自动失焦
    保证按回车触发的逻辑和 blur 校验逻辑一致,用户体验更统一。
  3. 可扩展性强
    可以扩展单位(如"%"或"kg"),或增加范围限制(如最大值不能超过 100)。

👉组件使用示例

  1. 引入组件
vue 复制代码
import WearRangeSelector from '@/components/WearRangeSelector'
  1. 导入组件
vue 复制代码
components: {WearRangeSelector}
  1. 使用组件
vue 复制代码
<wear-range-selector 
  :min-wear.sync="query.minWear"
  :max-wear.sync="query.maxWear"
  @enter="crud.toQuery"
  />
  • 父组件通过 .sync 获取和更新 minWearmaxWear
  • 按回车时触发 crud.toQuery 方法执行查询。
  • 可以在父组件中通过 @change 监听区间变化实时响应。

⚠总结

WearRangeSelector 是一个简单却实用的区间选择组件,封装了常见的输入校验、样式处理和交互逻辑,减少了重复代码,也提升了用户体验。

无论是价格区间、分数区间还是磨损区间,这个组件都能直接复用,非常适合在企业级后台管理系统中使用。

相关推荐
遗憾随她而去.2 小时前
前端 Vue 虚拟列表(Virtual List),从原理到实战
前端·javascript·vue.js
tangdou3690986552 小时前
图文并茂手把手教你Claude Code 多智能体 Agent Teams,一人变团队
前端·后端·ai编程
看客随心2 小时前
element-ui table表格 tr间距\行间距设置
vue.js·ui·elementui
工边页字2 小时前
图文教学,服务端如何发送(钉钉 +飞书 )机器人通知
java·前端·后端
竹林8182 小时前
从零集成RainbowKit:我如何解决多链钱包连接中的“幽灵网络”问题
前端·javascript
前端炒粉2 小时前
Webpack 基础核心内容总结
前端·webpack·node.js
光影少年2 小时前
前端安全问题?XSS和CSRF?
前端·安全·xss
小小小小宇2 小时前
RSA攻略
前端
happymaker06262 小时前
web前端学习日记——DAY08(jQuery,json文件格式,bootstrap)
前端·学习·jquery