From 9307a861cf80e406c3593a872c432402f6c5ce37 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 15 Sep 2026 15:01:03 +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 | 2 +- pages/picking/picking.vue | 12 ++++++------ styles/pda.scss | 3 ++- tests/pda-loading-api.test.mjs | 6 +++++- tests/picking-evidence-stage.test.mjs | 4 ++-- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2f4de01..9502fd9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,7 +25,7 @@ - 允许部分拣货和跳拣,但每个未完成项必须填写异常原因;存在未解决异常时任务挂起,不能完成或推进 `LOADED`。 - 作业提交使用对员工不可见的轻量幂等标识和服务端版本;重复提交返回原结果,版本冲突提示刷新后重试,不静默覆盖。 - 提交成功或明确放弃后清理草稿;退出应用或设备重启后同一作业员、任务和柜次可恢复,其他账号不得继承。 -- PDA 仅允许现场拍照;Admin 可通过独立受权入口人工上传。五类证据各至少 1 张有效照片,同类型允许多张追加;错误或重复照片标记作废并保留原因、操作人和时间,不硬删除。设备端证据查询、上传、预览和作废统一按当前 `taskId + warehouseId` 走任务专用 API,服务端再绑定真实柜 `containerBookingId`;不得调用旧入仓单/客户作用域证据接口。 +- PDA 仅允许现场拍照;Admin 可通过独立受权入口人工上传。五类证据各至少 1 张有效照片,同类型允许多张追加;缩略图使用右上角“×”作为员工可见的删除入口,不显示“作废”文案,服务端仍对错误或重复照片保留删除原因、操作人和时间,不硬删除。设备端证据查询、上传、预览和删除统一按当前 `taskId + warehouseId` 走任务专用 API,服务端再绑定真实柜 `containerBookingId`;不得调用旧入仓单/客户作用域证据接口。 - 与管理后台头程订单、仓库任务和 `baiyue-firstleg` Controller/OpenAPI 对齐稳定标识、状态机、时间和异常码,禁止在前端自行发明另一套业务状态。 - 一件代发清点组托使用独立 `/first-leg/pda-dropship` 任务链:箱码先经只读识别再确认实收;系统托盘码可先于或晚于箱码扫描。`count` 携托盘码时直接绑定,箱码先扫则进入待绑托区并由 `bind-pending` 批量绑定;换托只使用服务端 `cartonId`,封托后按整托不可拆。只消费 `platform:firstleg:pda:view` 与 `platform:firstleg:pda:work` 权限结果,不在 PDA 自建权限语义。 - 清点组托使用测量审核预生成的 DP 托盘;扫描托盘通过 `bind-pending` 原子绑定待绑箱。幂等键按操作员、任务、载荷和版本稳定生成,失败重试复用;换托先移回待绑区再扫描目标托盘,封托不可拆分。装柜拣货扫描后等待正式整托 API 成功再更新本地结果。 diff --git a/pages/picking/picking.vue b/pages/picking/picking.vue index abcca33..5d8c823 100644 --- a/pages/picking/picking.vue +++ b/pages/picking/picking.vue @@ -136,7 +136,7 @@ 图片处理中 {{ photo.fileName || '已记录' }} - + @@ -1358,25 +1358,25 @@ this.sealSaving = false } }, - async voidEvidencePhoto(photo) { + async deleteEvidencePhoto(photo) { if (!this.requireEvidenceMutation()) return if (!photo?.id || !this.evidenceIdentity?.taskId || !this.evidenceIdentity?.warehouseId) return const contextId = this.evidenceContextId const identity = { ...this.evidenceIdentity } - const reason = 'PDA现场标记错图' + const reason = 'PDA现场删除错图' try { await voidEvidence(identity.taskId, photo.id, identity.warehouseId, { expectedVersion: photo.version || 0, reason }) if (!this.isEvidenceContextActive(contextId, identity)) return this.evidenceFiles = { ...this.evidenceFiles, [photo.evidenceType]: (this.evidenceFiles[photo.evidenceType] || []).map((item) => item.id === photo.id ? { ...item, status: 'VOID', voidReason: reason } : item) } - this.showToast('照片已作废,可重新拍摄', 'success') + this.showToast('照片已删除,可重新拍摄', 'success') } catch (error) { if (!this.isEvidenceContextActive(contextId, identity)) return if (this.isTaskConflict(error)) { await this.refreshAfterTaskConflict(sanitizeUserMessage(error.message, '任务内容已更新,请刷新后重试')) return } - this.remoteError = error.network ? '网络失败,照片未作废,请重试' : this.remoteFailureMessage(error, '照片作废失败') - this.showToast('照片作废失败', 'error') + this.remoteError = error.network ? '网络失败,照片未删除,请重试' : this.remoteFailureMessage(error, '照片删除失败') + this.showToast('照片删除失败', 'error') } }, async completeLoadingTask() { diff --git a/styles/pda.scss b/styles/pda.scss index 48e73cf..259e01b 100644 --- a/styles/pda.scss +++ b/styles/pda.scss @@ -1238,7 +1238,8 @@ .evidence-thumbnail { display: flex; width: 100%; height: 68px; align-items: center; justify-content: center; border-radius: 6px; color: $text-secondary; font-size: 9px; line-height: 13px; text-align: center; background: #e7edf5; } .evidence-thumbnail-placeholder { box-sizing: border-box; padding: 6px; } .evidence-file { display: block; overflow: hidden; margin-top: 4px; padding: 0 2px; color: $text-secondary; font-size: 9px; line-height: 14px; text-overflow: ellipsis; white-space: nowrap; } - .evidence-void-btn { position: absolute; top: 8px; right: 8px; min-width: 40px; height: 40px; padding: 0 7px; border: 0; border-radius: 20px; color: #ffffff; font-size: 10px; line-height: 40px; background: rgba(185, 28, 28, 0.9); } + .evidence-delete-btn { position: absolute; top: 1px; right: 1px; width: 40px; min-width: 40px; height: 40px; margin: 0; padding: 0; border: 0; color: #ffffff; font-size: 29px; font-weight: 400; line-height: 40px; text-align: center; text-shadow: 0 1px 4px rgba(0, 0, 0, 0.95); background: transparent; } + .evidence-delete-btn::after { border: 0; } .evidence-upload-state { display: block; margin-top: 5px; color: $text-secondary; font-size: 10px; line-height: 15px; } .evidence-upload-state.uploading, .evidence-upload-state.pending { color: #174d91; } .evidence-upload-state.failed { color: $danger; } diff --git a/tests/pda-loading-api.test.mjs b/tests/pda-loading-api.test.mjs index 7e1d490..8b5af3e 100644 --- a/tests/pda-loading-api.test.mjs +++ b/tests/pda-loading-api.test.mjs @@ -5,6 +5,7 @@ import { readFile } from 'node:fs/promises' const source = await readFile(new URL('../api/pda-loading.js', import.meta.url), 'utf8') const http = await readFile(new URL('../api/http.js', import.meta.url), 'utf8') const page = await readFile(new URL('../pages/picking/picking.vue', import.meta.url), 'utf8') +const styles = await readFile(new URL('../styles/pda.scss', import.meta.url), 'utf8') test('sealed pallets use a single authoritative pallet command and prohibit partial picking', () => { assert.match(source, /pallets\/pick/) @@ -92,7 +93,10 @@ test('picking page exposes five evidence slots, retry and remote completion stat assert.match(page, /sourceType: \['camera'\]/) assert.doesNotMatch(page, /sourceType: \['camera', 'album'\]/) assert.match(page, /evidenceEntries\(slot\.type\)/) - assert.match(page, /voidEvidencePhoto\(/) + assert.match(page, /deleteEvidencePhoto\(/) + assert.match(page, /class="evidence-delete-btn" aria-label="删除照片"[\s\S]*?>×<\/button>/) + assert.doesNotMatch(page, />作废<\/button>/) + assert.match(styles, /\.evidence-delete-btn\s*\{[^}]*width:\s*40px;[^}]*background:\s*transparent;/) assert.match(page, /evidenceComplete/) }) diff --git a/tests/picking-evidence-stage.test.mjs b/tests/picking-evidence-stage.test.mjs index 77d5450..2bf851b 100644 --- a/tests/picking-evidence-stage.test.mjs +++ b/tests/picking-evidence-stage.test.mjs @@ -171,7 +171,7 @@ test('photos upload, bind, reload and void before picking completion', () => { assert.doesNotMatch(retry, /if \(!this\.loadingStageReady\)/) assert.match(page, /async loadLoadingEvidence\(\) \{[\s\S]*?!this\.evidenceIdentity\?\.taskId[\s\S]*?!this\.evidenceIdentity\?\.warehouseId[\s\S]*?getEvidence\(identity\.taskId, identity\.warehouseId\)/) assert.match(page, /await this\.loadLoadingEvidence\(\)[\s\S]*?if \(this\.loadingStageReady\) this\.activeStage = 'evidence'/) - assert.match(page, /async voidEvidencePhoto\([^)]*\) \{[\s\S]*?if \(!this\.requireEvidenceMutation\(\)\) return/) + assert.match(page, /async deleteEvidencePhoto\([^)]*\) \{[\s\S]*?if \(!this\.requireEvidenceMutation\(\)\) return/) assert.match(page, /async completeLoadingTask\([^)]*\) \{[\s\S]*?if \(!this\.requireEvidenceStage\(\)\) return/) assert.doesNotMatch(page, /照片已上传,拣货完成后自动保存/) }) @@ -239,7 +239,7 @@ test('returning from an image preview does not refresh the picking task', () => test('evidence save, void and loading completion ignore stale cabinet responses', () => { for (const [method, awaitedCall] of [ - ['voidEvidencePhoto', 'voidEvidence'], + ['deleteEvidencePhoto', 'voidEvidence'], ['completeLoadingTask', 'completeContainerPickingTask'] ]) { const body = page.match(new RegExp(`async ${method}\\([^)]*\\) \\{[\\s\\S]*?\\n\\t\\t\\t\\},`))?.[0] || ''