Skip to content

微信小程序 page-container 拦截返回并弹窗

约 1228 字大约 4 分钟

微信小程序

2024-08-26

需求:一个原生页活动专题,从专题页进去商详页,实现用户返回退出商详页时发放优惠券,即弹出优惠券弹窗。

用小程序原生组件page-container来实现, 官方文档:视图容器 / page-container

index 页面好比商详页,从上一级跳转进来的,可以返回。

思路:如果符合弹窗条件,show=true,组件一直存在页面中。初 始showModal 值为 false,组件 display 为 none,并不显示。当用户有返回行为时,即触发了组件的 beforeleave 事件(其实在离开组件时还会触发离开中、离开后事件,选择离开前进行显示弹窗操作较为合适),在事件中设置两个变量都为true,即显示组件。

遇到的问题:本人在将次逻辑运用于实际业务代码中时,在未触发返回操作,组件一直未显示时,会无法滚动页面,因为整个Page会被自动加上样式 style="position:fixed;top:0px;",导致页面无法滚动。我的解决方案是:在 wxss 中给 Page 加如下样式,不让它 fixed。但在 demo 中并没有出现这个问题。

Page{
  position: relative !important;
  top: 0px !important;
}

小程序 Demo 代码如下:

index.wxml
<scroll-view scroll-y style="height: 100vh;">
  <view style="width: 100vw;height: 900rpx;background-color: bisque;"></view>
  <view style="width: 100vw;height: 900rpx;background-color: cyan;"></view>
</scroll-view>

<view wx:if="{{show}}">
  <page-container 
    show="{{show}}"
    round="{{round}}"
    duration="{{duration}}"
    position="{{position}}"
    close-on-slide-down="{{false}}"
    bindbeforeleave="onBeforeLeave"
    custom-style="display:{{showModal ? 'block' : 'none'}};width: 100vw; height: 100vh; "
    overlay="{{false}}"
  >
    <view class="detail-page">
      <button type="primary" bindtap="exit">推出</button>
    </view>
  </page-container>
</view>

Taro Demo 代码如下:

@/components/BeforeLeavePopup/index.vue
<!--
描述:离开页面前确认弹窗(小程序只会首次离开弹窗)
支持端:weapp h5 webview
功能:
* NOTE: overlay 属性失效
-->
<template>
    <div class="beforeLeavePopup">
        <!-- 用来阻止离开,会触发beforeleave -->
        <!-- prettier-ignore -->
        <page-container
            class="exitDialog"
            :show="isExitDialogShow"
            :z-index="9999"
            round
            :duration="0"
            :overlay="false"
            custom-style="width: 0; height: 0;"
            overlay-style="display: none;"
            @beforeleave="onBeforeleave">
        </page-container>

        <!-- 通用确认弹窗 -->
        <Dialog ref="DialogRef" class="common-dialog" />
    </div>
</template>
<script setup lang="ts">
import {ref, onMounted} from 'vue';
import Taro, {useDidShow, useDidHide, useUnload} from '@tarojs/taro';
import Dialog from '../Dialog/index.vue';

const props = defineProps<{
    /** 提示信息 */
    message?: string;
    /** 判断是否可以离开 */
    canLeave: () => Promise<boolean> | boolean;
    /** 点击确定离开的回调 */
    beforeleave?: () => any;
}>();

const DialogRef = ref();

onMounted(proxyGo);
useDidShow(proxyGo);
useDidHide(recoverGo);
useUnload(recoverGo);

// 拦截history.go
const go = window?.history?.go;
function proxyGo() {
    // 拦截history.go
    // 只在h5下起作用
    if (!go) {
        return;
    }
    history.go = async function (...argus) {
        const canLeave = await props.canLeave?.();
        if (canLeave ?? true) {
            return go.call(history, ...argus);
        }
        history.go = go;
        showExitDialog();
    };
}
function recoverGo() {
    console.log('恢复history.go');
    history.go = go;
}

// 退出前检查内容是否修改,修改提示保存
const isExitDialogShow = ref(true);
async function onBeforeleave() {
    // 判断数据是否变化
    try {
        const canLeave = await props.canLeave?.();
        // 没有修改内容
        if (canLeave ?? true) {
            closeExitDialog();
            return;
        }
    } catch (error) {
        console.error(error);
    }

    showExitDialog();
}

function showExitDialog() {
    isExitDialogShow.value = true;
    DialogRef.value.onShow({
        content: props.message ?? '确定离开?',
        affirmConfig: {
            onClick: async () => {
                await props.beforeleave?.();
                closeExitDialog();
            }
        }
    });
}
function closeExitDialog() {
    isExitDialogShow.value = false;
    setTimeout(() => {
        Taro.navigateBack();
    });
}
</script>
<style lang="scss">
// page-container会导致page无法滚动
page {
    position: relative !important;
    top: 0px !important;
}
.beforeLeavePopup {
    // 弹窗
    .common-dialog {
        .only-content {
            font-size: 16px !important;
        }
    }
}
</style>