优化
This commit is contained in:
+4
-9
@@ -31,7 +31,6 @@ function request(options) {
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
debugLog('api.request', { method: options.method || 'GET', url: options.url })
|
||||
uni.request({
|
||||
...options,
|
||||
url: `${baseUrl}${options.url}`,
|
||||
@@ -39,12 +38,7 @@ function request(options) {
|
||||
success(response) {
|
||||
const body = response.data || {}
|
||||
const code = String(body.code ?? response.statusCode)
|
||||
debugLog('api.response', {
|
||||
method: options.method || 'GET', url: options.url, statusCode: response.statusCode, code,
|
||||
message: body.message || '', requestId: body.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id'] || '',
|
||||
traceId: body.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id'] || '',
|
||||
dataKeys: body.data && typeof body.data === 'object' ? Object.keys(body.data) : []
|
||||
})
|
||||
|
||||
if (code !== '200') {
|
||||
const error = new Error(body.message || '服务端请求失败')
|
||||
error.code = body.code || response.statusCode
|
||||
@@ -57,7 +51,6 @@ function request(options) {
|
||||
resolve(body.data)
|
||||
},
|
||||
fail(error) {
|
||||
debugLog('api.network-error', { method: options.method || 'GET', url: options.url, errMsg: error.errMsg || '' })
|
||||
const next = new Error(error.errMsg || '网络连接失败')
|
||||
next.network = true
|
||||
reject(next)
|
||||
@@ -289,6 +282,7 @@ export function uploadEvidenceFile(inboundId, evidenceType, localPath, tenantId,
|
||||
const headers = {}
|
||||
if (session.token) headers.Authorization = `Bearer ${session.token}`
|
||||
if (session.requestId) headers['X-Request-Id'] = session.requestId
|
||||
debugLog('evidence.upload.start', { inboundId, evidenceType, tenantId: tenant, hasLocalPath: Boolean(localPath) })
|
||||
return new Promise((resolve, reject) => {
|
||||
const task = uni.uploadFile({
|
||||
url: `${baseUrl}/files/loading-evidence/${encodeURIComponent(tenant)}/${encodeURIComponent(inboundId)}?evidenceType=${encodeURIComponent(evidenceType)}`,
|
||||
@@ -299,10 +293,11 @@ export function uploadEvidenceFile(inboundId, evidenceType, localPath, tenantId,
|
||||
let body = response.data
|
||||
try { body = typeof body === 'string' ? JSON.parse(body) : body } catch (_) { /* handled below */ }
|
||||
const code = String(body?.code ?? response.statusCode)
|
||||
debugLog('evidence.upload.response', { inboundId, evidenceType, statusCode: response.statusCode, code, message: body?.message || '', requestId: body?.requestId || response.header?.['X-Request-Id'] || response.header?.['x-request-id'] || '', traceId: body?.traceId || response.header?.['X-Trace-Id'] || response.header?.['x-trace-id'] || '', fileId: body?.data?.fileId || null })
|
||||
if (code !== '200') { const error = new Error(body?.message || '文件上传失败'); error.code = body?.code || response.statusCode; reject(error); return }
|
||||
resolve(body?.data || body)
|
||||
},
|
||||
fail(error) { const next = new Error(error.errMsg || '网络连接失败'); next.network = true; reject(next) }
|
||||
fail(error) { debugLog('evidence.upload.error', { inboundId, evidenceType, message: error.errMsg || '' }); const next = new Error(error.errMsg || '网络连接失败'); next.network = true; reject(next) }
|
||||
})
|
||||
if (task && typeof task.onProgressUpdate === 'function' && typeof onProgress === 'function') task.onProgressUpdate(({ progress }) => onProgress(progress))
|
||||
})
|
||||
|
||||
@@ -375,7 +375,7 @@
|
||||
selectedIncompleteReason: '',
|
||||
completionSummary: { picked: 0, partial: 0, skipped: 0, autoSkipped: 0 },
|
||||
evidenceSlots: EVIDENCE_SLOTS,
|
||||
evidenceFiles: {}, evidenceQueueItems: [], evidenceQueue: null, evidenceContextId: 0, evidenceIdentity: null, sealNo: '', loadingVersion: null, pickingVersion: null, remotePick: null,
|
||||
evidenceFiles: {}, evidenceQueueItems: [], evidenceQueue: null, evidenceContextId: 0, evidenceIdentity: null, cameraPicking: false, sealNo: '', loadingVersion: null, pickingVersion: null, remotePick: null,
|
||||
remoteTaskId: null, assignmentVersion: null, submitId: '',
|
||||
taskRequestId: 0,
|
||||
remoteError: '', remoteLoading: false, submitting: false,
|
||||
@@ -500,6 +500,10 @@
|
||||
onShow() {
|
||||
if (!requireSession()) return
|
||||
this.startHardwareScanner()
|
||||
if (this.cameraPicking) {
|
||||
this.debugEvidence('choose.resume', { evidenceIdentity: this.evidenceIdentity })
|
||||
return
|
||||
}
|
||||
this.syncRemoteTask()
|
||||
},
|
||||
onHide() { this.stopHardwareScanner() },
|
||||
@@ -512,6 +516,9 @@
|
||||
return false
|
||||
},
|
||||
methods: {
|
||||
debugEvidence(event, payload = {}) {
|
||||
try { console.info(`[PDA] evidence.${event}`, payload) } catch (_) { /* console may be unavailable on a device */ }
|
||||
},
|
||||
debugSubmit(event, payload = {}) {
|
||||
try {
|
||||
console.info('[PDA] picking.submit', {
|
||||
@@ -743,6 +750,10 @@
|
||||
this.pickScannedBox(result.item, value)
|
||||
},
|
||||
evidenceEntries(type) { return Array.isArray(this.evidenceFiles[type]) ? this.evidenceFiles[type].filter((item) => item.status !== 'VOID' && item.status !== 'VOIDED') : (this.evidenceFiles[type] ? [this.evidenceFiles[type]] : []) },
|
||||
evidenceItems(result) {
|
||||
if (Array.isArray(result?.evidences)) return result.evidences
|
||||
return Array.isArray(result?.evidences?.evidences) ? result.evidences.evidences : []
|
||||
},
|
||||
validEvidenceEntries(type) { return this.evidenceEntries(type).filter((item) => item.id && item.fileId && item.uploadStatus !== 'PENDING') },
|
||||
evidenceEntry(type) { const list = this.evidenceEntries(type); return list.length ? list[list.length - 1] : null },
|
||||
evidenceQueueEntry(type) {
|
||||
@@ -869,20 +880,28 @@
|
||||
return this.evidenceEntry(type) ? '继续拍照' : '拍照'
|
||||
},
|
||||
chooseEvidence(slot) {
|
||||
if (!this.requireEvidenceStage()) return
|
||||
if (!isOnline()) { this.showToast('当前网络不可用,请联网后再拍照上传', 'warning'); return }
|
||||
if (typeof uni.chooseImage !== 'function') { this.showToast('当前环境不支持选择照片', 'warning'); return }
|
||||
const evidenceType = slot?.type || null
|
||||
this.debugEvidence('choose.start', { evidenceType, evidenceIdentity: this.evidenceIdentity, loadingStageReady: this.loadingStageReady, hasChooseImage: typeof uni.chooseImage === 'function' })
|
||||
if (!this.requireEvidenceStage()) { this.debugEvidence('choose.blocked', { evidenceType, reason: 'stage-not-ready', evidenceIdentity: this.evidenceIdentity, loadingVersion: this.loadingVersion }); return }
|
||||
if (!isOnline()) { this.debugEvidence('choose.blocked', { evidenceType, reason: 'offline' }); this.showToast('当前网络不可用,请联网后再拍照上传', 'warning'); return }
|
||||
if (typeof uni.chooseImage !== 'function') { this.debugEvidence('choose.blocked', { evidenceType, reason: 'chooseImage-unavailable' }); this.showToast('当前环境不支持选择照片', 'warning'); return }
|
||||
const contextId = this.evidenceContextId
|
||||
const identity = { ...this.evidenceIdentity }
|
||||
this.cameraPicking = true
|
||||
this.debugEvidence('choose.open-camera', { evidenceType, contextId, evidenceIdentity: identity })
|
||||
uni.chooseImage({ count: 1, sizeType: ['compressed'], sourceType: ['camera'], success: (result) => {
|
||||
this.cameraPicking = false
|
||||
if (!this.isEvidenceContextActive(contextId, identity)) return
|
||||
const path = result.tempFilePaths && result.tempFilePaths[0]
|
||||
if (!path) return
|
||||
if (!path) { this.debugEvidence('camera.empty', { evidenceType: slot.type, evidenceIdentity: identity }); return }
|
||||
this.debugEvidence('camera.selected', { evidenceType: slot.type, evidenceIdentity: identity })
|
||||
const next = { evidenceType: slot.type, fileName: path.split('/').pop() || path, localPath: path, uploadStatus: 'PENDING' }
|
||||
this.evidenceFiles = { ...this.evidenceFiles, [slot.type]: [...(this.evidenceFiles[slot.type] || []), next] }
|
||||
this.queueEvidenceUpload(slot.type, path)
|
||||
}, fail: () => {
|
||||
}, fail: (error) => {
|
||||
this.cameraPicking = false
|
||||
if (!this.isEvidenceContextActive(contextId, identity)) return
|
||||
this.debugEvidence('camera.error', { evidenceType: slot.type, evidenceIdentity: identity, message: error?.errMsg || '' })
|
||||
this.showToast('未选择照片,可稍后重试', 'warning')
|
||||
} })
|
||||
},
|
||||
@@ -893,9 +912,11 @@
|
||||
const identity = { ...this.evidenceIdentity }
|
||||
const queue = this.evidenceQueue
|
||||
const entry = queue.add({ ...identity, tenantId: this.currentBox.tenantId, evidenceType: type, localPath: path })
|
||||
this.debugSubmit('evidence.queue.start', { evidenceType: type, queueId: entry.id, evidenceIdentity: identity })
|
||||
await queue.flush()
|
||||
if (!this.isEvidenceContextActive(contextId, identity) || queue !== this.evidenceQueue) return
|
||||
const latest = queue.snapshot().find((item) => item.id === entry.id)
|
||||
this.debugSubmit('evidence.queue.result', { evidenceType: type, queueId: entry.id, status: latest?.status || 'missing', fileId: latest?.result?.fileId || null, error: latest?.error || '' })
|
||||
if (latest?.status !== 'SUCCEEDED') { this.remoteError = latest?.error || '文件上传失败'; return }
|
||||
const current = this.evidenceEntry(type) || {}
|
||||
this.evidenceFiles = { ...this.evidenceFiles, [type]: [...(this.evidenceFiles[type] || []).slice(0, -1), { ...current, fileId: latest.result?.fileId, fileName: latest.result?.fileName || current.fileName, uploadStatus: 'SUCCEEDED' }] }
|
||||
@@ -928,10 +949,12 @@
|
||||
const current = this.evidenceEntry(type) || {}
|
||||
if (!current.fileId) { this.showToast('照片尚未上传完成', 'warning'); return }
|
||||
const payload = { expectedVersion: current.version || 0, evidenceType: type, fileId: current.fileId || null, fileName: current.fileName || null, sealNo: type === 'SEAL' ? this.sealNo : null, remark: null, operationId: operationId || `evidence-${identity.taskId || identity.inboundId}-${type}-${current.fileId || 'seal'}` }
|
||||
this.debugSubmit('evidence.bind.start', { evidenceType: type, fileId: current.fileId, expectedVersion: payload.expectedVersion, evidenceIdentity: identity })
|
||||
try {
|
||||
const result = await upsertEvidence(inboundId, payload, tenantId)
|
||||
if (!this.isEvidenceContextActive(contextId, identity)) return
|
||||
this.evidenceFiles = (result && result.evidences ? result.evidences : []).reduce((all, item) => { const list = (all[item.evidenceType] || []).filter((entry) => entry.id || entry.fileId !== item.fileId); const index = list.findIndex((entry) => entry.id === item.id); all[item.evidenceType] = index >= 0 ? list.map((entry, i) => i === index ? item : entry) : [...list, item]; return all }, this.evidenceFiles)
|
||||
this.debugSubmit('evidence.bind.success', { evidenceType: type, fileId: current.fileId, evidenceCount: result?.evidences?.length || 0, evidenceIdentity: identity })
|
||||
this.evidenceFiles = this.evidenceItems(result).reduce((all, item) => { const list = (all[item.evidenceType] || []).filter((entry) => entry.id || entry.fileId !== item.fileId); const index = list.findIndex((entry) => entry.id === item.id); all[item.evidenceType] = index >= 0 ? list.map((entry, i) => i === index ? item : entry) : [...list, item]; return all }, this.evidenceFiles)
|
||||
this.showToast(`${type === 'SEAL' ? '封签' : '照片'}已保存`, 'success')
|
||||
} catch (error) {
|
||||
if (!this.isEvidenceContextActive(contextId, identity)) return
|
||||
|
||||
@@ -61,6 +61,11 @@ test('picking page exposes five evidence slots, retry and remote completion stat
|
||||
assert.match(page, /evidenceComplete/)
|
||||
})
|
||||
|
||||
test('PDA evidence binding unwraps the nested loading action response before rendering saved files', () => {
|
||||
assert.match(page, /evidenceItems\(result\)[\s\S]*?Array\.isArray\(result\?\.evidences\?\.evidences\)[\s\S]*?result\.evidences\.evidences/)
|
||||
assert.match(page, /this\.evidenceFiles = this\.evidenceItems\(result\)\.reduce/)
|
||||
})
|
||||
|
||||
test('PDA cabinet picking adapter claims online and submits one atomic batch', () => {
|
||||
assert.match(source, /\/first-leg\/pda-loading\/tasks\/pending/)
|
||||
assert.match(source, /\/first-leg\/pda-loading\/tasks\?/)
|
||||
|
||||
@@ -45,7 +45,7 @@ test('evidence mutations defensively reject calls before picking completion', ()
|
||||
assert.match(page, /requireEvidenceStage\(\)[\s\S]*?if \(!this\.pickingCompleted\)[\s\S]*?请先提交拣货完成[\s\S]*?if \(!this\.loadingStageReady\)/)
|
||||
for (const method of ['chooseEvidence', 'queueEvidenceUpload', 'retryEvidence', 'saveEvidence', 'saveSealNo', 'voidEvidencePhoto', 'completeLoadingTask']) {
|
||||
const body = page.match(new RegExp(`(?:async )?${method}\\([^)]*\\) \\{[\\s\\S]*?\\n\\t\\t\\t\\},`))?.[0] || ''
|
||||
assert.match(body, /if \(!this\.requireEvidenceStage\(\)\) return/, `${method} should guard the evidence stage`)
|
||||
assert.match(body, /if \(!this\.requireEvidenceStage\(\)\)(?: return| \{)/, `${method} should guard the evidence stage`)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -69,6 +69,15 @@ test('camera callbacks cannot append evidence after switching cabinets', () => {
|
||||
assert.match(chooser, /isEvidenceContextActive\(contextId, identity\)[\s\S]*?this\.evidenceFiles =/)
|
||||
})
|
||||
|
||||
test('returning from the camera does not refresh and invalidate the active evidence context', () => {
|
||||
assert.match(page, /cameraPicking: false/)
|
||||
assert.match(page, /onShow\(\) \{[\s\S]*?if \(this\.cameraPicking\)[\s\S]*?choose\.resume[\s\S]*?return[\s\S]*?this\.syncRemoteTask\(\)/)
|
||||
const chooser = page.match(/chooseEvidence\(slot\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||
assert.match(chooser, /this\.cameraPicking = true/)
|
||||
assert.match(chooser, /success: \(result\) => \{[\s\S]*?this\.cameraPicking = false/)
|
||||
assert.match(chooser, /fail: \(error\) => \{[\s\S]*?this\.cameraPicking = false/)
|
||||
})
|
||||
|
||||
test('evidence save, void and loading completion ignore stale cabinet responses', () => {
|
||||
for (const [method, awaitedCall] of [
|
||||
['saveEvidence', 'upsertEvidence'],
|
||||
|
||||
Reference in New Issue
Block a user