# PDA 作业端 面向仓库现场人员的移动作业应用。 技术方案:使用 uni-app Vue 3 开发,页面采用 Vue 3 兼容的 Options API,适配仓库 PDA 设备。 当前静态原型范围:人员登录、模块化工作台、入库/出库/库存/查询入口、作业记录、个人中心,以及出库模块内的箱号选择、扫码输入、整单拣货、部分拣货、异常原因、撤销和完成提交。 国内货柜履约装柜取证已具备接口适配基础:拣货页展示空仓、半仓、满仓、锁仓和带锁码照五类证据,经 File 服务上传取得真实 `fileId`,并展示待上传、上传中、失败、成功和重试状态。产品已确认 PDA 只能现场拍照,Admin 可人工上传;每类至少 1 张有效照片、同类型可多张追加,错误或重复照片只作废不硬删除。当前页面和上传队列仍按会话内单类型记录设计,尚未完整达到“多图追加+作废审计”契约。 登录后默认进入首页,不再直接进入拣货。首页当前只展示“入库 → 一件代发”和“出库 → 出库拣货”;“库位查询”等其余业务入口保留在数据、页面和路由中,通过 `visible` 隐藏,不从源码删除。首页左上角从 Warehouse 主档选择启用国内仓,首次未选择时自动打开;一件代发与出库拣货待办都按所选仓库从服务端查询。首页不再展示作业记录和个人中心入口,对应页面与路由仍保留。 ## 页面与状态结构 - `pages/login/login`:登录与作业仓库选择,是应用首屏; - `pages/home/home`:按业务分组的具体作业入口; - `pages/module/module`:入库、出库、库存、查询四类业务入口; - `pages/operation/operation`:非拣货业务共用的静态任务页; - `pages/history/history`:本次会话的作业记录; - `pages/profile/profile`:人员、班次、仓库信息及退出登录; - `pages/scanner-test/scanner-test`:Android 硬件扫描广播诊断页; - `pages/picking/picking`:出库拣货完整交互。 一件代发清点组托只调用 Firstleg 的 `/first-leg/pda-dropship` 正式接口。正式入口沿用 `pages/dropship-tasks/dropship-tasks` 和 `pages/dropship-palletization/dropship-palletization`。箱码先经 `cartons/resolve` 只读识别,确认后才调用 `cartons/count`;先扫托盘时确认箱码直接绑定当前托盘,先扫多箱时箱码进入待绑托区,后扫系统托盘码通过 `pallets/bind-pending` 批量绑定。所有写操作要求在线并携带稳定幂等标识,冲突后刷新权威任务;封托后的托盘只读并作为后续分柜、装柜的不可拆单元。 混托作业保持当前实物托盘,允许连续扫描不同客户的箱码。仓库级 `cartons/resolve` 只读返回原任务与箱内 SKU;确认清点时重新读取并领取原任务,再按原 taskId 写入,不将货物改归当前页客户。`pallets/resolve` 返回真实托盘状态和逐客户、逐票组成;未知托盘不能默认开放。切换入仓单携带已校验开放托盘,托内移箱使用箱的原任务;封托前显示各客户入仓码及箱数供核对。混托要求同作业仓、同渠道、同目的仓,封托和解封继续遵守正式后端门禁。合同及行为测试:`node --test tests/dropship*.test.mjs`。 登录会话集中在 `stores/session.js` 并持久化到设备,拣货会话集中在 `stores/picking.js`。认证请求由 `api/http.js` 统一处理:Access Token 临近过期时使用 Refresh Token 轮换;并发请求共享一次刷新;401 刷新成功后只重放一次;Refresh Token 明确失效时清理会话和作业草稿并返回登录页,网络或服务异常不会误清会话。产品契约要求未提交拣货草稿持久化到设备,并绑定作业员、任务和柜次,其他账号不能继承;成功提交或明确放弃后才清理。页面头部与底部主导航位于 `components/`,演示账号、模块和任务数据位于 `data/demo-data.js`,跨页面 PDA 视觉规则位于 `styles/pda.scss`。 拣货页会在设备端保留当前任务草稿和最近扫码结果,但正式作业以服务端事实为准:进入任务后由后端标记“拣货中”,每次有效箱码/托盘码扫描及“确认拣出、部分拣货、跳拣”操作立即写入服务端,后台智能装柜据此锁定已发生实物拣货的明细。底部提交只负责收尾当前仍未处理的明细并进入装柜取证;网络失败时页面不得先显示成功。允许部分拣货或跳拣,但每个未完成项必须填写异常原因,并挂起任务、阻断完成和 `LOADED`。 正式 PDA 作业必须联网。断网时不能开始正式拣货、提交、完成任务或推进 `LOADED`;网络恢复后由员工刷新并重试。提交使用对员工不可见的轻量幂等键和服务端版本:重复提交返回原结果,版本冲突提示刷新。项目不建设离线业务提交队列。 拣货页面和本地交互以 `../../docs/悦/PDA拣货/pda-picking.html` 为依据。PDA 采用 uni-app 开发并支持在线更新;Android App 端已按广播模式接入:Action=`android.intent.action.SCANRESULT`,字符串 Extra=`value`;拣货页的扫码框仅显示广播值和当前接收目标样式,不获取输入焦点或弹出软键盘,页面隐藏或销毁时会注销接收器。当前保留演示任务,同时已有国内货柜履约拣货、装柜证据和完成操作的远程接口适配;只有会话包含真实 Token、客户标识和入仓单标识时才会请求服务端,不会把缺少契约字段的本地操作模拟为远程成功。设备型号不构成开发、发布或验收门禁;正式鉴权和任务领取按服务端契约接入。 扫码输入统一使用 `components/ScanTargetInput.vue` 展示和选择目标,组件本身不注册广播。每个业务页面只在 `onShow` 注册一次广播、在 `onHide`/`onUnload` 注销,并通过页面内的 `activeScanField` 将广播值路由到当前选中的字段。多输入框页面应由用户点击切换蓝色“当前扫码”目标,避免多个输入框或隐藏页面同时消费同一次扫描。 声音反馈统一由 `utils/audio-feedback.js` 管理,内部复用一个 `uni.createInnerAudioContext()`,连续调用时会停止上一段声音,业务页面不要自行创建播放器: ```js import { playSuccessSound, playErrorSound } from '@/utils/audio-feedback.js' playSuccessSound() playErrorSound() ``` 应用需要主动停止或释放播放器时,可分别调用 `stopFeedbackSound()` 和 `destroyFeedbackSound()`。 ## 自动检查 项目未引入 npm 依赖,状态规则和页面契约使用 Node.js 内置测试运行器: ```powershell node --test tests/*.test.mjs ``` 当前环境验证使用 Node.js 22。测试同时约束 Vue 3 入口、页面契约和本地拣货状态规则,但不代替 H5、App 或真机交互验收。 H5 已使用 HBuilderX 5.15 自带的 uni-app Vue 3 编译器验证。先在 `.env.local` 配置当前环境的 `VITE_API_BASE_URL`、`VITE_H5_API_BASE_URL` 和 `API_PROXY_TARGET`,再执行: ```powershell & '\cli.exe' open & '\cli.exe' project open --path '<仓库绝对路径>\apps\pda-uniapp' & '\cli.exe' launch web --project '<仓库绝对路径>\apps\pda-uniapp' --browser Built ``` 命令会使用 HBuilderX 内置依赖启动 Vite H5 服务,实际端口以控制台输出为准。仓库仍未提供 `package.json`、锁文件或 npm 构建命令,也尚无可核实的 Android 构建命令和自动化 UI 测试设施;不得借用海外 PDA 的依赖版本替代。 ## 已确认但尚待实现/验收 - 在目标 Android PDA 上继续验证设备草稿按作业员、任务和柜次隔离及恢复。 - 在目标 Android PDA 上继续验证逐项即时提交、幂等重放、版本冲突刷新和异常挂起。 - 将五类证据从单槽位模型改为多图追加,补错误记录作废及 Admin 人工上传。 - 在目标 Android PDA 上验证状态栏、导航栏、软键盘避让、连续扫描、重复码、低电量和前后台切换。 - 按既定 uni-app 在线更新安排完成正式发布,并在目标设备验证扫描、网络和前后台行为。 ## 仍待产品确认 - 同一柜次是否允许多人并行拣货,以及任务拆分/合并口径。 - 退出任务或换班时是否释放任务、由谁接管草稿。 - 批量提交超时、业务错误和网络恢复后的具体提示与重试交互。 - 谁触发最终 `LOADED`,是否需要 Admin 二次复核。 - 哪些 Admin 角色可人工上传照片,以及照片可见范围和留存期限。 - 锁码格式、长度和唯一性规则。 订单与客户需要确认是否与管理后台头程订单及仓库任务同步上线。 ## App 在线更新 Android App 启动后会读取 `VITE_PDA_UPDATE_URL` 并检查国内 PDA 的独立更新通道,员工也可在“个人中心 → 检查更新”手动触发;H5 仅展示 App 操作提示,不执行更新。更新服务接收 `appid`、`platform`、`appVersion`、`appVersionCode`、`wgtVersion` 查询参数,无更新返回 `{ "update": false }`,有更新返回: ```json { "update": true, "appid": "<国内PDA AppID>", "packageType": "apk", "versionName": "0.0.2", "versionCode": 2, "downloadUrl": "https://updates.example.com/pda/domestic/0.0.2.apk", "mandatory": false, "releaseNotes": "本次更新内容" } ``` `packageType` 只允许 `apk` 或 `wgt`,`appid` 必须与当前安装包一致。WGT 清单还须提供 `minAppVersionCode`(最低兼容 APK 整数版本),不满足时客户端拒绝安装并提示升级 APK。`mandatory=true` 时员工不能跳过,并在下载或安装失败后提供重试;wgt 安装完成会立即重启,APK 交由 Android 安装器完成覆盖安装。正式发布要求: 1. 国内 PDA 每次整包发布同时递增 `manifest.json` 的 `versionName` 与整数 `versionCode`,使用国内独立 AppID、包名和签名打包 APK。 2. wgt 发布至少递增 `versionName`,并使用与已安装原生包兼容的 uni-app 编译器生成;新增原生模块、权限或不兼容运行时能力时必须发布 APK,不能只发 wgt。 3. 更新清单与下载地址使用 HTTPS,先在测试设备验证可选更新、强制更新、断网重试、APK 覆盖安装、wgt 重启及扫码作业回归,再切换正式清单。 4. 国内和海外更新记录、文件目录及发布权限保持隔离,服务端必须按请求中的 `appid` 拒绝跨应用更新包。 ## H5 静态托管(TEST 与灰度) 国内、海外 PDA H5 统一由宿主机已有 Nginx/宝塔站点直接提供静态文件,不再构建或发布 PDA H5 Docker 镜像、Compose 服务或容器。正式部署约束见 [测试环境单机部署与配置契约](../../docs/测试环境单机部署与配置契约.md) 的“PDA H5 发布方式”;历史灰度报告不再作为发布入口。 发布构建使用 `/` 资源 base 与 hash 路由,部署到本应用独立域名根目录,浏览器入口为 `/#/`。不能放到子目录后继续共用根路径资源,也不能将 Vite 开发代理当作线上配置。 国内使用本机已安装 HBuilderX:`cli.exe project open --path <本仓库绝对路径>` 后执行 `cli.exe publish web --project <本仓库绝对路径> --webHosting false --sourceMap false`,仅导出本地 Web 静态文件,不上传云托管。 发布步骤: 1. 构建后执行 `node scripts/verify-h5.mjs`,核对 HTML/JS/CSS 资源路径及同源 API。构建、测试仍按根规则经 `local-check.ps1` 执行。 2. 将 `unpackage/dist/build/web/` **目录内的文件**上传到该 PDA 站点现有 `root`。先上传 `assets/`、`static/` 等资源,最后替换 `index.html`,保留站点证书验证目录和站点配置;不要把外层构建目录一起嵌套上传。 3. 页面 `location /` 使用 `try_files $uri $uri/ =404` 直接读取静态文件,设置 `Cache-Control: no-store`;不得再转发到旧 PDA 容器。接口 `/gateway-api/` 独立转发到当前环境统一 Gateway `/api/`,保留 Authorization 和上传能力。 4. 首次切换前备份实际生效站点配置,合并下述片段并执行 `nginx -t`,成功后才重载。已有独立 API 规则先核对并复用,不能直接追加重复 `location`。后续仅更新静态文件时无需新建容器或重载 Nginx。 5. 检查域名首页及实际引用的 JS/CSS 均返回 200,并将响应内容的 SHA-256 与本次产物逐项比较;确认响应不是旧容器内容。再检查登录和受影响页面。首页 200 不等于接口或业务验收通过。 ### 站点配置片段 `deploy/nginx.conf.template` 是合并到已有 `server` 的参考片段,不是完整站点配置。保留现有域名、HTTPS、证书与宝塔 include;替换原有 root/index 及同名 location。环境地址、端口及网站目录只在部署侧配置,不提交到源码。 在部署机器上将 `PDA_SITE_ROOT` 设置为该站点真实根目录,`PDA_GATEWAY_ORIGIN` 设置为宿主机可达的当前环境 Gateway 源地址(协议、主机、端口,不含末尾 `/` 或 `/api`)。不要使用仅 Docker 网络可解析的服务名。显式只渲染这两个变量,保留 `$uri`、`$host` 和代理请求头变量: ```bash envsubst '${PDA_SITE_ROOT} ${PDA_GATEWAY_ORIGIN}' < deploy/nginx.conf.template ``` 输出仅供核对和合并,不直接覆盖完整站点。若机器没有 `envsubst`,手工替换这两个占位符;未替换的模板不能直接加载。 ### 两端统一 API 配置 App 的 `VITE_API_BASE_URL` 为统一 Gateway 绝对地址(含 `/api`);H5 使用 `VITE_H5_API_BASE_URL=/gateway-api`。`API_PROXY_TARGET` 仅供本地 Vite 开发代理使用,不随静态包发布。实际地址放在未提交环境文件;变更编译变量后须重新构建。 站点 API 规则须使用 `location ^~ /gateway-api/`,防止驾驶证等包含 `LICENSE` 的业务路径被站点敏感文件正则截获。仅更新静态包不能修复代理配置;域名入口和每层代理均须核对。 部署定向检查:`node --test tests/h5-deployment.test.mjs`,经 `local-check.ps1` 执行。该检查覆盖静态发布约束、资源 base 和开发代理重写,不替代目标主机 Nginx 校验、真实 HTTP、人工页面及 Android 真机验收。