优化
This commit is contained in:
+107
-3
@@ -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) => {
|
||||
|
||||
Reference in New Issue
Block a user