Laravel livewire 动态下拉列表在单个模型上有很多关系

Laravel livewire dynamic dropdown with lots of relations on a single model

我有一个相当复杂的问题,我有一只动物

型号

class Animal extends Model
{
    use HasFactory;

    protected $fillable = [
        "breed_ID",
        "name",
        "color_ID",
        "eyes_color_ID",
        "birth_date",
        "animal_types_id",
        "born_location",
        "profile_picture_id",
        "gender_ID",
        "status",
        "image",
        "bio",
        "lenght",
        "weight",
        "passport_url",
        "chip_number",
        "breeder_ID",
    ];

    protected function genders(): BelongsTo
    {
        return $this->belongsTo(Gender::class);
    }

    public function borns(): BelongsTo
    {
        return $this->belongsTo(Born::class);
    }

    public function eyeColors(): BelongsTo
    {
        return $this->belongsTo(EyeColor::class);
    }

    public function colors(): BelongsTo
    {
        return $this->belongsTo(Color::class);
    }

    public function breeders(): BelongsTo
    {
        return $this->belongsTo(Breeder::class);
    }

    public function weights(): BelongsTo
    {
        return $this->belongsTo(Weight::class);
    }

    public function lengths(): BelongsTo
    {
        return $this->belongsTo(Length::class);
    }

    public function users(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function animalTypes(): BelongsTo
    {
        return $this->belongsTo(AnimalType::class);
    }

    public function images(): HasMany
    {
        return $this->hasMany(Image::class);
    }
}

这只动物有品种、性别、颜色e.t.c

当用户想要添加新动物时,他们会看到一个表单,这个表单是一个完整的页面 livewire 组件。

<main class="add-animal-page">
    <section class="relative">
        <div class="container px-4 mx-auto">
            <div
                class="flex flex-col justify-center w-full min-w-0 mb-6 break-words rounded-lg shadow-xl xl:flex-row bg-gray-50">
                <form enctype="multipart/form-data" class="flex justify-center" wire:submit.prevent="upload">
                    <div class="w-full xl:w-4/6">
                        <div class="flex justify-center px-4 py-5 sm:p-6">
                            <div class="grid max-w-4xl grid-cols-6 gap-6">
                                <div class="col-span-6 sm:col-span-3 lg:col-span-2">
                                    <label for="first_name" class="block text-sm font-medium text-gray-700">Name</label>
                                    <input type="text" name="first_name" id="first_name" autocomplete="given-name"
                                        wire:model.defer="animal.name"
                                        class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                </div>
                                <div class="col-span-6 sm:col-span-6">
                                    <label for="type" class="block text-sm font-medium text-gray-700">
                                        Type</label>
                                    <select wire:model="animal.breeds_id" name="breed" id="breed"
                                        class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                        <option value="">Choose a type</option>
                                        @foreach ($types as $type)
                                            <option value={{ $type->id }}>{{ $type->animal_name }}</option>
                                        @endforeach
                                    </select>
                                </div>
                                <div class="col-span-6 sm:col-span-6">
                                    <label for="breed" class="block text-sm font-medium text-gray-700">
                                        Breed</label>
                                    <select wire:model="animal.breeds_id" name="breed" id="breed"
                                        class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                        <option value="">Choose a breed</option>
                                        @foreach ($breeds as $breed)
                                            <option value={{ $breed->id }}>{{ $breed->breed_name }}</option>
                                        @endforeach
                                    </select>
                                </div>
                                <div class="col-span-6 sm:col-span-6">
                                    <label for="breed" class="block text-sm font-medium text-gray-700">
                                        Breed</label>
                                    <select wire:model="animal.breeds_id" name="breed" id="breed"
                                        class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                        <option value="">Choose a breed</option>
                                        @foreach ($breeds as $breed)
                                            <option value={{ $breed->id }}>{{ $breed->breed_name }}</option>
                                        @endforeach
                                    </select>
                                </div>
                                <div class="col-span-6 sm:col-span-6">
                                    <label for="gender" class="block text-sm font-medium text-gray-700">
                                        Gender</label>
                                    <select wire:model="animal.genders_id" name="gender" id="gender"
                                        class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                        <option value="">Choose a gender</option>
                                        @foreach ($genders as $gender)
                                            <option value={{ $gender->id }}>{{ $gender->type }}</option>
                                        @endforeach
                                    </select>
                                </div>
                                <div class="col-span-6 sm:col-span-6">
                                    <label for="eye_color" class="block text-sm font-medium text-gray-700">
                                        Eye color</label>
                                    <select wire:model="animal.eye_color_id" name="eye_color" id="eye_color"
                                        class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                        <option value="">Choose an eye color</option>
                                        @foreach ($eyeColors as $eyeColor)
                                            <option value={{ $eyeColor->id }}>{{ $eyeColor->name }}</option>
                                        @endforeach
                                    </select>
                                </div>
                                <div class="col-span-6 sm:col-span-6">
                                    <label for="Breeder" class="block text-sm font-medium text-gray-700">
                                        Breeder</label>
                                    <select wire:model="animal.breeders_id" name="Breeder" id="Breeder"
                                        class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                        <option value="">Choose a breeder</option>
                                        @foreach ($breeders as $breeder)
                                            <option value={{ $breeder->id }}>{{ $breeder->name }}</option>
                                        @endforeach
                                    </select>
                                </div>

                                <div class="col-span-6 sm:col-span-6">
                                    <label for="passport" class="block text-sm font-medium text-gray-700">
                                        Passport URL</label>
                                    <input type="text" wire:model.defer="animal.passport_url" name="passport"
                                        id="passport" autocomplete="text"
                                        class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                </div>

                                <div class="col-span-6 sm:col-span-6">
                                    <label for="chip_number" class="block text-sm font-medium text-gray-700">
                                        Chip number</label>
                                    <input type="text" wire:model.defer="animal.chip_number" name="chip_number"
                                        id="chip_number" autocomplete="chip_number"
                                        class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                </div>

                                <div class="col-span-6">
                                    <label for="bio" class="block text-sm font-medium text-gray-700">
                                        Bio
                                    </label>
                                    <div class="mt-1">
                                        <textarea wire:model.defer="animal.bio" id="bio" name="bio" rows="3"
                                            class="block w-full mt-1 border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
                                </textarea>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="max-w-md mx-auto overflow-hidden rounded-lg md:max-w-xl">
                            <div class="md:flex">
                                <div class="w-full p-3 ">
                                    <div
                                        class="relative flex items-center justify-center h-48 bg-gray-100 border-2 border-dotted rounded-lg border-primary-light">
                                        <div class="absolute">
                                            <div class="flex flex-col items-center"><i
                                                    class="fa fa-folder-open fa-4x text-primary"></i> <span
                                                    class="block font-normal text-gray-400">Upload your image
                                                    here</span>
                                            </div>
                                        </div>
                                        <input wire:model.defer="image" type="file"
                                            class="w-full h-full opacity-0 cursor-pointer">
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>

                    <div class="absolute bottom-0 right-0 px-6 py-3 mx-4 my-6 text-right bg-gray-50 sm:px-6">
                        <button type="submit"
                            class="inline-flex justify-center px-4 py-2 text-sm font-medium text-white border border-transparent rounded-md shadow-sm bg-primary hover:bg-primary-hover focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                            Save
                        </button>
                    </div>
                </form>
            </div>
        </div>
    </section>
</main>

表单是通过此 livewire 填充的 class。

class AddAnimal extends Component
{
    use WithFileUploads;

    public User $user;
    public Animal $animal;
    public Collection $genders;
    public Collection $eyeColors;
    public Collection $breeds;
    public Collection $colors;
    public Collection $breeders;
    public Collection $types;
    public $image;

    protected array $rules = [
        'animal.name' => 'required|min:2',
        'animal.eye_color_id' => 'nullable',
        'animal.bio' => 'nullable',
        'animal.breeds_id' => 'nullable',
        'animal.genders_id' => 'nullable',
        'animal.breeders_id' => 'nullable',
        'animal.chip_number' => 'nullable',
        'animal.passport_url' => 'nullable',
        'image' => 'nullable',
    ];

    public function mount(User $user)
    {
        $this->user = $user;
        $this->animal = new Animal();
        $this->genders = Gender::all();
        $this->eyeColors = EyeColor::all();
        $this->breeds = Breed::all();
        $this->colors = Color::all();
        $this->breeders = Breeder::all();
        $this->types = AnimalType::all();
    }

    public function render()
    {
        return view('livewire.add-animal')
            ->layout('components.layouts.dashboard', ['title' => 'Add-animal'])
            ->with(['user' => $this->user, 'genders' => $this->genders, 'eyeColors' => $this->eyeColors, 'breeds' => $this->breeds, 'colors' => $this->colors, 'breeders' => $this->breeders, 'types' => $this->types]);
    }

我怎样才能使我的下拉菜单变得动态?例如,如果用户选择 dog 作为动物类型,则在 breed 下拉列表中只应显示相关的狗品种,而不是猫或马。我尝试使用一些在线可用的教程来帮助我入门,但无法弄清楚我的模型中发生的所有关系。

由于 Breed 模型有一个 animal_type id,我们可以使用 Livewire 更新的钩子来检查动物类型的变化,并只渲染与动物类型相关的品种。

所以在 livewire 组件中,

class AddAnimal extends Component
{
    public User $user;
    public Animal $animal;
    public Collection $genders;
    public Collection $eyeColors;

    // public Collection $breeds; we will use a computed property

    public Collection $colors;
    public Collection $breeders;
    public Collection $types;
    public $image;

    // newly added variable to keep track of animal type changed
    public $filters = [
        'animal_type_id' => ''
    ];

    protected array $rules = [
        'animal.name' => 'required|min:2',
        'animal.eye_color_id' => 'nullable',
        'animal.bio' => 'nullable',
        'animal.breeds_id' => 'nullable',
        'animal.genders_id' => 'nullable',
        'animal.breeders_id' => 'nullable',
        'animal.chip_number' => 'nullable',
        'animal.passport_url' => 'nullable',
        'image' => 'nullable',
        'animal.animal_type_id' => '', // make sure you have the rule can be left empty if its not required
    ];

    public function mount(User $user)
    {
        $this->user = $user;
        $this->animal = new Animal();
        $this->genders = Gender::all();
        $this->eyeColors = EyeColor::all();
        $this->colors = Color::all();
        $this->breeders = Breeder::all();
        $this->types = AnimalType::all();
    }


    public function updatedAnimalAnimalTypeId($value)
    {
        $this->filters['animal_type_id'] = $value;
    }

    public function getBreedsProperty()
    {
        return Breed::when($this->filters['animal_type_id'], function($query, $animal_type_id){
            return $query->where('animal_type_id', $animal_type_id);
        })->get();
    }

    public function render()
    {
        return view('livewire.add-animal')
            ->layout('components.layouts.dashboard', ['title' => 'Add-animal'])
            ->with(['user' => $this->user, 'genders' => $this->genders, 'eyeColors' => $this->eyeColors, 'breeds' => $this->breeds, 'colors' => $this->colors, 'breeders' => $this->breeders, 'types' => $this->types]);
    }


}

请注意,我已使用 computed property 获取品种。 我还使用了 when 子句来避免空检查。

所以在 blade 文件中,我们只需要 wire:model animal_type_id.

....

<div class="col-span-6 sm:col-span-6">
    <label for="type" class="block text-sm font-medium text-gray-700">
        Type</label>
    <select wire:model="animal.animal_type_id" name="animal_type_id" id="animal_type_id"
        class="block w-full px-3 py-2 mt-1 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
        <option value="">Choose a type</option>
        @foreach ($types as $type)
        <option value={{ $type->id }}>{{ $type->animal_name }}</option>
        @endforeach
    </select>
</div>
....

现在将根据所选的动物类型呈现品种。

我假设 animal_type_idAnimal 模型中的正确列名。如果不是,请分别更改列名称。