vue3实现虚拟键盘simple-keyboard

初始化,需要引入依赖:

npm install simple-keyboard --save
npm install simple-keyboard-layouts --save //中文依赖库

 初始软键盘:
 

<template>
  <div :class="keyboardClass"></div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import Keyboard from 'simple-keyboard'
import 'simple-keyboard/build/css/index.css'
// 引入中文输入法
import layout from 'simple-keyboard-layouts/build/layouts/chinese' // 中文库

const props = defineProps({
  keyboardClass: {
    default: 'simple-keyboard',
    type: String,
  },
  maxLength: { default: '' }
})

const emit = defineEmits(['onChange', 'empty', 'onKeyPress','requestSub', 'closeKeyboard'])

let keyboard = null
const displayDefault = ref({
  '{bksp}': 'backspace',
  '{lock}': '切换',
  '{enter}': 'Enter',
  '{tab}': 'Tab',
  '{shift}': 'Shift',
  '{change}': '中文',
  '{space}': ' ',
  '{clear}': '清空',
  '{close}': '关闭'
})

onMounted(() => {
  keyboard = new Keyboard(props.keyboardClass, {
    onChange: onChange,
    onKeyPress: onKeyPress,
    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} {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}',
        '{shift} Z X C V B N M < > ? {clear} {close}'
      ]
    },
    display: displayDefault.value,
    buttonTheme: [
      {
        class: 'hg-red close',
        buttons: '{close}'
      },
      {
        class: 'change',
        buttons: '{change}'
      }
    ],
    maxLength: props.maxLength
  })
})

function onChange(input) {
  emit('onChange', input)
}

function onChangeKey() {
  if (keyboard) {
    keyboard.setInput('')
    emit('empty')
  }
}

function onChangeFocus(value) {
  if (keyboard) {
    keyboard.setInput(value)
  }
}
function onKeyPress(button, $event) {
  if (button === '{close}') {
    emit('closeKeyboard') // 注意:这里假设父组件有名为 closeKeyboard 的方法
    return false
  } else if (button === '{change}') {
    console.log(1)
    toggleChineseLayout()
  } else if (button === '{clear}') {
    console.log(2)
    onChangeKey()
  } else if (button === '{enter}') {
    emit('requestSub')
    return
  } else {
    console.log(button)
    let value = $event.target.offsetParent.parentElement.children[0].children[0].value
    if (value) {
      if (keyboard) {
        keyboard.setInput(value)
      }
    }
    emit('onKeyPress', button)
  }

  if (button === '{shift}' || button === '{lock}') handleShift()
}

function toggleChineseLayout() {
  if (keyboard) {
    const currentLayoutCandidates = keyboard.options.layoutCandidates !== null
    displayDefault.value['{change}'] = currentLayoutCandidates ? '英文' : '中文'
    keyboard.setOptions({
      layoutCandidates: currentLayoutCandidates ? null : layout.layoutCandidates,
      display: displayDefault.value
    })
  }
}

function handleShift() {
  if (keyboard) {
    const currentLayout = keyboard.options.layoutName
    const shiftToggle = currentLayout === 'default' ? 'shift' : 'default'
    keyboard.setOptions({ layoutName: shiftToggle })
  }
}
// 暴露方法给父组件
defineExpose({
  onChangeFocus
})
</script>
<style lang="less">
.hg-candidate-box {
  position: fixed;
  width: 100%;
  font-size: 42px;

  .hg-candidate-box-list {
    .hg-candidate-box-list-item {
      padding: 0 20px;
    }
  }
}
.hg-rows {
  width: 100% !important;
  .hg-row {
    height: 40px; // 按钮行的高度
    //padding: 10px;
    .hg-button { // 按钮属性
      font-size: 24px; /* 调整字体大小 */
      padding: 5px;    /* 调整内边距 */
      //width: 40px;     /* 设置按钮宽度 */
      height: 40px;    /* 设置按钮高度 */
    }
  }
}
//.hg-candidate-box {
//  max-width: 5rem;
//  left: 10px;
//}
//li.hg-candidate-box-list-item {
//  width: 80px;
//  height: 55px;
//}
.hg-theme-default {
  width: 100%;
  //height: 265px;
  left: 0;
  position: fixed;
  bottom: 0;
  background-color: rgb(215, 214, 214); //间隙背景颜色
  z-index: 9999;
  .hg-button {
    &.hg-red {
      background: #db3e5d !important;
      color: white;
      &.close {
        max-width: 200px;
      }
    }
    &.change {
      max-width: 200px;
    }
  }
}
.hg-button {
  background-color: rgb(19, 19, 19) !important;
  color: antiquewhite;
}
// shift按钮大小
.hg-button-shift {
  width: 180px;
}
// 清空按钮大小
.hg-button-clear {
  width: 180px;
}
// 回车按钮大小
.hg-button-enter {
  width: 150px;
}
// 切换按钮大小
.hg-button-lock {
  width: 150px;
}
</style>

进行二次封装:
 

<template>
  <div>
    <div v-show="showKeyboard">
      <SimpleKeyboard ref="refSimpleKeyboard" class="Keyboard" @requestSub="halderRequestSub" @onChange="onChangeKeyboard"
                      @empty="empty" @closeKeyboard="handleCloseKeyboard"/>
    </div>
  </div>
</template>

<script setup>
import {ref, watch} from 'vue'
import SimpleKeyboard from '@/components/SimpleKeyboard.vue'

const showKeyboard = ref(false) // 键盘默认隐藏
const value = ref('')
const key = ref('')

// input获取焦点显示虚拟键盘
function onInputFocus() {
  showKeyboard.value = true
}

// 给输入框赋值
function onChangeKeyboard(input) {
  console.log(input)
  emit('input', {value: input, key: key.value});
}

function halderRequestSub() {
  emit('requestSub');
}

// 隐藏键盘 父组件调用
function closeInputFocus() {
  showKeyboard.value = false
}

// 隐藏键盘 子组件调用
function handleCloseKeyboard() {
  showKeyboard.value = false
}

// 清空输入框
function empty() {
  emit('input', {value: '', key: key.value});
}

// 给虚拟键盘赋当前输入框的值
function setKeyboardInput(input) {
  refSimpleKeyboard.value.onChangeFocus(input)
}

// 监听 key 的变化
watch(key, (val) => {
  if (showKeyboard.value) {
    showKeyboard.value = false
    setTimeout(() => {
      showKeyboard.value = true
    }, 100)
  }
})

// 暴露方法给父组件
defineExpose({
  onInputFocus,
  closeInputFocus,
  setKeyboardInput,
})

// 定义事件发射器
const emit = defineEmits(['input','requestSub'])

// 定义引用
const refSimpleKeyboard = ref(null)
</script>

<style lang="less" scoped>
// 键盘样式
.Keyboard {
  position: absolute;
}
</style>

在需要的组件中使用:
 

<template>
  <el-container>
    <el-main>
      <div class="main-background">
        <div class="binding-box">
          <div class="title">
            <H2>{{ titleName }}</h2>
          </div>
          <div style="padding: 5px;">
            <el-row :gutter="90">
              <el-col :span="45">
                <el-form-item label="车辆号:">
                  <el-input ref="vinInputRef"
                            v-model="sNNumber.text" type="text" :placeholder="placeholderText"
                            @focus="snBindCode('sNNumber.text', sNNumber.text)"
                            @change="validateSN"/>
                  <!--                <el-icon v-if="isVINSuccess || isVINFail " :size="40" :color="iconVinColor" ><component :is="iconVinComponent" /></el-icon>-->
                </el-form-item>
              </el-col>
              <el-col :span="45">
                <el-form-item label="终端号:">
                  <el-input v-model="tagId.text" type="text"
                            placeholder="请输入标签号" :disabled="!isVINValid"
                            @focus="tagBindCode('tagId.text', tagId.text)"
                            ref="tagIdInputRef" @change="tryToBind"/>
                  <!--                <el-icon v-if="isTagIdSuccess || isTagIdFail " :size="40" :color="iconTagColor" ><component :is="iconTagComponent" /></el-icon>-->
                </el-form-item>
              </el-col>
            </el-row>
          </div>
          <div class="table-List">
            <el-table :data="tableData" height="500" stripe="true" border style="width: 100%;  margin-top: 20px;">
              <el-table-column prop="sn" label="SN号"></el-table-column>
              <el-table-column prop="projectName" label="项目名称" show-overflow-tooltip></el-table-column>
              <el-table-column prop="carType" label="车型"></el-table-column>
              <el-table-column prop="pilotStage" label="试制阶段"></el-table-column>
              <el-table-column prop="carConfig" label="车配置" show-overflow-tooltip></el-table-column>
              <el-table-column prop="bodyColor" label="车身颜色"></el-table-column>
              <el-table-column prop="status" label="上线状态"></el-table-column>
              <el-table-column prop="assemblyPlanStart" label="装配开始时间"></el-table-column>
            </el-table>
          </div>
        </div>
      </div>
    </el-main>
    <VirtualKeyboard ref="keysInput" @requestSub="requestSub" @input="updateInputValue"/>
  </el-container>
</template>
<script>
import VirtualKeyboard from '@/components/VirtualKeyboard.vue'; // 引用
import {defineComponent, nextTick, onBeforeUnmount, onMounted, reactive, ref} from 'vue';
import Head from "../../components/head.vue";
import {ElNotification} from 'element-plus';
import {VerifyVin, VerifyTag, BindingVinAndTag, GetCarQueue,} from '@/api'

export default defineComponent({
  name: "bindingCar",
  components: {Head, VirtualKeyboard},
  props: {
    bindingType: {
      type: String,
      required: true
    }
  },
  setup(props) {
    const {bindingType} = props;
    const titleName = ref("");
    const tableData = ref([]);
    const keysInput = ref(null);
    const inputFlag = ref(0);
    const snBindCode = (event, value) => {
      inputFlag.value = 0
      showKeyboard('sNNumber.text', value);
    };

    const tagBindCode = (event, value) => {
      inputFlag.value = 1
      showKeyboard('tagId.text', value);
    };

    function requestSub() {
      console.log('发请求')
      if (sNNumber.text){
        validateSN();
      }
      if (tagId.text){
        tryToBind();
      }


    }

    const updateInputValue = (value) => {
      if (inputFlag.value === 0){
        sNNumber.text = value.value
      }else {
        tagId.text = value.value
      }
      // 根据key 回写input值
      // const setValue = new Function(`this.${value.key} = '${value.value}';`);
      // setValue.call(this);
    }
    const showKeyboard = (key, value) => {
      if (keysInput.value) {
        keysInput.value.onInputFocus(); // 调用子组件暴露的方法显示键盘
        keysInput.value.key = key; // 设置子组件的 key 属性
        keysInput.value.setKeyboardInput(value); // 设置子组件的输入值
      }
    }
    let placeholderText = ref("请输入车架号");
    const sNNumber = reactive({
      text: ''
    });
    const tagId = reactive({
      text: ''
    });
    const isVINValid = ref(false);
    const isVINSuccess = ref(false);
    const isVINFail = ref(false);
    const isTagIdSuccess = ref(false);
    const isTagIdFail = ref(false);
    const vinInputRef = ref(null);
    const tagIdInputRef = ref(null);
    const timer = ref(null)
    onMounted(() => {
      nextTick(() => {
        // 当DOM更新后,设置焦点到第一个输入框
        vinInputRef.value.$refs.input.focus();
      });
      // 首次获取数据
      GetCarQueue({type: bindingType}).then((res) => {
        tableData.value = res.data
        console.log('定时请求')
      })
      //启动定时器,每5秒请求一次数据
      timer.value = setInterval(() => {
        GetCarQueue({type: bindingType}).then((res) => {
          tableData.value = res.data
          console.log('定时请求')
        })
      }, 5000)
    });
    onBeforeUnmount(() => {
      clearInterval(timer.value)
    })
    //校验vin
    const validateSN = () => {
      isVINSuccess.value = false;
      isVINFail.value = false;
      isVINValid.value = true;
      if (sNNumber.text) {
        let param = {sn: sNNumber.text, type: bindingType};
        VerifyVin(param).then((res) => {
          if (res.data.code == 200) {
            isVINSuccess.value = true;
            tagIdInputRef.value.$refs.input.focus();
            ElNotification({
              title: '系统提示',
              message: "校验VIN通过",
              type: 'success',
              duration: 5000 // 设置通知自动关闭的时间
            });
          } else {
            isVINFail.value = true;
            isVINValid.value = false;
            ElNotification({
              title: '系统提示',
              message: res.data.msg,
              type: 'error',
              duration: 5000 // 设置通知自动关闭的时间
            });
            setTimeout(() => {
              isVINFail.value = false;
              sNNumber.text = '';
            }, 10000);
            vinInputRef.value.$refs.input.focus();
          }
        });
      } else {
        isVINFail.value = true;
        isVINValid.value = false;
        ElNotification({
          title: '系统提示',
          message: "请输入正确的车架号",
          type: 'error',
          duration: 5000 // 设置通知自动关闭的时间
        });
        setTimeout(() => {
          isVINFail.value = false;
          sNNumber.text = '';
        }, 5000);
        vinInputRef.value.$refs.input.focus();
      }
    };
//校验tagid,如果通过,调用绑定接口
    const tryToBind = () => {
      isTagIdSuccess.value = false;
      isTagIdFail.value = false;
      if (sNNumber.text && tagId.text) {
        //校验tagID
        VerifyTag({tagId: tagId.text, type: bindingType}).then((res) => {
          if (res.data.code == 200) {
            isTagIdSuccess.value = true;
            BindingVinAndTag({sn: sNNumber.text, tagId: tagId.text, type: bindingType}).then((res) => {
              if (res.data.code == 200) {
                ElNotification({
                  title: '系统提示',
                  message: '上线成功!',
                  type: 'success',
                  duration: 3000 // 设置通知自动关闭的时间
                });
                sNNumber.text = '';
                tagId.text = '';
                isVINSuccess.value = false;
                isVINFail.value = false;
                isTagIdSuccess.value = false;
                isTagIdFail.value = false;
                //光标回到原来的位置
                vinInputRef.value.$refs.input.focus();
                GetCarQueue({type: bindingType}).then((res) => {
                  tableData.value = res.data
                })
              } else {
                ElNotification({
                  title: '系统提示',
                  message: res.data.msg,
                  type: 'error',
                  duration: 10000 // 设置通知自动关闭的时间
                });
                sNNumber.text = '';
                tagId.text = '';
                isVINSuccess.value = false;
                isVINFail.value = false;
                isTagIdSuccess.value = false;
                isTagIdFail.value = false;
                //光标回到原来的位置
                vinInputRef.value.$refs.input.focus();
              }
            });
          } else {
            isTagIdFail.value = true;
            ElNotification({
              title: '系统提示',
              message: res.data.msg,
              type: 'error',
              duration: 10000 // 设置通知自动关闭的时间
            });
            setTimeout(() => {
              isTagIdFail.value = false;
              tagId.text = '';
            }, 10000);
            setTimeout(() => {
              sNNumber.text = '';
              isVINSuccess.value = false;
              //光标回到原来的位置
              vinInputRef.value.$refs.input.focus();
            }, 30000);
          }
        });
      }
    };

    return {
      placeholderText,
      isVINSuccess,
      isVINFail,
      isTagIdSuccess,
      isTagIdFail,
      titleName,
      sNNumber,
      tagId,
      isVINValid,
      vinInputRef,
      tagIdInputRef,
      validateSN,
      tryToBind,
      tableData,
      updateInputValue,
      showKeyboard,
      snBindCode,
      tagBindCode,
      keysInput,
      requestSub
    };
  },
  created() {
    this.initHtml();
  },
  computed: {
    iconVinColor() {
      return this.isVINSuccess ? 'green' : 'red';
    },
    iconVinComponent() {
      return this.isVINSuccess ? 'CircleCheck' : 'Warning';
    },
    iconTagColor() {
      return this.isTagIdSuccess ? 'green' : 'red';
    },
    iconTagComponent() {
      return this.isTagIdSuccess ? 'CircleCheck' : 'Warning';
    },
  },
  methods: {
    initHtml() {
      if (this.bindingType === "1") {
        this.titleName = '车身标签绑定';
        this.placeholderText = '请输入车身SN号';
      } else if (this.bindingType === "2") {
        this.titleName = '底盘标签绑定';
        this.placeholderText = '请输入底盘SN号';
      }
    }
  }
});
</script>
<style scoped>
.main-background {
  display: flex;
  justify-content: center; /* 水平居中 */
  align-items: center; /* 垂直居中 */
  height: 100%; /* 如果需要占满整个容器的高度 */
}

.binding-box {
  padding-bottom: 100px;
}

.input-group {
  margin-bottom: 50px; /* 调整这个值以改变间距大小 */
  display: flex;
  justify-content: center;
  align-items: center;
}

.table-List {
  height: 600px;
  width: 1400px;
  justify-content: center;
  align-items: center;
}

.title {
  width: 100%;
  text-align: center;
  font-size: 30px;
  font-weight: bold;
}

/deep/ .el-input__inner {
  --el-input-inner-height: 50px !important;;
}

/deep/ .el-input {
  font-size: 30px;
  height: 60px;
  width: 500px;
  line-height: 100px;
  caret-color: red;

}

/deep/ .el-form-item__label {
  font-size: 30px;
  height: 60px;
  line-height: 60px;
}


</style>

最后效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值