【问题标题】:Laravel Nova - Load dropdown field based on relationship with of another dropdownLaravel Nova - 根据与另一个下拉列表的关系加载下拉字段
【发布时间】:2019-06-22 16:23:21
【问题描述】:

我有这个资源叫Distributor

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

到目前为止,一切都准备就绪。但是Country 模型应该依赖于Regionmodel,所以当我选择一个地区时,我想显示与该地区相关的国家/地区的选项。

Region 和 Country 在其模型中已基于 belongsToMany 关系关联。

有没有办法让这些字段一起工作?

【问题讨论】:

    标签: laravel laravel-nova


    【解决方案1】:

    我意识到这个问题现在已经差不多一年了,但我想我会回答 1. 这个问题仍然有流量,2. 我们最近遇到了一个相同的问题,对缺乏可用的问题感到失望信息。

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

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

    CountryState.php:

    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']; 
        }
    }
    

    FormField.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>
    

    IndexField.vue

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

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

    CountryState::make('Country and State')->rules('required')
    

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

    【讨论】:

      【解决方案2】:

      Erics 的回答对我帮助很大。谢谢!

      但我没有在自定义 Nova 字段中编写自己的填充和解析函数,而是继承了 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>
      

      【讨论】:

        【解决方案3】:

        为此有一个 laravel nova 包:

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

        也许是最简单的方法!

        【讨论】:

          猜你喜欢
          • 2019-04-19
          • 1970-01-01
          • 2017-06-13
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-04-13
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多