返回顶部

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 &lt; &gt; ? {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()转换返回值

 

posted @   前端-xyq  阅读(13399)  评论(10编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示
回到顶部