diff --git a/pages/picking/picking.vue b/pages/picking/picking.vue
index 941bd90..d788f2c 100644
--- a/pages/picking/picking.vue
+++ b/pages/picking/picking.vue
@@ -31,7 +31,12 @@
+
+
+
+
+
@@ -115,9 +120,10 @@
+
-
+
装柜证据空仓、半仓、满仓、锁仓及带锁码照
{{ remoteSyncState.label }}
@@ -152,7 +158,7 @@
-
+
{{ summary.pending }} 箱待拣
{{ submitStatusHint }}
@@ -363,6 +369,7 @@
incompleteReasons: INCOMPLETE_REASONS,
...getPickingSession(),
activeScanField: 'boxCode',
+ activeStage: 'picking',
partialFocused: false,
boxDropdownOpen: false,
activeOverlay: '',
@@ -516,6 +523,13 @@
return false
},
methods: {
+ selectStage(stage) {
+ if (stage === 'evidence' && !this.loadingStageReady) {
+ this.showToast('请先完成拣货并刷新任务', 'warning')
+ return
+ }
+ this.activeStage = stage
+ },
debugEvidence(event, payload = {}) {
try { console.info(`[PDA] evidence.${event}`, payload) } catch (_) { /* console may be unavailable on a device */ }
},
@@ -692,6 +706,7 @@
this.taskRequestId += 1
this.submitting = false
this.currentBoxIndex = index
+ this.activeStage = 'picking'
this.currentBox.inboundId = null
this.currentBox.inboundVersion = null
this.remoteTaskId = null
diff --git a/styles/pda.scss b/styles/pda.scss
index 5d58aa9..c131b4a 100644
--- a/styles/pda.scss
+++ b/styles/pda.scss
@@ -289,6 +289,47 @@
transition: transform 160ms ease-out;
}
+ .stage-switch {
+ display: flex;
+ gap: 4px;
+ margin: 0 12px 10px;
+ padding: 4px;
+ border: 1px solid $border;
+ border-radius: 10px;
+ background: #eef2f7;
+ }
+
+ .stage-tab {
+ flex: 1;
+ min-height: 42px;
+ padding: 0 8px;
+ border: 0;
+ border-radius: 7px;
+ color: $text-secondary;
+ font-size: 13px;
+ font-weight: 600;
+ line-height: 42px;
+ background: transparent;
+ }
+
+ .stage-tab.active {
+ color: $primary;
+ background: #ffffff;
+ box-shadow: 0 1px 4px rgba(31, 41, 55, 0.12);
+ }
+
+ .stage-tab.locked {
+ color: #9aa5b1;
+ }
+
+ .stage-tab-hint {
+ display: block;
+ margin-top: -9px;
+ font-size: 9px;
+ font-weight: 400;
+ line-height: 12px;
+ }
+
.box-chevron.open {
transform: rotate(180deg);
}
diff --git a/tests/picking-evidence-stage.test.mjs b/tests/picking-evidence-stage.test.mjs
index d4199e0..8b33b01 100644
--- a/tests/picking-evidence-stage.test.mjs
+++ b/tests/picking-evidence-stage.test.mjs
@@ -5,7 +5,7 @@ import { readFile } from 'node:fs/promises'
const page = await readFile(new URL('../pages/picking/picking.vue', import.meta.url), 'utf8')
test('loading evidence is mounted only after the current picking task is complete', () => {
- assert.match(page, /v-if="loadingStageReady" class="loading-evidence-section"/)
+ assert.match(page, /v-if="loadingStageReady && activeStage === 'evidence'" class="loading-evidence-section"/)
assert.match(page, /isPickingCompleteTask\(task\)[\s\S]*?AWAITING_REVIEW[\s\S]*?COMPLETED/)
assert.match(page, /pickingCompleted\(\)[\s\S]*?completedBoxes\[this\.currentBoxIndex\][\s\S]*?isPickingCompleteTask\(this\.remotePick\)/)
assert.doesNotMatch(page, /class="loading-evidence-section" v-if="currentBox\.inboundId"/)
@@ -33,6 +33,14 @@ test('AWAITING_REVIEW alone cannot mount or request evidence without a server lo
assert.doesNotMatch(page, /if \(this\.pickingCompleted\) await this\.loadLoadingEvidence\(\)/)
})
+test('picking and evidence are separate operator-selected stages', () => {
+ assert.match(page, /class="stage-switch" role="tablist"/)
+ assert.match(page, /@tap="selectStage\('picking'\)"/)
+ assert.match(page, /@tap="selectStage\('evidence'\)"/)
+ assert.match(page, /selectStage\(stage\)[\s\S]*?stage === 'evidence'[\s\S]*?!this\.loadingStageReady[\s\S]*?请先完成拣货并刷新任务/)
+ assert.match(page, /activeStage === 'picking' \|\| !loadingStageReady/)
+})
+
test('evidence identity is bound to task, inbound, loading plan and container booking', () => {
assert.match(page, /captureEvidenceIdentity\(\)[\s\S]*?taskId:[\s\S]*?inboundId:[\s\S]*?loadingPlanId:[\s\S]*?containerBookingId:/)
assert.match(page, /isSameEvidenceIdentity\(left, right\)[\s\S]*?taskId[\s\S]*?inboundId[\s\S]*?loadingPlanId[\s\S]*?containerBookingId/)