feat: 抽取通用扫码输入监听并优化设备台账扫码体验

master
ck-chenkang 6 days ago
parent 226b7009b0
commit a2c7ba01ae

@ -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
}
}

@ -7,14 +7,14 @@
<text :class="['line-filter-text', selectedLineId === '' ? 'placeholder' : '']">{{ selectedLineLabel }}</text>
<uni-icons type="bottom" size="14" color="#a8adb7"></uni-icons>
</view>
<view class="keyword-wrap">
<view :class="['keyword-wrap', isKeywordInputActive ? 'active' : '']">
<input
v-model="searchKeyword"
class="keyword-input"
:placeholder="t('equipmentLedger.searchPlaceholder')"
:focus="keywordFocus"
confirm-type="search"
@blur="keywordFocus = false"
@blur="handleKeywordBlur"
@tap="handleKeywordTap"
@confirm="handleSearch"
/>
</view>
@ -82,7 +82,7 @@
</template>
<script setup>
import { ref, computed, nextTick } from 'vue'
import { ref, computed } from 'vue'
import { onLoad, onPageScroll, onReachBottom, onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
@ -90,11 +90,13 @@ import { getDeviceLedgerPage, updateDeviceLedger } from '@/api/mes/deviceLedger'
import { getDeviceLineTree } from '@/api/mes/deviceLine'
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
import useDictStore from '@/store/modules/dict'
import { useScannerInput } from '@/hooks/useScannerInput'
const { t } = useI18n()
const dictStore = useDictStore()
const resetFilterText = computed(() => t('functionCommon.reset'))
const searchKeyword = ref('')
const isKeywordInputActive = ref(true)
const selectedStatus = ref('')
const selectedLineId = ref('')
const lineTree = ref([])
@ -110,7 +112,19 @@ const pageSize = ref(10)
const total = ref(0)
const showGoTop = ref(false)
const statusUpdatingMap = ref({})
const keywordFocus = ref(false)
const { clearScannerBuffer } = useScannerInput({
minLength: 3,
commitDelay: 120,
uppercase: true,
hideKeyboardOnScan: true,
hideKeyboardOnLeave: true,
onScan: async (value) => {
searchKeyword.value = value
isKeywordInputActive.value = true
await fetchList(true)
}
})
const statusOptions = computed(() => {
const dicts = dictStore.getDict(DICT_TYPE.MES_TZ_STATUS) || []
@ -142,14 +156,13 @@ const selectedLineLabel = computed(() => {
})
onLoad(async () => {
activateKeywordFocus()
await initAllDict()
await fetchLineTree()
await fetchList(true)
})
onShow(() => {
activateKeywordFocus()
isKeywordInputActive.value = true
})
onReachBottom(() => {
@ -226,11 +239,13 @@ function findLinePath(nodes, id, parents = []) {
return []
}
function activateKeywordFocus() {
keywordFocus.value = false
nextTick(() => {
keywordFocus.value = true
})
function handleKeywordTap() {
isKeywordInputActive.value = true
clearScannerBuffer()
}
function handleKeywordBlur() {
isKeywordInputActive.value = true
}
async function fetchList(reset) {
@ -299,7 +314,6 @@ async function resetFilters() {
lineCascaderValue.value = []
lineCascaderShow.value = false
lineCascaderKey.value += 1
activateKeywordFocus()
await fetchList(true)
}
@ -308,6 +322,7 @@ async function resetLineFilter() {
}
async function handleSearch() {
uni.hideKeyboard()
await fetchList(true)
}
@ -466,6 +481,11 @@ function formatDateValue(value) {
align-items: center;
}
.keyword-wrap.active {
border-color: #2f7dff;
box-shadow: 0 0 0 2rpx rgba(47, 125, 255, 0.12);
}
.keyword-input {
width: 100%;
height: 64rpx;

Loading…
Cancel
Save