找回密码
 立即注册
首页 业界区 业界 微信小程序车牌键盘输入组件(支持单个删除更改,支持赋 ...

微信小程序车牌键盘输入组件(支持单个删除更改,支持赋值,支持新能源)

杜优瑗 2025-6-6 15:34:24
网上一搜一大堆类似但大多都相对简单,适用的场景并不多。多数也不支持赋值  不支持单个删除更改
我就借鉴了一下网上文章的思路,为了达到自己想要的效果做了相对应的更改。
效果图如下:
1.gif

直接上代码!

WXML代码:
点击查看代码
  1. <view wx:if="{{isSplit}}" >
  2.   <view wx:for="{{carNum}}" wx:key="id"  bind:tap='openKeyboard'>
  3.    
  4.     <text wx:if="{{ index === 2 }}" >.</text>
  5.    
  6.     <input wx:if="{{ index !== 7 }}" value="{{ item.value }}" data-index="{{index}}" disabled  bindinput="updateCarNum" />
  7.    
  8.     <input wx:else value="{{!showNewPower ? '+' : item.value }}" data-index="{{index}}" disabled  bindinput="updateCarNum" />
  9.   </view>
  10. </view>
  11. <view wx:else>
  12.   <input model:value="{{ vehicleNo }}" maxlength="8" placeholder="请输入车牌号" disabled="{{inputDisabled}}" focus="{{inputFocus}}" cursor="{{inputCursor}}"  bind:tap='openKeyboard' bindinput="updateVehicleNo" />
  13. </view>
  14. <view  hidden='{{!KeyboardState}}'>
  15.   <view >
  16.     <text >请输入车牌号</text>
  17.     <view ></view>
  18.     <view wx:if="{{!isSplit}}"  bindtap='switchKeyboard'>切换键盘</view>
  19.     <view  bindtap='closeKeyboard'>关闭</view>
  20.   </view>
  21.   
  22.   <view  hidden="{{ focusIndex !== 0 }}">
  23.     <view wx:for="{{provinces}}" wx:key="index" >
  24.       <view wx:for="{{item}}" wx:key="index" wx:for-item="char" data-val="{{char}}"  bindtap='bindChoose'>{{char}}</view>
  25.     </view>
  26.   </view>
  27.   
  28.   <view  hidden="{{focusIndex === 0}}">
  29.    
  30.     <view wx:for="{{numbers}}" wx:key="index" >
  31.       <view wx:for="{{item}}" wx:key="index" wx:for-item="char" data-val="{{char}}"  bindtap="{{ focusIndex !== 1 ? 'bindChoose' : '' }}">{{char}}</view>
  32.     </view>
  33.    
  34.     <view wx:for="{{letters}}" wx:key="index" >
  35.       <view wx:for="{{item}}" wx:key="index" wx:for-item="char" data-val="{{char}}"  bindtap="bindChoose">{{char}}</view>
  36.     </view>
  37.     <view  bindtap='bindDelChoose'>
  38.       <text >删除</text>
  39.     </view>
  40.   </view>
  41. </view>
复制代码
JS代码:
点击查看代码
  1. import { isEmpty } from '../../utils/util';
  2. const icon = 'none', duration = 3000
  3. const component = {
  4.   externalClasses: ['car-number-item'], // 提供给父组件外部设置样式
  5.   properties: {
  6.     vehicleNo: { type: String, value: undefined }, // 车牌号
  7.     isSplit: { type: Boolean, value: false } // 是否将车牌拆分为单个字符
  8.   },
  9.   data: {
  10.     provinces: [ // 省份键盘
  11.       ['京', '沪', '粤', '津', '冀', '晋', '蒙', '辽', '吉', '黑'],
  12.       ['苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘'],
  13.       ['桂', '琼', '渝', '川', '贵', '云', '藏'],
  14.       ['陕', '甘', '青', '宁', '新']
  15.     ],
  16.     numbers: [ // 数字键盘
  17.       ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
  18.     ],
  19.     letters: [ // 字母键盘
  20.       ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K'],
  21.       ['L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V'],
  22.       ['W', 'X', 'Y', 'Z', '挂', '港', '澳', '学']
  23.     ],
  24.     specialChar: ['挂', '港', '澳', '学'], // 特殊字符
  25.     carNum: Array(8).fill({ value: undefined, focus: false }),
  26.     inputDisabled: true, // 虚拟键盘时禁用input。避免弹出系统键盘
  27.     inputFocus: false, // input操作时获取焦点(车牌不拆分单个字符,显示为input输入框时)
  28.     inputCursor: true, // input获取焦点弹出系统键盘时光标所在位置
  29.     focusIndex: 0, // 输入时焦点索引(车牌拆分为单个字符时)
  30.     showNewPower: false, // 是否新能源车牌
  31.     KeyboardState: false // 是否弹出虚拟键盘
  32.   },
  33.   attached() {
  34.     const v = this
  35.     const vehcles = v.data.vehicleNo.split('')
  36.     const carNum = v.data.carNum
  37.     let showNewPower = v.data.showNewPower
  38.     vehcles.forEach((m, i) => { carNum[i].value = m })
  39.     if (!isEmpty(carNum[7].value)) showNewPower = true
  40.     v.setData({ carNum, showNewPower })
  41.   },
  42.   /**
  43. * 组件的方法列表
  44. */
  45.   methods: {
  46.     // 车牌输入框点击展开车牌键盘
  47.     openKeyboard(e) {
  48.       const v = this
  49.       // 车牌不拆分为单个字符时 只弹出键盘
  50.       if (!v.data.isSplit) {
  51.         v.setData({ KeyboardState: true, focusIndex: v.data.vehicleNo.length, inputDisabled: true, inputFocus: false })
  52.         wx.hideKeyboard()
  53.       }
  54.       // 车牌拆分为单个字符
  55.       else {
  56.         const focusIndex = e.target.dataset.index
  57.         let carNum = v.data.carNum
  58.         let showNewPower = v.data.showNewPower
  59.         // 添加新能源尾数
  60.         if (focusIndex === 7) {
  61.           if (isEmpty(carNum[6].value)) return wx.showToast({ title: '为新能源车牌时,前几位车牌号不能为空', icon, duration })
  62.           if (v.data.specialChar.includes(carNum[6].value)) return wx.showToast({ title: `为新能源车牌时,第6位车牌号不能为'${carNum[6].value}'`, icon, duration })
  63.           showNewPower = true
  64.           v.setData({ showNewPower, KeyboardState: true })
  65.         }
  66.         // 当前点击获得焦点,其余失去焦点
  67.         carNum[focusIndex].focus = true
  68.         carNum = carNum.map((item, index) => {
  69.           return { ...item, focus: index === focusIndex ? item.focus : false }
  70.         })
  71.         // 点击索引不为新能源且新能源无值时
  72.         if (focusIndex !== 7 && isEmpty(carNum[7].value)) showNewPower = false
  73.         v.setData({ KeyboardState: true, focusIndex, carNum, showNewPower })
  74.       }
  75.     },
  76.     // 键盘选中点击设置
  77.     bindChoose(e) {
  78.       const v = this
  79.       const val = e.target.dataset.val
  80.       // 车牌不拆分为单个字符时
  81.       if (!v.data.isSplit) {
  82.         if (v.data.vehicleNo.length < 8) {
  83.           const vehicleNo = v.data.vehicleNo.concat(val)
  84.           v.setData({ vehicleNo, focusIndex: vehicleNo.length })
  85.           v.triggerEvent('change', { vehicleNo })
  86.         }
  87.       }
  88.       // 车牌拆分为单个字符
  89.       else {
  90.         const carNum = v.data.carNum
  91.         let focusIndex = v.data.focusIndex
  92.         if (focusIndex !== 6 && v.data.specialChar.includes(val)) return
  93.         // 当前选中车牌无值时更新为输入值
  94.         if (isEmpty(carNum[focusIndex].value)) {
  95.           carNum[focusIndex].value = val
  96.           carNum[focusIndex].focus = false
  97.           const validate = v.data.showNewPower ? 7 : 6
  98.           // 上一位车牌取消聚焦
  99.           if (focusIndex < validate) focusIndex++
  100.           carNum[focusIndex].focus = true
  101.           v.setData({ carNum, focusIndex })
  102.         }
  103.         const vehicleNo = carNum.map(m => m.value).join('')
  104.         v.triggerEvent('change', { vehicleNo })
  105.       }
  106.     },
  107.     // 删除
  108.     bindDelChoose() {
  109.       const v = this
  110.       // 车牌不拆分为单个字符时
  111.       if (!v.data.isSplit) {
  112.         const vehicleNo = v.data.vehicleNo.slice(0, -1)
  113.         v.setData({ vehicleNo, focusIndex: vehicleNo.length })
  114.         v.triggerEvent('change', { vehicleNo })
  115.       }
  116.       // 车牌拆分为单个字符
  117.       else {
  118.         const carNum = v.data.carNum
  119.         let focusIndex = v.data.focusIndex
  120.         let showNewPower = v.data.showNewPower
  121.         // 如果删除第6位后继续删除 则focusIndex后退一位并删除第5位车牌 依此类推
  122.         // 当删除至省份位车牌时,界面会控制取消删除按钮
  123.         if (isEmpty(carNum[focusIndex].value)) {
  124.           // 如果当前索引是新能源车牌
  125.           if (focusIndex == 7) showNewPower = false
  126.           focusIndex--
  127.           carNum[focusIndex].value = undefined
  128.           carNum[focusIndex].focus = true
  129.           // 后一位车牌取消聚焦
  130.           carNum[focusIndex + 1].focus = false
  131.         }
  132.         else {
  133.           carNum[v.data.focusIndex].value = undefined
  134.           carNum[v.data.focusIndex].focus = true
  135.         }
  136.         v.setData({ carNum, focusIndex, showNewPower })
  137.         const vehicleNo = carNum.map(m => m.value).join('')
  138.         v.triggerEvent('change', { vehicleNo })
  139.       }
  140.     },
  141.     // 切换键盘
  142.     switchKeyboard() {
  143.       this.setData({ KeyboardState: false, inputDisabled: false }, () => {
  144.         setTimeout(() => {
  145.           this.setData({ inputFocus: true, inputCursor: this.data.vehicleNo.length })
  146.         }, 50)
  147.       })
  148.     },
  149.     // 关闭虚拟键盘
  150.     closeKeyboard() {
  151.       const v = this
  152.       // 车牌不拆分为单个字符时
  153.       if (!v.data.isSplit) v.setData({ inputDisabled: true, inputFocus: false })
  154.       v.setData({ KeyboardState: false })
  155.     },
  156.     // 车牌不拆分为单个字符时,更新车牌
  157.     updateCarNum(e) {
  158.       const index = e.currentTarget.dataset.index
  159.       const value = e.detail.value
  160.       this.setData({ [`carNum[${index}].value`]: value })
  161.     },
  162.     // 系统键盘输入时
  163.     updateVehicleNo() { this.triggerEvent('change', { vehicleNo: this.data.vehicleNo }) }
  164.   }
  165. }
  166. Component(component);
复制代码
WXSS代码:
点击查看代码
  1. .com-vehicle {
  2.   white-space: nowrap;
  3. }
  4. /* 车牌号码 */
  5. .com-input {
  6.   height: 60rpx;
  7.   width: 465rpx;
  8.   background-color: rgba(242, 242, 242, 1);
  9.   box-sizing: border-box;
  10.   padding: 0 20rpx;
  11.   border-radius: 6rpx;
  12.   margin-left: 50rpx;
  13. }
  14. /* 拆分单个字符 车牌号码 */
  15. .com-carNumber-item {
  16.   width: 55rpx;
  17.   height: 60rpx;
  18.   font-size: 18px;
  19.   border: 2rpx solid #CCCCCC;
  20.   border-radius: 8rpx;
  21.   display: inline-block;
  22.   line-height: 30px;
  23.   margin: 0 2rpx;
  24.   vertical-align: middle;
  25.   caret-color: transparent;
  26. }
  27. .com-focus {
  28.   border: 3rpx solid #A8BFF3;
  29. }
  30. /* 新能源 */
  31. .com-carNumber-item-newpower {
  32.   border: 2rpx dashed #19e622;
  33.   background-color: #e1fde2;
  34.   color: #19e622;
  35.   font-size: 25px;
  36.   line-height: 45rpx;
  37. }
  38. /* 虚拟键盘 */
  39. .com-keyboard {
  40.   height: auto;
  41.   background: #d1d5d9;
  42.   position: fixed;
  43.   bottom: 0;
  44.   width: 100%;
  45.   left: 0;
  46.   padding-bottom: 30rpx;
  47. }
  48. .com-keyboard-item {
  49.   padding: 10rpx 0 5rpx 0;
  50.   position: relative;
  51.   display: block;
  52. }
  53. .com-vehicleTip {
  54.   flex: 1;
  55.   color: rgba(150, 179, 253, 1);
  56. }
  57. .com-separator {
  58.   width: 1px;
  59.   height: 50%;
  60.   background-color: #ccc;
  61.   margin: 0 10px;
  62. }
  63. .com-keyboardConfirm {
  64.   height: 70rpx;
  65.   background-color: #f7f7f7;
  66. }
  67. .com-keyboardConfirm_btn {
  68.   float: right;
  69.   line-height: 70rpx;
  70.   font-size: 15px;
  71.   margin-right: 30rpx;
  72.   color: #FFA500;
  73. }
  74. .com-keyboard-line {
  75.   margin: 0 auto;
  76.   text-align: center;
  77. }
  78. .com-carNumber .com-keyboard-line {
  79.   text-align: left;
  80.   margin-left: 5rpx;
  81. }
  82. .com-keyboard-btn {
  83.   font-size: 18px;
  84.   color: #333333;
  85.   background: #fff;
  86.   display: inline-block;
  87.   padding: 18rpx 0;
  88.   width: 63rpx;
  89.   text-align: center;
  90.   box-shadow: 0 2rpx 0 0 #999999;
  91.   border-radius: 10rpx;
  92.   margin: 5rpx 6rpx;
  93. }
  94. .com-keyboard-disabled-btn {
  95.   font-size: 18px;
  96.   color: #333333;
  97.   background: #cacaca;
  98.   display: inline-block;
  99.   padding: 18rpx 0;
  100.   width: 63rpx;
  101.   text-align: center;
  102.   box-shadow: 0 2rpx 0 0 #999999;
  103.   border-radius: 10rpx;
  104.   margin: 5rpx 6rpx;
  105. }
  106. .com-keyboard-del {
  107.   color: black;
  108.   background: #A7B0BC;
  109.   display: inline-block;
  110.   padding: 10rpx 25rpx;
  111.   box-shadow: 0 2rpx 0 0 #999999;
  112.   border-radius: 10rpx;
  113.   margin: 5rpx;
  114.   position: absolute;
  115.   bottom: 10rpx;
  116.   right: 12rpx;
  117. }
复制代码
公用样式代码(app.wxss):
点击查看代码
  1. .text-center {
  2.   text-align: center;
  3. }
  4. .inline-block {
  5.   display: inline-block;
  6. }
  7. .ml-5 {
  8.   margin-left: 5rpx;
  9. }
  10. .mr-5 {
  11.   margin-right: 5;
  12. }
  13. .fs-40 {
  14.   font-size: 40rpx;
  15. }
  16. .bold {
  17.   font-weight: bold;
  18. }
  19. .flex-center {
  20.   display: flex;
  21.   align-items: center;
  22.   justify-content: center;
  23. }
复制代码
公用JS代码:
点击查看代码
  1. /**
  2. * 是否都是空数据(类型:字符串,数值,对象,数组,布尔)
  3. */
  4. const isEmpty = (...values) => {
  5.   const isEmptyFunc = (val) => {
  6.     switch (typeof val) {
  7.       case 'string':
  8.       case 'number': return !val // 数值0为空
  9.       case 'object':
  10.         // 数组
  11.         if (Array.isArray(val)) return val.length === 0
  12.         // null/对象
  13.         return val === null || Object.keys(val).length === 0
  14.       case 'boolean': return false // 布尔类型即非空
  15.       // undefined / function
  16.       default: return true
  17.     }
  18.   }
  19.   let result = true
  20.   values.forEach(m => { result = result && isEmptyFunc(m) })
  21.   return result
  22. }
  23. /**
  24. * 验证
  25. */
  26. const validate = {
  27.   /** 是车牌号 */
  28.   vehicle(value) {
  29.     let result = false
  30.     if (value && [7, 8].includes(value.length)) {
  31.       const express = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4,5}[A-Z0-9挂学警港澳]{1}$/
  32.       result = express.test(value)
  33.     }
  34.     return result
  35.   }
  36. }
复制代码
调用方式:
json文件:
点击查看代码
  1. {
  2.   "navigationBarTitleText": "车牌号输入",
  3.   "usingComponents": {
  4.     "vehicle-keyboard": "../../../../components/vehicle-keyboard/index"
  5.   }
  6. }
复制代码
js:
点击查看代码
  1. // 车牌输入组件完成
  2. confirmVehicle(e) { this.setData({ shipNo: e.detail.vheicleNo }) }
复制代码
wxml:
点击查看代码[code][/code]上面gif生成是用 Windows 系统 Win+Shift+S 将操作截图成视频,再去这个网站 https://www.aconvert.com/cn/video/mp4-to-gif/   将视频转成GIF
2.png

预览的时候把图片下载下来
录制gif的时候有个小bug,已被我修复。
两种模式 isSplit为true 车牌拆分成一个个小框显示,否则文本框显示同时可以切换系统键盘

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册