fix(member): 修复账号注销/退出登录后无法正常跳转到微信原生授权登录的问题
修复背景:
账号注销成功后, 用户被重定向到 native 登录页 (手机号+验证码表单风格, 像 H5),
而不是微信小程序原生授权登录 (ns-login 弹窗 + getPhoneNumber 授权).
此外, 微信小程序下短时间内连发 reLaunch 会触发 native 错误:
appLaunch with non-empty page stack
以及历史遗留的 navigateTo push 登录页 + 旧页面在栈中导致的死循环问题.
主要修复:
1. util.js redirectTo reLaunch 分支 (4 平台兼容):
- H5 平台: window.location.replace (真正清空 history, 避免 history.back + router.replace 假清栈)
- 小程序平台 (mp-weixin/mp-harmony/...): 加 300ms 全局跳转锁 + setTimeout(0) 包裹
解决 appLaunch with non-empty page stack 错误
- APP/其他: 保留原生 uni.reLaunch
2. util.js redirectTo 入口智能默认 (跨平台兜底):
- 目标 == LOGIN_PAGE_URL && mode 是默认 navigateTo && 没传 back 参数
→ 自动改成 reLaunch (避免注销后死循环)
- 显式传 mode 或带 back 参数的场景保持原行为 (不影响登录后回原页面)
3. member-public.js mixin 增强:
- getLoginEntryUrl(): 跨平台登录入口智能分流
微信小程序/鸿蒙/其他小程序 → INDEX_PAGE_URL (首页, 走 ns-login 原生授权弹窗)
H5/快应用/APP → LOGIN_PAGE_URL (原生登录页)
- redirectToLoginOnce(): 统一封装 clearLoginState + 跳登录入口, 带 800ms 组件级防抖锁
- logout / getCancelStatus 兜底 / submitCancelApply 三个地方都改用 redirectToLoginOnce()
4. 5 个注销/退出流程页面改造:
- cancelsuccess.vue / cancelstatus.vue / cancellation.vue / cancelrefuse.vue
/ assets.vue: 跳登录入口全部用 redirectToLoginOnce (不再直接 redirectTo(LOGIN_PAGE_URL))
- cancellation.vue / cancelrefuse.vue: 加 memberPublic mixin (原本没有)
5. 取消了在 pages/index/public/js/index.js 中 onShow 自动弹登录弹窗的尝试
(因触发 Vue 框架的 dataset of null pre-existing 错误, 改由其他途径处理)
测试覆盖 (300+ 测试用例全部通过):
- tests/member-cancel-flow.test.js (28 个测试, 9 个用户故事场景)
- tests/redirectTo-relaunch-h5.test.js (11 个测试, 9 个跨平台场景)
- tests/redirectTo-real.test.js (12 个)
- tests/util.test.js (30 个)
- 其他 8 个测试文件全部通过
This commit is contained in:
@@ -511,6 +511,27 @@ export default {
|
||||
// 注意: URL 清理只在 diyRedirectTo 入口做(更上游,职责清晰)。
|
||||
// redirectTo 接收到的 param 应当是干净的;不重复清理,避免性能浪费 + 误伤边界。
|
||||
//
|
||||
// 0. 智能默认 mode (2026-07-23, 修正 2026-07-23):
|
||||
// 跳登录入口 LOGIN_PAGE_URL 时, 默认使用 reLaunch 模式 (即使调用方没传 mode).
|
||||
// 修复场景:
|
||||
// - 注销 / 退出登录后, token 被清空, cancelstatus.vue 等页面再次进入
|
||||
// 会调 redirectTo(LOGIN_PAGE_URL), 历史上走 navigateTo 模式, 旧页面
|
||||
// (cancelstatus) 还在栈中, 用户按返回键回到 cancelstatus 后又触发
|
||||
// onShow 死循环跳登录页.
|
||||
// 排除场景 (重要, 避免影响):
|
||||
// - 带 back 参数的: 表示登录后要回到原页面 (订单详情/退款详情等),
|
||||
// 必须保留 navigateTo 模式让 back 参数生效. 例如:
|
||||
// refund_detail.vue: redirectTo(LOGIN_PAGE_URL, { back: '/pages_tool/order/refund_detail?id=xxx' })
|
||||
// - 显式传 'navigateTo' / 'redirectTo' / 其他 mode: 保持原意.
|
||||
// 触发条件: mode 是默认 'navigateTo' AND 没传 back 参数 AND 目标是 LOGIN_PAGE_URL.
|
||||
if (mode === 'navigateTo' && typeof to === 'string' && !param?.back) {
|
||||
const cleanPath = to.indexOf('?') === -1 ? to : to.substring(0, to.indexOf('?'));
|
||||
if (cleanPath === LOGIN_PAGE_URL) {
|
||||
mode = 'reLaunch';
|
||||
console.log('[navigate] redirectTo: 跳 LOGIN_PAGE_URL 自动改为 reLaunch 模式 (没带 back 参数)');
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 拆分 path 与已有 query,避免后面 indexOf('?') 误判
|
||||
console.log('[navigate] redirectTo entry: to=', to, 'mode=', mode, 'param=', JSON.stringify(param));
|
||||
|
||||
@@ -538,18 +559,18 @@ export default {
|
||||
merged.page_params = page_params;
|
||||
}
|
||||
|
||||
// 3. 参数编码策略(委托给 util-navigate 的 _safeEncodeForPlatform)
|
||||
// - 微信小程序 + 鸿蒙元服务(mp-weixin/mp-harmony):uni.navigateTo / router.pushUrl 内部会自动对 url 做一次编码,
|
||||
// JS 侧再做 encodeURIComponent 会被平台再编码一层,目标页 onLoad(options) 拿到的
|
||||
// 是已编码字符串(典型表现:商品分类 → 商品列表,
|
||||
// 搜索栏关键词变成 %E6%B2%99%E5%8F%91 乱码)
|
||||
// => 这里必须保持原始值,完全交给平台原生去编码(_safeEncodeForPlatform
|
||||
// 在 mp-weixin / mp-harmony 下不做 encode,只做 decode roundtrip 去除双向 encode)
|
||||
// - H5 / 华为快应用:浏览器/快应用 Webview 不自动编码 url,
|
||||
// 参数含 & ? = 等特殊字符会被 queryString 错位解析(微信甚至把 & 当
|
||||
// 成 top-level 分隔符丢参数)
|
||||
// => 这里必须手动 encodeURIComponent 一次(_safeEncodeForPlatform 在这些平台下
|
||||
// 做 decode roundtrip + encode 一次)
|
||||
// 3. 参数编码策略(委托给 util-navigate 的 _safeEncodeForPlatform)
|
||||
// - 微信小程序 + 鸿蒙元服务(mp-weixin/mp-harmony):uni.navigateTo / router.pushUrl 内部会自动对 url 做一次编码,
|
||||
// JS 侧再做 encodeURIComponent 会被平台再编码一层,目标页 onLoad(options) 拿到的
|
||||
// 是已编码字符串(典型表现:商品分类 → 商品列表,
|
||||
// 搜索栏关键词变成 %E6%B2%99%E5%8F%91 乱码)
|
||||
// => 这里必须保持原始值,完全交给平台原生去编码(_safeEncodeForPlatform
|
||||
// 在 mp-weixin / mp-harmony 下不做 encode,只做 decode roundtrip 去除双向 encode)
|
||||
// - H5 / 华为快应用:浏览器/快应用 Webview 不自动编码 url,
|
||||
// 参数含 & ? = 等特殊字符会被 queryString 错位解析(微信甚至把 & 当
|
||||
// 成 top-level 分隔符丢参数)
|
||||
// => 这里必须手动 encodeURIComponent 一次(_safeEncodeForPlatform 在这些平台下
|
||||
// 做 decode roundtrip + encode 一次)
|
||||
const queryStr = Object.keys(merged)
|
||||
.map(k => _safeEncodeForPlatform(k) + '=' + _safeEncodeForPlatform(merged[k]))
|
||||
.join('&');
|
||||
@@ -669,6 +690,41 @@ export default {
|
||||
break;
|
||||
case 'reLaunch':
|
||||
// 关闭所有页面,打开到应用内的某个页面。清空栈后打开新页面,长度=1
|
||||
// H5 平台修复 (2026-07-23):
|
||||
// uni.reLaunch 在 H5 上是 history.back() + router.replace 的组合,
|
||||
// 并不能真正删除浏览器历史条目。在某些浏览器 (微信内置浏览器 /
|
||||
// Android 定制浏览器) 中, 按浏览器返回键仍可回到原页面, 导致
|
||||
// 注销/退出登录后用户能看到旧会员信息。
|
||||
// location.replace 不产生新历史条目, 直接替换当前条目,
|
||||
// 真正"清空历史栈"。对快应用 (H5 webview 嵌入) 同样生效。
|
||||
// #ifdef H5
|
||||
if (typeof window !== 'undefined' && typeof window.location !== 'undefined'
|
||||
&& typeof window.location.replace === 'function') {
|
||||
window.location.replace(url);
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
// 小程序平台修复 (2026-07-23):
|
||||
// 微信小程序在以下场景会抛 "appLaunch with non-empty page stack" native 错误:
|
||||
// - 短时间内连发 reLaunch (页面销毁未完成, 新 launch 时栈非空)
|
||||
// - 异步回调/onLoad 中调 reLaunch (上下文丢失)
|
||||
// 解决方案:
|
||||
// 1) 跳转锁: 300ms 内不重复 reLaunch (避免连发)
|
||||
// 2) setTimeout(0) 包裹: 让 native 层先完成当前页面销毁, 再发新 launch
|
||||
// #ifdef MP
|
||||
if (typeof globalThis !== 'undefined' && globalThis.__reLaunchLock) {
|
||||
console.warn('[redirectTo] reLaunch 锁定中, 忽略本次跳转:', url);
|
||||
return;
|
||||
}
|
||||
globalThis.__reLaunchLock = true;
|
||||
setTimeout(() => {
|
||||
uni.reLaunch({ url });
|
||||
// 300ms 后释放锁
|
||||
setTimeout(() => { globalThis.__reLaunchLock = false; }, 300);
|
||||
}, 0);
|
||||
return;
|
||||
// #endif
|
||||
// APP / 其他平台: 原生 reLaunch
|
||||
uni.reLaunch({
|
||||
url
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user