From 0cdd709e662dd7bad3f9b48d153e815ab6636fa1 Mon Sep 17 00:00:00 2001 From: joywayer Date: Mon, 22 Jun 2026 08:02:24 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=AD=E4=BC=98=203=20=E9=A1=B9=E6=94=B6?= =?UTF-8?q?=E5=B0=BE=EF=BC=9APlan=20=C2=A76.4=20=E9=87=8C=E7=A8=8B?= =?UTF-8?q?=E7=A2=91=20+=20Design=20=C2=A720.1=20=E6=8B=86=E5=88=86=20+=20?= =?UTF-8?q?Contract=20=C2=A710=20=E9=AA=8C=E6=94=B6=E6=B8=85=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 补完本轮 audit 与 Design 大改后的文档边界,避免后续 Phase 实施者 按陈旧描述走弯路。 1. Plan §6.4 文档同步段新增「Design 接口骨架覆盖里程碑」表: - 4 行时间线记录 Phase 1 闭环 / Design 全 51 项接口骨架 / Contract §4.2 按 daoqi 原项目修订 / CLAUDE.md 加典型案例的 commit hash - 末尾澄清「§3.4.1 撤销说明」与 §7.5 主路径关系,避免新人疑惑 2. Design §20.1 LobbyHandlers 加架构说明段: - 明示实际拆为 AudioHandlers / DeviceHandlers / LocationHandlers / ShakeHandlers / OpenurlTitleDataHandler 5 个 sub-struct(§8.1.1 / §8.4.1 / §8.6 / §8.7 / §3.4.2 各自归属) - LobbyHandlers 成为聚合点,依次调子 .register() - 保留 monolithic register 速查表作为与 Contract §3.1 编号对账用 - 每行注释 §3.1 [N] 编号便于追溯 3. Contract §10 验收清单加 5 项硬约束: §A 启动新增: - app_*.js 4 个文件存在 - 15 个 app_* 全局变量命名 100% 正确(大小写硬约束) - 大厅 vs 子游戏 app_Launchtype = "0"/"1" 字面差异 - app_gameid / app_compareCode 必须 undefined(早期 Contract 误列) §B 桥接新增: - H5 alert(msg) → UIAlertController 单按钮 - H5 confirm(msg) → 取消/确定 双按钮 + 正确返回 bool 至此 Plan / Design / Contract 三文档完全对齐 51 项接口(含 §7.5 app_*.js 15 个、§3.7 WKUIDelegate 2 个)。Phase 2 实施可直接对照 Contract §10 验收清单做契约测试。 Co-Authored-By: Claude Opus 4.7 --- docs/Development-Plan.md | 11 ++++ docs/H5-Native-Contract.md | 6 ++ docs/H5-Native-Implementation-Design.md | 80 +++++++++++++++++-------- 3 files changed, 71 insertions(+), 26 deletions(-) diff --git a/docs/Development-Plan.md b/docs/Development-Plan.md index 50829a0..27bc7b0 100644 --- a/docs/Development-Plan.md +++ b/docs/Development-Plan.md @@ -758,6 +758,17 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin - 桥接 handler 任何变动必须同步更新 Contract(Design §18.6) - Phase 完成时如发现 Contract 描述与实测不符 → 优先修 Contract(事实为准) +#### Design 接口骨架覆盖里程碑 + +| 日期 | 完成 | 涉及 commit | +|------|------|-------------| +| 2026-06-22 | Phase 1 闭环(1.10–1.16)完整启动链路打通:SceneDelegate → WebContainer → ensureReady → fetch → resolve → upgrade → loadFileURL → fade out splash | `c95e80d` 及之前 | +| 2026-06-22 | Design 蓝图按 Contract 全 51 项接口补完实现骨架(H5↔Native 5 条通讯路径):22 项 §3.1 异步 handler / 15 项 §3.2 反向 callback / 3 项 §3.4 弹层 polyfill / 15 项 §7.5 `app_*.js` 预注入全局变量 / 2 项 §3.7 WKUIDelegate alert/confirm | `92298b6` / `c6221d3` / `d48ecc0` / `a06a18c` / `5f5f6a9` / `33574a1` / `f68b0db` | +| 2026-06-22 | Contract §4.2 按 daoqi 原项目代码(grep `var app_` 字面字符串)全面修订:删 `app_gameid` / `app_compareCode`(原项目不写)/ 修正 `app_battery` → `app_getbattery`、`app_network` → `app_getnetwork`(文件名不带 get、变量名带 get)/ 补 6 项遗漏(version / Launchtype / getwifisignalLevel / gamename / invitationcode / gamesname) | `47a89aa` | +| 2026-06-22 | CLAUDE.md 新增第 2 个典型案例「H5 与原生通讯接口的真实路径」记录"凭语义猜测会一字之差犯错、必须 grep 字面字符串验证"的教训 | `fae7b3d` | + +> **§3.4.1 撤销说明**:早期 commit `82bad8a` 实现的 `window.settings.getXxx()` polyfill 已撤销 — Contract §附录 A 自身明示「iOS<9 路径,新外壳如最低系统 ≥ iOS 14 可不实现」,本项目最低 iOS 15.6 → polyfill 路径未启用,H5 用 §7.5 的 `app_*.js` 文件路径作为唯一主路径。详细缘由见 Design §3.4.1(撤销说明)和 CLAUDE.md「典型案例」段。 + --- ## 7. 真实风险与缓解(项目独有) diff --git a/docs/H5-Native-Contract.md b/docs/H5-Native-Contract.md index e5b34f9..3b5851f 100644 --- a/docs/H5-Native-Contract.md +++ b/docs/H5-Native-Contract.md @@ -979,6 +979,10 @@ Library/Caches/ - [ ] 重装 + 热启动:< 1 秒进入大厅 - [ ] 渠道注入:把 `msext/channel/xxx` 目录名改成 `yyy`,重打包后大厅显示的渠道 ID 变为 `yyy` - [ ] iOS 18 / 19 / 26 上启动不卡死 +- [ ] **`app_*.js` 4 个文件存在**:`{gamedir}/{gamestart}/` 下 `app_data.js` / `app_battery.js` / `app_network.js` / `app_gamesname.js` 全部生成、loadFileURL 前已落盘 +- [ ] **`app_*` 12 + 1 + 1 + 1 = 15 个全局变量命名 100% 正确**(参 §4.2):H5 console 检查 `app_version` / `app_gameconfig` / `app_gamedir` / `app_gamestart` / `app_agent` / `app_appversion` / `app_market` / `app_channel` / `app_Launchtype` / `app_getwifisignalLevel` / `app_gamename` / `app_invitationcode` / `app_getbattery` / `app_getnetwork` / `app_gamesname` 全部 `!== undefined`;尤其大小写 `app_Launchtype`(L 大写)/ `app_getwifisignalLevel`(wifi 小写 + signal/Level 区分)/ `app_getbattery`、`app_getnetwork`(带 get 前缀)必须精确 +- [ ] **大厅 vs 子游戏字面差异**:大厅 H5 内 `app_Launchtype === "0"`、子游戏 H5 内 `app_Launchtype === "1"` +- [ ] **不要存在已撤销的变量**:H5 console 检查 `app_gameid` / `app_compareCode` 应为 `undefined`(早期 Contract §4.2 误列,原项目代码字面字符串里不写) ### B. 桥接 - [ ] H5 调 `accreditlogin` → 微信弹授权页 → 同意后 H5 收到 `sharelogin` 回调,7 字段完整 @@ -993,6 +997,8 @@ Library/Caches/ - [ ] H5 调 `OpenurlTitleData`(大厅 WVJB)→ 打开内嵌弹层;弹层内 H5 调 `settings.finishweb()`(弹层 JSExport)关闭,且弹层内 `settings.backgameData(data)` 能把 data 传回大厅,大厅 H5 收到 `getWebdata` - [ ] H5 调 `browser` → Safari 打开外链 - [ ] H5 调 `SwitchOverGameData` → push 到子游戏;子游戏内调 `backgameData` → 回到大厅;H5 收到 `getWebdata` +- [ ] **H5 调 `alert("登录失败")`** → 弹出原生 UIAlertController,标题 = appDisplayName,单"确定"按钮(不实现 WKUIDelegate 则 H5 提示完全不弹) +- [ ] **H5 调 `confirm("是否退出")`** → 弹"取消"/"确定"两按钮 → 取消返回 false / 确定返回 true,H5 业务正常分支 ### C. 系统事件 - [ ] 切后台 → H5 收到 `appservice("1")`;回前台 → 收到 `appservice("2")` diff --git a/docs/H5-Native-Implementation-Design.md b/docs/H5-Native-Implementation-Design.md index 8f460ff..ae94c4a 100644 --- a/docs/H5-Native-Implementation-Design.md +++ b/docs/H5-Native-Implementation-Design.md @@ -3985,34 +3985,62 @@ final class AppCoordinator { ### 20.1 LobbyHandlers — 大厅注册 20 个 handler +> **架构说明**(与 §8.x 各 module 章节同步):本节早期版本把 20 项 handler 全列在单一 `LobbyHandlers struct.register()` 内,便于一眼看全。实际实施时(§8.1.1 / §8.4.1 / §8.6 / §8.7 / §3.4.2)按 module 维度拆为多个 sub-struct(`AudioHandlers` / `DeviceHandlers` / `LocationHandlers` / `ShakeHandlers` / `OpenurlTitleDataHandler` 等),`LobbyHandlers` 成为聚合点: +> +> ```swift +> public struct LobbyHandlers { +> let bridge: BridgeProtocol +> let audioHandlers: AudioHandlers // §8.1.1 +> let deviceHandlers: DeviceHandlers // §8.4.1 +> let locationHandlers: LocationHandlers // §8.6 +> let shakeHandlers: ShakeHandlers // §8.7 +> let openurlHandler: OpenurlTitleDataHandler // §3.4.2 +> // 其它单点:accreditLogin / friendsShare / browser / SwitchOverGameData / +> // opensaoma stub 留在 LobbyHandlers 本体 +> +> public func register() { +> audioHandlers.register() +> deviceHandlers.register() +> locationHandlers.register() +> shakeHandlers.register() +> openurlHandler.register() +> registerLobbyOnly() // accreditLogin / friendsShare / browser / SwitchOverGameData / opensaoma +> } +> } +> ``` +> +> 下方 monolithic register 调用清单**作为 20 项接口的速查表保留**(与 Contract §3.1 编号对账用),实际实施请按上述 sub-struct 拆分。 + ```swift +// LobbyHandlers 聚合视角的 20 项接口速查表(与 Contract §3.1 编号一一对应) func register() { - // 登录 / 分享 - bridge.register("accreditlogin", handler: accreditLogin) - bridge.register("friendsSharetypeUrlToptitleDescript", handler: friendsShare) - // 音频 / 录音 - bridge.register("srcIsloop", handler: srcIsLoop) - bridge.register("prepareaudio", handler: prepareAudio) - bridge.register("mediaTypeAudio", handler: mediaTypeAudio) - bridge.register("voicePlaying", handler: voicePlaying) - // 摇一摇 / 振动 - bridge.register("startshake", handler: startShake) - bridge.register("stopshake", handler: stopShake) - bridge.register("SwitchShake", handler: switchShake) - bridge.register("vibrator", handler: vibrator) - bridge.register("repeatvibrator", handler: repeatVibrator) - bridge.register("canclevibrator", handler: cancelVibrator) - // 剪贴板 - bridge.register("gamepastetext", handler: gamePasteText) - bridge.register("gameCopytext", handler: gameCopyText) - // 网页 / 浏览器 / 跳子游戏 - bridge.register("OpenurlTitleData", handler: openUrlTitleData) - bridge.register("browser", handler: browser) - bridge.register("SwitchOverGameData", handler: switchOverGameData) - // 定位 / 设备 / 扫码 - bridge.register("startlocation", handler: startLocation) - bridge.register("getphoneInfo", handler: getPhoneInfo) - bridge.register("opensaoma", handler: openSaoma) // ⚠️ 空实现仍要注册 + // 登录 / 分享 (留在 LobbyHandlers 本体) + bridge.register("accreditlogin", handler: accreditLogin) // §3.1 [1] + bridge.register("friendsSharetypeUrlToptitleDescript", handler: friendsShare) // §3.1 [2] + // 音频 / 录音 (AudioHandlers, §8.1.1) + bridge.register("srcIsloop", handler: srcIsLoop) // §3.1 [3] + bridge.register("prepareaudio", handler: prepareAudio) // §3.1 [4] + bridge.register("mediaTypeAudio", handler: mediaTypeAudio) // §3.1 [5] + bridge.register("voicePlaying", handler: voicePlaying) // §3.1 [6] + // 摇一摇 (ShakeHandlers, §8.7) + bridge.register("startshake", handler: startShake) // §3.1 [7] + bridge.register("stopshake", handler: stopShake) // §3.1 [8] + bridge.register("SwitchShake", handler: switchShake) // §3.1 [9] + // 振动 + 剪贴板 + 设备 (DeviceHandlers, §8.4.1) + bridge.register("vibrator", handler: vibrator) // §3.1 [10] + bridge.register("repeatvibrator", handler: repeatVibrator) // §3.1 [11] + bridge.register("canclevibrator", handler: cancelVibrator) // §3.1 [12] + bridge.register("gamepastetext", handler: gamePasteText) // §3.1 [13] + bridge.register("gameCopytext", handler: gameCopyText) // §3.1 [14] + bridge.register("getphoneInfo", handler: getPhoneInfo) // §3.1 [21] + // 网页 / 浏览器 / 跳子游戏 (OpenurlTitleDataHandler §3.4.2 + LobbyHandlers 本体) + bridge.register("OpenurlTitleData", handler: openUrlTitleData) // §3.1 [15] + bridge.register("browser", handler: browser) // §3.1 [16] + bridge.register("SwitchOverGameData", handler: switchOverGameData) // §3.1 [17] + // 定位 (LocationHandlers, §8.6) + bridge.register("startlocation", handler: startLocation) // §3.1 [20] + // 扫码 stub (LobbyHandlers 本体) + bridge.register("opensaoma", handler: openSaoma) // §3.1 [22] ⚠️ 空实现仍要注册 } ```