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()转换返回值

浙公网安备 33010602011771号