vue——虚拟键盘simple-keyboard的使用,支持中英文切换
正文
simple-keyboard官网:https://hodgef.com/simple-keyboard/
(多刷新几次)
效果:

我是将输入框+虚拟键盘抽取为组件进行调用的
1.安装依赖
安装simpleKeyboard依赖:
npm install simple-keyboard --save
安装simpleKeyboard输入法依赖:
npm install simple-keyboard-layouts --save
2.组件
2.1.虚拟键盘组件simpleKeyboard.vue
<template> <div :class="keyboardClass"></div> </template> <script> import Keyboard from 'simple-keyboard'; import 'simple-keyboard/build/css/index.css'; import layout from 'simple-keyboard-layouts/build/layouts/chinese'; // 中文输入法 export default { name: 'SimpleKeyboard', props: { keyboardClass: { default: 'simple-keyboard', type: String, }, input: { default: '', }, maxLength: { default: '' }, }, data: () => ({ keyboard: null, displayDefault: { '{bksp}': 'backspace', '{lock}': 'caps', '{enter}': '> enter', '{tab}': 'tab', '{shift}': 'shift', '{change}': '英文', '{space}': ' ', '{clear}': '清空', '{close}': '关闭', }, }), mounted() { this.keyboard = new Keyboard(this.keyboardClass, { onChange: this.onChange, onKeyPress: this.onKeyPress, layoutCandidates: layout.layoutCandidates,
layoutCandidatesPageSize: 5,// 输入建议词每页显示数,默认值5 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}", '{shift} z x c v b n m , . / {clear}', '{change} {space} {close}', ], // shift布局 shift: [ '~ ! @ # $ % ^ & * ( ) _ + {bksp}', '{tab} Q W E R T Y U I O P { } |', '{lock} A S D F G H J K L : " {enter}', '{shift} Z X C V B N M < > ? {clear}', '{change} {space} {close}', ], }, // 按钮展示文字 display: this.displayDefault, // 按钮样式 buttonTheme: [ { class: 'hg-red close', buttons: '{close}', }, { class: 'change', buttons: '{change}', }, ], // 输入限制长度 maxLength: this.maxLength, }); }, methods: { onChange(input) { this.keyboard.setInput(input); this.$emit('onChange', input); }, // 点击键盘 onKeyPress(button, $event) { // 点击关闭 if (button === '{close}') { let keyboard = $event.path[3]; keyboard.style.visibility = 'hidden'; return false; } else if (button === '{change}') { // 切换中英文输入法 if (this.keyboard.options.layoutCandidates !== null) { this.$set(this.displayDefault, '{change}', '中文'); // 切换至英文 this.keyboard.setOptions({ layoutCandidates: null, display: this.displayDefault, }); } else { // 切换至中文 this.$set(this.displayDefault, '{change}', '英文'); this.keyboard.setOptions({ layoutCandidates: layout.layoutCandidates, display: this.displayDefault, }); } } else if (button === '{clear}') { this.keyboard.clearInput(); } else { let value = $event.target.offsetParent.parentElement.children[0].children[0] .value; // 输入框有默认值时,覆写 if (value) { this.keyboard.setInput(value); } this.$emit('onKeyPress', button); } if (button === '{shift}' || button === '{lock}') this.handleShift(); }, // 切换shift/默认布局 handleShift() { let currentLayout = this.keyboard.options.layoutName; let shiftToggle = currentLayout === 'default' ? 'shift' : 'default'; this.keyboard.setOptions({ layoutName: shiftToggle, }); }, }, watch: { input(input) { this.keyboard.setInput(input); }, }, }; </script> <style lang="less"> @deep: ~'>>>'; .hg-theme-default { width: 70%; .hg-button { &.hg-red { background: #db3e5d; color: white; &.close { max-width: 200px; } } &.change { max-width: 200px; } } } </style>
2.2 输入框组件keyboard-input.vue
<template> <div class="input-keyboard"> <el-input v-model="inputValue" :autofocus="autofocus" :class="inputClass" :suffix-icon="suffixIcon" :type="type" :show-password="showPassword" :rows="rows" :show-word-limit="showWordLimit" :disabled="disabled" :maxlength="maxlength" :clearable="clearable" :size="size" :placeholder="placeholder" @focus="focusInput($event)" @input="inputFun" > <template v-if="appendPort" slot="append">[1-65535]</template></el-input ><SimpleKeyboard :ref="keyboardClass" :keyboardClass="keyboardClass" @onChange="onChange" @onKeyPress="onKeyPress" :input="inputValue" :maxLength="maxlength" /> </div> </template> <script> import SimpleKeyboard from './simpleKeyboard.vue'; export default { name: 'keyboard-input', components: { SimpleKeyboard, }, props: { keyboardClass: String, autofocus: Boolean, field: String, value: { default: '', }, inputClass: String, type: { type: String, default: 'text', }, showPassword: { type: Boolean, default: false, }, rows: Number, showWordLimit: { default: true, }, disabled: { type: Boolean, default: false, }, maxlength: Number, clearable: { type: Boolean, default: true, }, size: String, placeholder: String, appendPort: { type: Boolean, default: false, }, autocomplete: { default: '', }, suffixIcon: { default: '', }, }, data() { return { input: null, inputEle: null, }; }, computed: { inputValue: { get() { return this.value; }, set(value) { this.$emit('inputChange', value, this.field); }, }, }, methods: { inputChange() { this.$emit('inputChange'); }, inputFun() { this.$emit('input'); }, focusInput(e) { this.inputEle = e.srcElement; // 关闭所有keyboard let arr = document.querySelectorAll('.hg-theme-default'); arr.forEach((ele) => { ele.style.visibility = 'hidden'; }); // 打开当前输入框的keyboard let currentKeyborad = this.$refs[this.keyboardClass]; currentKeyborad.$el.style.visibility = 'visible'; this.$emit('focus'); }, onChange(input) { this.inputValue = input; // 解决当输入框为密码输入框时,切换显示/隐藏密码,光标在开头问题,注意:element-ui版本号需为2.15.2及以上 this.inputEle.focus(); }, onKeyPress(button) { // console.log('onKeyPress', button); }, }, }; </script> <style lang="less" scoped> @import 'style/less/var'; @deep: ~'>>>'; .input-keyboard { @{deep}.hg-theme-default { position: fixed; left: 50%; bottom: 20px; transform: translate(-50%); visibility: hidden; margin-top: 20px; z-index: 2000; // 中文文字选择框 .hg-candidate-box { position: static; transform: translateY(0); } // 键盘 .hg-rows { } } &.citc-search-input { .el-input { width: 100% !important; } } // 密码输入框显示密码图标效果 @{deep}.el-input__inner[type='text'] + span .el-icon-view { color: red; } } </style>
2.3 登录页login.vue
<template> <div class="login" @click="judgeCloseKeyboard"> <div class="form-card" id="form-card"> <div class="form-body"> <el-form :model="form" ref="form" :rules="rules" @submit.native.prevent> <el-form-item prop="username"> <keyboard-input keyboardClass="login-username" <== 注意:页面内有多个输入框时,每个输入框的keyboardClass必须是唯一的,才不会报错 field="username" :value="form.username" :maxlength="10" clearable @inputChange="formItemChange" placeholder="用户名" ></keyboard-input> </el-form-item> <button class="btn-login" :disabled="loginDisabled" @click.prevent="submit('form')" > {{ isSubmitting ? '登录中' : '登录' }} </button> </el-form> </div> </div> </div> </template> <script> import { KeyboardInput } from '../../components/keyboard-input'; import { keyboardInputMixin } from '@/util/mixins'; import { judgeCloseKeyboard } from '@/util/core'; export default { components: { KeyboardInput, LoginDialog }, mixins: [keyboardInputMixin], data() { return { judgeCloseKeyboard, isSubmitting: false, form: { username: '', }, rules: { username: [ { required: true, message: '请输入用户名', trigger: 'blur' }, ], }, }; }, computed: { loginDisabled() { return this.form.username === '' || this.isSubmitting; }, }, methods: { submit(formName) { this.$refs[formName].validate((valid, errFields) => { if (valid) {} }); }, }, }; </script> <style lang="less" scoped> @deep: ~'>>>'; .login { width: 100%; height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; @{deep}.vue-touch-keyboard{ width: 600px; } } .form-card { width: 550px; border-radius: 5px; background: rgba(255, 255, 255, 0.8); padding: 40px 120px 80px 120px; box-sizing: border-box; .btn-login { width: 100%; height: 40px; background: #db3e5d; border-radius: 2px; border: none; color: rgba(255, 255, 255, 1); &:hover { cursor: pointer; } &:focus { outline: none; } &:disabled { cursor: not-allowed; background-color: rgba(219, 62, 93, 0.5); } } } @{deep}.el-form-item { margin-bottom: 30px; .el-input__inner { padding: 0 16px; font-size: 13px; border-radius: 2px; border-color: rgba(0, 0, 0, 0.15); &:hover, &:focus, &.is-active { border-color: #db3e5d; } } } </style>
2.4 core.js
/** * 点击除输入框及虚拟键盘外元素时,隐藏虚拟键盘 * @param {e} 点击的元素 */ export const judgeCloseKeyboard = e => { e.stopPropagation(); // 阻止事件冒泡 let arr = document.querySelectorAll('.input-keyboard'); // 设置目标区域 let flag = false; const excludeClassName = [ 'input-keyboard', // 带虚拟键盘的输入框 'el-input__inner', // 输入框 'el-input__clear', // 输入框-清除图标 'el-textarea__inner', // 输入框-textarea 'hg-candidate-box', // 中文选择框 'hg-candidate-box-prev', // 中文选择框-上一页 'hg-candidate-box-list', // 中文选择框-内容列表 'hg-candidate-box-list-item', // 中文选择框-内容选项 'hg-candidate-box-next', // 中文选择框-下一页 'hg-theme-default', // 虚拟键盘 'hg-rows', // 虚拟键盘-内容 'hg-row', // 虚拟键盘-行 'hg-button' // 虚拟键盘-按钮 ]; let classList = e.target.classList.value.split(' '); let concatArr = excludeClassName.concat(classList); arr.forEach(ele => { // 判断点击事件发生在区域外的条件是:1. 点击事件的对象不是目标区域本身 2. 事件对象同时也不是目标区域的子元素 if ( // 判断当前点击的元素类名,是否包含排除元素 new Set(concatArr).size === concatArr.length && !ele.contains(e.target) ) { flag = true; } }); flag && hideKeyboard(); }; /** * 隐藏虚拟键盘 */ export const hideKeyboard = () => { let arr = document.querySelectorAll('.hg-theme-default'); arr.forEach(ele => { ele.style.visibility = 'hidden'; }); };
2.5 mixins.js
// 虚拟键盘输入公用方法 export const keyboardInputMixin = { methods: { // 对部分表单字段进行校验 validataForm(field, formname = 'form') { this.$refs[formname].validateField(field); }, // 虚拟键盘输入 formItemChange(val, formItemField) { this.form[formItemField] = val; this.validataForm(formItemField); }, }, };
3.遇到的问题
3.1 列表可复合搜索,搜索部分是组件ref为search,虚拟键盘输入后,直接修改输入框绑定的值,发现变量值改变,但页面未更新
解决方法:
使用$set方法修改复合搜索条件值
keyWordsChange(val) { this.$set(this.$refs.search.form, 'keyWords', val); // 响应式对象修改数据
...
}
3.2 表单内有两个输入框表单项,切换radio通过v-if判断显示其中一个,发现在切换radio后,虚拟键盘的样式错乱
解决方法:
使用v-show判断,但我的两个表单项是必填的,校验时就有问题,这个问题可通过单独给两个表单项写校验规则解决
... <el-form-item v-show="form.typeRadio=== 1" label="名称:" prop="name" class="is-required" <== 必填图标 > <keyboard-input keyboardClass="name" :value="form.name" :maxlength="20" clearable @inputChange="nameChange" placeholder="20位字符以内" ></keyboard-input> </el-form-item> ... const validateName = (rule, value, callback) => { if (this.form.typeRadio === 1) { if (value === '') { return callback(new Error('请输入名称')); } else { callback(); } } else { callback(); } }; ...
3.3 点击虚拟键盘报错:Uncaught TypeError: t.slice is not a function,输入无效
原因:
输入框的值是后端返回的数字,是number类型
解决方法:
使用toString()转换返回值
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通