如何为Filament表单构建器绑定JS实现手机号OTP验证
Laravel Livewire + Filament 实现手机号动态验证与OTP流程
核心问题修正
你当前代码里直接在Filament Form的schema中插入原生<PhoneInput>标签的方式不符合Filament规范,Filament字段应通过其官方API配置,我们可以借助Livewire的wire:model结合Filament字段的live()方法绑定事件,无需直接编写原生HTML。
完整实现步骤
1. 优化Livewire组件代码
<?php namespace App\Livewire; use App\Models\AdCondition; use App\Models\PriceType; use App\Models\User; use Filament\Forms\Components\MarkdownEditor; use Filament\Forms\Components\PhoneInput; use Filament\Forms\Components\Select; use Filament\Forms\Components\TagsInput; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\ToggleButtons; use Filament\Forms\Form; use Illuminate\Support\Facades\Session; use Livewire\Component; class YourLivewireComponent extends Component { public $phone_number; public $phone_verified = false; // 标记手机号是否验证通过 public $display_phone = false; public function detailForm(Form $form): Form { return $form->schema([ ToggleButtons::make('for_sale_by') ->label(__('messages.t_for_sale_by')) ->live() ->grouped() ->options([ 'owner' => __('messages.t_owner_for_sale'), 'business' => __('messages.t_business_for_sale'), ]), MarkdownEditor::make('description') ->disableToolbarButtons(['attachFiles']) ->label(__('messages.t_description')) ->live(onBlur: true) ->minLength(20) ->required(), ToggleButtons::make('condition_id') ->label(__('messages.t_condition')) ->live() ->options(AdCondition::all()->pluck('name', 'id')) ->inline(), Select::make('price_type_id') ->selectablePlaceholder(false) ->label(__('messages.t_price')) ->live() ->required() ->options(PriceType::all()->pluck('name', 'id')), TextInput::make('price') ->helperText(__('messages.t_set_fair_price')) ->required() ->numeric() ->minValue(1) ->placeholder(__('messages.t_price_your_ad')) ->prefix(config('app.currency_symbol')) ->live(onBlur: true) ->hidden(fn (Get $get): bool => $get('price_type_id') != 1) ->hiddenLabel(), ToggleButtons::make('display_phone') ->label(__('messages.t_display_phone_number')) ->live() ->required() ->boolean() ->grouped() ->afterStateUpdated(fn ($state) => $this->display_phone = $state), PhoneInput::make('phone_number') ->placeholder(__('messages.t_enter_phone_number')) ->helperText(__('messages.t_phone_number_helper')) ->required() ->live() ->debounce(500) ->hidden(fn () => !$this->display_phone) ->hiddenLabel() ->afterStateUpdated(function (string $state) { if (strlen($state) === 11) { $this->checkPhoneExistence(); } }), TagsInput::make('tags') ->label(__('messages.t_tags')) ->helperText(__('messages.t_set_tags')) ->live(onBlur: true), ]); } // 检查手机号是否存在于数据库 public function checkPhoneExistence() { $exists = User::where('phone_number', $this->phone_number)->exists(); if (!$exists) { $this->dispatch('show-otp-input'); $this->sendOtpToPhone(); // 触发OTP发送逻辑 } else { $this->phone_verified = true; } } // 发送OTP到手机号(需对接短信服务) private function sendOtpToPhone() { $otp = rand(100000, 999999); Session::put([ 'phone_otp' => $otp, 'target_phone' => $this->phone_number ]); // 调用短信API发送$otp到$this->phone_number } // 验证OTP并更新用户信息 public function verifyOtp($otp) { $storedOtp = Session::get('phone_otp'); $targetPhone = Session::get('target_phone'); if ($storedOtp == $otp && $targetPhone == $this->phone_number) { $user = auth()->user(); $user->update([ 'phone_number' => $this->phone_number, 'phone_number_verified_at' => now() ]); $this->phone_verified = true; $this->dispatch('show-success-message', ['message' => '手机号验证成功']); Session::forget(['phone_otp', 'target_phone']); } else { $this->dispatch('show-error-message', ['message' => 'OTP验证码错误']); } } // 表单提交前校验手机号验证状态 public function submitForm() { if ($this->display_phone && !$this->phone_verified) { $this->dispatch('show-error-message', ['message' => '请先完成手机号验证']); return; } // 执行表单保存逻辑 } public function render() { return view('livewire.your-livewire-component'); } }
2. 前端脚本优化(嵌入Livewire视图文件)
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <script> document.addEventListener('livewire:init', function () { // 成功提示弹窗 Livewire.on('show-success-message', ({ message }) => { Swal.fire('成功!', message, 'success'); }); // 错误提示弹窗 Livewire.on('show-error-message', ({ message }) => { Swal.fire('错误!', message, 'error'); }); // OTP输入弹窗 Livewire.on('show-otp-input', () => { Swal.fire({ title: "输入验证码", input: "text", inputAttributes: { autocapitalize: "off", maxlength: 6, placeholder: "请输入6位验证码" }, showCancelButton: true, confirmButtonText: "验证", allowOutsideClick: false, preConfirm: (otp) => { if (!otp || otp.length !== 6) { Swal.showValidationMessage('请输入有效的6位验证码'); } return otp; } }).then((result) => { if (result.isConfirmed) { Livewire.dispatch('verifyOtp', result.value); } }); }); }); </script>
3. 移除冗余路由与控制器
原代码中单独的checkPhoneNumberExistence控制器方法可替换为Livewire内部方法,无需额外注册路由,简化架构。
关键逻辑说明
- Filament字段规范:通过Filament官方API配置PhoneInput,利用
live()和afterStateUpdated()实现输入值实时同步与后端触发,避免原生HTML侵入。 - 验证状态管控:新增
phone_verified属性标记验证状态,表单提交前强制校验,确保未验证手机号无法提交。 - OTP安全存储:使用Session临时存储OTP与待验证手机号,避免直接将OTP写入用户表带来的安全风险。
- 前后端通信统一:采用Livewire的
dispatch与on方法管理事件,替代原生AJAX请求,贴合组件化开发逻辑。
内容的提问来源于stack exchange,提问作者Gilbert Ozioma
相关产品推荐
相关产品推荐

