vue3 命令式弹窗的封装

1.使用方法

<script setup lang="ts">
import { ElButton } from 'element-plus';
import MyDialog from 'components/MyDialog.vue';

const myDialog = useDialog(MyDialog);

const handleOpenDialog = () => {
  // 打开弹窗 
  myDialog({
    // 传入一些myDialog 需要的 props
    title: "弹窗标题",
    onSubmit: () => {
        // 弹窗的回调处理,如果有的话
    },
    onClose: () =>{
        // close 回调 , 如果需要的话
    }
  })
  // myDialog.close()可以关闭弹窗
};
</script>

<template>
  <div>
    <ElButton @click="handleOpenDialog"> 打开弹窗 </ElButton>
    <div>其他内容。。。 </div>
  </div>
  // 不需要将MyDialog声明到template中
</template>

2.组件

<script setup lang="ts" name="MyDialog">
const props = defineProps<{
  visible: boolean;
  title?: string;
  onConfirm: (imgSrc: string, imgId: number) => void;
}>()
const emits = defineEmits<{
  close: [],
}>()
const dialogVisible = ref(false)

// 取消选择
const cancel = () => {
  dialogVisible.value = false
}

// 确认选择
const confirm = () => {
  // 其他逻辑
  props?.onConfirm()
  cancel()
}

const dialogVisible = computed<boolean>({
  get() {
    return props.visible;
  },
  set(visible) {
    emits('update:visible', visible);
    if (!visible) {
      emits('close');
    }
  },
});

</script>

<template>
  <el-dialog
    v-model="dialogVisible"
    :title="props.title"
    width="800"
    :before-close="cancel"
  >
    <div>
      弹窗内容
    </div>

    <template #footer>
      <el-button @click="cancel">
        取消
      </el-button>
      <el-button type="primary" @click="confirm">
        确定
      </el-button>
    </template>
  </el-dialog>
</template>

3.源码

import { AppContext, Component, ComponentPublicInstance, createVNode, getCurrentInstance, render, VNode } from 'vue';

export interface Options {
  visible?: boolean;
  onClose?: () => void;
  appendTo?: HTMLElement | string;
  [key: string]: unknown;
}

export interface DialogComponent {
  (options: Options): VNode;
  close: () => void;
}

const getAppendToElement = (props: Options): HTMLElement => {
  let appendTo: HTMLElement | null = document.body;
  if (props.appendTo) {
    if (typeof props.appendTo === 'string') {
      appendTo = document.querySelector<HTMLElement>(props.appendTo);
    }
    if (props.appendTo instanceof HTMLElement) {
      appendTo = props.appendTo;
    }
    if (!(appendTo instanceof HTMLElement)) {
      appendTo = document.body;
    }
  }
  return appendTo;
};

const initInstance = <T extends Component>(
  Component: T,
  props: Options,
  container: HTMLElement,
  appContext: AppContext | null = null
) => {
  const vNode = createVNode(Component, props);
  vNode.appContext = appContext;
  render(vNode, container);

  getAppendToElement(props).appendChild(container);
  return vNode;
};

export const useDialog = <T extends Component>(Component: T): DialogComponent => {
  const appContext = getCurrentInstance()?.appContext;
  if (appContext) {
    const currentProvides = (getCurrentInstance() as any)?.provides;
    Reflect.set(appContext, 'provides', { ...appContext.provides, ...currentProvides });
  }

  const container = document.createElement('div');

  const close = () => {
    render(null, container);
    container.parentNode?.removeChild(container);
  };

  const DialogComponent = (options: Options): VNode => {
    if (!Reflect.has(options, 'visible')) {
      options.visible = true;
    }
    if (typeof options.onClose !== 'function') {
      options.onClose = close;
    } else {
      const originOnClose = options.onClose;
      options.onClose = () => {
        originOnClose();
        close();
      };
    }
    const vNode = initInstance<T>(Component, options, container, appContext);
    const vm = vNode.component?.proxy as ComponentPublicInstance<Options>;
    for (const prop in options) {
      if (Reflect.has(options, prop) && !Reflect.has(vm.$props, prop)) {
        vm[prop as keyof ComponentPublicInstance] = options[prop];
      }
    }
    return vNode;
  };

  DialogComponent.close = close;

  return DialogComponent;
};

export default useDialog;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值