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

View File

@ -14,7 +14,7 @@ export function setRefreshTokenCookie(
) { ) {
setCookie(event, 'jwt', refreshToken, { setCookie(event, 'jwt', refreshToken, {
httpOnly: true, httpOnly: true,
maxAge: 24 * 60 * 60 * 1000, maxAge: 24 * 60 * 60, // unit: seconds
sameSite: 'none', sameSite: 'none',
secure: true, secure: true,
}); });

View File

@ -137,11 +137,19 @@ const [Drawer, drawerApi] = useVbenDrawer({
### drawerApi ### drawerApi
| 方法 | 描述 | 类型 | | 方法 | 描述 | 类型 | 版本限制 |
| --- | --- | --- | | --- | --- | --- | --- |
| setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>drawerApi` | | setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial<ModalState>)\| Partial<ModalState>)=>drawerApi` |
| open | 打开弹窗 | `()=>void` | | open | 打开弹窗 | `()=>void` | --- |
| close | 关闭弹窗 | `()=>void` | | close | 关闭弹窗 | `()=>void` | --- |
| setData | 设置共享数据 | `<T>(data:T)=>drawerApi` | | setData | 设置共享数据 | `<T>(data:T)=>drawerApi` | --- |
| getData | 获取共享数据 | `<T>()=>T` | | getData | 获取共享数据 | `<T>()=>T` | --- |
| useStore | 获取可响应式状态 | - | | useStore | 获取可响应式状态 | - | --- |
| lock | 将抽屉标记为提交中,锁定当前状态 | `(isLock:boolean)=>drawerApi` | >5.5.3 |
| unlock | lock方法的反操作解除抽屉的锁定状态也是lock(false)的别名 | `()=>drawerApi` | >5.5.3 |
::: info lock
`lock`方法用于锁定抽屉的状态一般用于提交数据的过程中防止用户重复提交或者抽屉被意外关闭、表单数据被改变等等。当处于锁定状态时抽屉的确认按钮会变为loading状态同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭抽屉、开启抽屉的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的抽屉时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。
:::

View File

@ -155,9 +155,10 @@ const [Modal, modalApi] = useVbenModal({
| getData | 获取共享数据 | `<T>()=>T` | - | | getData | 获取共享数据 | `<T>()=>T` | - |
| useStore | 获取可响应式状态 | - | - | | useStore | 获取可响应式状态 | - | - |
| lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 | | lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 |
| unlock | lock方法的反操作解除弹窗的锁定状态也是lock(false)的别名 | `()=>modalApi` | >5.5.3 |
::: info lock ::: info lock
`lock`方法用于锁定当前弹窗的状态一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时弹窗的确认按钮会变为loading状态同时禁用确认按钮、隐藏关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。 `lock`方法用于锁定当前弹窗的状态一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时弹窗的确认按钮会变为loading状态同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。
::: :::

View File

@ -72,7 +72,7 @@ pnpm install
## 其他 ## 其他
如果你想更进一步精简,你可以删除参考下文件或者文件夹的作用,判断自己是否需要,不需要删除即可: 如果你想更进一步精简,你可以删除参考下文件或者文件夹的作用,判断自己是否需要,不需要删除即可:
- `.changeset` 文件夹用于管理版本变更 - `.changeset` 文件夹用于管理版本变更
- `.github` 文件夹用于存放 GitHub 的配置文件 - `.github` 文件夹用于存放 GitHub 的配置文件

View File

@ -52,6 +52,7 @@ export class DrawerApi {
placement: 'right', placement: 'right',
showCancelButton: true, showCancelButton: true,
showConfirmButton: true, showConfirmButton: true,
submitting: false,
title: '', title: '',
}; };
@ -92,7 +93,11 @@ export class DrawerApi {
// 如果 onBeforeClose 返回 false则不关闭弹窗 // 如果 onBeforeClose 返回 false则不关闭弹窗
const allowClose = this.api.onBeforeClose?.() ?? true; const allowClose = this.api.onBeforeClose?.() ?? true;
if (allowClose) { 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; 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; return this;
} }
/**
*
* @description lock方法设置的锁定状态lock(false)
*/
unlock() {
return this.lock(false);
}
} }

View File

@ -75,12 +75,12 @@ export interface DrawerProps {
* @default false * @default false
*/ */
loading?: boolean; loading?: boolean;
/** /**
* *
* @default true * @default true
*/ */
modal?: boolean; modal?: boolean;
/** /**
* *
*/ */
@ -89,12 +89,12 @@ export interface DrawerProps {
* *
*/ */
overlayBlur?: number; overlayBlur?: number;
/** /**
* *
* @default right * @default right
*/ */
placement?: DrawerPlacement; placement?: DrawerPlacement;
/** /**
* *
* @default true * @default true
@ -105,6 +105,10 @@ export interface DrawerProps {
* @default true * @default true
*/ */
showConfirmButton?: boolean; showConfirmButton?: boolean;
/**
*
*/
submitting?: boolean;
/** /**
* *
*/ */

View File

@ -36,6 +36,7 @@ const props = withDefaults(defineProps<Props>(), {
appendToMain: false, appendToMain: false,
closeIconPlacement: 'right', closeIconPlacement: 'right',
drawerApi: undefined, drawerApi: undefined,
submitting: false,
zIndex: 1000, zIndex: 1000,
}); });
@ -73,6 +74,7 @@ const {
placement, placement,
showCancelButton, showCancelButton,
showConfirmButton, showConfirmButton,
submitting,
title, title,
titleTooltip, titleTooltip,
zIndex, zIndex,
@ -91,12 +93,12 @@ watch(
); );
function interactOutside(e: Event) { function interactOutside(e: Event) {
if (!closeOnClickModal.value) { if (!closeOnClickModal.value || submitting.value) {
e.preventDefault(); e.preventDefault();
} }
} }
function escapeKeyDown(e: KeyboardEvent) { function escapeKeyDown(e: KeyboardEvent) {
if (!closeOnPressEscape.value) { if (!closeOnPressEscape.value || submitting.value) {
e.preventDefault(); e.preventDefault();
} }
} }
@ -104,7 +106,11 @@ function escapeKeyDown(e: KeyboardEvent) {
function pointerDownOutside(e: Event) { function pointerDownOutside(e: Event) {
const target = e.target as HTMLElement; const target = e.target as HTMLElement;
const dismissableDrawer = target?.dataset.dismissableDrawer; const dismissableDrawer = target?.dataset.dismissableDrawer;
if (!closeOnClickModal.value || dismissableDrawer !== id) { if (
submitting.value ||
!closeOnClickModal.value ||
dismissableDrawer !== id
) {
e.preventDefault(); e.preventDefault();
} }
} }
@ -169,6 +175,7 @@ const getAppendTo = computed(() => {
<SheetClose <SheetClose
v-if="closable && closeIconPlacement === 'left'" v-if="closable && closeIconPlacement === 'left'"
as-child 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" 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"> <slot name="close-icon">
@ -209,6 +216,7 @@ const getAppendTo = computed(() => {
<SheetClose <SheetClose
v-if="closable && closeIconPlacement === 'right'" v-if="closable && closeIconPlacement === 'right'"
as-child 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" 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"> <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> <slot></slot>
</div> </div>
@ -253,6 +265,7 @@ const getAppendTo = computed(() => {
:is="components.DefaultButton || VbenButton" :is="components.DefaultButton || VbenButton"
v-if="showCancelButton" v-if="showCancelButton"
variant="ghost" variant="ghost"
:disabled="submitting"
@click="() => drawerApi?.onCancel()" @click="() => drawerApi?.onCancel()"
> >
<slot name="cancelText"> <slot name="cancelText">
@ -263,7 +276,7 @@ const getAppendTo = computed(() => {
<component <component
:is="components.PrimaryButton || VbenButton" :is="components.PrimaryButton || VbenButton"
v-if="showConfirmButton" v-if="showConfirmButton"
:loading="confirmLoading" :loading="confirmLoading || submitting"
@click="() => drawerApi?.onConfirm()" @click="() => drawerApi?.onConfirm()"
> >
<slot name="confirmText"> <slot name="confirmText">

View File

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

View File

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

View File

@ -23,6 +23,7 @@ const props = withDefaults(
appendTo?: HTMLElement | string; appendTo?: HTMLElement | string;
class?: ClassType; class?: ClassType;
closeClass?: ClassType; closeClass?: ClassType;
closeDisabled?: boolean;
modal?: boolean; modal?: boolean;
open?: boolean; open?: boolean;
overlayBlur?: number; overlayBlur?: number;
@ -30,7 +31,7 @@ const props = withDefaults(
zIndex?: number; zIndex?: number;
} }
>(), >(),
{ appendTo: 'body', showClose: true }, { appendTo: 'body', closeDisabled: false, showClose: true },
); );
const emits = defineEmits< const emits = defineEmits<
DialogContentEmits & { close: []; closed: []; opened: [] } DialogContentEmits & { close: []; closed: []; opened: [] }
@ -108,6 +109,7 @@ defineExpose({
<DialogClose <DialogClose
v-if="showClose" v-if="showClose"
:disabled="closeDisabled"
:class=" :class="
cn( 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', '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',

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useVbenDrawer } from '@vben/common-ui'; import { useVbenDrawer } from '@vben/common-ui';
import { message } from 'ant-design-vue'; import { Button, message } from 'ant-design-vue';
const [Drawer, drawerApi] = useVbenDrawer({ const [Drawer, drawerApi] = useVbenDrawer({
onCancel() { onCancel() {
@ -15,12 +15,19 @@ const [Drawer, drawerApi] = useVbenDrawer({
// drawerApi.close(); // drawerApi.close();
}, },
}); });
function lockDrawer() {
drawerApi.lock();
setTimeout(() => {
drawerApi.unlock();
}, 3000);
}
</script> </script>
<template> <template>
<Drawer title="基础抽屉示例" title-tooltip="标题提示内容"> <Drawer title="基础抽屉示例" title-tooltip="标题提示内容">
<template #extra> extra </template> <template #extra> extra </template>
base demo base demo
<Button type="primary" @click="lockDrawer">锁定抽屉状态</Button>
<!-- <template #prepend-footer> slot </template> --> <!-- <template #prepend-footer> slot </template> -->
<!-- <template #append-footer> prepend slot </template> --> <!-- <template #append-footer> prepend slot </template> -->
</Drawer> </Drawer>

View File

@ -1,7 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { useVbenModal } from '@vben/common-ui'; import { useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue'; import { Button, message } from 'ant-design-vue';
const [Modal, modalApi] = useVbenModal({ const [Modal, modalApi] = useVbenModal({
onCancel() { onCancel() {
@ -18,9 +18,17 @@ const [Modal, modalApi] = useVbenModal({
message.info('onOpened打开动画结束'); message.info('onOpened打开动画结束');
}, },
}); });
function lockModal() {
modalApi.lock();
setTimeout(() => {
modalApi.unlock();
}, 3000);
}
</script> </script>
<template> <template>
<Modal class="w-[600px]" title="基础弹窗示例" title-tooltip="标题提示内容"> <Modal class="w-[600px]" title="基础弹窗示例" title-tooltip="标题提示内容">
base demo base demo
<Button type="primary" @click="lockModal">锁定弹窗</Button>
</Modal> </Modal>
</template> </template>