vue3+typescript 实现全局h5弹窗AlertDialog组件

废话少说先上图

基本使用


image.png

可以使用输入框 editable设置为true即可


image.png

直接拷贝AlertDialog.ts和alert-dialog.vue,稍微更改一下导入路径及修改css少量样式就可以使用了
使用方式也非常简单 ,一看就会

坐标不传的话 默认居中 ,传了坐标 组件有算法 设置弹窗跟随鼠标位置,不需要自己计算弹窗位置

AlertDialog.show({
      title: '提示',
      content: '确定要作废该条订单吗?',
      left: event.x, 
      top: event.y,
      editable: false,
      canceledOnTouchOutside: true,
      result: async (res: ResultParam) => {
        if (res.confirm) {
          console.log('web', '用户确定')
        } else if (res.cancle) {
          console.log('web', '用户取消')
        }
      }
    })
 // 如果不喜欢回调的方式 可以使用await ,即showByAwait方法
 // await用法
  let res = await AlertDialog.showByAwait({
      title: '提示',
      content: '确定要作废该条订单吗?',
  })
   if (res.confirm) {
   } else if (res.cancle) {
       console.log('web', '用户取消')
  }

vue3中已经不再支持用vue.extend来创建组件,那全局弹窗的代替方案是啥呢?这里推荐createApp来创建
下面是完整代码 唯一不同就是你需要更换一下路径,下面看关键类(AlertDialog.ts) createApp方法主要在show里面调用

import {createApp} from 'vue'
import AlertDialogCom from '@assets/components/dialog/alert-dialog.vue'

// prettier-ignore
interface DialogParam {
    title?: string
    content?: string
    left?: number  // 弹窗距离左侧的距离 不传则默认居中
    top?: number   // 弹窗距离顶侧的距离 不传则默认居中
    cancelText?: string // 传入空字符串则不会显示取消按钮
    sureText?: string   // 传入空字符串则不会显示确认按钮
    editable?: boolean  // 是否显示输入框
    canceledOnTouchOutside?: boolean // 点击空白处关闭弹窗
    result?: Function   // 回调用户操作结果
}

// prettier-ignore
export interface ResultParam {
    content: string  // editable 为 true 时,用户输入的文本
    confirm: boolean // 为 true 时,表示用户点击了确定按钮
    cancle: boolean  // 为 true 时,表示用户点击了取消
}

export default class AlertDialog{
  private static mountNode: HTMLElement | null = null

  public static show(param: DialogParam) {
    let app = createApp(AlertDialogCom, {...param})
    this.mountNode = document.createElement('div')
    app.mount(this.mountNode)
    document.body.appendChild(this.mountNode)
  }

  // await用法
  // let res = await DialogAialog.showByAwait({
  //     title: '提示',
  //     content: '确定要作废该条订单吗?',
  // })
  // if (res.confirm) {
  //
  // } else if (res.cancle) {
  //     console.log('web', '用户取消')
  // }
  public static showByAwait(param: DialogParam): Promise<ResultParam> {
    return new Promise((resolve, reject) => {
      param.result = (res: ResultParam) => resolve(res)
      this.show(param)
    })
  }

  public static close() {
    if (!this.mountNode) return
    document.body.removeChild(this.mountNode)
    this.mountNode = null
    // console.log('close', 'DialogAialog已销毁')
  }
}

弹窗组件(alert-dialog.vue)
1.关闭按钮是使用阿里巴巴图标库 可以自己使用图片代替
2.css样式很多都是引用var引用项目配置,可自行修改

<template>
  <div class="dialog w-h-100 flex-row flex-center" @click="touchOutside">
    <div ref="refContent" class="dialog-content flex-column pd-10-15 relative" :style="style">
      <div class="dialog-title flex-row flex-between">
        <span>{{ title }}</span>
        <i class="dialog-icon-close t1font t1-close pointer" @click="close"></i>
      </div>
      <div class="content text-secondary">{{ content }}</div>
      <div v-if="editable" class="t-input">
        <input ref="tInput" v-model="inputContent" type="text" placeholder="" class="t-input__inner" />
      </div>
      <div class="footer w-100 flex-row flex-end">
        <button v-if="sureText.length > 0" @click="result(true)" class="btn-confirm">{{ sureText }}</button>
        <button v-if="cancelText.length > 0" @click="result(false)" class="btn-cancel">{{ cancelText }}</button>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
  /**
   * @用途
   * @author Pino
   * @创建时间 2023-01-11 15:19
   **/
  import DialogAialog from '@assets/components/dialog/AlertDialog'
  import {computed, onMounted, ref, StyleValue} from 'vue'

  const props = defineProps({
    title: {type: String, default: '提示'},
    content: {type: String, default: '确定要删除该数据吗?'},
    left: {type: Number, default: 0},
    top: {type: Number, default: 0},
    cancelText: {type: String, default: '取消'},
    sureText: {type: String, default: '确定'},
    editable: {type: Boolean, default: false},
    canceledOnTouchOutside: {type: Boolean, default: true},
    result: {type: Function}
  })

  let refContent = ref<HTMLElement | null>(null)
  let style = computed(() => {
    let style: Partial<StyleValue> = {}
    if (props.left || props.top) {
      style.position = 'absolute'
      let cw = document.documentElement.clientWidth
      let ch = document.documentElement.clientHeight
      // 弹窗的宽高
      let contentH = refContent.value?.offsetHeight || 0
      let contentW = refContent.value?.offsetWidth || 0
      let offset = 30 // 调整间隔
      if (props.left < (cw * 1) / 3) {
        // 鼠标点击在屏幕左侧,弹窗要显示在右侧
        style.left = props.left + offset + 'px'
        let top = props.top
        if (ch - top < contentH) top = ch - contentH // 如果鼠标距离最底部 的距离小于弹窗高度
        style.top = top + 'px'
      } else if (props.left > (cw * 1) / 3 && props.left < (cw * 2) / 3) {
        if (props.top < ch / 2) {
          // 鼠标在中间上方点击 则弹窗在正下方显示
          style.top = props.top + offset + 'px'
        } else {
          // 鼠标在中间下方点击 则弹窗在正上方显示
          style.top = props.top - contentH - offset + 'px'
        }
        style.left = props.left - contentW / 2 + 'px'
      } else {
        // 点击屏幕右侧
        style.left = props.left - contentW - offset + 'px'
        let top = props.top
        if (ch - top < contentH) top = ch - contentH // 如果鼠标距离最底部 的距离小于弹窗高度
        style.top = top + 'px'
      }
    }
    return style
  })

  let inputContent = ref('')
  let tInput = ref<HTMLElement | null>(null)
  onMounted(() => {
    if (props.editable) setTimeout(() => tInput.value?.focus(), 100)
  })

  let touchOutside = (e: any) => {
    if (!props.canceledOnTouchOutside) return
    if (e.target?.classList?.length && e.target.classList[0] == 'dialog') close()
  }
  const close = () => DialogAialog.close()

  let result = (isSure: boolean) => {
    if (props.result) props.result({content: inputContent.value, confirm: isSure == true, cancle: isSure == false})
    close()
  }
</script>

<style scoped>
  @import '@assets/ff-ui/icon-font/t1-icon.css';

  .dialog {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    z-index: 1024;
    background-color: rgba(0, 0, 0, 0.5);
  }

  .dialog-content {
    border-radius: 5px;
    box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
    width: 400px;
    height: max-content;
    background-color: var(--bg-2);
    box-sizing: border-box;
  }

  .dialog-title {
    color: white;
    font-size: 16px;
  }

  .content {
    font-size: 14px;
  }

  .btn-confirm,
  .btn-cancel {
    width: 60px;
    height: 30px;
    border: none;
    outline: none;
    color: #fff;
    background-color: #1664ff;
    margin-left: 10px;
    border-radius: 3px;
    cursor: pointer;
  }

  .btn-cancel {
    color: #409eff;
    border: 1px solid #1664ff;
    background-color: #fff;
  }

  .btn-cancel:hover {
    background: #ecf5ff;
    color: var(--txt-secondary);
  }

  .btn-confirm:hover {
    opacity: 0.8;
  }

  .dialog-content > div {
    margin-top: 10px;
  }

  .t-input {
    font-size: 14px;
    display: inline-block;
    width: 100%;
  }

  .t-input__inner {
    -webkit-appearance: none;
    background-color: #fff;
    background-image: none;
    border-radius: 4px;
    border: 1px solid #dcdfe6;
    box-sizing: border-box;
    color: #606266;
    display: inline-block;
    font-size: inherit;
    height: 40px;
    line-height: 40px;
    outline: none;
    padding: 0 15px;
    transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
    width: 100%;
  }

  .dialog-icon-close {
    color: #909399;
    line-height: 15px;
    font-size: 15px;
  }

  .dialog-icon-close:hover {
    color: var(--color-link);
  }
</style>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容