微信小程序 page-container 拦截返回并弹窗
需求:一个原生页活动专题,从专题页进去商详页,实现用户返回退出商详页时发放优惠券,即弹出优惠券弹窗。
用小程序原生组件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>index.js
Page({
data: {
show: true,
duration: 300,
position: 'center',
round: false,
overlay: true,
showModal: false,
},
onShow() {
},
exit() {
this.setData({
show: false,
showModal: false,
})
},
onBeforeLeave(res) {
console.log(res)
if(this.data.show){
this.setData({
showModal: true,
show: true,
})
}
},
})index.wxss
.detail-page {
width: 100%;
height: 100%;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}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>EditContainer.vue
<template>
<scroll-view class="editContainer" style="height: 100vh" :scroll-y="true">
<Title class="title" :btn-type="btnType" :name="name" :module-id="moduleId" @save="onSaveClick" @del="onDelClick" />
<div class="mainForm">
<slot></slot>
</div>
<!-- 用来阻止离开,会触发beforeleave -->
<BeforeLeavePopup message="内容修改未保存,是否离开?" :can-leave="canLeave" />
<!-- 通用确认弹窗 -->
<Dialog ref="DialogRef" class="common-dialog" />
</scroll-view>
</template>
<script setup lang="ts">
import {ref} from 'vue';
import Taro, {useRouter} from '@tarojs/taro';
import throttle from 'lodash/throttle';
import * as gio from '@/gioData/resumeNew';
import {isObjectEuqual} from '@/pagesZhiye/resume/util';
// components
import {Dialog} from '@ncfe/nowpick-ui-h5';
import BeforeLeavePopup from '@/components/BeforeLeavePopup/index.vue';
import Title from './Title.vue';
// store
import {useModule} from '@/pagesZhiye/resume/pinia/module';
const {moduleType} = useRouter().params;
const props = defineProps<{
moduleId?: number;
/** title名称 */
name?: string;
/** 验证,失败返回false,成功返回要提交的form */
validate: (notShowError?: boolean) => Promise<Record<string, any>> | Record<string, any> | false;
beforeDel?: () => Promise<boolean> | boolean;
btnType?: 'del';
typeId?: number;
save?: Function;
}>();
const {save, delItem, setIsEditing, module, data} = useModule(moduleType as any, props.moduleId, props.typeId);
// 保存
const onSaveClick = throttle(
async () => {
gio.clicksaveResume({
contentType_var: module.moduleName,
resumeID_var: module.resumeId
});
_canLeave.value = true;
let form = await props.validate();
if (!form) {
return;
}
try {
// 保存
const errMsg = await save(form);
if (errMsg) {
throw new Error(errMsg);
}
setIsEditing(false);
Taro.navigateBack();
} catch (error) {
console.error(error);
}
},
1000,
{trailing: false}
);
// 删除
const DialogRef = ref();
const _canLeave = ref(false);
const onDelClick = () => {
if (props.beforeDel?.() === false) {
return;
}
_canLeave.value = true;
DialogRef.value.onShow({
content: '是否删除此项内容',
affirmConfig: {
onClick() {
delItem().then(() => Taro.navigateBack());
}
},
cancelConfig: {
onClick() {
_canLeave.value = false;
}
}
});
};
// 退出前检查内容是否修改,修改提示保存
async function canLeave() {
if (_canLeave.value) {
return true;
}
// 判断数据是否变化
try {
const form = await props.validate(true);
// 没有修改内容
return !form || isObjectEuqual(form, data.value);
} catch (error) {
console.error(error);
}
return true;
}
</script>
<style lang="scss">
.editContainer {
background: #fff;
min-height: 100%;
position: relative;
.mainForm {
padding-left: 16px;
padding-right: 16px;
padding-bottom: 16px;
.__input-box {
padding-left: 0;
}
}
}
</style>
<style lang="scss">
@import './edit.scss';
// 覆盖样式
.editContainer {
padding-bottom: 36px;
// 弹窗
.common-dialog {
.only-content {
font-size: 16px !important;
}
}
}
</style>