Vue3信息提示(Modal)

简介: 这是一个基于 Vue2 的信息提示模态框组件,支持多种弹窗类型(如 info、success、error 等),并提供丰富的自定义属性,包括按钮文本、按钮类型、居中方式等。该组件可根据内容自动调整高度,并兼容不同按钮样式配置。预览效果展示了不同类型的模态框及其样式。代码中详细介绍了组件的实现方式和使用方法。

可自定义设置以下属性:

  • 提示框宽度(width),类型:number,单位 px,默认 420

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

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

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

  • 确认按钮类型(okType),类型:'default' | 'reverse' | 'primary' | 'danger' | 'dashed' | 'text' | 'link',默认 'primary'

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

  • 通知按钮文字(noticeText),类型:string,默认 '知道了'

  • 通知按钮 props 配置(noticeProps),参考 Button 组件 Props,类型:object,默认 {}

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

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

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

  • 提示框是否可见(v-model:show),类型:boolean,默认 false

弹窗类型一共有六种:'info' 'success' 'error' 'warning' 'confirm' 'erase',弹窗随内容自适应增加高度;同时支持对弹窗位置进行设置:①水平垂直居中②高度固定水平居中

效果如下图:在线预览(整体样式模仿ant-design-vue Modal,同时阴影覆盖浏览器窗口)

modal.value.info():

modal.value.success():

modal.value.error():

modal.value.warning():

modal.value.confirm():

modal.value.erase():

其中引入使用了以下组件:

①创建信息提示框组件Modal.vue:

<script setup lang="ts">
import { ref, nextTick } from 'vue'
import Button from '../button'
interface Desc {
  title: string // 标题
  content: string // 内容
}
interface Props {
  width?: number // 提示框宽度,单位 px
  cancelText?: string // 取消按钮文字
  cancelProps?: object // 取消按钮 props 配置,参考 Button 组件 Props
  okText?: string // 确定按钮文字
  okType?: 'default' | 'reverse' | 'primary' | 'danger' | 'dashed' | 'text' | 'link' // 确认按钮类型
  okProps?: object // 确认按钮 props 配置,优先级高于 okType,参考 Button 组件 Props
  noticeText?: string // 通知按钮文字
  noticeProps?: object // 通知按钮 props 配置,参考 Button 组件 Props
  center?: boolean // 水平垂直居中:true  固定高度水平居中:false
  top?: number // 固定高度水平居中时,距顶部高度,仅当 center: false 时生效,单位 px
  loading?: boolean // 确定按钮 loading
  show?: boolean // (v-model) 提示框是否可见
}
withDefaults(defineProps<Props>(), {
  width: 420,
  cancelText: '取消',
  cancelProps: () => ({}),
  okText: '确定',
  okType: 'primary',
  okProps: () => ({}),
  noticeText: '知道了',
  noticeProps: () => ({}),
  center: true,
  top: 100,
  loading: false,
  show: false
})
// 弹窗类型:'info' 'success' 'error' 'warning' 'confirm' 'erase'
const mode = ref('')
const desc = ref<Desc>()
const modalRef = ref() // DOM引用
const emits = defineEmits(['update:show', 'cancel', 'ok', 'know'])
function info(data: Desc) {
  mode.value = 'info'
  desc.value = data
  showModal()
}
function success(data: Desc) {
  mode.value = 'success'
  desc.value = data
  showModal()
}
function error(data: Desc) {
  mode.value = 'error'
  desc.value = data
  showModal()
}
function warning(data: Desc) {
  mode.value = 'warning'
  desc.value = data
  showModal()
}
function confirm(data: Desc) {
  mode.value = 'confirm'
  desc.value = data
  showModal()
}
function erase(data: Desc) {
  mode.value = 'erase'
  desc.value = data
  showModal()
}
function showModal() {
  emits('update:show', true)
  nextTick(() => {
    modalRef.value.focus()
  })
}
function onBlur() {
  emits('update:show', false)
  emits('cancel')
}
function onCancel() {
  emits('update:show', false)
  emits('cancel')
}
function onOK() {
  emits('ok')
}
function onKnow() {
  emits('update:show', false)
  emits('know')
}
defineExpose({
  info,
  success,
  error,
  warning,
  confirm,
  erase
})
</script>
<template>
  <div class="m-modal-root">
    <Transition name="fade">
      <div v-show="show" class="m-modal-mask"></div>
    </Transition>
    <Transition name="zoom">
      <div v-show="show" class="m-modal-wrap" @click.self="onBlur">
        <div
          ref="modalRef"
          tabindex="-1"
          :class="['m-modal', center ? 'relative-hv-center' : 'top-center']"
          :style="`width: ${width}px; top: ${!center ? top + 'px' : '50%'};`"
          @keydown.esc="onCancel"
        >
          <div class="m-modal-body-wrap">
            <div class="m-modal-body">
              <div class="modal-header">
                <template v-if="mode === 'confirm' || mode === 'erase'">
                  <svg
                    class="icon-svg icon-confirm"
                    focusable="false"
                    data-icon="exclamation-circle"
                    aria-hidden="true"
                    viewBox="64 64 896 896"
                  >
                    <path
                      d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
                    ></path>
                    <path
                      d="M464 688a48 48 0 1096 0 48 48 0 10-96 0zm24-112h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8z"
                    ></path>
                  </svg>
                </template>
                <svg
                  v-if="mode === 'info'"
                  class="icon-svg icon-info"
                  focusable="false"
                  data-icon="info-circle"
                  aria-hidden="true"
                  viewBox="64 64 896 896"
                >
                  <path
                    d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm32 664c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V456c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272zm-32-344a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
                  ></path>
                </svg>
                <svg
                  v-if="mode === 'success'"
                  class="icon-svg icon-success"
                  focusable="false"
                  data-icon="check-circle"
                  aria-hidden="true"
                  viewBox="64 64 896 896"
                >
                  <path
                    d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"
                  ></path>
                </svg>
                <svg
                  v-if="mode === 'error'"
                  class="icon-svg icon-error"
                  focusable="false"
                  data-icon="close-circle"
                  aria-hidden="true"
                  viewBox="64 64 896 896"
                >
                  <path
                    d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z"
                  ></path>
                </svg>
                <svg
                  v-if="mode === 'warning'"
                  class="icon-svg icon-warning"
                  focusable="false"
                  data-icon="exclamation-circle"
                  aria-hidden="true"
                  viewBox="64 64 896 896"
                >
                  <path
                    d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm-32 232c0-4.4 3.6-8 8-8h48c4.4 0 8 3.6 8 8v272c0 4.4-3.6 8-8 8h-48c-4.4 0-8-3.6-8-8V296zm32 440a48.01 48.01 0 010-96 48.01 48.01 0 010 96z"
                  ></path>
                </svg>
                <div class="modal-title">{
  
  { desc?.title }}</div>
              </div>
              <div class="modal-content">{
  
  { desc?.content }}</div>
            </div>
            <div class="modal-btns">
              <template v-if="mode === 'confirm' || mode === 'erase'">
                <Button class="mr8" @click="onCancel" v-bind="cancelProps">{
  
  { cancelText }}</Button>
                <Button :type="okType" :loading="loading" @click="onOK" v-bind="okProps">{
  
  { okText }}</Button>
              </template>
              <template v-if="['info', 'success', 'error', 'warning'].includes(mode)">
                <Button type="primary" :loading="loading" @click="onKnow" v-bind="noticeProps">{
  
  { noticeText }}</Button>
              </template>
            </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;
}
.relative-hv-center {
  // 水平垂直居中方法②:相对定位,随内容增大高度,并自适应水平垂直居中
  position: relative;
  top: 50%;
  transform: translateY(-50%);
}
.top-center {
  // 相对定位,固定高度,始终距离视图顶端100px
  position: relative;
}
.m-modal-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-modal-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-modal {
    width: 420px;
    margin: 0 auto;
    color: rgba(0, 0, 0, 0.88);
    font-size: 14px;
    line-height: 1.5714285714285714;
    outline: none;
    .m-modal-body-wrap {
      position: relative;
      word-break: break-all;
      padding: 20px 24px;
      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);
      .m-modal-body {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        .modal-header {
          width: 100%;
          .icon-svg {
            display: inline-block;
            margin-right: 12px;
            margin-top: 1px;
            width: 22px;
            height: 22px;
            vertical-align: top;
          }
          .icon-confirm {
            fill: #faad14;
          }
          .icon-info {
            fill: @themeColor;
          }
          .icon-success {
            fill: #52c41a;
          }
          .icon-error {
            fill: #ff4d4f;
          }
          .icon-warning {
            fill: #faad14;
          }
          .modal-title {
            display: inline-block;
            vertical-align: top;
            font-size: 16px;
            line-height: 1.5;
            font-weight: 600;
            max-width: calc(100% - 34px);
          }
        }
        .modal-content {
          flex-basis: 100%;
          margin-left: 34px;
          margin-top: 8px;
          font-size: 14px;
          max-width: calc(100% - 34px);
        }
      }
      .modal-btns {
        margin-top: 12px;
        text-align: right;
        .mr8 {
          margin-right: 8px;
        }
      }
    }
  }
}
</style>

②在要使用的页面引入:

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

const modal = ref()
const okType = ref('primary')
const center = ref(true)
const loading = ref(false)
const show = ref(false)

function showInfoModal() {
  modal.value.info({
    title: 'Do you See these items ?',
    content: 'Some descriptions ...'
  })
  center.value = true
}
function showSuccessModal() {
  modal.value.success({
    title: 'Do you See these items ?',
    content: 'Some descriptions ...'
  })
  center.value = true
}
function showErrorModal() {
  modal.value.error({
    title: 'Do you See these items ?',
    content: 'Some descriptions ...'
  })
  center.value = true
}
function showWarningModal() {
  modal.value.warning({
    title: 'Do you See these items ?',
    content: 'Some descriptions ...'
  })
  center.value = true
}
function showConfirmModal() {
  modal.value.confirm({
    title: 'Do you Want to submit these items ?',
    content: 'Some descriptions ...'
  })
  okType.value = 'primary'
  center.value = true
}
function showEraseModal() {
  modal.value.erase({
    title: 'Do you Want to delete these items ?',
    content: 'Some descriptions ...'
  })
  okType.value = 'danger'
  center.value = true
}
function showFixModal() {
  modal.value.info({
    title: 'Do you See these items ?',
    content: 'Some descriptions ...'
  })
  center.value = false
}
function onCancel() {
  // 点击遮罩层或取消按钮的回调
  console.log('cancel')
}
function onOk() {
  // “确定”按钮回调
  loading.value = true // 开启加载状态
  setTimeout(() => {
    show.value = false
    loading.value = false
  }, 2000)
}
function onKnow() {
  // “我知道了”按钮回调
  console.log('know')
}
</script>
<template>
  <div>
    <h1>{
  
  { $route.name }} {
  
  { $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Space vertical>
      <Button type="primary" @click="showInfoModal">Info Modal</Button>
      <Button type="primary" @click="showSuccessModal">Success Modal</Button>
      <Button type="primary" @click="showErrorModal">Error Modal</Button>
      <Button type="primary" @click="showWarningModal">Warning Modal</Button>
      <Button type="primary" @click="showConfirmModal">Confirm Modal</Button>
      <Button type="primary" @click="showEraseModal">Erase Modal</Button>
      <Button type="primary" @click="showFixModal">Height Fixed Modal</Button>
    </Space>
    <Modal
      ref="modal"
      v-model:show="show"
      :width="420"
      cancel-text="取消"
      ok-text="确认"
      :ok-type="okType"
      notice-text="知道了"
      :center="center"
      :top="120"
      :loading="loading"
      @cancel="onCancel"
      @ok="onOk"
      @know="onKnow"
    />
  </div>
</template>
相关文章
|
11天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
8天前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
23 7
|
9天前
|
前端开发 数据库
芋道框架审批流如何实现(Cloud+Vue3)
芋道框架审批流如何实现(Cloud+Vue3)
29 3
|
8天前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
28 1
|
8天前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
28 1
|
10天前
|
前端开发 JavaScript
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
|
11天前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
12天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
4天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发
|
4天前
|
存储 JavaScript
Vue 状态管理工具vuex
Vue 状态管理工具vuex