Vue带弹窗组件回退监听,实现弹窗打开时点击浏览器返回或触发手机返回时页面不回退而是关闭弹窗

Vue带弹窗组件回退监听,实现弹窗打开时点击浏览器返回或触发手机返回时页面不回退而是关闭弹窗

最近在做移动端的时候遇到了这样一个问题,封装的某些带有弹窗的组件,在弹窗弹出时点击触发手机的返回或者是点击了浏览器的回退,会出现页面返回了上一个访问的页面,但是弹窗没有关闭的问题,需要实现的是弹窗打开时点击返回使弹窗关闭,页面不返回
为了解决这个问题查阅了一些资料,个人采取的是在弹窗弹出时通过 window.addEventListener(“popstate”, onPopstate, false);监听页面的返回,然后在打开弹窗时,通过pushState改变页面的url(pushState会改变页面的url但不刷新页面),使得弹窗打开时模拟进入了新的路由页面,这样点击返回时就相当于返回到了当前页面,只需要再进行关闭弹窗的操作即可

//弹窗组件内
//弹窗标识,确保每次渲染唯一
const backKey = Math.random();

onMounted(() => {
  //添加返回监听
  addListenerAppBack(backKey, function () {
    console.log("如果弹窗已经打开,且页面发生了回退,则将弹窗关闭", backKey);
    // 如果弹窗已经打开,且页面发生了回退,则将弹窗关闭
    if (modelShow.value) {
      modelShow.value = false;
    }
  });
});

watch(
  () => modelShow.value,
  (newVal) => {
    if (!newVal) {
      // 消除多余的路由, 回退做了阻止多余回退,不必处理重复调用
      appBack();
    }
  }
);

/**
 * 显示弹窗
 * @return {*}
 */
const show = async () => {
  modelShow.value = true;
  // 每次打开弹窗时都需改变一下history.state的状态
  await changeHistoryState(backKey);
};


封装的方法js代码
import router from '../router';
import {isEmpty} from "lodash";

/**
 * 【注意】:
 *  1、请保证每一个弹窗开启时仅执行一次 changeHistoryState()
 */

//弹窗回调列表
let backList = [{
  id: '',
  onAppBack: ''
}]
//监听标识
let popStateFlag = false

/**
 * 弹窗(model)标识
 * 回退栈,用于处理嵌套弹窗
 */
let currentModelBackKeyStack = []
//原 history 状态
let originState = ''
//修改后的 history 状态
let originStateEdit = ''

export function useListenerAppBack() {
  return {
    appBack,
    changeHistoryState,
    addListenerAppBack,
    emptyBackListener
  };
}

/**
 * 消除推入的当前页
 */
function appBack() {
  //阻止多余的返回
  if (history.state.current !== originStateEdit ) {
    return
  }
  window.history.back();
}

/**
 * 监听返回事件并阻止返回到上一个页面
 *
 * @param backKey 回退标识
 * @param onAppBack 自定义返回逻辑
 */
function addListenerAppBack(backKey, onAppBack) {
  if (!window.history.pushState) {
    console.warn('当前浏览器不支持 pushState!');
  }
  //存储原始的状态
  originState = history.state.current;
  //放入弹窗回调列表
  const idx = backList.findIndex(i => i.id === backKey)
  let backOjk = {
    id: backKey,
    onAppBack
  }
  idx < 0
      ? backList.push(backOjk)
      : backList.splice(idx, 1, backOjk)

  //避免重复监听
  if (!popStateFlag) {
    // 监听 popstate 事件
    window.addEventListener("popstate", onPopstate, false);
    popStateFlag = true
  }
}

/**
 * 改变一下history.state的状态
 * @param modelBackKey 等同于 backKey
 */
async function changeHistoryState(modelBackKey) {
  //压入回退栈
  currentModelBackKeyStack.unshift(modelBackKey)
  let currentQuery = router.currentRoute.value.query
  // 刷新时如果行为不符合预期,可在此补充路由参数,目前只有name和query属性
  await router.push({
    name: router.currentRoute.value.name,
    query: {
      ...currentQuery,
      activeDate: new Date().getTime(),
    }
  })

  history.replaceState({...history.state, target: originState, random: Math.random()}, '')
  //存储更改后的状态
  originStateEdit = history.state.current;
}

/**
 * 重置监听状态
 */
function emptyBackListener() {
  // 移除监听 popstate 事件
  window.removeEventListener("popstate", onPopstate, false);
  //清空回退栈
  popStateFlag = false
  backList = []
  currentModelBackKeyStack = []
  originStateEdit = ''
}

/**
 * 页面返回时执行
 * @param e
 */
function onPopstate(e) {
  //e.state.current !== originState ||&& ( e.state.current === originState)
  //if (e.state && ( e.state.current === originState || e.state.target === originState) ) {
  //TODO 嵌套弹窗处理
  if (e.state) {
    appBackCallBack()
  }
}

/**
 * 执行自定义返回逻辑回调
 */
function appBackCallBack() {
  console.log("执行自定义返回逻辑回调currentModelBackKeyStack---",currentModelBackKeyStack);
  let back = backList.find(item => item.id === currentModelBackKeyStack[0])
  if (!isEmpty(back)) {
    back.onAppBack()
  }
  //弹出关闭的标识
  currentModelBackKeyStack.shift()
}




代码来自公司内部大佬

你可能感兴趣的:(vue.js,javascript,前端)