问题描述
在视频通话应用中,走的充值会跳转新的页面,此时回来会中断视频,改为使用 iframe 模拟弹窗支付后,原先router.back(-1),退出功能失效。用户点击退出按钮后,页面停留在空白状态,无法正常返回。
问题分析
现象
通过日志发现,iframe 打开前后 history.length 发生了变化:
[Payment] iframe 打开前 history.length: 47
[Payment] iframe 关闭前 history.length: 50
历史记录被污染了 3 条。
A → B → iframe → back → B ❌
A → B → iframe → back → A ✅
从工程上来说,iframe的打开是是“模态行为”,不是“导航行为”
原因
虽然 iframe 是弹窗样式,不涉及路由跳转,但浏览器在加载外部 URL(如 Stripe 支付页面)时,仍然会在主窗口的历史记录中添加条目。
具体流程:
- 进入视频通话页面时:
history.length = 47 - iframe 打开并加载支付页面:
history.length = 50(不一定50,看iframe的跳转次数) - 退出时使用
router.back(-1):只能回退 1 步,无法清除被污染的历史记录
其他方案的局限性
方案一:锁定“返回目标”,使用router.replace()进行跳转
具体是在跳转视频通话的时候带一个_entry参数,close的时候根据参数来replace,虽然能退出,但会导致用户在返回时需要点击两次才能回到上一页。因为 replace 会覆盖当前历史记录,导致历史记录变成 A → A(B 被 replace 成A),用户看似是返回了,但如果A还是有返回键的,第一次 back 仍然停在 A,就需要再点击一次了
方案二:记录污染数量并清除
记录 iframe 打开前后的历史记录长度差,退出时使用 router.go() 清除。但 router.go() 在某些情况下不稳定,且需要维护 localStorage,逻辑复杂。
解决方案
核心思路
在进入页面时记录历史记录基线,退出时直接回退到基线位置。这样无论 iframe 污染了多少条历史记录,都能精准回退。
方案优势
- 不新建历史记录:使用
history.go()直接回退,不产生新记录 - 不覆盖历史记录:避免
replace导致的"返回要点两次"问题 - 精准回退:直接回到进入页面前的历史节点,不受 iframe 污染影响
- 有兜底方案:如果没有基线记录,仍使用
router.back(-1)作为后备 - 代码简洁:逻辑清晰,不需要维护 localStorage
技术要点
1. 使用 history.state 存储基线
Vue Router 4 支持 state 参数,不会出现在 URL 中,适合存储临时状态:
router.push({
path: '/video',
query: { ... },
state: {
__historyBase: historyBase // 不会进入 URL
}
})
2. 计算回退步数
const delta = window.history.length - historyBase;
if (delta > 0) {
window.history.go(-delta); // 回退 delta 步
}
实现
1. 进入页面时记录基线
在跳转到视频通话页面时,记录当前历史记录长度:
navigateToVideoPage(cid, otherId, isMatchIn, aid) {
// 记录进入1v1前的历史记录基线
const historyBase = window.history.length
router.push({
path: '/video',
query: {...},
state: {
__historyBase: historyBase // 使用 state,不会进入 URL
}
})
}
2. 退出时精准回退
退出时,计算当前历史记录长度与基线的差值,使用 history.go() 回退相应步数:
const executeHangupDirectly = () => {
nextTick(() => {
// 使用 history.go() 精准回退到进入1v1前的历史记录节点
const historyBase = window.history.state?.__historyBase;
if (typeof historyBase === 'number') {
const delta = window.history.length - historyBase;
if (delta > 0) {
// 直接回退到进入1v1前的历史记录节点
window.history.go(-delta);
return;
}
}
// 兜底:如果没有记录基线,使用 back
router.back(-1);
});
};
总结
iframe 虽然不涉及路由跳转,但加载外部 URL 时仍会污染浏览器历史记录。通过记录历史基线并使用 history.go() 精准回退,可以解决这个问题。
该方案既保证了退出功能的正常,又不会影响用户的返回体验。需要注意的是,需要确保所有退出路径都使用相同的逻辑,以保证一致性。