From f960b9fe5f607dc91c13f4314b2818a7982d6895 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 4 Sep 2026 12:42:17 +0800 Subject: [PATCH] feat: complete overseas PDA warehouse operations --- AGENTS.md | 19 ++-- README.md | 6 +- src/api/overseas.js | 4 +- src/api/relabel.js | 44 +++++++++ src/api/warehouse-operations.js | 70 ++++++++++++++ src/data/overseas-workbench.js | 47 ++------- src/pages.json | 4 + src/pages/move/move.vue | 65 +++++++++++++ src/pages/query/query.vue | 55 +++++++++++ src/pages/relabel/relabel.vue | 105 ++++++++++++++++++++ src/pages/stocktake/stocktake.vue | 80 ++++++++++++++++ src/pages/warehouse/operations.scss | 1 + src/stores/session.js | 2 + src/stores/warehouse-work.js | 74 ++++++++++++++ tests/business-identifiers-ui.test.mjs | 10 +- tests/project-contract.test.mjs | 14 +-- tests/warehouse-workflows.test.mjs | 127 +++++++++++++++++++++++++ tests/workbench-availability.test.mjs | 17 +++- 18 files changed, 678 insertions(+), 66 deletions(-) create mode 100644 src/api/relabel.js create mode 100644 src/api/warehouse-operations.js create mode 100644 src/pages/move/move.vue create mode 100644 src/pages/query/query.vue create mode 100644 src/pages/relabel/relabel.vue create mode 100644 src/pages/stocktake/stocktake.vue create mode 100644 src/pages/warehouse/operations.scss create mode 100644 src/stores/warehouse-work.js create mode 100644 tests/warehouse-workflows.test.mjs diff --git a/AGENTS.md b/AGENTS.md index d0a0095..e499646 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,13 +4,13 @@ 1. 先用“页面与能力快速索引”定位 `src/pages.json`、工作台或最小页面;页面业务配置从 `src/data/overseas-workbench.js` 进入,不扫描 `src/uni_modules/`。 2. 登录、Token 或双区域请求先读 `src/api/auth.js`、`src/stores/session.js`、`src/config/runtime.js` 和 `../../docs/跨区域认证与服务通信契约.md`。 -3. 海外入仓、库存或仓储资源联调先读 `../../docs/四期/README.md`、`../../baiyue-firstleg/lastleg-service/AGENTS.md`、`warehouse-overseas-service/AGENTS.md` 与 `gateway-overseas/AGENTS.md`。 +3. 海外入仓、库存或仓储资源联调先读 `../../docs/海外仓业务/README.md`、`../../baiyue-firstleg/lastleg-service/AGENTS.md`、`warehouse-overseas-service/AGENTS.md` 与 `gateway-overseas/AGENTS.md`。 ## 项目定位 - 本项目是“柏悦物流海外版”独立 uni-app Vue 3 工程,面向海外仓现场人员。 - 工程必须独立安装、构建和发布,不得依赖 `apps/pda-uniapp/` 或仓库外 `pda-shared` 目录。 -- 当前设备端业务范围包括四期入仓、库内、换标、库存/库位/托盘查询,以及五期排车拣货、普通/一件代发出库任务、任务完成和发车;不包含派送、客户回访,也不包含国内集装箱拣货/装柜。 +- 当前设备端业务范围包括海外仓入仓、库内、换标、库存/库位/托盘查询,以及排车拣货、普通/一件代发出库任务、任务完成和发车;派送与客户回访由 Admin/Client 承载,国内集装箱拣货/装柜由国内 PDA 承载。 ## 服务与发布边界 @@ -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 逐箱收货并逐入仓码核平;托盘管理已接扫码查询、组托、拆托、换托和解绑专用 PDA API。其余库内、换标与查询入口在前端完成真实契约接入前必须禁用,不得调用平台/客户/内部接口或模拟成功。 +- 入仓作业已接柜号级真实海外 API:账号锁柜、三类照片签收、统一箱码定位入仓码、FBA 按箱数收货、OVERSEAS 逐箱收货并逐入仓码核平;库内盘点、移库、托盘管理、换标收货/执行/复核以及库存/库位/托盘查询均已接入专用 PDA API。 ## 页面与能力快速索引 @@ -42,9 +42,12 @@ | 中英西语言设置 | `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/pages/pallet/pallet.vue`、`src/api/pallet.js` | 扫码查询托盘内容,按服务端返回的托盘/库存版本执行组托、拆托、换托和解绑;公网前缀 `/api/pda/v1/last-leg/pallet-operations/**` | -| 五期排车拣货与发车 | `src/pages/dispatch/dispatch.vue`、`src/api/fulfillment.js`、`src/stores/fulfillment.js` | 任务列表/详情、托盘业务码扫码拣货、幂等完成;发车必须同时完成拣货核平、平台转派送并严格消费服务端 `canDepart`、`departBlockReason`,不得按本地数量自行推断;公网前缀 `/api/pda/v1/last-leg/dispatches/**` | -| 五期普通/一件代发出库 | `src/pages/outbound/outbound.vue`、`src/api/fulfillment.js`、`src/stores/fulfillment.js` | 任务列表/详情、稳定业务码扫码拣货和幂等完成;公网前缀 `/api/pda/v1/last-leg/outbound-tasks/**`;派送不进入 PDA | -| 其余库内、换标与现场查询待接前端 | `src/data/overseas-workbench.js` 中对应 `available: false` 入口 | Backend 已有 `pda/stocktake/`、`pda/move/`、`pda/warehouse/`、`relabel/pda/` 与 `overseas-pda-operations-v1.yaml`;本应用新增专用 API/状态恢复和测试前不得启用 | +| 库存盘点真实作业 | `src/pages/stocktake/stocktake.vue`、`src/api/warehouse-operations.js`、`src/stores/warehouse-work.js` | 创建/恢复盘点任务,扫描库位和库存后计数、提交;严格使用服务端版本和幂等键 | +| 商品移库真实作业 | `src/pages/move/move.vue`、`src/api/warehouse-operations.js`、`src/stores/warehouse-work.js` | 扫描来源托盘与目标库位执行同仓移库;冲突后重新读取服务端事实 | +| 换标真实作业 | `src/pages/relabel/relabel.vue`、`src/api/relabel.js` | 同一页面按操作参数承载换标收货、执行与复核;支持扫码、拍照、异常/返工和幂等恢复 | +| 现场查询真实作业 | `src/pages/query/query.vue`、`src/api/warehouse-operations.js` | 同一页面按操作参数承载库存、库位与托盘查询,只展示稳定业务标识 | +| 排车拣货与发车 | `src/pages/dispatch/dispatch.vue`、`src/api/fulfillment.js`、`src/stores/fulfillment.js` | 任务列表/详情、托盘业务码扫码拣货、幂等完成;发车必须同时完成拣货核平、平台转派送并严格消费服务端 `canDepart`、`departBlockReason`,不得按本地数量自行推断;公网前缀 `/api/pda/v1/last-leg/dispatches/**` | +| 普通/一件代发出库 | `src/pages/outbound/outbound.vue`、`src/api/fulfillment.js`、`src/stores/fulfillment.js` | 任务列表/详情、稳定业务码扫码拣货和幂等完成;公网前缀 `/api/pda/v1/last-leg/outbound-tasks/**`;派送由 Admin/Client 承载 | | 扫码设备与诊断 | `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` | 会话隔离、退出清理和后续真实作业历史契约 | | 业务标识与内部 ID 隔离 | `tests/business-identifiers-ui.test.mjs`;页面入口 `src/pages/`、`src/components/`,会话映射 `src/stores/session.js` | 页面只展示集装箱号、入仓码、箱码、托盘码、库位码、仓库名称等业务值;不得以 orderId、inboundId、planId、warehouseId、palletId、locationId、user.id 等内部 ID 或截断值回退,错误诊断保留 code、requestId、traceId | @@ -57,10 +60,10 @@ - `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 冻结仓库范围和服务端资源归属约束;写操作必须保留稳定幂等键,409 后重新读取服务端状态,不得把平台、客户或内部接口直接暴露给 PDA。 - 已有海外入仓 PDA Controller 统一位于 Lastleg `/api/pda/v1/last-leg/inbound-plans/{planNo}/**`;浏览器或设备请求仍必须经 `gateway-overseas` 的显式 PDA 路由,不得直连 Lastleg,也不得改走国内 Gateway。 - 海外业务 Token、PDA 权限和可信身份最终由 Gateway 与服务端校验;前端不得用工作台 `available`、本地客户 ID 或仓库 ID 代替授权和资源归属校验。 -- 五期排车和出库入口只调用专用 PDA 接口;客户与冻结海外仓范围由 Token、Gateway 和服务端解析,前端不提交 `customerId`、`warehouseId`,也不把内部数据库 ID 展示成业务编号。网络失败保留草稿和幂等键,恢复后以服务端任务状态为准;排车详情返回的 `canDepart`、`departBlockReason` 是发车按钮唯一门禁,平台未转派送生成 `WAIT_OUTBOUND` 出库单时不得发车;完成或发车成功后不得重复提交。派送始终不进入 PDA。 +- 排车和出库入口只调用专用 PDA 接口;客户与冻结海外仓范围由 Token、Gateway 和服务端解析,前端不提交 `customerId`、`warehouseId`,也不把内部数据库 ID 展示成业务编号。网络失败保留草稿和幂等键,恢复后以服务端任务状态为准;排车详情返回的 `canDepart`、`departBlockReason` 是发车按钮唯一门禁,平台未转派送生成 `WAIT_OUTBOUND` 出库单时不得发车;完成或发车成功后不得重复提交。派送始终由 Admin/Client 承载。 ## 质量门禁 diff --git a/README.md b/README.md index ffa70da..6ee0a84 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 独立的 uni-app Vue 3 App 工程,正式产品形态是安装在海外仓手持 PDA 上的 App。工程不依赖国内 PDA 源码或仓库外共享目录,可独立安装依赖、运行、编译和发布;H5 只用于浏览器调试和编译门禁,不是最终交付形态。 -工作台提供柜号级真实入仓、托盘管理,以及五期排车拣货、出库任务、一件代发出库、任务完成和发车入口;派送单、派送状态与客户回访不进入 PDA。登录使用国内 Auth 统一签发的双 Token;海外业务请求统一经 `gateway-overseas`,不得请求国内 Firstleg/Warehouse 业务接口。 +工作台提供柜号级真实入仓、托盘管理、排车拣货、出库任务、一件代发出库、任务完成和发车入口;派送单、派送状态与客户回访由 Admin/Client 承载。登录使用国内 Auth 统一签发的双 Token;海外业务请求统一经 `gateway-overseas`,不得请求国内 Firstleg/Warehouse 业务接口。 ## 本地运行与检查 @@ -44,10 +44,10 @@ npm run build:app - 托盘管理已接 `/api/pda/v1/last-leg/pallet-operations/**`:支持扫描查询当前仓实物托盘及库存内容,并按服务端版本执行组托、拆托、换托和解绑;不同目的仓不能合托,冻结或处理中的库存由服务端拒绝。 - 排车拣货已接 `/api/pda/v1/last-leg/dispatches/**`:支持任务列表、业务单号恢复、托盘码扫码拣货、任务完成及全部箱数核平后的发车;服务端仍会校验关联出库单和托盘门禁。 - 出库作业已接 `/api/pda/v1/last-leg/outbound-tasks/**`:支持普通出库和一件代发任务列表/详情、托盘码/箱码/SKU/入仓码扫码拣货与核平完成;不会展示或进入派送流程。 -- 五期写操作使用可恢复的稳定幂等键。网络失败保留当前任务、扫码草稿和幂等键;成功后释放键并按服务端状态恢复,避免重复完成或发车。 +- 排车与出库写操作使用可恢复的稳定幂等键。网络失败保留当前任务、扫码草稿和幂等键;成功后释放键并按服务端状态恢复,避免重复完成或发车。 - 入仓请求不手填客户、计划、分配、仓库或库位标识;身份与当前柜范围由 Token、Gateway 和服务端会话确定。页面同时支持 PDA 广播扫码与 H5 人工输入。 - 登录、恢复和刷新会 fail-closed 校验国内/海外双 Token 的同一平台操作主体、`OVERSEAS_WAREHOUSE_PDA_OPERATOR`、`lastleg:pda:operate` 及同一冻结海外仓;并发海外 401 共享一次国内 refresh,校验完成后原子替换双 Token并将原请求或照片上传重放一次。 -- 库存盘点、商品移库、换标收货/执行/复核以及库存/库位/托盘查询仍未接入海外 PDA 前端,入口保持禁用;不会调用平台、客户或内部接口,也不会模拟成功。 +- 库存盘点、商品移库、换标收货/执行/复核以及库存/库位/托盘查询均已接入 `/api/pda/**` 专用接口;支持 PDA 广播扫码与 H5 人工输入,并按服务端版本、幂等键和冲突状态恢复,不调用平台、客户或内部接口。 - 本地契约测试与 H5/App 资源编译不等于真实开发环境 HTTP 或 PDA 真机验收。 ## App 在线更新 diff --git a/src/api/overseas.js b/src/api/overseas.js index 8a6eb31..0171768 100644 --- a/src/api/overseas.js +++ b/src/api/overseas.js @@ -77,7 +77,7 @@ function rawUpload(path, filePath, name, token) { name, header: { Authorization: `Bearer ${token}` }, success: resolve, - fail: (cause) => reject(Object.assign(new Error(cause.errMsg || '卸柜照片上传失败'), { network: true })) + fail: (cause) => reject(Object.assign(new Error(cause.errMsg || '海外作业文件上传失败'), { network: true })) }) }) } @@ -111,7 +111,7 @@ export async function overseasUpload(path, filePath, name = 'file', retry = true response = await rawUpload(path, filePath, name, replacementToken) try { body = JSON.parse(response.data || '{}') } catch (_) { body = null } } - if (!successful(response, body)) throw errorFrom({ ...response, data: body }, '卸柜照片上传失败') + if (!successful(response, body)) throw errorFrom({ ...response, data: body }, '海外作业文件上传失败') return body.data } diff --git a/src/api/relabel.js b/src/api/relabel.js new file mode 100644 index 0000000..1460d8d --- /dev/null +++ b/src/api/relabel.js @@ -0,0 +1,44 @@ +import { overseasRequest, overseasUpload } from './overseas.js' + +const BASE = '/pda/v1/last-leg/relabel-tasks' +const encoded = (value) => encodeURIComponent(String(value || '').trim()) + +function asList(value) { return Array.isArray(value) ? value : [] } + +export function normalizeRelabel(value = {}) { + return { + ...value, + relabelNo: String(value.relabelNo || ''), customerOrderNo: String(value.customerOrderNo || ''), + version: Number(value.version || 0), expectedPackages: Number(value.expectedPackages || 0), + relabelledPackages: Number(value.relabelledPackages || 0), skippedPackages: Number(value.skippedPackages || 0), + exceptionPackages: Number(value.exceptionPackages || 0), + items: asList(value.items).map((item) => ({ + ...item, itemId: String(item.itemId || ''), skuCode: String(item.skuCode || ''), + newSkuCode: String(item.newSkuCode || ''), expectedPackages: Number(item.expectedPackages || 0), + relabelledPackages: Number(item.relabelledPackages || 0), skippedPackages: Number(item.skippedPackages || 0), + exceptionPackages: Number(item.exceptionPackages || 0) + })), + unitFacts: asList(value.unitFacts), history: asList(value.history), + completionPhotoFileIds: asList(value.completionPhotoFileIds).map(String) + } +} + +export async function listRelabelCandidates() { + return asList(await overseasRequest('GET', `${BASE}/candidates`)).map(normalizeRelabel) +} + +export async function getRelabelTask(relabelNo) { + return normalizeRelabel(await overseasRequest('GET', `${BASE}/${encoded(relabelNo)}`)) +} + +async function detailAction(relabelNo, suffix, request) { + return normalizeRelabel(await overseasRequest('POST', `${BASE}/${encoded(relabelNo)}${suffix}`, request)) +} + +export const claimRelabelTask = (relabelNo, request) => detailAction(relabelNo, '/claim', request) +export const relabelUnit = (relabelNo, request) => detailAction(relabelNo, '/units', request) +export const skipRelabelUnit = (relabelNo, request) => detailAction(relabelNo, '/units/skip', request) +export const reportRelabelException = (relabelNo, request) => detailAction(relabelNo, '/exceptions', request) +export const completeRelabelTask = (relabelNo, request) => detailAction(relabelNo, '/complete', request) +export const reviewRelabelTask = (relabelNo, request) => detailAction(relabelNo, '/review', request) +export const uploadRelabelPhoto = (relabelNo, filePath) => overseasUpload(`${BASE}/${encoded(relabelNo)}/photos/files`, filePath) diff --git a/src/api/warehouse-operations.js b/src/api/warehouse-operations.js new file mode 100644 index 0000000..9b01388 --- /dev/null +++ b/src/api/warehouse-operations.js @@ -0,0 +1,70 @@ +import { overseasRequest } from './overseas.js' + +const BASE = '/pda/v1/last-leg' +const encoded = (value) => encodeURIComponent(String(value || '').trim()) + +function asList(value) { + if (Array.isArray(value)) return value + if (Array.isArray(value?.records)) return value.records + return [] +} + +function count(value) { + const result = Number(value || 0) + return Number.isFinite(result) ? Math.max(0, result) : 0 +} + +export function normalizeStocktake(value) { + const task = value?.task || value || {} + return { + ...value, + task: { ...task, taskNo: String(task.taskNo || ''), scopeCode: String(task.scopeCode || ''), version: count(task.version) }, + items: asList(value?.items).map((item) => ({ + ...item, + itemId: String(item.itemId || ''), balanceId: String(item.balanceId || ''), positionId: String(item.positionId || ''), + palletCode: String(item.palletCode || ''), locationCode: String(item.locationCode || ''), + bookPackages: count(item.bookPackages), actualPackages: item.actualPackages == null ? null : count(item.actualPackages), + differencePackages: item.differencePackages == null ? null : Number(item.differencePackages), + positionVersion: count(item.positionVersion), version: count(item.version) + })) + } +} + +export async function listStocktakes(status = 'IN_PROGRESS') { + const suffix = `?page=1&size=100${status ? `&status=${encoded(status)}` : ''}` + return asList(await overseasRequest('GET', `${BASE}/stocktakes${suffix}`)).map((item) => ({ + ...item, taskNo: String(item.taskNo || ''), scopeCode: String(item.scopeCode || ''), version: count(item.version) + })) +} + +export async function createStocktake(request) { + return normalizeStocktake(await overseasRequest('POST', `${BASE}/stocktakes`, request)) +} + +export async function getStocktake(taskNo) { + return normalizeStocktake(await overseasRequest('GET', `${BASE}/stocktakes/${encoded(taskNo)}`)) +} + +export function countStocktake(taskNo, request) { + return overseasRequest('POST', `${BASE}/stocktakes/${encoded(taskNo)}/counts`, request) +} + +export async function submitStocktake(taskNo, request) { + return normalizeStocktake(await overseasRequest('POST', `${BASE}/stocktakes/${encoded(taskNo)}/submit`, request)) +} + +export function moveInventory(request) { + return overseasRequest('POST', `${BASE}/inventory-moves`, request) +} + +export function resolveInventory(code) { + return overseasRequest('POST', `${BASE}/inventory/resolve`, { code: String(code || '').trim() }) +} + +export function resolveWarehouseLocation(code) { + return overseasRequest('POST', `${BASE}/locations/resolve`, { code: String(code || '').trim() }) +} + +export function resolveWarehousePallet(code) { + return overseasRequest('POST', `${BASE}/pallets/resolve`, { code: String(code || '').trim() }) +} diff --git a/src/data/overseas-workbench.js b/src/data/overseas-workbench.js index da27998..0e26b0f 100644 --- a/src/data/overseas-workbench.js +++ b/src/data/overseas-workbench.js @@ -7,49 +7,18 @@ export const MODULES = [ { key: 'outboundPicking', name: '出库作业', nameKey: 'modules.outboundPicking.name', icon: '拣', iconType: 'checkbox', description: '处理出库与一件代发任务,扫码核平后完成', descriptionKey: 'modules.outboundPicking.description', available: true, route: '/pages/outbound/outbound' } ] }, { 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: 'stocktake', name: '库存盘点', nameKey: 'modules.stocktake.name', icon: '盘', iconType: 'list', description: '按任务扫描库位和库存', descriptionKey: 'modules.stocktake.description', available: true, route: '/pages/stocktake/stocktake' }, + { key: 'move', name: '商品移库', nameKey: 'modules.move.name', icon: '移', iconType: 'loop', description: '扫描来源与目标库位完成移库', descriptionKey: 'modules.move.description', available: true, route: '/pages/move/move' }, { 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 }, - { key: 'relabelExecute', name: '换标执行', nameKey: 'modules.relabelExecute.name', icon: '换', iconType: 'compose', description: '扫描旧标和新标记录换标结果', descriptionKey: 'modules.relabelExecute.description', available: false }, - { key: 'relabelReview', name: '换标复核', nameKey: 'modules.relabelReview.name', icon: '核', iconType: 'checkbox', description: '复核标签和商品关联关系', descriptionKey: 'modules.relabelReview.description', available: false } + { key: 'relabelReceive', name: '换标收货', nameKey: 'modules.relabelReceive.name', icon: '收', iconType: 'download', description: '扫描换标单并确认待处理库存', descriptionKey: 'modules.relabelReceive.description', available: true, route: '/pages/relabel/relabel?op=relabelReceive' }, + { key: 'relabelExecute', name: '换标执行', nameKey: 'modules.relabelExecute.name', icon: '换', iconType: 'compose', description: '扫描旧标和新标记录换标结果', descriptionKey: 'modules.relabelExecute.description', available: true, route: '/pages/relabel/relabel?op=relabelExecute' }, + { key: 'relabelReview', name: '换标复核', nameKey: 'modules.relabelReview.name', icon: '核', iconType: 'checkbox', description: '复核标签和商品关联关系', descriptionKey: 'modules.relabelReview.description', available: true, route: '/pages/relabel/relabel?op=relabelReview' } ] }, { key: 'query', name: '现场查询', nameKey: 'modules.query.name', icon: '查', description: '库存、库位与托盘查询', descriptionKey: 'modules.query.description', operations: [ - { key: 'inventoryQuery', name: '库存查询', nameKey: 'modules.inventoryQuery.name', icon: '存', iconType: 'search', description: '扫描商品或箱码查询可用库存', descriptionKey: 'modules.inventoryQuery.description', available: false }, - { key: 'locationQuery', name: '库位查询', nameKey: 'modules.locationQuery.name', icon: '位', iconType: 'location', description: '扫描库位查看容量和在库明细', descriptionKey: 'modules.locationQuery.description', available: false }, - { key: 'palletQuery', name: '托盘查询', nameKey: 'modules.palletQuery.name', icon: '托', iconType: 'flag', description: '扫描托盘码查看绑定库存', descriptionKey: 'modules.palletQuery.description', available: false } + { key: 'inventoryQuery', name: '库存查询', nameKey: 'modules.inventoryQuery.name', icon: '存', iconType: 'search', description: '扫描商品或箱码查询可用库存', descriptionKey: 'modules.inventoryQuery.description', available: true, route: '/pages/query/query?op=inventoryQuery' }, + { key: 'locationQuery', name: '库位查询', nameKey: 'modules.locationQuery.name', icon: '位', iconType: 'location', description: '扫描库位查看容量和在库明细', descriptionKey: 'modules.locationQuery.description', available: true, route: '/pages/query/query?op=locationQuery' }, + { key: 'palletQuery', name: '托盘查询', nameKey: 'modules.palletQuery.name', icon: '托', iconType: 'flag', description: '扫描托盘码查看绑定库存', descriptionKey: 'modules.palletQuery.description', available: true, route: '/pages/query/query?op=palletQuery' } ] } ] - -const PROTOTYPES = { - '到仓登记': { subtitle: '核对入仓计划与车辆到仓信息', scanLabel: '入仓计划 / 柜号', scanPlaceholder: '扫描入仓计划号或柜号', prefix: 'INB', titles: ['海外仓入仓计划', '合作仓到仓任务'], meta: '等待到仓', location: '收货月台 R-01', quantity: '12 托 · 86 箱', action: '开始登记' }, - '卸柜收货': { subtitle: '按箱或托盘确认卸柜实收', scanLabel: '箱码 / 托盘码', scanPlaceholder: '扫描箱码或托盘码', prefix: 'RCV', titles: ['海运柜卸柜收货', '转运车辆卸货'], meta: '卸柜中', location: '卸货区 U-02', quantity: '待收 68 箱', action: '继续收货' }, - '入仓上架': { subtitle: '将已收货库存上架到指定库位', scanLabel: '容器码 / 库位码', scanPlaceholder: '扫描容器或目标库位', prefix: 'PUT', titles: ['到仓库存上架', '待上架托盘任务'], meta: '待上架', location: 'S-01 → A-03-12', quantity: '8 托 · 42 箱', action: '开始上架' }, - '库存盘点': { subtitle: '按盘点任务扫描库位和库存', scanLabel: '盘点单 / 库位码', scanPlaceholder: '扫描盘点单或库位码', prefix: 'CC', titles: ['A 区循环盘点', '异常库位复盘'], meta: '动态盘点', location: '库区 A-03', quantity: '24 库位', action: '开始盘点' }, - '商品移库': { subtitle: '从来源库位移动到目标库位', scanLabel: '移库单 / 库位码', scanPlaceholder: '扫描移库单或来源库位', prefix: 'MV', titles: ['库位容量优化', '换标区库存移库'], meta: '待执行', location: 'A-05 → B-01', quantity: '6 托 · 28 箱', action: '开始移库' }, - '托盘管理': { subtitle: '核对托盘状态并执行组托或拆托', scanLabel: '托盘码', scanPlaceholder: '扫描托盘码', prefix: 'PLT', titles: ['到仓库存组托', '异常托盘拆托'], meta: '托盘作业', location: '托盘区 P-02', quantity: '6 个托盘', action: '开始处理' }, - '换标收货': { subtitle: '确认换标单与待处理库存', scanLabel: '换标单 / 箱码', scanPlaceholder: '扫描换标单号或箱码', prefix: 'RL-R', titles: ['FBA 换标收货', '退货库存换标收货'], meta: '待收货', location: '换标区 L-01', quantity: '36 箱', action: '开始收货' }, - '换标执行': { subtitle: '扫描旧标与新标记录换标结果', scanLabel: '旧标 / 新标', scanPlaceholder: '扫描商品标签', prefix: 'RL-W', titles: ['FBA 标签更换', '商品条码更正'], meta: '换标中', location: '换标台 L-03', quantity: '待换 120 件', action: '继续换标' }, - '换标复核': { subtitle: '复核标签与商品关联关系', scanLabel: '换标单 / 新标', scanPlaceholder: '扫描换标单或新标签', prefix: 'RL-C', titles: ['换标结果复核', '异常标签复核'], meta: '待复核', location: '复核台 L-04', quantity: '待核 42 件', action: '开始复核' }, - '库存查询': { subtitle: '查询商品、箱和托盘的库存状态', scanLabel: '商品码 / 箱码', scanPlaceholder: '扫描商品或箱码', prefix: 'SKU', titles: ['商品库存查询', '箱库存查询'], meta: '可用库存', location: 'A-03-12', quantity: '可用 1,280 件', action: '查看详情' }, - '库位查询': { subtitle: '查看库位容量、状态与在库明细', scanLabel: '库位码', scanPlaceholder: '扫描库位编码', prefix: 'LOC', titles: ['标准货架位', '托盘库位'], meta: '库位正常', location: 'A 区 03 通道', quantity: '占用 72%', action: '查看详情' }, - '托盘查询': { subtitle: '查看托盘状态与绑定库存', scanLabel: '托盘码', scanPlaceholder: '扫描托盘码', prefix: 'PLT', titles: ['在库托盘', '待处理托盘'], meta: '托盘正常', location: 'B-02-08', quantity: '绑定 12 箱', action: '查看详情' } -} - -export function getOperationPrototype(name) { - const prototype = PROTOTYPES[name] || PROTOTYPES['库存查询'] - return { - ...prototype, - metrics: [ - { label: '待处理', value: 6, tone: 'metric-primary' }, - { label: '进行中', value: 2, tone: 'metric-warning' }, - { label: '今日完成', value: 18, tone: 'metric-success' } - ], - tasks: [ - { code: `${prototype.prefix}-260820-001`, title: prototype.titles[0], meta: prototype.meta, location: prototype.location, quantity: prototype.quantity, action: prototype.action, status: '待处理', tone: 'status-pending' }, - { code: `${prototype.prefix}-260820-002`, title: prototype.titles[1], meta: prototype.meta, location: prototype.location, quantity: prototype.quantity, action: '继续处理', status: '进行中', tone: 'status-progress' } - ] - } -} diff --git a/src/pages.json b/src/pages.json index e3d882c..1f7abe7 100644 --- a/src/pages.json +++ b/src/pages.json @@ -5,6 +5,10 @@ { "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/stocktake/stocktake", "style": { "navigationStyle": "custom", "navigationBarTitleText": "库存盘点", "backgroundColor": "#F5F7FA" } }, + { "path": "pages/move/move", "style": { "navigationStyle": "custom", "navigationBarTitleText": "商品移库", "backgroundColor": "#F5F7FA" } }, + { "path": "pages/relabel/relabel", "style": { "navigationStyle": "custom", "navigationBarTitleText": "换标作业", "backgroundColor": "#F5F7FA" } }, + { "path": "pages/query/query", "style": { "navigationStyle": "custom", "navigationBarTitleText": "现场查询", "backgroundColor": "#F5F7FA" } }, { "path": "pages/dispatch/dispatch", "style": { "navigationStyle": "custom", "navigationBarTitleText": "排车拣货与发车", "backgroundColor": "#F5F7FA" } }, { "path": "pages/outbound/outbound", "style": { "navigationStyle": "custom", "navigationBarTitleText": "出库作业", "backgroundColor": "#F5F7FA" } }, { "path": "pages/history/history", "style": { "navigationStyle": "custom", "navigationBarTitleText": "作业记录", "backgroundColor": "#F5F7FA" } }, diff --git a/src/pages/move/move.vue b/src/pages/move/move.vue new file mode 100644 index 0000000..b85adb0 --- /dev/null +++ b/src/pages/move/move.vue @@ -0,0 +1,65 @@ +