feat: 优化手动进仓流程并修复N5S扫码识别升至1.1.26

This commit is contained in:
2026-11-10 18:43:55 +08:00
parent c0067e3b89
commit b1648bcb30
5 changed files with 219 additions and 92 deletions
+4
View File
@@ -35,7 +35,11 @@ export async function ensureScanConfigAfterLogin(api) {
if (result.skipped) { if (result.skipped) {
console.log('本地已有扫码设置,跳过自动匹配', result.config && result.config.name); console.log('本地已有扫码设置,跳过自动匹配', result.config && result.config.name);
} else if (result.saved) { } else if (result.saved) {
if (result.reason === 'rematch_n5s_from_seuic') {
console.log('N5S 误识别为东集,已纠正为', result.config && result.config.name);
} else {
console.log('已自动写入扫码设置', result.config && result.config.name); console.log('已自动写入扫码设置', result.config && result.config.name);
}
} else { } else {
console.log('自动匹配扫码设置未写入', result.reason); console.log('自动匹配扫码设置未写入', result.reason);
} }
+105 -9
View File
@@ -50,6 +50,14 @@ export const BUILTIN_SCANNER_CONFIGS = [
dataKey: 'SCAN_BARCODE1', dataKey: 'SCAN_BARCODE1',
needSetup: false, needSetup: false,
}, },
{
// N5S 部分固件走智联天地兼容广播,而不是 nlscan.action
brand: 'nlscan_compat',
name: '新大陆(兼容广播)',
action: 'android.intent.action.RECEIVE_SCANDATA_BROADCAST',
dataKey: 'android.intent.extra.SCAN_BROADCAST_DATA',
needSetup: false,
},
{ ...PHONE_SCAN_CONFIG }, { ...PHONE_SCAN_CONFIG },
]; ];
@@ -62,10 +70,19 @@ const DETECT_RULES = [
packages: ['com.sunmi.scanner', 'woyou.aidlservice.jiuiv5'], packages: ['com.sunmi.scanner', 'woyou.aidlservice.jiuiv5'],
}, },
{ {
matchNames: ['新大陆'], matchNames: ['新大陆', '新大陆(兼容广播)'],
matchActions: ['nlscan.action.SCANNER_RESULT'], matchActions: [
keywords: ['newland', 'nls', 'nlscan'], 'nlscan.action.SCANNER_RESULT',
packages: ['com.nlscan.barcodescanner', 'com.newland'], 'android.intent.action.RECEIVE_SCANDATA_BROADCAST',
],
// N5S 机型常标成 alps/zltd,不能只靠 newland 关键字
keywords: ['newland', 'nls', 'nlscan', 'n5s', 'zltd'],
models: ['n5s', 'n5'],
packages: [
'com.nlscan.barcodescanner',
'com.newland',
'com.nlscan.android.scan.service',
],
}, },
{ {
matchNames: ['优博讯'], matchNames: ['优博讯'],
@@ -94,10 +111,19 @@ const DETECT_RULES = [
]; ];
const PDA_DEVICE_KEYWORDS = [ const PDA_DEVICE_KEYWORDS = [
'newland', 'nls', 'nlscan', 'sunmi', 'urovo', 'ubx', 'zebra', 'honeywell', 'newland', 'nls', 'nlscan', 'n5s', 'zltd', 'sunmi', 'urovo', 'ubx', 'zebra', 'honeywell',
'idata', 'seuic', 'autoid', '东集', '东大', 'idata', 'seuic', 'autoid', '东集', '东大',
]; ];
/** 明确机型 → 优先品牌,避免通用东集广播抢分 */
const MODEL_PREFERRED_BRANDS = [
{
models: ['n5s'],
brands: ['nlscan', 'nlscan_compat'],
score: 220,
},
];
const NO_SETUP_HINT_KEY = 'pda_scan_config_hint_shown'; const NO_SETUP_HINT_KEY = 'pda_scan_config_hint_shown';
export function isPhoneScanConfig(config) { export function isPhoneScanConfig(config) {
@@ -319,6 +345,57 @@ function canResolveBroadcast(action) {
return false; return false;
} }
function isModelMatch(identity, models = []) {
const model = String((identity && identity.model) || '').toLowerCase();
if (!model) return false;
return (models || []).some((item) => model === item || model.includes(item));
}
function getModelPreferredBoost(config, identity) {
if (!config || isPhoneScanConfig(config)) {
return 0;
}
const brand = String(config.brand || '');
for (let i = 0; i < MODEL_PREFERRED_BRANDS.length; i += 1) {
const rule = MODEL_PREFERRED_BRANDS[i];
if (!isModelMatch(identity, rule.models)) continue;
if ((rule.brands || []).includes(brand)) {
return Number(rule.score) || 0;
}
}
return 0;
}
/** 新大陆 N5S / 智联天地 N5S:机型名常为 n5s,厂商可能是 newland/zltd/alps */
export function isNewlandN5sDevice(identity = getDeviceIdentity()) {
const model = String((identity && identity.model) || '').toLowerCase();
const text = `${identity.manufacturer || ''} ${identity.brand || ''} ${model}`.toLowerCase();
if (model === 'n5s' || model.includes('n5s')) {
return true;
}
return text.includes('n5s') && (
text.includes('newland')
|| text.includes('zltd')
|| text.includes('nlscan')
|| text.includes('alps')
);
}
function isWrongSeuicOnNewlandDevice(config, identity = getDeviceIdentity()) {
if (!config || isPhoneScanConfig(config)) {
return false;
}
if (!isNewlandN5sDevice(identity) && !isModelMatch(identity, ['n5s'])) {
return false;
}
const brand = String(config.brand || '');
const action = String(config.action || '');
return brand === 'seuic'
|| action.includes('scannerservice.broadcast')
|| String(config.name || '').includes('东集')
|| String(config.name || '').includes('东大');
}
function scoreScannerConfig(config, identity) { function scoreScannerConfig(config, identity) {
if (!config || isPhoneScanConfig(config)) { if (!config || isPhoneScanConfig(config)) {
return 0; return 0;
@@ -339,6 +416,19 @@ function scoreScannerConfig(config, identity) {
if ((rule.keywords || []).some((keyword) => deviceText.includes(String(keyword).toLowerCase()))) { if ((rule.keywords || []).some((keyword) => deviceText.includes(String(keyword).toLowerCase()))) {
score += 40; score += 40;
} }
if (isModelMatch(identity, rule.models || [])) {
score += 80;
}
}
score += getModelPreferredBoost(config, identity);
// 明确是 N5S 时,压低东集通用广播抢分
if (isNewlandN5sDevice(identity) && (
config.brand === 'seuic'
|| String(config.action || '').includes('scannerservice.broadcast')
)) {
score = Math.min(score, 30);
} }
return score; return score;
@@ -376,12 +466,16 @@ export function matchBestScanConfig(candidateConfigs = []) {
} }
/** /**
* 仅本地没有有效配置时,自动匹配并写入扫描设置 * 仅本地没有有效配置时,自动匹配并写入扫描设置。
* 若本地误把 N5S 识别成东集,则强制重配。
* @returns {{ saved: boolean, config: object|null, skipped: boolean, reason?: string }} * @returns {{ saved: boolean, config: object|null, skipped: boolean, reason?: string }}
*/ */
export function autoConfigureScanSettings({ remoteConfigs = [] } = {}) { export function autoConfigureScanSettings({ remoteConfigs = [] } = {}) {
const existing = getScanConfig(BUILTIN_SCANNER_CONFIGS); const existing = getScanConfig(BUILTIN_SCANNER_CONFIGS);
if (existing) { const identity = getDeviceIdentity();
const forceRematch = !!(existing && isWrongSeuicOnNewlandDevice(existing, identity));
if (existing && !forceRematch) {
return { return {
saved: false, saved: false,
skipped: true, skipped: true,
@@ -396,7 +490,7 @@ export function autoConfigureScanSettings({ remoteConfigs = [] } = {}) {
return { return {
saved: false, saved: false,
skipped: false, skipped: false,
config: null, config: existing || null,
reason: 'no_match', reason: 'no_match',
}; };
} }
@@ -406,6 +500,8 @@ export function autoConfigureScanSettings({ remoteConfigs = [] } = {}) {
saved, saved,
skipped: false, skipped: false,
config: matched, config: matched,
reason: saved ? 'saved' : 'save_failed', reason: saved
? (forceRematch ? 'rematch_n5s_from_seuic' : 'saved')
: 'save_failed',
}; };
} }
+2 -2
View File
@@ -2,8 +2,8 @@
"name" : "浩拓织厂", "name" : "浩拓织厂",
"appid" : "__UNI__F79F300", "appid" : "__UNI__F79F300",
"description" : "周尚-浩拓织厂", "description" : "周尚-浩拓织厂",
"versionName" : "1.1.25", "versionName" : "1.1.26",
"versionCode" : 125, "versionCode" : 126,
"transformPx" : false, "transformPx" : false,
"app-plus" : { "app-plus" : {
"optimization" : { "optimization" : {
+97 -79
View File
@@ -9,58 +9,10 @@
</template> </template>
</common-navbar> </common-navbar>
<view class="manual-in__panel">
<view class="manual-in__row manual-in__row--input" @click="openInspectorPicker">
<text class="manual-in__label">查布工</text>
<view class="manual-in__right">
<text :class="detail.inspector_name ? 'manual-in__field-text' : 'manual-in__placeholder'">
{{ detail.inspector_name || '请选择查布工' }}
</text>
<text class="manual-in__action" @click.stop="clearInspector">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">重量</text>
<view class="manual-in__right">
<input
class="manual-in__field"
type="digit"
v-model="weightKg"
placeholder="请输入"
placeholder-class="manual-in__placeholder"
/>
<text class="manual-in__unit">KG</text>
<text class="manual-in__action" @click="clearWeight">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input" @click="openLevelPicker">
<text class="manual-in__label">等级</text>
<view class="manual-in__right">
<text :class="levelDisplayName ? 'manual-in__field-text' : 'manual-in__placeholder'">
{{ levelDisplayName || '请选择等级' }}
</text>
<text class="manual-in__action" @click.stop="clearLevel">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">存放仓位</text>
<view class="manual-in__right">
<input
class="manual-in__field"
type="text"
v-model="warehouseBinName"
placeholder="/"
placeholder-class="manual-in__placeholder"
/>
<text class="manual-in__action" @click="editWarehouseBin">修改</text>
</view>
</view>
</view>
<PlusBarcodePanel <PlusBarcodePanel
ref="plusBarcodePanel" ref="plusBarcodePanel"
:visible.sync="phoneScanPanelOpen" :visible.sync="phoneScanPanelOpen"
hint-text="连续扫码进仓,扫完可继续下一条" hint-text="先扫描布票,再填写信息后确认进仓"
:scan-time-space="1200" :scan-time-space="1200"
@success="onPlusBarcodeSuccess" @success="onPlusBarcodeSuccess"
/> />
@@ -73,7 +25,7 @@
:confirm-hold="true" :confirm-hold="true"
:focus="scanFocus" :focus="scanFocus"
v-model="scanCode" v-model="scanCode"
placeholder="先选查布工并填写信息,再扫描布票编码" placeholder="请先扫描布票编码"
placeholder-class="manual-in__placeholder" placeholder-class="manual-in__placeholder"
@confirm="onScanConfirm" @confirm="onScanConfirm"
/> />
@@ -121,12 +73,71 @@
<text class="manual-in__label">坯布编号</text> <text class="manual-in__label">坯布编号</text>
<text class="manual-in__value">{{ detail.grey_fabric_code || '-' }}</text> <text class="manual-in__value">{{ detail.grey_fabric_code || '-' }}</text>
</view> </view>
<view class="manual-in__row" v-if="detail.volume_number != null && detail.volume_number !== ''"> <view class="manual-in__row">
<text class="manual-in__label">卷号</text> <text class="manual-in__label">卷号</text>
<text class="manual-in__value">{{ detail.volume_number }}</text> <text class="manual-in__value">{{ detail.volume_number != null && detail.volume_number !== '' ? detail.volume_number : '-' }}</text>
</view> </view>
</view> </view>
<view class="manual-in__panel manual-in__panel--form">
<view class="manual-in__row manual-in__row--input" @click="openInspectorPicker">
<text class="manual-in__label">查布工</text>
<view class="manual-in__right">
<text :class="detail.inspector_name ? 'manual-in__field-text' : 'manual-in__placeholder'">
{{ detail.inspector_name || '请选择查布工' }}
</text>
<text class="manual-in__action" @click.stop="clearInspector">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">重量</text>
<view class="manual-in__right">
<input
class="manual-in__field"
type="digit"
v-model="weightKg"
placeholder="请输入"
placeholder-class="manual-in__placeholder"
/>
<text class="manual-in__unit">KG</text>
<text class="manual-in__action" @click="clearWeight">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input" @click="openLevelPicker">
<text class="manual-in__label">等级</text>
<view class="manual-in__right">
<text :class="levelDisplayName ? 'manual-in__field-text' : 'manual-in__placeholder'">
{{ levelDisplayName || '请选择等级' }}
</text>
<text class="manual-in__action" @click.stop="clearLevel">取消</text>
</view>
</view>
<view class="manual-in__row manual-in__row--input">
<text class="manual-in__label">存放仓位</text>
<view class="manual-in__right">
<input
class="manual-in__field"
type="text"
v-model="warehouseBinName"
placeholder="/"
placeholder-class="manual-in__placeholder"
/>
<text class="manual-in__action" @click="editWarehouseBin">修改</text>
</view>
</view>
</view>
<view class="manual-in__footer">
<u-button
type="primary"
:loading="submitting"
:disabled="submitting || loadingDetail"
@click="submitInWarehouse"
>
确认进仓
</u-button>
</view>
<SearchSelectPopup <SearchSelectPopup
:show.sync="levelPickerShow" :show.sync="levelPickerShow"
title="选择等级" title="选择等级"
@@ -380,11 +391,6 @@ export default {
this.refocusScanInput(); this.refocusScanInput();
}, },
handlePhoneScan() { handlePhoneScan() {
const err = this.validateBeforeScan();
if (err) {
this.$u.toast(err);
return;
}
this.phoneScanPanelOpen = !this.phoneScanPanelOpen; this.phoneScanPanelOpen = !this.phoneScanPanelOpen;
}, },
onScanConfirm() { onScanConfirm() {
@@ -395,32 +401,29 @@ export default {
} }
this.fetchScanDetail(code); this.fetchScanDetail(code);
}, },
validateBeforeScan() { validateBeforeSubmit() {
const fabricPieceCode = String(this.detail.fabric_piece_code || this.scanCode || '').trim();
if (!fabricPieceCode) {
return '请先扫描或输入布票编码';
}
const inspectorName = String(this.detail.inspector_name || '').trim(); const inspectorName = String(this.detail.inspector_name || '').trim();
if (!inspectorName && (this.detail.inspector_id == null || this.detail.inspector_id === '')) { if (!inspectorName && (this.detail.inspector_id == null || this.detail.inspector_id === '')) {
return '请先选择查布工'; return '请选择查布工';
} }
if (toWeightInt(this.weightKg) == null) { if (toWeightInt(this.weightKg) == null) {
return '请先输入有效重量'; return '请输入有效重量';
} }
return ''; return '';
}, },
async fetchScanDetail(code) { async fetchScanDetail(code) {
if (this.loadingDetail || this.submitting) return; if (this.loadingDetail || this.submitting) return;
const err = this.validateBeforeScan();
if (err) {
this.$u.toast(err);
return;
}
this.loadingDetail = true; this.loadingDetail = true;
this.scanCode = code; this.scanCode = code;
let detailOk = false;
try { try {
const res = await this.$u.api.productionScheduleOrder.getScanFineCodeDetail({ const res = await this.$u.api.productionScheduleOrder.getScanFineCodeDetail({
fabric_piece_code: code, fabric_piece_code: code,
}); });
this.applyScanDetail(res, code); this.applyScanDetail(res, code);
detailOk = true;
} catch (error) { } catch (error) {
console.log('扫码获取细码失败', error); console.log('扫码获取细码失败', error);
const kept = this.keepFormFields(); const kept = this.keepFormFields();
@@ -434,9 +437,6 @@ export default {
} finally { } finally {
this.loadingDetail = false; this.loadingDetail = false;
} }
if (detailOk) {
await this.submitInWarehouse();
}
}, },
keepFormFields() { keepFormFields() {
return { return {
@@ -463,16 +463,13 @@ export default {
this.scanCode = this.detail.fabric_piece_code; this.scanCode = this.detail.fabric_piece_code;
}, },
async submitInWarehouse() { async submitInWarehouse() {
const err = this.validateBeforeSubmit();
if (err) {
this.$u.toast(err);
return;
}
const fabricPieceCode = String(this.detail.fabric_piece_code || this.scanCode || '').trim(); const fabricPieceCode = String(this.detail.fabric_piece_code || this.scanCode || '').trim();
if (!fabricPieceCode) {
this.$u.toast('请先扫描或输入布票编码');
return;
}
const actualWeight = toWeightInt(this.weightKg); const actualWeight = toWeightInt(this.weightKg);
if (actualWeight == null) {
this.$u.toast('请输入有效重量');
return;
}
const levelIds = normalizeLevelIds(this.detail.grey_fabric_level_id); const levelIds = normalizeLevelIds(this.detail.grey_fabric_level_id);
if (this.submitting) return; if (this.submitting) return;
this.submitting = true; this.submitting = true;
@@ -540,7 +537,7 @@ export default {
.manual-in { .manual-in {
min-height: 100vh; min-height: 100vh;
background: #f5f6f8; background: #f5f6f8;
padding-bottom: 40rpx; padding-bottom: calc(160rpx + env(safe-area-inset-bottom));
box-sizing: border-box; box-sizing: border-box;
} }
@@ -605,6 +602,27 @@ export default {
background: #fff; background: #fff;
} }
.manual-in__panel--form {
margin-top: 16rpx;
}
.manual-in__footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 20rpx 28rpx calc(20rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
box-sizing: border-box;
z-index: 20;
::v-deep .u-btn {
height: $pda-button-height !important;
font-size: $pda-font-button !important;
}
}
.manual-in__row { .manual-in__row {
display: flex; display: flex;
align-items: center; align-items: center;
+10 -1
View File
@@ -42,6 +42,7 @@ import {
mapDictionaryToScannerConfig, mapDictionaryToScannerConfig,
PHONE_SCAN_BRAND, PHONE_SCAN_BRAND,
saveScanConfig, saveScanConfig,
autoConfigureScanSettings,
} from '@/common/scanConfig.js'; } from '@/common/scanConfig.js';
export default { export default {
@@ -100,6 +101,7 @@ export default {
this.ensurePhoneScanOption(); this.ensurePhoneScanOption();
this.ensureSeuicOption(); this.ensureSeuicOption();
this.ensureNlscanOption(); this.ensureNlscanOption();
this.ensureNlscanCompatOption();
this.initFormFromSaved(); this.initFormFromSaved();
}, },
useFallbackConfigList() { useFallbackConfigList() {
@@ -136,8 +138,15 @@ export default {
ensureNlscanOption() { ensureNlscanOption() {
this.ensureBuiltinScannerOption('nlscan'); this.ensureBuiltinScannerOption('nlscan');
}, },
ensureNlscanCompatOption() {
this.ensureBuiltinScannerOption('nlscan_compat');
},
initFormFromSaved() { initFormFromSaved() {
const savedConfig = getScanConfig(this.getScannerConfigs()); // N5S 曾被误识别为东集时,进入设置页会纠正并写回
const rematch = autoConfigureScanSettings({
remoteConfigs: this.remoteScannerConfigs || [],
});
const savedConfig = (rematch && rematch.config) || getScanConfig(this.getScannerConfigs());
if (savedConfig) { if (savedConfig) {
this.applyConfig(savedConfig); this.applyConfig(savedConfig);
return; return;