FaModal 组件增加 beforeClose 回调函数,优化关闭逻辑并支持异步处理

This commit is contained in:
Hooray
2025-06-10 14:39:35 +08:00
parent 0a940763ef
commit 801d346bc7
3 changed files with 122 additions and 25 deletions
+8 -10
View File
@@ -22,6 +22,10 @@ export interface ModalProps {
cancelButtonText?: string
confirmButtonDisabled?: boolean
confirmButtonLoading?: boolean
beforeClose?: (
action: 'confirm' | 'cancel' | 'close',
done: () => void
) => void
header?: boolean
footer?: boolean
closeOnClickOverlay?: boolean
@@ -30,7 +34,6 @@ export interface ModalProps {
headerClass?: HTMLAttributes['class']
contentClass?: HTMLAttributes['class']
footerClass?: HTMLAttributes['class']
modalId?: string
}
export interface ModalEmits {
'update:modelValue': [value: boolean]
@@ -44,13 +47,13 @@ export interface ModalEmits {
type alertOptions = Pick<ModalProps, 'title' | 'description' | 'icon' | 'alignCenter' | 'overlay' | 'overlayBlur' | 'confirmButtonText' | 'confirmButtonDisabled' | 'confirmButtonLoading' | 'closeOnClickOverlay' | 'closeOnPressEscape' | 'class' | 'headerClass' | 'contentClass' | 'footerClass'> & {
content: string
onConfirm?: () => any
onConfirm?: () => void
}
type confirmOptions = Pick<ModalProps, 'title' | 'description' | 'alignCenter' | 'overlay' | 'overlayBlur' | 'confirmButtonText' | 'cancelButtonText' | 'confirmButtonDisabled' | 'confirmButtonLoading' | 'closeOnClickOverlay' | 'closeOnPressEscape' | 'class' | 'headerClass' | 'contentClass' | 'footerClass'> & {
type confirmOptions = Pick<ModalProps, 'title' | 'description' | 'alignCenter' | 'overlay' | 'overlayBlur' | 'confirmButtonText' | 'cancelButtonText' | 'confirmButtonDisabled' | 'confirmButtonLoading' | 'beforeClose' | 'closeOnClickOverlay' | 'closeOnPressEscape' | 'class' | 'headerClass' | 'contentClass' | 'footerClass'> & {
content: string
onConfirm?: () => any
onCancel?: () => any
onConfirm?: () => void
onCancel?: () => void
}
export function useFaModal() {
@@ -67,7 +70,6 @@ export function useFaModal() {
closeOnClickOverlay: false,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
modalId: useId(),
onClosed: () => {
app.unmount()
},
@@ -89,7 +91,6 @@ export function useFaModal() {
closeOnClickOverlay: false,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
modalId: useId(),
onClosed: () => {
app.unmount()
},
@@ -111,7 +112,6 @@ export function useFaModal() {
closeOnClickOverlay: false,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
modalId: useId(),
onClosed: () => {
app.unmount()
},
@@ -133,7 +133,6 @@ export function useFaModal() {
closeOnClickOverlay: false,
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
modalId: useId(),
onClosed: () => {
app.unmount()
},
@@ -155,7 +154,6 @@ export function useFaModal() {
contentClass: 'py-0 min-h-auto',
footerClass: 'p-4',
showCancelButton: true,
modalId: useId(),
onClosed: () => {
app.unmount()
},
+56 -14
View File
@@ -83,6 +83,7 @@ function setTransform() {
}
watch(isOpen, (val) => {
emits('update:modelValue', val)
if (val) {
nextTick(() => {
if (dialogContentRef.value) {
@@ -90,28 +91,69 @@ watch(isOpen, (val) => {
setTransform()
}
})
}
})
function updateOpen(value: boolean) {
isOpen.value = value
emits('update:modelValue', value)
if (value) {
emits('open')
}
else {
emits('close')
}
})
async function updateOpen(value: boolean) {
if (value) {
isOpen.value = value
emits('open')
}
else {
if (props.beforeClose) {
await props.beforeClose(
'close',
() => {
isOpen.value = value
emits('close')
},
)
}
else {
isOpen.value = value
emits('close')
}
}
}
function onConfirm() {
updateOpen(false)
emits('confirm')
const isConfirmButtonLoading = ref(false)
async function onConfirm() {
if (props.beforeClose) {
isConfirmButtonLoading.value = true
await props.beforeClose(
'confirm',
() => {
isOpen.value = false
emits('confirm')
},
)
isConfirmButtonLoading.value = false
}
else {
isOpen.value = false
emits('confirm')
}
}
function onCancel() {
updateOpen(false)
emits('cancel')
async function onCancel() {
if (props.beforeClose) {
await props.beforeClose(
'cancel',
() => {
isOpen.value = false
emits('cancel')
},
)
}
else {
isOpen.value = false
emits('cancel')
}
}
function handleFocusOutside(e: Event) {
@@ -223,7 +265,7 @@ function handleAnimationEnd() {
<FaButton v-if="showCancelButton" variant="outline" @click="onCancel">
{{ cancelButtonText }}
</FaButton>
<FaButton v-if="showConfirmButton" :disabled="confirmButtonDisabled" :loading="confirmButtonLoading" @click="onConfirm">
<FaButton v-if="showConfirmButton" :disabled="confirmButtonDisabled" :loading="confirmButtonLoading || isConfirmButtonLoading" @click="onConfirm">
{{ confirmButtonText }}
</FaButton>
</slot>
+58 -1
View File
@@ -4,6 +4,7 @@ meta:
</route>
<script setup lang="ts">
import { toast } from 'vue-sonner'
import { useFaModal } from '@/ui/components/FaModal'
const modal = ref(false)
@@ -25,34 +26,87 @@ watch(() => modalInfo.value.loading, (loading) => {
}
})
function handleBeforeClose(action: 'confirm' | 'cancel' | 'close', done: () => void) {
if (action === 'close') {
useFaModal().confirm({
title: '提示',
content: '确定要关闭吗?',
onConfirm: () => {
done()
},
})
}
else {
done()
}
}
function showModalInfo() {
useFaModal().info({
title: '温馨提醒',
content: '这是 info 弹窗',
onConfirm: () => {
toast.info('你点了确定')
},
})
}
function showModalSuccess() {
useFaModal().success({
title: '温馨提醒',
content: '这是 success 弹窗',
onConfirm: () => {
toast.success('你点了确定')
},
})
}
function showModalWarning() {
useFaModal().warning({
title: '温馨提醒',
content: '这是 warning 弹窗',
onConfirm: () => {
toast.warning('你点了确定')
},
})
}
function showModalError() {
useFaModal().error({
title: '温馨提醒',
content: '这是 confirm 弹窗',
onConfirm: () => {
toast.error('你点了确定')
},
})
}
function showModalConfirm() {
useFaModal().confirm({
title: '温馨提醒',
content: '这是 confirm 弹窗',
onConfirm: () => {
toast.success('你点了确定')
},
})
}
function showModalPromiseConfirm() {
useFaModal().confirm({
title: '温馨提醒',
content: '这是 confirm 弹窗',
confirmButtonText: '确认(随机成功或失败)',
beforeClose: async (action, done) => {
if (action === 'confirm') {
await new Promise(resolve => setTimeout(resolve, 1000))
if (Math.random() > 0.5) {
toast.success('成功了!')
done()
}
else {
toast.error('失败了!')
}
}
else {
done()
}
},
})
}
</script>
@@ -80,8 +134,11 @@ function showModalConfirm() {
<FaButton @click="showModalConfirm">
Confirm
</FaButton>
<FaButton @click="showModalPromiseConfirm">
Confirm with promise
</FaButton>
</div>
<FaModal v-model="modal" title="标题" description="这里是一段描述介绍" :maximizable="modalInfo.maximizable" :closable="modalInfo.closable" :draggable="modalInfo.draggable" :center="modalInfo.center" :loading="modalInfo.loading" :header="modalInfo.header" :footer="modalInfo.footer">
<FaModal v-model="modal" title="标题" description="这里是一段描述介绍" :maximizable="modalInfo.maximizable" :closable="modalInfo.closable" :draggable="modalInfo.draggable" :center="modalInfo.center" :loading="modalInfo.loading" :header="modalInfo.header" :footer="modalInfo.footer" :before-close="handleBeforeClose">
<div :class="modalInfo.contentHeight">
<div class="flex-start-center flex-wrap gap-2">
<FaButton :variant="modalInfo.maximizable ? 'default' : 'outline'" class="w-full" @click="modalInfo.maximizable = !modalInfo.maximizable">