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
let NodeArray = []
let createMount = (opts) => {
// doubleDialog  是否需要二级弹窗
  if (NodeArray.length>0 && !opts.doubleDialog) {//确保只存在一个弹窗
    // 执行组件生命周期
    render(null, mountNode);
    // 移除dom
    document.body.removeChild(NodeArray.pop())
  }
  mountNode = document.createElement('div')
  NodeArray.push(mountNode)
  document.body.appendChild(mountNode)
  let vnode = h(iLawDialog, {
    options: {
      ...opts,
      remove() { //传入remove函数,组件内可移除dom 组件内通过props接收x
        // 执行组件生命周期
        render(null, mountNode);
        // 移除dom
        NodeArray.length > 0 && document.body.removeChild(NodeArray.pop());
        // 清空NodeArray
        NodeArray.length === 0 && (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"
    @opened="handleCallback('opened')"
    @close="handleCallback('close')"
    @closed="handleCallback('closed')"
    @open-auto-focus="handleCallback('openAutoFocus')"
    @close-auto-focus="handleCallback('closeAutoFocus')"
  >
    <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 reset-element">
        <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, watch, computed, getCurrentInstance } from "vue";
import { useRoute } from "vue-router";
const route = useRoute();
/**
 * @description: 获取 函数 实例
 * @return {*}
 */
//  注册 绑定 父组件 v-model change 事件
// 获取 父组件传值 v-model
const props = defineProps({
  options: {
    type: Object,
    default: () => {},
  },
});

const handleCallback = (val) => {
  props.options[val] && props.options[val]();
};

// 定义 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) => {
    if (!val) {
      props.options.remove();
    }
  }
);
watch(
  () => route.path,
  (val) => {
    props.options.remove();
  }
);
/**
 * 处理按钮回调
 * @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 {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  &.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.name"
        ><i :class="`icon iconName ${item.fontIcon}`" v-if="item.fontIcon"></i>
        {{ item.name }}</template
      >
      <template v-if="item.tip"
        ><i :class="`icon ${item.fontIcon}`" v-if="item.fontIcon"></i
      ></template>
    </component>
  </div>
</template>
<script setup lang="ts">
import { ElButton } from "element-plus";
import { ref, Ref, computed, getCurrentInstance } 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;
  }
}
:deep(.iconName::before) {
  font-size: 16px;
  margin-right: 5px;
  font-weight: 900;
}
</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();//弹框
    },
  });

你可能感兴趣的:(javascript,vue.js,前端)