feat: add support for setting uniqueId in Dialog to prevent duplicate creation (#6737)

#### What type of PR is this?

/area ui
/kind feature
/milestone 2.20.x

#### What this PR does / why we need it:

Dialog API 支持传入 uniqueId,以限制同一时间仅打开一个。

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/6724

#### Does this PR introduce a user-facing change?

```release-note
Dialog API 支持传入 uniqueId,以限制同一时间仅打开一个。
```
pull/6736/head^2
Ryan Wang 2024-09-30 17:29:52 +08:00 committed by GitHub
parent 56804c9be1
commit c80ceb460d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 56 additions and 56 deletions

View File

@ -1,6 +1,6 @@
import type { Meta, StoryObj } from "@storybook/vue3";
import { VDialog } from ".";
import { Dialog, VDialog } from ".";
import { VButton } from "../button";
const meta: Meta<typeof VDialog> = {
@ -12,8 +12,11 @@ const meta: Meta<typeof VDialog> = {
height: 400,
setup() {
const showDialog = () => {
// @ts-ignore
args.visible = true;
Dialog.success({
title: "Hi",
// @ts-ignore
type: args.type,
});
};
return {
@ -24,7 +27,6 @@ const meta: Meta<typeof VDialog> = {
template: `
<div style="height: 400px">
<VButton @click="showDialog" >Dialog</VButton>
<VDialog v-bind="args" @onCancel="args.visible = false" />
</div>
`,
}),

View File

@ -1,6 +1,5 @@
<script lang="ts" setup>
import type { Type as ButtonType } from "@/components/button/interface";
import type { Type } from "@/components/dialog/interface";
import type { DialogProps, Type } from "@/components/dialog/interface";
import { markRaw, ref, type Component, type Raw } from "vue";
import {
IconCheckboxCircle,
@ -12,36 +11,22 @@ import {
import { VButton } from "../button";
import { VModal } from "../modal";
const props = withDefaults(
defineProps<{
type?: Type;
title?: string;
description?: string;
confirmText?: string;
confirmType?: ButtonType;
showCancel?: boolean;
cancelText?: string;
visible?: boolean;
onConfirm?: () => void;
onCancel?: () => void;
}>(),
{
type: "info",
title: "提示",
description: "",
confirmText: "确定",
confirmType: "primary",
showCancel: true,
cancelText: "取消",
visible: false,
onConfirm: () => {
return;
},
onCancel: () => {
return;
},
}
);
const props = withDefaults(defineProps<DialogProps>(), {
type: "info",
title: "提示",
description: "",
confirmText: "确定",
confirmType: "primary",
showCancel: true,
cancelText: "取消",
visible: false,
onConfirm: () => {
return;
},
onCancel: () => {
return;
},
});
const emit = defineEmits<{
(event: "update:visible", visible: boolean): void;
@ -102,6 +87,7 @@ const handleClose = () => {
:visible="visible"
:width="450"
:layer-closable="true"
:data-unique-id="uniqueId"
@close="handleCancel()"
>
<div class="flex justify-between items-start py-2 mb-2">

View File

@ -19,17 +19,32 @@ const defaultProps: DialogProps = {
visible: false,
};
const DIALOG_CONTAINER_CLASS = ".dialog-container";
const MODAL_WRAPPER_CLASS = ".modal-wrapper";
function getOrCreateContainer() {
let container = document.body.querySelector(DIALOG_CONTAINER_CLASS);
if (!container) {
container = document.createElement("div");
container.className = "dialog-container";
document.body.appendChild(container);
}
return container;
}
const dialog: DialogEntry = (userProps: DialogProps) => {
const props = {
...defaultProps,
...userProps,
};
let container = document.body.querySelector(".dialog-container");
if (!container) {
container = document.createElement("div");
container.className = "dialog-container";
document.body.appendChild(container);
const container = getOrCreateContainer();
if (
props.uniqueId &&
container.querySelector(`[data-unique-id="${props.uniqueId}"]`)
) {
return;
}
const { vnode, container: hostContainer } = createVNodeComponent(
@ -37,19 +52,21 @@ const dialog: DialogEntry = (userProps: DialogProps) => {
props
);
if (hostContainer.firstElementChild) {
hostContainer.firstElementChild &&
container.appendChild(hostContainer.firstElementChild);
}
if (vnode.component?.props) {
vnode.component.props.visible = true;
}
vnode.component?.props && (vnode.component.props.visible = true);
if (vnode?.props) {
// close emit
vnode.props.onClose = () => {
container?.remove();
const modals = container.querySelectorAll(MODAL_WRAPPER_CLASS);
if (modals.length > 1) {
hostContainer.firstElementChild?.remove();
} else {
container.remove();
}
render(null, hostContainer);
};
}

View File

@ -11,13 +11,7 @@ export interface DialogProps {
showCancel?: boolean;
confirmText?: string;
cancelText?: string;
uniqueId?: string;
onConfirm?: () => void;
onCancel?: () => void;
}
type useDialogOptions = Omit<DialogProps, "visible" | "title"> & {
visible: boolean;
title: string;
};
export type useDialogUserOptions = Omit<useDialogOptions, "type" | "visible">;

View File

@ -126,6 +126,7 @@ watch(
class="modal-wrapper"
role="dialog"
tabindex="0"
v-bind="$attrs"
@keyup.esc.stop="handleClose()"
>
<transition