需求:页面a、b、c,并且a=>b=>c,b和c都有自定义返回按钮,c页面需要在页面返回(物理返回按钮或者自定义返回按钮)前给提示,并且每一个页面只能返回到它的入口页
解决方案:在onLoad里面pushState, onShow监听popstate
问题:
1、a navigateTo
b,b navigateTo
c,在c自定义返回按钮点击的回调函数是popstate的回调函数,点击自定义按钮并且确认离开uni.navigateBack,页面闪一下它会返回当前页面,我理解是pushstate添加了一个历史记录就有了两个记录,back的时候回第一个记录了,于是我uni.navigateBack({ delta:2 }),这时候它可以回到b页面了,但是物理返回按钮返回却直接跳到a页面了,同一个回调函数为什么会有这样的情况?
2、a navigateTo
b,b redirectTo
c,返回方法改为uni.redirectTo,c=>b=>a全程使用物理返回按钮返回功能正常,
①当我在c使用自定义返回按钮返回,会去到b,
②b用物理返回按钮返回时,b会去到c,
③c这时候如果用物理返回按钮会回到b,
④再物理返回按钮会回到a,
我的理解:①之前的历史记录是[a,c,c],①之后变成[a,c,b],
②之后变成[a,c],③之后变成[a,b],④之后变成[a],
而如果在③用自定义按钮返回,③④会无限循环,
为什么全程物理返回按钮没问题,加入了自定义返回按钮的操作就变的不一样了?
3、在2的问题上,我在b也监听popstate,强行让页面跳到a,这时候a物理返回在一些情况会去到b,一些情况去到c,就更复杂了,不描述。
4、在3的问题上,我在a也监听popstate,这时候其它不监听popstate的页面回到a的时候,a会被重新加载等于location.href,这时候首页store缓存的数据就每没有缓存的作用了,把其它没监听的页面都加上popstate监听就不会出现这个问题。
onHide() {
// 卸载监听
window.removeEventListener('popstate', this.goBack);
},
beforeDestroy() {
// 卸载监听
window.removeEventListener('popstate', this.goBack);
},
onUnload(){
// 卸载监听
window.removeEventListener('popstate', this.goBack);
},
async onLoad(option) {
this.$historyUrl();
}
async onShow() {
window.addEventListener('popstate', this.goBack);
}
async goBack() {
this.modalType = 2;
this.showBox = true;
const res = await new Promise(resolve => {
uni.$on('leave', (res) => { // 监听是否离开弹窗点击按钮
resolve(res)
});
})
if (!res) {
this.$historyUrl();
} else{
uni.redirectTo({
url: redirectMap[this.subType]
})
}
this.showBox = false;
},
有没有其它方案解决这个需求?
🤔可以考虑通过 uni.navigateBack()、uni.redirectTo() 等 uni-app 提供的 API 来实现需求。具体实现方案如下:
1、在每个页面的 onLoad() 钩子中使用 uni.addInterceptor() 方法注册一个拦截器,用于拦截页面的返回操作:
export default {
onLoad() {
// 注册拦截器
uni.addInterceptor((to, options) => {
// 保存当前页面的路径
this.fromUrl = getCurrentPages().slice(-2)[0].route
// 如果是返回操作,则弹出确认框
if (options.navigateBack && options.delta === 1) {
// 执行弹框操作,返回 true 或 false
const shouldBack = await this.showModal()
// 根据弹框操作的结果进行返回或取消
if (shouldBack) {
// 执行返回操作
uni.navigateBack({ delta: 1 })
} else {
// 取消返回操作,阻止拦截器继续执行
return Promise.reject('cancel')
}
}
// 放行拦截器
return options
})
},
methods: {
async showModal() {
return new Promise(resolve => {
uni.showModal({
title: '确定返回吗?',
success(res) {
resolve(res.confirm)
},
fail() {
resolve(false)
}
})
})
}
}
}
在拦截器中,如果发现当前操作是返回操作,则弹出确认框,根据确认框的结果决定是否执行返回操作。
2、在每个页面的 onUnload() 钩子中使用 uni.removeInterceptor() 方法注销拦截器:
export default {
onUnload() {
// 注销拦截器
uni.removeInterceptor()
}
}
在页面卸载时,确保注销掉之前注册的拦截器,避免出现意外的行为。
3、在需要自定义返回行为的页面中,使用 uni.navigateBack() 或 uni.redirectTo() 方法来实现自定义返回行为:
export default {
methods: {
goBack() {
// 执行自定义返回行为
uni.redirectTo({
url: '/pages/b/index'
})
}
}
}
在执行自定义返回行为时,可以使用 uni.navigateBack() 或 uni.redirectTo() 等 uni-app 提供的 API 来实现,在拦截器中不再拦截这些自定义的返回操作。
4、为了确保用户只能返回到当前页面的入口页面,可以在 onLoad() 钩子中记录当前页面的入口页面,然后在拦截器中进行判断:
export default {
onLoad() {
// 记录当前页面的入口页面
const pages = getCurrentPages()
this.fromUrl = pages[pages.length - 2]?.route || ''
// 注册拦截器...
},
methods: {
showModal() {
// ...
}
}
}
在进行返回操作时,可以比较当前页面和入口页面的路径是否相同,如果不同则不允许返回。