From a2c7ba01ae6c2f3f16d03a0fadee0cdad8c6f0d1 Mon Sep 17 00:00:00 2001 From: ck-chenkang Date: Wed, 17 Jun 2026 16:39:54 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=8A=BD=E5=8F=96=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E6=89=AB=E7=A0=81=E8=BE=93=E5=85=A5=E7=9B=91=E5=90=AC?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96=E8=AE=BE=E5=A4=87=E5=8F=B0=E8=B4=A6?= =?UTF-8?q?=E6=89=AB=E7=A0=81=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/hooks/useScannerInput.js | 203 ++++++++++++++++++ .../pages/equipmentLedger/index.vue | 46 ++-- 2 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 src/hooks/useScannerInput.js diff --git a/src/hooks/useScannerInput.js b/src/hooks/useScannerInput.js new file mode 100644 index 0000000..4de89c9 --- /dev/null +++ b/src/hooks/useScannerInput.js @@ -0,0 +1,203 @@ +import { onHide, onShow, onUnload } from '@dcloudio/uni-app' + +const androidKeyCodeMap = { + 7: '0', + 8: '1', + 9: '2', + 10: '3', + 11: '4', + 12: '5', + 13: '6', + 14: '7', + 15: '8', + 16: '9', + 29: 'A', + 30: 'B', + 31: 'C', + 32: 'D', + 33: 'E', + 34: 'F', + 35: 'G', + 36: 'H', + 37: 'I', + 38: 'J', + 39: 'K', + 40: 'L', + 41: 'M', + 42: 'N', + 43: 'O', + 44: 'P', + 45: 'Q', + 46: 'R', + 47: 'S', + 48: 'T', + 49: 'U', + 50: 'V', + 51: 'W', + 52: 'X', + 53: 'Y', + 54: 'Z', + 55: ',', + 56: '.', + 69: '-', + 70: '=', + 71: '[', + 72: ']', + 73: '\\', + 74: ';', + 75: "'", + 76: '/', + 81: '+' +} + +export function useScannerInput(options = {}) { + const minLength = Number(options.minLength ?? 3) + const commitDelay = Number(options.commitDelay ?? 120) + const uppercase = options.uppercase !== false + const ignoreInputTarget = options.ignoreInputTarget !== false + const hideKeyboardOnScan = options.hideKeyboardOnScan !== false + const hideKeyboardOnLeave = options.hideKeyboardOnLeave !== false + const onScan = typeof options.onScan === 'function' ? options.onScan : () => {} + + let scannerBuffer = '' + let scannerTimer = null + let scannerListening = false + let plusKeyListening = false + + function getScannerEventTarget() { + if (typeof document !== 'undefined') return document + if (typeof window !== 'undefined') return window + return null + } + + function registerScannerListener() { + if (scannerListening) return + const target = getScannerEventTarget() + if (target?.addEventListener) { + target.addEventListener('keydown', handleScannerKeydown) + scannerListening = true + } + registerPlusKeyListener() + } + + function unregisterScannerListener() { + const target = getScannerEventTarget() + if (scannerListening && target?.removeEventListener) { + target.removeEventListener('keydown', handleScannerKeydown) + } + scannerListening = false + unregisterPlusKeyListener() + clearScannerBuffer() + if (hideKeyboardOnLeave) hideSoftKeyboard() + } + + function registerPlusKeyListener() { + if (plusKeyListening) return + if (typeof plus === 'undefined' || !plus?.key?.addEventListener) return + plus.key.addEventListener('keydown', handlePlusScannerKeydown) + plusKeyListening = true + } + + function unregisterPlusKeyListener() { + if (!plusKeyListening) return + if (typeof plus !== 'undefined' && plus?.key?.removeEventListener) { + plus.key.removeEventListener('keydown', handlePlusScannerKeydown) + } + plusKeyListening = false + } + + function handlePlusScannerKeydown(event) { + handleScannerKeydown({ ...event, isPlusKeyEvent: true }) + } + + function handleScannerKeydown(event) { + if (ignoreInputTarget) { + const targetTag = String(event?.target?.tagName || '').toLowerCase() + if (targetTag === 'input' || targetTag === 'textarea') return + } + + const key = normalizeScannerKey(event) + if (!key) return + if (key === 'Enter') { + commitScannerBuffer() + return + } + + scannerBuffer += key + if (scannerTimer) clearTimeout(scannerTimer) + scannerTimer = setTimeout(() => { + commitScannerBuffer() + }, commitDelay) + } + + function normalizeScannerKey(event) { + if (event?.ctrlKey || event?.altKey || event?.metaKey) return '' + if (isAndroidPlusKeyEvent(event)) return normalizeAndroidKeyCode(event) + + const key = event?.key + if (key === 'Enter' || key === 'Tab') return 'Enter' + if (typeof key === 'string' && key.length === 1) return uppercase ? key.toUpperCase() : key + + const code = Number(event?.keyCode || event?.which || 0) + if (code === 13 || code === 9) return 'Enter' + if (code >= 48 && code <= 90) { + const value = String.fromCharCode(code) + return uppercase ? value.toUpperCase() : value + } + if (code >= 96 && code <= 105) return String(code - 96) + if (code === 189 || code === 109) return '-' + if (code === 190 || code === 110) return '.' + return '' + } + + function isAndroidPlusKeyEvent(event) { + return Boolean(event?.isPlusKeyEvent) + } + + function normalizeAndroidKeyCode(event) { + const code = Number(event?.keyCode || event?.which || 0) + if (code === 66 || code === 61 || code === 160) return 'Enter' + const value = androidKeyCodeMap[code] || '' + return uppercase ? value.toUpperCase() : value.toLowerCase() + } + + function clearScannerBuffer() { + scannerBuffer = '' + if (scannerTimer) { + clearTimeout(scannerTimer) + scannerTimer = null + } + } + + async function commitScannerBuffer() { + const value = scannerBuffer.trim() + clearScannerBuffer() + if (value.length < minLength) return + if (hideKeyboardOnScan) hideSoftKeyboard() + await onScan(value) + } + + function hideSoftKeyboard() { + try { + uni.hideKeyboard() + } catch (e) {} + } + + onShow(() => { + registerScannerListener() + }) + + onHide(() => { + unregisterScannerListener() + }) + + onUnload(() => { + unregisterScannerListener() + }) + + return { + clearScannerBuffer, + registerScannerListener, + unregisterScannerListener + } +} diff --git a/src/pages_function/pages/equipmentLedger/index.vue b/src/pages_function/pages/equipmentLedger/index.vue index a3662a1..d8ea078 100644 --- a/src/pages_function/pages/equipmentLedger/index.vue +++ b/src/pages_function/pages/equipmentLedger/index.vue @@ -7,14 +7,14 @@ {{ selectedLineLabel }} - + @@ -82,7 +82,7 @@ + + diff --git a/src/uni_modules/sv-focus-no-keyboard/package.json b/src/uni_modules/sv-focus-no-keyboard/package.json new file mode 100644 index 0000000..a0b2234 --- /dev/null +++ b/src/uni_modules/sv-focus-no-keyboard/package.json @@ -0,0 +1,88 @@ +{ + "id": "sv-focus-no-keyboard", + "displayName": "输入框聚焦且阻止键盘弹出", + "version": "1.0.2", + "description": "使输入框在聚焦的同时,从根本上阻止软键盘弹出,并非是单纯的隐藏键盘", + "keywords": [ + "聚焦", + "键盘", + "光标", + "focus", + "keyboard" +], + "repository": "", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "type": "component-vue", + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "插件不采集任何数据", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y", + "alipay": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "n", + "app-uvue": "n", + "app-harmony": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "n", + "阿里": "n", + "百度": "n", + "字节跳动": "n", + "QQ": "n", + "钉钉": "n", + "快手": "n", + "飞书": "n", + "京东": "n" + }, + "快应用": { + "华为": "n", + "联盟": "n" + } + } + } + } +} \ No newline at end of file diff --git a/src/uni_modules/sv-focus-no-keyboard/readme.md b/src/uni_modules/sv-focus-no-keyboard/readme.md new file mode 100644 index 0000000..8fe2490 --- /dev/null +++ b/src/uni_modules/sv-focus-no-keyboard/readme.md @@ -0,0 +1,134 @@ +# sv-focus-no-keyboard + +### 兼容性 + +✅已兼容,❌未兼容 + +| VUE2 |VUE3 | Android(APP/H5) | iOS(APP/H5) | 小程序 | +|:---: |:---:| :---: | :---: | :---: | +| ✅ ️ | ✅️ | ✅ | ❌ | ❌ | + +- 本插件使用 [renderjs](https://uniapp.dcloud.net.cn/tutorial/renderjs.html#renderjs) +- nvue,小程序,无法使用 renderjs +- ios 由于固件限制聚焦必弹出键盘,本插件虽然能用,但本质上是通过设置输入框的 readonly 属性进行控制,会导致光标丢失,并非能像安卓那样完美兼容 + +### props + +| 属性名 | 类型 | 默认值 | 说明 | +| :--- | :--- | :--- | :--- | +| banSelector | String | | 禁止软键盘弹出的输入框 选择器,可见示例 | + +`被 banSelector 禁止的输入框,将永久不再弹出软键盘,除非通过下列 focus 方法重新主动聚焦` + +### event + +| 事件名 | 参数 | 说明 | +| :--- | :--- | :--- | +| focus | selector: String| 指定 选择器 的输入框聚焦,且禁止软键盘弹出,可见示例 | + +`focus 不影响用户手动点击输入框的聚焦行为,即用户仍可手动点击输入框进行聚焦并弹出软键盘` + +### 使用示例 + +``` + + + +``` + +### 注意 + +1. 必须指定给到实际可以聚焦的dom盒子,如:实际的input,textarea标签,或者editor组件下的.ql-editor盒子等 + +2. 在vue2下,部分非原生input标签,如uni-easyinput插件由于内部特殊处理,可能无法兼容阻止键盘弹出的问题;但是该问题在vue3下可能不会出现,所以建议使用vue3,或者尽量使用原生input等标签 + +### 结语 + +本插件免费开源,如若借鉴源码还请注明出处,未经授权禁止转载售卖等侵犯版权行为,谢谢! + +感谢您使用本插件,如果在使用过程中遇到任何问题,欢迎在评论区留言或加群讨论,制作不易,还望五星好评 🌟🌟🌟🌟🌟 + +欢迎进群🍌交流,🐧Q群: +- ①群:852637893 +- ②群:816646292 +- ③群:704990626 + +💬WX群可通过🐧Q群进入 \ No newline at end of file