Keyboard 键盘

自定义键盘组件,支持数字键盘、身份证键盘、车牌号键盘等多种模式。支持主题定制和随机键位功能。

代码示例

				
<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>
			

API

Props

参数 说明 类型 默认值
show 是否显示键盘 boolean false
type 键盘类型,可选值为 number/idcard/plate/plateNumber/letter string plate
title 键盘标题 string 请输入
showHeader 是否显示标题栏 boolean true
random 是否开启随机键盘 boolean false
maxLength 输入值最大长度 number 8
themeColors 主题配置,支持自定义键盘颜色 object 从全局主题获取

Events

事件名 说明 回调参数
input 点击按键时触发 按键值
delete 点击删除键时触发 -
close 键盘关闭时触发 -

主题定制

键盘组件支持主题定制,可以通过全局主题配置或组件级别的 themeColors 属性来自定义以下样式:

键盘类型说明

类型 说明 特点
number 数字键盘 包含 0-9 和小数点
idcard 身份证键盘 包含 0-9 和 X
plate 车牌号键盘 支持省份和字母数字切换
plateNumber 车牌号数字键盘 仅字母数字模式
letter 字母键盘 包含字母和数字