|
|
<script lang="ts" setup> import pinyinDict from 'libs/pinyinDict.json' import { useDeviceStore } from 'stores/deviceStore' import type { Ref } from 'vue' import { computed, defineEmits, defineProps, onMounted, ref, watch, watchEffect } from 'vue'
const props = defineProps<{ modelValue: string keyboardType: 'text' | 'number' isVisible: boolean }>() const emits = defineEmits<{ (e: 'update:modelValue', value: string): void (e: 'updateKeyboardVisible', value: boolean): void (e: 'confirm', value: string): void (e: 'close'): void }>() const deviceStete = useDeviceStore() const languageType = ref('en') const inputValue = ref(props.modelValue) const cnList = ref<string[]>([]) const pinyinMap: Record<string, string[]> = pinyinDict const pinyinValue = ref('') // 拖动相关状态
const isDragging = ref(false)// 是否正在拖动
const startX = ref(0)// 触摸起始 X
const startY = ref(0)// 触摸起始 Y
const x = ref(0)// 容器偏移 X
const y = ref(-50)// 容器偏移 Y
const keyboardRef = ref() as Ref<HTMLDivElement> // 软键盘容器 DOM
onMounted(() => { document.addEventListener('click', (e: any) => { if (isOpen.value && !e.target?.name) { isOpen.value = false emits('updateKeyboardVisible', false) } }) })
const isOpen = ref(false) watchEffect(() => { // 在焦点内二次点击时不触发EventListener,做一下延迟处理
console.log('deviceStete.deviceType--11-', deviceStete.isLowCost) if (!deviceStete.isLowCost) { setTimeout(() => { isOpen.value = props.isVisible }, 100) } inputValue.value = props.modelValue })
watch(() => props.isVisible, (newVal) => { console.log('deviceStete.deviceType--2-', deviceStete.isLowCost) if (!deviceStete.isLowCost) { isOpen.value = newVal } })
const activeKey = ref('') const keyboardLayout = computed(() => { if (props.keyboardType === 'number') { return [ ['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], ['.', '0', 'del'], ] } return [ ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'del'], ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '/'], ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', 'enter'], ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '?', '.', ':'], ['close', ' ', 'en'], ] }) const specialKeys = ['del', 'enter', ' '] const handleKeyCn = (cn: string) => { emits('update:modelValue', props.modelValue + cn) cnList.value = [] pinyinValue.value = '' } const handleKeyPress = (key: string) => { activeKey.value = key setTimeout(() => { activeKey.value = '' }, 150)
if (key === 'del') { if (props.keyboardType === 'text' && languageType.value === 'cn' && pinyinValue.value) { pinyinValue.value = pinyinValue.value.slice(0, -1) // 中文
onHandlePinyinToCn(pinyinValue.value) } else { emits('update:modelValue', props.modelValue.slice(0, -1)) } } else if (key === 'enter') { emits('confirm', props.modelValue) closeKeyboard() } else if (key === 'close') { closeKeyboard() } else if ((key === 'en' || key === 'cn') && props.keyboardType === 'text') { languageType.value = key === 'en' ? 'cn' : 'en' keyboardLayout.value[4][2] = key === 'en' ? 'cn' : 'en' cnList.value = [] } else { if (props.keyboardType === 'text' && languageType.value === 'cn') { // 中文
pinyinValue.value = pinyinValue.value + key onHandlePinyinToCn(pinyinValue.value) } else { emits('update:modelValue', props.modelValue + key) } } }
const onHandlePinyinToCn = (keyValue: string) => { const cn: string[] = pinyinMap[keyValue] if (cn && cn.length) { cnList.value = cn } else { cnList.value = [] } }
const closeKeyboard = () => { isOpen.value = false emits('close') }
// 触摸开始:记录初始位置
const handleTouchStart = (e: TouchEvent) => { isDragging.value = true const touch = e.touches[0] startX.value = touch.clientX - x.value startY.value = touch.clientY - y.value }
// 触摸移动:计算偏移量
const handleTouchMove = (e: TouchEvent) => { if (isDragging.value) { const touch = e.touches[0] x.value = touch.clientX - startX.value y.value = touch.clientY - startY.value // const keyboardRect = keyboardRef.value.getBoundingClientRect()
// // 拖动时不超出左边界
// if (newX < 0) {
// newX = 0
// }
// // 拖动时不超出右边界
// else if ((newX + keyboardRect.width) > window.innerWidth) {
// newX = window.innerWidth - keyboardRect.width
// }
// // 拖动时不超出上边界
// if (keyboardRect.height + newY < 0) {
// newY = keyboardRect.height - window.innerHeight + 50
// }
// // 拖动时不超出下边界
// const maxY = keyboardRect.height - (window.innerHeight - keyboardRect.height) + 10
// if (newY > maxY) {
// newY = maxY
// }
// x.value = newX
// y.value = newY
} }
// 触摸结束:停止拖动
const handleTouchEnd = () => { isDragging.value = false } </script>
<template> <div v-if="isOpen" class="soft-keyboard" :class="{ 'keyboard-open': isOpen }"> <!-- <div class="keyboard-header"> <button @click="closeKeyboard"> 关闭键盘 </button> </div> --> <div ref="keyboardRef" class="keyboard-container keyboard-body" :style="{ transform: `translate(${x}px, ${y}px)`, transition: isDragging ? 'none' : 'transform 0.3s ease', width: keyboardType === 'number' ? '30vw' : '66vw', height: keyboardType === 'number' ? '46vh' : '46vh', }" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" > <div> <div v-if="keyboardType === 'text'" class="pinyin-container"> <span v-if="pinyinValue" style="font-size:12px">拼音:{{ pinyinValue }}</span> <div v-if="cnList && cnList.length" class="pinyin-cn"> <div v-for="(cnName, cnIndex) in cnList" :key="cnIndex" class="cn-name" @click="(e) => { e.stopPropagation(); handleKeyCn(cnName) }" > {{ cnName }} </div> </div> </div> <div v-for="(row, index) in keyboardLayout" :key="index" class="keyboard-row"> <button v-for="(key, keyIndex) in row" :key="keyIndex" :class="{ 'key-space': key === ' ', 'key-special': specialKeys.includes(key), 'key-active': activeKey === key, 'key-number': keyboardType === 'number', 'key-text': key !== ' ' && keyboardType === 'text', }" :style="keyboardType === 'number' ? 'height: 10vh' : 'height:3rem;'" @click="(e) => { e.stopPropagation() handleKeyPress(key) }" > {{ key === ' ' ? '空格' : key === 'del' ? '删除' : key === 'enter' ? '确认' : key === 'close' ? '关闭' : key === 'cn' ? '英文' : key === 'en' ? '拼音' : key }} </button> </div> </div> </div> </div> </template>
<style lang="scss" scoped> .soft-keyboard { bottom: -300px; left: 0; right: 0; background-color: #f5f5f5; border-top: 1px solid #ddd; padding: 10px; transition: bottom 0.3s ease; user-select: none; z-index: 9999; }
.keyboard-open { bottom: 0; }
.keyboard-header { margin-bottom: 10px; position: absolute; float:right; }
.keyboard-header button { padding: 8px 15px; background-color: #e0e0e0; border: none; border-radius: 5px; cursor: pointer; }
.keyboard-body { display: flex; flex-direction: column; gap: 8px; .pinyin-container{ display: flex; width: 80%; height: 4rem; padding-left: 2rem; .pinyin-cn{ color: #1890ff; padding-left: 1rem; display: flex; width: 1rem; position: relative; gap:5px; //font-family: fangsong;
.cn-name{ font-size: 2.5rem; } } } }
.keyboard-row { display: flex; justify-content: center; gap: 5px; }
.keyboard-row button { min-width: 50px; font-size: 18px; border: none; border-radius: 5px; background-color: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); cursor: pointer; transition: all 0.2s; height: 10vh; }
.keyboard-row button:hover { /* background-color: #e0e0e0; */ }
.keyboard-row button:active { transform: scale(0.95); }
.key-space { margin-top: 3.5px; width: 75vw;; }
.key-special { background-color: #e0e0e0; }
.key-active { background-color: #a0c4ff; transform: scale(0.95); }
.key-number{ width: 30vw; height: 6vh; margin: 5px; }
.key-text { width: 8vw; height: 5vh; margin: 5px; } .input-w{ width: 20%; height: 4rem; font-size: 2rem; margin-left: 3rem; }
.keyboard-container { position: fixed; bottom: 0; left: 0; width: 90%; border-radius: 16px; box-shadow: 0 -4px 12px rgba(0,0,0,0.1); overflow: hidden; z-index: 9999; /* 让拖动更顺滑 */ will-change: transform; touch-action: none; /* 禁止浏览器默认触摸行为(如滚动) */ background: #c8c8c8; }
.input-box { padding: 16px; background: #f5f5f5; }
.input-field { width: 100%; padding: 12px; text-align: right; font-size: 18px; border: 1px solid #ddd; border-radius: 8px; background: #fff; }
.keys { display: grid; grid-template-columns: repeat(3, 1fr); }
.key { height: 60px; display: flex; justify-content: center; align-items: center; font-size: 20px; border: 1px solid #eee; background: #fff; touch-action: manipulation; /* 加速点击响应 */ }
.key:active { background: #f8f8f8; } </style>
|