This commit is contained in:
2026-08-06 13:56:01 +08:00
parent fb059e4691
commit 8a79cd02ca
6 changed files with 325 additions and 6 deletions
+107 -3
View File
@@ -117,13 +117,34 @@
</view>
</view>
<view class="loading-evidence-section" v-if="currentBox.inboundId">
<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>
</view>
<view class="evidence-grid">
<view v-for="slot in evidenceSlots" :key="slot.type" class="evidence-card" :class="{ filled: evidenceEntry(slot.type) }">
<text class="evidence-label">{{ slot.label }}</text>
<text v-if="evidenceEntry(slot.type)" class="evidence-file">{{ evidenceEntry(slot.type).fileName || '已记录' }}</text>
<button class="evidence-btn" @tap="chooseEvidence(slot)">{{ evidenceEntry(slot.type) ? '重新选择' : '上传照片' }}</button>
</view>
</view>
<view class="seal-row">
<input v-model="sealNo" class="seal-input" placeholder="输入封签号(可选)" maxlength="64" />
<button class="evidence-btn seal-btn" @tap="saveEvidence('SEAL')">保存封签</button>
</view>
<text v-if="remoteError" class="remote-error" role="alert">{{ remoteError }}</text>
<button class="loading-complete-btn" :disabled="submitting || loadingVersion === null" @tap="completeLoadingTask">确认装柜完成</button>
<text v-if="loadingVersion === null" class="version-hint">等待统一订单详情提供装柜版本号后可提交完成</text>
</view>
<view 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>
</view>
<button class="submit-btn" aria-label="提交拣货完成" :disabled="completedBoxes[currentBoxIndex] || !summary.canSubmit" @tap="submitPicking">
{{ submitButtonText }}
<button class="submit-btn" aria-label="提交拣货完成" :disabled="submitting || completedBoxes[currentBoxIndex] || !summary.canSubmit" @tap="submitPicking">
{{ submitting ? '正在提交…' : submitButtonText }}
</button>
</view>
</view>
@@ -296,6 +317,7 @@
import { navigateBackOr } from '@/utils/navigation.js'
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
import { completeLoading, completePicking, getEvidence, getPickingTask, isRemoteReady, normalizePickingTask, upsertEvidence } from '@/api/pda-loading.js'
import ScanTargetInput from '@/components/ScanTargetInput.vue'
import PopupHeader from '@/components/PopupHeader.vue'
@@ -320,6 +342,12 @@
switchTargetIndex: null,
selectedIncompleteReason: '',
completionSummary: { picked: 0, partial: 0, skipped: 0, autoSkipped: 0 },
evidenceSlots: [
{ type: 'PHOTO_STEP_1', label: '装柜照片 1' }, { type: 'PHOTO_STEP_2', label: '装柜照片 2' },
{ type: 'PHOTO_STEP_3', label: '装柜照片 3' }, { type: 'PHOTO_STEP_4', label: '装柜照片 4' }
],
evidenceFiles: {}, sealNo: '', loadingVersion: null, pickingVersion: null, remotePick: null,
remoteError: '', remoteLoading: false, submitting: false,
toast: { visible: false, message: '', type: '' }
}
},
@@ -394,12 +422,20 @@
.filter((entry) => this.boxListType === 'done'
? Boolean(this.completedBoxes[entry.index])
: !this.completedBoxes[entry.index])
},
remoteSyncState() {
if (!this.currentBox.inboundId) return { label: '演示任务', tone: 'local' }
if (!isRemoteReady(this.currentBox.inboundId)) return { label: '未连接', tone: 'error' }
if (this.remoteLoading) return { label: '同步中', tone: 'loading' }
if (this.remoteError) return { label: '同步失败', tone: 'error' }
return { label: this.remotePick ? '已同步' : '待同步', tone: this.remotePick ? 'ready' : 'local' }
}
},
onLoad() { requireSession() },
onShow() {
if (!requireSession()) return
this.startHardwareScanner()
this.syncRemoteTask()
},
onHide() { this.stopHardwareScanner() },
onUnload() { this.stopHardwareScanner(); this.persistSession(); this.clearTimers() },
@@ -550,6 +586,50 @@
playSuccessSound()
this.pickItem(result.item.id, 'scan')
},
evidenceEntry(type) { return this.evidenceFiles[type] || null },
async syncRemoteTask() {
if (!isRemoteReady(this.currentBox.inboundId)) return
this.remoteLoading = true
this.remoteError = ''
try {
this.remotePick = normalizePickingTask(await getPickingTask(this.currentBox.inboundId))
this.pickingVersion = this.currentBox.pickingVersion ?? this.remotePick.pickingVersion
this.loadingVersion = this.currentBox.inboundVersion ?? this.remotePick.inboundVersion ?? null
const evidence = await getEvidence(this.currentBox.inboundId)
this.evidenceFiles = (evidence && evidence.evidences ? evidence.evidences : []).reduce((all, item) => { all[item.evidenceType] = item; return all }, {})
const seal = this.evidenceFiles.SEAL
if (seal) this.sealNo = seal.sealNo || ''
} catch (error) {
this.remoteError = error.network ? '网络不可用,当前保留本地草稿,恢复网络后可重试' : (error.message || '任务同步失败')
} finally { this.remoteLoading = false }
},
chooseEvidence(slot) {
if (typeof uni.chooseImage !== 'function') { this.showToast('当前环境不支持选择照片', 'warning'); return }
uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['camera', 'album'], success: (result) => {
const path = result.tempFilePaths && result.tempFilePaths[0]
if (!path) return
this.evidenceFiles = { ...this.evidenceFiles, [slot.type]: { ...(this.evidenceFiles[slot.type] || {}), evidenceType: slot.type, fileName: path, localPath: path } }
this.saveEvidence(slot.type)
}, fail: () => this.showToast('未选择照片,可稍后重试', 'warning') })
},
async saveEvidence(type) {
if (!this.currentBox.inboundId) { this.showToast('演示任务已记录本地证据', 'success'); return }
if (!isRemoteReady(this.currentBox.inboundId)) { this.showToast('当前会话未连接服务端,证据保留本地', 'warning'); return }
const current = this.evidenceFiles[type] || {}
const payload = { expectedVersion: current.version || 0, evidenceType: type, fileId: current.fileId || null, fileName: current.fileName || null, sealNo: type === 'SEAL' ? this.sealNo : null, remark: null }
try {
const result = await upsertEvidence(this.currentBox.inboundId, payload)
this.evidenceFiles = (result && result.evidences ? result.evidences : []).reduce((all, item) => { all[item.evidenceType] = item; return all }, this.evidenceFiles)
this.showToast(`${type === 'SEAL' ? '封签' : '照片'}已保存`, 'success')
} catch (error) { this.remoteError = error.network ? '网络失败,证据未提交,请重试' : (error.message || '证据保存失败'); this.showToast('证据保存失败', 'error') }
},
async completeLoadingTask() {
if (!isRemoteReady(this.currentBox.inboundId) || this.loadingVersion === null) return
this.submitting = true
try { await completeLoading(this.currentBox.inboundId, { expectedVersion: this.loadingVersion, remark: 'PDA确认装柜完成' }); this.showToast('已确认装柜完成', 'success') }
catch (error) { this.remoteError = error.network ? '网络失败,装柜完成未提交' : (error.message || '装柜完成失败'); this.showToast('装柜完成失败', 'error') }
finally { this.submitting = false }
},
pickItem(id, source = 'manual') {
const item = this.currentBox.items.find((entry) => entry.id === id)
this.itemStates = { ...this.itemStates, [id]: ITEM_STATUS.PICKED }
@@ -648,8 +728,32 @@
if (!this.selectedIncompleteReason) return
this.performSubmit(this.selectedIncompleteReason)
},
performSubmit(incompleteReason = '') {
async performSubmit(incompleteReason = '') {
const beforeSubmit = this.summary
if (this.currentBox.inboundId && !isRemoteReady(this.currentBox.inboundId)) {
this.remoteError = '缺少服务端会话或 tenantId,未提交拣货完成'
this.showToast('拣货完成未提交', 'error')
return
}
if (this.currentBox.inboundId && isRemoteReady(this.currentBox.inboundId)) {
this.submitting = true
try {
if (this.pickingVersion === null) throw new Error('缺少拣货版本号 pickingVersion,无法提交')
const expectedCount = this.currentBox.items.reduce((total, item) => total + Number(item.qty || 0), 0)
const pickedCount = this.currentBox.items.reduce((total, item) => {
const status = this.itemState(item)
return total + (status === ITEM_STATUS.PICKED ? Number(item.qty || 0) : status === ITEM_STATUS.PARTIAL ? Number(this.partialQuantities[item.id] || 0) : 0)
}, 0)
this.remotePick = await completePicking(this.currentBox.inboundId, {
expectedVersion: Number(this.pickingVersion),
expectedCount, pickedCount, remark: incompleteReason || null
})
} catch (error) {
this.remoteError = error.network ? '网络失败,拣货完成未提交,请重试' : (error.message || '拣货完成失败')
this.showToast('拣货完成未提交', 'error')
return
} finally { this.submitting = false }
}
const result = submitStates(this.currentBox.items, this.itemStates)
this.itemStates = result.states
this.currentBox.items.forEach((item) => {