如何在Laravel Filament单页中创建含可编辑字段的表单与表格
实现单页采购主表单+可编辑明细表格
1. 准备数据库模型与迁移
先搭建主从表结构,主表存储采购单头部信息,子表存储产品明细。
迁移文件:创建purchase_heads表
Schema::create('purchase_heads', function (Blueprint $table) { $table->id(); $table->string('supplier_name'); $table->string('invoice_number')->unique(); $table->date('invoice_date'); $table->timestamps(); });
迁移文件:创建purchase_lines表
Schema::create('purchase_lines', function (Blueprint $table) { $table->id(); $table->foreignId('purchase_head_id')->constrained()->cascadeOnDelete(); $table->foreignId('product_id')->constrained(); $table->integer('purchase_quantity'); $table->decimal('unit_cost_before_discount', 10, 2); $table->decimal('discount_percent', 5, 2)->default(0); $table->decimal('unit_cost_before_tax', 10, 2)->nullable(); $table->decimal('line_total', 12, 2)->nullable(); $table->decimal('profit_margin_percent', 5, 2)->default(0); $table->decimal('unit_selling_price_inc_tax', 10, 2)->nullable(); $table->string('lot_number')->nullable(); $table->timestamps(); });
模型定义:PurchaseHead.php
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; class PurchaseHead extends Model { protected $fillable = ['supplier_name', 'invoice_number', 'invoice_date']; public function lines(): HasMany { return $this->hasMany(PurchaseLine::class); } }
模型定义:PurchaseLine.php
namespace App\Models; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; class PurchaseLine extends Model { protected $fillable = [ 'product_id', 'purchase_quantity', 'unit_cost_before_discount', 'discount_percent', 'unit_cost_before_tax', 'line_total', 'profit_margin_percent', 'unit_selling_price_inc_tax', 'lot_number' ]; public function purchaseHead(): BelongsTo { return $this->belongsTo(PurchaseHead::class); } public function product(): BelongsTo { return $this->belongsTo(Product::class); } // 自动计算派生字段 protected static function booted(): void { static::saving(function (PurchaseLine $line) { // 计算折扣后税前成本 $discountAmount = $line->unit_cost_before_discount * ($line->discount_percent / 100); $line->unit_cost_before_tax = $line->unit_cost_before_discount - $discountAmount; // 计算行总计 $line->line_total = $line->unit_cost_before_tax * $line->purchase_quantity; // 根据利润率自动计算售价(可手动覆盖) if ($line->profit_margin_percent > 0) { $line->unit_selling_price_inc_tax = $line->unit_cost_before_tax * (1 + ($line->profit_margin_percent / 100)); } }); } }
2. 创建Filament单页
运行命令生成自定义页面:
php artisan make:filament-page PurchaseHeadPage
编辑生成的app/Filament/Pages/PurchaseHeadPage.php:
namespace App\Filament\Pages; use App\Models\PurchaseHead; use App\Models\Product; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Grid; use Filament\Forms\Components\Select; use Filament\Forms\Components\TextInput; use Filament\Forms\Form; use Filament\Pages\Page; use Filament\Tables\Actions\Action; use Filament\Tables\Actions\DeleteAction; use Filament\Tables\Actions\EditAction; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; class PurchaseHeadPage extends Page { protected static ?string $navigationIcon = 'heroicon-o-shopping-cart'; protected static string $view = 'filament.pages.purchase-head-page'; public ?array $data = []; public ?PurchaseHead $purchaseHead = null; public function mount(): void { $this->purchaseHead = PurchaseHead::query()->firstOrNew(); $this->form->fill($this->purchaseHead->attributesToArray()); } public function form(Form $form): Form { return $form ->schema([ Grid::make(3) ->schema([ TextInput::make('supplier_name') ->label('供应商名称') ->required(), TextInput::make('invoice_number') ->label('发票编号') ->required() ->unique(PurchaseHead::class, 'invoice_number', ignoreRecord: true), DatePicker::make('invoice_date') ->label('发票日期') ->required(), ]), ]) ->statePath('data') ->model(PurchaseHead::class); } public function table(Table $table): Table { return $table ->query($this->purchaseHead->lines()) ->columns([ TextColumn::make('product.name') ->label('Product Name') ->sortable(), TextColumn::make('purchase_quantity') ->label('Purchase Quantity') ->numeric(), TextColumn::make('unit_cost_before_discount') ->label('Unit Cost (Before Discount)') ->money(), TextColumn::make('discount_percent') ->label('Discount Percent') ->suffix('%') ->numeric(), TextColumn::make('unit_cost_before_tax') ->label('Unit Cost (Before Tax)') ->money(), TextColumn::make('line_total') ->label('Line Total') ->money(), TextColumn::make('profit_margin_percent') ->label('Profit Margin %') ->suffix('%') ->numeric(), TextColumn::make('unit_selling_price_inc_tax') ->label('Unit Selling Price (Inc. tax)') ->money(), TextColumn::make('lot_number') ->label('Lot Number'), ]) ->headerActions([ Action::make('addLine') ->label('添加产品行') ->action(function (array $data) { $this->purchaseHead->lines()->create($data); }) ->form([ Select::make('product_id') ->label('Product Name') ->options(Product::pluck('name', 'id')) ->required() ->searchable(), TextInput::make('purchase_quantity') ->label('Purchase Quantity') ->numeric() ->minValue(1) ->required(), TextInput::make('unit_cost_before_discount') ->label('Unit Cost (Before Discount)') ->numeric() ->minValue(0) ->required(), TextInput::make('discount_percent') ->label('Discount Percent') ->numeric() ->minValue(0) ->maxValue(100) ->default(0), TextInput::make('profit_margin_percent') ->label('Profit Margin %') ->numeric() ->minValue(0) ->default(0), TextInput::make('lot_number') ->label('Lot Number'), ]), ]) ->actions([ EditAction::make() ->form([ Select::make('product_id') ->label('Product Name') ->options(Product::pluck('name', 'id')) ->required() ->searchable(), TextInput::make('purchase_quantity') ->label('Purchase Quantity') ->numeric() ->minValue(1) ->required(), TextInput::make('unit_cost_before_discount') ->label('Unit Cost (Before Discount)') ->numeric() ->minValue(0) ->required(), TextInput::make('discount_percent') ->label('Discount Percent') ->numeric() ->minValue(0) ->maxValue(100), TextInput::make('profit_margin_percent') ->label('Profit Margin %') ->numeric() ->minValue(0), TextInput::make('lot_number') ->label('Lot Number'), ]), DeleteAction::make(), ]); } public function save(): void { $this->purchaseHead->fill($this->form->getState()); $this->purchaseHead->save(); $this->notify('success', '采购单已保存'); } }
3. 创建页面视图文件
在resources/views/filament/pages/purchase-head-page.blade.php添加内容:
<x-filament-panels::page> <x-filament::form wire:submit="save"> {{ $this->form }} <x-filament::form.actions :actions="$this->getFormActions()" :full-width="$this->hasFullWidthFormActions()" /> </x-filament::form> <div class="mt-8"> {{ $this->table }} </div> </x-filament-panels::page>
4. 功能说明
- 单页集成:主表单(供应商、发票信息)与明细表格在同一页面展示,所有操作无需跳转
- 数据关联:通过模型关联自动绑定主表与明细行,添加/编辑明细时自动关联当前采购单
- 自动计算:明细行的税前成本、行总计、售价等字段自动计算,无需手动输入
- 行级操作:支持添加新明细行、编辑现有行、删除行,所有操作在表格内完成
内容的提问来源于stack exchange,提问作者Muhammad Fareed Khan
相关产品推荐
相关产品推荐

