一键切换中英,前端虚拟键盘的一次实现


需求背景

手头上这个项目是用于windows工业平板上的,客户在使用过程中是没有鼠标和键盘,那么做一些输入操作,就需要用到虚拟键盘了,今天我用simple-keyboard实现了该功能,遂写下文章记录并分享出来。
demo我放在码云仓库里了,地址是:https://gitee.com/pearpear/vue-simple-keyboard-demo

演示效果

组件交互和逻辑:

直接上代码

首先我们需要安装插件和中文输入法
yarn add simple-keyboard simple-keyboard-layouts

页面上有多个输入框,并且有文本和数字输入框的区别,所以我们需要2个变量来记录当前输入框的key和对应的值,分别是currentInputFieldcurrentInputValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<template>
<div class="container">
<div>
<p>文本输入框</p>
<input type="text" v-model="formData.text" @focus="showKeyboard('text')" />
</div>
<div>
<p>数字输入框</p>
<input type="text" v-model="formData.number" @focus="showKeyboard('number', 'numeric')" />
</div>

<button @click="handleShowResult">表单结果</button>

<div v-if="isShowResult">
<p>文本输入框:{{formData.text}}</p>
<p>数字输入框:{{formData.number}}</p>

</div>

<!-- 虚拟键盘 -->
<SimpleKeyboard
:isShowKeyboard="isShowKeyboard"
:currentKeyboardType="currentKeyboardType"
:currentInputValue="currentInputValue"
@onChange="handleKeyboardInput"
@close="handleKeyboardClose"
/>
</div>

</template>

<script setup>
import { reactive, ref } from 'vue';
import SimpleKeyboard from './components/SimpleKeyboard.vue';
const name = ref('');
const formData = reactive({
text: '',
number: '',
});

// 虚拟键盘相关状态
const isShowKeyboard = ref(false);
const currentInputField = ref('');
const currentInputValue = ref('');
const currentKeyboardType = ref('default');
const isShowResult = ref(false);

// 显示键盘
const showKeyboard = (fieldName, keyboardType = 'default') => {
currentInputField.value = fieldName; // 记录当前具体输入表单的key
currentInputValue.value = formData[fieldName] || ''; // 记录当前具体输入表单的值
currentKeyboardType.value = keyboardType; // 记录当前键盘类型
isShowKeyboard.value = true;
};

// 处理键盘输入
const handleKeyboardInput = (input) => {
currentInputValue.value = input;
formData[currentInputField.value] = input;
};

// 处理键盘关闭
const handleKeyboardClose = () => {
currentInputField.value = '';
currentInputValue.value = '';
isShowKeyboard.value = false;
};

// 显示表单结果
const handleShowResult = () => {
isShowResult.value = true;
};

</script>

首先自定义不同类型键盘,根据currentKeyboardType展示不同的键盘,根据currentInputValue将当前输入框值展示在虚拟键盘上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
SimpleKeyboard.vue

<template>
<div class="keyboard-container" v-show="isShowKeyboard">
<div class="keyboard-header">
<p>{{currentInputValue}}</p>
<button @click="close" class="close-btn">X</button>
</div>
<div class="simple-keyboard" ref="keyboardContainer"></div>
</div>
</template>

<script setup>
import Keyboard from "simple-keyboard";
import "simple-keyboard/build/css/index.css";
import layout from 'simple-keyboard-layouts/build/layouts/chinese'; // 中文输入法
import { ref, onMounted, onUnmounted, defineProps, defineEmits, watch, nextTick } from "vue";


const keyboardContainer = ref(null);
let keyboard = null;

const props = defineProps({
isShowKeyboard: {
type: Boolean,
default: false
},
currentKeyboardType: {
type: String,
default: 'default'
},
currentInputValue: {
type: String,
default: ''
}
})
const emits = defineEmits(["onChange","close"]);
const currentLayout = ref('default');
const displayLanguage = ref({
'{bksp}': '⌫',
'{enter}': '确认',
'{shift}': '⇧',
'{lock}': '⇪',
'{tab}': 'Tab',
'{space}': '空格',
'{language}': "中/英"
});

// 监听可见性变化
watch(() => props.isShowKeyboard, async (newValue) => {
if (newValue) {
// 如果键盘变为可见,确保DOM已渲染后再初始化
await nextTick();
// 给DOM一点时间完全渲染
setTimeout(() => {
initKeyboard();
}, 100);
} else if (!newValue && keyboard) {
// 如果键盘变为不可见,销毁键盘实例
keyboard.destroy();
keyboard = null;
}
});

// 监听初始值变化
watch(() => props.currentInputValue, (newValue) => {
if (keyboard && props.isShowKeyboard) {
keyboard.setInput(newValue);
}
});

// 监听键盘类型变化
watch(() => props.currentKeyboardType, (newValue) => {
if (props.isShowKeyboard) {
// 如果键盘类型变化,需要重新创建键盘
currentLayout.value = newValue === 'numeric' ? 'numeric' : 'default';
initKeyboard();
}
});

onMounted(() => {
initKeyboard();
});

// 初始化键盘
const initKeyboard = async () => {
try {
// 确保DOM已经完全渲染
await nextTick();

// 确保之前的键盘实例被销毁
if (keyboard) {
keyboard.destroy();
keyboard = null;
}

if (keyboardContainer.value) {
// 清空容器内容,避免潜在的DOM冲突
keyboardContainer.value.innerHTML = '';

// 创建新的键盘实例
keyboard = new Keyboard({
...getKeyboardOptions(),
container: keyboardContainer.value
});

// 设置初始值
if (props.currentInputValue) {
keyboard.setInput(props.currentInputValue);
}
}
} catch (error) {
console.error('初始化键盘失败:', error);
// 记录更详细的错误信息
console.error('错误详情:', error.message);
console.error('键盘容器状态:', keyboardContainer.value);
}
};

// 键盘布局配置
const getKeyboardOptions = () => {
const commonOptions = {
onChange: input => emits('onChange', input),
onKeyPress: button => onKeyPress(button),
mergeDisplay: true,
layoutName: currentLayout.value
};

if (props.currentKeyboardType === 'numeric') {
return {
...commonOptions,
layout: {
numeric: [
"1 2 3",
"4 5 6",
"7 8 9",
"0 . {bksp}",
"{clear} {enter}"
]
},
display: {
'{bksp}': '⌫',
'{enter}': '确认',
'{clear}': '清除'
}
};
}

// 默认布局(英文、中文、符号)
return {
...commonOptions,
layout: {
default: [
"` 1 2 3 4 5 6 7 8 9 0 - = {bksp}",
"{tab} q w e r t y u i o p [ ] \\",
"{space} a s d f g h j k l ; ' {enter}",
"{language} z x c v b n m , . / {shift}",
],
shift: [
"~ ! @ # $ % ^ & * ( ) _ + {bksp}",
"{tab} Q W E R T Y U I O P { } |",
'{space} A S D F G H J K L : " {enter}',
"{shift} Z X C V B N M < > ? {shift}",
],
},
display: {
'{bksp}': '⌫',
'{enter}': '确认',
'{shift}': '⇧',
'{lock}': '⇪',
'{tab}': 'Tab',
'{space}': '空格',
'{language}': "中/英"
}
};
};

// 处理输入变化
const onChange = input => {
emits('input', input);
};

// 处理按键点击
const onKeyPress = button => {
if (button === '{enter}') {
close();
} else if (button === '{shift}') {
const currentLayoutName = keyboard.options.layoutName;
const shiftToggle = currentLayoutName === "default" ? "shift" : "default";
keyboard.setOptions({
layoutName: shiftToggle
});
} else if (button === '{clear}') {
keyboard.clearInput();
} else if (button === '{language}') {
console.log(keyboard)
if (keyboard.options.layoutCandidates) {
keyboard.setOptions({
layoutCandidates: null,
display: {
...displayLanguage.value,
'{language}': '中文'
}
});
} else {
keyboard.setOptions({
layoutCandidates: layout.layoutCandidates,
display: {
...displayLanguage.value,
'{language}': 'english'
}
});
// displayLanguage.value = '中文';
}
}
};

const close = () => {
emits('close');
};

</script>

<style scoped>
.keyboard-container {
background: #ececec;
display: flex;
flex-direction: column;
}
.keyboard-header {
display: flex;
justify-content: space-between;
margin: 0 10px;
}
.close-btn {
align-items: flex-end;
}
</style>

ok,我们就完成了虚拟键盘的基本功能。

我的微信公众号: 梨的前端小屋


  目录