访客信息增加车牌号

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;
}
+1 -1
View File
@@ -80,7 +80,7 @@ Page({
onHostNameInput(e) { onHostNameInput(e) {
this.setData({ 'form.hostName': e.detail.value }) this.setData({ 'form.hostName': e.detail.value })
}, },
onPlateNumberInput(e) { onPlateNumberChange(e) {
this.setData({ 'form.plateNumber': e.detail.value }) this.setData({ 'form.plateNumber': e.detail.value })
}, },
+3 -1
View File
@@ -1,4 +1,6 @@
{ {
"usingComponents": {}, "usingComponents": {
"plate-input": "/components/plate-input/plate-input"
},
"navigationBarTitleText": "访客预约" "navigationBarTitleText": "访客预约"
} }
+11 -11
View File
@@ -4,32 +4,32 @@
<view class="section"> <view class="section">
<view class="section-title">预约人信息</view> <view class="section-title">预约人信息</view>
<view class="form-group"> <view class="form-group">
<text class="form-label">姓名</text> <text class="form-label">姓名<text class="required">*</text></text>
<input class="form-input" placeholder="请输入访客姓名" value="{{form.name}}" bindinput="onNameInput" /> <input class="form-input" placeholder="请输入访客姓名" value="{{form.name}}" bindinput="onNameInput" />
</view> </view>
<view class="form-group"> <view class="form-group">
<text class="form-label">手机号</text> <text class="form-label">手机号<text class="required">*</text></text>
<input class="form-input" type="number" maxlength="11" placeholder="请输入手机号" value="{{form.phone}}" bindinput="onPhoneInput" /> <input class="form-input" type="number" maxlength="11" placeholder="请输入手机号" value="{{form.phone}}" bindinput="onPhoneInput" />
</view> </view>
<view class="form-group"> <view class="form-group">
<text class="form-label">公司</text> <text class="form-label">公司<text class="required">*</text></text>
<input class="form-input" placeholder="请输入所属公司" value="{{form.company}}" bindinput="onCompanyInput" /> <input class="form-input" placeholder="请输入所属公司" value="{{form.company}}" bindinput="onCompanyInput" />
</view> </view>
<view class="form-group"> <view class="form-group">
<text class="form-label">来访事由</text> <text class="form-label">来访事由<text class="required">*</text></text>
<input class="form-input" placeholder="请输入来访事由" value="{{form.reason}}" bindinput="onReasonInput" /> <input class="form-input" placeholder="请输入来访事由" value="{{form.reason}}" bindinput="onReasonInput" />
</view> </view>
<view class="form-group"> <view class="form-group">
<text class="form-label">车牌号</text> <text class="form-label">车牌号</text>
<input class="form-input" placeholder="如: 粤A12345(选填)" value="{{form.plateNumber}}" bindinput="onPlateNumberInput" maxlength="8" confirm-type="done" /> <plate-input value="{{form.plateNumber}}" bindchange="onPlateNumberChange" />
</view> </view>
</view> </view>
<!-- 预约时间 --> <!-- 来访时间 -->
<view class="section"> <view class="section">
<view class="section-title">预约时间</view> <view class="section-title">来访时间</view>
<view class="form-group"> <view class="form-group">
<text class="form-label">来访日期</text> <text class="form-label">来访日期<text class="required">*</text></text>
<picker class="form-picker-wrap" mode="date" value="{{form.date}}" start="{{today}}" bindchange="onDateChange"> <picker class="form-picker-wrap" mode="date" value="{{form.date}}" start="{{today}}" bindchange="onDateChange">
<view class="form-picker"> <view class="form-picker">
<text class="{{form.date ? 'picker-value' : 'picker-placeholder'}}">{{form.date || '请选择日期'}}</text> <text class="{{form.date ? 'picker-value' : 'picker-placeholder'}}">{{form.date || '请选择日期'}}</text>
@@ -38,7 +38,7 @@
</picker> </picker>
</view> </view>
<view class="form-group"> <view class="form-group">
<text class="form-label">来访时</text> <text class="form-label">来访时段<text class="required">*</text></text>
<picker class="form-picker-wrap" mode="time" value="{{form.time}}" bindchange="onTimeChange"> <picker class="form-picker-wrap" mode="time" value="{{form.time}}" bindchange="onTimeChange">
<view class="form-picker"> <view class="form-picker">
<text class="{{form.time ? 'picker-value' : 'picker-placeholder'}}">{{form.time || '请选择时间'}}</text> <text class="{{form.time ? 'picker-value' : 'picker-placeholder'}}">{{form.time || '请选择时间'}}</text>
@@ -52,7 +52,7 @@
<view class="section"> <view class="section">
<view class="section-title">被访人信息</view> <view class="section-title">被访人信息</view>
<view class="form-group"> <view class="form-group">
<text class="form-label">拜访区域</text> <text class="form-label">拜访区域<text class="required">*</text></text>
<picker class="form-picker-wrap" range="{{areas}}" value="{{areaIndex}}" bindchange="onAreaChange"> <picker class="form-picker-wrap" range="{{areas}}" value="{{areaIndex}}" bindchange="onAreaChange">
<view class="form-picker"> <view class="form-picker">
<text class="{{areaIndex >= 0 ? 'picker-value' : 'picker-placeholder'}}">{{areaIndex >= 0 ? areas[areaIndex] : '请选择拜访区域'}}</text> <text class="{{areaIndex >= 0 ? 'picker-value' : 'picker-placeholder'}}">{{areaIndex >= 0 ? areas[areaIndex] : '请选择拜访区域'}}</text>
@@ -61,7 +61,7 @@
</picker> </picker>
</view> </view>
<view class="form-group"> <view class="form-group">
<text class="form-label">被访人</text> <text class="form-label">被访人<text class="required">*</text></text>
<picker wx:if="{{personNames.length > 0}}" class="form-picker-wrap" range="{{personNames}}" value="{{personIndex}}" bindchange="onPersonChange"> <picker wx:if="{{personNames.length > 0}}" class="form-picker-wrap" range="{{personNames}}" value="{{personIndex}}" bindchange="onPersonChange">
<view class="form-picker"> <view class="form-picker">
<text class="{{personIndex >= 0 ? 'picker-value' : 'picker-placeholder'}}">{{personIndex >= 0 ? personNames[personIndex] : '请选择被访人'}}</text> <text class="{{personIndex >= 0 ? 'picker-value' : 'picker-placeholder'}}">{{personIndex >= 0 ? personNames[personIndex] : '请选择被访人'}}</text>
+5
View File
@@ -44,6 +44,11 @@ page {
flex-shrink: 0; flex-shrink: 0;
} }
.required {
color: #ff4d4f;
margin-left: 4rpx;
}
.form-input { .form-input {
flex: 1; flex: 1;
font-size: 28rpx; font-size: 28rpx;
+1 -1
View File
@@ -26,7 +26,7 @@
</view> </view>
<view class="action-info"> <view class="action-info">
<text class="action-title">访客预约</text> <text class="action-title">访客预约</text>
<text class="action-desc">选择预约时间和预约人,提交预约信息</text> <text class="action-desc">选择来访时间和预约人,提交预约信息</text>
</view> </view>
<text class="action-arrow"></text> <text class="action-arrow"></text>
</view> </view>
+1 -1
View File
@@ -45,7 +45,7 @@
<text class="record-value">{{item.reason}}</text> <text class="record-value">{{item.reason}}</text>
</view> </view>
<view class="record-row"> <view class="record-row">
<text class="record-label">预约时间</text> <text class="record-label">来访时间</text>
<text class="record-value">{{item.date}} {{item.time}}</text> <text class="record-value">{{item.date}} {{item.time}}</text>
</view> </view>
<view class="record-row"> <view class="record-row">
+4
View File
@@ -36,6 +36,10 @@
<text class="detail-label">来访事由</text> <text class="detail-label">来访事由</text>
<text class="detail-value">{{record.reason}}</text> <text class="detail-value">{{record.reason}}</text>
</view> </view>
<view class="detail-row" wx:if="{{record.plateNumber}}">
<text class="detail-label">车牌号</text>
<text class="detail-value">{{record.plateNumber}}</text>
</view>
</view> </view>
<view class="detail-section"> <view class="detail-section">