uniapp公众号h5物理按钮返回问题

需求:页面a、b、c,并且a=>b=>c,b和c都有自定义返回按钮,c页面需要在页面返回(物理返回按钮或者自定义返回按钮)前给提示,并且每一个页面只能返回到它的入口页

img

解决方案:在onLoad里面pushState, onShow监听popstate
问题:
1、a navigateTob,b navigateToc,在c自定义返回按钮点击的回调函数是popstate的回调函数,点击自定义按钮并且确认离开uni.navigateBack,页面闪一下它会返回当前页面,我理解是pushstate添加了一个历史记录就有了两个记录,back的时候回第一个记录了,于是我uni.navigateBack({ delta:2 }),这时候它可以回到b页面了,但是物理返回按钮返回却直接跳到a页面了,同一个回调函数为什么会有这样的情况?
2、a navigateTob,b redirectToc,返回方法改为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() {
      // ...
    }
  }
}

在进行返回操作时,可以比较当前页面和入口页面的路径是否相同,如果不同则不允许返回。