diff --git a/AGENTS.md b/AGENTS.md
index 4434b34..122e731 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -21,7 +21,7 @@
- 并发401仅一次refresh,完整校验Token/快照再原子替换;主体/仓变化清会话重登录,原请求/照片上传最多重放一次。刷新失败清会话;业务/网络错留code、requestId、traceId或network诊断。
- 国内/海外 AppID、Android 包名、签名、版本号、产物、更新通道独立;`src/manifest.json` AppID须正式打包前由海外所属DCloud账号创建,禁伪造/复用国内AppID。
- 环境地址仅配 `.env.local` 等未提交环境文件,不入源码。
-- Android启动仅在 `APP-PLUS` 调 `src/utils/app-update.js`,以独立 `VITE_PDA_UPDATE_URL` 查HTTPS APK/WGT;强更禁跳过,WGT安装即重启。更新服务/标识/签名/版本序列不得与国内 PDA 混用。
+- Android仅在 `APP-PLUS` 调 `src/utils/app-update.js`,用独立 `VITE_PDA_UPDATE_URL` 查HTTPS APK/WGT;强更禁跳过,WGT安装即重启,不得与国内 PDA 混用。发布先装APK再发兼容WGT;原生变化先升APK。步骤及版本见 `README.md`“App 在线更新”。
- 源码根 `src/`;扫码广播 Action=`android.intent.action.SCANRESULT`,Extra=`value`。
- 退货号/SKU候选进入,按本包实收生成退货箱/计费批次;其他作业范围见下表及对应接口边界。
diff --git a/README.md b/README.md
index 9b0f087..315f4b9 100644
--- a/README.md
+++ b/README.md
@@ -72,10 +72,11 @@ HBuilderX 真机调试及标准基座使用当前工程 `src/manifest.json` 中
`packageType` 只允许 `apk` 或 `wgt`,`appid` 必须与当前安装包一致。WGT 清单还须提供 `minAppVersionCode`(最低兼容 APK 整数版本),不满足时客户端拒绝安装并提示升级 APK。`mandatory=true` 时员工不能跳过,并在下载或安装失败后提供重试;wgt 安装完成会立即重启,APK 交由 Android 安装器完成覆盖安装。正式发布要求:
-1. 海外 PDA 每次整包发布同时递增 `src/manifest.json` 的 `versionName` 与整数 `versionCode`,使用海外独立 AppID、包名和签名打包 APK。
-2. wgt 发布至少递增 `versionName`,并使用与已安装原生包兼容的 uni-app 编译器生成;新增原生模块、权限或不兼容运行时能力时必须发布 APK,不能只发 wgt。
-3. 更新清单与下载地址使用 HTTPS,先在测试设备验证可选更新、强制更新、断网重试、APK 覆盖安装、wgt 重启及扫码作业回归,再切换正式清单。
-4. 海外和国内更新记录、文件目录及发布权限保持隔离,服务端必须按请求中的 `appid` 拒绝跨应用更新包。
+1. 发布顺序固定为“先安装 APK 基线,再发布后续 WGT 热更新”。首次安装先使用海外独立 AppID、包名和签名打包 APK,在目标 PDA 安装后确认登录、扫码、拍照和“个人中心 → 检查更新”正常,再以该 APK 作为后续热更新基线。
+2. 后续兼容的页面、交互和业务逻辑修改发布 WGT。递增 `src/manifest.json` 的 `versionName`,使其高于设备当前的 `wgtVersion`,沿用 APK 基线的整数 `versionCode`,并使用与基线 APK 兼容的 uni-app 编译器生成。更新记录的 `minAppVersionCode` 填该 WGT 要求的 APK 基线版本码;设备低于基线时先安装新版 APK。
+3. 例如 APK 基线为 `0.0.5(versionCode=5)`,下一次 WGT 可发布 `0.0.6`,`minAppVersionCode=5`。安装基线 APK 后首次检查同版本资源会判定为最新版本;后续 WGT 使用更高的资源版本号。
+4. 新增原生模块、权限或不兼容运行时能力时,先同时递增 `versionName` 与整数 `versionCode` 并发布、安装新版 APK,完成真机验证后再基于新 APK 发布后续 WGT。
+5. 更新清单与下载地址使用 HTTPS。每次发布先在已安装对应 APK 基线的测试设备验证实际升级路径、断网重试、WGT 安装重启及本次受影响作业,再切换正式清单;新 APK 基线同时验证覆盖安装和签名。海外和国内更新记录、文件目录及发布权限保持隔离,服务端必须按请求中的 `appid` 拒绝跨应用更新包。
## H5 静态托管(TEST 与灰度)
diff --git a/src/manifest.json b/src/manifest.json
index f15f42a..6ba6975 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -2,8 +2,8 @@
"name" : "柏悦物流 PDA 海外版",
"appid" : "__UNI__936D079",
"description" : "柏悦物流海外仓 PDA 作业端",
- "versionName" : "0.0.4",
- "versionCode" : 4,
+ "versionName" : "0.0.5",
+ "versionCode" : 5,
"transformPx" : false,
"app-plus" : {
"usingComponents" : true,
diff --git a/src/pages/dispatch/dispatch.vue b/src/pages/dispatch/dispatch.vue
index b5ab505..f6aa225 100644
--- a/src/pages/dispatch/dispatch.vue
+++ b/src/pages/dispatch/dispatch.vue
@@ -78,7 +78,7 @@
拣货箱数本次最多可拣 {{ selectedRemaining }} 箱
- {{ current.actions.LOADING_COMPLETE?.blockReason || '尚未获得装车完成资格' }}
+ {{ current.actions.LOADING_COMPLETE?.blockReason || '尚未获得装车完成资格' }}
{{ successMessage }}
@@ -90,13 +90,6 @@
{{ scanNotice.packages }} 箱
3 秒后自动关闭
-
-
-
- 确认装车
- {{ loadingRows.length }} 托已拣托盘{{ loadingRows.reduce((sum, row) => sum + Number(row.pickedPackages || 0), 0) }} 箱已拣箱数
-
-
@@ -131,7 +124,7 @@ export default {
},
onLoad(options) { this.resumeOnEntry = options?.resume === '1' },
onShow() { if (!requireSession()) return; this.user = session.currentUser; restoreFulfillmentDraft(`${this.user?.id || ''}:${this.user?.warehouseId || ''}`); this.startScanner(); if (!this.bootstrapped) { this.bootstrapped = true; if (this.resumeOnEntry && fulfillment.dispatch.currentNo) this.openTask(fulfillment.dispatch.currentNo); else this.loadTasks() } else if (this.current?.dispatchNo) this.openTask(this.current.dispatchNo); else this.loadTasks() },
- onHide() { this.stopScanner(); this.clearScanNotice(); this.generation++ }, beforeUnmount() { this.stopScanner(); this.clearScanNotice(); this.generation++ },
+ onHide() { this.stopScanner(); this.clearScanNotice(); this.loadingPreview = null; this.generation++ }, beforeUnmount() { this.stopScanner(); this.clearScanNotice(); this.loadingPreview = null; this.generation++ },
methods: {
clearScanNotice() { clearTimeout(this._scanNoticeTimer); this._scanNoticeTimer = null; this.scanNotice = null },
showScanNotice(item) {
@@ -148,7 +141,7 @@ export default {
closeTask() { if (this.busy) return; this.current = null; this.activeTab = 'picking'; this.reviewReason = ''; this.loadingPreview = null; this.scanCode = ''; this.error = null; this.successMessage = ''; updateFulfillmentDraft('dispatch', { currentNo: '', palletCode: '', dispatchItemId: '', sourceKey: '', cartonCode: '', packages: 1 }); this.loadTasks() },
handleScan(raw) {
const code = String(raw?.detail?.value ?? raw ?? this.scanCode).trim()
- if (!code || this.busy || this.loading || this.activeTab !== 'picking') return
+ if (!code || this.busy || this.loading || this.loadingPreview || this.activeTab !== 'picking') return
this.clearScanNotice()
this.error = null; this.successMessage = ''; this.sourceChoices = []; this.choiceCartonCode = ''
updateFulfillmentDraft('dispatch', { palletCode: '', dispatchItemId: '', sourceKey: '', cartonCode: '', packages: 1 })
@@ -183,7 +176,42 @@ export default {
this.sourceChoices = []; this.choiceCartonCode = ''; this.error = null
},
async submitPick() { if (!this.canPick) return; try { const body = { recordVersion: requireDispatchVersion(this.current.recordVersion), positionId: this.selectedReturnCarton ? null : this.selectedPosition.positionId, packages: this.selectedReturnCarton ? 1 : Number(this.packages), productQuantity: this.selectedReturnCarton?.productQuantity || 0, cartonMark: this.selectedReturnCarton?.cartonMark || null, remark: null }; await this.submitCommand(this.current.dispatchNo, 'PICK', { ...body, dispatchItemId: this.selectedItem.dispatchItemId }) } catch (cause) { this.setError(cause, '拣货未提交') } },
- async prepareLoading() { if (!this.canComplete || this.busy || this.loading) return; await this.run(async () => { const data = await getDispatchLoading(this.current.dispatchNo); if (data.dispatchNo !== this.current.dispatchNo || data.pickingReconciled !== true || data.loadingComplete?.allowed !== true) throw new Error(data.loadingComplete?.blockReason || '装车前置未满足'); requireDispatchVersion(data.recordVersion); this.loadingPreview = data; this.loadingRows = data.pallets }) },
+ async prepareLoading() {
+ if (!this.canComplete || this.busy || this.loading || this.loadingPreview) return
+ const no = this.current.dispatchNo, generation = this.generation
+ await this.run(async () => {
+ const data = await getDispatchLoading(no)
+ if (generation !== this.generation || this.current?.dispatchNo !== no) return
+ if (data.dispatchNo !== no || data.pickingReconciled !== true || data.loadingComplete?.allowed !== true) throw new Error(data.loadingComplete?.blockReason || '装车前置未满足')
+ requireDispatchVersion(data.recordVersion)
+ if (!Array.isArray(data.pallets) || !data.pallets.length) throw new Error('装车清单未返回有效托盘,请重新核对')
+ this.loadingRows = data.pallets
+ this.loadingPreview = data
+ })
+ const preview = this.loadingPreview
+ if (!preview) return
+ const pickedPackages = this.loadingRows.reduce((sum, row) => sum + Number(row.pickedPackages || 0), 0)
+ uni.showModal({
+ title: '确认装车',
+ content: `已拣托盘 ${this.loadingRows.length} 托\n已拣箱数 ${pickedPackages} 箱`,
+ confirmText: '确认',
+ cancelText: '取消',
+ success: async result => {
+ if (this.loadingPreview !== preview) return
+ if (!result.confirm || generation !== this.generation || this.current?.dispatchNo !== no) {
+ this.loadingPreview = null
+ return
+ }
+ await this.completeLoading()
+ if (this.loadingPreview === preview) this.loadingPreview = null
+ },
+ fail: cause => {
+ if (this.loadingPreview !== preview) return
+ this.loadingPreview = null
+ this.setError(new Error(cause.errMsg || '无法打开装车确认,请重试'), '无法打开装车确认')
+ }
+ })
+ },
async completeLoading() { if (!this.loadingPreview || this.busy) return; try { if (!this.loadingRows.length || this.loadingRows.some(row => row.deliveryIndex == null || !Number.isInteger(row.deliveryIndex) || row.deliveryIndex < 1)) throw new Error('托盘卸货站尚未匹配,请后台核对'); await this.submitCommand(this.loadingPreview.dispatchNo, 'LOADING_COMPLETE', { recordVersion: requireDispatchVersion(this.loadingPreview.recordVersion), manifestHash: this.loadingPreview.manifestHash, remark: null }) } catch (cause) { this.setError(cause, '装车完成未确认') } },
updateReviewReason(event) { if (this.busy || this.loading) return; this.reviewReason = String(event.detail.value || '') },
async submitReview(decision) {
@@ -288,20 +316,8 @@ export default {
.review-card textarea { box-sizing: border-box; width: 100%; height: 160rpx; min-height: 160rpx; margin-top: 20rpx; padding: 16rpx; border: 1px solid #cbd5e1; border-radius: 8rpx; }
.loading-overlay { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background: rgba(15, 23, 42, .46); display: flex; align-items: center; justify-content: center; z-index: 100; padding: 40rpx; box-sizing: border-box; }
.loading-dialog { width: 100%; max-width: 620rpx; max-height: 88vh; overflow-y: auto; box-sizing: border-box; background: #fff; border-radius: 28rpx; padding: 36rpx 32rpx 32rpx; box-shadow: 0 24rpx 80rpx rgba(15, 23, 42, .18); }
-.loading-dialog-heading { display: flex; flex-direction: column; gap: 10rpx; }
.loading-dialog-title { color: #142c49; font-size: 36rpx; font-weight: 700; line-height: 1.4; }
-.loading-counts { display: flex; margin: 28rpx 0; padding: 24rpx 0; border-radius: 20rpx; background: #f3f7ff; }
-.loading-count { flex: 1; display: flex; align-items: center; flex-direction: column; gap: 8rpx; }
-.loading-count + .loading-count { border-left: 1px solid #dce6f6; }
-.loading-count-value { color: #1764ec; font-size: 48rpx; font-weight: 700; line-height: 1.2; }
.loading-count-unit { font-size: 26rpx; font-weight: 500; }
-.loading-count-label { color: #72839b; font-size: 24rpx; }
-.loading-dialog-actions { display: flex; gap: 20rpx; margin-top: 32rpx; }
-.loading-dialog-actions button { flex: 1; min-width: 0; margin: 0; height: 88rpx; line-height: 88rpx; padding: 0 12rpx; border-radius: 16rpx; font-size: 30rpx; font-weight: 600; }
-.loading-dialog-actions button::after { border: 0; }
-.loading-cancel { color: #52657d; background: #f0f3f8; }
-.loading-confirm { color: #fff; background: #1764ec; }
-.loading-dialog-actions button[disabled] { opacity: .5; }
.scan-notice-overlay { pointer-events: none; }
.scan-notice-dialog { text-align: center; }
.scan-notice-pallet { display: block; margin-top: 20rpx; color: #243c58; font-size: 30rpx; overflow-wrap: anywhere; }
diff --git a/src/pages/login/login.vue b/src/pages/login/login.vue
index ce38a78..8813430 100644
--- a/src/pages/login/login.vue
+++ b/src/pages/login/login.vue
@@ -7,6 +7,7 @@
工号 / 用户名
密码
+ 记住账号
{{ error }}
@@ -16,12 +17,25 @@