hookehuyr

修复(标签栏): 调整导航逻辑并更新文档以修复WebView原生返回按钮显示问题

1. 修改AppTabbar.vue的点击事件处理,仅对首页按钮使用Taro.redirectTo,其他按钮使用Taro.navigateTo以保留页面栈
2. 调整底部导航栏的面板和内容布局样式
3. 更新README和AGENTS.md文档,说明新的导航约定及修改原因
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
5 5
6 - 首页主链路在 `src/pages/index/`,通过 `src/api/index.js` 拉取 banner、宫格导航和图片入口;点击后要么跳内部页面,要么通过 `src/utils/webview.js` 组装参数进入通用 `WebView` 容器。 6 - 首页主链路在 `src/pages/index/`,通过 `src/api/index.js` 拉取 banner、宫格导航和图片入口;点击后要么跳内部页面,要么通过 `src/utils/webview.js` 组装参数进入通用 `WebView` 容器。
7 - 底部导航不是写死在页面里的,核心在 `src/components/AppTabbar.vue``src/stores/tabbar.js``src/api/tabbar.js``src/utils/tabbar.js`。应用启动时会预加载 tabbar 配置,`message``application``mine` 现在都按接口返回地址承接 WebView。 7 - 底部导航不是写死在页面里的,核心在 `src/components/AppTabbar.vue``src/stores/tabbar.js``src/api/tabbar.js``src/utils/tabbar.js`。应用启动时会预加载 tabbar 配置,`message``application``mine` 现在都按接口返回地址承接 WebView。
8 +- 当前底部导航的真实结构是“后台动态菜单 + 固定 `pages/webview-preview/index` 承接页”,不是“每个 tab 都有一个固定业务页”。因此不要因为菜单地址来自后台,就把这条链路改回 `redirectTo`;非 `home` 按钮需要用 `navigateTo` 保留页面栈,这样进入 `webview-preview` 后微信原生左上角返回按钮才会出现。只有小程序冷启动直接进入某个 WebView 场景时,才会因为没有上一页而天然没有原生返回按钮。
8 - `src/pages/message/` 现在已经切到和导航栏“应用 / 我的”一致的 WebView 容器模式;`src/pages/message-detail/``src/api/message.js` 更适合作为旧版原生资讯列表/详情演示链路参考,不应再默认当作线上资讯主入口继续扩展。 9 - `src/pages/message/` 现在已经切到和导航栏“应用 / 我的”一致的 WebView 容器模式;`src/pages/message-detail/``src/api/message.js` 更适合作为旧版原生资讯列表/详情演示链路参考,不应再默认当作线上资讯主入口继续扩展。
9 - 支付相关目前有三类页面:`src/pages/pay-test/` 用于手工调试授权和支付参数;`src/pages/pay-confirm/` 是用户确认金额后点击支付的正式按钮页;`src/pages/pay-bridge/` 是给 H5/WebView 调起小程序支付用的桥页,负责自动授权、拉起支付、展示结果并返回上一页。 10 - 支付相关目前有三类页面:`src/pages/pay-test/` 用于手工调试授权和支付参数;`src/pages/pay-confirm/` 是用户确认金额后点击支付的正式按钮页;`src/pages/pay-bridge/` 是给 H5/WebView 调起小程序支付用的桥页,负责自动授权、拉起支付、展示结果并返回上一页。
10 - `src/pages/webview-preview/` 是通用外链承接页,`src/pages/application/``src/pages/mine/`、首页外链入口都会复用这类能力;`src/pages/map-guide/` 是固定地图签到 H5 页;`src/pages/auth/` 是统一授权页,不能绕过。 11 - `src/pages/webview-preview/` 是通用外链承接页,`src/pages/application/``src/pages/mine/`、首页外链入口都会复用这类能力;`src/pages/map-guide/` 是固定地图签到 H5 页;`src/pages/auth/` 是统一授权页,不能绕过。
...@@ -41,6 +42,8 @@ Mock 目录也有明确分工:`src/mock/index.js` 只做统一入口和分发 ...@@ -41,6 +42,8 @@ Mock 目录也有明确分工:`src/mock/index.js` 只做统一入口和分发
41 ## 授权与支付链路约定 42 ## 授权与支付链路约定
42 授权逻辑的核心在 `src/app.js``src/utils/authRedirect.js``src/utils/request.js``src/pages/auth/index`。应用启动时会优先尝试静默授权,`sessionid` 统一写入 Taro 本地缓存,并由请求拦截器动态注入到请求头;接口返回 `401` 时,会先尝试 `refreshSession` 静默续期并重放原请求,失败后再降级跳转授权页。因此除非明确重构整条链路,否则不要随意改动 `sessionid` 的存取方式、`saveCurrentPagePath` / `returnToOriginalPage` 的回跳机制、`navigateToAuth` 的防重逻辑,也不要跳过 `src/pages/auth/index` 直接在业务页硬编码授权流程。若修改分享进入、启动授权、401 重试或来源页回填逻辑,需至少手工验证一次“未授权进入页面 -> 自动或手动授权 -> 成功回跳原页面”的完整闭环。 43 授权逻辑的核心在 `src/app.js``src/utils/authRedirect.js``src/utils/request.js``src/pages/auth/index`。应用启动时会优先尝试静默授权,`sessionid` 统一写入 Taro 本地缓存,并由请求拦截器动态注入到请求头;接口返回 `401` 时,会先尝试 `refreshSession` 静默续期并重放原请求,失败后再降级跳转授权页。因此除非明确重构整条链路,否则不要随意改动 `sessionid` 的存取方式、`saveCurrentPagePath` / `returnToOriginalPage` 的回跳机制、`navigateToAuth` 的防重逻辑,也不要跳过 `src/pages/auth/index` 直接在业务页硬编码授权流程。若修改分享进入、启动授权、401 重试或来源页回填逻辑,需至少手工验证一次“未授权进入页面 -> 自动或手动授权 -> 成功回跳原页面”的完整闭环。
43 44
45 +如果改动底部动态菜单、`webview-preview`、授权回跳或页面跳转方式,还要额外验证一次“首页 -> 底部动态菜单进入 WebView -> 如未授权则去授权页 -> 授权成功后回到原 WebView 页 -> 左上角仍可返回首页”的完整闭环。这里的关键前提是保住页面栈,所以除 `home` 外不要随手把 tab 点击改成 `redirectTo`
46 +
44 支付链路现在至少有两种入口,不要再把它理解成只有一个测试桥页。第一种是当前线上主支付链路:业务页把 `order_id`、金额等参数带到 `src/pages/pay-confirm/index.vue`,用户确认后通过 `src/composables/useWechatMiniPay.js` 调用 `/srv/?a=pay`,再由请求层统一补上公共参数 `f``client_id`,最后由小程序侧执行 `Taro.requestPayment`。第二种是 H5/WebView 发起支付:外部页面先进入 `pages/webview-preview/index` 或 tabbar 对应的 WebView 容器,再把 `order_id` 传给 `pages/pay-bridge/index`;桥页负责检查授权状态、必要时补做静默授权、拉起支付、展示成功/取消/失败结果,并自动返回上一页。 47 支付链路现在至少有两种入口,不要再把它理解成只有一个测试桥页。第一种是当前线上主支付链路:业务页把 `order_id`、金额等参数带到 `src/pages/pay-confirm/index.vue`,用户确认后通过 `src/composables/useWechatMiniPay.js` 调用 `/srv/?a=pay`,再由请求层统一补上公共参数 `f``client_id`,最后由小程序侧执行 `Taro.requestPayment`。第二种是 H5/WebView 发起支付:外部页面先进入 `pages/webview-preview/index` 或 tabbar 对应的 WebView 容器,再把 `order_id` 传给 `pages/pay-bridge/index`;桥页负责检查授权状态、必要时补做静默授权、拉起支付、展示成功/取消/失败结果,并自动返回上一页。
45 48
46 仓库实现上,共享支付能力核心在 `src/composables/useWechatMiniPay.js``src/api/index.js`;调试入口在 `src/pages/pay-test/index.vue`,正式确认页在 `src/pages/pay-confirm/index.vue`,H5 桥接页在 `src/pages/pay-bridge/index.vue`。另一条是历史保留的通用支付封装,位于 `src/utils/wechatPay.js``src/api/wx/pay.js`,通过 `pay_id` 调用 `/srv/?a=icbc_pay_wxamp`。当前若处理支付问题,默认先看 `order_id -> useWechatMiniPay -> /srv/?a=pay` 这条主链路;其中 `f``client_id` 由请求层公共参数统一补齐,不要在单个支付接口 URL 上重复手写。若没有用户明确点名 `pay_id``wechatPay.js``src/api/wx/pay.js`,暂时不要把排查范围扩到这条历史链路,也不要顺手改它。修改支付逻辑时务必先确认当前页面接的是哪一条接口链路,不要混用 `order_id``pay_id`,也不要在未拿到后端有效支付参数时直接调用 `requestPayment`。涉及 H5/WebView 唤起支付时,应优先保持 `pages/pay-bridge` 的桥接职责与返回参数约定稳定;涉及小程序内直接支付时,应优先保持 `pages/pay-confirm` 的“展示金额 -> 用户点击 -> 调用共享支付能力”职责单一。无论改哪条链路,都需要区分成功、取消、失败三类状态,并至少在微信开发者工具或真机中验证一次“授权状态检查 -> 拉起支付 -> 返回结果展示/回跳”的流程。 49 仓库实现上,共享支付能力核心在 `src/composables/useWechatMiniPay.js``src/api/index.js`;调试入口在 `src/pages/pay-test/index.vue`,正式确认页在 `src/pages/pay-confirm/index.vue`,H5 桥接页在 `src/pages/pay-bridge/index.vue`。另一条是历史保留的通用支付封装,位于 `src/utils/wechatPay.js``src/api/wx/pay.js`,通过 `pay_id` 调用 `/srv/?a=icbc_pay_wxamp`。当前若处理支付问题,默认先看 `order_id -> useWechatMiniPay -> /srv/?a=pay` 这条主链路;其中 `f``client_id` 由请求层公共参数统一补齐,不要在单个支付接口 URL 上重复手写。若没有用户明确点名 `pay_id``wechatPay.js``src/api/wx/pay.js`,暂时不要把排查范围扩到这条历史链路,也不要顺手改它。修改支付逻辑时务必先确认当前页面接的是哪一条接口链路,不要混用 `order_id``pay_id`,也不要在未拿到后端有效支付参数时直接调用 `requestPayment`。涉及 H5/WebView 唤起支付时,应优先保持 `pages/pay-bridge` 的桥接职责与返回参数约定稳定;涉及小程序内直接支付时,应优先保持 `pages/pay-confirm` 的“展示金额 -> 用户点击 -> 调用共享支付能力”职责单一。无论改哪条链路,都需要区分成功、取消、失败三类状态,并至少在微信开发者工具或真机中验证一次“授权状态检查 -> 拉起支付 -> 返回结果展示/回跳”的流程。
......
...@@ -209,6 +209,19 @@ export default { ...@@ -209,6 +209,19 @@ export default {
209 209
210 此配置已在 `config/index.js` 中设置,请确保遵循此规范。 210 此配置已在 `config/index.js` 中设置,请确保遵循此规范。
211 211
212 +## 🧭 动态底部导航与 WebView 承接
213 +
214 +当前底部导航不是微信原生 tabBar,而是首页里的自定义 `AppTabbar`。除 `home` 外,其余按钮的菜单地址都来自后台 `app_menu` 配置,但最终仍然统一落到固定承接页 `pages/webview-preview/index`,再由该页把 `url` / `title` 传给 `<web-view>`
215 +
216 +这里有一个很重要的导航约定:
217 +
218 +- `home` 可以继续用 `redirectTo`
219 +-`home` 的动态菜单必须用 `navigateTo`
220 +
221 +原因不是业务页是否固定,而是页面栈是否保住。只要首页点击动态菜单时用的是 `navigateTo``webview-preview` 就会作为新页面入栈,微信原生左上角返回按钮才能正常出现;如果改成 `redirectTo`,首页会被替换掉,进入 WebView 后就不会有原生返回按钮。
222 +
223 +只有一种场景例外:如果小程序是冷启动后直接打开某个 WebView 页面,那么它天然没有上一页,这时即使用的是同一个承接页,也不会凭空出现原生返回按钮。这类场景需要另外设计“返回首页”兜底,不要误判成 tabbar 跳转逻辑失效。
224 +
212 ## 🔐 认证流程 225 ## 🔐 认证流程
213 226
214 项目内置完整的微信登录认证系统: 227 项目内置完整的微信登录认证系统:
...@@ -223,6 +236,13 @@ export default { ...@@ -223,6 +236,13 @@ export default {
223 236
224 **重要**:当前授权链路使用的是 `/srv/?a=openid`。如果后端动作名、cookie 返回方式或公共参数发生变化,需要同时检查真实授权链路和本地 mock 授权链路是否保持一致。 237 **重要**:当前授权链路使用的是 `/srv/?a=openid`。如果后端动作名、cookie 返回方式或公共参数发生变化,需要同时检查真实授权链路和本地 mock 授权链路是否保持一致。
225 238
239 +如果你同时修改了动态菜单跳转、`webview-preview` 或授权回跳逻辑,建议至少手工验证一次这条闭环:
240 +
241 +1. 首页点击底部动态菜单进入 WebView
242 +2. 如未授权则跳到授权页
243 +3. 授权成功后回到原来的 WebView 页面
244 +4. 左上角原生返回按钮仍然可以返回首页
245 +
226 ## 🌐 弱网/离线支持 246 ## 🌐 弱网/离线支持
227 247
228 项目内置弱网和离线支持: 248 项目内置弱网和离线支持:
......
...@@ -123,7 +123,13 @@ const handleTabClick = (item) => { ...@@ -123,7 +123,13 @@ const handleTabClick = (item) => {
123 return 123 return
124 } 124 }
125 125
126 - Taro.redirectTo({ url: targetUrl }) 126 + if (item?.key === 'home') {
127 + Taro.redirectTo({ url: targetUrl })
128 + return
129 + }
130 +
131 + // 非首页按钮需要保留页面栈,这样 webview-preview 才能显示微信原生返回按钮。
132 + Taro.navigateTo({ url: targetUrl })
127 } 133 }
128 134
129 const clearScrollHintTimers = () => { 135 const clearScrollHintTimers = () => {
...@@ -210,7 +216,7 @@ onBeforeUnmount(() => { ...@@ -210,7 +216,7 @@ onBeforeUnmount(() => {
210 } 216 }
211 217
212 .app-tabbar__panel { 218 .app-tabbar__panel {
213 - height: 140rpx; 219 + height: 150rpx;
214 border-top: 2rpx solid rgba(166, 121, 57, 0.12); 220 border-top: 2rpx solid rgba(166, 121, 57, 0.12);
215 background: rgba(255, 255, 255, 0.98); 221 background: rgba(255, 255, 255, 0.98);
216 box-sizing: border-box; 222 box-sizing: border-box;
...@@ -220,7 +226,6 @@ onBeforeUnmount(() => { ...@@ -220,7 +226,6 @@ onBeforeUnmount(() => {
220 .app-tabbar__content { 226 .app-tabbar__content {
221 display: flex; 227 display: flex;
222 align-items: stretch; 228 align-items: stretch;
223 - height: 132rpx;
224 padding: 16rpx 28rpx 16rpx 52rpx; 229 padding: 16rpx 28rpx 16rpx 52rpx;
225 box-sizing: border-box; 230 box-sizing: border-box;
226 } 231 }
......