From c9bc6bf6168b449cbebb2e39c7f91f10f9bb6214 Mon Sep 17 00:00:00 2001
From: ouyangxq <775153912@qq.com>
Date: Mon, 24 Aug 2026 17:04:40 +0800
Subject: [PATCH] feat(pda): add locked container receiving flow
---
src/api/receiving.js | 14 ++++++
src/data/overseas-workbench.js | 6 +--
src/pages.json | 2 +
src/pages/home/home.vue | 2 +-
.../inbound-receiving/inbound-receiving.vue | 46 +++++++++++++++++++
src/pages/inbound-sign/inbound-sign.vue | 39 ++++++++++++++++
src/pages/module/module.vue | 2 +-
tests/project-contract.test.mjs | 19 ++++++++
8 files changed, 125 insertions(+), 5 deletions(-)
create mode 100644 src/api/receiving.js
create mode 100644 src/pages/inbound-receiving/inbound-receiving.vue
create mode 100644 src/pages/inbound-sign/inbound-sign.vue
diff --git a/src/api/receiving.js b/src/api/receiving.js
new file mode 100644
index 0000000..00a077e
--- /dev/null
+++ b/src/api/receiving.js
@@ -0,0 +1,14 @@
+import { overseasRequest, overseasUpload } from '@/api/overseas.js'
+
+const root = '/pda/v1/last-leg/receiving-containers'
+const container = (containerNo) => `${root}/${encodeURIComponent(containerNo)}`
+
+export const listReceivingContainers = (page = 1, size = 50) => overseasRequest('GET', `${root}?status=WAIT_SIGN&page=${page}&size=${size}`)
+export const getActiveReceivingContainer = () => overseasRequest('GET', `${root}/active`)
+export const getReceivingContainer = (containerNo) => overseasRequest('GET', container(containerNo))
+export const uploadSignPhoto = (containerNo, filePath) => overseasUpload(`${container(containerNo)}/sign-photos/files`, filePath)
+export const signReceivingContainer = (containerNo, request) => overseasRequest('POST', `${container(containerNo)}/sign`, request)
+export const resolveReceivingCarton = (containerNo, request) => overseasRequest('POST', `${container(containerNo)}/cartons/resolve`, request)
+export const resolveReceivingPallet = (containerNo, request) => overseasRequest('POST', `${container(containerNo)}/pallets/resolve`, request)
+export const receiveContainerCargo = (containerNo, request) => overseasRequest('POST', `${container(containerNo)}/receive-batches`, request)
+export const completeReceivingContainer = (containerNo, request) => overseasRequest('POST', `${container(containerNo)}/complete`, request)
diff --git a/src/data/overseas-workbench.js b/src/data/overseas-workbench.js
index d474925..80c4591 100644
--- a/src/data/overseas-workbench.js
+++ b/src/data/overseas-workbench.js
@@ -1,8 +1,8 @@
export const MODULES = [
{ key: 'inbound', name: '入仓作业', icon: '入', description: '到仓、卸柜收货与上架', operations: [
- { name: '到仓登记', icon: '到', iconType: 'download', description: '扫描入仓计划并登记车辆到仓', available: true, badge: '已接入' },
- { name: '卸柜收货', icon: '卸', iconType: 'paperplane', description: '按箱或托盘确认卸柜实收', available: true, badge: '已接入' },
- { name: '入仓上架', icon: '上', iconType: 'upload', description: '扫描容器与目标库位完成上架', available: true, badge: '已接入' }
+ { name: '入库签收', icon: '签', iconType: 'camera', description: '选择待处理柜号、拍照并确认签收', path: '/pages/inbound-sign/inbound-sign', available: true, badge: '真实接口' },
+ { name: '收货', icon: '收', iconType: 'paperplane', description: '自动读取当前柜并按目的仓规则收货', path: '/pages/inbound-receiving/inbound-receiving', available: true, badge: '真实接口' },
+ { name: '入仓上架', icon: '上', iconType: 'upload', description: '扫描容器与目标库位完成上架', available: false, badge: '待接入' }
] },
{ key: 'inventory', name: '库内作业', icon: '库', description: '盘点、移库与托盘管理', operations: [
{ name: '库存盘点', icon: '盘', iconType: 'list', description: '按任务扫描库位和库存', available: true, badge: '原型' },
diff --git a/src/pages.json b/src/pages.json
index f9e33e7..8528e19 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -4,6 +4,8 @@
{ "path": "pages/home/home", "style": { "navigationStyle": "custom", "navigationBarTitleText": "海外仓工作台", "backgroundColor": "#F5F7FA" } },
{ "path": "pages/module/module", "style": { "navigationStyle": "custom", "navigationBarTitleText": "海外仓业务", "backgroundColor": "#F5F7FA" } },
{ "path": "pages/operation/operation", "style": { "navigationStyle": "custom", "navigationBarTitleText": "现场作业", "backgroundColor": "#F5F7FA" } },
+ { "path": "pages/inbound-sign/inbound-sign", "style": { "navigationStyle": "custom", "navigationBarTitleText": "入库签收", "backgroundColor": "#F5F7FA" } },
+ { "path": "pages/inbound-receiving/inbound-receiving", "style": { "navigationStyle": "custom", "navigationBarTitleText": "收货", "backgroundColor": "#F5F7FA" } },
{ "path": "pages/history/history", "style": { "navigationStyle": "custom", "navigationBarTitleText": "作业记录", "backgroundColor": "#F5F7FA" } },
{ "path": "pages/profile/profile", "style": { "navigationStyle": "custom", "navigationBarTitleText": "个人中心", "backgroundColor": "#F5F7FA" } },
{ "path": "pages/scanner-test/scanner-test", "style": { "navigationStyle": "custom", "navigationBarTitleText": "广播扫码测试", "backgroundColor": "#F5F7FA" } }
diff --git a/src/pages/home/home.vue b/src/pages/home/home.vue
index e6c5d52..ed0f237 100644
--- a/src/pages/home/home.vue
+++ b/src/pages/home/home.vue
@@ -41,7 +41,7 @@
openOperation(operation) {
if (!operation.available) return
const module = this.groups.find((group) => group.operations.some((item) => item.name === operation.name))
- uni.navigateTo({ url: `/pages/operation/operation?module=${module?.key || 'query'}&name=${encodeURIComponent(operation.name)}` })
+ uni.navigateTo({ url: operation.path || `/pages/operation/operation?module=${module?.key || 'query'}&name=${encodeURIComponent(operation.name)}` })
}
}
}
diff --git a/src/pages/inbound-receiving/inbound-receiving.vue b/src/pages/inbound-receiving/inbound-receiving.vue
new file mode 100644
index 0000000..a4f79cc
--- /dev/null
+++ b/src/pages/inbound-receiving/inbound-receiving.vue
@@ -0,0 +1,46 @@
+
+
+
+
+ 当前柜 {{ active.containerNo }}{{ active.inboundCodeCount }} 个入仓码 · {{ active.totalPallets }} 托 · {{ active.totalPackages }} 箱已收 {{ active.receivedPackages }} · 待收 {{ active.remainingPackages }}当前柜完成前不能处理其他柜号
+ 当前没有已签收柜号
+
+ {{ scanTitle }}
+ 入仓码 {{ cargo.inboundCode }}{{ cargo.skuCode }} · {{ cargo.productName || '-' }}目的仓:{{ cargo.destinationWarehouseName }}({{ destinationLabel }}){{ cargo.totalPallets }} 托 · 总计 {{ cargo.totalPackages }} 箱已收 {{ cargo.receivedPackages }} · 待收 {{ cargo.remainingPackages }}
+ 托盘 {{ pallet.palletCode }}{{ pallet.locationCode || '待分配库位' }} · 已装 {{ pallet.currentPackages }} 箱
+ FBA 按箱数收货
+ 海外仓逐箱扫码中当前托盘 {{ pallet.palletCode }},每扫一箱立即绑定
+
+
+ {{ lastResult }}{{ error.message }}code={{ error.code }}requestId={{ error.requestId }}traceId={{ error.traceId }}
+
+
+
+
+
+
+
diff --git a/src/pages/inbound-sign/inbound-sign.vue b/src/pages/inbound-sign/inbound-sign.vue
new file mode 100644
index 0000000..e06e073
--- /dev/null
+++ b/src/pages/inbound-sign/inbound-sign.vue
@@ -0,0 +1,39 @@
+
+
+
+
+ 当前柜 {{ active.containerNo }}当前柜完成前不能处理其他柜号
+
+ 选择本次处理柜号
+
+ {{ item.containerNo }}{{ item.inboundCodeCount }} 个入仓码 · {{ item.totalPallets }} 托 · {{ item.totalPackages }} 箱预计到仓 {{ item.estimatedWarehouseAt }}
+
+ 签收 {{ selected.containerNo }}已上传 {{ photos.length }} 张,确认签收后只能处理此柜,完成前不能切换。
+
+ {{ error.message }}code={{ error.code }}requestId={{ error.requestId }}traceId={{ error.traceId }}
+
+
+
+
+
+
+
diff --git a/src/pages/module/module.vue b/src/pages/module/module.vue
index ccdba48..f28796a 100644
--- a/src/pages/module/module.vue
+++ b/src/pages/module/module.vue
@@ -21,7 +21,7 @@
onLoad(options) { this.moduleKey = options.key || 'inbound' },
onShow() { if (!requireSession()) return; this.user = session.currentUser },
methods: {
- openOperation(operation) { if (operation.available) uni.navigateTo({ url: `/pages/operation/operation?module=${this.moduleKey}&name=${encodeURIComponent(operation.name)}` }) }
+ openOperation(operation) { if (operation.available) uni.navigateTo({ url: operation.path || `/pages/operation/operation?module=${this.moduleKey}&name=${encodeURIComponent(operation.name)}` }) }
}
}
diff --git a/tests/project-contract.test.mjs b/tests/project-contract.test.mjs
index 40561df..e80a22b 100644
--- a/tests/project-contract.test.mjs
+++ b/tests/project-contract.test.mjs
@@ -38,3 +38,22 @@ test('login has no committed demo credentials and inbound operation consumes rea
assert.match(overseas, /replaceTokens|refreshOverseasToken/)
assert.match(overseas, /requestId/)
})
+
+test('container receiving uses sign then locked-container receiving without internal IDs', async () => {
+ const [pages, workbench, api, sign, receiving] = await Promise.all([
+ read('../src/pages.json'), read('../src/data/overseas-workbench.js'), read('../src/api/receiving.js'),
+ read('../src/pages/inbound-sign/inbound-sign.vue'), read('../src/pages/inbound-receiving/inbound-receiving.vue')
+ ])
+ assert.match(pages, /pages\/inbound-sign\/inbound-sign/)
+ assert.match(pages, /pages\/inbound-receiving\/inbound-receiving/)
+ assert.match(workbench, /入库签收/)
+ assert.match(workbench, /name: '收货'/)
+ for (const path of ['/active', '/sign-photos/files', '/sign', '/cartons/resolve', '/pallets/resolve', '/receive-batches', '/complete']) assert.match(api, new RegExp(path.replaceAll('/', '\\/')))
+ assert.match(sign, /当前柜完成前不能处理其他柜号/)
+ assert.doesNotMatch(sign, /切换柜号|customerId|planNo|allocationId|warehouseId|locationId/)
+ assert.match(receiving, /receiptMode: 'BULK_COUNT'/)
+ assert.match(receiving, /receiptMode: 'CARTON_SCAN'/)
+ assert.match(receiving, /cargo\.destinationType === 'FBA'/)
+ assert.match(receiving, /cargo\?\.destinationType === 'OVERSEAS'/)
+ assert.doesNotMatch(receiving, /切换柜号|customerId|planNo|allocationId|warehouseId|locationId/)
+})