我有一个名为Distributor
的资源
ID::make()->sortable(),
Text::make('Name')
->creationRules('required'),
BelongsTo::make('Region')
->creationRules('required')
->searchable(),
BelongsTo::make('Country')
->creationRules('required')
->searchable(),
一切都准备就绪。但是Country
模型应取决于Region
模型,因此,当我选择一个区域时,我想显示与该区域相关的国家/地区的选项。
地区和国家/地区已经在基于belongsToMany
关系的模型中建立了联系。
有没有办法使这些字段协同工作?
答案 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)