车牌输入框 封装 (小程序 vue)

车牌输入框 封装

  • 小程序
    • licenseNumber.js
    • licenseNumber.json
    • licenseNumber.wxml
    • licenseNumber.wxss
    • 页面调用
        • .wxml
            • .js
            • .json
            • .wxss
    • 样例
  • vue
    • vnp-input-box.vue
    • vnp-input.vue
    • vnp-keyboard.vue
    • 样例

小程序

licenseNumber.js

const INPUT_NUM = 8;//车牌号输入框个数
const EmptyArray = new Array(INPUT_NUM).fill('');//['','','','','','','','']

// 车牌输入框的下标
const INPUT_INDEX = {
  FIRST: 0,
  SECOND: 1
};

Component({
  data: {
    // 键
    provinceArr: ['京', '沪', '津', '苏', '粤', '冀', '晋', '蒙', '辽', '吉', '黑', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '桂', '琼', '渝', '川', '贵', '云', '藏', '陕', '甘', '青', '宁', '新', '港', '澳', '台', '使', '领', "→"],
    strArrOne: ['Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'P', '挂'],
    strArrTwo: ['A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', '港', '澳'],
    strArrThree: ['Z', 'X', 'C', 'V', 'B', 'N', 'M', '学', '警'],
    numArr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
    hiddenPro: false, // 隐藏省份键盘
    hiddenStr: true, // 隐藏数字字母键盘
    carNumArr: EmptyArray,
    selectInputIndex: 0,
    btnDisabled: true
  },
  methods: {
    proTap(e) { //点击省份
      let province = e.currentTarget.dataset.province;
      const { carNumArr, selectInputIndex } = this.data;
      this.setData({
        hiddenPro: true,
        hiddenStr: false
      });
      if(province != "→"){
        carNumArr[selectInputIndex] = province;
        // 选择车牌号时触发
        this.setData({
          carNumArr,
          // 选中一个后,下一个输入框聚焦
          selectInputIndex: selectInputIndex !== carNumArr.length - 1 ? selectInputIndex + 1 : selectInputIndex,
          btnDisabled: this.btnDisabled()
        });
      }
    },
    strTap(e) { //点击字母数字
      const str = e.currentTarget.dataset.str;
      const { carNumArr, selectInputIndex } = this.data;
      carNumArr[selectInputIndex] = str;
      this.setData({
        carNumArr,
        // 选中一个后,下一个输入框聚焦
        selectInputIndex: selectInputIndex !== carNumArr.length - 1 ? selectInputIndex + 1 : selectInputIndex,
        btnDisabled: this.btnDisabled()
      });
    },
    inputCarNum(e) {
      const { index } = e.currentTarget.dataset;
      this.setData({
        showCarKeyboard: true,
        selectInputIndex: index
      });
      if (index === INPUT_INDEX.FIRST) {
        // 第一个输入框展示省份键盘,第二个展示字母数字输入框(数字不可点),以后就是数字字母输入框(都可点)
        this.setData({
          hiddenPro: false,
          hiddenStr: true
        });
      } else if (index === INPUT_INDEX.SECOND) {
        this.setData({
          hiddenPro: true,
          hiddenStr: false
        });
      } else {
        this.setData({
          hiddenPro: true,
          hiddenStr: false
        });
      }
    },
    backSpace() { //删除
      const { carNumArr, selectInputIndex } = this.data;
      carNumArr[selectInputIndex] = '';
      this.setData({
        carNumArr,
        selectInputIndex: selectInputIndex !== INPUT_INDEX.FIRST ? selectInputIndex - 1 : selectInputIndex,
        btnDisabled: this.btnDisabled()
      }, () => {
        if (this.data.selectInputIndex === INPUT_INDEX.FIRST) { //这里必须要用this.data.selectInputIndex,用最新的
          this.setData({
            hiddenPro: false,
            hiddenStr: true
          });
        }
      });
    },
    // 只有输入内容的车牌号位数合法时,展示确认按钮
    btnDisabled() {
      const { carNumArr } = this.data;
      const disabled = carNumArr.some((item, index) => {
        if (index !== carNumArr.length - 1) {
          return !item;
        }
        return false;
      });
      return disabled;
    },
    onCancel() {
      this.setData({ carNumArr: EmptyArray });
      this.triggerEvent('onCancel');
    },
    onOk() {
      const carNum = this.data.carNumArr.join('');
      this.triggerEvent('onOk', carNum);
    }
  },
});

licenseNumber.json

{
  "component": true,
  "usingComponents": {}
}

licenseNumber.wxml

<!--车牌-->
<view class="modal-box">
  <view class="modal-wrapper">
    <view class="modal-title">
      <view class="titleWrapper"><text class="title-text">请录入车牌号</text></view>
      <view class="iconWrapper"><image class="close-icon" bindtap="onCancel" src="../../chat/images/SC.png"/></view>
    </view>
      
    <view class="modal-content">
      <view class="modal-input">
        <block wx:for="{{8}}" wx:key="index">
            <view  data-index="{{index}}"  class="input {{selectInputIndex===index?'activeInput':''}}"  bindtap='inputCarNum'>
                <text style="font-size: 30rpx;position: relative;" class="{{index == 7 && carNumArr[7] == '' ? 'XNY' : ''}}">
                  <text wx:if="{{index == 7 && carNumArr[7] == ''}}" style="position: absolute; color: green; display: flex;justify-content: center;align-items:center;font-size: 6px;margin-left: -10px; margin-top: 7px;">新能源</text>
                  <text wx:else="">{{carNumArr[index] || ''}}</text>
                </text>
            </view>
        </block>
        <view class="line"></view>
      </view>
    </view>
    <view>
    </view>
    <view class="model-btn-group">
        <button bindtap="onOk" class="btn confirm" disabled="{{btnDisabled}}">确认</button>
    </view>
  </view>
  <!-- 车牌 -->
  <view class='keyboard' >
    <!-- 省键盘 -->
    <view class="provinces" hidden='{{hiddenPro}}'>
      <view class="pro-li fl" wx:for="{{provinceArr}}" wx:key="index" catchtap='proTap' data-province="{{item}}">{{item}}</view>
    </view>
    <!-- 号码键盘	 -->
    <view class="keyNums" hidden='{{hiddenStr}}'>
      <view  wx:if="{{selectInputIndex===1}}" class="row numRow">
        <view  class="pro-li  disabled"  wx:for="{{numArr}}"  wx:key="index" data-str="{{item}}">{{item}}</view>
      </view>
      <view wx:else class="row numRow">
          <view  class="pro-li " wx:for="{{numArr}}"  wx:key="index" catchtap='strTap' data-str="{{item}}">{{item}}</view>
      </view>
      <view class="strOne row">
        <view  class="pro-li " wx:for="{{strArrOne}}" wx:key="index" catchtap='strTap' data-str="{{item}}">{{item}}</view>
      </view>
      <view class="strTwo row">
        <view  class="pro-li " wx:for="{{strArrTwo}}"wx:key="index" catchtap='strTap' data-str="{{item}}">{{item}}</view>
      </view>
      <view class="strThree row">
        <view class="pro-li " wx:for="{{strArrThree}}" wx:key="index" catchtap='strTap' data-str="{{item}}">{{item}}</view>
        <view class='kb-icon pro-li' catchtap='backSpace'>
          <image class='delete-icon' src="../../chat/images/SC.png" />
        </view>
      </view>
    </view>
  </view>
  <view class="modal-cover"></view>   
</view>


licenseNumber.wxss

/* 键盘 */
.keyboard {
	width: 100%;
	position: fixed;
	bottom: 0;
	left:0;
	z-index: 1000;
	background-color: rgba(210, 213, 219, 90);
}

.fl {
	float: left
}

.carnum {
	text-align: center;
	height: 88rpx
}

.tel {
	border-bottom: 2rpx solid #ddd;
	height: 100rpx;
	line-height: 100rpx;
}

.provinces {
	overflow: hidden;
	padding-top: 20rpx;
}

.pro-li {
	font-size: 32rpx;
	color: #353535;
	height: 76rpx;
	width: 62rpx;
	line-height: 76rpx;
	text-align: center;
	margin-left: 12rpx;
	margin-bottom: 20rpx;
	background-color: #fff;
	box-shadow: 0px 1rpx 2rpx 0 #979797;
	border-radius: 5px;
	flex: 1
}


.keyNums .disabled {
	background-color: #F7F7F7;
	color: #CCC
}

.keyNums {
	overflow: hidden;
	padding-top: 20rpx;
	display: flex;
	flex-direction: column;
}

.keyNums .row {
	display: flex;
}

.keyNums .numRow {
	padding: 0 10rpx;
}

.keyNums .strOne {
	padding: 0 10rpx;
}

.keyNums .strOne .strOneItem {
	flex: 1
}

.keyNums .strTwo {
	padding: 0 40rpx;
}

.keyNums .strOne .strTwoItem {
	flex: 1
}

.keyNums .strThree {
	padding-left: 80rpx;
	padding-right: 10rpx;
}

.keyNums .strOne .strThreeItem {
	flex: 1
}

.keyNums .strOne .strThreeItem:nth-child(7) {
	margin-left: 100px
}

.keyNums .pro-li:nth-child(16) {
	color: red
}

.keyNums .strThree .kb-del {
	margin-left: 12rpx
}

.keyNums .strThree .kb-icon {
	flex: 1.5;
	background: #ABB3BD;
	margin-left: 20rpx;
}

/* modal样式 */

.modal-box {
	width: 100%;
	position: absolute;
	top: 0;
	bottom: 0;
	display: flex;
	align-items: center;
	justify-content: center;
}

.modal-wrapper {
	margin: 30% 30rpx;
	height: 380rpx;
	padding: 30rpx;
	background-color: #fff;
	border-radius: 10rpx;
	z-index: 300;
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	display: flex;
	flex-direction: column;
	align-content: space-between;
	justify-content: space-between;
	overflow: hidden;
	text-align: left;
}

.modal-wrapper .model-btn-group {
	display: flex;
	box-sizing: border-box;
	font-size: 32rpx;
}

.model-btn-group view {
	width: 50%;
	text-align: center;
	box-sizing: border-box;
}

.model-btn-group .btn {
	flex: 1;
	font-size: 18px
}

.model-btn-group .cancel {
	color: #999;
}

.model-btn-group .confirm {
	color: #fff;
	background-color: #ff5000;
}

.model-btn-group .confirm.active {
	opacity: 1;
}

.modal-cover {
	width: 100%;
	background-color: #242424;
	opacity: 0.5;
	z-index: 299;
	position: fixed;
	top: 0;
	left: 0;
	bottom: 0;
	right: 0;
	margin: auto;
	overflow: hidden;
}

.modal-input {
	display: flex;
}

.modal-input .input {
	border: 1px solid #979797;
	margin-right:6rpx;
	border-radius: 3px;
	flex: 1;
	text-align: center;
	padding: 8px;
	height: 22px;
}

.modal-input .input:nth-child(1) {
	border-right-width: 0;
	margin-right: 0;
	position: relative;
	border-bottom-right-radius: 0;
	border-top-right-radius: 0;
}

.modal-input .input:nth-child(1)::after {
	content: "";
	position: absolute;
	right: -1px;
	width: 1px;
	height: 20px;
	background-color: #979797;
	z-index: -10
}


.modal-input .input:nth-child(2) {
	position: relative;
	border-left-width: 0;
	margin-right:20rpx;
	border-bottom-left-radius: 0;
	border-top-left-radius: 0;
}

.modal-input .input:nth-child(2)::after {
	content: "";
	position: absolute;
	right:-16rpx;
	top: 45%;
	width: 5px;
	height: 5px;
	border-radius: 50%;
	background-color: #979797
}

.modal-input .input:nth-child(8) {
	border: 1px dashed #18ca71;
}

.modal-input .activeInput {
	border-radius: 3px !important;
	border: 1px solid #FF5000 !important
}

.modal-input .text {
	text-align: right;
	color: #c5c5c5;
	font-size: 28rpx;
}

.modal-placeholder-class {
	color: #c5c5c5;
}

.modal-title {
	display: flex;
	font-size: 20px;
	color: #333333
}

.titleWrapper {
	flex: 1;
}

.title-text {
	font-size: 18px;
	font-weight: bold;
}

.iconWrapper {
	flex: 1;
	text-align: right;
}

.close-icon {
	width: 35rpx;
	height: 35rpx;
}

.delete-icon {
	width: 40rpx;
	height: 40rpx;
	margin-top: 18rpx;
}

.XNY{
  display: flex;
  justify-content: center;
  align-items:center;
}

页面调用

.wxml

<!-- 保值修复 申请报价……-->
<block wx:if="{{!showKeyboard}}">

	<view wx:else class="jm_pjs_main_four">
       <ul class="jm_pjs_main_five_ul">
         <li>
           <view>
              <input wx:if="{{!showKeyboard}}" value="{{plateNo}}" class="no_border_input" disabled="{{isEdit}}" placeholder="填写车牌号" bindfocus='inputCarNum' placeholder-class='placeholder_input_putty'/>
           </view>
              车牌号:
         </li>
       </ul>
    </view>
</block>
<block wx:if="{{showKeyboard}}">
  <carKeyboard
      carNum="{{carNum}}"
      bind:onOk="onOk"
      bind:onCancel="onCancel"
  />
</block>
.js
Page({
  data: {
    showKeyboard:false, //是否弹出车牌输入键盘
    plateNo: "",//车牌号

  },
  onLoad: function (options) {    
   
  },
  onReady: function () { 
    
  },

   // 点击输入框弹出车牌键盘
   inputCarNum() {
    this.setData({showKeyboard: true})
  },
  onOk(e){
    this.setData({
      plateNo: e.detail
    })
    this.setData({ showKeyboard: false   })
  },
  onCancel(){
    this.setData({
      plateNo:this.data.plateNo
    })
    this.setData({ showKeyboard: false   })
  },
})
.json
{
  "usingComponents": {
  //组件地址
    "carKeyboard": "../components/……/licenseNumber"
  }
}
.wxss
//修改 修改input placeholder    样式
.placeholder_input_putty{
  font-weight: 400;
  color: #b5b5b5;
}

样例

车牌输入框 封装 (小程序 vue)_第1张图片
车牌输入框 封装 (小程序 vue)_第2张图片
车牌输入框 封装 (小程序 vue)_第3张图片
车牌输入框 封装 (小程序 vue)_第4张图片

vue

vnp-input-box.vue

<template>
  <div class="vnp-input-box">
    <ul>
      <li
        v-for="(item, index) in val"
        :key="index"
        :class="{ active: activeIndex === index }"
        @click="handleClickItem(index)"
      >
        <span>{{ item }}</span>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    value: {
      type: String,
      default: "",
    },
    editable: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      val: ["", "", "", "", "", "", "", ""],
      activeIndex: this.editable ? 0 : undefined,
    };
  },
  watch: {
    activeIndex() {
      this.$emit("activeChange", this.activeIndex);
    },
    value: {
      immediate: true,
      handler() {
        this.value=this.value||'';
        if (this.val.join("") === this.value) {
          return;
        }
        const val = this.value.split("");
        if (this.editable) {
          this.activeIndex = val.length;
        }
        while (val.length < 8) {
          val.push("");
        }
        this.val = val;
      },
    },
    val() {
      this.$emit("input", this.val.join(""));
    },
  },
  methods: {
    handleClickItem(index) {
      if (!this.editable) {
        return;
      }
      this.activeIndex = index;
    },
    setValue(val) {
      this.$set(this.val, this.activeIndex, val);
      if (this.activeIndex < 7) {
        this.activeIndex += 1;
      }
    },
    del() {
      this.$set(this.val, this.activeIndex, "");
      if (this.activeIndex > 0) {
        this.activeIndex -= 1;
      }
    },
  },
}
</script>

<style lang="less" scoped>
.vnp-input-box {
  padding: 10px 0;
  border: 1px solid #d8d8d8;
  border-radius: 2px;
  color: #8d8d8d;
  font-size: 15px;
  text-align: center;

  ul {
    display: flex;
  }
  li {
    flex: 1;
    border-right: 1px solid #eaeaea;
    height: 28px;
    line-height: 28px;

    &:first-child {
      border-color: #a6a6a6;
      flex: 1.3;
    }
    &:last-child {
      border: none;
    }
    &.active {
      color: #1989fa;

      > span {
        height: 100%;
        width: 20px;
        display: inline-block;
        border-bottom: 1px solid #1989fa;
      }
    }
  }
}
</style>

vnp-input.vue

<template>
  <div>
    <vnp-input-box
      :value="val"
      @click.native="show = true"
    ></vnp-input-box>
    <vnp-keyboard
      :show.sync="show"
      v-model="val"
    ></vnp-keyboard>
  </div>
</template>

<script>
import Box from "./vnp-input-box";
import Keyboard  from "./vnp-keyboard";

export default {
  props: {
    value: {
      type: String,
      default: "",
    },
  },
  components: {
    'vnp-input-box': Box,
    'vnp-keyboard': Keyboard
  },
  data() {
    return {
      show: false,
    };
  },
  computed: {
    val: {
      set(v) {
        this.$emit("input", v);
      },
      get() {
        return this.value;
      },
    },
  },
};
</script>

vnp-keyboard.vue

<template>
  <van-action-sheet v-model="visible" get-container="body">
    <div class="vnp-header">
      <button type="button" class="vnp-btn-finish" @click="finish">完成</button>
    </div>

    <div class="vnp-input-box-outer">
      <vnp-input-box ref="inputBox" v-model="val" editable @activeChange="handleActiveChange"></vnp-input-box>
    </div>

    <div class="vnp-keys">
       <div class="vnp-keys-row" v-for="(item, index) in list" :key="index">
          <div
            class="vnp-btn-key-wrapper"
            v-for="(val, index) in item"
            :key="index"
            :class="{
              'vnp-del-wrapper': val === 'del',
              'vnp-type-wrapper': val === 'type'
            }"
          >
            <van-button v-if="val === 'type'" class="vnp-btn-key" @click="handleChangeType">
              <span v-if="type === 'cn'">/<span class="vnp-smaller"></span></span>
              <span v-else><span class="vnp-smaller"></span>/</span>
            </van-button>

            <van-button v-else-if="val === 'del'" class="vnp-btn-key" @click="handleDel">
              <svg
                viewBox="0 0 32 22"
                xmlns="http://www.w3.org/2000/svg"
                class="vnp-delete-icon"
              >
                <path
                  d="M28.016 0A3.991 3.991 0 0132 3.987v14.026c0 2.2-1.787 3.987-3.98 3.987H10.382c-.509 0-.996-.206-1.374-.585L.89 13.09C.33 12.62 0 11.84 0 11.006c0-.86.325-1.62.887-2.08L9.01.585A1.936 1.936 0 0110.383 0zm0 1.947H10.368L2.24 10.28c-.224.226-.312.432-.312.73 0 .287.094.51.312.729l8.128 8.333h17.648a2.041 2.041 0 002.037-2.04V3.987c0-1.127-.915-2.04-2.037-2.04zM23.028 6a.96.96 0 01.678.292.95.95 0 01-.003 1.377l-3.342 3.348 3.326 3.333c.189.188.292.43.292.679 0 .248-.103.49-.292.679a.96.96 0 01-.678.292.959.959 0 01-.677-.292L18.99 12.36l-3.343 3.345a.96.96 0 01-.677.292.96.96 0 01-.678-.292.962.962 0 01-.292-.68c0-.248.104-.49.292-.679l3.342-3.348-3.342-3.348A.963.963 0 0114 6.971c0-.248.104-.49.292-.679A.96.96 0 0114.97 6a.96.96 0 01.677.292l3.358 3.348 3.345-3.348A.96.96 0 0123.028 6z"
                  fill="currentColor"
                ></path>
              </svg>
            </van-button>

            <van-button v-else class="vnp-btn-key" :class="{'vnp-btn-empty': !val}" @click="handleClickKey(val)">
              {{ val }}
            </van-button
            >
          </div>
        </div>
    </div>
  </van-action-sheet>
</template>

<script>
import Box from './vnp-input-box'

export default {
  components: {
    'vnp-input-box': Box
  },
  props: {
    show: {
      type: Boolean,
      default: false
    },
    value: {
      type: String,
      default: ""
    },
  },
  data() {
    return {
      val: this.value,
      type: "cn",
      cn: [
        ["京", "津", "沪", "渝", "冀", "豫", "云", "辽", "黑", "湘"],
        ["皖", "鲁", "新", "苏", "浙", "赣", "鄂", "桂", "甘", "晋"],
        ["蒙", "陕", "吉", "闽", "贵", "粤", "青", "藏", "川", "宁"],
        ["type", "琼", "使", "领", "学", "", "", "", "del"]
      ],
      en: [
        ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"],
        ["Q", "W", "E", "R", "T", "Y", "U", "O", "P"],
        ["A", "S", "D", "F", "G", "H", "J", "K", "L"],
        ["type", "Z", "X", "C", "V", "B", "N", "M", "del"]
      ],
    }
  },
  computed: {
    visible: {
      set(val) {
        this.$emit("update:show", val);
      },
      get() {
        return this.show;
      }
    },
    list() {
      return this.type === "en" ? this.en : this.cn;
    }
  },
  watch: {
    show() {
      if (this.show) {
        this.val = this.value;
      }
    }
  },
  methods: {
    handleChangeType() {
      this.type = this.type === "en" ? "cn" : "en";
    },
    handleClickKey(val) {
      if (val) {
        this.$refs.inputBox.setValue(val);
      }
    },
    handleActiveChange(activeIndex) {
      if (activeIndex === 0) {
        this.type = "cn";
      } else {
        this.type = "en";
      }
    },
    handleDel() {
      this.$refs.inputBox.del();
    },
    finish() {
      this.$emit("input", this.val);
      this.visible = false;
    }
  }
}
</script>

<style lang="less" scoped>
.vnp-header {
  height: 40px;
  padding-top: 6px;
  position: relative;

  .vnp-btn-finish {
    position: absolute;
    right: 0;
    height: 100%;
    padding: 0 16px;
    color: #576b95;
    font-size: 14px;
    background-color: transparent;
    border: none;
    cursor: pointer;
  }
}

.vnp-input-box-outer {
  width: 82%;
  max-width: 600px;
  margin: 0 auto;
  padding: 10px;
}

.vnp-keys {
  padding: 3px;
  background: #f2f3f5;
  padding-bottom: 22px;

  .vnp-keys-row {
    display: flex;
    justify-content: center;
  }
  .vnp-btn-key-wrapper {
    flex: 0 1 calc((100% - 6px * 10) / 10);
    padding: 3px;
    box-sizing: content-box;

    &.vnp-del-wrapper,
    &.vnp-type-wrapper {
      flex: 1;
    }
    &.vnp-type-wrapper {
      .vnp-smaller {
        color: #999;
        font-size: 12px;
      }
    }

    .vnp-btn-key {
      padding: 0;
      width: 100%;
      border-radius: 4px;
    }
    .vnp-btn-empty {
      background: transparent;
      border: none;
    }
    .vnp-delete-icon {
      width: 18px;
      vertical-align: middle;
    }
  }
}
</style>>

样例

车牌输入框 封装 (小程序 vue)_第5张图片
车牌输入框 封装 (小程序 vue)_第6张图片
车牌输入框 封装 (小程序 vue)_第7张图片

你可能感兴趣的:(小程序,vue.js,前端,车牌输入框)