From 59e81516a486f4ba6608b2bd92a64c723389cbf7 Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Mon, 26 Oct 2026 15:51:10 +0800
Subject: [PATCH] fix(pda): simplify dispatch review and loading confirmation
---
src/components/DispatchLoadingDocuments.vue | 6 +--
src/pages/dispatch/dispatch.vue | 43 +++++++++++++--------
tests/warehouse-transfer-api.test.mjs | 5 ++-
tests/warehouse-transfer-counting.test.mjs | 7 ++--
4 files changed, 35 insertions(+), 26 deletions(-)
diff --git a/src/components/DispatchLoadingDocuments.vue b/src/components/DispatchLoadingDocuments.vue
index 3db4448..d42babd 100644
--- a/src/components/DispatchLoadingDocuments.vue
+++ b/src/components/DispatchLoadingDocuments.vue
@@ -1,19 +1,17 @@
- 装车照片每类 1 张,上传后自动保存,可更换
+ 装车照片
{{ busy ? '同步中' : snapshot ? `${snapshot.photos.length}/5 已保存` : '待同步' }}
- 可边装车边上传;确认装车完成且照片齐全后自动进入派送中。
- 上传并保存 5 张装车照片后,本车自动进入派送中。
{{ error.message }}requestId:{{ error.requestId }}traceId:{{ error.traceId }}
diff --git a/src/pages/dispatch/dispatch.vue b/src/pages/dispatch/dispatch.vue
index 708c407..a15651c 100644
--- a/src/pages/dispatch/dispatch.vue
+++ b/src/pages/dispatch/dispatch.vue
@@ -36,20 +36,25 @@
理货员审核
- 车牌号:{{ current.resources?.vehicle?.plate || '—' }} · 托车公司:{{ current.resources?.vehicle?.carrierName || '—' }}
- 司机:{{ current.resources?.driver?.name || '—' }} · 电话:{{ current.resources?.driver?.phone || '—' }}
- 计划发车(北京时间):{{ reviewTime(current.plannedDepartureAt) }}
- 预计送达(北京时间):{{ reviewTime(current.estimatedArrivalAt) }}
- {{ appointment.destinationWarehouseCode }} · {{ appointment.appointmentNo || '—' }} · {{ appointment.status === 'SUCCESS' ? '已确认' : appointment.status === 'REGISTERED' ? '已登记' : '未填写' }}:{{ reviewTime(appointment.confirmedAt) }}(北京时间)
- 驾驶证号:{{ current.resources?.driver?.evidence?.licenseNo || '—' }} · 有效期:{{ current.resources?.driver?.evidence?.licenseExpiresOn || '—' }}
- 调度审核:{{ reviewLabel(current.reviews?.dispatchReview) }} · {{ current.reviews?.dispatchReview?.reviewedAt || '—' }}
- 调度意见:{{ current.reviews.dispatchReview.reason }}
+
+ 车牌号{{ current.resources?.vehicle?.plate || '—' }}
+ 托车公司{{ current.resources?.vehicle?.carrierName || '—' }}
+ 司机{{ current.resources?.driver?.name || '—' }}
+ 电话{{ current.resources?.driver?.phone || '—' }}
+ 计划发车{{ reviewTime(current.plannedDepartureAt) }}(北京时间)
+ 预计送达{{ reviewTime(current.estimatedArrivalAt) }}(北京时间)
+ {{ appointment.destinationWarehouseCode }}{{ appointment.appointmentNo || '—' }}{{ appointment.status === 'SUCCESS' ? '已确认' : appointment.status === 'REGISTERED' ? '已登记' : '未填写' }}:{{ reviewTime(appointment.confirmedAt) }}(北京时间)
+ 驾驶证号{{ current.resources?.driver?.evidence?.licenseNo || '—' }}
+ 有效期{{ current.resources?.driver?.evidence?.licenseExpiresOn || '—' }}
+ 调度审核{{ reviewLabel(current.reviews?.dispatchReview) }} · {{ reviewTime(current.reviews?.dispatchReview?.reviewedAt) }}
+ 调度意见{{ current.reviews.dispatchReview.reason }}
+
已退回客服处理(车辆信息):{{ rejectionReason }}。修改后须重新提交审核。
{{ current.actions.LOADING_REVIEW.blockReason }}
请核对车辆、司机及下方货物;通过后才能开始真实拣货。驳回将回到客服处理(车辆信息)。
-
+
{{ current.actions.LOADING_REVIEW?.blockReason || '当前不具备理货审核资格,请先完成客服提交和调度审核' }}
@@ -72,16 +77,15 @@
拣货箱数本次最多可拣 {{ selectedRemaining }} 箱
- 确认装车完成{{ current.pickingReconciled ? 'PDA拣货已核平,可边装车边上传5张装车照片;确认装车完成且照片齐全后自动进入派送中。' : '先完成全部真实拣货,不能补录数量代替核平。' }}{{ current.actions.LOADING_COMPLETE?.blockReason || '尚未获得装车完成资格' }}
+ {{ current.actions.LOADING_COMPLETE?.blockReason || '尚未获得装车完成资格' }}
{{ successMessage }}
- 确认装车完成{{ loadingPreview.dispatchNo }}
+ 确认装车
{{ loadingRows.length }} 托已拣托盘{{ loadingRows.reduce((sum, row) => sum + Number(row.pickedPackages || 0), 0) }} 箱已拣箱数
- 本车货物已全部装车?照片可在“装车拍照”中继续上传
-
+
@@ -244,20 +248,25 @@ export default {
.stage-tab::after { border: 0; }
.stage-tab.active { color: #1262f4; background: #fff; box-shadow: 0 1px 4px rgba(31, 41, 55, .12); }
.stage-tab[disabled] { opacity: .55; }
-.review-card textarea { box-sizing: border-box; width: 100%; min-height: 160rpx; padding: 16rpx; border: 1px solid #cbd5e1; border-radius: 8rpx; }
+.review-facts { margin: 16rpx 0; }
+.review-fact { display: flex; align-items: flex-start; gap: 20rpx; padding: 10rpx 0; color: #243c58; font-size: 26rpx; line-height: 1.5; }
+.review-label { flex: 0 0 132rpx; color: #718095; overflow-wrap: anywhere; }
+.review-fact > :last-child { flex: 1; min-width: 0; overflow-wrap: anywhere; }
+.review-appointment { display: flex; flex-direction: column; gap: 4rpx; }
+.review-actions { display: flex; gap: 20rpx; margin-top: 24rpx; }
+.review-actions button { flex: 1; min-width: 0; margin: 0; padding: 0 16rpx; min-height: 88rpx; line-height: 88rpx; font-size: 28rpx; }
+.review-actions .primary-button { min-height: 88rpx; line-height: 88rpx; font-size: 28rpx; }
+.review-card textarea { box-sizing: border-box; width: 100%; height: 160rpx; min-height: 160rpx; margin-top: 20rpx; padding: 16rpx; border: 1px solid #cbd5e1; border-radius: 8rpx; }
.loading-overlay { position: fixed; inset: 0; background: rgba(15, 23, 42, .46); display: flex; align-items: center; justify-content: center; z-index: 100; padding: 40rpx; box-sizing: border-box; }
.loading-dialog { width: 100%; max-width: 620rpx; max-height: 88vh; overflow-y: auto; box-sizing: border-box; background: #fff; border-radius: 28rpx; padding: 36rpx 32rpx 32rpx; box-shadow: 0 24rpx 80rpx rgba(15, 23, 42, .18); }
.loading-dialog-heading { display: flex; flex-direction: column; gap: 10rpx; }
.loading-dialog-title { color: #142c49; font-size: 36rpx; font-weight: 700; line-height: 1.4; }
-.loading-dialog-number { color: #7a8aa0; font-size: 26rpx; line-height: 1.5; overflow-wrap: anywhere; }
.loading-counts { display: flex; margin: 28rpx 0; padding: 24rpx 0; border-radius: 20rpx; background: #f3f7ff; }
.loading-count { flex: 1; display: flex; align-items: center; flex-direction: column; gap: 8rpx; }
.loading-count + .loading-count { border-left: 1px solid #dce6f6; }
.loading-count-value { color: #1764ec; font-size: 48rpx; font-weight: 700; line-height: 1.2; }
.loading-count-unit { font-size: 26rpx; font-weight: 500; }
.loading-count-label { color: #72839b; font-size: 24rpx; }
-.loading-confirm-question { display: block; color: #253a54; font-size: 30rpx; font-weight: 600; line-height: 1.5; }
-.loading-confirm-note { display: block; color: #7a8aa0; font-size: 25rpx; line-height: 1.5; margin-top: 10rpx; }
.loading-dialog-actions { display: flex; gap: 20rpx; margin-top: 32rpx; }
.loading-dialog-actions button { flex: 1; min-width: 0; margin: 0; height: 88rpx; line-height: 88rpx; padding: 0 12rpx; border-radius: 16rpx; font-size: 30rpx; font-weight: 600; }
.loading-dialog-actions button::after { border: 0; }
diff --git a/tests/warehouse-transfer-api.test.mjs b/tests/warehouse-transfer-api.test.mjs
index a4bf222..4a89d9d 100644
--- a/tests/warehouse-transfer-api.test.mjs
+++ b/tests/warehouse-transfer-api.test.mjs
@@ -62,9 +62,10 @@ test('warehouse transfer shows forecast and actual variance and requires counted
assert.doesNotMatch(page, /本托 SKU 箱数|itemCounts/)
})
-test('warehouse transfer retries reuse the pending idempotency key', async () => {
+test('warehouse transfer creates a fresh completion key for each explicit attempt', async () => {
const page = await readFile(new URL('../src/pages/warehouse-transfer/warehouse-transfer.vue', import.meta.url), 'utf8')
- assert.match(page, /if \(!this\.pendingKey\) this\.pendingKey = `pda-transfer-/)
+ assert.doesNotMatch(page, /if \(!this\.pendingKey\) this\.pendingKey = `pda-transfer-/)
+ assert.match(page, /this\.pendingKey = `pda-transfer-/)
assert.match(page, /idempotencyKey: this\.pendingKey/)
assert.equal((page.match(/pendingKey = `pda-transfer-/g) || []).length, 1)
})
diff --git a/tests/warehouse-transfer-counting.test.mjs b/tests/warehouse-transfer-counting.test.mjs
index 8046f3d..4bc4982 100644
--- a/tests/warehouse-transfer-counting.test.mjs
+++ b/tests/warehouse-transfer-counting.test.mjs
@@ -107,12 +107,13 @@ test('transfer counts require valid pieces and measurements and record variance
assert.equal(value.cartonCountReady,true);value.countDiscrepancyReason='多一箱';assert.equal(value.cartonCountReady,true)
assert.equal(value.cartonQuantity,9);assert.equal(value.cartonVolume,'0.012')
})
-test('network retry preserves request key and changed content uses a new request',async()=>{
+test('failed count unlocks the draft and each explicit retry sends current contents with a fresh key',async()=>{
const {value,scope}=setup();const requests=[]
scope.countWarehouseTransferCartons=async(no,request)=>{requests.push(request);throw new Error('offline')}
await value.confirmCartons();await value.confirmCartons()
- assert.equal(requests.length,2);assert.equal(requests[0].idempotencyKey,requests[1].idempotencyKey)
+ assert.equal(requests.length,2);assert.notEqual(requests[0].idempotencyKey,requests[1].idempotencyKey)
value.cartonBoxes[0].items[0].quantity=8;await value.confirmCartons();assert.notEqual(requests[1].idempotencyKey,requests[2].idempotencyKey)
+ assert.equal(requests[2].boxes[0].items[0].quantity,8);assert.equal(value.cartonBoxes[0].items[0].quantity,8)
assert.equal(value.countingCartons,false);assert.equal(value.error.message,'offline')
})
test('draft restores additional cartons and discrepancy reason without crossing tasks',()=>{
@@ -221,7 +222,7 @@ for (const answer of [{confirm:false}, {confirm:true,content:' '}, {confirm:tru
await value.submit()
assert.equal(prompts,1);assert.equal(value.submitting,false)
assert.equal(requests.length,answer.confirm && answer.content?.trim()?1:0)
- if(requests.length){assert.equal(requests[0].discrepancyReason,'少到一箱');await value.submit();assert.equal(requests[0].idempotencyKey,requests[1].idempotencyKey)}
+ if(requests.length){assert.equal(requests[0].discrepancyReason,'少到一箱');await value.submit();assert.notEqual(requests[0].idempotencyKey,requests[1].idempotencyKey)}
})
}
test('matching SKU counts with different carton count submit without prompting, and missing photos block before prompting',async()=>{