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

View File

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

View File

@ -19,17 +19,32 @@ const defaultProps: DialogProps = {
visible: false, 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 dialog: DialogEntry = (userProps: DialogProps) => {
const props = { const props = {
...defaultProps, ...defaultProps,
...userProps, ...userProps,
}; };
let container = document.body.querySelector(".dialog-container"); const container = getOrCreateContainer();
if (!container) {
container = document.createElement("div"); if (
container.className = "dialog-container"; props.uniqueId &&
document.body.appendChild(container); container.querySelector(`[data-unique-id="${props.uniqueId}"]`)
) {
return;
} }
const { vnode, container: hostContainer } = createVNodeComponent( const { vnode, container: hostContainer } = createVNodeComponent(
@ -37,19 +52,21 @@ const dialog: DialogEntry = (userProps: DialogProps) => {
props props
); );
if (hostContainer.firstElementChild) { hostContainer.firstElementChild &&
container.appendChild(hostContainer.firstElementChild); container.appendChild(hostContainer.firstElementChild);
}
if (vnode.component?.props) { vnode.component?.props && (vnode.component.props.visible = true);
vnode.component.props.visible = true;
}
if (vnode?.props) { if (vnode?.props) {
// close emit
vnode.props.onClose = () => { 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); render(null, hostContainer);
}; };
} }

View File

@ -11,13 +11,7 @@ export interface DialogProps {
showCancel?: boolean; showCancel?: boolean;
confirmText?: string; confirmText?: string;
cancelText?: string; cancelText?: string;
uniqueId?: string;
onConfirm?: () => void; onConfirm?: () => void;
onCancel?: () => 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" class="modal-wrapper"
role="dialog" role="dialog"
tabindex="0" tabindex="0"
v-bind="$attrs"
@keyup.esc.stop="handleClose()" @keyup.esc.stop="handleClose()"
> >
<transition <transition