前言
在小程序开发中,<web-view> 组件是承载 H5 页面的重要载体。但小程序和 H5 有各自独立的 Storage,token 并不互通。当 H5 页面的 token 失效时,如何让用户顺利跳转到小程序原生登录页,并在登录成功后原路返回?
本文将以实际项目为例,分享一套完整的跨端会话管理方案,涵盖:
- Token 失效时从 H5 跳转小程序登录页
- 登录成功后携带参数原路返回
- 登录页返回按钮的智能控制
一、问题背景
1.1 会话隔离问题
H5 页面通过小程序 <web-view> 内嵌运行时,存在一个典型的跨端会话隔离问题:
| 存储 | 作用域 | 特点 |
|---|---|---|
| 小程序 Storage | 小程序进程 | 独立存储,生命周期跟随小程序 |
| H5 Storage | web-view 进程 | 独立存储,与小程序不互通 |
核心矛盾 :H5 页面 token 失效时,无法通过 H5 页面直接跳转到小程序原生登录页(wx.miniProgram API 在 web-view 中访问受限),用户被"困在" web-view 里。
1.2 期望的用户旅程
css
H5 活动页 → token 失效 → Toast 提示 → 跳转小程序登录页
→ 登录成功 → 携带参数返回 H5 页面 → 正常使用
二、Token 失效跳转小程序登录
2.1 核心方法
javascript
// pages/webH5View/index.vue
noValidTokenGoMiniProgramLogin() {
// 1. Toast 提示用户即将跳转
uni.showToast({
title: '登录过期,即将进入登录页面',
icon: 'none',
duration: 2 * 1000
})
// 2. 判断是否在小程序运行环境
if (typeof window.__wxjs_environment !== 'undefined'
&& window.__wxjs_environment === 'miniprogram') {
setTimeout(() => {
// 3. 通过 JSSDK 跳转小程序原生登录页
this.$wx.miniProgram.redirectTo({
url: `/pages/login/login?isShare=1&back=1&page=${encodeURIComponent(
'/pages/webH5View/index'
)}&h5Src=${encodeURIComponent(
'https://testh5/#/index?isMiniprogram=1'
)}`
})
}, 2000)
}
}
2.2 关键技术点解析
环境检测
javascript
window.__wxjs_environment === 'miniprogram'
这是微信 JSSDK 注入的环境变量,不同于 navigator.userAgent,在 web-view 内是唯一可靠的判断方式。
延迟跳转
javascript
setTimeout(() => { ... }, 2000)
给 Toast 足够的展示时间,避免用户感到突兀。
携带 H5 原始地址
javascript
h5Src=${encodeURIComponent('H5完整地址')}
h5Src 参数记录 H5 访问地址,登录完成后原路返回。
redirectTo vs navigateTo
| 方法 | 行为 | 适用场景 |
|---|---|---|
navigateTo |
保留当前页面,推入新页面 | 普通跳转 |
redirectTo |
关闭当前页面,替换为新页面 | 登录场景,避免返回键回到失效页面 |
三、登录成功后的回跳链路
3.1 登录页参数接收
javascript
// pages/login/login.vue --- onLoad
onLoad(option) {
this.activityH5 = option.activityH5
this.goId = option.id
option.page ? this.goPage = option.page : null
option.h5Src ? this.h5Src = option.h5Src : null // 接收 H5 地址
// 处理分享页登录场景
if (option.isShare && option.isShare === '1') {
this.isShare = option.isShare
this.toHideBackButton = true
// 已登录则跳首页
if (this.$store.getters.userInfo && this.$store.getters.userInfo.token) {
uni.switchTab({ url: '/pages/index/index' })
}
} else {
// 根据路由栈长度控制返回按钮
const pages = getCurrentPages()
this.toHideBackButton = (pages.length > 1) ? false : true
}
}
3.2 登录成功回跳逻辑
javascript
// pages/login/login.vue --- 登录成功后处理
if (this.h5Src) {
// H5 场景:登录完成 → 打开 web-view 并带入 H5 地址
uni.redirectTo({
url: `${decodeURIComponent(this.goPage)}?webViewUrl=${this.h5Src}`
})
} else {
// 普通场景:直接跳转目标页面
uni.redirectTo({ url: decodeURIComponent(this.goPage) })
}
四、WebView 加载时的 Token 校验
4.1 完整的 onLoad 流程
javascript
// pages/webH5View/index.vue --- onLoad
onLoad(option) {
this.option = option
this.isShare = option.isShare === '1' ? '1' : '0'
if (this.userInfo.token) {
// 有 token → 先校验有效性
this.isLoginFunction()
} else {
// 无 token → 直接打开 H5(H5 侧会触发登录流程)
this.isLogin = false
this.handleUrl(this.option)
}
}
// 是否登陆判断(调用小程序侧接口校验 token)
async isLoginFunction() {
try {
const res = await checkMiniProgramLoginFlag()
this.isLogin = !!res // token 有效则复用
} catch (e) {
this.isLogin = false // token 失效触发登录
}
this.handleUrl(this.option)
}
4.2 URL 参数拼接策略
javascript
handleUrl(option) {
let url = option.webViewUrl
? decodeURIComponent(option.webViewUrl)
: decodeURIComponent(option.url || '')
// H5 访问时 token 取空字符串(因为小程序 token 不在 H5 域下)
let token = this.isLogin ? this.userInfo.token : ''
let params = [
'token=' + encodeURIComponent(token || ''),
'userId=' + (this.userInfo.userId || '')
]
let baseUrl = url + (url.indexOf('?') !== -1 ? '&' : '?') + params.join('&')
// 标记来源为小程序,供 H5 页面识别并触发登录流程
if (baseUrl.indexOf('isMiniprogram') === -1) {
baseUrl = baseUrl + '&isMiniprogram=1'
}
this.hrefUrl = baseUrl
}
五、登录页返回按钮智能控制
5.1 问题场景
登录页作为入口页面,如果按返回键:
| 场景 | 行为 | 结果 |
|---|---|---|
| 有历史页面 | 正常返回上一页 | ✅ 正常 |
| 无历史页面(直接打开小程序) | 返回到空页面 | ❌ 体验差 |
5.2 解决方案
javascript
// pages/login/login.vue --- onLoad
if (option.isShare && option.isShare === '1') {
// 分享页进入:强制隐藏返回按钮
this.toHideBackButton = true
} else {
// 正常进入:根据路由栈长度智能判断
const pages = getCurrentPages()
this.toHideBackButton = (pages.length > 1) ? false : true
}
5.3 导航栏组件
html
<!-- topnavigation 接收 isfx prop 控制返回按钮显隐 -->
<topnavigation :isfx="toHideBackButton"></topnavigation>
5.4 逻辑总结
| 场景 | pages.length |
toHideBackButton |
返回按钮 |
|---|---|---|---|
| 直接打开小程序 → 登录页 | 1(只有当前页) |
true |
隐藏 ✅ |
| 从其他页面跳转 → 登录页 | ≥ 2 |
false |
显示 ✅ |
六、完整流程图
flowchart TD
A[用户在小程序内打开 H5 活动页] --> B{web-view 加载 H5 URL}
B --> C{H5 检测 token}
C -->|有效| D[H5 正常加载]
C -->|失效| E[调用 noValidTokenGoMiniProgramLogin]
E --> F{判断环境}
F -->|小程序环境| G[2秒后 wx.miniProgram.redirectTo]
F -->|非小程序| H[普通 H5 登录流程]
G --> I[跳转小程序登录页
携带 isShare/back/h5Src 参数] I --> J{登录页 onLoad} J --> K{isShare=1?} K -->|是| L[强制隐藏返回按钮] K -->|否| M[根据路由栈长度判断] M --> N{pages.length > 1?} N -->|是| O[显示返回按钮] N -->|否| P[隐藏返回按钮] L --> Q[用户完成登录] O --> Q P --> Q Q --> R{有 h5Src?} R -->|是| S[打开 web-view
携带 webViewUrl=h5Src] R -->|否| T[普通跳转目标页面] S --> U[web-view 重新 onLoad] U --> V[checkMiniProgramLoginFlag 校验 token] V -->|有效| W[token 拼接进 URL] V -->|失效| E W --> D
携带 isShare/back/h5Src 参数] I --> J{登录页 onLoad} J --> K{isShare=1?} K -->|是| L[强制隐藏返回按钮] K -->|否| M[根据路由栈长度判断] M --> N{pages.length > 1?} N -->|是| O[显示返回按钮] N -->|否| P[隐藏返回按钮] L --> Q[用户完成登录] O --> Q P --> Q Q --> R{有 h5Src?} R -->|是| S[打开 web-view
携带 webViewUrl=h5Src] R -->|否| T[普通跳转目标页面] S --> U[web-view 重新 onLoad] U --> V[checkMiniProgramLoginFlag 校验 token] V -->|有效| W[token 拼接进 URL] V -->|失效| E W --> D
七、踩坑记录
7.1 navigateTo 会导致返回键回到失效页面
错误做法:
javascript
wx.miniProgram.navigateTo({
url: '/pages/login/login?back=1'
})
正确做法:
javascript
wx.miniProgram.redirectTo({
url: '/pages/login/login?back=1&h5Src=...'
})
7.2 环境判断不能依赖 UA
错误做法:
javascript
navigator.userAgent.includes('miniProgram') // 不可靠
正确做法:
javascript
window.__wxjs_environment === 'miniprogram' // JSSDK 注入,可靠
7.3 忘记 encodeURIComponent
错误做法:
javascript
h5Src=https://example.com/#/page?activityId=123&isMiniprogram=1
// URL 中的 ? 和 & 会与小程序路由参数冲突
正确做法:
javascript
h5Src=${encodeURIComponent('https://example.com/#/page?activityId=123&isMiniprogram=1')}
八、总结
本文分享了一套小程序 <web-view> 内嵌 H5 的完整会话管理方案,核心要点:
- 环境检测 :使用
window.__wxjs_environment判断运行环境 - 跨端跳转 :通过 JSSDK 的
wx.miniProgram.redirectTo实现 H5 → 小程序原生页面跳转 - 参数透传 :
h5Src参数记录原始 H5 地址,登录成功后原路返回 - 智能 UI:根据路由栈长度控制返回按钮显隐,提升用户体验
这套方案已在生产环境验证,希望能帮助到有类似需求的开发者。