访客信息增加车牌号

This commit is contained in:
chenglijuan
2026-05-06 10:12:36 +08:00
parent 85dca5c7a1
commit 57243dfdf3
11 changed files with 387 additions and 15 deletions
+134
View File
@@ -0,0 +1,134 @@
// plate-input.js
// 车牌号输入组件:先选省份简称 → 再选城市代码 → 最后键盘输入号码
Component({
properties: {
value: { type: String, value: '' }
},
data: {
plateChars: [],
numValue: '',
inputFocus: false,
showProvince: false,
showCity: false,
hasValue: false,
provinces: [
'京', '津', '沪', '渝', '冀', '豫', '云', '辽', '黑', '湘',
'皖', '鲁', '新', '苏', '浙', '赣', '鄂', '桂', '甘', '晋',
'蒙', '陕', '吉', '闽', '贵', '粤', '川', '青', '藏', '琼', '宁'
],
cityLetters: [
'A', 'B', 'C', 'D', 'E', 'F', 'G',
'H', 'J', 'K', 'L', 'M', 'N', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z'
],
numSlots: [0, 1, 2, 3, 4, 5]
},
observers: {
'value': function (val) {
if (val && val !== this._lastEmitted) {
this._parseValue(val)
}
}
},
lifetimes: {
attached() {
if (this.data.value) {
this._parseValue(this.data.value)
}
}
},
methods: {
_parseValue(val) {
const chars = val.split('')
const numValue = chars.slice(2).join('')
this.setData({
plateChars: chars,
numValue: numValue,
hasValue: chars.length > 0
})
},
_emit(chars) {
const value = chars.filter(Boolean).join('')
this._lastEmitted = value
this.triggerEvent('change', { value })
},
// 点击省份格
onProvinceTap() {
this.setData({ showProvince: true, showCity: false, inputFocus: false })
},
// 选择省份
selectProvince(e) {
const code = e.currentTarget.dataset.value
const chars = [code, this.data.plateChars[1]].filter(Boolean)
this.setData({ plateChars: chars, showProvince: false, showCity: true, hasValue: true })
this._emit(chars)
},
// 点击城市格
onCityTap() {
if (!this.data.plateChars[0]) {
this.setData({ showProvince: true })
return
}
this.setData({ showCity: true, showProvince: false, inputFocus: false })
},
// 选择城市代码
selectCity(e) {
const letter = e.currentTarget.dataset.value
const numPart = this.data.plateChars.slice(2).join('')
const chars = [this.data.plateChars[0], letter].concat(numPart.split(''))
this.setData({
plateChars: chars,
showCity: false,
numValue: numPart,
inputFocus: true,
hasValue: true
})
this._emit(chars)
},
// 点击号码格 → 弹出键盘
onNumTap() {
if (!this.data.plateChars[0] || !this.data.plateChars[1]) {
this.setData({ showProvince: true })
return
}
this.setData({ inputFocus: true, showProvince: false, showCity: false })
},
// 键盘输入号码
onNumInput(e) {
const raw = e.detail.value.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 6)
const chars = [this.data.plateChars[0], this.data.plateChars[1]].concat(raw.split(''))
this.setData({ plateChars: chars, numValue: raw, hasValue: true })
this._emit(chars)
},
// 关闭弹窗
hidePicker() {
this.setData({ showProvince: false, showCity: false })
},
noop() {},
// 清除车牌号
clearPlate() {
this.setData({
plateChars: [],
numValue: '',
inputFocus: false,
hasValue: false
})
this._emit([])
}
}
})
+3
View File
@@ -0,0 +1,3 @@
{
"component": true
}
+69
View File
@@ -0,0 +1,69 @@
<!--plate-input.wxml-->
<view class="plate-container">
<!-- 车牌号格子显示 -->
<view class="plate-body">
<view class="plate-cell {{plateChars[0] ? 'filled' : 'empty'}}" bindtap="onProvinceTap">
{{plateChars[0] || '省'}}
</view>
<view class="plate-cell {{plateChars[1] ? 'filled' : 'empty'}}" bindtap="onCityTap">
{{plateChars[1] || '市'}}
</view>
<view class="plate-sep">·</view>
<view
wx:for="{{numSlots}}" wx:key="*this"
class="plate-cell num {{plateChars[item + 2] ? 'filled' : 'empty'}} {{item >= 5 ? 'extra' : ''}}"
bindtap="onNumTap"
>
{{plateChars[item + 2] || (item < 5 ? '' : '新')}}
</view>
</view>
<!-- 隐藏输入框(唤起键盘) -->
<input
class="plate-hidden-input"
type="text"
focus="{{inputFocus}}"
value="{{numValue}}"
maxlength="6"
bindinput="onNumInput"
adjust-position="{{true}}"
confirm-type="done"
/>
<!-- 清除按钮 -->
<view class="plate-clear" wx:if="{{hasValue}}" bindtap="clearPlate">清除</view>
<!-- 省份选择弹窗 -->
<view class="popup-mask" wx:if="{{showProvince}}" bindtap="hidePicker">
<view class="popup-panel" catchtap="noop">
<view class="popup-title">选择省份简称</view>
<scroll-view scroll-y class="popup-scroll">
<view class="popup-grid">
<view
class="popup-item"
wx:for="{{provinces}}" wx:key="*this"
data-value="{{item}}" bindtap="selectProvince"
>{{item}}</view>
</view>
</scroll-view>
<view class="popup-cancel" bindtap="hidePicker">取消</view>
</view>
</view>
<!-- 城市选择弹窗 -->
<view class="popup-mask" wx:if="{{showCity}}" bindtap="hidePicker">
<view class="popup-panel" catchtap="noop">
<view class="popup-title">选择城市代码</view>
<scroll-view scroll-y class="popup-scroll">
<view class="popup-grid">
<view
class="popup-item"
wx:for="{{cityLetters}}" wx:key="*this"
data-value="{{item}}" bindtap="selectCity"
>{{item}}</view>
</view>
</scroll-view>
<view class="popup-cancel" bindtap="hidePicker">取消</view>
</view>
</view>
</view>
+155
View File
@@ -0,0 +1,155 @@
/* plate-input.wxss */
.plate-container {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
/* 车牌格子区域 */
.plate-body {
display: flex;
align-items: center;
background: #f5f7fa;
border: 2rpx solid #dce3ec;
border-radius: 12rpx;
padding: 8rpx 16rpx;
height: 72rpx;
flex: 1;
}
.plate-cell {
width: 56rpx;
height: 56rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 30rpx;
border-radius: 8rpx;
margin: 0 4rpx;
flex-shrink: 0;
transition: all 0.2s;
}
.plate-cell.empty {
color: #b8c9db;
background: #eaf0f7;
font-size: 24rpx;
}
.plate-cell.filled {
color: #2c3e50;
background: #ffffff;
border: 1rpx solid #dce3ec;
font-weight: 600;
}
.plate-cell.num {
width: 44rpx;
}
.plate-cell.num.extra {
border-style: dashed;
border-color: #c8d6e5;
}
.plate-sep {
font-size: 32rpx;
color: #b8c9db;
margin: 0 6rpx;
font-weight: bold;
}
/* 隐藏输入框(用于唤起键盘) */
.plate-hidden-input {
position: absolute;
left: -9999rpx;
width: 0;
height: 0;
opacity: 0;
}
/* 清除按钮 */
.plate-clear {
position: absolute;
right: -60rpx;
top: 50%;
transform: translateY(-50%);
font-size: 24rpx;
color: #ff4d4f;
padding: 8rpx 12rpx;
flex-shrink: 0;
}
/* 弹窗遮罩 */
.popup-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
display: flex;
align-items: flex-end;
}
/* 弹窗面板 */
.popup-panel {
width: 100%;
background: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 32rpx;
padding-bottom: 0;
}
.popup-title {
font-size: 30rpx;
font-weight: 600;
color: #2c3e50;
margin-bottom: 24rpx;
text-align: center;
}
.popup-scroll {
max-height: 400rpx;
padding-bottom: 16rpx;
}
.popup-grid {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
padding: 0 8rpx;
}
.popup-item {
width: calc((100% - 96rpx) / 7);
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
background: #f5f7fa;
border-radius: 12rpx;
font-size: 30rpx;
color: #2c3e50;
border: 1rpx solid #dce3ec;
transition: all 0.15s;
}
.popup-item:active {
background: #5b9bd5;
color: #fff;
border-color: #5b9bd5;
}
.popup-cancel {
text-align: center;
font-size: 28rpx;
color: #999;
padding: 24rpx 0;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
border-top: 1rpx solid #f0f0f0;
margin-top: 16rpx;
}