This commit is contained in:
2026-08-15 11:51:18 +08:00
parent a9b5559148
commit f7166f2eba
3 changed files with 67 additions and 3 deletions
+17 -2
View File
@@ -31,7 +31,12 @@
</view>
</view>
</view>
<view class="stage-switch" role="tablist" aria-label="作业阶段">
<button class="stage-tab" :class="{ active: activeStage === 'picking' }" role="tab" :aria-selected="activeStage === 'picking'" @tap="selectStage('picking')">拣货作业</button>
<button class="stage-tab" :class="{ active: activeStage === 'evidence', locked: !loadingStageReady }" role="tab" :aria-selected="activeStage === 'evidence'" :aria-disabled="!loadingStageReady" @tap="selectStage('evidence')">装柜取证<text v-if="!loadingStageReady" class="stage-tab-hint">完成拣货后开放</text></button>
</view>
<view v-if="activeStage === 'picking' || !loadingStageReady" class="picking-stage">
<view class="scan-section">
<view class="scan-row">
<ScanTargetInput v-model="scanValue" name="boxCode" label="箱码扫码框" placeholder="扫描或输入箱唛标签码" :active="activeScanField === 'boxCode'" @activate="setScanTarget" @submit="handleManualSubmit" />
@@ -115,9 +120,10 @@
</view>
</view>
</view>
</view>
</view>
<view v-if="loadingStageReady" class="loading-evidence-section">
<view v-if="loadingStageReady && activeStage === 'evidence'" class="loading-evidence-section">
<view class="section-heading-row">
<view><text class="section-title">装柜证据</text><text class="section-subtitle">空仓、半仓、满仓、锁仓及带锁码照</text></view>
<text class="sync-state" :class="remoteSyncState.tone">{{ remoteSyncState.label }}</text>
@@ -152,7 +158,7 @@
<button class="evidence-retry-btn" :disabled="remoteLoading" @tap="refreshTask">刷新任务</button>
</view>
<view class="submit-section">
<view v-if="activeStage === 'picking' || !loadingStageReady" class="submit-section">
<view class="submit-info">
<view class="submit-pending"><text class="submit-pending-number">{{ summary.pending }}</text><text class="submit-pending-label"> 箱待拣</text></view>
<text class="submit-status-hint">{{ submitStatusHint }}</text>
@@ -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
+41
View File
@@ -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);
}
+9 -1
View File
@@ -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/)