初始化,需要引入依赖:
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>
最后效果: