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

Filament v3中Repeater的afterStateUpdate无法更新外部字段

问题:Filament v3订单表单Repeater组件无法同步更新外部字段

使用Filament v3开发订单表单时,通过Repeater组件关联Product表,需求是选择产品或调整数量后,Repeater外部的subtotal和total字段同步更新。目前Repeater的afterStateUpdate方法仅能更新内部字段,外部字段无变化。


相关代码

Repeater组件代码

Repeater::make('products')
    ->relationship()
    ->schema([
        Select::make('product_id')
            ->label("Products")
            ->relationship('product','name')
            ->options(
                $products->mapWithKeys(function (Product $product) {
                    return [$product->id => sprintf('%s ($%s)', $product->name, $product->price)];
                })
            )
            ->disableOptionWhen(function ($value, $state, Get $get) {
                return collect($get('../*.product_id'))
                    ->reject(fn($id) => $id == $state)
                    ->filter()
                    ->contains($value);
            })
            ->required()
            ->createOptionForm([
                Forms\Components\FileUpload::make('image')
                    ->image()
                    ->required(),
                Forms\Components\TextInput::make('name')
                    ->required()
                    ->maxLength(255),
                Forms\Components\RichEditor::make('description')
                    ->required()
                    ->maxLength(65535)
                    ->columnSpanFull(),
                Forms\Components\TextInput::make('sku')
                    ->label('SKU')
                    ->required()
                    ->maxLength(255),
                Forms\Components\TextInput::make('price')
                    ->required()
                    ->maxLength(255)
                    ->numeric(),
                Forms\Components\TextInput::make('qty')
                    ->required()
                    ->maxLength(255)
                    ->numeric(),
                Forms\Components\Select::make('availablity')
                    ->options([
                        "yes" => "Yes",
                        'no' => "No"
                    ])
                    ->required(),
                Forms\Components\Select::make('brands')
                    ->relationship('brands', 'name')
                    ->required()
                    ->multiple()
                    ->preload()
                    ->createOptionForm([
                        Forms\Components\TextInput::make('name')
                            ->required()
                            ->maxLength(255),
                        Forms\Components\Toggle::make('active')
                            ->required(),
                    ]),
                Forms\Components\Select::make('categories')
                    ->relationship('categories', 'name')
                    ->required()
                    ->multiple()
                    ->preload()
                    ->createOptionForm([
                        Forms\Components\TextInput::make('name')
                            ->required()
                            ->maxLength(255),
                        Forms\Components\TextInput::make('description')
                            ->required()
                            ->maxLength(255),
                        Forms\Components\Toggle::make('active')
                            ->required(),
                    ]),
                Forms\Components\Select::make('warehouse_id')
                    ->relationship('warehouse', 'name')
                    ->required()
                    ->createOptionForm([
                        Forms\Components\TextInput::make('name')
                            ->required()
                            ->maxLength(255),
                        Toggle::make('active')
                    ])
            ])
            ->columnSpan([
                "md" => 5
            ]),
        TextInput::make('amount')
            ->numeric()
            ->required()
            ->reactive()
            ->label('Quantity')
            ->default(1)
            ->columnSpan([
                "md" => 3
            ]),
        TextInput::make('price')
            ->disabled()
            ->numeric()
            ->minValue('1')
            ->required()
            ->default(1)
            ->dehydrated()
            ->columnSpan([
                "md" => 2
            ]),
    ])->columns(["md" => 10])
    ->live()
    ->afterStateUpdated(fn ($state) => dd($state))
    ->deleteAction(
        fn(Action $action) => $action->after(fn(Get $get, Set $set) => self::updateTotals($get, $set)),
    )
    ->reorderable(false)
])->collapsible(),

外部区域代码

Section::make()
    ->columns(1)
    ->maxWidth('1/2')->schema([
        TextInput::make('subtotal')
            ->numeric()
            ->readOnly()
            ->afterStateHydrated(function (Get $get, Set $set) {
                self::updateTotals($get, $set);
            }),
        TextInput::make('total')->readOnly()->numeric()
    ])

updateTotals函数代码

public static function updateTotals(Get $get, Set $set): void
{
    // 筛选出已选择的有效产品行
    $selectedProducts = collect($get('products'))->filter(fn($item) => !empty($item['product_id']) && !empty($item['amount']));

    // 获取选中产品的价格映射
    $prices = Product::find($selectedProducts->pluck('product_id'))->pluck('price', 'id');

    // 计算小计
    $subtotal = $selectedProducts->reduce(function ($subtotal, $product) use ($prices) {
        return $subtotal + ($prices[$product['product_id']] * $product['amount']);
    }, 0);

    // 更新小计和总计字段
    $set('subtotal', number_format($subtotal, 2, '.', ''));
    $set('total', number_format($subtotal + ($subtotal * ($get('taxes') / 100)), 2, '.', ''));
}

解决方案

问题核心是Repeater内部字段变更时未触发外部总计更新逻辑,且Repeater的状态更新回调未正确执行计算。

  1. 启用内部字段响应式并触发更新
    给product_id和amount添加reactive(),并在afterStateUpdated中同时更新当前行价格和外部总计:

    // 给product_id字段添加
    ->reactive()
    ->afterStateUpdated(function (Get $get, Set $set) {
        $product = Product::find($get('product_id'));
        if ($product) {
            $set('price', $product->price * $get('amount'));
        }
        self::updateTotals($get, $set);
    })
    
    // 给amount字段添加
    ->reactive()
    ->afterStateUpdated(function (Get $get, Set $set) {
        $product = Product::find($get('product_id'));
        if ($product) {
            $set('price', $product->price * $get('amount'));
        }
        self::updateTotals($get, $set);
    })
    
  2. 修正Repeater的afterStateUpdated回调
    移除调试用的dd($state),改为调用总计更新函数,确保新增行后触发计算:

    ->live()
    ->afterStateUpdated(function (Get $get, Set $set) {
        self::updateTotals($get, $set);
    })
    
  3. 处理taxes字段空值问题
    在updateTotals中给taxes设置默认值,避免除以0错误:

    $taxRate = $get('taxes') ?? 0;
    $set('total', number_format($subtotal + ($subtotal * ($taxRate / 100)), 2, '.', ''));
    

内容的提问来源于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.05 03:13:10