From 0b51ed434702eb59eca00c7523b31bc801b88115 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 12 Aug 2026 09:26:00 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 3 ++- pages/picking/picking.vue | 21 +++++++++++++++------ tests/page-contract.test.mjs | 12 ++++++++---- tests/pda-loading-api.test.mjs | 3 ++- tests/picking-state.test.mjs | 10 +++++----- 5 files changed, 32 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b47c4f1..5e182d2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,6 +16,7 @@ ## 作业流程原则 - 扫码是主路径:输入焦点、连续扫描、防抖、重复码、无效码、错箱和扫描反馈必须作为一等场景处理。 +- 拣货扫描身份是后端下发的稳定箱唛箱码 `boxCode`;一次有效且未重复的箱码扫描表示拣出一箱。SKU、商品件数、入库单号和页面展示文案只用于辅助识别,不得作为扫码匹配键或拣货计数单位。 - 进入任务时由服务端标记“拣货中”;扫描结果先写入绑定作业员、任务和柜次的设备持久化草稿,仅在点击提交时批量写后端,禁止逐码提交。 - 允许部分拣货和跳拣,但每个未完成项必须填写异常原因;存在未解决异常时任务挂起,不能完成或推进 `LOADED`。 - 作业提交使用对员工不可见的轻量幂等标识和服务端版本;重复提交返回原结果,版本冲突提示刷新后重试,不静默覆盖。 @@ -46,7 +47,7 @@ | 页面注册与启动 | `pages.json`、`main.js`、`App.vue` | 全局样式和应用生命周期 | | 登录与会话 | `pages/login/`、`stores/session.js` | 远程鉴权适配和会话清理 | | 工作台与模块入口 | `pages/home/`、`pages/module/`、`pages/operation/` | `data/demo-data.js` 与页面禁用规则 | -| 拣货任务与扫码 | `pages/picking/`、`stores/picking.js` | `api/pda-loading.js`、`utils/scan-broadcast.js`、`components/ScanTargetInput.vue` | +| 拣货任务与箱码扫码 | `pages/picking/`、`stores/picking.js` | `api/pda-loading.js`、`utils/scan-broadcast.js`、`components/ScanTargetInput.vue`;服务端契约见 `../../baiyue-firstleg/firstleg/src/main/resources/openapi/phase3-pda-convergence-v1.yaml` | | 扫描诊断 | `pages/scanner-test/` | `utils/scan-broadcast.js` 与广播 Action/Extra | | 作业记录与人员 | `pages/history/`、`pages/profile/` | session 与本地敏感数据清理 | diff --git a/pages/picking/picking.vue b/pages/picking/picking.vue index baec523..ecba9d7 100644 --- a/pages/picking/picking.vue +++ b/pages/picking/picking.vue @@ -41,12 +41,12 @@ {{ lastScanResult.type === 'success' ? '✓' : '!' }} 最近扫码 · {{ lastScanResult.title }} - {{ lastScanResult.sku }} + {{ lastScanResult.boxCode }} {{ lastScanResult.detail }} - {{ entry.sku }} · {{ entry.title }} + {{ entry.boxCode }} · {{ entry.title }} @@ -479,6 +479,14 @@ return false }, methods: { + normalizeScanHistory(entries) { + if (!Array.isArray(entries)) return [] + return entries.map((entry) => { + const normalized = { ...entry, boxCode: entry.boxCode ?? entry.sku ?? '' } + delete normalized.sku + return normalized + }) + }, draftIdentity() { const operatorId = session.currentUser?.id || session.currentUser?.userId || session.currentUser?.accountId return { operatorId, taskId: this.remoteTaskId || this.currentBox.taskId || this.currentBox.inboundId, containerNo: this.currentBox.boxNo, warehouseId: this.currentBox.warehouseId || session.currentUser?.warehouseId || session.currentUser?.warehouse } @@ -492,7 +500,7 @@ this.scannedBoxCodes = { ...(restored.scannedBoxCodes || {}) } this.boxDrafts = restored.boxDrafts || {} this.completedBoxes = restored.completedBoxes || {} - this.scanHistory = restored.scanHistory || [] + this.scanHistory = this.normalizeScanHistory(restored.scanHistory) return true }, handleOverlayChange(name, event) { @@ -555,7 +563,7 @@ this.itemStates = { ...draft.itemStates } this.partialQuantities = { ...draft.partialQuantities } this.scannedBoxCodes = { ...(draft.scannedBoxCodes || {}) } - this.scanHistory = draft.scanHistory.map((entry) => ({ ...entry })) + this.scanHistory = this.normalizeScanHistory(draft.scanHistory) } else { this.itemStates = createItemStates(this.currentBox.items) this.partialQuantities = {} @@ -687,6 +695,7 @@ const firstItem = this.currentBox.items[0] this.currentBox.orderNo = firstItem.orderNo || firstItem.inboundCode || this.currentBox.orderNo this.currentBox.inboundId = firstItem.inboundId || this.currentBox.inboundId + this.currentBox.tenantId = firstItem.tenantId || this.currentBox.tenantId this.itemStates = createItemStates(this.currentBox.items) this.partialQuantities = {} this.scannedBoxCodes = {} @@ -812,8 +821,8 @@ this.showToast(item ? `已确认拣出 ${item.qty} 箱` : '已确认拣出', 'success') this.persistSession() }, - recordScanResult(type, title, sku, detail) { - const entry = { id: `${Date.now()}-${this.scanHistory.length}-${sku}`, type, title, sku, detail } + recordScanResult(type, title, boxCode, detail) { + const entry = { id: `${Date.now()}-${this.scanHistory.length}-${boxCode}`, type, title, boxCode, detail } this.scanHistory = [entry, ...this.scanHistory].slice(0, 3) }, undoItem(id) { diff --git a/tests/page-contract.test.mjs b/tests/page-contract.test.mjs index 7890cb5..685d2e9 100644 --- a/tests/page-contract.test.mjs +++ b/tests/page-contract.test.mjs @@ -102,12 +102,16 @@ test('picking page keeps scan, draft and risky transition contracts', async () = assert.match(page, /submitStatusHint\(\)[\s\S]*?提交时需选择原因/) assert.match(page, /onBackPress\(\)/) assert.doesNotMatch(page, /scanFocused|restoreScanFocus|hideKeyboard|@confirm="handleScan"/) - assert.match(page, / { @@ -209,8 +213,8 @@ test('reusable scan target input separates field selection from page-level broad assert.match(component, / { diff --git a/tests/pda-loading-api.test.mjs b/tests/pda-loading-api.test.mjs index d669203..701beae 100644 --- a/tests/pda-loading-api.test.mjs +++ b/tests/pda-loading-api.test.mjs @@ -7,7 +7,7 @@ const page = await readFile(new URL('../pages/picking/picking.vue', import.meta. test('PDA loading adapter keeps C04 read and write paths', () => { for (const path of [ - '/orders?', + '/first-leg/pda-loading/tasks/pending', '/loading/picking?tenantId=', '/loading/picking/start?tenantId=', '/loading/picking/complete?tenantId=', @@ -56,6 +56,7 @@ test('picking page exposes five evidence slots, retry and remote completion stat }) 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\?/) assert.match(source, /tasks\/\$\{encodeURIComponent\(taskId\)\}\/claim/) assert.match(source, /tasks\/\$\{encodeURIComponent\(task\.taskId\)\}\/batch/) diff --git a/tests/picking-state.test.mjs b/tests/picking-state.test.mjs index 7d376a0..3034aad 100644 --- a/tests/picking-state.test.mjs +++ b/tests/picking-state.test.mjs @@ -69,25 +69,25 @@ test('submitStates turns only pending items into skipped', () => { }) }) -test('classifyScan distinguishes pending, repeated, and unknown SKU codes', () => { - const scanItems = [{ id: 1, sku: 'EL-38201' }, { id: 2, sku: 'EL-38202' }] +test('classifyScan distinguishes pending, repeated, and unknown carton codes', () => { + const scanItems = [{ id: 1, boxCodes: ['BOX-001'] }, { id: 2, boxCodes: ['BOX-002', 'BOX-003'] }] assert.deepEqual(state.classifyScan(scanItems, { 1: state.ITEM_STATUS.PICKED, 2: state.ITEM_STATUS.PENDING, - }, 'EL-38202'), { + }, 'BOX-002'), { type: 'pending', item: scanItems[1], }) assert.deepEqual(state.classifyScan(scanItems, { 1: state.ITEM_STATUS.PARTIAL, 2: state.ITEM_STATUS.PENDING, - }, 'EL-38201'), { + }, 'BOX-001', { 'BOX-001': 1 }), { type: 'processed', item: scanItems[0], status: state.ITEM_STATUS.PARTIAL, }) - assert.deepEqual(state.classifyScan(scanItems, {}, 'NO-SUCH-SKU'), { + assert.deepEqual(state.classifyScan(scanItems, {}, 'NO-SUCH-BOX'), { type: 'unknown', item: null, })