You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

InertiaJS表单onSuccess关闭Bootstrap模态框后背景残留问题解决

问题:Laravel+Breeze+Inertia+Vue登录后Bootstrap模态框遮罩残留

我正在开发Laravel项目,基于laravel/breeze + VueJS + Inertia搭建。登录功能采用Bootstrap模态框实现,验证和认证流程均正常,但存在一个问题:认证成功后模态框虽已关闭,背景遮罩却残留页面上。

排查发现,表单提交的onSuccess回调中,emit('closeParent')未执行,但如果换成console.log能正常输出。

以下是相关代码:

Header.vue

<script setup>
import { ref } from 'vue';
import Modal from '@/Components/Modal.vue'

let thisModal = ref(null)

function showModal() {
    thisModal.value.show()
}

function closeModal() {
    thisModal.value.close()
}
</script>

<template>
    <header class="shadow-sm">
        <Modal v-if="! $page.props.auth.user" ref="thisModal">
            <template #body>
                <Login @closeParent="closeModal" @showParent="showModal" />
                <Register />
            </template>
        </Modal>
    </header>
</template>
<script setup>
import { onMounted, ref } from 'vue'
import { Modal } from 'bootstrap'

defineProps({
    title: String
})

let modalEle = ref(null)
let thisModalObj = null

onMounted(() => {
    thisModalObj = new Modal(modalEle.value)
})

function _show() {
    thisModalObj.show()
}

function _close() {
    thisModalObj.hide()
}

defineExpose({ show: _show, close: _close })
</script>

<template>
    <div class="modal fade" tabindex="-1" role="dialog" ref="modalEle">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <div class="modal-header bg-secondary">
                    <slot name="header" />
                </div>
                <div class="modal-body tab-content py-4">
                    <slot name="body" />
                </div>
                <slot name="footer" />
            </div>
        </div>
    </div>
</template>

Login.vue(表单组件)

<script setup>
import { Link, useForm } from '@inertiajs/vue3'
import Checkbox from '@/Components/Checkbox.vue'
import InputError from '@/Components/InputError.vue'
import InputLabel from '@/Components/InputLabel.vue'
import PrimaryButton from '@/Components/PrimaryButton.vue'
import TextInput from '@/Components/TextInput.vue'
import PasswordToggle from '@/Components/PasswordToggle.vue'

defineProps({
    canResetPassword: {
        type: Boolean,
    },
    status: {
        type: String,
    },
})

const form = useForm({
    email: '',
    password: '',
    remember: false,
})

const emit = defineEmits()

const submit = () => {
    form.post(route('login'), {
        onFinish: () => form.reset('password'),
        // onBefore 会隐藏模态框
        onBefore: () => emit('closeParent'),
        // onError 会重新显示模态框
        onError: () => emit('showParent'),
        // 期望仅在成功时关闭模态框
        onSuccess: () => emit('closeParent') // <- 这里应该是最合理的处理方式,但未生效
    })
}
</script>

<template>
    <form @submit.prevent="submit" id="login-tab" class="tab-pane fade show active" autocomplete="off" novalidate>
        <div class="mb-3">
            <InputLabel for="email" value="Email" />
            <TextInput
                id="email"
                type="text"
                name="email"
                v-model="form.email"
                autofocus
                autocomplete="username"
            />
            <InputError class="fs-sm" :message="form.errors.email" />
        </div>
        <div class="mb-3">
            <InputLabel for="password" value="Password" />
            <PasswordToggle>
                <TextInput
                    id="password"
                    type="password"
                    v-model="form.password"
                    autocomplete="current-password"
                />
            </PasswordToggle>
            <InputError class="fs-sm" :message="form.errors.password" />
        </div>
        <div class="mb-3 d-flex flex-wrap justify-content-between">
            <div class="form-check mb-2">
                <Checkbox id="remember" name="remember" v-model:checked="form.remember" />
                <InputLabel for="remember" value="Remember me" />
            </div>
            <Link v-if="$page.props.app.canResetPassword" class="fs-sm" :href="route('password.request')">
                Forgot password?
            </Link>
        </div>
        <PrimaryButton class="btn-shadow d-block w-100" :disabled="form.processing">
            <span v-if="form.processing" class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
            <span v-else class="me-2"><i class="ci-unlocked"></i></span>
            {{ `Sign in` }}
        </PrimaryButton>
    </form>
</template>

AuthenticatedSessionController.php

/**
 * Handle an incoming authentication request.
 */
 public function store(LoginRequest $request) // : RedirectResponse
 {
     $request->authenticate();

     $request->session()->regenerate();

     return redirect()->intended(RouteServiceProvider::HOME);
 }

问题排查思路

当前代码中,点击登录按钮就会触发onBefore隐藏模态框,验证失败时onError重新显示。但我希望仅在认证成功时关闭模态框,但onSuccess里的emit未执行。

推测核心原因:控制器返回的redirect()->intended()会触发Inertia页面跳转,此时Header.vue中的Modal组件因为v-if="! $page.props.auth.user"的条件不再满足,会被立即卸载,导致onSuccess回调还没执行完,组件就已经销毁,emit自然无法生效。同时Bootstrap模态框的遮罩层因为组件卸载前没正确触发hide()方法的完整流程,导致残留。


内容的提问来源于stack exchange,提问作者Richard C. Alvarez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 02:45:55