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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 02:15:43