Laravel+Livewire Jetstream Modal无法显示及Alpine表达式错误修复
问题场景
使用Laravel结合Livewire开发时,采用Jetstream的x-jet-dialog-modal组件,点击upgrade.blade.php中的升级按钮后,模态框无法正常显示,控制台抛出多个Alpine语法相关错误。
控制台错误信息
Alpine Expression Error: Unexpected token ')'
Expression: "{ show: window.Livewire.find('RVerQgFNN56RogBnUr0F').entangle('upgrademodal').defer, focusables() { // All focusable element types... let selector = 'a, button, input:not([type='hidden']), textarea, select, details, [tabindex]:not([tabindex='-1'])' return [...$el.querySelectorAll(selector)] // All non-disabled elements... .filter(el => ! el.hasAttribute('disabled')) }, firstFocusable() { return this.focusables()[0] }, lastFocusable() { return this.focusables().slice(-1)[0] }, nextFocusable() { return this.focusables()[this.nextFocusableIndex()] || this.firstFocusable() }, prevFocusable() { return this.focusables()[this.prevFocusableIndex()] || this.lastFocusable() }, nextFocusableIndex() { return (this.focusables().indexOf(document.activeElement) + 1) % (this.focusables().length + 1) }, prevFocusableIndex() { return Math.max(0, this.focusables().indexOf(document.activeElement)) -1 }, }"
Alpine Expression Error: show is not defined
Expression: "show"
Uncaught SyntaxError: Unexpected token ')'
Uncaught ReferenceError: show is not defined
修复步骤
1. 确保Livewire组件类定义正确属性
在对应的Livewire组件类(如App\Http\Livewire\UpgradeModal.php)中,必须定义upgrademodal属性,并添加打开模态框的方法:
class UpgradeModal extends Component { public $upgrademodal = false; // 其他业务属性:plans、plan_id、plan_name等 // 触发打开模态框的方法 public function openModal() { $this->upgrademodal = true; } // 其他业务方法:selectPlan、payment等 public function render() { return view('livewire.upgrade-modal'); } }
2. 清理模态框视图中的冗余脚本
将视图中的jQuery、Stripe脚本移到项目主布局文件(如resources/views/layouts/app.blade.php)的</body>标签前,避免与Alpine.js冲突。修改后的upgrade-modal.php视图代码:
<div> <x-jet-dialog-modal wire:model="upgrademodal" maxWidth="md"> <x-slot name="title"> <div class="w-full h-12 flex items-center"> <h2 class="text-xl font-medium tracking-wider flex items-center space-x-3"> <span>Unlock Endless Creativites</span> <img src="{{ asset('studio-asset/svgs/leaf.png') }}" alt="heading image" class="w-8 h-8"> </h2> </div> </x-slot> <x-slot name="content"> <div class="w-full space-y-5"> <div class="w-full flex items-center"> <!-- 可添加月度/年度切换逻辑 --> </div> <div class="w-full space-y-2"> @foreach ($this->plans as $plan ) <div wire:click="selectPlan({{ $plan->id }})" class="w-full h-20 @if($plan->id == $this->plan_id) bg-green-50 @else border @endif rounded cursor-pointer p-4 flex"> <div class="w-10/12 h-full"> <div><span class="text-md font-bold tracking-widest">{{ $plan->name }}</span> - <span>{{ App\Http\Livewire\UpgradeModal::totalCredit($plan->id) }}</span> <span class="text-sm text-gray-400">Credits/month</span></div> <div class="space-x-1"><span>US$</span> <span>{{ $plan->price_monthly }}</span><span>/</span> <span>Month</span></div> </div> <div class="w-2/12 h-full flex items-center justify-center"> <div class="w-full h-full flex items-center justify-center"> @if($plan->id == $this->plan_id) <div class="w-5 h-5 rounded-full p-1 bg-green-400 flex items-center"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8.922 6.927"><path data-name="Combined Shape" d="M3.261 4.798L7.468.245a.86.86 0 011.2 0 .968.968 0 010 1.3l-4.676 5.06a1 1 0 01-1.468 0L.251 4.147a.968.968 0 010-1.3.86.86 0 011.2 0" fill="#fafafa"></path></svg> </div> @else <div class="w-5 h-5 rounded-full border-2"></div> @endif </div> </div> </div> @endforeach </div> <div class="w-full h-12 flex items-center"> <span class="px-5 h-8 flex items-center cursor-pointer bg-yellow-100 rounded ">Have a Promo Code ?</span> </div> </div> </x-slot> <x-slot name="footer"> <div class="w-full h-12 flex items-center justify-end"> <span wire:click="payment()" wire:loading.class="text-gray-700" wire:loading.attr="disabled" class="px-5 h-10 text-sm tracking-wider flex items-center cursor-pointer bg-indigo-500 rounded text-white">Get Started with {{ $this->plan_name }}</span> </div> </x-slot> </x-jet-dialog-modal> </div>
3. 迁移Stripe脚本到主布局
将原视图中的Stripe脚本移至app.blade.php末尾:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script type="text/javascript" src="https://js.stripe.com/v2/"></script> <script type="text/javascript"> $(function() { var $form = $(".require-validation"); $('form.require-validation').bind('submit', function(e) { var $form = $(".require-validation"), inputSelector = ['input[type=email]', 'input[type=password]', 'input[type=text]', 'input[type=file]', 'textarea'].join(', '), $inputs = $form.find('.required').find(inputSelector), $errorMessage = $form.find('div.error'), valid = true; $errorMessage.addClass('hidden'); $('.has-error').removeClass('has-error'); $inputs.each(function(i, el) { var $input = $(el); if ($input.val() === '') { $input.parent().addClass('has-error'); $errorMessage.removeClass('hidden'); e.preventDefault(); } }); if (!$form.data('cc-on-file')) { e.preventDefault(); Stripe.setPublishableKey('pk_test_51JEZdbSCJoZTKKTopB08PolKHDBMgGMsTskcj3Hbdjic5XCn9Z1ovtH0sgUMVaPhOse8CAhMsqzvP2MAwqSGOv8f00gGuc0A7H'); Stripe.createToken({ number: $('.card-number').val(), cvc: $('.card-cvc').val(), exp_month: $('.card-expiry-month').val(), exp_year: $('.card-expiry-year').val() }, stripeResponseHandler); } }); function stripeResponseHandler(status, response) { if (response.error) { $('.error') .removeClass('hidden') .find('.alert') .text(response.error.message); } else { var token = response['id']; $form.find('input[type=text]').empty(); $form.append("<input wire:model='stripeToken' type='hidden' name='stripeToken' value='" + token + "'/>"); $form.get(0).submit(); } } }); </script>
4. 确保Alpine.js版本兼容
Jetstream依赖特定版本的Alpine.js,安装官方推荐版本并初始化:
npm install alpinejs@^3.x --save-dev
在resources/js/app.js中添加初始化代码:
import Alpine from 'alpinejs'; window.Alpine = Alpine; Alpine.start();
5. 清理缓存并重新编译资源
php artisan view:clear php artisan cache:clear npm run dev
错误原因总结
- 模态框视图中直接引入第三方脚本,导致Alpine.js表达式解析冲突
- Jetstream模态框的内部Alpine逻辑因语法错误无法初始化
show状态变量 - Livewire组件类未正确定义
upgrademodal属性,导致双向绑定失效
内容的提问来源于stack exchange,提问作者Hirak Roy

