Vue3对话框(Dialog)

简介: 该 Vue2 对话框组件提供丰富的可定制属性,如标题、内容、宽度、高度等,并支持自定义按钮文本和样式。其预览效果展示了多种使用场景,包括全屏切换、加载状态及自定义样式等。该组件适用于各种需要弹窗功能的应用场景。[在线预览](https://themusecatcher.github.io/vue-amazing-ui/guide/components/dialog.html)提供了更多实例。此文章详情见原文链接,若涉及版权问题,请告知以便删除。

可自定义设置以下属性:

  • 标题(title),类型:string | slot,默认 undefined

  • 内容(content),类型:string | slot,默认 undefined

  • 对话框宽度(width),类型:number,单位 px,默认 540

  • 对话框高度(height),类型:number | string,单位 px,默认 'auto',自适应内容高度

  • 取消按钮文字(cancelText),类型:string,默认 '取消'

  • 取消按钮 props 配置(cancelProps),参考 Button 组件 Props,类型:object,默认 {}

  • 确定按钮文字(okText),类型:string,默认 '确认'

  • 确定按钮类型(okType),类型:'primary' | 'danger',默认 'primary'

  • 确认按钮 props 配置(okProps),优先级高于 okType,参考 Button 组件 Props,类型:object,默认 {}

  • 设置对话框 body 样式(bodyStyle),类型:CSSProperties,默认 {}

  • 是否显示底部按钮(footer),类型:boolean | slot,默认 true

  • 是否水平垂直居中(center),类型:boolean,默认 true,(false 时是固定高度水平居中)

  • 固定高度水平居中时,距顶部高度(top),仅当 center: false 时生效,单位 px,类型:string | number,默认 100

  • 是否允许切换全屏(switchFullscreen),允许后右上角会出现一个按钮,类型:boolean,默认 false

  • 确定按钮加载中(loading),类型:boolean,默认 false

  • 对话框是否可见(show),类型:boolean,默认 false

效果如下图:在线预览

其中引入使用了 Vue3按钮(Button)

①创建对话框组件Dialog.vue:

<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import type { CSSProperties } from 'vue'
import Button from '../button'
interface Props {
  title?: string // 标题 string | slot
  content?: string // 内容 string | slot
  width?: number // 对话框宽度,单位 px
  height?: number | string // 对话框高度,单位 px,默认 auto,自适应内容高度
  cancelText?: string // 取消按钮文字
  cancelProps?: object // 取消按钮 props 配置,参考 Button 组件 Props
  okText?: string // 确定按钮文字
  okType?: 'primary' | 'danger' // 确定按钮类型
  okProps?: object // 确认按钮 props 配置,优先级高于 okType,参考 Button 组件 Props
  bodyStyle?: CSSProperties // 设置对话框 body 样式
  footer?: boolean // 是否显示底部按钮 boolean | slot
  center?: boolean // 水平垂直居中:true  固定高度水平居中:false
  top?: string | number // 固定高度水平居中时,距顶部高度,仅当 center: false 时生效,单位 px
  switchFullscreen?: boolean // 是否允许切换全屏,允许后右上角会出现一个按钮
  loading?: boolean // 确定按钮 loading
  show?: boolean // 对话框是否可见
}
const props = withDefaults(defineProps<Props>(), {
  title: undefined,
  content: undefined,
  width: 540,
  height: 'auto',
  cancelText: '取消',
  cancelProps: () => ({}),
  okText: '确定',
  okType: 'primary',
  okProps: () => ({}),
  bodyStyle: () => ({}),
  footer: true,
  center: true,
  top: 100,
  switchFullscreen: false,
  loading: false,
  show: false
})
const fullScreen = ref(false)
const dialogHeight = computed(() => {
  if (typeof props.height === 'number') {
    return props.height + 'px'
  } else {
    return props.height
  }
})
const dialogStyle = computed(() => {
  return {
    width: fullScreen.value ? '100%' : props.width + 'px',
    top: props.center ? '50%' : (fullScreen.value ? 0 : (typeof props.top === 'number' ? props.top + 'px' : props.top))
  }
})
const dialogRef = ref() // DOM 引用
watch(
  () => props.show,
  (to) => {
    if (to) {
      nextTick(() => {
        dialogRef.value.focus()
      })
      // 重置全屏显示
      fullScreen.value = false
    }
  }
)
const emits = defineEmits(['update:show', 'cancel', 'ok'])
function onBlur() {
  emits('update:show', false)
  emits('cancel')
}
function onFullScreen() {
  fullScreen.value = !fullScreen.value
}
function onClose() {
  emits('update:show', false)
  emits('cancel')
}
function onCancel() {
  emits('update:show', false)
  emits('cancel')
}
function onOk() {
  emits('ok')
}
</script>
<template>
  <div>
    <Transition name="fade">
      <div v-show="show" class="m-dialog-mask"></div>
    </Transition>
    <Transition name="zoom">
      <div
        v-show="show"
        ref="dialogRef"
        tabindex="-1"
        class="m-dialog-wrap"
        @click.self="onBlur"
        @keydown.esc="onClose"
      >
        <div
          :class="['m-dialog', center ? 'horizontal-vertical-centered' : 'fix-height-centered']"
          :style="dialogStyle"
        >
          <div class="m-dialog-content" :style="`--height: ${fullScreen ? '100vh' : dialogHeight}`">
            <div class="m-dialog-header">
              <p class="dialog-head">
                <slot name="title">{
  
  { title }}</slot>
              </p>
            </div>
            <span v-if="switchFullscreen" class="m-fullscreen-action" @click="onFullScreen">
              <svg
                v-show="!fullScreen"
                class="icon-svg"
                viewBox="64 64 896 896"
                data-icon="fullscreen"
                aria-hidden="true"
                focusable="false"
              >
                <path
                  d="M290 236.4l43.9-43.9a8.01 8.01 0 0 0-4.7-13.6L169 160c-5.1-.6-9.5 3.7-8.9 8.9L179 329.1c.8 6.6 8.9 9.4 13.6 4.7l43.7-43.7L370 423.7c3.1 3.1 8.2 3.1 11.3 0l42.4-42.3c3.1-3.1 3.1-8.2 0-11.3L290 236.4zm352.7 187.3c3.1 3.1 8.2 3.1 11.3 0l133.7-133.6 43.7 43.7a8.01 8.01 0 0 0 13.6-4.7L863.9 169c.6-5.1-3.7-9.5-8.9-8.9L694.8 179c-6.6.8-9.4 8.9-4.7 13.6l43.9 43.9L600.3 370a8.03 8.03 0 0 0 0 11.3l42.4 42.4zM845 694.9c-.8-6.6-8.9-9.4-13.6-4.7l-43.7 43.7L654 600.3a8.03 8.03 0 0 0-11.3 0l-42.4 42.3a8.03 8.03 0 0 0 0 11.3L734 787.6l-43.9 43.9a8.01 8.01 0 0 0 4.7 13.6L855 864c5.1.6 9.5-3.7 8.9-8.9L845 694.9zm-463.7-94.6a8.03 8.03 0 0 0-11.3 0L236.3 733.9l-43.7-43.7a8.01 8.01 0 0 0-13.6 4.7L160.1 855c-.6 5.1 3.7 9.5 8.9 8.9L329.2 845c6.6-.8 9.4-8.9 4.7-13.6L290 787.6 423.7 654c3.1-3.1 3.1-8.2 0-11.3l-42.4-42.4z"
                ></path>
              </svg>
              <svg
                v-show="fullScreen"
                class="icon-svg"
                viewBox="64 64 896 896"
                data-icon="fullscreen-exit"
                aria-hidden="true"
                focusable="false"
              >
                <path
                  d="M391 240.9c-.8-6.6-8.9-9.4-13.6-4.7l-43.7 43.7L200 146.3a8.03 8.03 0 0 0-11.3 0l-42.4 42.3a8.03 8.03 0 0 0 0 11.3L280 333.6l-43.9 43.9a8.01 8.01 0 0 0 4.7 13.6L401 410c5.1.6 9.5-3.7 8.9-8.9L391 240.9zm10.1 373.2L240.8 633c-6.6.8-9.4 8.9-4.7 13.6l43.9 43.9L146.3 824a8.03 8.03 0 0 0 0 11.3l42.4 42.3c3.1 3.1 8.2 3.1 11.3 0L333.7 744l43.7 43.7A8.01 8.01 0 0 0 391 783l18.9-160.1c.6-5.1-3.7-9.4-8.8-8.8zm221.8-204.2L783.2 391c6.6-.8 9.4-8.9 4.7-13.6L744 333.6 877.7 200c3.1-3.1 3.1-8.2 0-11.3l-42.4-42.3a8.03 8.03 0 0 0-11.3 0L690.3 279.9l-43.7-43.7a8.01 8.01 0 0 0-13.6 4.7L614.1 401c-.6 5.2 3.7 9.5 8.8 8.9zM744 690.4l43.9-43.9a8.01 8.01 0 0 0-4.7-13.6L623 614c-5.1-.6-9.5 3.7-8.9 8.9L633 783.1c.8 6.6 8.9 9.4 13.6 4.7l43.7-43.7L824 877.7c3.1 3.1 8.2 3.1 11.3 0l42.4-42.3c3.1-3.1 3.1-8.2 0-11.3L744 690.4z"
                ></path>
              </svg>
            </span>
            <span class="m-close-action" @click="onClose">
              <svg class="icon-svg" viewBox="64 64 896 896" data-icon="close" aria-hidden="true" focusable="false">
                <path
                  d="M563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8 295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512 196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1 216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
                ></path>
              </svg>
            </span>
            <div class="m-dialog-body" :style="bodyStyle">
              <slot>{
  
  { content }}</slot>
            </div>
            <div class="m-dialog-footer" v-if="footer">
              <slot name="footer">
                <Button class="mr8" @click="onCancel" v-bind="cancelProps">{
  
  { cancelText }}</Button>
                <Button :type="okType" :loading="loading" @click="onOk" v-bind="okProps">{
  
  { okText }}</Button>
              </slot>
            </div>
          </div>
        </div>
      </div>
    </Transition>
  </div>
</template>
<style lang="less" scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s linear;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
.zoom-enter-active {
  transition: all 0.3s;
}
.zoom-leave-active {
  transition: all 0.3s cubic-bezier(0.78, 0.14, 0.15, 0.86);
}
.zoom-enter-from,
.zoom-leave-to {
  opacity: 0;
  transform: scale(0.2);
}
.flex-hv-center {
  // 水平垂直居中方法①:弹性布局,随内容增大高度,并自适应水平垂直居中
  display: flex;
  justify-content: center;
  align-items: center;
}
.horizontal-vertical-centered {
  // 水平垂直居中方法②:相对定位,随内容增大高度,并自适应水平垂直居中
  position: relative;
  top: 50%;
  transform: translateY(-50%);
}
.fix-height-centered {
  // 相对定位,固定高度,始终距离视图顶端100px
  position: relative;
  // top: 100px;
}
.m-dialog-mask {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1000;
  background: rgba(0, 0, 0, 0.45);
}
.m-dialog-wrap {
  position: fixed;
  top: 0;
  inset-inline-end: 0;
  bottom: 0;
  inset-inline-start: 0;
  overflow: auto;
  outline: 0;
  inset: 0;
  z-index: 1010;
  .m-dialog {
    margin: 0 auto;
    .m-dialog-content {
      display: flex;
      flex-direction: column;
      height: var(--height);
      position: relative;
      background-color: #fff;
      border-radius: 8px;
      box-shadow:
        0 6px 16px 0 rgba(0, 0, 0, 0.08),
        0 3px 6px -4px rgba(0, 0, 0, 0.12),
        0 9px 28px 8px rgba(0, 0, 0, 0.05);
      padding: 20px 24px;
      .m-dialog-header {
        color: rgba(0, 0, 0, 0.88);
        background: transparent;
        border-radius: 8px 8px 0 0;
        margin-bottom: 8px;
        max-width: calc(100% - 54px);
        .dialog-head {
          margin: 0;
          color: rgba(0, 0, 0, 0.88);
          font-weight: 600;
          font-size: 16px;
          line-height: 1.5;
          word-break: break-all;
        }
      }
      .m-fullscreen-action {
        .m-close-action();
        inset-inline-end: 48px;
      }
      .m-close-action {
        position: absolute;
        top: 17px;
        inset-inline-end: 17px;
        z-index: 1010;
        font-weight: 600;
        line-height: 1;
        background: transparent;
        border-radius: 4px;
        width: 22px;
        height: 22px;
        cursor: pointer;
        transition: background 0.2s;
        display: flex;
        align-items: center;
        justify-content: center;
        .icon-svg {
          display: inline-block;
          width: 16px;
          height: 16px;
          line-height: 22px;
          fill: rgba(0, 0, 0, 0.45);
          cursor: pointer;
          transition: fill 0.2s;
        }
        &:hover {
          background: rgba(0, 0, 0, 0.06);
          .icon-svg {
            fill: rgba(0, 0, 0, 0.88);
          }
        }
      }
      .m-dialog-body {
        flex: 1;
        font-size: 14px;
        color: rgba(0, 0, 0, 0.88);
        line-height: 1.5714285714285714;
        word-break: break-all;
        overflow: auto;
        transition: all 0.25s;
      }
      .m-dialog-footer {
        text-align: end;
        background: transparent;
        margin-top: 12px;
        .mr8 {
          margin-inline-end: 8px;
        }
      }
    }
  }
}
</style>

②在要使用的页面引入:

<script setup lang="ts">
import Dialog from './Dialog.vue'
import { ref } from 'vue'

const show1 = ref(false)
const show2 = ref(false)
const show3 = ref(false)
const show4 = ref(false)
const show5 = ref(false)
const show6 = ref(false)
const show7 = ref(false)
const show8 = ref(false)
const show9 = ref(false)
const loading = ref(false)
function onCancel() {
  // 点击遮罩层或右上角叉或取消按钮的回调
  console.log('cancel')
}
function onOk() {
  // 点击确定回调
  show1.value = false
  show2.value = false
  show3.value = false
  show4.value = false
  show5.value = false
  show6.value = false
  show7.value = false
  show8.value = false
  show9.value = false
}
function handleCancel() {
  show4.value = false
}
function handleOk() {
  loading.value = true
  setTimeout(() => {
    loading.value = false
    show4.value = false
  }, 2000)
}
function onLoadingOk() {
  // 点击确定回调
  loading.value = true // 开启加载状态
  setTimeout(() => {
    show8.value = false
    loading.value = false
  }, 2000)
}
</script>
<template>
  <div>
    <h1>{
  
  { $route.name }} {
  
  { $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Button type="primary" @click="show1 = true">Open Dialog</Button>
    <Dialog v-model:show="show1" title="Title" @cancel="onCancel" @ok="onOk">
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">自定义宽高</h2>
    <Button type="primary" @click="show2 = true">Open Dialog</Button>
    <Dialog v-model:show="show2" :width="480" :height="180" @ok="onOk">
      <template #title>Title</template>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">自定义按钮文字 & 类型</h2>
    <Button type="primary" @click="show3 = true">Open Dialog</Button>
    <Dialog v-model:show="show3" title="Title" cancelText="cancel" okText="ok" okType="danger" @ok="onOk">
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">自定义底部按钮</h2>
    <Button type="primary" @click="show4 = true">Open Dialog</Button>
    <Dialog
      v-model:show="show4"
      title="Title"
      cancel-text="Return"
      :cancel-props="{ type: 'danger', ghost: true }"
      ok-text="Submit"
      :ok-props="{ type: 'primary', ghost: true, loading: loading }"
      @ok="handleOk"
    >
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">隐藏底部按钮</h2>
    <Button type="primary" @click="show5 = true">Open Dialog</Button>
    <Dialog v-model:show="show5" title="Title" :footer="false" @ok="onOk">
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">固定高度</h2>
    <Button type="primary" @click="show6 = true">Open Dialog</Button>
    <Dialog v-model:show="show6" title="Title" :center="false" :top="120" @ok="onOk">
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">切换全屏</h2>
    <Button type="primary" @click="show7 = true">Open Dialog</Button>
    <Dialog v-model:show="show7" title="Title" switch-fullscreen @ok="onOk">
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">提交 loading</h2>
    <Button type="primary" @click="show8 = true">Open Dialog</Button>
    <Dialog v-model:show="show8" title="Title" :loading="loading" @ok="onLoadingOk">
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
    <h2 class="mt30 mb10">body 样式自定义</h2>
    <Button type="primary" @click="show9 = true">Open Dialog</Button>
    <Dialog v-model:show="show9" title="Title" :body-style="{ fontSize: '20px', color: '#eb2f96' }" @ok="onOk">
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
      <p>Bla bla ...</p>
    </Dialog>
  </div>
</template>
相关文章
|
25天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
22天前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
50 7
|
24天前
|
前端开发 数据库
芋道框架审批流如何实现(Cloud+Vue3)
芋道框架审批流如何实现(Cloud+Vue3)
41 3
|
22天前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
39 1
|
22天前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
44 1
|
25天前
|
前端开发 JavaScript
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
|
25天前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
7天前
|
缓存 JavaScript 前端开发
vue学习第四章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。本文介绍了Vue中计算属性的基本与复杂使用、setter/getter、与methods的对比及与侦听器的总结。如果你觉得有用,请关注我,将持续更新更多优质内容!🎉🎉🎉
vue学习第四章
|
7天前
|
JavaScript 前端开发
vue学习第九章(v-model)
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生,自学前端2年半,正向全栈进发。此篇介绍v-model在不同表单元素中的应用及修饰符的使用,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
vue学习第九章(v-model)
|
7天前
|
JavaScript 前端开发 开发者
vue学习第十章(组件开发)
欢迎来到瑞雨溪的博客,一名热爱JavaScript与Vue的大一学生。本文深入讲解Vue组件的基本使用、全局与局部组件、父子组件通信及数据传递等内容,适合前端开发者学习参考。持续更新中,期待您的关注!🎉🎉🎉
vue学习第十章(组件开发)
下一篇
无影云桌面