fix(pda): guard duplicate submission and invalidate hidden task requests

This commit is contained in:
2026-10-02 11:12:53 +08:00
parent 7ba6a24505
commit 8d2d80ec11
4 changed files with 155 additions and 17 deletions
+25 -13
View File
@@ -282,7 +282,7 @@
<uni-popup ref="submitConfirmPopup" type="bottom" background-color="#ffffff" border-radius="16px 16px 0 0" :is-mask-click="true" @change="handleOverlayChange('submitConfirm', $event)">
<view class="modal-sheet decision-modal submit-decision-modal" role="dialog" aria-modal="true" aria-label="确认未完成提交" @tap.stop>
<PopupHeader :title="`仍有 ${summary.pending} 件未完成`" tone="danger" @close="closeOverlay" />
<text class="decision-desc submit-decision-desc">确认提交后,以下待拣商品会被明确标记为“未拣出”,本次演示提交不可撤销。</text>
<text class="decision-desc submit-decision-desc">确认提交后,以下待拣商品会标记为“未拣出”,并记录所选原因。</text>
<view class="pending-sku-list">
<text v-for="item in pendingItems.slice(0, 3)" :key="item.id">{{ item.location ? `${item.sku} · ${item.location}` : item.sku }}</text>
<text v-if="pendingItems.length > 3">另有 {{ pendingItems.length - 3 }} 件</text>
@@ -293,7 +293,7 @@
</view>
<view class="modal-actions submit-decision-actions">
<button class="modal-cancel-btn" @tap="closeOverlay">继续拣货</button>
<button class="modal-confirm-btn danger-confirm" :disabled="!selectedIncompleteReason" @tap="confirmIncompleteSubmit">确认未拣并提交</button>
<button class="modal-confirm-btn danger-confirm" :disabled="submitting || !selectedIncompleteReason" @tap="confirmIncompleteSubmit">{{ submitting ? '正在提交…' : '确认未拣并提交' }}</button>
</view>
</view>
</uni-popup>
@@ -571,12 +571,15 @@
return
}
if (this._showRefreshPromise) return this._showRefreshPromise
this._showRefreshPromise = (async () => {
const showGeneration = this.taskRequestId
const refreshPromise = (async () => {
await this.refreshAvailableBoxes()
if (!this._pageVisible || showGeneration !== this.taskRequestId) return
await this.syncRemoteTask()
})()
try { await this._showRefreshPromise }
finally { this._showRefreshPromise = null }
this._showRefreshPromise = refreshPromise
try { await refreshPromise }
finally { if (this._showRefreshPromise === refreshPromise) this._showRefreshPromise = null }
},
onHide() {
this._pageVisible = false
@@ -584,10 +587,11 @@
this.evidencePreviewOpening = false
this.evidencePreviewReturning = true
}
if (!this.cameraPicking && !this.evidencePreviewReturning) this.invalidateTaskSync()
this.stopHardwareScanner()
},
onUnload() { this.stopHardwareScanner(); this.persistSession(); this.clearTimers() },
beforeUnmount() { this.stopHardwareScanner(); this.clearTimers() },
onUnload() { this._pageVisible = false; this.invalidateTaskSync(); this.evidenceContextId++; this.stopHardwareScanner(); this.persistSession(); this.clearTimers() },
beforeUnmount() { this._pageVisible = false; this.invalidateTaskSync(); this.stopHardwareScanner(); this.clearTimers() },
onBackPress() {
if (this.activeOverlay) { this.closeOverlay(); return true }
if (this.boxDropdownOpen) { this.boxDropdownOpen = false; return true }
@@ -595,6 +599,11 @@
return false
},
methods: {
invalidateTaskSync() { this.taskRequestId++; this.remoteLoading = false; this._showRefreshPromise = null },
isTaskSyncActive(requestId, taskId) {
return this._pageVisible === true && requestId === this.taskRequestId
&& String(this.remoteTaskId || this.currentBox.taskId) === String(taskId)
},
containerReference,
isSelectableTask(task) {
return Boolean(task?.recordStatus === 'ACTIVE'
@@ -1090,7 +1099,7 @@
return ''
},
async syncRemoteTask() {
if (!session.token) return
if (!session.token || !this._pageVisible) return
const requestedTaskId = this.remoteTaskId || this.currentBox.taskId
if (!requestedTaskId) { this.remoteError = '当前没有可进入的拣货任务,请返回任务列表刷新'; return }
this.rememberEvidenceDraft()
@@ -1114,7 +1123,7 @@
try {
const warehouseId = this.currentBox.warehouseId
const task = await getContainerPickingTask(selector, warehouseId)
if (requestId !== this.taskRequestId) return
if (!this.isTaskSyncActive(requestId, requestedTaskId)) return
if (!task) throw new Error('服务端未返回当前拣货任务')
if (this.isMergedContainerRecord(task)) {
this.clearMergedTask(task, previousIdentity)
@@ -1181,9 +1190,10 @@
if (remoteTask.workflowStatus !== 'AWAITING_REVIEW' && this.assignmentVersion === null) throw new Error('任务领取状态尚未同步,请刷新重试')
let claimed = null
if (remoteTask.workflowStatus === 'UNASSIGNED') {
if (!this.isTaskSyncActive(requestId, requestedTaskId)) return
const operationId = `claim-${session.currentUser?.id || 'operator'}-${this.remoteTaskId}-${task.loadingPlanId}-${task.containerBookingId}`
claimed = await claimContainerPickingTask(this.remoteTaskId, this.assignmentVersion, operationId)
if (requestId !== this.taskRequestId) return
if (!this.isTaskSyncActive(requestId, requestedTaskId)) return
} else if (remoteTask.workflowStatus === 'IN_PROGRESS') {
const assigneeId = remoteTask.assignment?.assigneeId
if (!assigneeId || String(assigneeId) !== String(session.currentUser?.id || '')) throw new Error('当前任务已由其他作业员领取,请返回任务列表刷新')
@@ -1215,9 +1225,10 @@
if (addedItemIds.length) this.showToast(`已同步 ${addedItemIds.length} 条新增待拣明细`, 'warning')
}
await this.loadLoadingEvidence()
if (!this.isTaskSyncActive(requestId, requestedTaskId)) return
if (this.loadingStageReady) this.activeStage = 'evidence'
} catch (error) {
if (requestId !== this.taskRequestId) return
if (!this.isTaskSyncActive(requestId, requestedTaskId)) return
this.debugSubmit('refresh.error', { requestId, message: error.message || '', code: error.code || '', statusCode: error.statusCode || '', requestIdFromServer: error.requestId || '', traceId: error.traceId || '' })
this.remoteError = error.network ? '网络不可用,无法开始拣货,恢复网络后请重试' : this.remoteFailureMessage(error, '任务同步失败')
} finally { if (requestId === this.taskRequestId) this.remoteLoading = false }
@@ -1675,10 +1686,11 @@
this.performSubmit()
},
confirmIncompleteSubmit() {
if (!this.selectedIncompleteReason) return
this.performSubmit(this.selectedIncompleteReason)
if (this.submitting || !this.selectedIncompleteReason) return
return this.performSubmit(this.selectedIncompleteReason)
},
async performSubmit(incompleteReason = '') {
if (this.submitting) return
const beforeSubmit = this.summary
const remoteRequired = Boolean(session.token)
this.remoteError = ''