|
|
<script setup lang="ts"> import 'simple-keyboard/build/css/index.css'
import Keyboard from 'simple-keyboard' import layout from 'simple-keyboard-layouts/build/layouts/chinese.js' import { useDeviceStore } from 'stores/deviceStore' import { computed, onUnmounted, ref } from 'vue'
defineOptions({ inheritAttrs: false, }) const props = defineProps({ layoutName: { type: String, default: 'default', }, // 保留几位小数 layoutName为number时生效
precision: { type: Number, default: 2, }, }) const emits = defineEmits(['onChange', 'enter', 'close', 'focus']) const deviceStore = useDeviceStore() const deviceType = computed(() => { return __DEVICE_TYPE__ })
const model = defineModel() const keyboard = ref<any>(null) const visible = ref(false) const inputRef = ref() const popoverRef = ref() const entering = ref(false) const width = ref(800) if (props.layoutName === 'number') width.value = 300 const displayDefault = ref({ '{bksp}': 'backspace', '{lock}': 'caps', '{enter}': '回车', '{tab}': 'tab', '{shift}': 'shift', '{change}': 'en', '{space}': 'space', '{clear}': '清空', '{close}': '关闭', '{arrowleft}': '←', '{arrowright}': '→', })
const open = () => { if (visible.value) return inputRef.value.focus() emits('focus') visible.value = true }
const focusInput = (e: any) => { const rect = e.target.getBoundingClientRect() if (rect.top + window.scrollY > 800) { placement.value = 'top' } else { placement.value = 'bottom' } if (visible.value) return if (deviceType.value !== deviceStore.deviceTypeMap.LargeSpaceDM_B) visible.value = true } emits('focus')
// const blurInput = debounce(() => {
// if (!entering.value) {
// handleClose()
// } else {
// entering.value = false
// }
// }, 100)
const blurInput = () => { setTimeout(() => { if (!entering.value) { handleClose() } else { entering.value = false } }, 100) }
const afterEnter = () => { // 存在上一个实例时移除元素
const prevKeyboard = document.querySelectorAll('.init-keyboard') if (prevKeyboard.length > 0) prevKeyboard[0]?.remove() keyboard.value = new Keyboard('simple-keyboard', { onChange, onKeyPress, onInit, layout: { // 默认布局
default: [ '` 1 2 3 4 5 6 7 8 9 0 - = {bksp}', '{tab} q w e r t y u i o p [ ] \\', '{lock} a s d f g h j k l ; \' {enter}', '{change} z x c v b n m , . / {clear}', '{arrowleft} {arrowright} {space} {close}', ], // 大小写
shift: [ '~ ! @ # $ % ^ & * ( ) _ + {bksp}', '{tab} Q W E R T Y U I O P { } |', '{lock} A S D F G H J K L : " {enter}', '{change} Z X C V B N M < > ? {clear}', '{arrowleft} {arrowright} {space} {close}', ], // 数字布局
number: ['7 8 9', '4 5 6', '1 2 3', '. 0 {bksp}', '{arrowleft} {arrowright} {clear} {close}'], }, layoutName: props.layoutName, display: displayDefault.value, theme: 'hg-theme-default init-keyboard', // 添加自定义class处理清空逻辑
}) }
const beforeLeave = () => { visible.value = false entering.value = false inputRef.value.blur() displayDefault.value['{change}'] = 'en' document.removeEventListener('click', handlePopClose) }
const onInit = (keyboard: any) => { keyboard.setInput(model.value) keyboard.setCaretPosition(inputRef.value?.ref.selectionEnd) document.addEventListener('click', handlePopClose) } const onChange = (input: any) => { model.value = input emits('onChange', input) }
const onKeyPress = (button: any) => { if (button === '{lock}') return handleLock() if (button === '{change}') return handleChange() if (button === '{clear}') return handleClear() if (button === '{enter}') return handleEnter() if (button === '{close}') return handleClose() if (button === '{arrowleft}') return handleArrow(0) if (button === '{arrowright}') return handleArrow(1) } const handleLock = () => { entering.value = true const currentLayout = keyboard.value.options.layoutName const shiftToggle = currentLayout === 'default' ? 'shift' : 'default'
keyboard.value.setOptions({ layoutName: shiftToggle, }) } const handleChange = () => { entering.value = true const layoutCandidates = keyboard.value.options.layoutCandidates // 切换中英文输入法
if (layoutCandidates !== null && layoutCandidates !== undefined) { displayDefault.value['{change}'] = 'en' keyboard.value.setOptions({ layoutName: 'default', layoutCandidates: null, display: displayDefault.value, }) } else { displayDefault.value['{change}'] = '中' keyboard.value.setOptions({ layoutName: 'default', layoutCandidates: (layout as any).layoutCandidates, display: displayDefault.value, }) } } const handleClear = () => { keyboard.value.clearInput() model.value = '' } const handleEnter = () => { emits('enter') } const handleClose = () => { if (props.layoutName === 'number') { // 处理精度
// model.value = model.value?.replace(new RegExp(`(\\d+)\\.(\\d{${props.precision}}).*$`), '$1.$2').replace(/\.$/, '')
} popoverRef.value.hide() emits('close') } const handleArrow = (num: number) => { // 处理左右箭头下标位置
const index = keyboard.value.getCaretPositionEnd() if (num === 0 && index - 1 >= 0) { keyboard.value.setCaretPosition(index - 1) } else if (num === 1 && index + 1 <= (model.value?.length || 0)) { keyboard.value.setCaretPosition(index + 1) } }
const handlePopClose = (e: any) => { // 虚拟键盘区域 输入框区域 中文选项区域
if ( (e.target as Element).closest('.keyboard-popper') || e.target === inputRef.value?.ref || /hg-candidate-box/.test(e.target.className) ) { entering.value = true const index = keyboard.value.getCaretPositionEnd() inputRef.value.ref.selectionStart = inputRef.value.ref.selectionEnd = index inputRef.value.focus() } }
const close = () => { handleClose() }
const placement = ref('bottom')
onUnmounted(() => { // 某些情况下未触发动画关闭时销毁事件。此处销毁做后备处理
document.removeEventListener('click', handlePopClose) })
defineExpose({ inputRef, visible, open, close }) </script>
<template> <el-input ref="inputRef" v-model="model" v-bind="$attrs" @focus="focusInput" @blur="blurInput" @keyup.enter="handleEnter" > <template v-for="(item, index) in $slots" :key="index" #[index]> <slot :name="index" /> </template> </el-input>
<el-popover ref="popoverRef" :visible="visible" :virtual-ref="inputRef" virtual-triggering :width="width" :show-arrow="false" :hide-after="0" :placement="placement" popper-style="padding: 0px;color:#000" :persistent="false" popper-class="keyboard-popper" @after-enter="afterEnter" @before-leave="beforeLeave" > <div class="simple-keyboard" /> </el-popover> </template>
<style> .hg-theme-default .hg-button.hg-button-arrowleft, .hg-theme-default .hg-button.hg-button-arrowright { max-width: 70px; } .hg-theme-default .hg-button.hg-button-close { max-width: 100px; } .hg-layout-number .hg-button.hg-button-close { max-width: none; } .hg-layout-number .hg-button.hg-button-bksp { max-width: 92px; } </style>
|