Release/test #1
@@ -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" />
|
||||
@@ -116,8 +121,9 @@
|
||||
</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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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/)
|
||||
|
||||
Reference in New Issue
Block a user