FORMA

企业微信 uni-app H5:OAuth 回退白屏与列表缓存

第三方 BI 报表项目,企业微信内嵌 H5,uni-app 编译,history 模式。主页面 BiLink 同时承担静默授权和列表展示,上线后碰到两个问题:授权完清掉 URL 参数,iOS 侧滑返回还是白屏;列表加了缓存以后,刷新行为又不对。

场景说明

从企业微信工作台点进来,走 snsapi_base 静默授权。getUserInfo 拿到数据后按权限分组出 Grid(集团 / 销售 / 事业部),点击 navigateTopages-sub/bi-links/detailsweb-view 打开 extLink。没有单独的授权页,OAuth 回调直接落在 BiLink,URL 带 code,换票后 replaceState 清参。

页面配置:

json5
{
  layout: 'default',
  style: {
    navigationBarTitleText: 'BI报表',
    navigationStyle: 'custom',
  },
}

整体流程:

text
打开 BiLink(无 code)
  → redirectToOAuth()
  → 带 code 回到 BiLink
  → initPage():getUserInfo → 写 bi-cache → replaceState
  → 出列表;只有一个报表则 reLaunch 进 details

进报表时带 autoRefresh=false,列表留在页面栈里,回来读缓存:

ts
const url =
  `/pages-sub/bi-links/details?title=...` +
  `&autoRefresh=false` +
  `&extLink=...`
uni.navigateTo({ url })

// onLoad
const autoRefresh = getUrlParam('autoRefresh')
if (autoRefresh !== 'false') {
  state.initPage()
} else {
  const cached = getBiReportGroupsCache()
  if (cached?.length) {
    state.reportGroups = cached
    state.isLoading = false
    window.history.replaceState({}, '', getCleanPageUrl())
  } else {
    state.initPage()
  }
}

initPagefinally 里会 clearOAuthState(),再 replaceStategetCleanPageUrl()。授权封装在 useAuth 里,code 也会存一份到 sessionStoragebi_auth_code)。


问题现象

侧滑返回白屏

列表正常出来后,企业微信 iOS 里左滑返回,页面空白。再 redirectToOAuthreplaceState 没用,有时得关掉重进。这里没用 uni.redirectTo,就是原页 replaceState,一样中招。

缓存和刷新

报表跳转带 autoRefresh=false,列表数据走 bi-cache

  • 列表 F5:URL 上没有 code 也没有 autoRefresh=false,重新 initPage(),没 code 又去 OAuth,像刷新坏了;
  • 报表 navigateBack 回来:onLoad 不重跑,onShow 里只调了 hideShareMenu(),实例没了或缓存空了列表出不来;
  • 报表页刷新:web-view token 过期,白屏或 403。

原因

OAuth 走的是浏览器历史,不只是 uni-app 页面栈。

history 模式,回调地址类似 https://domain.com/pages/bi-links/index?code=xxx。整页跳转下来,浏览器历史大致是:

text
BiLink(无 code)
  → 企业微信 OAuth(302)
  → BiLink(带 code)
  → replaceState 清参    ← 只改栈顶,下面几条还在

redirectToOAuth 整页跳,历史会叠;replaceState 只动当前这一条。iOS 侧滑回的是浏览器栈,不一定走 navigateBack,容易回到还带 code 的地址——code 只能用一次,页面起不来就白了。

单页接回调的好处是链路短,坏处是 OAuth 前后同一个页面,replaceState 删不掉 302 产生的下层历史。用户已经滑回旧 URL,你再 replaceState,改的不一定是他眼前那一层,所以会觉得 replace「又不生效」。

autoRefresh=false 本来是想解决「从报表返回别重新鉴权」,不是给 F5 用的:

  • 首次 / 带 code:initPage(),正常;
  • navigateBack:不触发 onLoad,靠内存 state 和 bi-cache;
  • F5 刷列表:又进 initPage(),没 code 就 OAuth;
  • F5 刷报表:details 自己一套,跟列表缓存没接上。

onShow 目前没管缓存恢复。另外 history 部署记得配 try_filesindex.html,不然回退到某些路径直接 404,也是白屏。


处理方向

换票后别只 replaceState

地址栏好看用 replaceState 够了;想少踩回退,换票成功后可以直接:

ts
window.location.replace(getCleanPageUrl())

或者缓存写完后 uni.reLaunch({ url: '/pages/bi-links/index?autoRefresh=false' })。单报表那条已经 reLaunch 了,多报表列表授权完也该硬跳一次。

popstate 兜底

#ifdef H5,滑回带 code 的 URL 时纠正一下:

ts
window.addEventListener('popstate', () => {
  const code = getUrlParam('code')
  const hasCache = getBiReportGroupsCache()?.length

  if (code) {
    hasCache ? window.location.replace(getCleanPageUrl()) : redirectToOAuth()
    return
  }
  if (!hasCache && !sessionStorage.getItem('bi_auth_code')) {
    redirectToOAuth()
  }
})

列表页可以加 "disableSwipeBack": true

分开处理「回调落地」和「从报表回来」

autoRefresh=false 只留给子页返回读缓存。F5 先看有没有有效 session,有的话只拉 permissions,别一见没 code 就 OAuth:

ts
onLoad(() => {
  hideShareMenu()
  const code = getUrlParam('code')
  const fromBack = getUrlParam('autoRefresh') === 'false'

  if (code) {
    state.initPage()
    return
  }
  if (fromBack) {
    const cached = getBiReportGroupsCache()
    if (cached?.length) {
      state.reportGroups = cached
      state.isLoading = false
      window.history.replaceState({}, '', getCleanPageUrl())
      return
    }
  }
  if (hasValidSession()) {
    state.initPageFromSession()
  } else {
    state.initPage()
  }
})

onShow 补缓存,报表 web-view 加 key

ts
onShow(() => {
  hideShareMenu()
  if (!state.reportGroups.length && !state.isLoading) {
    const cached = getBiReportGroupsCache()
    if (cached?.length) state.reportGroups = cached
  }
})
vue
<web-view v-if="reportUrl" :key="reportKey" :src="reportUrl" />

Nginx

nginx
location / {
  try_files $uri $uri/ /index.html;
}

参考

Series

team documents

22 / 22