feat(scanlistener): 添加PDA扫码模块支持广播和键盘输入

添加qs-scanlistener模块,支持通过广播和键盘输入方式获取扫码结果
更新manifest.json添加abiFilters配置
重构easycode.vue使用新的扫码模块替代原生扫码
This commit is contained in:
砂糖
2025-12-25 15:48:21 +08:00
parent fb5dea4dbe
commit 3650d87a34
9 changed files with 639 additions and 147 deletions

View File

@@ -0,0 +1,101 @@
<template>
<view>
<uni-popup ref="pdaScanPopup" type='center'>
<view>扫码提示</view>
请对准二维码或条形码扫描查看结果
<button>取消</button>
</uni-popup>
</view>
</template>
<script>
export default {
name: "klp-scaner",
data() {
return {
pdaScaning: false,
main: null,
indent: null
};
},
onHide: function() {
if (uni.getSystemInfoSync().platform === 'android') {
this.stopScan();
}
},
destroyed: function() {
// 页面退出时一定要卸载监听,否则下次进来时会重复造成扫一次出2个以上的结果
if (uni.getSystemInfoSync().platform === 'android') {
this.stopScan();
}
},
created() {
if (uni.getSystemInfoSync().platform === 'android') {
this.init();
}
},
methods: {
init() {
this.main = plus.android.runtimeMainActivity(); //获取activity
let context = plus.android.importClass('android.content.Context'); //上下文
let receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
onReceive: this.doReceive
});
this.receiver = receiver;
let IntentFilter = plus.android.importClass('android.content.IntentFilter');
let Intent = plus.android.importClass('android.content.Intent');
let filter = new IntentFilter();
filter.addAction("nlscan.action.SCANNER_RESULT"); //监听扫描,根据设备的广播动作进行更换
// filter.addAction("android.intent.action.SCANRESULT"); //监听扫描,根据设备的广播动作进行更换
this.doS(receiver, filter);
},
doS(receiver, filter) {
this.main.registerReceiver(receiver, filter);
},
doReceive(context, intent) {
//通过intent实例引入intent类方便以后的.’操作
console.log(indent);
plus.android.importClass(intent);
let barcodeData = intent.getStringExtra("SCAN_BARCODE1"); //获取扫描结果
// let barcodeData = intent.getStringExtra("value"); //获取扫描结果
let barcodeType = intent.getIntExtra("SCAN_BARCODE_TYPE", -1); //获取扫码类型
if (this.pdaScaning) {
uni.$emit('scan', {
code: barcodeData
})
}
},
stopScan() {
this.main.unregisterReceiver(this.receiver);
},
startScan(mode = 'camera') {
if (mode == 'camera') {
uni.scanCode({
success(res) {
resolve(res.result);
this.$emit('scan', res.result)
},
fail() {
reject()
}
})
} else if (mode == 'pda') {
this.pdaScaning = true;
this.$refs.pdaScanPopup.open()
// 等待对应的广播消息,收到广播消息后返回扫码结果,
}
}
},
closePdaScanPopup() {
this.pdaScaning = false;
this.$refs.pdaScanPopup.close()
}
}
</script>
<style>
</style>

View File

@@ -35,7 +35,8 @@
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
],
"abiFilters" : [ "armeabi-v7a" ]
},
"ios" : {
"dSYMs" : false

View File

@@ -105,6 +105,9 @@
</view>
</view>
</uni-popup>
<!-- <klp-scaner></klp-scaner> -->
<qs-scanlistener ref='pda'></qs-scanlistener>
</view>
</template>
@@ -124,7 +127,6 @@
import {
addPendingAction
} from '@/api/wms/pendingAction.js'
// 导入获取库区详情的接口(需确保接口存在,若接口名不同请调整)
import {
getActualWarehouse
} from '@/api/wms/actualWarehouse.js'
@@ -137,7 +139,8 @@
coilDetail: {},
form: {},
targetWarehouse: null, // 存储选中的目标库区信息
bomDialogShow: false // BOM参数弹窗控制原有功能补充
bomDialogShow: false, // BOM参数弹窗控制原有功能补充
mode: 'pda' // pda或camera
}
},
computed: {
@@ -165,6 +168,28 @@
})
},
scan(mode = 'pda') {
return new Promise((resolve, reject) => {
if (mode == 'camera') {
uni.scanCode({
success(res) {
console.log('扫码成功')
resolve(res.result)
},
fail() {
reject()
}
})
} else if (mode == 'pda') {
this.$refs.pda.open().then(code => {
resolve(code);
}).catch(() => {
reject()
})
}
})
},
async handlePack() {
const content = await this.getQRCodeContent()
let coilId = content.current_coil_id && content.current_coil_id !== 'null' ? content
@@ -181,35 +206,32 @@
})
},
handleSee() {
uni.scanCode({
success(scanRes) {
// 查询真实库区在此处的钢卷
listMaterialCoil({
actualWarehouseId: scanRes.result,
dataType: 1
}).then(res => {
if (res.total == 0) {
uni.showToast({
title: '该库区未发现钢卷',
icon: 'none'
});
return;
}
if (res.rows.length == 1) {
const coilId = res.rows[0].coilId;
uni.navigateTo({
url: '/pages/easycode/editby?coilId=' + coilId
})
} else {
uni.showToast({
title: '数据异常',
icon: 'none'
})
}
})
}
async handleSee() {
const actualWarehouseId = await this.scan()
// 查询真实库区在此处的钢卷
const res = await listMaterialCoil({
actualWarehouseId,
dataType: 1
})
if (res.total == 0) {
uni.showToast({
title: '该库区未发现钢卷',
icon: 'none'
});
return;
}
if (res.rows.length == 1) {
const coilId = res.rows[0].coilId;
uni.navigateTo({
url: '/pages/easycode/editby?coilId=' + coilId
})
} else {
uni.showToast({
title: '数据异常',
icon: 'none'
})
}
},
// 扫码并创建待操作(原有方法不变)
@@ -279,65 +301,55 @@
// 通用扫码获取二维码内容(原有方法不变)
async getQRCodeContent() {
return new Promise((resolve, reject) => {
uni.scanCode({
success: async (res) => {
console.log('=== 开始扫码流程 ===');
console.log('扫码结果:', res.result);
uni.showLoading({
title: '处理中...'
});
this.scan().then(async res => {
uni.showLoading({
title: '处理中...'
});
try {
const qrcodeId = res;
// 1. 通过二维码ID获取二维码详情
console.log('1. 获取二维码详情ID:', qrcodeId);
const qrcodeRes = await getGenerateRecord(qrcodeId);
console.log('二维码响应:', qrcodeRes);
try {
const qrcodeId = res.result;
// 1. 通过二维码ID获取二维码详情
console.log('1. 获取二维码详情ID:', qrcodeId);
const qrcodeRes = await getGenerateRecord(qrcodeId);
console.log('二维码响应:', qrcodeRes);
if (qrcodeRes.code !== 200) {
throw new Error('未找到二维码记录');
}
// 2. 解析二维码的content获取coil_id
const qrcodeRecord = qrcodeRes.data;
console.log('2. 二维码记录:', qrcodeRecord);
// 检查二维码状态0=失效1=有效)
if (qrcodeRecord.status === 0) {
uni.hideLoading();
uni.showModal({
title: '提示',
content: '该二维码已失效,无法创建待操作任务',
showCancel: false
});
return;
}
uni.hideLoading();
const content = JSON.parse(qrcodeRecord.content);
console.log('解析后的内容:', content);
resolve(content)
} catch (err) {
console.error('=== 扫码处理失败 ===');
console.error('错误信息:', err);
console.error('错误堆栈:', err.stack);
uni.hideLoading();
uni.showToast({
title: err.message || '处理失败',
icon: 'none',
duration: 3000
});
reject()
if (qrcodeRes.code !== 200) {
throw new Error('未找到二维码记录');
}
},
fail: (err) => {
// 2. 解析二维码的content获取coil_id
const qrcodeRecord = qrcodeRes.data;
console.log('2. 二维码记录:', qrcodeRecord);
// 检查二维码状态0=失效1=有效)
if (qrcodeRecord.status === 0) {
uni.hideLoading();
uni.showModal({
title: '提示',
content: '该二维码已失效,无法创建待操作任务',
showCancel: false
});
return;
}
uni.hideLoading();
const content = JSON.parse(qrcodeRecord.content);
console.log('解析后的内容:', content);
resolve(content)
} catch (err) {
console.error('=== 扫码处理失败 ===');
console.error('错误信息:', err);
console.error('错误堆栈:', err.stack);
uni.hideLoading();
uni.showToast({
title: '扫码失败,请重试',
icon: 'none'
title: err.message || '处理失败',
icon: 'none',
duration: 3000
});
reject();
reject()
}
});
}).catch(() => {
reject()
})
})
},
@@ -416,7 +428,7 @@
} catch (error) {
console.error('发货失败:', error);
uni.showToast({
title: error.message || '发货失败',
title: error?.message || '发货失败',
icon: 'none'
});
}
@@ -475,7 +487,7 @@
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || '打开移库失败',
title: error?.message || '打开移库失败',
icon: 'none',
duration: 2000
});
@@ -489,17 +501,10 @@
title: '扫码中...'
});
// 扫描库区二维码
const scanRes = await new Promise((resolve, reject) => {
uni.scanCode({
success: resolve,
fail: (err) => {
reject(new Error('扫码取消或失败'));
}
});
});
const scanRes = await this.scan()
uni.hideLoading();
const warehouseQrCode = scanRes.result;
const warehouseQrCode = scanRes;
if (!warehouseQrCode) {
throw new Error('未识别到库区信息');
}
@@ -539,7 +544,7 @@
} catch (error) {
uni.hideLoading();
uni.showToast({
title: error.message || '扫描库区失败',
title: error?.message || '扫描库区失败',
icon: 'none',
duration: 2500
});
@@ -630,7 +635,7 @@
uni.hideLoading();
console.error('移库失败详情:', error);
uni.showToast({
title: error.message || '移库失败,请重试',
title: error?.message || '移库失败,请重试',
icon: 'none',
duration: 3000
});

View File

@@ -0,0 +1,2 @@
## 1.0.02022-12-17
create

View File

@@ -0,0 +1,118 @@
<template>
<uni-popup ref="pdaScanPopup" type='center'>
<view>扫码提示</view>
请对准二维码或条形码扫描查看结果
<button @click="close">取消</button>
</uni-popup>
</template>
<script>
import scaninput from './scanInput.js'
scaninput.initScan()
scaninput.startScan()
export default {
name: "scan-listener",
created() {
scaninput.install(this.scanHandle)
// uni.$on('scan_handle', this.scanHandle)
},
beforeDestroy() {
scaninput.uninstall(this.scanHandle)
},
data() {
return {
scaning: false,
timer: undefined,
loading: false,
code: undefined,
}
},
methods: {
onEvent(event) {
// console.log(event.key)
if (event.key != 'Enter' && event.key != 'PrintScreen') { // 拼接输入的值Enter与PrintScreen是物理按钮要排除
this.inputVal = this.inputVal + event.key
}
if (event.key == 'Enter') {
let reg = new RegExp('Shift', 'g') //g代表全部
let reg2 = new RegExp('Unidentified', 'g') //排除Unidentified字符
let inputVal = this.inputVal
inputVal = inputVal.replace(reg, "")
inputVal = inputVal.replace(reg2, "")
inputVal = inputVal.replace(/\s/g, "")
inputVal = inputVal.replace(/\r\n/g, "")
inputVal = inputVal.replace(/\n/g, "")
if (this.inputVal) {
// console.log('键盘监听模式')
this.$emit('scan', this.inputVal)
}
this.inputVal = ''
}
},
scanHandle(code) {
// console.log('广播模式')
this.loading = true;
this.code = code;
this.$emit('scan', code)
},
open() {
return new Promise((resolve, reject) => {
if (!this.scaning) {
this.scaning = true;
this.$refs.pdaScanPopup.open();
this.timer = setInterval(() => {
if (this.loading) {
resolve(this.code)
this.close();
}
}, 100)
} else {
uni.showToast({
title: '请等待当前操作完成'
})
reject()
}
})
},
close() {
this.scaning = false;
this.loading = false;
clearInterval(this.timer);
this.$refs.pdaScanPopup.close();
}
},
data() {
return {
inputVal: '',
};
}
}
</script>
<script module="keyboard" lang="renderjs">
export default {
mounted() {
const onKey = (event) => {
const keys1 = ['type', 'timeStamp']
const keys2 = ['altKey', 'code', 'ctrlKey', 'isComposing', 'key', 'location', 'metaKey', 'repeat', 'shiftKey']
const keys3 = ['char', 'charCode', 'keyCode', 'keyIdentifier', 'keyLocation', 'which']
const data = {}
keys1.concat(keys2, keys3).forEach(key => data[key] = event[key])
this.$ownerInstance.callMethod('onEvent', data)
}
const names = ['keyup'] //'keydown',
names.forEach(name => {
document.addEventListener(name, onKey, false)
})
this.$on('hook:beforeDestroy', () => {
names.forEach(name => {
document.removeEventListener(name, onKey, false)
})
})
}
}
</script>
<style>
</style>

View File

@@ -0,0 +1,56 @@
let main, receiver, filter, _codeQueryTag = false, temp = [], init = false, start = false;
export default {
initScan() {
if(init) return
let _this = this;
main = plus.android.runtimeMainActivity(); //获取activity
var IntentFilter = plus.android.importClass('android.content.IntentFilter');
filter = new IntentFilter();
// android.intent.ACTION_DECODE_DATA
filter.addAction("com.hc.scan"); // 换你的广播动作你的pda设备里面看
receiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
onReceive: function(context, intent) {
//barcode_string
plus.android.importClass(intent);
let code = intent.getStringExtra("Barcode"); // 换你的广播标签你的pda设备里面看
_this.queryCode(code);
}
});
init = true
},
startScan() {
if(!start) {
start = true
main.registerReceiver(receiver, filter);
}
},
stopScan() {
if(start) {
start = false
main.unregisterReceiver(receiver);
}
},
install(fn) {
if(typeof fn == 'function' && !~temp.indexOf(fn)) temp.push(fn)
},
uninstall(fn) {
if(typeof fn == 'function') {
const index = temp.find(i=>i == fn)
if(~index) temp.splice(index, 1)
}
},
queryCode: function(code) {
//防重复
// if (_codeQueryTag) return false;
// _codeQueryTag = true;
// setTimeout(function() {
// _codeQueryTag = false;
// }, 150);
if(temp && temp.length) {
temp[temp.length - 1](code)
}
uni.vibrateShort()
uni.$emit("qs_scanlistener_handle", code);
}
}

View File

@@ -0,0 +1,82 @@
{
"id": "qs-scanlistener",
"displayName": "qs-scanlistener PDA扫码",
"version": "1.0.0",
"description": "PDA扫码 兼容广播和键盘",
"keywords": [
"pda",
"扫码"
],
"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"
},
"client": {
"Vue": {
"vue2": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "n",
"Android Browser": "n",
"微信浏览器(Android)": "n",
"QQ浏览器(Android)": "n"
},
"H5-pc": {
"Chrome": "n",
"IE": "n",
"Edge": "n",
"Firefox": "n",
"Safari": "n"
},
"小程序": {
"微信": "n",
"阿里": "n",
"百度": "n",
"字节跳动": "n",
"QQ": "n",
"钉钉": "n",
"快手": "n",
"飞书": "n",
"京东": "n"
},
"快应用": {
"华为": "n",
"联盟": "n"
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
## qs-scanlistener PDA扫码
## 支持广播和键盘
---
### 广播动作可在main.js设置uni._qs_scanlistener_action = 你的广播动作名称, 默认android.intent.ACTION_DECODE_DATA
### 广播标签可在main.js设置uni._qs_scanlistener_label = 你的广播标签名称, 默认barcode_string
---
### 扫码结果也可以uni.$on('qs_scanlistener_handle', code=>{}) 中获取
---
Template
```html
<qs-scanlistener @scan="scan"></qs-scanlistener>
```
js
```javascript
methods: {
scan(code) {
console.log(code)
}
}
```