This commit is contained in:
2026-08-27 14:53:44 +08:00
parent a493a7faeb
commit c06b808354
3 changed files with 62 additions and 7 deletions
+27 -5
View File
@@ -347,7 +347,7 @@
import { ITEM_STATUS, classifyScan, createItemStates, getPickSummary, submitStates } from '@/utils/picking-state.js' import { ITEM_STATUS, classifyScan, createItemStates, getPickSummary, submitStates } from '@/utils/picking-state.js'
import { SKIP_REASONS, INCOMPLETE_REASONS } from '@/data/demo-data.js' import { SKIP_REASONS, INCOMPLETE_REASONS } from '@/data/demo-data.js'
import { session, requireSession } from '@/stores/session.js' import { session, requireSession } from '@/stores/session.js'
import { clearPickingDraft, getPickingSession, restorePickingSession, savePickingSession } from '@/stores/picking.js' import { clearLoadingCompletionAttempt, clearPickingDraft, getPickingSession, restoreLoadingCompletionAttempt, restorePickingSession, saveLoadingCompletionAttempt, savePickingSession } from '@/stores/picking.js'
import { navigateBackOr } from '@/utils/navigation.js' import { navigateBackOr } from '@/utils/navigation.js'
import { startScanBroadcast } from '@/utils/scan-broadcast.js' import { startScanBroadcast } from '@/utils/scan-broadcast.js'
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js' import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
@@ -356,6 +356,7 @@
import { isOnline } from '@/utils/network.js' import { isOnline } from '@/utils/network.js'
import { stableOperationId } from '@/utils/idempotency.js' import { stableOperationId } from '@/utils/idempotency.js'
import { stableTaskOperationIdentity } from '@/utils/task-identity.js' import { stableTaskOperationIdentity } from '@/utils/task-identity.js'
import { COMPLETION_PHASE, resolveCompletionPhase, shouldReconcileCompletion } from '@/utils/loading-completion-state.js'
import ScanTargetInput from '@/components/ScanTargetInput.vue' import ScanTargetInput from '@/components/ScanTargetInput.vue'
import PopupHeader from '@/components/PopupHeader.vue' import PopupHeader from '@/components/PopupHeader.vue'
@@ -365,7 +366,11 @@
const workflowStatus = task?.workflowStatus const workflowStatus = task?.workflowStatus
const taskStatus = task?.taskStatus ?? task?.status const taskStatus = task?.taskStatus ?? task?.status
return workflowStatus === 'AWAITING_REVIEW' return workflowStatus === 'AWAITING_REVIEW'
|| workflowStatus === 'PROCESSING'
|| workflowStatus === 'COMPLETION_FAILED'
|| workflowStatus === 'COMPLETED' || workflowStatus === 'COMPLETED'
|| taskStatus === 'COMPLETING'
|| taskStatus === 'COMPLETION_FAILED'
|| taskStatus === 'COMPLETED' || taskStatus === 'COMPLETED'
} }
@@ -1390,29 +1395,46 @@
if (!this.evidenceComplete) { this.showToast('五类有效照片和封签号均为必填', 'warning'); return } if (!this.evidenceComplete) { this.showToast('五类有效照片和封签号均为必填', 'warning'); return }
const contextId = this.evidenceContextId const contextId = this.evidenceContextId
const identity = { ...this.evidenceIdentity } const identity = { ...this.evidenceIdentity }
const expectedTaskVersion = Number(this.pickingVersion) const savedAttempt = restoreLoadingCompletionAttempt(this.draftIdentity())
const expectedTaskVersion = savedAttempt?.expectedTaskVersion ?? Number(this.pickingVersion)
const operationIdentity = stableTaskOperationIdentity(this.draftIdentity()) const operationIdentity = stableTaskOperationIdentity(this.draftIdentity())
const operationId = stableOperationId('complete', operationIdentity, { const operationId = savedAttempt?.operationId || stableOperationId('complete', operationIdentity, {
taskId: this.remoteTaskId, taskId: this.remoteTaskId,
expectedTaskVersion, expectedTaskVersion,
loadingPlanId: identity.loadingPlanId, loadingPlanId: identity.loadingPlanId,
containerBookingId: identity.containerBookingId containerBookingId: identity.containerBookingId
}) })
const completionAttempt = { operationId, expectedTaskVersion, remark: savedAttempt?.remark || 'PDA确认装柜完成' }
saveLoadingCompletionAttempt(this.draftIdentity(), completionAttempt)
this.submitting = true this.submitting = true
try { try {
const completedTask = await completeContainerPickingTask(this.remoteTaskId, { const completedTask = await completeContainerPickingTask(this.remoteTaskId, {
expectedTaskVersion, expectedTaskVersion,
operationId, operationId,
remark: 'PDA确认装柜完成' remark: completionAttempt.remark
}) })
if (!this.isEvidenceContextActive(contextId, identity)) return if (!this.isEvidenceContextActive(contextId, identity)) return
if (completedTask) { if (completedTask) {
this.remotePick = normalizePickingTask({ ...this.remotePick, ...completedTask }) this.remotePick = normalizePickingTask({ ...this.remotePick, ...completedTask })
this.pickingVersion = this.remotePick.pickingVersion ?? this.remotePick.version ?? this.pickingVersion this.pickingVersion = this.remotePick.pickingVersion ?? this.remotePick.version ?? this.pickingVersion
} }
this.showToast('已确认装柜完成', 'success') const phase = resolveCompletionPhase({ taskStatus: completedTask?.taskStatus, commandStatus: completedTask?.commandStatus })
if (phase === COMPLETION_PHASE.PROCESSING) {
this.showToast('完成命令已受理,后台处理中', 'success')
this.persistSession()
navigateBackOr('/pages/tasks/tasks')
return
}
if (phase === COMPLETION_PHASE.COMPLETED) clearLoadingCompletionAttempt(this.draftIdentity())
this.showToast('装柜已完成', 'success')
} catch (error) { } catch (error) {
if (!this.isEvidenceContextActive(contextId, identity)) return if (!this.isEvidenceContextActive(contextId, identity)) return
if (shouldReconcileCompletion(error)) {
this.remoteError = '提交结果待确认,请返回任务列表刷新'
this.showToast('提交结果待确认', 'warning')
navigateBackOr('/pages/tasks/tasks')
return
}
if (this.isTaskConflict(error)) { if (this.isTaskConflict(error)) {
await this.refreshAfterTaskConflict(error.message || '订舱、装柜关联或证据状态已变化,请刷新后重试') await this.refreshAfterTaskConflict(error.message || '订舱、装柜关联或证据状态已变化,请刷新后重试')
return return
+6 -2
View File
@@ -46,11 +46,14 @@
onUnload() { if (this._stopNetwork) this._stopNetwork(); clearTimeout(this._toastTimer) }, onUnload() { if (this._stopNetwork) this._stopNetwork(); clearTimeout(this._toastTimer) },
methods: { methods: {
statusLabel(status) { statusLabel(status) {
if (status === 'PROCESSING') return '完成处理中'
if (status === 'COMPLETION_FAILED') return '完成失败'
if (status === 'AWAITING_REVIEW') return '待上传证据/确认装柜' if (status === 'AWAITING_REVIEW') return '待上传证据/确认装柜'
return status === 'IN_PROGRESS' ? '拣货中' : '待领取' return status === 'IN_PROGRESS' ? '拣货中' : '待领取'
}, },
taskActionLabel(task) { taskActionLabel(task) {
if (!this.isTaskReady(task)) return '任务暂不可进入' if (!this.isTaskReady(task)) return '任务暂不可进入'
if (task.workflowStatus === 'COMPLETION_FAILED') return '重试完成'
return task.workflowStatus === 'AWAITING_REVIEW' ? '继续装柜取证' : '进入并开始拣货' return task.workflowStatus === 'AWAITING_REVIEW' ? '继续装柜取证' : '进入并开始拣货'
}, },
isMergedTask(task) { return task?.recordStatus === 'MERGED' || Boolean(task?.mergedIntoContainerBookingId) }, isMergedTask(task) { return task?.recordStatus === 'MERGED' || Boolean(task?.mergedIntoContainerBookingId) },
@@ -58,7 +61,8 @@
const operatorId = session.currentUser?.id || session.currentUser?.userId || session.currentUser?.accountId const operatorId = session.currentUser?.id || session.currentUser?.userId || session.currentUser?.accountId
clearPickingDraft({ operatorId, taskId: task.taskId, loadingPlanId: task.loadingPlanId, containerBookingId: task.containerBookingId, warehouseId: task.warehouseId }) clearPickingDraft({ operatorId, taskId: task.taskId, loadingPlanId: task.loadingPlanId, containerBookingId: task.containerBookingId, warehouseId: task.warehouseId })
}, },
isTaskReady(task) { return Boolean(task.recordStatus === 'ACTIVE' && !task.mergedIntoContainerBookingId && task.taskId && task.loadingPlanId && task.containerBookingId && task.cabinetNo && task.currentContainerNo && task.warehouseId && task.version !== null && task.version !== undefined && Number.isInteger(Number(task.version)) && (task.workflowStatus === 'AWAITING_REVIEW' || (task.assignmentVersion !== null && task.assignmentVersion !== undefined && Number.isInteger(Number(task.assignmentVersion)))) && ['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW'].includes(task.workflowStatus)) }, isTaskReady(task) { return Boolean(task.recordStatus === 'ACTIVE' && !task.mergedIntoContainerBookingId && task.taskId && task.loadingPlanId && task.containerBookingId && task.cabinetNo && task.currentContainerNo && task.warehouseId && task.version !== null && task.version !== undefined && Number.isInteger(Number(task.version)) && (['AWAITING_REVIEW', 'COMPLETION_FAILED'].includes(task.workflowStatus) || (task.assignmentVersion !== null && task.assignmentVersion !== undefined && Number.isInteger(Number(task.assignmentVersion)))) && ['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'COMPLETION_FAILED'].includes(task.workflowStatus)) },
isTaskVisible(task) { return Boolean(task.recordStatus === 'ACTIVE' && !task.mergedIntoContainerBookingId && task.taskId && ['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW', 'PROCESSING', 'COMPLETION_FAILED'].includes(task.workflowStatus)) },
async loadTasks() { async loadTasks() {
if (!isOnline()) { this.error = '当前网络不可用,请联网后刷新'; return } if (!isOnline()) { this.error = '当前网络不可用,请联网后刷新'; return }
this.loading = true; this.error = ''; this.notice = '' this.loading = true; this.error = ''; this.notice = ''
@@ -66,7 +70,7 @@
const result = await listLoadingOrders({ page: 1, size: 50 }) const result = await listLoadingOrders({ page: 1, size: 50 })
const records = result.records || [] const records = result.records || []
records.filter((task) => this.isMergedTask(task)).forEach((task) => this.clearMergedTaskDraft(task)) records.filter((task) => this.isMergedTask(task)).forEach((task) => this.clearMergedTaskDraft(task))
this.tasks = records.filter((task) => this.isTaskReady(task)) this.tasks = records.filter((task) => this.isTaskVisible(task))
if (records.length > this.tasks.length) this.notice = '部分任务暂不可作业,请稍后刷新' if (records.length > this.tasks.length) this.notice = '部分任务暂不可作业,请稍后刷新'
} catch (error) { } catch (error) {
this.error = error.network ? '当前网络不可用,请联网后刷新' : (error.message || '任务加载失败') this.error = error.network ? '当前网络不可用,请联网后刷新' : (error.message || '任务加载失败')
+29
View File
@@ -19,6 +19,7 @@ function createInitialState() {
let state = createInitialState() let state = createInitialState()
const STORAGE_PREFIX = 'pda-picking-draft' const STORAGE_PREFIX = 'pda-picking-draft'
const COMPLETION_PREFIX = 'pda-loading-completion'
function storageAvailable() { function storageAvailable() {
return typeof uni !== 'undefined' && typeof uni.getStorageSync === 'function' && typeof uni.setStorageSync === 'function' return typeof uni !== 'undefined' && typeof uni.getStorageSync === 'function' && typeof uni.setStorageSync === 'function'
@@ -80,6 +81,34 @@ export function clearPickingDraft(identity = {}) {
if (key && typeof uni !== 'undefined' && typeof uni.removeStorageSync === 'function') uni.removeStorageSync(key) if (key && typeof uni !== 'undefined' && typeof uni.removeStorageSync === 'function') uni.removeStorageSync(key)
} }
function completionKey(identity = {}) {
const identityKey = stableTaskIdentityKey(identity)
return identityKey ? `${COMPLETION_PREFIX}:${identityKey}` : ''
}
export function saveLoadingCompletionAttempt(identity = {}, attempt = {}) {
const key = completionKey(identity)
if (!key || !storageAvailable() || !attempt.operationId) return
uni.setStorageSync(key, {
operationId: String(attempt.operationId),
expectedTaskVersion: Number(attempt.expectedTaskVersion),
remark: attempt.remark || '',
updatedAt: new Date().toISOString()
})
}
export function restoreLoadingCompletionAttempt(identity = {}) {
const key = completionKey(identity)
if (!key || !storageAvailable()) return null
const saved = uni.getStorageSync(key)
return saved?.operationId && Number.isInteger(Number(saved.expectedTaskVersion)) ? clone(saved) : null
}
export function clearLoadingCompletionAttempt(identity = {}) {
const key = completionKey(identity)
if (key && typeof uni !== 'undefined' && typeof uni.removeStorageSync === 'function') uni.removeStorageSync(key)
}
export function resetPickingSession() { export function resetPickingSession() {
state = createInitialState() state = createInitialState()
} }