<template>
<zk-theme type="page" :padding="30" showBack navbarTitle="Keyboard键盘">
<view class="" ref="page-wrapper">
</view>
<!-- 基础数字键盘 -->
<zk-theme type="card" :padding="30" :radius="16" margin="0 0 30rpx 0">
<zk-text type="primary" :size="32" weight="bold" margin="0 0 24rpx 0" text="基础数字键盘" />
<view class="input-wrapper input-number" :style="[styleBG]" ref="input-number"
@tap="showKeyboard($event, 'number')">
<view class="">
<zk-text type="secondary" :size="28" text="手机号" />
</view>
<view class="">
<zk-text type="primary" :size="28" :text="numberValue || '点击输入'" />
</view>
</view>
</zk-theme>
<!-- 身份证键盘 -->
<zk-theme type="card" :padding="30" :radius="16" margin="0 0 30rpx 0">
<zk-text type="primary" :size="32" weight="bold" margin="0 0 24rpx 0" text="身份证键盘" />
<view class="input-wrapper input-idcard" :style="[styleBG]" ref="input-idcard"
@tap="showKeyboard($event, 'idcard')">
<view class="">
<zk-text type="secondary" :size="28" text="身份证号" />
</view>
<view class="">
<zk-text type="primary" :size="28" :text="idcardValue || '点击输入'" />
</view>
</view>
</zk-theme>
<!-- 车牌号输入 -->
<zk-theme type="card" :padding="30" :radius="16" margin="0 0 30rpx 0">
<zk-text type="primary" :size="32" weight="bold" margin="0 0 24rpx 0" text="车牌号输入" />
<view class="plate-wrapper">
<!-- 车牌类型切换 -->
<view class="plate-type">
<view class="plate-type__item" :class="{'plate-type__item--active': !isNewEnergy}"
@tap="togglePlateType(false)">
<zk-text :type="!isNewEnergy ? 'primary' : 'secondary'" :size="26" text="普通车牌" />
</view>
<view class="plate-type__item" :class="{'plate-type__item--active': isNewEnergy}"
@tap="togglePlateType(true)">
<zk-text :type="isNewEnergy ? 'primary' : 'secondary'" :size="26" text="新能源车牌" />
</view>
</view>
<!-- 车牌号输入框 -->
<view class="license-plate" ref="license-plate">
<view v-for="(item, index) in showPlateNumber" :key="index" :style="[styleBG]" class="plate-item"
:class="{
'plate-item--active': currentPlateIndex === index,
'plate-item--energy': isNewEnergy && index > 6
}" @tap="showKeyboard($event, 'plate', index)">
<zk-text :type="isNewEnergy && index > 6 ? 'success' : 'primary'" :size="36" weight="bold"
:text="item || ' '" />
</view>
</view>
</view>
</zk-theme>
<!-- 随机数字键盘 -->
<zk-theme type="card" :padding="30" :radius="16" margin="0 0 30rpx 0">
<zk-text type="primary" :size="32" weight="bold" margin="0 0 24rpx 0" text="随机数字键盘" />
<view class="input-wrapper input-random" :style="[styleBG]" ref="input-random"
@tap="showKeyboard($event, 'random')">
<view class="">
<zk-text type="secondary" :size="28" text="支付密码" />
</view>
<view class="">
<zk-text type="primary" :size="28" :text="randomValue || '点击输入'" class="password-text" />
</view>
</view>
</zk-theme>
<view class="" style="height: 600rpx;">
</view>
<!-- 键盘组件 -->
<zk-keyboard :show="keyboardVisible" :type="currentType" :title="keyboardTitle" :random="isRandom"
:max-length="maxLength" @input="onInput" @delete="onDelete" @close="onClose"
@update:show="val => keyboardVisible = val" />
</zk-theme>
</template>
<script>
export default {
data() {
return {
// 键盘控制
keyboardVisible: false,
currentType: 'number',
isRandom: false,
maxLength: 0,
// 各类型输入值
numberValue: '',
idcardValue: '',
plateNumber: ['', '', '', '', '', '', '', ''],
randomValue: '',
// 当前激活的类型和索引
activeType: '',
currentPlateIndex: -1,
// 是否新能源车牌
isNewEnergy: false,
// 页面滚动状态
pageScrolled: false,
originalScrollTop: 0
}
},
computed: {
keyboardTitle() {
const titles = {
number: '请输入手机号',
idcard: '请输入身份证号',
plate: this.currentPlateIndex === 0 ? '请输入省份简称' : '请输入车牌号',
random: '请输入支付密码'
}
return titles[this.activeType] || '请输入'
},
// 显示的车牌号数组
showPlateNumber() {
return this.isNewEnergy ? this.plateNumber : this.plateNumber.slice(0, 7)
},
styleBG() {
var bgInput = uni.$store.state['zk-global'].theme.current.bgInput
const style = {
backgroundColor: bgInput,
}
return style;
}
},
methods: {
// 切换车牌类型
togglePlateType(isNew) {
if (this.isNewEnergy === isNew) return
this.isNewEnergy = isNew
// 清空输入
this.plateNumber = ['', '', '', '', '', '', '', '']
this.currentPlateIndex = -1
},
// 修改 showKeyboard 方法
showKeyboard(event, type, index = -1) {
this.activeType = type
// 获取页面滚动位置
uni.pageScrollTo({
scrollTop: 0,
duration: 0
})
setTimeout(() => {
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
if (type === 'plate') {
dom.getComponentRect(this.$refs['license-plate'], (res) => {
if (res && res.size) {
this.handleScrollPosition(res.size.top)
}
})
} else {
const refName = `input-${type}`
dom.getComponentRect(this.$refs[refName], (res) => {
if (res && res.size) {
this.handleScrollPosition(res.size.top)
}
})
}
// #endif
// #ifndef APP-NVUE
const query = uni.createSelectorQuery().in(this)
if (type === 'plate') {
query.select('.license-plate').boundingClientRect(data => {
if (data) {
this.handleScrollPosition(data.top)
}
}).exec()
} else {
query.select(`.input-${type}`).boundingClientRect(data => {
console.log("data:", data)
if (data) {
this.handleScrollPosition(data.top)
}
}).exec()
}
// #endif
// 设置键盘参数
switch (type) {
case 'number':
this.currentType = 'number'
this.maxLength = 11
this.isRandom = false
break
case 'idcard':
this.currentType = 'idcard'
this.maxLength = 18
this.isRandom = false
break
case 'plate':
this.currentType = index === 0 ? 'plate' : 'plateNumber'
this.currentPlateIndex = index
this.maxLength = 1
this.isRandom = false
break
case 'random':
this.currentType = 'number'
this.maxLength = 6
this.isRandom = true
break
}
this.keyboardVisible = true
}, 100)
},
// 处理���动位置
handleScrollPosition(top) {
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
uni.getSystemInfo({
success: (res) => {
const windowHeight = res.windowHeight
const keyboardHeight = 480
const keyboardHeightPx = keyboardHeight * (res.windowWidth / 750)
const availableHeight = windowHeight - keyboardHeightPx
if (top > availableHeight / 2) {
this.pageScrolled = true
this.originalScrollTop = top
const scrollDistance = top - (availableHeight * 0.3)
// 使用 weex dom 滚动到指定位置
const el = this.$refs['page-wrapper'] // 确保给页面最外层添加 ref
console.log("scrollDistance: " + JSON.stringify(scrollDistance));
if (el) {
dom.scrollToElement(el, {
offset: scrollDistance,
animated: true
})
}
}
}
})
// #endif
// #ifndef APP-NVUE
uni.getSystemInfo({
success: (res) => {
const windowHeight = res.windowHeight
const halfHeight = windowHeight / 2
// 如果点击位置在屏幕下半部分
if (top > halfHeight) {
this.pageScrolled = true
this.originalScrollTop = top
// 滚动到点击位置,向上偏移200px以确保输入框在可见区域
uni.pageScrollTo({
scrollTop: top - 200,
duration: 300
})
}
}
})
// #endif
},
// 输入处理
onInput(value) {
switch (this.activeType) {
case 'number':
this.handleNumberInput(value)
break
case 'idcard':
this.handleIdcardInput(value)
break
case 'plate':
this.handlePlateInput(value)
break
case 'random':
this.handleRandomInput(value)
break
}
},
// 手机号输入处理
handleNumberInput(value) {
this.numberValue += value
if (this.numberValue.length > 11) {
this.numberValue = this.numberValue.slice(0, 11)
}
if (this.numberValue.length >= 11) {
this.keyboardVisible = false
}
},
// 身份证输入处理
handleIdcardInput(value) {
this.idcardValue += value
if (this.idcardValue.length >= 18) {
this.keyboardVisible = false
}
},
// 车牌号输入处理
handlePlateInput(value) {
if (this.currentPlateIndex >= 0 && this.currentPlateIndex < this.plateNumber.length) {
let isValid = true
// 第一位省份验证
if (this.currentPlateIndex === 0) {
isValid = true // 允许输入任何字符,因为键盘本身就限制了可输入内容
}
// 新能源车牌最后一位只允许数字
else if (this.isNewEnergy && this.currentPlateIndex === 7) {
isValid = /^d$/.test(value)
}
// 其他位置允许字母和数字
else {
isValid = /^[A-Z0-9]$/.test(value)
}
if (!isValid) return
this.$set(this.plateNumber, this.currentPlateIndex, value)
// 自动跳转到下一个输入位置
const maxIndex = this.isNewEnergy ? 7 : 6
if (this.currentPlateIndex < maxIndex) {
this.currentPlateIndex++
// 更新键盘类型
this.currentType = this.currentPlateIndex === 0 ? 'plate' : 'plateNumber'
} else {
this.keyboardVisible = false
this.currentPlateIndex = -1
// 输入完成后的回调
const plateNumber = this.plateNumber.join('')
uni.showToast({
title: `车牌号:${plateNumber}`,
icon: 'none'
})
}
}
},
// 随机键盘输入处理
handleRandomInput(value) {
this.randomValue += value
if (this.randomValue.length >= 6) {
this.keyboardVisible = false
}
},
// 删除处理
onDelete() {
switch (this.activeType) {
case 'number':
this.numberValue = this.numberValue.replace(/s/g, '').slice(0, -1)
break
case 'idcard':
this.idcardValue = this.idcardValue.slice(0, -1)
break
case 'plate':
if (this.currentPlateIndex >= 0) {
this.$set(this.plateNumber, this.currentPlateIndex, '')
if (this.currentPlateIndex > 0) {
this.currentPlateIndex--
// 更新键盘类型
this.currentType = this.currentPlateIndex === 0 ? 'plate' : 'plateNumber'
}
}
break
case 'random':
this.randomValue = this.randomValue.slice(0, -1)
break
}
},
// 关闭处理
onClose() {
this.keyboardVisible = false
this.currentPlateIndex = -1
if (this.pageScrolled) {
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
const el = this.$refs['page-wrapper']
if (el) {
dom.scrollToElement(el, {
offset: 0,
animated: true
})
}
// #endif
// #ifndef APP-NVUE
setTimeout(() => {
uni.pageScrollTo({
scrollTop: 0,
duration: 300
})
}, 100)
// #endif
this.pageScrolled = false
}
}
}
}
</script>
<style lang="scss">
.keyboard-show {
padding-bottom: 480rpx;
}
.input-wrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 20rpx 20rpx;
flex: 1;
border-radius: 12rpx;
background-color: red;
&:active {}
}
.password-text {
letter-spacing: 10rpx;
font-family: monospace;
}
.plate-wrapper {
padding: 20rpx;
}
.plate-type {
flex-direction: row;
justify-content: center;
margin-bottom: 20rpx;
display: flex;
&__item {
padding: 12rpx 30rpx;
background-color: #f5f6fa;
border-radius: 8rpx;
margin: 0 10rpx;
transition: all 0.3s;
&--active {
background-color: #e6f0fc;
}
&:active {
opacity: 0.8;
}
}
}
.license-plate {
display: flex;
flex-direction: row;
justify-content: center;
.plate-item {
flex: 1;
height: 80rpx;
border-width: 2rpx;
border-style: solid;
border-color: #ddd;
border-radius: 8rpx;
margin: 0 6rpx;
align-items: center;
justify-content: center;
background-color: #fff;
display: flex;
&--active {
border-color: #007aff;
background-color: #f0f7ff;
}
&--energy {
border-color: #4cd964;
&.plate-item--active {
background-color: #f0fdf0;
}
}
&:first-child {
margin-right: 20rpx;
}
}
}
</style>