Laravel Nova-基于与另一个下拉列表的关系的下拉列表字段

时间:2019-01-29 14:01:43

标签: laravel laravel-nova

我有一个名为Distributor的资源

  ID::make()->sortable(),
            Text::make('Name')
                ->creationRules('required'),
            BelongsTo::make('Region')
                ->creationRules('required')
                ->searchable(),
            BelongsTo::make('Country')
                ->creationRules('required')
                ->searchable(),

一切都准备就绪。但是Country模型应取决于Region模型,因此,当我选择一个区域时,我想显示与该区域相关的国家/地区的选项。

地区和国家/地区已经在基于belongsToMany关系的模型中建立了联系。

有没有办法使这些字段协同工作?

3 个答案:

答案 0 :(得分:2)

我意识到这个问题已经存在了将近一年,但我想我会回答:1.这个问题仍在增加流量中; 2.我们最近遇到了一个相同的问题,对缺乏可用资源感到失望信息。

据我所知,这个问题也可以通过相关查询解决,但是由于各种原因,我们最终添加了一个自定义字段。 The official documentation for custom fields相当稀疏,但应该足够入门。

在Vue方面,我们的自定义字段仍然非常简单。 Vue处理的唯一真实逻辑是从我们的API中提取国家/州,并将其填充到下拉列表中。在PHP方面,我们最终需要重写字段控制器中的两个函数:fillAttributeFromRequest()和resolve()。见下文:

CountryState.php:

IServiceCollection

FormField.vue

namespace Gamefor\CountryState;

use Laravel\Nova\Fields\Field;

class CountryState extends Field
{
    public $component = 'country-state';

    /**
     * Hydrate the given attribute on the model based on the incoming request.
     *
     * @param  \Laravel\Nova\Http\Requests\NovaRequest  $request
     * @param  string  $requestAttribute
     * @param  object  $model
     * @param  string  $attribute
     * @return void
     */
    protected function fillAttributeFromRequest($request, $requestAttribute, $model, $attribute)
    {
        parent::fillAttributeFromRequest($request, $requestAttribute, $model, $attribute);

        if ($request->exists('state_id')) {
            $model->state_id = $request['state_id'];
        }

        if ($request->exists('country_id')) {
            $model->country_id = $request['country_id'];
        }
    }

    /**
     * Resolve the field's value for display.
     *
     * @param  mixed  $resource
     * @param  string|null  $attribute
     * @return void
     */
    public function resolve($resource, $attribute = null)
    {
        // Model has both country_id and state_id foreign keys
        // In the model, we have
        //
        //  public function country(){
        //      return $this->belongsTo('App\Country', 'country_id', 'id');
        //  }
        //
        //  public function state(){
        //      return $this->belongsTo('App\State', 'state_id', 'id');
        //  }
        $this->value = $resource->country['name'] . ', ' .  $resource->state['name']; 
    }
}

IndexField.vue

<template>
  <default-field :field="field" :errors="errors">
    <template slot="field">
      <select
        name="country"
        ref="menu"
        id="country"
        class="form-control form-select mb-3 w-full"
        v-model="selectedCountryId"
        @change="updateStateDropdown"
      >
        <option
          :key="country.id"
          :value="country.id"
          v-for="country in countries"
        >
          {{ country.name }}
        </option>
      </select>

      <select
        v-if="states.length > 0"
        name="state"
        ref="menu"
        id="state"
        class="form-control form-select mb-3 w-full"
        v-model="selectedStateId"
      >
        <option :value="state.id" :key="state" v-for="state in states">
          {{ state.name }}
        </option>
      </select>
    </template>
  </default-field>
</template>

<script>
import { FormField, HandlesValidationErrors } from "laravel-nova";

export default {
  mixins: [FormField, HandlesValidationErrors],

  props: {
    name: String
  },

  data() {
    return {
      countries: [],
      states: [],
      allStates: [],
      selectedCountryId: null,
      selectedStateId: null
    };
  },

  created: function() {
    this.fetchCountriesWithStates();
  },

  methods: {
    updateStateDropdown() {
      this.states = this.allStates.filter(
        item => item.country_id === this.selectedCountryId
      );

      this.selectedStateId = this.states.length > 0 ? this.states[0].id : null;
    },

    async fetchCountriesWithStates() {
      const countryResponse = await Nova.request().get("/api/v1/countries");
      const stateResponse = await Nova.request().get("/api/v1/states");

      this.countries = countryResponse.data;
      this.allStates = stateResponse.data;
      this.updateStateDropdown();
    },

    fill(formData){
       formData.append('country_id', this.selectedCountryId);
       formData.append('state_id', this.selectedStateId);
    },
  },
};
</script>

最后,在我们的Nova资源的字段数组中:

<template>
    <span>{{ field.value }}</span>
</template>

<script>
export default {
    props: ['resourceName', 'field',],
}
</script>

这些样本在“准备好投入生产”之前肯定需要进行调整,但是希望它们可以帮助任何敢于冒险进入Nova定制野兔洞的人。

答案 1 :(得分:2)

埃里克斯的回答对我有很大帮助。谢谢!

但我没有在自定义Nova字段中编写自己的fill和resolve函数,而是继承了BelongsTo字段:

<?php

namespace Travelguide\DestinationSelect;

use App\Models\Destination;
use Laravel\Nova\Fields\Field;
use Laravel\Nova\Http\Requests\NovaRequest;

class DestinationSelect extends \Laravel\Nova\Fields\BelongsTo
{
  /**
   * The field's component.
   *
   * @var string
   */
  public $component = 'destination-select';

  /**
   * Prepare the field for JSON serialization.
   *
   * @return array
   */
  public function jsonSerialize()
  {
    $parentId = null;
    $parentName = null;

    if (isset($this->belongsToId)) {
      $destination = Destination::where('id', $this->belongsToId)->first();
      if (isset($destination) && isset($destination->parent)) {
        $parentId = $destination->parent->id;
        $parentName = $destination->parent->name;
      }
    }

    return array_merge([
      'parent_id' => $parentId,
      'parent_name' => $parentName,
    ], parent::jsonSerialize());
  }
}

然后可以使用jsonSerialize函数中的其他数据来预填充前端选择元素:

<template>
  <default-field :field="field" :errors="errors">
    <template slot="field">
      <select
        name="country"
        ref="menu"
        id="country"
        class="form-control form-select mb-3 w-full"
        v-model="selectedCountryId"
        @change="onCountryChange"
      >
        <option
          :key="country.id"
          :value="country.id"
          v-for="country in countries"
        >
          {{ country.name }}
        </option>
      </select>

      <select
        v-if="regions.length > 0"
        name="region"
        ref="menu"
        id="region"
        class="form-control form-select mb-3 w-full"
        v-model="selectedRegionId"
      >
        <option :value="region.id" :key="region" v-for="region in regions">
          {{ region.name }}
        </option>
      </select>
    </template>
  </default-field>
</template>

<script>
import { FormField, HandlesValidationErrors } from "laravel-nova";

export default {
  mixins: [FormField, HandlesValidationErrors],

  props: ['resourceName', 'field'],

  data() {
    return {
      countries: [],
      regions: [],
      selectedCountryId: null,
      selectedRegionId: null
    };
  },

  created: function() {
    this.fetchCountries();
  },

  methods: {

    async fetchCountries() {
      const countryResponse = await Nova.request().get("/api/destinations");
      this.countries = countryResponse.data;

      // Add 'null' option to countries
      this.countries.unshift({
        name: '-',
        id: null
      });

      if (this.field.parent_id) {
        this.selectedCountryId = this.field.parent_id;
        this.selectedRegionId = this.field.belongsToId || null;
      } else {
        this.selectedCountryId = this.field.belongsToId || null;
      }

      this.updateRegionDropdown();
    },

    async updateRegionDropdown() {
      if (!this.selectedCountryId) {
        return;
      }

      // Get all regions of the selected country
      const regionResponse = await Nova.request().get("/api/destinations/" + this.selectedCountryId);
      this.regions = regionResponse.data;

      // Add 'null' option to regions
      this.regions.unshift({
        name: '-',
        id: null
      });
    },

    onCountryChange() {
      // De-select current region and load all regions of new country
      this.selectedRegionId = null;
      this.updateRegionDropdown();
    },

    fill(formData) {
      if (this.selectedRegionId) {
        formData.append('destination', this.selectedRegionId);
      } else if (this.selectedCountryId) {
        formData.append('destination', this.selectedCountryId);
      }
    },
  },
};
</script>

答案 2 :(得分:0)

有一个laravel nova包装:

https://novapackages.com/packages/orlyapps/nova-belongsto-depend

也许是最简单的方法!