This commit is contained in:
dap
2025-03-05 10:10:09 +08:00
12 changed files with 97 additions and 23 deletions

View File

@@ -52,6 +52,7 @@ export class DrawerApi {
placement: 'right',
showCancelButton: true,
showConfirmButton: true,
submitting: false,
title: '',
};
@@ -92,7 +93,11 @@ export class DrawerApi {
// 如果 onBeforeClose 返回 false则不关闭弹窗
const allowClose = this.api.onBeforeClose?.() ?? true;
if (allowClose) {
this.store.setState((prev) => ({ ...prev, isOpen: false }));
this.store.setState((prev) => ({
...prev,
isOpen: false,
submitting: false,
}));
}
}
@@ -108,6 +113,15 @@ export class DrawerApi {
return (this.sharedData?.payload ?? {}) as T;
}
/**
* 锁定抽屉状态(用于提交过程中的等待状态)
* @description 锁定状态将禁用默认的取消按钮使用spinner覆盖抽屉内容隐藏关闭按钮阻止手动关闭弹窗将默认的提交按钮标记为loading状态
* @param isLocked 是否锁定
*/
lock(isLocked: boolean = true) {
return this.setState({ submitting: isLocked });
}
/**
* 取消操作
*/
@@ -165,4 +179,12 @@ export class DrawerApi {
}
return this;
}
/**
* 解除抽屉的锁定状态
* @description 解除由lock方法设置的锁定状态是lock(false)的别名
*/
unlock() {
return this.lock(false);
}
}

View File

@@ -75,12 +75,12 @@ export interface DrawerProps {
* @default false
*/
loading?: boolean;
/**
* 是否显示遮罩
* @default true
*/
modal?: boolean;
/**
* 是否自动聚焦
*/
@@ -89,12 +89,12 @@ export interface DrawerProps {
* 弹窗遮罩模糊效果
*/
overlayBlur?: number;
/**
* 抽屉位置
* @default right
*/
placement?: DrawerPlacement;
/**
* 是否显示取消按钮
* @default true
@@ -105,6 +105,10 @@ export interface DrawerProps {
* @default true
*/
showConfirmButton?: boolean;
/**
* 提交中(锁定抽屉状态)
*/
submitting?: boolean;
/**
* 弹窗标题
*/

View File

@@ -36,6 +36,7 @@ const props = withDefaults(defineProps<Props>(), {
appendToMain: false,
closeIconPlacement: 'right',
drawerApi: undefined,
submitting: false,
zIndex: 1000,
});
@@ -73,6 +74,7 @@ const {
placement,
showCancelButton,
showConfirmButton,
submitting,
title,
titleTooltip,
zIndex,
@@ -91,12 +93,12 @@ watch(
);
function interactOutside(e: Event) {
if (!closeOnClickModal.value) {
if (!closeOnClickModal.value || submitting.value) {
e.preventDefault();
}
}
function escapeKeyDown(e: KeyboardEvent) {
if (!closeOnPressEscape.value) {
if (!closeOnPressEscape.value || submitting.value) {
e.preventDefault();
}
}
@@ -104,7 +106,11 @@ function escapeKeyDown(e: KeyboardEvent) {
function pointerDownOutside(e: Event) {
const target = e.target as HTMLElement;
const dismissableDrawer = target?.dataset.dismissableDrawer;
if (!closeOnClickModal.value || dismissableDrawer !== id) {
if (
submitting.value ||
!closeOnClickModal.value ||
dismissableDrawer !== id
) {
e.preventDefault();
}
}
@@ -169,6 +175,7 @@ const getAppendTo = computed(() => {
<SheetClose
v-if="closable && closeIconPlacement === 'left'"
as-child
:disabled="submitting"
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
>
<slot name="close-icon">
@@ -209,6 +216,7 @@ const getAppendTo = computed(() => {
<SheetClose
v-if="closable && closeIconPlacement === 'right'"
as-child
:disabled="submitting"
class="data-[state=open]:bg-secondary ml-[2px] cursor-pointer rounded-full opacity-80 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none"
>
<slot name="close-icon">
@@ -233,7 +241,11 @@ const getAppendTo = computed(() => {
})
"
>
<VbenLoading v-if="showLoading" class="size-full" spinning />
<VbenLoading
v-if="showLoading || submitting"
class="size-full"
spinning
/>
<slot></slot>
</div>
@@ -253,6 +265,7 @@ const getAppendTo = computed(() => {
:is="components.DefaultButton || VbenButton"
v-if="showCancelButton"
variant="ghost"
:disabled="submitting"
@click="() => drawerApi?.onCancel()"
>
<slot name="cancelText">
@@ -263,7 +276,7 @@ const getAppendTo = computed(() => {
<component
:is="components.PrimaryButton || VbenButton"
v-if="showConfirmButton"
:loading="confirmLoading"
:loading="confirmLoading || submitting"
@click="() => drawerApi?.onConfirm()"
>
<slot name="confirmText">

View File

@@ -188,4 +188,12 @@ export class ModalApi {
}
return this;
}
/**
* 解除弹窗的锁定状态
* @description 解除由lock方法设置的锁定状态是lock(false)的别名
*/
unlock() {
return this.lock(false);
}
}

View File

@@ -200,12 +200,13 @@ const getAppendTo = computed(() => {
"
:modal="modal"
:open="state?.isOpen"
:show-close="submitting ? false : closable"
:show-close="closable"
:z-index="zIndex"
:overlay-blur="overlayBlur"
close-class="top-3"
@close-auto-focus="handleFocusOutside"
@closed="() => modalApi?.onClosed()"
:close-disabled="submitting"
@escape-key-down="escapeKeyDown"
@focus-outside="handleFocusOutside"
@interact-outside="interactOutside"

View File

@@ -23,6 +23,7 @@ const props = withDefaults(
appendTo?: HTMLElement | string;
class?: ClassType;
closeClass?: ClassType;
closeDisabled?: boolean;
modal?: boolean;
open?: boolean;
overlayBlur?: number;
@@ -30,7 +31,7 @@ const props = withDefaults(
zIndex?: number;
}
>(),
{ appendTo: 'body', showClose: true },
{ appendTo: 'body', closeDisabled: false, showClose: true },
);
const emits = defineEmits<
DialogContentEmits & { close: []; closed: []; opened: [] }
@@ -108,6 +109,7 @@ defineExpose({
<DialogClose
v-if="showClose"
:disabled="closeDisabled"
:class="
cn(
'data-[state=open]:bg-accent data-[state=open]:text-muted-foreground hover:bg-accent hover:text-accent-foreground text-foreground/80 flex-center absolute right-3 top-3 h-6 w-6 rounded-full px-1 text-lg opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none',