From d096116cf735e1bd50f35c5e446ede6b0fd9b6f2 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 28 Aug 2026 10:39:02 +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 | 10 +- README.md | 9 +- src/data/overseas-workbench.js | 2 +- src/pages.json | 1 + src/pages/home/home.vue | 2 +- src/pages/module/module.vue | 4 +- src/pages/pallet/pallet.vue | 316 ++++++++++++++++++++++++++ src/stores/session.js | 4 +- src/styles/pda.scss | 5 +- tests/pallet-operations.test.mjs | 40 ++++ tests/project-contract.test.mjs | 13 +- tests/workbench-availability.test.mjs | 8 +- 12 files changed, 390 insertions(+), 24 deletions(-) create mode 100644 src/pages/pallet/pallet.vue create mode 100644 tests/pallet-operations.test.mjs diff --git a/AGENTS.md b/AGENTS.md index 4a157ad..4e0f08d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ ## 服务与发布边界 - 国内 Auth 是唯一账号和 Token 签发方;海外业务请求统一经 `gateway-overseas`。 -- 登录、刷新和本地会话必须同时校验国内/海外 Token 的平台主体、成员、冻结海外仓一致;海外 Token 还必须精确包含 `WAREHOUSE_PICKER` 与 `lastleg:pda:operate`。无效、过期、跨主体或撤权响应必须 fail-closed 清理会话,不得只依赖页面入口隐藏。 +- 登录、刷新和本地会话必须同时校验国内/海外 Token 的平台主体、成员、冻结海外仓一致;海外 Token 还必须精确包含 `OVERSEAS_WAREHOUSE_PDA_OPERATOR` 与 `lastleg:pda:operate`。无效、过期、跨主体或撤权响应必须 fail-closed 清理会话,不得只依赖页面入口隐藏。 - 并发海外 401 只允许一次国内 refresh;双 Token 完整校验后原子替换,每个原请求或照片上传最多重放一次。刷新失败清理本地会话,业务错误和网络错误保留 code、requestId、traceId 或 network 诊断。 - 国内与海外 AppID、Android 包名、签名、版本号、构建产物和在线更新通道必须独立。 - `src/manifest.json` 中的 AppID 在正式打包前由海外版所属 DCloud 账号创建;不得伪造或复用国内 AppID。 @@ -28,7 +28,7 @@ - 工作台定义:`src/data/overseas-workbench.js`。 - 登录与会话:`src/api/auth.js`、`src/stores/session.js`。 - 扫码设备层:`src/utils/scan-broadcast.js`;广播 Action=`android.intent.action.SCANRESULT`,Extra=`value`。 -- 当前通用作业页中的入仓作业已接柜号级真实海外 API:账号锁柜、三类照片签收、统一箱码定位入仓码、FBA 按箱数收货、OVERSEAS 逐箱收货并逐入仓码核平;收货页不预列柜内全部入仓码,也不要求作业员扫描 SKU;库内、换标与查询入口在海外 PDA 专用 Controller、权限和 Token 冻结仓库范围契约齐备前必须禁用,不得调用平台/客户/内部接口或模拟成功。 +- 入仓作业已接柜号级真实海外 API:账号锁柜、三类照片签收、统一箱码定位入仓码、FBA 按箱数收货、OVERSEAS 逐箱收货并逐入仓码核平;托盘管理已接扫码查询、组托、拆托、换托和解绑专用 PDA API。其余库内、换标与查询入口在前端完成真实契约接入前必须禁用,不得调用平台/客户/内部接口或模拟成功。 ## 页面与能力快速索引 @@ -41,7 +41,8 @@ | 海外仓工作台与模块入口 | `src/pages/home/home.vue`、`src/pages/module/module.vue` | `src/data/overseas-workbench.js` 是模块与作业可用性入口;首页通过 `/api/pda/v1/last-leg/receiving-containers/warehouse-profile` 读取 Token 绑定仓的中文主档名称 | | 中英西语言设置 | `src/i18n/index.js`、`src/i18n/locales/`、`src/pages/profile/profile.vue` | 个人中心可选择并持久化 `zh-CN`、`en`、`es`;当前业务翻译资源仍为空,页面业务文案继续使用中文,不得把语言选择入口误报为全量翻译完成 | | 柜号签收、照片与入仓真实作业 | `src/pages/operation/operation.vue`、`src/api/inbound.js`、`src/api/receiving-adapter.js`、`src/api/overseas.js`、`src/stores/receiving.js` | 三个固定照片角色各一张,支持拍照/相册、鉴权预览及更换失败保留原图;Backend `lastleg-service/inboundplan/pda/controller/ReceivingContainerController.java`、`ReceivingContainerService.java`,公网前缀 `/api/pda/v1/last-leg/receiving-containers/**` | -| 库内、托盘、换标与现场查询待接前端 | `src/data/overseas-workbench.js` 中对应 `available: false` 入口 | Backend 已有 `pda/stocktake/`、`pda/move/`、`pda/warehouse/`、`palletops/`、`relabel/pda/` 与 `overseas-pda-operations-v1.yaml`;本应用新增专用 API/状态恢复和测试前不得启用 | +| 托盘管理真实作业 | `src/pages/pallet/pallet.vue`、`src/api/pallet.js` | 扫码查询托盘内容,按服务端返回的托盘/库存版本执行组托、拆托、换托和解绑;公网前缀 `/api/pda/v1/last-leg/pallet-operations/**` | +| 其余库内、换标与现场查询待接前端 | `src/data/overseas-workbench.js` 中对应 `available: false` 入口 | Backend 已有 `pda/stocktake/`、`pda/move/`、`pda/warehouse/`、`relabel/pda/` 与 `overseas-pda-operations-v1.yaml`;本应用新增专用 API/状态恢复和测试前不得启用 | | 扫码设备与诊断 | `src/utils/scan-broadcast.js`、`src/pages/scanner-test/scanner-test.vue` | `src/components/ScanTargetInput.vue`、声音反馈工具;广播 Action/Extra 见本文件服务边界 | | 作业记录与人员 | `src/pages/history/history.vue`、`src/pages/profile/profile.vue` | 会话隔离、退出清理和后续真实作业历史契约 | | 本地契约测试 | `tests/project-contract.test.mjs`、`navigation.test.mjs`、`scan-broadcast.test.mjs`、`audio-feedback.test.mjs` | 运行 `npm test`;不代表真实海外 API 或真机验收 | @@ -49,9 +50,10 @@ ## 海外业务接口边界 - `src/api/inbound.js` 已接入待卸柜候选、柜详情/prepare、current 恢复、三类照片签收、统一箱码定位入仓码、实物托盘解析、FBA bulk/OVERSEAS carton 收货及整柜完成;FBA bulk 不提交 `skuCode`,库存分配由服务端按计划明细完成;响应与 404/409 判定集中在 `src/api/receiving-adapter.js`,双 Token 刷新与海外 401 单次重放由 `src/api/overseas.js` 统一处理。 +- `src/api/pallet.js` 只调用 Lastleg 托盘专用 PDA Controller;前端提交服务端扫码返回的 `palletVersion`、`positionVersion` 和随机幂等键,不提交客户、仓库或库位标识。409 后必须重新扫码获取最新版本,禁止用旧库存快照继续作业。 - 请求身份和资源范围只取 Token 与 current 会话;前端不得提交 `customerId`、`planNo`、`allocationId`、`warehouseId` 或 `locationId`。409 必须引导账号恢复当前柜,完成前不得切柜。 - 签收照片固定为三个角色且每个角色只有一个当前文件;上传成功后使用服务端 `downloadPath` 在当前会话内鉴权读取。更换失败时保留原 `fileId` 和可提交状态,无原图的失败槽不得计入 `photosReady`;本地临时路径仅用于本次设备预览,不作为跨会话文件事实。 -- 库内、换标与现场查询只有视觉原型依据,当前工作台入口保持禁用;新增真实作业必须建立 `/api/pda/**` 业务 API/DTO、`lastleg:pda:operate` 权限、Token 冻结仓库范围、错误态和契约测试,不得把平台、客户或内部接口直接暴露给 PDA。 +- 除已开放的托盘管理外,其余库内、换标与现场查询仍保持禁用;新增真实作业必须接入 `/api/pda/**` 业务 API/DTO、`lastleg:pda:operate` 权限、Token 冻结仓库范围、错误态和契约测试,不得把平台、客户或内部接口直接暴露给 PDA。 - 已有海外入仓 PDA Controller 统一位于 Lastleg `/api/pda/v1/last-leg/inbound-plans/{planNo}/**`;浏览器或设备请求仍必须经 `gateway-overseas` 的显式 PDA 路由,不得直连 Lastleg,也不得改走国内 Gateway。 - 海外业务 Token、PDA 权限和可信身份最终由 Gateway 与服务端校验;前端不得用工作台 `available`、本地客户 ID 或仓库 ID 代替授权和资源归属校验。 - 四期仍止于入仓、库存与库内操作。后端历史 `dispatch/`、出库和派送代码不构成海外 PDA 页面入口,也不得加入工作台。 diff --git a/README.md b/README.md index 4276243..6683547 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 独立的 uni-app Vue 3 App 工程,正式产品形态是安装在海外仓手持 PDA 上的 App。工程不依赖国内 PDA 源码或仓库外共享目录,可独立安装依赖、运行、编译和发布;H5 只用于浏览器调试和编译门禁,不是最终交付形态。 -首版工作台按四期边界提供柜号级真实入仓作业,以及库内作业、换标作业和现场查询原型。登录使用国内 Auth 统一签发的双 Token;海外入仓请求统一经 `gateway-overseas`,不得请求国内 Firstleg/Warehouse 业务接口。 +工作台按四期边界提供柜号级真实入仓作业和托盘管理,以及其余库内、换标和现场查询的禁用入口。登录使用国内 Auth 统一签发的双 Token;海外业务请求统一经 `gateway-overseas`,不得请求国内 Firstleg/Warehouse 业务接口。 ## 本地运行与检查 @@ -14,7 +14,7 @@ npm test npm run dev:h5 ``` -开发环境可在不提交的 `.env.local` 中配置 `VITE_PDA_DEV_USERNAME`、`VITE_PDA_DEV_PASSWORD`,登录页会自动预填;生产 App 构建始终忽略这两个值,账号必须具备 `WAREHOUSE_PICKER`、`lastleg:pda:operate` 和有效海外仓绑定。 +开发环境可在不提交的 `.env.local` 中配置 `VITE_PDA_DEV_USERNAME`、`VITE_PDA_DEV_PASSWORD`,登录页会自动预填;生产 App 构建始终忽略这两个值,账号必须具备 `OVERSEAS_WAREHOUSE_PDA_OPERATOR`、`lastleg:pda:operate` 和有效海外仓绑定。 生产编译: @@ -40,9 +40,10 @@ npm run build:app - 已复用并内置:登录壳、PDA 样式、扫码广播、声音反馈、导航、网络检测、图标及 `uni_modules`。 - 已隔离:国内集装箱拣货、装柜证据、国内 Firstleg API、国内任务状态和国内本地草稿。 - 入仓页面已接待卸柜候选、柜详情与账号锁柜、三类角色照片签收、current 恢复、统一箱码与既有物理托盘解析、FBA 按箱数和 OVERSEAS 逐箱收货、逐入仓码核平及整柜完成的真实 API;收货页不预列柜内入仓码,箱码解析成功后只显示本次命中的入仓码;409 会引导返回当前柜,错误会保留业务码、requestId 与 traceId。 +- 托盘管理已接 `/api/pda/v1/last-leg/pallet-operations/**`:支持扫描查询当前仓实物托盘及库存内容,并按服务端版本执行组托、拆托、换托和解绑;不同目的仓不能合托,冻结或处理中的库存由服务端拒绝。 - 入仓请求不手填客户、计划、分配、仓库或库位标识;身份与当前柜范围由 Token、Gateway 和服务端会话确定。页面同时支持 PDA 广播扫码与 H5 人工输入。 -- 登录、恢复和刷新会 fail-closed 校验国内/海外双 Token 的同一平台操作主体、`WAREHOUSE_PICKER`、`lastleg:pda:operate` 及同一冻结海外仓;并发海外 401 共享一次国内 refresh,校验完成后原子替换双 Token并将原请求或照片上传重放一次。 -- 库存盘点、商品移库、托盘管理、换标收货/执行/复核以及库存/库位/托盘查询当前缺少海外 PDA 专用 Controller、权限和 Token 冻结仓库范围契约,入口已禁用;不会调用平台、客户或内部接口,也不会模拟成功。 +- 登录、恢复和刷新会 fail-closed 校验国内/海外双 Token 的同一平台操作主体、`OVERSEAS_WAREHOUSE_PDA_OPERATOR`、`lastleg:pda:operate` 及同一冻结海外仓;并发海外 401 共享一次国内 refresh,校验完成后原子替换双 Token并将原请求或照片上传重放一次。 +- 库存盘点、商品移库、换标收货/执行/复核以及库存/库位/托盘查询仍未接入海外 PDA 前端,入口保持禁用;不会调用平台、客户或内部接口,也不会模拟成功。 - 本地契约测试与 H5/App 资源编译不等于真实开发环境 HTTP 或 PDA 真机验收。 ## App 在线更新 diff --git a/src/data/overseas-workbench.js b/src/data/overseas-workbench.js index 7ced9d5..f4b274d 100644 --- a/src/data/overseas-workbench.js +++ b/src/data/overseas-workbench.js @@ -5,7 +5,7 @@ export const MODULES = [ { key: 'inventory', name: '库内作业', nameKey: 'modules.inventory.name', icon: '库', description: '盘点、移库与托盘管理', descriptionKey: 'modules.inventory.description', operations: [ { key: 'stocktake', name: '库存盘点', nameKey: 'modules.stocktake.name', icon: '盘', iconType: 'list', description: '按任务扫描库位和库存', descriptionKey: 'modules.stocktake.description', available: false }, { key: 'move', name: '商品移库', nameKey: 'modules.move.name', icon: '移', iconType: 'loop', description: '扫描来源与目标库位完成移库', descriptionKey: 'modules.move.description', available: false }, - { key: 'pallet', name: '托盘管理', nameKey: 'modules.pallet.name', icon: '托', iconType: 'map', description: '组托、拆托及托盘状态核对', descriptionKey: 'modules.pallet.description', available: false } + { key: 'pallet', name: '托盘管理', nameKey: 'modules.pallet.name', icon: '托', iconType: 'map', description: '扫码查询、组托、拆托、换托及解绑', descriptionKey: 'modules.pallet.description', available: true, route: '/pages/pallet/pallet' } ] }, { key: 'relabel', name: '换标作业', nameKey: 'modules.relabel.name', icon: '换', description: '换标收货、执行与复核', descriptionKey: 'modules.relabel.description', operations: [ { key: 'relabelReceive', name: '换标收货', nameKey: 'modules.relabelReceive.name', icon: '收', iconType: 'download', description: '扫描换标单并确认待处理库存', descriptionKey: 'modules.relabelReceive.description', available: false }, diff --git a/src/pages.json b/src/pages.json index 5ce934d..250e454 100644 --- a/src/pages.json +++ b/src/pages.json @@ -4,6 +4,7 @@ { "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/pallet/pallet", "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 6d49ebc..aba4ce0 100644 --- a/src/pages/home/home.vue +++ b/src/pages/home/home.vue @@ -73,7 +73,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'}&op=${operation.key}` }) + uni.navigateTo({ url: operation.route || `/pages/operation/operation?module=${module?.key || 'query'}&op=${operation.key}` }) } } } diff --git a/src/pages/module/module.vue b/src/pages/module/module.vue index 667819e..f2b777d 100644 --- a/src/pages/module/module.vue +++ b/src/pages/module/module.vue @@ -5,7 +5,7 @@ {{ currentModule.icon }}{{ currentModule.name }}{{ currentModule.description }}{{ availableCount }}/{{ currentModule.operations.length }} 可用 业务功能点击可用功能开始作业 - 当前仅开放拆柜收货,其他作业暂未开放。 + 灰色功能尚未开放,仅可进入已接入真实接口的作业。 @@ -24,7 +24,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}&op=${operation.key}` }) } + openOperation(operation) { if (operation.available) uni.navigateTo({ url: operation.route || `/pages/operation/operation?module=${this.moduleKey}&op=${operation.key}` }) } } } diff --git a/src/pages/pallet/pallet.vue b/src/pages/pallet/pallet.vue new file mode 100644 index 0000000..3b0515d --- /dev/null +++ b/src/pages/pallet/pallet.vue @@ -0,0 +1,316 @@ +