Vue3封装全局自定义弹框,结合element的el-dialog

main.ts中

//引入并注册
import {
    
     createApp } from "vue";
import SubDialog from '@/components/sub.ts'
let instance: any = null;
instance = createApp(App);
instance.use(SubDialog);

components下的sub.ts文件

import {
    
     h, render } from 'vue'
import iLawDialog from '#/components/iLawDialog.vue'
let mountNode
let app
// doubleDialog  是否需要二级弹窗
let createMount = (opts) => {
    
    
  if (mountNode && !opts.doubleDialog) {
    
    //确保只存在一个弹窗
    document.body.removeChild(mountNode)
    mountNode = null
  }
  mountNode = document.createElement('div')
  document.body.appendChild(mountNode)
  const vnode = h(iLawDialog, {
    
    
    options: {
    
    
      ...opts,
      remove() {
    
     //传入remove函数,组件内可移除dom 组件内通过props接收x
        document.body.removeChild(mountNode)
        mountNode = null
      }
    },
  })
  vnode.appContext = app
  render(vnode, mountNode);
}

const V3Popup = (options = {
     
      id: Number }) => {
    
    
  options.id = options.id || 'v3popup_' + 1
  createMount(options)
 
}

export default {
    
    
  install(Vue: any) {
    
    
    /**
     * @description:  将公共的工具类 注册在vue 实例上
     * @param {*}
     * @return {*}
    */
    app = Vue._context
    Vue.config.globalProperties.$subDialog = V3Popup;
  },
};

components下的iLawDialog.vue

<template>
  <el-dialog
    v-model="dialogVisible"
    :custom-class="`subDialog ${props.options.hideHeader}`"
    v-bind="props.options"
  >
    <template #header>
      <component
        :is="props.options?.headerDom"
        v-bind="props.options.contentComponent.attrs"
        @onClick="_handleToolbarClick($event)"
      ></component>
    </template>
    <template #default>
      <p
        v-html="props.options.contentText"
        v-if="props.options.contentText"
      ></p>
      <div v-if="props.options.contentComponent">
        <component
          :is="componentId"
          v-bind="props.options.contentComponent.attrs"
          @onClick="_handleToolbarClick($event)"
        ></component>
      </div>
    </template>

    <template #footer v-if="is_toolbar">
      <div class="dialog-footer">
        <iLawButtons
          :toolbar="props.options.toolbar"
          @onClick="_handleToolbarClick($event)"
        ></iLawButtons>
      </div>
    </template>
  </el-dialog>
</template>
<script lang="ts" setup>
import iLawButtons from "@/components/iLawButtons.vue";
import {
    
     ref, Ref, defineProps, watch, computed } from "vue";
/**
 * @description: 获取 函数 实例
 * @return {*}
 */
//  注册 绑定 父组件 v-model change 事件
// 获取 父组件传值 v-model
const props = defineProps({
    
    
  options: {
    
    
    type: Object,
    default: () => {
    
    },
  },
});

// 定义 dialog v-model 传值

let dialogVisible: Ref<boolean> = ref(true);
/**
 * @description: 计算属性判断 底部按钮是否默认还是没有
 * @return {*}
 */

const is_toolbar = computed(() => {
    
    
  return props.options?.toolbar ? true : false;
});
const componentId = computed(() => {
    
    
  return props.options.contentComponent.is;
});
// 监听 dialogVisible
watch(
  () => dialogVisible.value,
  (val) => {
    
    }
);
/**
 * 处理按钮回调
 * @param item
 * @param data
 * @param $event
 */
const _handleToolbarClick = (item?: any, $event?: any) => {
    
    
  if (item.click == "cancel") {
    
    
    props.options.remove();
    return;
  }
  // 将 移除dom 的当 callback 传出
  props.options[item.click](props.options.remove, item.options);
};
/**
 * @description: 将属性方法首字母大写
 * @param {*} str
 * @return {*}
 */
const titleCase = (str) => {
    
    
  let newStr = str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
  return newStr;
};
</script>
<style lang="scss">
.subDialog {
    
    
  &.el-dialog {
    
    
    z-index: 99;
    background-color: #fff;
  }

  .el-dialog__header {
    
    
    border-bottom: 1px solid rgb(0 0 0 / 6%);
    padding: 0 25px;
    width: 100%;
    height: 50px;
    text-align: left;
    line-height: 50px;
    box-sizing: border-box;
  }

  .el-dialog__title {
    
    
    font-size: 18px;
    font-family: "PingFangSC-Medium", "PingFang SC";
    font-weight: 500;
    color: rgb(0 0 0 / 85%);
  }

  .el-dialog__footer {
    
    
    display: flex;
    justify-content: center;
    align-items: center;
    margin-right: 20px;
    border-top: 0;
    padding: 0;
    height: 60px;
    line-height: 60px;

    .el-button {
    
    
      border-radius: 22px;
      padding: 0;
      width: 120px;
      height: 32px;
      line-height: 32px;
    }

    .color-info {
    
    
      border-color: #999;
      background: #999 !important;
    }
  }
}

.hideHeader .el-dialog__header {
    
    
  display: none;
}
</style>

components下的 iLawButtons.vue

<template>
  <div :class="`iLaw-buttons-${align}`">
    <component
      :is="componentName"
      v-for="(item, index) of props.toolbar"
      :key="index"
      v-bind="item"
      @click.stop="handleToolbarClick(item, $event)"
    >
      <template v-if="item.text">{
    
    {
    
     item.text }}</template>
      <template v-else>{
    
    {
    
     item.content }}</template>
    </component>
  </div>
</template>
<script setup lang="ts">
import {
    
     ElButton } from "element-plus";
import {
    
    
  defineProps,
  ref,
  Ref,
  computed,
  getCurrentInstance,
  defineEmits,
} from "vue";
const {
    
     proxy }: any = getCurrentInstance();
/**
 * @description: 获取 函数 实例
 * @return {*}
 */

const emits = defineEmits(["onClick"]);
// 按钮组
// @group Z.私有组件
const Types = {
    
    
  button: ElButton,
  text: "ge-textButton",
};

const props = defineProps({
    
    
  // 按钮对齐方式
  align: {
    
    
    type: String,
    default: (): string => "center",
    validator: (value: string) => ["left", "center", "right"].includes(value),
  },
  // 按钮数组
  toolbar: {
    
    
    // type: Array,
    default: (): any => [{
    
    }],
    required: true,
  },
  // 按钮类型
  type: {
    
    
    type: String,
    default: (): string => "button",
    // validator: (value: string) => Types[value],
  },
});

let timer: Ref<any> = ref(null);

const componentName = computed(() => {
    
    
  return Types[props.type] || Types.button;
});

const handleToolbarClick = (item, event) => {
    
    
  timer.value && clearTimeout(timer.value);
  const {
    
     click, ...rest } = item;
  timer.value = setTimeout(() => {
    
    
    if (typeof click === "function") {
    
    
      click(rest, event);
    } else {
    
    
      const func = proxy.$attrs[click] ? click : "onClick";
      emits(func, item);
    }
  }, 300);
};
</script>

<style lang="scss" scoped>
.iLaw-buttons {
    
    
  &-left {
    
    
    text-align: left;
  }

  &-center {
    
    
    text-align: center;
  }

  &-right {
    
    
    text-align: right;
  }
}
</style>

创建组件

<template>
  <el-button @click="cancel">取消</el-button>
  <el-button type="primary" @click="submit">确认</el-button>
</template>
<script setup lang="ts">
import {
    
     defineProps, ref, defineEmits } from "vue";
const props: any = defineProps({
    
    
  data: {
    
    
    type: Object,
    default: () => {
    
    },
  },
});

const emits = defineEmits(["onClick"]);
const form = ref('');
const cancel = () => {
    
    
  emits("onClick", {
    
    
    click: "cancel",
  });
};
const submit = () => {
    
    
  emits("onClick", {
    
    
    click: "confirm",
    options: form.value,
  });
};
</script>
<style scoped lang="scss">

</style>

如何使用

import {
    
     getCurrentInstance, markRaw } from 'vue'
const {
    
    proxy}:any = getCurrentInstance()

//执行
proxy.$subDialog({
    
    
    width: 383,
    hideHeader: "hideHeader",
    "show-close": false,
    contentComponent: {
    
    
      is: markRaw(DownloadContent),//创建的自定义的组件
      attrs: {
    
    
        data: '',//传值给自定义组件
        toolbar: [
          {
    
     text: "取消", click: "cancel" },
          {
    
     text: "确认", type: "primary", click: "confirm" },
        ],
      },
    },
    confirm: (remove, form) => {
    
    
    // 自定义组件传回来的值 form
      remove();//弹框
    },
  });

猜你喜欢

转载自blog.csdn.net/HelloWorldLJY/article/details/125874489