# dialogPage概述

HBuilderX 4.31+新增了dialogPage,适用于制作弹框和内置界面。

# 需求背景

  • uni.showModal、actionsheet,自定义性不足
  • 通过前端组件实现的弹框,无法覆盖pages.json的导航栏和tabbar
  • 前端实现的弹框,无法拦截back按键,一点back整页关了
  • 组件方式实现弹框,需要每个页面都引入组件,写法较麻烦
  • 部分内置API涉及界面但没有统一管理,比如chooseLocation、previewImage等。

# dialogPage方案

dialogPage是一种背景透明的页面,可以覆盖pages.json中的导航栏和tabbar。之前的page被称为主page或parentPage。dialogPage需要挂在主page上。

dialogPage是一种特殊的page,它和主page有很多相同之处:

  • dialogPage需在pages.json注册
  • dialogPage有页面生命周期,onLoad里也可以拿到各种参数
  • dialogPage里如果引用了组件,对于组件而言,其page就是dialogPage。组合式组件中监听onPageShow,是监听dialogPage,而不是dialogPage的parentPage。
  • dialogPage可以通过uni.$on等eventbus方案进行页面级通信

dialogPage和主page的区别:

  • dialogPage的背景固定为透明、大小为铺满应用。蒙层由页面内部实现,蒙层颜色、是否响应点击,均由页面内部处理。如果是模态,蒙层不应该允许点击;非模态,则点击蒙层应关闭dialogPage
  • dialogPage不使用uni.navigatorTo等路由API,而是单独提供了openDialogPagecloseDialogPage
  • dialogPage不影响页面栈和路由地址,在getCurrentPages里不能直接得到dialogPage(需在UniPage对象通过getDialogPages获取)
  • 因为dialogPage不进入主页面栈,那么uni.getElementById是无法获取到dialogPage内的元素的。因为uni这个全局API是获取栈顶元素。如果想获取指定页面的元素,需获取到指定页面的UniPage对象,在这个对象上使用.getElementById方法。如果想获取当前dialogPage页面的元素,应该使用this.$page.getElementById()
  • dialogPage在Android上并不是一个activity,而是一个全屏view,它和主page所属同一个activity。
  • dialogPage默认不响应iOS侧滑返回,即disableSwipeBack默认值为true,可以在pages.json中进行配置。响应Android和Harmony的back键和back手势,可通过dialogPage onBackPress生命周期控制是否阻止Android的back键和back手势关闭dialogPage。
  • dialogPage默认不影响调用页面或其parentPage的show、hide生命周期。如需影响,比如弹出全屏界面时,需手动设置triggerParentHide
  • dialogPage中可以调用普通路由api,比如uni.navigateTo、navigateBack,但并不作用于dialogPage,而是作用于其parentPage。即,之前的路由API均只作用于主Page。
  • 在web平台,dialogPage显示时,不影响URL的变化。
  • dialogPage默认没有窗体动画。如果是半屏内容,建议在页面内通过css或uts操作页面元素进行动画,灵活度更高。如果是全屏界面,可以使用窗体动画,但没有popin这种与上一个页面的联动动画。

dialogPage的绑定:

  • dialogPage需绑定在某个主页面上,即parentPage。parentPage页面关闭时,自动销毁相关dialogPage。
  • 在app的onLaunch调用openDialogPage,绑定到首页。
  • openDialogPage 时可通过 parentPage 参数指定所属页面,不指定时默认为当前页面。

多dialogPage注意事项:

  • dialogPage可以有多个,通过UniPage对象的getDialogPages()可以获取主页面挂载的所有dialogPage。
  • 多个dialogPage层叠时,可以通过close api任意关闭某个dialogPage。
  • 当前 dialogPage 关闭会触发前一个 dialogPage onShow 生命周期

调用时机注意事项:

  • 最早的调用时机是在app的onLaunch里调用openDialogPage,不支持在main.uts中调用openDialogPage。

app-android平台注意事项:

  • dialogPage不会创建Android原生Activity,复用parentPage的Android原生Activity。

# uni.openDialogPage(options) GitCodeGitHub

打开模态弹窗页面

# openDialogPage 兼容性

Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
4.31 x 4.31 4.31 4.31 4.61

# 参数

名称 类型 必填 默认值 兼容性 描述
options OpenDialogPageOptions -
打开 dialogPage 参数
名称 类型 必备 默认值 兼容性 描述
url string (string.PageURIString) -
需要跳转的应用内非 tabBar 的页面的路径 , 路径后可以带参数
animationType string none
窗口显示的动画类型
合法值 兼容性 描述
auto
自动选择动画效果
none
无动画效果
slide-in-right
从右侧横向滑动效果
slide-in-left
左侧横向滑动效果
slide-in-top
从上侧竖向滑动效果
slide-in-bottom
从下侧竖向滑动效果
fade-in
从透明到不透明逐渐显示效果
zoom-out
从小到大逐渐放大显示效果
zoom-fade-out
从小到大逐渐放大并且从透明到不透明逐渐显示效果
animationDuration number -
窗口关闭动画的持续时间,单位为 ms
disableEscBack boolean false
是否禁用按键盘 ESC 时关闭
parentPage UniPage -
要绑定的父级页面实例
triggerParentHide boolean false
是否触发父页面的 onHide 生命周期
success (result: OpenDialogPageSuccess) => void -
接口调用成功的回调函数
fail (result: OpenDialogPageFail) => void -
接口调用失败的回调函数
complete (result: OpenDialogPageComplete) => void -
接口调用结束的回调函数(调用成功、失败都会执行)

# OpenDialogPageSuccess 的属性值

名称 类型 必备 默认值 兼容性 描述
errMsg string -

# OpenDialogPageFail 的属性值

名称 类型 必备 默认值 兼容性 描述
errCode number -
路由错误码
- 4: 框架内部异常
errSubject string -
统一错误主题(模块)名称
data any -
错误信息中包含的数据
cause Error -
源错误信息,可以包含多个错误,详见SourceError
errMsg string -

# OpenDialogPageComplete 的属性值

名称 类型 必备 默认值 兼容性 描述
errMsg string -

# 返回值

类型 必备
UniPage

# 参见

# uni.closeDialogPage(options?) GitCodeGitHub

关闭模态弹窗页面

closeDialogPage 可通过 dialogPage 参数指定要关闭的 dialogPage, 不指定时默认关闭当前页面的所有 dialogPage

# closeDialogPage 兼容性

Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
4.31 x 4.31 4.31 4.31 4.61

# 参数

名称 类型 必填 默认值 兼容性 描述
options CloseDialogPageOptions -
关闭 dialogPage 参数
名称 类型 必备 默认值 兼容性 描述
dialogPage UniPage -
要关闭的 dialogPage 实例
animationType string auto
窗口关闭的动画类型
合法值 兼容性 描述
auto
自动选择动画效果
none
无动画效果
slide-out-right
横向向右侧滑出屏幕动画
slide-out-left
横向向左侧滑出屏幕动画
slide-out-top
竖向向上侧滑出屏幕动画
slide-out-bottom
竖向向下侧滑出屏幕动画
fade-out
从不透明到透明逐渐隐藏动画
zoom-in
从大逐渐缩小关闭动画
zoom-fade-in
从大逐渐缩小并且从不透明到透明逐渐隐藏关闭动画
animationDuration number -
窗口关闭动画的持续时间,单位为 ms
success (result: CloseDialogPageSuccess) => void -
接口调用成功的回调函数
fail (result: CloseDialogPageFail) => void -
接口调用失败的回调函数
complete (result: CloseDialogPageComplete) => void -
接口调用结束的回调函数(调用成功、失败都会执行)

# CloseDialogPageSuccess 的属性值

名称 类型 必备 默认值 兼容性 描述
errMsg string -

# CloseDialogPageFail 的属性值

名称 类型 必备 默认值 兼容性 描述
errCode number -
路由错误码
- 4: 框架内部异常
errSubject string -
统一错误主题(模块)名称
data any -
错误信息中包含的数据
cause Error -
源错误信息,可以包含多个错误,详见SourceError
errMsg string -

# CloseDialogPageComplete 的属性值

名称 类型 必备 默认值 兼容性 描述
errMsg string -

# 返回值

类型
null

# 参见

# 示例

示例为hello uni-app x alpha分支,与最新HBuilderX Alpha版同步。与最新正式版同步的master分支示例另见

扫码体验(手机浏览器跳转到App直达页)

示例

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1;">
  <!-- #endif -->
  <view class="uni-padding-wrap">
    <view class="uni-common-mt flex-row" v-if="pageBody != null">pageBody: {
      top: <text id="page-body-top">{{pageBody!.top}}</text>,
      right: <text id="page-body-right">{{pageBody!.right}}</text>,
      bottom: <text id="page-body-bottom">{{pageBody!.bottom}}</text>,
      left: <text id="page-body-left">{{pageBody!.left}}</text>,
      width: <text id="page-body-width">{{pageBody!.width}}</text>,
      height: <text id="page-body-height">{{pageBody!.height}}</text>
      }
    </view>
    <view class="uni-common-mt flex-row" v-if="safeAreaInsets != null">safeAreaInsets: {
      top: <text id="page-safe-area-insets-top">{{safeAreaInsets!.top}}</text>,
      right: <text id="page-safe-area-insets-right">{{safeAreaInsets!.right}}</text>,
      bottom: <text id="page-safe-area-insets-bottom">{{safeAreaInsets!.bottom}}</text>,
      left: <text id="page-safe-area-insets-left">{{safeAreaInsets!.left}}</text>}
    </view>
    <!-- #ifdef APP-ANDROID || APP-IOS || APP-HARMONY || WEB -->
    <view class="uni-common-mt flex-row" v-if="width != null">width: <text id="page-width">{{width!}}</text>
    </view>
    <view class="uni-common-mt flex-row" v-if="height != null">height: <text id="page-height">{{height!}}</text>
    </view>
    <view class="uni-common-mt flex-row" v-if="statusBarHeight != null">statusBarHeight: <text id="page-statusBarHeight">{{statusBarHeight!}}</text>
    </view>
    <!-- #endif -->
    <button class="uni-common-mt" id="go-next-page" @click="goNextPage">
      go next page
    </button>
    <button class="uni-common-mt" id="open-dialog1" @click="openDialog1">
      open dialog 1
    </button>
    <button class="uni-common-mt" id="open-dialog1-wrong-path" @click="openDialog1WrongPath">
      open dialog page 1 with wrong path
    </button>
    <button class="uni-common-mt" id="go-next-page-open-dialog1" @click="goNextPageOpenDialog1">
      go next page & open dialog1
    </button>
    <button class="uni-common-mt" id="open-dialog1" @click="openDialog3">
      open dialog 3 test page style
    </button>
    <button class="uni-common-mt" id="open-dialog4" @click="openDialogWithTriggerParentHide">
      openDialog with triggerParentHide
    </button>
    <button class="uni-common-mt" id="open-dialog5" @click="openDialogCheckMoreAttribute">
      openDialog check more attribute
    </button>
    <button class="uni-common-mt" id="open-dialog5" @click="openDialogWithRelativePath">
      openDialog with relative path
    </button>
    <text class="uni-common-mt choose-open-animation-type-title">choose open dialogPage animationType</text>
    <radio-group class="choose-open-animation-type-radio-group" @change="handleOpenAnimationType">
      <radio class="ml-10 uni-common-mt" v-for="item in openAnimationTypeList" :key="item" :value="item"
        :checked="openAnimationType == item">{{ item }}
      </radio>
    </radio-group>
  </view>
  <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
  import {
    state,
    setLifeCycleNum
  } from '@/store/index.uts'

  type OpenAnimationType =
    'auto' |
    'none' |
    'slide-in-right' |
    'slide-in-left' |
    'slide-in-top' |
    'slide-in-bottom' |
    'fade-in' |
    'zoom-out' |
    'zoom-fade-out'

  export default {
    data() {
      return {
        pageBody : null as UniPageBody | null,
        safeAreaInsets: null as UniSafeAreaInsets | null,
        width: null as number | null,
        height: null as number | null,
        statusBarHeight: null as number | null,
        jest_click_x: -1,
        jest_click_y: -1,
        openAnimationType: 'none' as OpenAnimationType,
        openAnimationTypeList: [
          'auto',
          'none',
          'slide-in-right',
          'slide-in-left',
          'slide-in-top',
          'slide-in-bottom',
          'fade-in',
          'zoom-out',
          'zoom-fade-out'
        ]
      }
    },
    onLoad() {
      console.log('dialogPage parent onLoad')
    },
    onShow() {
      console.log('dialogPage parent onShow')
      setLifeCycleNum(state.lifeCycleNum + 10)
    },
    onReady() {
      console.log('dialogPage parent onReady')
      const currentPage = this.$page
      this.pageBody = currentPage.pageBody;
      this.safeAreaInsets = currentPage.safeAreaInsets
      // #ifdef APP-ANDROID || APP-IOS || APP-HARMONY || WEB
      this.width = currentPage.width
      this.height = currentPage.height
      this.statusBarHeight = currentPage.statusBarHeight
      // #endif
    },
    onHide() {
      console.log('dialogPage parent onHide')
      setLifeCycleNum(state.lifeCycleNum - 10)
    },
    onUnload() {
      console.log('dialogPage parent onUnload')
    },
    methods: {
      goNextPage() {
        uni.navigateTo({
          url: '/pages/API/dialog-page/next-page'
        })
      },
      openDialog1() {
        uni.openDialogPage({
          url: '/pages/API/dialog-page/dialog-1?name=dialog1',
          animationType: this.openAnimationType,
          success(res) {
            console.log('openDialogPage1 success', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          },
          fail(err) {
            console.log('openDialogPage1 fail', err)
            setLifeCycleNum(state.lifeCycleNum - 4)
          },
          complete(res) {
            console.log('openDialogPage1 complete', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          }
        })
      },
      openDialog2() {
        uni.openDialogPage({
          url: '/pages/API/dialog-page/dialog-2',
          animationType: this.openAnimationType,
          disableEscBack: true,
          success(res) {
            console.log('openDialog2 success', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          },
          fail(err) {
            console.log('openDialog2 fail', err)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum - 4)
          },
          complete(res) {
            console.log('openDialog2 complete', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          }
        })
      },
      openDialog1WrongPath() {
        uni.openDialogPage({
          url: '/pages/API/dialog-page/dialog-11?name=dialog1',
          success(res) {
            console.log('openDialogPage1 success', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          },
          fail(err) {
            console.log('openDialogPage1 fail', err)
            setLifeCycleNum(state.lifeCycleNum - 4)
          },
          complete(res) {
            console.log('openDialogPage1 complete', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          }
        })
      },
      goNextPageOpenDialog1() {
        uni.navigateTo({
          url: '/pages/API/dialog-page/next-page',
          success() {
            setTimeout(() => {
              uni.openDialogPage({
                url: '/pages/API/dialog-page/dialog-1?name=dialog1',
                animationType: this.openAnimationType,
                success(res) {
                  console.log('openDialogPage1 success', res)
                  // 自动化测试
                  setLifeCycleNum(state.lifeCycleNum + 1)
                },
                fail(err) {
                  console.log('openDialogPage1 fail', err)
                  // 自动化测试
                  setLifeCycleNum(state.lifeCycleNum - 4)
                },
                complete(res) {
                  console.log('openDialogPage1 complete', res)
                  // 自动化测试
                  setLifeCycleNum(state.lifeCycleNum + 1)
                }
              })
            }, 2000)
          }
        })
      },
      closeDialog() {
        uni.closeDialogPage({
          success(res) {
            console.log('closeDialog success', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          },
          fail(err) {
            console.log('closeDialog fail', err)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum - 4)
          },
          complete(res) {
            console.log('closeDialog complete', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          }
        })
      },
      closeSpecifiedDialog(index : number) {
        const dialogPages = this.$page.getDialogPages()
        uni.closeDialogPage({
          dialogPage: dialogPages[index],
          success(res) {
            console.log('closeSomeOneDialog success', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          },
          fail(err) {
            console.log('closeSomeOneDialog fail', err)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum - 4)
          },
          complete(res) {
            console.log('closeSomeOneDialog complete', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          }
        })
      },
      // 自动化测试
      openDialog4() {
        uni.openDialogPage({
          url: '/pages/API/dialog-page/dialog-4',
        })
      },
      openDialogWithTriggerParentHide() {
        uni.openDialogPage({
          url: `/pages/API/dialog-page/dialog-4?tag=${Date.now()}`,
          triggerParentHide: true,
          success(res) {
            console.log('openDialogWithTriggerParentHide success', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          },
          fail(err) {
            console.log('openDialogWithTriggerParentHide fail', err)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum - 4)
          },
          complete(res) {
            console.log('openDialogWithTriggerParentHide complete', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          }
        })
      },
      openDialogCheckMoreAttribute(){
        uni.openDialogPage({
          url: '/pages/API/dialog-page/dialog-5',
        })
      },
      setLifeCycleNum(value : number) {
        setLifeCycleNum(value)
      },
      getLifeCycleNum() : number {
        return state.lifeCycleNum
      },
      jest_OpenDialog1() {
        uni.openDialogPage({
          url: '/pages/API/dialog-page/dialog-1?name=dialog1'
        })
      },
      jest_CloseDialog1() {
        uni.closeDialogPage({})
      },
      jest_getTapPoint() {
        const systemInfo = uni.getSystemInfoSync()
        let ratio = 1
        if (systemInfo.platform == 'android') {
          ratio = systemInfo.devicePixelRatio
        }
        this.jest_click_x = systemInfo.screenWidth / 2 * ratio
        this.jest_click_y = systemInfo.statusBarHeight * ratio + 10
      },
      openDialog2ForTest() {
        uni.openDialogPage({
          url: '/pages/API/dialog-page/dialog-2'
        });
      },
      closeDialog2ForTest() {
        uni.closeDialogPage({});
      },
      setPageStyleForTest(style : UTSJSONObject) {
        const pages = this.$page.getDialogPages();
        if (pages.length > 0) pages[pages.length - 1].setPageStyle(style);
      },
      setPageStyleForTest2(style : UTSJSONObject) {
        this.$page.setPageStyle(style);
      },
      openDialog3() {
        uni.openDialogPage({ url: '/pages/API/dialog-page/dialog-3', animationType: this.openAnimationType })
      },
      handleOpenAnimationType(e : RadioGroupChangeEvent) {
        this.openAnimationType = e.detail.value as OpenAnimationType
      },
      openDialogWithRelativePath(){
        uni.openDialogPage({
          url: './dialog-1?name=dialog1',
          animationType: this.openAnimationType,
          success(res) {
            console.log('openDialogPage1 success', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          },
          fail(err) {
            console.log('openDialogPage1 fail', err)
            setLifeCycleNum(state.lifeCycleNum - 4)
          },
          complete(res) {
            console.log('openDialogPage1 complete', res)
            // 自动化测试
            setLifeCycleNum(state.lifeCycleNum + 1)
          }
        })
      },
      // 自动化测试
      getDialogPage() : UniPage | null {
        const dialogPages = this.$page.getDialogPages()
        return dialogPages.length > 0 ? dialogPages[0] : null
      },
      // 自动化测试
      getDialogPageRoute() : string {
        const dialogPage = this.getDialogPage()
        if(dialogPage != null){
          return dialogPage.route
        }
        return ''
      },
      // 自动化测试
      dialogPageCheckGetDialogPages() : boolean {
        const dialogPage = this.getDialogPage()!
        const dialogPages = dialogPage.getDialogPages()
        const res = dialogPages.length == 0
        return res
      },
      // 自动化测试
      dialogPageGetPageStyle() : UTSJSONObject {
        const dialogPage = this.getDialogPage()!
        return dialogPage.getPageStyle()
      },
      // 自动化测试
      dialogPageSetPageStyle() {
        const dialogPage = this.getDialogPage()!
        dialogPage.setPageStyle({
          backgroundColorContent: 'red'
        })
      },
      // 自动化测试
      dialogPageCheckGetElementById() : boolean {
        const dialogPage = this.getDialogPage()!
        const element = dialogPage.getElementById('dialog1-go-next-page')
        let res = element != null
        // #ifndef APP-ANDROID
        if (res) {
          const elPage = element!.getPage()
          console.log('elPage', elPage)
          res = elPage === dialogPage
        }
        // #endif
        return res
      },
      // 自动化测试
      dialogCheckGetAndroidView() : boolean {
        const dialogPage = this.getDialogPage()!
        const androidView = dialogPage.getAndroidView()
        const res = androidView != null
        return res
      },
      // 自动化测试
      dialogCheckGetIOSView() : boolean {
        const dialogPage = this.getDialogPage()!
        const IOSView = dialogPage.getIOSView()
        const res = IOSView != null
        return res
      },
      // 自动化测试
      dialogCheckGetHTMLElement() : boolean {
        const dialogPage = this.getDialogPage()!
        const HTMLView = dialogPage.getHTMLElement()
        const res = HTMLView != null
        return res
      },
    }
  }
</script>

<style>
  .uni-padding-wrap{
    padding-bottom: var(--uni-safe-area-inset-bottom);
  }

  .ml-10 {
    margin-left: 10px;
  }

  .choose-open-animation-type-title {
    font-weight: bold;
  }

  .choose-open-animation-type-radio-group {
    flex-direction: row;
    flex-wrap: wrap;
    margin-bottom: 20px;
  }
  .flex-row{
    flex-direction: row;
    flex-wrap: wrap;
  }
</style>style>

# 通用类型

# GeneralCallbackResult

名称 类型 必备 默认值 兼容性 描述
errMsg string -
错误信息

# Tips

  • 可通过如下方式获取 dialogPage
// 1. 通过 parentPage 获取 dialogPage 集合
const pages = getCurrentPages()
// 获取当前页面
const page = pages[pages.length-1]
// 获取当前页面的 `dialogPage` 集合
const dialogPages = page.getDialogPages()

// 2. 在 dialogPage 中通过 this.$page 获取 dialogPage 实例 (组件中不支持)
// 选项式 API
const dialogPage = this.$page
// 组合式 API
const currentInstance = getCurrentInstance()
const dialogPage = instance?.proxy?.$page
  • tabBar 页面中的 dialogPage,在 App 端不会随 tabBar 页面切换而隐藏,在 Web 端会随 tabBar 页面切换而隐藏。
    即:在 tabA 页面打开 dialogPage 后 switchTab 到 tabB 页面 在 App 端 dialogPage 仍会显示,
    在 Web 端 dialogPage 会隐藏,再次 switchTab 到 tabA 页面 dialogPage 会显示。