查询效果图
直接上代码:
<template>
<view class="query-vehicle">
<!-- <page-navbar :is-slot="true" title="车辆查询"></page-navbar> -->
<view class="title">请输入查询的车牌号</view>
<!-- 车辆查询 -->
<view>
<view class="car" @click="bFocus = true">
<input :focus="bFocus" type="text" v-model="sCar" disabled class="ipt-hide" :maxlength="cCarNum" />
<view class="car-item" v-for="(nIndex, k) in cCarNum" :class="{active: k === sCar.length }"
:key="nIndex">
{{sCar.split('')[k]?sCar.split('')[k].toUpperCase():''}}
</view>
<view class="car-change" v-if="bIsUnit">
<u-image width="50rpx" height="50rpx" :src="newEnergyPath" />
<text>新能源</text>
</view>
</view>
<!-- 自定义键盘组件 -->
<keyboard-input @typeChange="typeChange" v-if="bFocus" :plate="sCar" @export="setPlate"
@close="bFocus = false" />
</view>
<submit-btn text="确认查询" @handleSubmit="handleSubmit" :isOpacity="isOpacity"></submit-btn>
</view>
</template>
<script>
import KeyboardInput from '@/components/keyboard-input/keyboard-input.vue'
export default {
components: { KeyboardInput },
mixins: [],
data() {
return {
newEnergyPath: '/pages/home/static/new-energy.png',
sCar: '', // 车牌号
bIsUnit: true, // 是否新能源
bFocus: false, // 输入车键盘触发
queryRecord: []
}
},
mounted() {},
computed: {
cCarNum() {
return this.bIsUnit ? 7 : 8; //车牌位数(新能源8位)
},
isOpacity() {
return this.sCar.length >= 7
}
},
onShow() {},
watch: {},
methods: {
// 车牌号完成触发
setPlate(plate) {
if (plate.length >= 7) this.sCar = plate;
this.bFocus = false;
},
// 切换新能源触发
typeChange(e) {
// 2新能源
if (Number(e) === 2) {
this.bIsUnit = false
} else {
this.bIsUnit = true
}
this.sCar = ''
},
// 查询车辆信息
parkingGet(sCarNumber) {
uni.showLoading({
title: '正在查询...',
mask: true
})
this.$u.api.parkingGet({
carLicense: sCarNumber
}).then(res => {
const {
cardUuid
} = res.data
if (cardUuid && cardUuid !== '' && cardUuid !== '0') {
this.$u.route({
url: 'pages/home/parkingFee/view/vehicleInformation',
params: res.data
})
} else {
uni.showToast({
title: '未查询到停车记录',
icon: 'none'
})
}
}).catch(err => {
throw err
}).finally(() => {
uni.hideLoading();
})
},
handleSubmit() {
if (this.sCar.length >= 7) {
this.parkingGet(this.sCar)
} else {
uni.showToast({
title: '请输入正确车牌号查询',
icon: 'none'
})
}
}
}
}
</script>
<style lang='scss'>
.query-vehicle {
padding: 60rpx 0;
background: #FFFFFF;
.title {
padding: 0 30rpx 50rpx;
font-size: 40rpx;
font-weight: bold;
color: #262626;
}
.car {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
margin-bottom: 270rpx;
width: 100%;
box-sizing: border-box;
.car-item {
height: 106rpx;
width: 72rpx;
margin: 0 6rpx;
text-align: center;
line-height: 106rpx;
color: #262626;
font-size: 50rpx;
border: 2rpx solid #D8D8D8;
border-radius: 6rpx;
}
.active {
border: 2rpx solid #0683FF;
}
.ipt-hide {
position: absolute;
z-index: -1;
left: -100%;
opacity: 0
}
.car-change {
height: 106rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 20rpx;
padding: 4rpx;
margin: 0 6rpx;
line-height: 2;
color: #BBBBBB;
border: 2rpx dotted #D8D8D8;
border-radius: 6rpx;
}
}
}
</style>
键盘组件部分:
自定义车键盘组件:keyboard-input.vue uni-plate-input.css uni-plate-input.less
新建三个文件夹同级:
keyboard-input.vue
<template>
<view class="so-mask">
<view style="height: 100%" @tap="$emit('close')" />
<view class="so-plate animation-scale-up">
<view class="so-plate-head">
<view class="so-plate-type">
<radio-group @change="typeChange">
<label>
<radio value="1" :checked="type===1" />
普通车牌
</label>
<label>
<radio value="2" :checked="type===2" />
新能源车牌
</label>
</radio-group>
</view>
</view>
<view class="so-plate-body">
<view class="so-plate-word" :class="{ active: currentInputIndex == 0 }" @tap="inputSwitch"
data-index="0">
<text>{{ currentInputValue[0] }}</text>
</view>
<view class="so-plate-word" :class="{ active: currentInputIndex == 1 }" @tap="inputSwitch"
data-index="1">
<text>{{ currentInputValue[1] }}</text>
</view>
<view class="so-plate-dot"></view>
<view class="so-plate-word" :class="{ active: currentInputIndex == 2 }" @tap="inputSwitch"
data-index="2">
<text>{{ currentInputValue[2] }}</text>
</view>
<view class="so-plate-word" :class="{ active: currentInputIndex == 3 }" @tap="inputSwitch"
data-index="3">
<text>{{ currentInputValue[3] }}</text>
</view>
<view class="so-plate-word" :class="{ active: currentInputIndex == 4 }" @tap="inputSwitch"
data-index="4">
<text>{{ currentInputValue[4] }}</text>
</view>
<view class="so-plate-word" :class="{ active: currentInputIndex == 5 }" @tap="inputSwitch"
data-index="5">
<text>{{ currentInputValue[5] }}</text>
</view>
<view class="so-plate-word" :class="{ active: currentInputIndex == 6 }" @tap="inputSwitch"
data-index="6">
<text>{{ currentInputValue[6] }}</text>
</view>
<view class="so-plate-word" :class="{ active: currentInputIndex == 7 }" @tap="inputSwitch"
v-if="type == 2" data-index="7">
<text>{{ currentInputValue[7] }}</text>
</view>
</view>
<view class="so-plate-foot">
<view class="so-plate-keyboard" :style="{height:keyboardHeight}">
<view id="keyboard">
<block v-if="inputType == 1">
<view hover-class="hover" class="so-plate-key" v-for="el of provinceText" :key="el"
:data-value="el" @tap="chooseKey">{{ el }}</view>
</block>
<block v-if="inputType == 1">
<text class="so-plate-key fill-block"></text>
</block>
<block v-if="inputType >= 3">
<view hover-class="hover" class="so-plate-key" v-for="el of numberText" :key="el"
:data-value="el" @tap="chooseKey">{{ el }}</view>
</block>
<block v-if="inputType >= 2">
<view hover-class="hover" class="so-plate-key" v-for="el of wordText" :key="el"
:data-value="el" @tap="chooseKey">{{ el }}</view>
</block>
<block v-if="inputType == 3">
<text v-for="el of fillBlock" :key="el.num" class="so-plate-key fill-block"></text>
</block>
<block v-if="inputType == 4">
<view hover-class="hover" class="so-plate-key" v-for="el of lastWordText" :key="el"
:data-value="el" @tap="chooseKey">{{ el }}</view>
</block>
<text v-if="inputType == 4" class="so-plate-key fill-block"></text>
</view>
</view>
<view class="so-plate-btn-group">
<view>
<button class="so-plate-btn so-plate-btn--cancel" @tap="$emit('close')">取消</button>
</view>
<view>
<button class="so-plate-btn so-plate-btn--delete" @tap="deleteKey">删除</button>
<button class="so-plate-btn so-plate-btn--submit" @tap="exportPlate">完成</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'uni-plate-input',
data() {
return {
type: 1, //车牌类型
currentInputIndex: 0, //当前编辑的输入框
currentInputValue: ['', '', '', '', '', '', ''],
fillBlock: [{
num: 11
}, {
num: 12
}, {
num: 13
}, {
num: 14
}, {
num: 15
}, {
num: 16
}], //避免:key报错
keyboardHeightInit: false,
keyboardHeight: 'auto',
provinceText: [
'粤',
'京',
'冀',
'沪',
'津',
'晋',
'蒙',
'辽',
'吉',
'黑',
'苏',
'浙',
'皖',
'闽',
'赣',
'鲁',
'豫',
'鄂',
'湘',
'桂',
'琼',
'渝',
'川',
'贵',
'云',
'藏',
'陕',
'甘',
'青',
'宁',
'新'
],
numberText: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],
wordText: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U',
'V', 'W', 'X', 'Y', 'Z'
],
lastWordText: ['挂', '港', '学', '领', '警']
};
},
props: {
plate: {
type: String
}
},
computed: {
//输入框类型
inputType() {
switch (this.currentInputIndex) {
case 0:
return 1;
break;
case 1:
return 2;
break;
case 2:
return 3;
break;
case 3:
return 3;
break;
case 4:
return 3;
break;
case 5:
return 3;
break;
case 6:
return this.type == 2 ? 3 : 4;
break;
case 7:
return 4;
break;
default:
return 1;
break;
}
}
},
watch: {
currentInputIndex: function(n, o) {
if (!this.keyboardHeightInit) return
this.$nextTick(() => {
this.changeKeyboardHeight()
})
}
},
methods: {
//车牌类型切换
typeChange(e) {
this.$emit("typeChange", e.detail.value);
const {
value
} = e.detail;
this.type = parseInt(value)
this.currentInputIndex = 0
if (value == 1) {
this.currentInputValue = ['', '', '', '', '', '', '']
} else {
this.currentInputValue = ['', '', '', '', '', '', '', '']
}
},
inputSwitch(e) {
const {
index
} = e.currentTarget.dataset;
this.currentInputIndex = parseInt(index);
},
chooseKey(e) {
const {
value
} = e.currentTarget.dataset;
this.$set(this.currentInputValue, this.currentInputIndex, value);
if (this.type == 1 && this.currentInputIndex < 6) {
this.currentInputIndex++
}
if (this.type == 2 && this.currentInputIndex < 7) {
this.currentInputIndex++
}
},
deleteKey() {
this.$set(this.currentInputValue, this.currentInputIndex, '')
if (this.currentInputIndex != 0) this.currentInputIndex--
},
exportPlate() {
const plate = this.currentInputValue.join('')
let err = false
if (this.type === 1 && plate.length != 7) {
err = true
} else if (this.type === 2 && plate.length != 8) {
err = true
}
if (err) return uni.showToast({
title: '请输入完整的车牌号码',
icon: 'none'
})
this.$emit('export', plate)
},
changeKeyboardHeight() {
const that = this
const query = uni.createSelectorQuery().in(this);
query.select('#keyboard').boundingClientRect();
query.exec(function(res) {
if (res && res[0]) {
that.keyboardHeight = res[0].height + uni.upx2px(30) + 'px'
that.keyboardHeightInit = true
}
});
}
},
mounted() {
// console.log(this.plate);
const plateKey = this.plate.split('')
if (plateKey.length === 7) {
this.type = 1
} else if (plateKey.length === 8) {
this.type = 2
}
if (plateKey.length === 7 || plateKey.length === 8) {
this.currentInputValue = plateKey
this.currentInputIndex = plateKey.length - 1
}
setTimeout(() => { //在动画结束之后才开始获取
this.$nextTick(() => {
this.changeKeyboardHeight()
})
}, 500);
}
};
</script>
<style scoped lang="less">
@import './uni-plate-input';
</style>
关于uni-plate-input.css部分:
.so-mask {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 998
}
.so-plate {
box-sizing: border-box;
position: absolute;
bottom: 0;
width: 100%;
left: 0;
background: #fff;
padding: 25upx 25upx 0 25upx
}
.so-plate-head {
display: -webkit-box;
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
align-items: center
}
.so-plate-type {
-webkit-box-flex: 1;
flex: 1;
display: block
}
.so-plate-type label {
display: inline-block;
min-height: 32upx;
font-size: 26upx;
margin-right: 20upx
}
.so-plate-body {
box-sizing: border-box;
padding: 30upx 0;
display: -webkit-box;
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
align-items: center
}
.so-plate-word {
border: 1upx solid #ccc;
border-radius: 10upx;
height: 0;
margin: 0 5upx;
box-sizing: border-box;
padding-bottom: calc(4.28571429%);
width: calc(4.28571429%);
position: relative
}
.so-plate-word.active {
border-color: #007aff;
box-shadow: 0 0 15upx 0 #007aff
}
.so-plate-word text {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
font-weight: 700;
font-size: 32upx
}
.so-plate-dot {
width: 15upx;
height: 15upx;
background: #ccc;
border-radius: 50%;
margin: 0 5upx
}
.so-plate-keyboard {
background: #eee;
margin-left: -25upx;
margin-right: -25upx;
padding: 20upx 25upx 10upx 25upx;
box-sizing: border-box;
-webkit-transition: all .3s;
transition: all .3s
}
.so-plate-keyboard>view {
display: -webkit-box;
display: flex;
flex-wrap: wrap;
-webkit-box-pack: justify;
justify-content: space-between
}
.so-plate-key {
display: block;
background: #fff;
border-radius: 10upx;
box-shadow: 0 0 8upx 0 #bbb;
width: 80upx;
height: 80upx;
margin: 5upx 0;
font-size: 32upx;
text-align: center;
display: -webkit-box;
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
position: relative
}
.so-plate-key.hover {
background: #efefef
}
.so-plate-key.fill-block {
width: 80upx;
height: 80upx;
background: none;
box-shadow: none
}
.so-plate-btn {
display: inline-block;
background: #fff;
border-radius: 10upx;
box-shadow: 0 0 10upx 0 #bbb;
font-size: 28upx;
text-align: center;
margin: 0 0 0 10upx
}
.so-plate-btn-group {
display: -webkit-box;
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
background: #eee;
margin-left: -25upx;
margin-right: -25upx;
box-sizing: border-box;
padding: 0 25upx 10upx 25upx
}
.so-plate-btn--cancel {
margin: 0
}
.so-plate-btn--submit {
background: #5773f9;
color: #fff
}
.so-plate-btn--delete {
color: #fd6b6d
}
.animation-scale-up {
-webkit-animation-duration: .2s;
animation-duration: .2s;
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation-name: scale-up;
animation-name: scale-up
}
@-webkit-keyframes scale-up {
0% {
opacity: .8;
-webkit-transform: scale(.8);
transform: scale(.8)
}
100% {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1)
}
}
@keyframes scale-up {
0% {
opacity: .8;
-webkit-transform: scale(.8);
transform: scale(.8)
}
100% {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1)
}
}
关于uni-plate-input.less部分:
.so-mask {
position: fixed;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 998;
}
.so-plate {
box-sizing: border-box;
position: absolute;
bottom: 0;
width: 100%;
left: 0;
background: #fff;
padding: 25upx 25upx 0 25upx;
&-head {
display: flex;
justify-content: space-between;
align-items: center;
}
&-type {
flex:1;
display:block;
label {
display: inline-block;
min-height: 32upx;
font-size: 26upx;
margin-right: 10upx;
}
}
&-body {
box-sizing: border-box;
padding: 30upx 0;
display: flex;
justify-content: space-between;
align-items: center;
}
&-word {
border: 1upx solid #ccc;
border-radius: 10upx;
height: 0;
margin: 0 5upx;
box-sizing: border-box;
padding-bottom: calc((100% - 70upx) / 7);
width: calc((100% - 70upx) / 7);
position: relative;
&.active {
border-color: #007aff;
box-shadow: 0 0 15upx 0 #007aff;
}
text {
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
font-weight: 700;
font-size: 32upx;
}
}
&-dot {
width: 15upx;
height: 15upx;
background: #ccc;
border-radius: 50%;
margin: 0 5upx;
}
&-keyboard {
background: #eee;
margin-left: -25upx;
margin-right: -25upx;
padding: 20upx 25upx 10upx 25upx;
box-sizing: border-box;
transition: all .3s;
&>view{
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
}
&-key {
display: block;
background: #fff;
border-radius: 10upx;
box-shadow: 0 0 8upx 0 #bbb;
width: 80upx;
height: 80upx;
margin: 5upx 0;
font-size: 32upx;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
position: relative;
&.hover {
background: #efefef;
}
&.fill-block {
width: 80upx;
height: 80upx;
background: none;
box-shadow: none;
}
}
&-btn {
display: inline-block;
background: #fff;
border-radius: 10upx;
box-shadow: 0 0 10upx 0 #bbb;
font-size: 28upx;
text-align: center;
margin:0 0 0 10upx;
padding:0 25upx;
&-group{
display: flex;
justify-content: space-between;
background: #eee;
margin-left: -25upx;
margin-right: -25upx;
box-sizing: border-box;
padding: 0 25upx 10upx 25upx;
}
&--cancel{
margin:0;
}
&--submit{
background:#5773f9;
color:#fff;
}
&--delete{
color:#fd6b6d;
}
}
}
.animation-scale-up {
animation-duration: .2s;
animation-timing-function: ease-out;
animation-fill-mode: both;
animation-name: scale-up
}
@keyframes scale-up {
0% {
opacity: .8;
transform: scale(.8)
}
100% {
opacity: 1;
transform: scale(1)
}
}
三个文件目录结构: