Laravel中Vue组件@click按钮二次点击报错求助
问题排查与解决:Laravel+Vue模态框二次点击报错
问题描述
在Laravel项目中使用Vue 3开发模态框时,首次点击绑定@click="showModal = true"的按钮功能正常,但再次点击时出现报错。
相关代码
Laravel Blade 代码
<div class="col-span-12 mb-5" id="app"> <div class="text-end text-base"> <button type="button" class="text-primary hover:text-secondary" @click="showModal = true">Verify your email </button> </div> <v-modal :open="showModal"> <form action="" method="post" id="form_verify_email"> @csrf <input type="hidden" name="email" value="{{ auth()->user()->email }}"> <div> <x-user.form.buttons.primary> {{ __('Verify Email') }} </x-user.form.buttons.primary> </div> </form> </v-modal> <x-user.form.inputs name="email" placeholder="Email" type="email" :value="old('email', auth()->user()->email)"/> </div>
JS 代码
import { createApp } from 'vue' import modal from './components/modal.vue' const app = createApp({ data() { return { showModal: false, } }, }) app.component('v-modal', modal) app.mount('#app')
Vue 组件代码
<script setup> import { ref, defineProps, onMounted } from 'vue' import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue' let { open } = defineProps({ open: { type: Boolean, required: true, }, }) </script>
问题原因与解决方法
1. 响应式丢失问题
直接解构defineProps返回的对象会丢失响应式。defineProps返回的是响应式Proxy对象,普通解构会提取出原始值而非响应式引用,导致Headless UI的Dialog无法正确监听状态变化。
修复代码:
<script setup> import { defineProps, defineEmits, toRefs } from 'vue' import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue' const props = defineProps({ open: { type: Boolean, required: true, }, }) const emit = defineEmits(['update:open']) // 使用toRefs解构,保留响应式 const { open } = toRefs(props) </script>
2. 双向绑定缺失,未重置父组件状态
当前组件仅接收open属性,但未在模态框关闭时通知父组件更新showModal状态。导致关闭后showModal仍为true,再次点击按钮时触发组件内部状态冲突。
修复步骤:
- 在组件中添加关闭事件的发射逻辑
- 父组件使用
v-model:open实现双向绑定
完整修改后的Vue组件(包含TailwindUI模态框结构):
<script setup> import { defineProps, defineEmits, toRefs } from 'vue' import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue' const props = defineProps({ open: { type: Boolean, required: true, }, }) const emit = defineEmits(['update:open']) const { open } = toRefs(props) </script> <template> <TransitionRoot appear :show="open" as="template"> <Dialog @close="emit('update:open', false)" class="relative z-10"> <!-- 背景遮罩 --> <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0" enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0" > <div class="fixed inset-0 bg-black bg-opacity-25" /> </TransitionChild> <!-- 模态框面板 --> <div class="fixed inset-0 overflow-y-auto"> <div class="flex min-h-full items-center justify-center p-4 text-center"> <TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0 scale-95" enter-to="opacity-100 scale-100" leave="ease-in duration-200" leave-from="opacity-100 scale-100" leave-to="opacity-0 scale-95" > <DialogPanel class="w-full max-w-md transform overflow-hidden rounded-2xl bg-white p-6 text-left align-middle shadow-xl transition-all"> <DialogTitle class="text-lg font-medium leading-6 text-gray-900"> 验证邮箱 </DialogTitle> <div class="mt-4"> <form action="" method="post" id="form_verify_email"> @csrf <input type="hidden" name="email" value="{{ auth()->user()->email }}"> <div> <x-user.form.buttons.primary> {{ __('Verify Email') }} </x-user.form.buttons.primary> </div> </form> </div> </DialogPanel> </TransitionChild> </div> </div> </Dialog> </TransitionRoot> </template>
修改Blade中的模态框调用,使用双向绑定:
<v-modal v-model:open="showModal"> <!-- 表单内容保持不变 --> </v-modal>
3. 额外检查
若表单为AJAX提交,需确保提交成功后手动将showModal设为false,避免模态框残留打开状态;若为普通表单提交,页面刷新后状态会重置,无需额外处理。
内容的提问来源于stack exchange,提问作者Nguyễn Tấn Dũng
相关产品推荐
相关产品推荐

