diff --git a/apps/backend-mock/utils/cookie-utils.ts b/apps/backend-mock/utils/cookie-utils.ts index 0d92f577..78f3aab7 100644 --- a/apps/backend-mock/utils/cookie-utils.ts +++ b/apps/backend-mock/utils/cookie-utils.ts @@ -14,7 +14,7 @@ export function setRefreshTokenCookie( ) { setCookie(event, 'jwt', refreshToken, { httpOnly: true, - maxAge: 24 * 60 * 60 * 1000, + maxAge: 24 * 60 * 60, // unit: seconds sameSite: 'none', secure: true, }); diff --git a/docs/src/components/common-ui/vben-drawer.md b/docs/src/components/common-ui/vben-drawer.md index 61b7c9e5..16accf0e 100644 --- a/docs/src/components/common-ui/vben-drawer.md +++ b/docs/src/components/common-ui/vben-drawer.md @@ -137,11 +137,19 @@ const [Drawer, drawerApi] = useVbenDrawer({ ### drawerApi -| 方法 | 描述 | 类型 | -| --- | --- | --- | +| 方法 | 描述 | 类型 | 版本限制 | +| --- | --- | --- | --- | | setState | 动态设置弹窗状态属性 | `(((prev: ModalState) => Partial)\| Partial)=>drawerApi` | -| open | 打开弹窗 | `()=>void` | -| close | 关闭弹窗 | `()=>void` | -| setData | 设置共享数据 | `(data:T)=>drawerApi` | -| getData | 获取共享数据 | `()=>T` | -| useStore | 获取可响应式状态 | - | +| open | 打开弹窗 | `()=>void` | --- | +| close | 关闭弹窗 | `()=>void` | --- | +| setData | 设置共享数据 | `(data:T)=>drawerApi` | --- | +| getData | 获取共享数据 | `()=>T` | --- | +| 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参数。 + +::: diff --git a/docs/src/components/common-ui/vben-modal.md b/docs/src/components/common-ui/vben-modal.md index e49196ee..39b88cf9 100644 --- a/docs/src/components/common-ui/vben-modal.md +++ b/docs/src/components/common-ui/vben-modal.md @@ -155,9 +155,10 @@ const [Modal, modalApi] = useVbenModal({ | getData | 获取共享数据 | `()=>T` | - | | useStore | 获取可响应式状态 | - | - | | lock | 将弹窗标记为提交中,锁定当前状态 | `(isLock:boolean)=>modalApi` | >5.5.2 | +| unlock | lock方法的反操作,解除弹窗的锁定状态,也是lock(false)的别名 | `()=>modalApi` | >5.5.3 | ::: info lock -`lock`方法用于锁定当前弹窗的状态,一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时,弹窗的确认按钮会变为loading状态,同时禁用确认按钮、隐藏关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。 +`lock`方法用于锁定当前弹窗的状态,一般用于提交数据的过程中防止用户重复提交或者弹窗被意外关闭、表单数据被改变等等。当处于锁定状态时,弹窗的确认按钮会变为loading状态,同时禁用取消按钮和关闭按钮、禁止ESC或者点击遮罩等方式关闭弹窗、开启弹窗的spinner动画以遮挡弹窗内容。调用`close`方法关闭处于锁定状态的弹窗时,会自动解锁。要主动解除这种状态,可以调用`unlock`方法或者再次调用lock方法并传入false参数。 ::: diff --git a/docs/src/guide/introduction/thin.md b/docs/src/guide/introduction/thin.md index 4db0e3b3..80884005 100644 --- a/docs/src/guide/introduction/thin.md +++ b/docs/src/guide/introduction/thin.md @@ -72,7 +72,7 @@ pnpm install ## 其他 -如果你想更进一步精简,你可以删除参考一下文件或者文件夹的作用,判断自己是否需要,不需要删除即可: +如果你想更进一步精简,你可以删除参考以下文件或者文件夹的作用,判断自己是否需要,不需要删除即可: - `.changeset` 文件夹用于管理版本变更 - `.github` 文件夹用于存放 GitHub 的配置文件 diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts index 99c6fa98..996730ea 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer-api.ts @@ -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); + } } diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts index c43677d3..b3ae0fb8 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.ts @@ -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; /** * 弹窗标题 */ diff --git a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue index 673a1d45..220d318f 100644 --- a/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue +++ b/packages/@core/ui-kit/popup-ui/src/drawer/drawer.vue @@ -36,6 +36,7 @@ const props = withDefaults(defineProps(), { 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(() => { @@ -209,6 +216,7 @@ const getAppendTo = computed(() => { @@ -233,7 +241,11 @@ const getAppendTo = computed(() => { }) " > - + @@ -253,6 +265,7 @@ const getAppendTo = computed(() => { :is="components.DefaultButton || VbenButton" v-if="showCancelButton" variant="ghost" + :disabled="submitting" @click="() => drawerApi?.onCancel()" > @@ -263,7 +276,7 @@ const getAppendTo = computed(() => { diff --git a/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts b/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts index 3bff3a0e..c690f250 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts +++ b/packages/@core/ui-kit/popup-ui/src/modal/modal-api.ts @@ -188,4 +188,12 @@ export class ModalApi { } return this; } + + /** + * 解除弹窗的锁定状态 + * @description 解除由lock方法设置的锁定状态,是lock(false)的别名 + */ + unlock() { + return this.lock(false); + } } diff --git a/packages/@core/ui-kit/popup-ui/src/modal/modal.vue b/packages/@core/ui-kit/popup-ui/src/modal/modal.vue index 35858f69..9328d671 100644 --- a/packages/@core/ui-kit/popup-ui/src/modal/modal.vue +++ b/packages/@core/ui-kit/popup-ui/src/modal/modal.vue @@ -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" diff --git a/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue b/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue index e7ef1b58..9f078987 100644 --- a/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue +++ b/packages/@core/ui-kit/shadcn-ui/src/ui/dialog/DialogContent.vue @@ -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({ import { useVbenDrawer } from '@vben/common-ui'; -import { message } from 'ant-design-vue'; +import { Button, message } from 'ant-design-vue'; const [Drawer, drawerApi] = useVbenDrawer({ onCancel() { @@ -15,12 +15,19 @@ const [Drawer, drawerApi] = useVbenDrawer({ // drawerApi.close(); }, }); + +function lockDrawer() { + drawerApi.lock(); + setTimeout(() => { + drawerApi.unlock(); + }, 3000); +}