app端新增原材料送检功能
This commit is contained in:
16
JeecgUniapp-master/.hbuilderx/launch.json
Normal file
16
JeecgUniapp-master/.hbuilderx/launch.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"version" : "1.0",
|
||||
"configurations" : [
|
||||
{
|
||||
"playground" : "standard",
|
||||
"type" : "uni-app:app-android"
|
||||
},
|
||||
{
|
||||
"app-plus" :
|
||||
{
|
||||
"launchtype" : "local"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
||||
136
JeecgUniapp-master/docs/DT50扫描识别-广播参数与接入说明.md
Normal file
136
JeecgUniapp-master/docs/DT50扫描识别-广播参数与接入说明.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# DT50 扫描识别 —— 广播参数与接入说明
|
||||
|
||||
> 面向 APP「协作」页「扫描识别」功能。
|
||||
> 设备:优博讯 UROVO **DT50**(Android 11)。
|
||||
> 参数来源:**设备实测 adb logcat 抓真实扫码** + **优博讯官方 ScanManager 文档** + **DCloud UniApp 官方示例** 三方交叉验证。
|
||||
> 记录日期:2026-07-20。
|
||||
|
||||
---
|
||||
|
||||
## 一、核心参数(最重要)
|
||||
|
||||
| 项目 | 值 | 说明 |
|
||||
|---|---|---|
|
||||
| 输出方式 | **Intent / 广播** | 设备当前**已是此模式**,无需在设置页切换 |
|
||||
| 广播 Action | `android.intent.ACTION_DECODE_DATA` | 实测 + 官方一致 |
|
||||
| 条码文本(String) | `barcode_string` | 官方常量 `BARCODE_STRING_TAG`,**首选** |
|
||||
| 原始字节(byte[]) | `barcode` | 官方常量 `DECODE_DATA_TAG`,兜底用 |
|
||||
| 长度(int) | `length` | 官方常量 `BARCODE_LENGTH_TAG` |
|
||||
| 码制(int/String) | `barcodeType` | 官方常量 `BARCODE_TYPE_TAG`,实测读出 `Code128` |
|
||||
| 扫描键 keycode | `523` | KEYCODE_SCAN(机身侧键) |
|
||||
|
||||
## 二、设备侧验证结论
|
||||
|
||||
- **硬件**:成像头正常,成功解出 Code128 条码 ✅
|
||||
- **触发**:侧键 keycode = 523,按下发 `ACTION_KEYCODE_SCAN_PRESSED`
|
||||
- **广播**:成功解码时实测发出 `android.intent.ACTION_DECODE_DATA (has extras)` 并被接收 ✅
|
||||
- **模式**:当前即广播输出(非键盘输出),**直接 registerReceiver 即可**
|
||||
- **无需**:原生 `.aar` 插件 / 自定义基座 / USB 驱动 / 完整 AndroidStudio 套壳;标准基座即可
|
||||
|
||||
相关系统组件(备查):
|
||||
- 扫描设置 App:`com.ubx.barcodescanner.tool`
|
||||
- 扫描服务:`com.ubx.uscanner`
|
||||
- DataWedge 式配置:`com.ubx.datawedge`
|
||||
|
||||
## 三、UniApp 监听代码(标准基座,无需插件)
|
||||
|
||||
```js
|
||||
let scanReceiver = null;
|
||||
|
||||
/** 开始监听 DT50 扫描广播 */
|
||||
function startScanListener(onBarcode) {
|
||||
const main = plus.android.runtimeMainActivity();
|
||||
const IntentFilter = plus.android.importClass('android.content.IntentFilter');
|
||||
const filter = new IntentFilter();
|
||||
filter.addAction('android.intent.ACTION_DECODE_DATA'); // DT50 广播 Action
|
||||
|
||||
scanReceiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
|
||||
onReceive: function (context, intent) {
|
||||
plus.android.importClass(intent);
|
||||
// 首选直接取字符串
|
||||
let code = intent.getStringExtra('barcode_string');
|
||||
// 兜底:部分场景用字节数组
|
||||
if (!code) {
|
||||
const bytes = intent.getByteArrayExtra('barcode');
|
||||
if (bytes) code = plus.android.newObject('java.lang.String', bytes);
|
||||
}
|
||||
const type = intent.getStringExtra('barcodeType'); // 码制,可选
|
||||
if (code) onBarcode(code, type);
|
||||
}
|
||||
});
|
||||
main.registerReceiver(scanReceiver, filter);
|
||||
}
|
||||
|
||||
/** 停止监听(务必在页面卸载时调用,避免重复注册 / 内存泄漏) */
|
||||
function stopScanListener() {
|
||||
if (scanReceiver) {
|
||||
plus.android.runtimeMainActivity().unregisterReceiver(scanReceiver);
|
||||
scanReceiver = null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
页面接入:
|
||||
|
||||
```js
|
||||
onLoad() {
|
||||
startScanListener((code, type) => {
|
||||
this.barcode = code;
|
||||
// TODO: 调 MES 后端接口 / 查料
|
||||
});
|
||||
},
|
||||
onUnload() {
|
||||
stopScanListener();
|
||||
}
|
||||
```
|
||||
|
||||
> 注意:仅 App(plus 环境)下可用,H5/小程序无 `plus`,接入时需做平台判断(`#ifdef APP-PLUS`)。
|
||||
|
||||
## 四、故障排查
|
||||
|
||||
| 现象 | 原因 / 处理 |
|
||||
|---|---|
|
||||
| 收不到广播 | 确认扫描设置里输出为「Intent/广播」而非「键盘/输入法」;确认 Action 拼写 |
|
||||
| 扫描后文本框被输入内容 | 键盘输出模式也开了,去扫描设置关掉键盘输出 |
|
||||
| `barcode_string` 为空 | 用兜底的 `barcode`(byte[]) 转 String |
|
||||
| 一直解码失败(红光有但不出码) | 距离 15~20cm,红光完整压住条码,拿稳 1~2 秒;避免手机屏幕反光码 |
|
||||
|
||||
## 五、参考
|
||||
|
||||
- 优博讯官方 ScanManager 文档:https://en.urovo.com/developer/android/device/ScanManager.html
|
||||
- DCloud UniApp 监听优博讯扫码广播(含示例):https://ask.dcloud.net.cn/article/36468
|
||||
|
||||
## 六、本仓库接入位置(2026-07-20)
|
||||
|
||||
| 项 | 位置 |
|
||||
|---|---|
|
||||
| 菜单入口 | `src/common/work.ts` → 其他服务「合同」后「扫描识别」 |
|
||||
| 页面 | `src/pages/scanIdentify/scanIdentify.vue` |
|
||||
| 广播工具 | `src/utils/dt50Scan.ts` |
|
||||
| 行为 | 仅广播展示条码/码制/时间,不调后端、不弹输入法 |
|
||||
| 生命周期 | `onShow` 注册 / `onHide`+`onUnload` 注销;同码防抖 |
|
||||
|
||||
## 七、键盘输出 vs 广播输出
|
||||
|
||||
| | 键盘输出(Keyboard / 楔入) | 广播输出(Intent / Broadcast) |
|
||||
|---|---|---|
|
||||
| 原理 | 把条码当成「敲键盘」打进当前焦点输入框 | 系统发广播,App 用 `registerReceiver` 收 |
|
||||
| App 是否要抢焦点 | 要,通常还会弹出软键盘 | **不需要**输入框焦点 |
|
||||
| 适合场景 | 记事本、任意输入框快速验证 | 业务页稳定取码、拿码制等扩展字段 |
|
||||
| 本页策略 | 已关闭楔入,避免弹输入法 | **本页只走广播** |
|
||||
|
||||
设备设置建议:扫描设置 → 关闭「键盘输出」,开启「Intent/广播输出」。
|
||||
本页进入时会主动 `hideKeyboard`,不自动聚焦任何输入框。
|
||||
|
||||
## 附:参数是如何抓到的(可复现)
|
||||
|
||||
```bash
|
||||
# adb 路径(本机 Android SDK)
|
||||
ADB="C:/Users/hp/AppData/Local/Android/Sdk/platform-tools/adb.exe"
|
||||
|
||||
$ADB devices -l # 确认 DT50 已连(USB 调试)
|
||||
$ADB logcat -c # 清空日志缓冲
|
||||
$ADB shell "timeout 40 logcat -v threadtime '*:V'" > scan.log # 抓 40s
|
||||
# 抓取期间用 DT50 侧键扫几次条码,然后:
|
||||
grep -E "ACTION_DECODE_DATA|barcodeType|DECODER" scan.log
|
||||
```
|
||||
6
JeecgUniapp-master/env/.env.development
vendored
6
JeecgUniapp-master/env/.env.development
vendored
@@ -2,7 +2,7 @@
|
||||
NODE_ENV = 'development'
|
||||
# 是否去除console 和 debugger
|
||||
VITE_DELETE_CONSOLE = false
|
||||
# 是否开启sourcemap
|
||||
VITE_SHOW_SOURCEMAP = true
|
||||
# 是否开启sourcemap(App 真机编译易 OOM,开发期先关闭)
|
||||
VITE_SHOW_SOURCEMAP = false
|
||||
|
||||
VITE_SERVER_BASEURL = 'http://localhost:8080/jeecg-boot'
|
||||
VITE_SERVER_BASEURL = 'http://192.168.1.3:8888/jeecg-boot'
|
||||
|
||||
@@ -36,7 +36,7 @@ export default defineUniPages({
|
||||
iconPath: 'static/tabbar/tabbar-home-2.png',
|
||||
selectedIconPath: 'static/tabbar/tabbar-home.png',
|
||||
pagePath: 'pages/index/index',
|
||||
text: '协作',
|
||||
text: '生产',
|
||||
},
|
||||
// {
|
||||
// iconPath: 'static/tabbar/tabbar-workHome-2.png',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 常用服务
|
||||
* 基础服务(原常用服务)
|
||||
* useful server
|
||||
*/
|
||||
|
||||
@@ -108,36 +108,28 @@ export const us = {
|
||||
}
|
||||
|
||||
/**
|
||||
* other server 其他服务
|
||||
* 生产服务(原其他服务)
|
||||
* 首页「更多」按钮仍按 type=other 追加,勿删空数组
|
||||
*/
|
||||
export const os = {
|
||||
data: [
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP首页】生产服务菜单调整-----------
|
||||
{
|
||||
title: '新闻中心',
|
||||
icon: icon_prefix + 'xinwen.png',
|
||||
description: '新闻中心',
|
||||
routeIndex: 'news',
|
||||
title: '原材料送检',
|
||||
icon: icon_prefix + 'renwu.png',
|
||||
description: '扫码送检与录入检验结果',
|
||||
useCount: 10000,
|
||||
},
|
||||
{
|
||||
title: '会议',
|
||||
icon: icon_prefix + 'huiyi.png',
|
||||
description: '会议',
|
||||
useCount: 10000,
|
||||
routeIndex: 'meeting',
|
||||
routeIndex: 'rawMaterialInspect',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
title: '任务中心',
|
||||
icon: icon_prefix + 'renwu.png',
|
||||
description: '任务中心',
|
||||
useCount: 10000,
|
||||
},
|
||||
{
|
||||
title: '合同',
|
||||
icon: icon_prefix + 'hetong.png',
|
||||
description: '合同',
|
||||
title: '扫描识别',
|
||||
icon: icon_prefix + 'wendang.png',
|
||||
description: '手持机扫描头识别条码',
|
||||
useCount: 10000,
|
||||
routeIndex: 'scanIdentify',
|
||||
enabled: true,
|
||||
},
|
||||
//update-end---author:xsl ---date:20260720 for:【APP首页】生产服务菜单调整-----------
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<view class="help-wrap">
|
||||
<view class="help-icon" @click.stop="open">
|
||||
<text class="q">?</text>
|
||||
</view>
|
||||
<view v-if="visible" class="mask" @click="close" @touchmove.stop.prevent="">
|
||||
<view class="bubble" :style="bubbleStyle" @click.stop="">
|
||||
<text class="bubble-text">{{ text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
defineOptions({ name: 'HelpTipBubble' })
|
||||
|
||||
const props = defineProps({
|
||||
text: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: '180upx',
|
||||
},
|
||||
})
|
||||
|
||||
const visible = ref(false)
|
||||
|
||||
const bubbleStyle = computed(() => ({
|
||||
top: props.top,
|
||||
}))
|
||||
|
||||
function open() {
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible.value = false
|
||||
}
|
||||
|
||||
defineExpose({ open, close })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.help-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
.help-icon {
|
||||
width: 36upx;
|
||||
height: 36upx;
|
||||
border-radius: 50%;
|
||||
border: 2upx solid #888;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 8upx;
|
||||
}
|
||||
.q {
|
||||
font-size: 22upx;
|
||||
color: #666;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
}
|
||||
.mask {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 1000;
|
||||
background: transparent;
|
||||
}
|
||||
.bubble {
|
||||
position: absolute;
|
||||
left: 32upx;
|
||||
right: 32upx;
|
||||
padding: 24upx 28upx;
|
||||
border-radius: 16upx;
|
||||
background: rgb(180, 180, 180);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.bubble-text {
|
||||
font-size: 26upx;
|
||||
color: #fff;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
@@ -5,6 +5,7 @@ Breadcrumb: typeof import('./Breadcrumb/Breadcrumb.vue')['default']
|
||||
CategorySelect: typeof import('./CategorySelect/CategorySelect.vue')['default']
|
||||
DateTime: typeof import('./DateTime/DateTime.vue')['default']
|
||||
Grid: typeof import('./Grid/Grid.vue')['default']
|
||||
HelpTipBubble: typeof import('./HelpTipBubble/HelpTipBubble.vue')['default']
|
||||
ImgPreview: typeof import('./ImgPreview/ImgPreview.vue')['default']
|
||||
LFile: typeof import('./LFile/LFile.vue')['default']
|
||||
PageLayout: typeof import('./PageLayout/PageLayout.vue')['default']
|
||||
|
||||
@@ -130,8 +130,8 @@
|
||||
"sdkConfigs": {
|
||||
"maps": {
|
||||
"amap": {
|
||||
"key": "???",
|
||||
"securityJsCode": "???",
|
||||
"key": "20854e7d231ee339bfa3b277c840070c",
|
||||
"securityJsCode": "7a542edee4a82e56ed88fef8ef42b5a5",
|
||||
"serviceHost": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"iconPath": "static/tabbar/tabbar-home-2.png",
|
||||
"selectedIconPath": "static/tabbar/tabbar-home.png",
|
||||
"pagePath": "pages/index/index",
|
||||
"text": "协作"
|
||||
"text": "生产"
|
||||
},
|
||||
{
|
||||
"iconPath": "static/tabbar/tabbar-user-2.png",
|
||||
@@ -197,6 +197,45 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/rawMaterialInspect/inspectResult",
|
||||
"type": "page",
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationBarTitleText": "录入检验结果",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true,
|
||||
"app-plus": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/rawMaterialInspect/rawMaterialInspect",
|
||||
"type": "page",
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationBarTitleText": "原材料送检",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true,
|
||||
"app-plus": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/scanIdentify/scanIdentify",
|
||||
"type": "page",
|
||||
"layout": "default",
|
||||
"style": {
|
||||
"navigationBarTitleText": "扫描识别",
|
||||
"navigationStyle": "custom",
|
||||
"disableScroll": true,
|
||||
"app-plus": {
|
||||
"bounce": "none"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user/people",
|
||||
"type": "page",
|
||||
|
||||
@@ -28,22 +28,22 @@
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<scroll-view class="scrollView" :scroll-y="true" scroll-with-animation>
|
||||
<!--常用服务-->
|
||||
<!--生产服务(原其他服务,置顶)-->
|
||||
<view class="serveBox">
|
||||
<view class="title">
|
||||
<view class="dot"></view>
|
||||
<wd-text text="常用服务"></wd-text>
|
||||
</view>
|
||||
<Grid :column="4" v-model="usList" @itemClik="goPage"></Grid>
|
||||
</view>
|
||||
<!--其他服务-->
|
||||
<view class="serveBox">
|
||||
<view class="title">
|
||||
<view class="dot"></view>
|
||||
<wd-text text="其他服务"></wd-text>
|
||||
<wd-text text="生产服务"></wd-text>
|
||||
</view>
|
||||
<Grid :column="4" v-model="osList" @itemClik="goPage"></Grid>
|
||||
</view>
|
||||
<!--基础服务(原常用服务)-->
|
||||
<view class="serveBox">
|
||||
<view class="title">
|
||||
<view class="dot"></view>
|
||||
<wd-text text="基础服务"></wd-text>
|
||||
</view>
|
||||
<Grid :column="4" v-model="usList" @itemClik="goPage"></Grid>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</PageLayout>
|
||||
</template>
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
<template>
|
||||
<PageLayout :navbarShow="false">
|
||||
<view class="page-container">
|
||||
<view class="text-center">
|
||||
<image :src="compLogo" mode="aspectFit" class="logo"></image>
|
||||
<view class="title text-shadow">{{ compTitle || 'JEECG BOOT' }}</view>
|
||||
<view class="brand text-center">
|
||||
<view class="brand-title">启航密炼</view>
|
||||
<view class="brand-subtitle">智能制造MES手持端</view>
|
||||
</view>
|
||||
|
||||
<view class="form-wrap">
|
||||
<view class="enter-area">
|
||||
<view v-if="loginWay == 1" class="account-login-area">
|
||||
<view class="box account">
|
||||
@@ -69,7 +72,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-area text-center">
|
||||
<wd-button custom-class="login align-top" :loading="loading" @click="hanldeLogin">
|
||||
<wd-button custom-class="login align-top" :loading="loading" @click="hanldeLogin">
|
||||
{{ loading ? '登录...' : '登录' }}
|
||||
</wd-button>
|
||||
<wd-button v-if="loginWay == 2" plain hairline @click="toggleLoginWay(1)">
|
||||
@@ -80,6 +83,11 @@
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="footer">
|
||||
<image src="/static/logo/xslmes.png" mode="aspectFit" class="footer-logo"></image>
|
||||
<text class="footer-text">XSL-MES APP</text>
|
||||
</view>
|
||||
<wd-notify />
|
||||
</view>
|
||||
</PageLayout>
|
||||
@@ -361,20 +369,64 @@ if (isLocalConfig === false) {
|
||||
.page-container {
|
||||
padding: 0 20upx;
|
||||
padding-top: 100upx;
|
||||
padding-bottom: calc(24upx + env(safe-area-inset-bottom));
|
||||
position: relative;
|
||||
font-size: 15px;
|
||||
color: var(--UI-FG-0);
|
||||
.logo {
|
||||
width: 200upx;
|
||||
height: 150px;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.brand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.title {
|
||||
font-size: 58upx;
|
||||
.brand-title {
|
||||
font-size: 52upx;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
letter-spacing: 6upx;
|
||||
line-height: 1.3;
|
||||
}
|
||||
.brand-subtitle {
|
||||
margin-top: 12upx;
|
||||
font-size: 28upx;
|
||||
color: #666;
|
||||
letter-spacing: 2upx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.form-wrap {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-height: 0;
|
||||
}
|
||||
.footer {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12upx;
|
||||
padding-top: 24upx;
|
||||
padding-bottom: 12upx;
|
||||
}
|
||||
.footer-logo {
|
||||
width: 48upx;
|
||||
height: 48upx;
|
||||
border-radius: 8upx;
|
||||
}
|
||||
.footer-text {
|
||||
font-size: 22upx;
|
||||
color: #888;
|
||||
letter-spacing: 1upx;
|
||||
}
|
||||
.enter-area {
|
||||
padding-top: 75px;
|
||||
padding-top: 24px;
|
||||
width: 87%;
|
||||
margin: 0 auto 60upx;
|
||||
margin: 0 auto 40upx;
|
||||
.box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
<route lang="json5" type="page">
|
||||
{
|
||||
layout: 'default',
|
||||
style: {
|
||||
navigationBarTitleText: '录入检验结果',
|
||||
navigationStyle: 'custom',
|
||||
disableScroll: true,
|
||||
'app-plus': {
|
||||
bounce: 'none',
|
||||
},
|
||||
},
|
||||
}
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<PageLayout
|
||||
:backRouteName="backRouteName"
|
||||
navTitle="录入检验结果"
|
||||
:routeMethod="backRouteName === 'index' ? 'pushTab' : 'replace'"
|
||||
>
|
||||
<template #navRight>
|
||||
<HelpTipBubble :text="helpText" top="176upx" />
|
||||
</template>
|
||||
|
||||
<view class="page">
|
||||
<view class="info-card">
|
||||
<view class="row"><text class="label">条码</text><text class="val">{{ record.barcode || '-' }}</text></view>
|
||||
<view class="row"><text class="label">批次</text><text class="val">{{ record.batchNo || '-' }}</text></view>
|
||||
<view class="row"><text class="label">物料</text><text class="val">{{ record.materialName || '-' }}</text></view>
|
||||
<view class="row"><text class="label">送检时间</text><text class="val">{{ record.inspectTime || '-' }}</text></view>
|
||||
</view>
|
||||
|
||||
<scroll-view class="list" scroll-y>
|
||||
<view v-if="loading" class="empty">加载中…</view>
|
||||
<view v-else-if="!lines.length" class="empty">暂无检验明细</view>
|
||||
<view v-for="(line, idx) in lines" :key="line.id" class="line-card">
|
||||
<view class="line-title">{{ idx + 1 }}. {{ line.inspectItemName || '检验项' }}</view>
|
||||
<view class="range">容差:{{ formatRange(line) }}</view>
|
||||
<view class="status">状态:{{ passText(line.passFlag) }}</view>
|
||||
<view v-if="String(line.passFlag || '0') === '0'" class="input-row">
|
||||
<text class="label">检验值</text>
|
||||
<input
|
||||
class="input"
|
||||
type="digit"
|
||||
:value="line.inspectValue == null ? '' : String(line.inspectValue)"
|
||||
placeholder="请输入"
|
||||
@input="(e) => onValueInput(line, e)"
|
||||
/>
|
||||
</view>
|
||||
<view v-else class="input-row">
|
||||
<text class="label">检验值</text>
|
||||
<text class="val">{{ line.inspectValue ?? '-' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="footer">
|
||||
<wd-button block type="primary" :loading="saving" @click="handleSave">保存</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import HelpTipBubble from '@/components/HelpTipBubble/HelpTipBubble.vue'
|
||||
import {
|
||||
prepareResultEntry,
|
||||
queryRecordById,
|
||||
saveInspectResult,
|
||||
type InspectRecord,
|
||||
type InspectRecordLine,
|
||||
} from '@/service/xslmes/rawMaterialInspect'
|
||||
|
||||
defineOptions({ name: 'inspectResult' })
|
||||
|
||||
const helpText = '请填写待检项目的检验值后点击保存。已录入项目不可重复修改;可分次录入,全部完成后再判定合格/不合格。'
|
||||
|
||||
const toast = useToast()
|
||||
const backRouteName = ref('rawMaterialInspect')
|
||||
const recordId = ref('')
|
||||
const record = ref<InspectRecord>({})
|
||||
const lines = ref<InspectRecordLine[]>([])
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
function passText(flag?: string) {
|
||||
const f = String(flag ?? '0')
|
||||
if (f === '1') return '合格'
|
||||
if (f === '2') return '不合格'
|
||||
return '待检'
|
||||
}
|
||||
|
||||
function formatRange(line: InspectRecordLine) {
|
||||
const min = line.allowMin
|
||||
const max = line.allowMax
|
||||
const left = min == null ? '-∞' : `${line.includeMinFlag === 1 ? '[' : '('}${min}`
|
||||
const right = max == null ? '+∞' : `${max}${line.includeMaxFlag === 1 ? ']' : ')'}`
|
||||
return `${left}, ${right}`
|
||||
}
|
||||
|
||||
function onValueInput(line: InspectRecordLine, e: any) {
|
||||
const v = e?.detail?.value
|
||||
if (v === '' || v == null) {
|
||||
line.inspectValue = null
|
||||
return
|
||||
}
|
||||
line.inspectValue = v
|
||||
}
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP原材料送检】录入检验结果页-----------
|
||||
async function loadData() {
|
||||
if (!recordId.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const mainRes: any = await queryRecordById(recordId.value)
|
||||
if (mainRes?.success) {
|
||||
record.value = mainRes.result || {}
|
||||
}
|
||||
const lineRes: any = await prepareResultEntry(recordId.value)
|
||||
if (!lineRes?.success) {
|
||||
toast.warning(lineRes?.message || '加载明细失败')
|
||||
return
|
||||
}
|
||||
lines.value = Array.isArray(lineRes.result) ? lineRes.result : []
|
||||
} catch (e: any) {
|
||||
toast.warning(e?.data?.message || e?.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
const pending = lines.value.filter((x) => String(x.passFlag || '0') === '0')
|
||||
const filled = pending.filter(
|
||||
(x) => x.inspectValue !== null && x.inspectValue !== undefined && x.inspectValue !== '',
|
||||
)
|
||||
if (!filled.length) {
|
||||
toast.warning('请至少录入一条检验值')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const lineList = filled.map((x) => ({
|
||||
id: x.id,
|
||||
inspectValue: x.inspectValue as string | number,
|
||||
}))
|
||||
const res: any = await saveInspectResult(recordId.value, lineList)
|
||||
if (!res?.success) {
|
||||
toast.warning(res?.message || '保存失败')
|
||||
return
|
||||
}
|
||||
toast.success('保存成功')
|
||||
uni.navigateBack({
|
||||
fail: () => {
|
||||
uni.redirectTo({ url: '/pages/rawMaterialInspect/rawMaterialInspect' })
|
||||
},
|
||||
})
|
||||
} catch (e: any) {
|
||||
toast.warning(e?.data?.message || e?.message || '保存失败')
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onLoad((opts: any) => {
|
||||
recordId.value = opts?.recordId || ''
|
||||
if (opts?.backRouteName) {
|
||||
backRouteName.value = opts.backRouteName
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
//update-end---author:xsl ---date:20260720 for:【APP原材料送检】录入检验结果页-----------
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
.info-card,
|
||||
.line-card {
|
||||
background: #fff;
|
||||
border-radius: 16upx;
|
||||
padding: 24upx;
|
||||
margin: 20upx 24upx 0;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
padding: 6upx 0;
|
||||
font-size: 24upx;
|
||||
}
|
||||
.label {
|
||||
width: 140upx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.val {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
.list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding-bottom: 20upx;
|
||||
}
|
||||
.empty {
|
||||
padding: 60upx;
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
}
|
||||
.line-title {
|
||||
font-size: 28upx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
}
|
||||
.range,
|
||||
.status {
|
||||
margin-top: 8upx;
|
||||
font-size: 22upx;
|
||||
color: #888;
|
||||
}
|
||||
.input-row {
|
||||
margin-top: 16upx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.input {
|
||||
flex: 1;
|
||||
height: 64upx;
|
||||
padding: 0 20upx;
|
||||
background: #f5f6f8;
|
||||
border-radius: 8upx;
|
||||
font-size: 28upx;
|
||||
}
|
||||
.footer {
|
||||
padding: 20upx 24upx calc(20upx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,289 @@
|
||||
<route lang="json5" type="page">
|
||||
{
|
||||
layout: 'default',
|
||||
style: {
|
||||
navigationBarTitleText: '原材料送检',
|
||||
navigationStyle: 'custom',
|
||||
disableScroll: true,
|
||||
'app-plus': {
|
||||
bounce: 'none',
|
||||
},
|
||||
},
|
||||
}
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<PageLayout backRouteName="index" navTitle="原材料送检" routeMethod="pushTab">
|
||||
<template #navRight>
|
||||
<HelpTipBubble :text="helpText" top="176upx" />
|
||||
</template>
|
||||
|
||||
<view class="page">
|
||||
<view class="search-bar">
|
||||
<input
|
||||
class="search-input"
|
||||
type="text"
|
||||
v-model="barcodeInput"
|
||||
placeholder="扫码或输入条码"
|
||||
confirm-type="search"
|
||||
@confirm="handleQuery"
|
||||
/>
|
||||
<wd-button size="small" type="primary" :loading="querying" @click="handleQuery">查询</wd-button>
|
||||
</view>
|
||||
|
||||
<scroll-view class="list" scroll-y>
|
||||
<view v-if="!list.length" class="empty">暂无待送检数据,请扫码或查询</view>
|
||||
<view v-for="(item, index) in list" :key="item.id" class="card">
|
||||
<view class="card-head">
|
||||
<text class="barcode">{{ item.barcode }}</text>
|
||||
<text class="remove" @click="removeItem(index)">移除</text>
|
||||
</view>
|
||||
<view class="row"><text class="label">批次</text><text class="val">{{ item.batchNo || '-' }}</text></view>
|
||||
<view class="row"><text class="label">入场日期</text><text class="val">{{ formatDate(item.entryDate) }}</text></view>
|
||||
<view class="row"><text class="label">物料名称</text><text class="val">{{ item.materialName || '-' }}</text></view>
|
||||
<view class="row"><text class="label">供应商</text><text class="val">{{ item.supplierName || '-' }}</text></view>
|
||||
<view class="row"><text class="label">厂家物料</text><text class="val">{{ item.manufacturerMaterialName || '-' }}</text></view>
|
||||
<view class="row"><text class="label">保质期</text><text class="val">{{ item.shelfLife || '-' }}</text></view>
|
||||
<view class="row"><text class="label">库区</text><text class="val">{{ item.warehouseArea || '-' }}</text></view>
|
||||
<view class="row"><text class="label">卸货人</text><text class="val">{{ item.unloadOperator || '-' }}</text></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="footer">
|
||||
<wd-button block type="primary" :loading="sending" :disabled="!list.length" @click="handleSendInspect">
|
||||
送检({{ list.length }})
|
||||
</wd-button>
|
||||
</view>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onHide, onShow, onUnload } from '@dcloudio/uni-app'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import HelpTipBubble from '@/components/HelpTipBubble/HelpTipBubble.vue'
|
||||
import { startDt50ScanListener, stopDt50ScanListener } from '@/utils/dt50Scan'
|
||||
import { createByBarcode, queryByBarcode, type RawMaterialCard } from '@/service/xslmes/rawMaterialInspect'
|
||||
|
||||
defineOptions({ name: 'rawMaterialInspect' })
|
||||
|
||||
const helpText =
|
||||
'请按下侧键扫描原材料条码,也可手动输入后点查询。未送检的加入下方列表;已送检待录入的将跳转检验结果页。列表中已有相同条码不会重复添加。'
|
||||
|
||||
const toast = useToast()
|
||||
const list = ref<RawMaterialCard[]>([])
|
||||
const barcodeInput = ref('')
|
||||
const querying = ref(false)
|
||||
const sending = ref(false)
|
||||
let listening = false
|
||||
|
||||
function formatDate(v?: string) {
|
||||
if (!v) return '-'
|
||||
return String(v).substring(0, 10)
|
||||
}
|
||||
|
||||
function hideSoftKeyboard() {
|
||||
try {
|
||||
uni.hideKeyboard()
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP原材料送检】条码输入查询与帮助气泡-----------
|
||||
async function queryCard(barcode: string, fromScan = false) {
|
||||
const code = String(barcode || '').trim()
|
||||
if (!code) {
|
||||
toast.warning('请输入或扫描条码')
|
||||
return
|
||||
}
|
||||
barcodeInput.value = code
|
||||
hideSoftKeyboard()
|
||||
querying.value = true
|
||||
try {
|
||||
uni.showLoading({ title: '查询中', mask: true })
|
||||
const res: any = await queryByBarcode(code)
|
||||
if (!res?.success) {
|
||||
toast.warning(res?.message || '查询失败')
|
||||
return
|
||||
}
|
||||
const data = res.result
|
||||
const action = data?.action
|
||||
const card = data?.card
|
||||
if (!card?.id) {
|
||||
toast.warning('未返回卡片数据')
|
||||
return
|
||||
}
|
||||
if (action === 'enterResult') {
|
||||
const recordId = data.pendingRecordId
|
||||
if (!recordId) {
|
||||
toast.warning('缺少送检记录ID')
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/rawMaterialInspect/inspectResult?recordId=${encodeURIComponent(recordId)}&backRouteName=rawMaterialInspect`,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (action === 'finished') {
|
||||
toast.info('该条码已完成检验,无需再送检')
|
||||
return
|
||||
}
|
||||
// 列表已有则不重复添加
|
||||
if (list.value.some((x) => x.id === card.id || x.barcode === card.barcode)) {
|
||||
if (!fromScan) toast.info('列表中已存在该条码')
|
||||
return
|
||||
}
|
||||
list.value.unshift(card)
|
||||
toast.success('已加入待送检列表')
|
||||
} catch (e: any) {
|
||||
toast.warning(e?.data?.message || e?.message || '查询失败')
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
querying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuery() {
|
||||
queryCard(barcodeInput.value, false)
|
||||
}
|
||||
|
||||
function onBarcode(code: string) {
|
||||
queryCard(code, true)
|
||||
}
|
||||
|
||||
function removeItem(index: number) {
|
||||
list.value.splice(index, 1)
|
||||
}
|
||||
|
||||
async function handleSendInspect() {
|
||||
if (!list.value.length || sending.value) return
|
||||
sending.value = true
|
||||
const okCodes: string[] = []
|
||||
const failMsgs: string[] = []
|
||||
try {
|
||||
for (const item of [...list.value]) {
|
||||
try {
|
||||
const res: any = await createByBarcode(item.barcode as string)
|
||||
if (res?.success) {
|
||||
okCodes.push(item.barcode as string)
|
||||
} else {
|
||||
failMsgs.push(`${item.barcode}: ${res?.message || '失败'}`)
|
||||
}
|
||||
} catch (e: any) {
|
||||
failMsgs.push(`${item.barcode}: ${e?.data?.message || e?.message || '失败'}`)
|
||||
}
|
||||
}
|
||||
list.value = list.value.filter((x) => !okCodes.includes(x.barcode as string))
|
||||
if (okCodes.length) {
|
||||
toast.success(`成功送检 ${okCodes.length} 条`)
|
||||
}
|
||||
if (failMsgs.length) {
|
||||
toast.warning(failMsgs[0])
|
||||
}
|
||||
} finally {
|
||||
sending.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function ensureListen() {
|
||||
if (typeof plus === 'undefined') return
|
||||
startDt50ScanListener((code) => onBarcode(code))
|
||||
listening = true
|
||||
}
|
||||
|
||||
onShow(() => ensureListen())
|
||||
onHide(() => {
|
||||
if (!listening) return
|
||||
stopDt50ScanListener()
|
||||
listening = false
|
||||
})
|
||||
onUnload(() => {
|
||||
stopDt50ScanListener()
|
||||
listening = false
|
||||
})
|
||||
//update-end---author:xsl ---date:20260720 for:【APP原材料送检】条码输入查询与帮助气泡-----------
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #f5f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.search-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16upx;
|
||||
margin: 20upx 24upx;
|
||||
padding: 16upx 20upx;
|
||||
background: #fff;
|
||||
border-radius: 16upx;
|
||||
}
|
||||
.search-input {
|
||||
flex: 1;
|
||||
height: 64upx;
|
||||
padding: 0 16upx;
|
||||
background: #f5f6f8;
|
||||
border-radius: 8upx;
|
||||
font-size: 28upx;
|
||||
}
|
||||
.list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 0 24upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.empty {
|
||||
padding: 80upx 24upx;
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
font-size: 26upx;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 16upx;
|
||||
padding: 24upx;
|
||||
margin-bottom: 20upx;
|
||||
}
|
||||
.card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12upx;
|
||||
}
|
||||
.barcode {
|
||||
font-size: 30upx;
|
||||
font-weight: 600;
|
||||
color: #111;
|
||||
word-break: break-all;
|
||||
}
|
||||
.remove {
|
||||
color: #e54d42;
|
||||
font-size: 24upx;
|
||||
padding-left: 16upx;
|
||||
}
|
||||
.row {
|
||||
display: flex;
|
||||
padding: 6upx 0;
|
||||
font-size: 24upx;
|
||||
}
|
||||
.label {
|
||||
width: 160upx;
|
||||
color: #999;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.val {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
.footer {
|
||||
padding: 20upx 24upx calc(20upx + env(safe-area-inset-bottom));
|
||||
background: #fff;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
</style>
|
||||
318
JeecgUniapp-master/src/pages/scanIdentify/scanIdentify.vue
Normal file
318
JeecgUniapp-master/src/pages/scanIdentify/scanIdentify.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<route lang="json5" type="page">
|
||||
{
|
||||
layout: 'default',
|
||||
style: {
|
||||
navigationBarTitleText: '扫描识别',
|
||||
navigationStyle: 'custom',
|
||||
disableScroll: true,
|
||||
'app-plus': {
|
||||
bounce: 'none',
|
||||
},
|
||||
},
|
||||
}
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<PageLayout backRouteName="index" navTitle="扫描识别" routeMethod="pushTab">
|
||||
<view class="page">
|
||||
<view class="tip-card">
|
||||
<view class="tip-title">{{ isAppEnv ? '请按下侧键扫描条码' : '请在 DT50 手持机 App 中使用' }}</view>
|
||||
<view class="tip-desc">
|
||||
{{
|
||||
isAppEnv
|
||||
? '本页使用广播接收扫码结果,不会自动弹出输入法。请在设备扫描设置中开启 Intent/广播输出。'
|
||||
: 'H5 无法调用扫描头'
|
||||
}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="result-card">
|
||||
<view class="label">最近一次识别</view>
|
||||
<view class="code" :class="{ empty: !latestCode }">{{ latestCode || '等待扫描…' }}</view>
|
||||
<view v-if="latestType || latestTime || latestSource" class="meta">
|
||||
<text v-if="latestSource">来源:{{ latestSource }}</text>
|
||||
<text v-if="latestType">码制:{{ latestType }}</text>
|
||||
<text v-if="latestTime">时间:{{ latestTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="history-head">
|
||||
<text>扫描记录</text>
|
||||
<text v-if="history.length" class="clear" @click="clearHistory">清空</text>
|
||||
</view>
|
||||
<scroll-view class="history-list" scroll-y :show-scrollbar="true">
|
||||
<view v-if="!history.length" class="empty">暂无记录</view>
|
||||
<view v-for="(item, index) in history" :key="item.id" class="history-item">
|
||||
<view class="idx">{{ history.length - index }}</view>
|
||||
<view class="body">
|
||||
<view class="h-code">{{ item.code }}</view>
|
||||
<view class="h-meta">
|
||||
<text v-if="item.source">{{ item.source }}</text>
|
||||
<text v-if="item.type">{{ item.type }}</text>
|
||||
<text>{{ item.time }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="debug-head">
|
||||
<text>调试日志</text>
|
||||
<text class="clear" @click="debugLogs = []">清空</text>
|
||||
</view>
|
||||
<scroll-view class="debug-list" scroll-y>
|
||||
<view v-for="(line, i) in debugLogs" :key="i" class="debug-line">{{ line }}</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</PageLayout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { onHide, onLoad, onShow, onUnload } from '@dcloudio/uni-app'
|
||||
import { useToast } from 'wot-design-uni'
|
||||
import {
|
||||
normalizeScanCode,
|
||||
setDt50ScanLogger,
|
||||
startDt50ScanListener,
|
||||
stopDt50ScanListener,
|
||||
} from '@/utils/dt50Scan'
|
||||
|
||||
defineOptions({
|
||||
name: 'scanIdentify',
|
||||
})
|
||||
|
||||
interface ScanRecord {
|
||||
id: number
|
||||
code: string
|
||||
type?: string
|
||||
source?: string
|
||||
time: string
|
||||
}
|
||||
|
||||
const toast = useToast()
|
||||
const isAppEnv = ref(typeof plus !== 'undefined')
|
||||
const latestCode = ref('')
|
||||
const latestType = ref('')
|
||||
const latestTime = ref('')
|
||||
const latestSource = ref('')
|
||||
const history = ref<ScanRecord[]>([])
|
||||
const debugLogs = ref<string[]>([])
|
||||
let seq = 0
|
||||
let started = false
|
||||
|
||||
function formatNow() {
|
||||
const d = new Date()
|
||||
const p = (n: number) => String(n).padStart(2, '0')
|
||||
return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
|
||||
}
|
||||
|
||||
function pushDebug(msg: string) {
|
||||
debugLogs.value.unshift(`${formatNow()} ${msg}`)
|
||||
if (debugLogs.value.length > 80) {
|
||||
debugLogs.value = debugLogs.value.slice(0, 80)
|
||||
}
|
||||
}
|
||||
|
||||
/** 强制收起软键盘,避免页面抢焦点弹出输入法 */
|
||||
function hideSoftKeyboard() {
|
||||
try {
|
||||
uni.hideKeyboard()
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
// #ifdef APP-PLUS
|
||||
try {
|
||||
const Context = plus.android.importClass('android.content.Context') as any
|
||||
const InputMethodManager = plus.android.importClass('android.view.inputmethod.InputMethodManager') as any
|
||||
const main = plus.android.runtimeMainActivity()
|
||||
const imm = main.getSystemService(Context.INPUT_METHOD_SERVICE)
|
||||
plus.android.importClass(imm)
|
||||
const decor = main.getWindow().getDecorView()
|
||||
imm.hideSoftInputFromWindow(decor.getWindowToken(), 0)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【需求】DT50扫描识别页展示条码-----------
|
||||
function onBarcode(code: string, type?: string, source = 'broadcast') {
|
||||
const normalized = normalizeScanCode(code)
|
||||
if (!normalized) return
|
||||
|
||||
hideSoftKeyboard()
|
||||
|
||||
latestCode.value = normalized
|
||||
latestType.value = type || ''
|
||||
latestSource.value = source
|
||||
latestTime.value = formatNow()
|
||||
history.value.unshift({
|
||||
id: ++seq,
|
||||
code: normalized,
|
||||
type,
|
||||
source,
|
||||
time: latestTime.value,
|
||||
})
|
||||
if (history.value.length > 50) {
|
||||
history.value = history.value.slice(0, 50)
|
||||
}
|
||||
toast.success('识别成功')
|
||||
}
|
||||
|
||||
function clearHistory() {
|
||||
history.value = []
|
||||
}
|
||||
|
||||
function ensureListen(from: string) {
|
||||
setDt50ScanLogger((msg) => pushDebug(msg))
|
||||
isAppEnv.value = typeof plus !== 'undefined'
|
||||
hideSoftKeyboard()
|
||||
if (!isAppEnv.value) {
|
||||
pushDebug(`${from}:非 App 环境`)
|
||||
return
|
||||
}
|
||||
// 仅广播监听,不抢输入框焦点,避免弹出输入法
|
||||
startDt50ScanListener((code, type, source) => onBarcode(code, type, source || 'broadcast'))
|
||||
started = true
|
||||
pushDebug(`${from}:已启动广播监听(无输入法)`)
|
||||
}
|
||||
|
||||
onLoad(() => ensureListen('onLoad'))
|
||||
onShow(() => {
|
||||
ensureListen('onShow')
|
||||
hideSoftKeyboard()
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
if (!started) return
|
||||
stopDt50ScanListener()
|
||||
setDt50ScanLogger(null)
|
||||
started = false
|
||||
pushDebug('onHide:停止监听')
|
||||
})
|
||||
|
||||
onUnload(() => {
|
||||
stopDt50ScanListener()
|
||||
setDt50ScanLogger(null)
|
||||
started = false
|
||||
})
|
||||
//update-end---author:xsl ---date:20260720 for:【需求】DT50扫描识别页展示条码-----------
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 24upx;
|
||||
background: #f5f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tip-card,
|
||||
.result-card {
|
||||
background: #fff;
|
||||
border-radius: 16upx;
|
||||
padding: 28upx;
|
||||
margin-bottom: 24upx;
|
||||
}
|
||||
.tip-title {
|
||||
font-size: 30upx;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
.tip-desc {
|
||||
margin-top: 12upx;
|
||||
font-size: 24upx;
|
||||
color: #999;
|
||||
}
|
||||
.label {
|
||||
font-size: 24upx;
|
||||
color: #999;
|
||||
}
|
||||
.code {
|
||||
margin-top: 16upx;
|
||||
font-size: 40upx;
|
||||
color: #111;
|
||||
word-break: break-all;
|
||||
font-weight: 600;
|
||||
&.empty {
|
||||
color: #bbb;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
.meta {
|
||||
margin-top: 16upx;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 24upx;
|
||||
font-size: 24upx;
|
||||
color: #666;
|
||||
}
|
||||
.history-head,
|
||||
.debug-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8upx 8upx 16upx;
|
||||
font-size: 28upx;
|
||||
color: #666;
|
||||
.clear {
|
||||
color: #39b54a;
|
||||
}
|
||||
}
|
||||
.history-list {
|
||||
flex: 1;
|
||||
min-height: 160upx;
|
||||
max-height: 360upx;
|
||||
background: #fff;
|
||||
border-radius: 16upx;
|
||||
margin-bottom: 16upx;
|
||||
}
|
||||
.debug-list {
|
||||
height: 240upx;
|
||||
background: #1e1e1e;
|
||||
border-radius: 16upx;
|
||||
padding: 12upx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.debug-line {
|
||||
color: #9cdcfe;
|
||||
font-size: 20upx;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 6upx;
|
||||
word-break: break-all;
|
||||
}
|
||||
.empty {
|
||||
padding: 48upx;
|
||||
text-align: center;
|
||||
color: #bbb;
|
||||
font-size: 26upx;
|
||||
}
|
||||
.history-item {
|
||||
display: flex;
|
||||
padding: 24upx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
.idx {
|
||||
width: 48upx;
|
||||
color: #999;
|
||||
font-size: 24upx;
|
||||
padding-top: 6upx;
|
||||
}
|
||||
.body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.h-code {
|
||||
font-size: 28upx;
|
||||
color: #333;
|
||||
word-break: break-all;
|
||||
}
|
||||
.h-meta {
|
||||
margin-top: 8upx;
|
||||
display: flex;
|
||||
gap: 24upx;
|
||||
font-size: 22upx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
68
JeecgUniapp-master/src/service/xslmes/rawMaterialInspect.ts
Normal file
68
JeecgUniapp-master/src/service/xslmes/rawMaterialInspect.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { http } from '@/utils/http'
|
||||
|
||||
const BASE = '/xslmes/app/rawMaterialInspect'
|
||||
|
||||
/** 原材料卡片(展示字段) */
|
||||
export interface RawMaterialCard {
|
||||
id: string
|
||||
barcode?: string
|
||||
batchNo?: string
|
||||
entryDate?: string
|
||||
materialName?: string
|
||||
supplierName?: string
|
||||
manufacturerMaterialName?: string
|
||||
shelfLife?: string
|
||||
warehouseArea?: string
|
||||
unloadOperator?: string
|
||||
testResult?: string
|
||||
}
|
||||
|
||||
export interface InspectScanResult {
|
||||
card: RawMaterialCard
|
||||
/** sendInspect | enterResult | finished */
|
||||
action: string
|
||||
hasPendingInspect?: boolean
|
||||
pendingRecordId?: string
|
||||
}
|
||||
|
||||
export interface InspectRecordLine {
|
||||
id: string
|
||||
recordId?: string
|
||||
inspectItemName?: string
|
||||
allowMin?: number | null
|
||||
includeMinFlag?: number
|
||||
allowMax?: number | null
|
||||
includeMaxFlag?: number
|
||||
inspectValue?: number | string | null
|
||||
passFlag?: string
|
||||
sortNo?: number
|
||||
}
|
||||
|
||||
export interface InspectRecord {
|
||||
id: string
|
||||
barcode?: string
|
||||
batchNo?: string
|
||||
materialName?: string
|
||||
inspectStatus?: string
|
||||
inspectTime?: string
|
||||
}
|
||||
|
||||
/** 按条码查询卡片及送检状态 */
|
||||
export const queryByBarcode = (barcode: string) =>
|
||||
http.get<InspectScanResult>(`${BASE}/queryByBarcode`, { barcode })
|
||||
|
||||
/** 按条码送检 */
|
||||
export const createByBarcode = (barcode: string) =>
|
||||
http.post<any>(`${BASE}/createByBarcode`, { barcode })
|
||||
|
||||
/** 加载检验明细 */
|
||||
export const prepareResultEntry = (id: string) =>
|
||||
http.post<InspectRecordLine[]>(`${BASE}/prepareResultEntry`, { id })
|
||||
|
||||
/** 保存检验结果 */
|
||||
export const saveInspectResult = (id: string, lineList: Array<{ id: string; inspectValue: number | string }>) =>
|
||||
http.post<string>(`${BASE}/saveInspectResult`, { id, lineList })
|
||||
|
||||
/** 查询送检记录主表 */
|
||||
export const queryRecordById = (id: string) =>
|
||||
http.get<InspectRecord>(`${BASE}/queryRecordById`, { id })
|
||||
BIN
JeecgUniapp-master/src/static/logo/xslmes.png
Normal file
BIN
JeecgUniapp-master/src/static/logo/xslmes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
3
JeecgUniapp-master/src/types/uni-pages.d.ts
vendored
3
JeecgUniapp-master/src/types/uni-pages.d.ts
vendored
@@ -17,6 +17,9 @@ interface NavigateToOptions {
|
||||
"/pages/login/loginOauth2" |
|
||||
"/pages/message/message" |
|
||||
"/pages/more/more" |
|
||||
"/pages/rawMaterialInspect/inspectResult" |
|
||||
"/pages/rawMaterialInspect/rawMaterialInspect" |
|
||||
"/pages/scanIdentify/scanIdentify" |
|
||||
"/pages/user/people" |
|
||||
"/pages/workHome/workHome" |
|
||||
"/pages-home/home/home" |
|
||||
|
||||
291
JeecgUniapp-master/src/utils/dt50Scan.ts
Normal file
291
JeecgUniapp-master/src/utils/dt50Scan.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* 优博讯 DT50 扫描头广播监听
|
||||
* 参数见 docs/DT50扫描识别-广播参数与接入说明.md
|
||||
*
|
||||
* 说明:设备常同时开启「广播 + 键盘」双通道,需优先广播并合并去重,
|
||||
* 否则键盘缓冲会把同一条码拼成两遍(如 6942…28286942…2828)。
|
||||
*/
|
||||
|
||||
export type Dt50ScanCallback = (code: string, type?: string, source?: string) => void
|
||||
export type Dt50LogCallback = (msg: string) => void
|
||||
|
||||
const SCAN_ACTIONS = ['android.intent.ACTION_DECODE_DATA']
|
||||
|
||||
const CODE_KEYS = ['barcode_string', 'scannerdata', 'data', 'barcode']
|
||||
const TYPE_KEYS = ['codetype', 'barcodeType', 'codeType', 'type']
|
||||
|
||||
/** 同码防抖窗口(广播+键盘几乎同时到达) */
|
||||
const DEBOUNCE_MS = 1200
|
||||
/** 广播成功后,忽略键盘楔入的时间窗 */
|
||||
const IGNORE_KEYBOARD_AFTER_BROADCAST_MS = 1500
|
||||
|
||||
let scanReceiver: any = null
|
||||
let listening = false
|
||||
let registering = false
|
||||
let lastCode = ''
|
||||
let lastTime = 0
|
||||
let lastBroadcastTime = 0
|
||||
let broadcastConfirmed = false
|
||||
let activeCallback: Dt50ScanCallback | null = null
|
||||
let logCallback: Dt50LogCallback | null = null
|
||||
|
||||
function log(msg: string) {
|
||||
const line = `[dt50Scan] ${msg}`
|
||||
console.log(line)
|
||||
try {
|
||||
logCallback && logCallback(line)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
function hasPlusAndroid() {
|
||||
return typeof plus !== 'undefined' && !!(plus as any).android
|
||||
}
|
||||
|
||||
function toJsString(val: any): string {
|
||||
if (val == null || val === undefined) return ''
|
||||
if (typeof val === 'string') return val.trim()
|
||||
try {
|
||||
if (typeof val === 'object' && typeof val.toString === 'function') {
|
||||
const s = val.toString()
|
||||
if (s && s !== '[object Object]') return String(s).trim()
|
||||
}
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
return String(plus.android.invoke(val, 'toString') || '').trim()
|
||||
} catch (_e) {
|
||||
return String(val).trim()
|
||||
}
|
||||
}
|
||||
|
||||
function bytesToString(bytes: any): string {
|
||||
if (!bytes) return ''
|
||||
try {
|
||||
const JString = plus.android.importClass('java.lang.String') as any
|
||||
return toJsString(new JString(bytes, 'UTF-8'))
|
||||
} catch (_e) {
|
||||
try {
|
||||
return toJsString(plus.android.newObject('java.lang.String', bytes))
|
||||
} catch (_e2) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 拆开「同一条码粘成两遍」的情况 */
|
||||
export function normalizeScanCode(raw: string): string {
|
||||
const s = String(raw || '')
|
||||
.replace(/[\r\n\t]+/g, '')
|
||||
.trim()
|
||||
if (!s) return ''
|
||||
// 整串 = A+A
|
||||
if (s.length >= 6 && s.length % 2 === 0) {
|
||||
const half = s.length / 2
|
||||
const a = s.slice(0, half)
|
||||
const b = s.slice(half)
|
||||
if (a && a === b) {
|
||||
log(`检测到粘连双码,已拆半: ${s} -> ${a}`)
|
||||
return a
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
function dumpAllExtras(intent: any): string {
|
||||
const found: string[] = []
|
||||
const allKeys = Array.from(new Set([...CODE_KEYS, ...TYPE_KEYS, 'length']))
|
||||
for (const key of allKeys) {
|
||||
try {
|
||||
const s = intent.getStringExtra(key)
|
||||
if (s != null) found.push(`${key}(str)=${toJsString(s)}`)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
const bytes = intent.getByteArrayExtra(key)
|
||||
if (bytes) found.push(`${key}(bytes)=${bytesToString(bytes)}`)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
const n = intent.getIntExtra(key, -999999)
|
||||
if (n !== -999999) found.push(`${key}(int)=${n}`)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return found.length ? found.join(' | ') : '(无可用 extra)'
|
||||
}
|
||||
|
||||
function parseBarcode(intent: any): string {
|
||||
for (const key of CODE_KEYS) {
|
||||
try {
|
||||
const s = toJsString(intent.getStringExtra(key))
|
||||
if (s) return normalizeScanCode(s)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
try {
|
||||
const bytes = intent.getByteArrayExtra('barcode')
|
||||
const s = bytesToString(bytes)
|
||||
if (s) return normalizeScanCode(s)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function parseType(intent: any): string {
|
||||
for (const key of TYPE_KEYS) {
|
||||
try {
|
||||
const s = toJsString(intent.getStringExtra(key))
|
||||
if (s) return s
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function emitBarcode(code: string, type: string, source: string) {
|
||||
const normalized = normalizeScanCode(code)
|
||||
if (!normalized) return
|
||||
|
||||
const now = Date.now()
|
||||
|
||||
// 广播已出结果时,短时间内的键盘结果一律丢弃(避免粘连/重复)
|
||||
if (source === 'keyboard') {
|
||||
if (broadcastConfirmed && now - lastBroadcastTime < IGNORE_KEYBOARD_AFTER_BROADCAST_MS) {
|
||||
log(`忽略键盘重复(广播优先) code=${normalized}`)
|
||||
return
|
||||
}
|
||||
if (broadcastConfirmed) {
|
||||
log(`忽略键盘(本页已确认广播可用) code=${normalized}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (normalized === lastCode && now - lastTime < DEBOUNCE_MS) {
|
||||
log(`防抖忽略重复码 source=${source} code=${normalized}`)
|
||||
return
|
||||
}
|
||||
|
||||
lastCode = normalized
|
||||
lastTime = now
|
||||
if (source === 'broadcast') {
|
||||
lastBroadcastTime = now
|
||||
broadcastConfirmed = true
|
||||
}
|
||||
|
||||
log(`识别成功 source=${source} code=${normalized} type=${type || '-'}`)
|
||||
const cb = activeCallback
|
||||
if (!cb) {
|
||||
log('警告:无页面回调,条码未展示')
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
try {
|
||||
cb(normalized, type || undefined, source)
|
||||
} catch (e) {
|
||||
log(`回调异常: ${e}`)
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function doReceive(_context: any, intent: any) {
|
||||
log('>>> onReceive 触发(说明广播已进入 App)')
|
||||
try {
|
||||
plus.android.importClass(intent)
|
||||
const action = toJsString(intent.getAction())
|
||||
log(`action=${action}`)
|
||||
log(`extras=${dumpAllExtras(intent)}`)
|
||||
const code = parseBarcode(intent)
|
||||
if (!code) {
|
||||
log('广播已收到,但未能解析出条码文本')
|
||||
return
|
||||
}
|
||||
emitBarcode(code, parseType(intent), 'broadcast')
|
||||
} catch (e) {
|
||||
log(`onReceive 异常: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
function registerNow(onBarcode: Dt50ScanCallback) {
|
||||
activeCallback = onBarcode
|
||||
if (listening && scanReceiver) {
|
||||
log('已在监听中,仅更新回调')
|
||||
return
|
||||
}
|
||||
if (registering) {
|
||||
log('正在注册中,跳过重复注册')
|
||||
return
|
||||
}
|
||||
registering = true
|
||||
|
||||
const main = plus.android.runtimeMainActivity()
|
||||
const IntentFilter = plus.android.importClass('android.content.IntentFilter') as any
|
||||
const filter = new IntentFilter()
|
||||
SCAN_ACTIONS.forEach((action) => filter.addAction(action))
|
||||
|
||||
scanReceiver = plus.android.implements('io.dcloud.feature.internal.reflect.BroadcastReceiver', {
|
||||
onReceive: doReceive,
|
||||
})
|
||||
|
||||
try {
|
||||
main.registerReceiver(scanReceiver, filter)
|
||||
listening = true
|
||||
log(`已注册广播监听 actions=${SCAN_ACTIONS.join(',')}`)
|
||||
} catch (e) {
|
||||
scanReceiver = null
|
||||
listening = false
|
||||
log(`registerReceiver 失败: ${e}`)
|
||||
} finally {
|
||||
registering = false
|
||||
}
|
||||
}
|
||||
|
||||
export function setDt50ScanLogger(cb: Dt50LogCallback | null) {
|
||||
logCallback = cb
|
||||
}
|
||||
|
||||
/** 本页是否已确认广播可用(用于关闭键盘楔入) */
|
||||
export function isDt50BroadcastConfirmed() {
|
||||
return broadcastConfirmed
|
||||
}
|
||||
|
||||
export function reportDt50ScanCode(code: string, source = 'keyboard') {
|
||||
emitBarcode(String(code || ''), '', source)
|
||||
}
|
||||
|
||||
export function startDt50ScanListener(onBarcode: Dt50ScanCallback) {
|
||||
if (!hasPlusAndroid()) {
|
||||
log('当前环境无 plus.android,无法监听扫描头广播')
|
||||
return
|
||||
}
|
||||
registerNow(onBarcode)
|
||||
}
|
||||
|
||||
export function stopDt50ScanListener() {
|
||||
activeCallback = null
|
||||
broadcastConfirmed = false
|
||||
lastBroadcastTime = 0
|
||||
if (!hasPlusAndroid() || !scanReceiver) {
|
||||
listening = false
|
||||
registering = false
|
||||
scanReceiver = null
|
||||
return
|
||||
}
|
||||
try {
|
||||
plus.android.runtimeMainActivity().unregisterReceiver(scanReceiver)
|
||||
log('已取消广播监听')
|
||||
} catch (e) {
|
||||
log(`unregisterReceiver 失败: ${e}`)
|
||||
}
|
||||
scanReceiver = null
|
||||
listening = false
|
||||
registering = false
|
||||
}
|
||||
@@ -1247,3 +1247,13 @@ jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/flyway/sql/
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslDryingProcessSwitch/MesXslDryingProcessSwitchPage.vue
|
||||
jeecgboot-vue3/src/views/xslmes/mesXslDryingProcessSwitch/MesXslDryingProcessSwitch.api.ts
|
||||
-- author:jiangxh---date:20260708--for: 【MES】烘胶流程开启(总开关+二次确认,不校验烘胶房日期) ---
|
||||
|
||||
-- author:xsl---date:20260720--for: <20><>APPԭ<50><D4AD><EFBFBD><EFBFBD><EFBFBD>ͼ졿<CDBC>ֳֶ˰<D6B6><CBB0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ѯ/<2F>ͼ<EFBFBD>/¼<><C2BC><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ӿ<EFBFBD><D3BF><EFBFBD>ҳ<EFBFBD><D2B3> ---
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/vo/MesXslRawMaterialInspectAppScanVo.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/controller/MesXslRawMaterialInspectAppController.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/IMesXslRawMaterialInspectRecordService.java
|
||||
jeecg-boot-module/jeecg-module-xslmes/src/main/java/org/jeecg/modules/xslmes/service/impl/MesXslRawMaterialInspectRecordServiceImpl.java
|
||||
JeecgUniapp-master/src/service/xslmes/rawMaterialInspect.ts
|
||||
JeecgUniapp-master/src/pages/rawMaterialInspect/rawMaterialInspect.vue
|
||||
JeecgUniapp-master/src/pages/rawMaterialInspect/inspectResult.vue
|
||||
JeecgUniapp-master/src/common/work.ts
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.jeecg.modules.xslmes.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInspectRecord;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialInspectRecordLine;
|
||||
import org.jeecg.modules.xslmes.service.IMesXslRawMaterialInspectRecordService;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialInspectAppScanVo;
|
||||
import org.jeecg.modules.xslmes.vo.MesXslRawMaterialInspectRecordPage;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* APP 原材料送检接口(手持端)
|
||||
* 判重规则与 PC 一致:存在 inspect_status=0 的送检记录视为已送检。
|
||||
*/
|
||||
@Tag(name = "APP-原材料送检")
|
||||
@RestController
|
||||
@RequestMapping("/xslmes/app/rawMaterialInspect")
|
||||
public class MesXslRawMaterialInspectAppController {
|
||||
|
||||
@Autowired private IMesXslRawMaterialInspectRecordService inspectRecordService;
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP原材料送检】扫码查询-----------
|
||||
@AutoLog(value = "APP原材料送检-按条码查询")
|
||||
@Operation(summary = "按条码查询卡片及送检状态")
|
||||
@GetMapping("/queryByBarcode")
|
||||
public Result<MesXslRawMaterialInspectAppScanVo> queryByBarcode(
|
||||
@RequestParam(name = "barcode") String barcode) {
|
||||
if (StringUtils.isBlank(barcode)) {
|
||||
return Result.error("条码不能为空");
|
||||
}
|
||||
return Result.OK(inspectRecordService.queryByBarcodeForApp(barcode.trim()));
|
||||
}
|
||||
//update-end---author:xsl ---date:20260720 for:【APP原材料送检】扫码查询-----------
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP原材料送检】按条码送检-----------
|
||||
@AutoLog(value = "APP原材料送检-按条码送检")
|
||||
@Operation(summary = "按条码送检")
|
||||
@PostMapping("/createByBarcode")
|
||||
public Result<MesXslRawMaterialInspectRecord> createByBarcode(@RequestBody Map<String, Object> body) {
|
||||
String barcode = body.get("barcode") == null ? "" : body.get("barcode").toString();
|
||||
if (StringUtils.isBlank(barcode)) {
|
||||
return Result.error("条码不能为空");
|
||||
}
|
||||
MesXslRawMaterialInspectRecord record = inspectRecordService.createByBarcode(barcode.trim());
|
||||
return Result.OK("送检成功", record);
|
||||
}
|
||||
//update-end---author:xsl ---date:20260720 for:【APP原材料送检】按条码送检-----------
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP原材料送检】录入检验结果-----------
|
||||
@AutoLog(value = "APP原材料送检-加载检验明细")
|
||||
@Operation(summary = "录入检验结果前加载明细")
|
||||
@PostMapping("/prepareResultEntry")
|
||||
public Result<List<MesXslRawMaterialInspectRecordLine>> prepareResultEntry(
|
||||
@RequestBody Map<String, Object> body) {
|
||||
String id = body.get("id") == null ? "" : body.get("id").toString();
|
||||
if (StringUtils.isBlank(id)) {
|
||||
return Result.error("id不能为空");
|
||||
}
|
||||
return Result.OK(inspectRecordService.prepareResultEntryLines(id.trim()));
|
||||
}
|
||||
|
||||
@AutoLog(value = "APP原材料送检-保存检验结果")
|
||||
@Operation(summary = "保存检验结果")
|
||||
@PostMapping("/saveInspectResult")
|
||||
public Result<String> saveInspectResult(@RequestBody MesXslRawMaterialInspectRecordPage page) {
|
||||
if (StringUtils.isBlank(page.getId())) {
|
||||
return Result.error("id不能为空");
|
||||
}
|
||||
inspectRecordService.saveInspectResult(page.getId(), page.getLineList());
|
||||
return Result.OK("保存成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "按送检记录ID查询主表")
|
||||
@GetMapping("/queryRecordById")
|
||||
public Result<MesXslRawMaterialInspectRecord> queryRecordById(@RequestParam(name = "id") String id) {
|
||||
MesXslRawMaterialInspectRecord db = inspectRecordService.getById(id);
|
||||
if (db == null) {
|
||||
return Result.error("未找到对应数据");
|
||||
}
|
||||
return Result.OK(db);
|
||||
}
|
||||
//update-end---author:xsl ---date:20260720 for:【APP原材料送检】录入检验结果-----------
|
||||
}
|
||||
@@ -23,4 +23,16 @@ public interface IMesXslRawMaterialInspectRecordService
|
||||
Integer includeMinFlag,
|
||||
BigDecimal allowMax,
|
||||
Integer includeMaxFlag);
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP原材料送检】按条码查询/送检-----------
|
||||
/**
|
||||
* APP:按条码查询卡片及送检状态(是否已有待检送检记录)
|
||||
*/
|
||||
org.jeecg.modules.xslmes.vo.MesXslRawMaterialInspectAppScanVo queryByBarcodeForApp(String barcode);
|
||||
|
||||
/**
|
||||
* APP:按条码发起送检
|
||||
*/
|
||||
MesXslRawMaterialInspectRecord createByBarcode(String barcode);
|
||||
//update-end---author:xsl ---date:20260720 for:【APP原材料送检】按条码查询/送检-----------
|
||||
}
|
||||
|
||||
@@ -324,4 +324,69 @@ public class MesXslRawMaterialInspectRecordServiceImpl
|
||||
private String normalize(String value) {
|
||||
return StringUtils.trimToEmpty(value);
|
||||
}
|
||||
|
||||
//update-begin---author:xsl ---date:20260720 for:【APP原材料送检】按条码查询/送检-----------
|
||||
@Override
|
||||
public org.jeecg.modules.xslmes.vo.MesXslRawMaterialInspectAppScanVo queryByBarcodeForApp(String barcode) {
|
||||
String code = normalize(barcode);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
throw new JeecgBootException("条码不能为空");
|
||||
}
|
||||
MesXslRawMaterialCard card =
|
||||
rawMaterialCardService
|
||||
.lambdaQuery()
|
||||
.eq(MesXslRawMaterialCard::getBarcode, code)
|
||||
.last("LIMIT 1")
|
||||
.one();
|
||||
if (card == null) {
|
||||
throw new JeecgBootException("未找到条码对应的原材料卡片");
|
||||
}
|
||||
MesXslRawMaterialInspectRecord pending =
|
||||
this.lambdaQuery()
|
||||
.eq(MesXslRawMaterialInspectRecord::getRawMaterialCardId, card.getId())
|
||||
.eq(MesXslRawMaterialInspectRecord::getInspectStatus, STATUS_PENDING)
|
||||
.orderByDesc(MesXslRawMaterialInspectRecord::getInspectTime)
|
||||
.last("LIMIT 1")
|
||||
.one();
|
||||
|
||||
org.jeecg.modules.xslmes.vo.MesXslRawMaterialInspectAppScanVo vo =
|
||||
new org.jeecg.modules.xslmes.vo.MesXslRawMaterialInspectAppScanVo();
|
||||
vo.setCard(card);
|
||||
if (pending != null) {
|
||||
// 已有待检送检记录 → 视为已送检,进入录入检验结果
|
||||
vo.setHasPendingInspect(true);
|
||||
vo.setPendingRecordId(pending.getId());
|
||||
vo.setAction("enterResult");
|
||||
return vo;
|
||||
}
|
||||
vo.setHasPendingInspect(false);
|
||||
String cardTestResult = normalize(card.getTestResult());
|
||||
if (StringUtils.isBlank(cardTestResult) || CARD_TEST_RESULT_UNTESTED.equals(cardTestResult)) {
|
||||
vo.setAction("sendInspect");
|
||||
} else {
|
||||
// 已合格/不合格且无待检记录
|
||||
vo.setAction("finished");
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public MesXslRawMaterialInspectRecord createByBarcode(String barcode) {
|
||||
String code = normalize(barcode);
|
||||
if (StringUtils.isBlank(code)) {
|
||||
throw new JeecgBootException("条码不能为空");
|
||||
}
|
||||
MesXslRawMaterialCard card =
|
||||
rawMaterialCardService
|
||||
.lambdaQuery()
|
||||
.eq(MesXslRawMaterialCard::getBarcode, code)
|
||||
.last("LIMIT 1")
|
||||
.one();
|
||||
if (card == null) {
|
||||
throw new JeecgBootException("未找到条码对应的原材料卡片");
|
||||
}
|
||||
return createByRawMaterialCardId(card.getId());
|
||||
}
|
||||
//update-end---author:xsl ---date:20260720 for:【APP原材料送检】按条码查询/送检-----------
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package org.jeecg.modules.xslmes.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
import org.jeecg.modules.xslmes.entity.MesXslRawMaterialCard;
|
||||
|
||||
/**
|
||||
* APP 原材料送检扫码查询结果
|
||||
*/
|
||||
@Data
|
||||
@Schema(description = "APP原材料送检扫码结果")
|
||||
public class MesXslRawMaterialInspectAppScanVo implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "原材料卡片")
|
||||
private MesXslRawMaterialCard card;
|
||||
|
||||
/**
|
||||
* sendInspect=可送检;enterResult=已送检待录入;finished=已完成检验
|
||||
*/
|
||||
@Schema(description = "下一步动作:sendInspect/enterResult/finished")
|
||||
private String action;
|
||||
|
||||
@Schema(description = "是否存在待检送检记录")
|
||||
private Boolean hasPendingInspect;
|
||||
|
||||
@Schema(description = "待检送检记录ID(action=enterResult 时有值)")
|
||||
private String pendingRecordId;
|
||||
}
|
||||
@@ -17,7 +17,8 @@
|
||||
"name": "打印调节器"
|
||||
},
|
||||
{
|
||||
"path": "JeecgUniapp-master"
|
||||
"path": "JeecgUniapp-master",
|
||||
"name": "APP端"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
|
||||
Reference in New Issue
Block a user