feat(Cascader): add field-names prop (#7932)

This commit is contained in:
neverland 2021-01-17 14:59:49 +08:00 committed by GitHub
parent d005d71eea
commit 4d843df5d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 195 additions and 23 deletions

View File

@ -134,6 +134,44 @@ export default {
}; };
``` ```
### Custom Field Names
```html
<van-cascader
v-model="cascaderValue"
title="Select Area"
:options="options"
:field-names="fieldNames"
/>
```
```js
export default {
data() {
return {
cascaderValue: '',
fieldNames: {
text: 'name',
value: 'code',
children: 'items',
},
options: [
{
name: 'Zhejiang',
code: '330000',
items: [{ name: 'Hangzhou', code: '330100' }],
},
{
name: 'Jiangsu',
code: '320000',
items: [{ name: 'Nanjing', code: '320100' }],
},
],
};
},
};
```
## API ## API
### Props ### Props
@ -146,6 +184,7 @@ export default {
| placeholder | Placeholder of unselected tab | _string_ | `Select` | | placeholder | Placeholder of unselected tab | _string_ | `Select` |
| active-color | Active color | _string_ | `#ee0a24` | | active-color | Active color | _string_ | `#ee0a24` |
| closeable | Whether to show close icon | _boolean_ | `true` | | closeable | Whether to show close icon | _boolean_ | `true` |
| field-names `v2.12.4` | Custom the fields of options | _object_ | `{ text: 'text', value: 'value', children: 'children' }` |
### Events ### Events

View File

@ -146,18 +146,59 @@ export default {
}; };
``` ```
### 自定义字段名
通过 `field-names` 属性可以自定义 `options` 里的字段名称。
```html
<van-cascader
v-model="cascaderValue"
title="请选择所在地区"
:options="options"
:field-names="fieldNames"
/>
```
```js
export default {
data() {
return {
cascaderValue: '',
fieldNames: {
text: 'name',
value: 'code',
children: 'items',
},
options: [
{
name: '浙江省',
code: '330000',
items: [{ name: '杭州市', code: '330100' }],
},
{
name: '江苏省',
code: '320000',
items: [{ name: '南京市', code: '320100' }],
},
],
};
},
};
```
## API ## API
### Props ### Props
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| ------------ | ------------------ | ------------------ | --------- | | --- | --- | --- | --- |
| title | 顶部标题 | _string_ | - | | title | 顶部标题 | _string_ | - |
| value | 选中项的值 | _string \| number_ | - | | value | 选中项的值 | _string \| number_ | - |
| options | 可选项数据源 | _Option[]_ | `[]` | | options | 可选项数据源 | _Option[]_ | `[]` |
| placeholder | 未选中时的提示文案 | _string_ | `请选择` | | placeholder | 未选中时的提示文案 | _string_ | `请选择` |
| active-color | 选中状态的高亮颜色 | _string_ | `#ee0a24` | | active-color | 选中状态的高亮颜色 | _string_ | `#ee0a24` |
| closeable | 是否显示关闭图标 | _boolean_ | `true` | | closeable | 是否显示关闭图标 | _boolean_ | `true` |
| field-names `v2.12.4` | 自定义 `options` 结构中的字段 | _object_ | `{ text: 'text', value: 'value', children: 'children' }` |
### Events ### Events

View File

@ -79,12 +79,40 @@
/> />
</van-popup> </van-popup>
</demo-block> </demo-block>
<demo-block card :title="t('customFieldNames')">
<van-field
v-model="customFieldNames.result"
is-link
readonly
:label="t('area')"
:placeholder="t('selectArea')"
@click="customFieldNames.show = true"
/>
<van-popup
v-model="customFieldNames.show"
round
position="bottom"
get-container="body"
safe-area-inset-bottom
>
<van-cascader
v-model="customFieldNames.value"
:title="t('selectArea')"
:options="customFieldOptions"
:field-names="fieldNames"
@close="customFieldNames.show = false"
@finish="onFinish('customFieldNames', $event)"
/>
</van-popup>
</demo-block>
</demo-section> </demo-section>
</template> </template>
<script> <script>
import zhCNOptions from './area-zh-CN'; import zhCNOptions from './area-zh-CN';
import enUSOptions from './area-en-US'; import enUSOptions from './area-en-US';
import { deepClone } from '../../utils/deep-clone';
export default { export default {
i18n: { i18n: {
@ -105,6 +133,7 @@ export default {
{ text: '杭州市', value: '330100' }, { text: '杭州市', value: '330100' },
{ text: '宁波市', value: '330200' }, { text: '宁波市', value: '330200' },
], ],
customFieldNames: '自定义字段名',
}, },
'en-US': { 'en-US': {
area: 'Area', area: 'Area',
@ -123,6 +152,7 @@ export default {
{ text: 'Hangzhou', value: '330100' }, { text: 'Hangzhou', value: '330100' },
{ text: 'Ningbo', value: '330200' }, { text: 'Ningbo', value: '330200' },
], ],
customFieldNames: 'Custom Field Names',
}, },
}, },
@ -144,9 +174,43 @@ export default {
result: '', result: '',
options: [], options: [],
}, },
customFieldNames: {
show: false,
value: null,
result: '',
},
fieldNames: {
text: 'name',
value: 'code',
children: 'items',
},
}; };
}, },
computed: {
customFieldOptions() {
const options = deepClone(this.t('options'));
const adjustFieldName = (item) => {
if ('text' in item) {
item.name = item.text;
delete item.text;
}
if ('value' in item) {
item.code = item.value;
delete item.value;
}
if ('children' in item) {
item.items = item.children;
delete item.children;
item.items.forEach(adjustFieldName);
}
};
options.forEach(adjustFieldName);
return options;
},
},
created() { created() {
this.async.options = this.t('asyncOptions1'); this.async.options = this.t('asyncOptions1');
}, },
@ -161,7 +225,10 @@ export default {
}, },
onFinish(type, { value, selectedOptions }) { onFinish(type, { value, selectedOptions }) {
const result = selectedOptions.map((option) => option.text).join('/'); const result = selectedOptions
.map((option) => option.text || option.name)
.join('/');
this[type] = { this[type] = {
...this[type], ...this[type],
show: false, show: false,

View File

@ -9,6 +9,7 @@ export default createComponent({
props: { props: {
title: String, title: String,
value: [Number, String], value: [Number, String],
fieldNames: Object,
placeholder: String, placeholder: String,
activeColor: String, activeColor: String,
options: { options: {
@ -28,6 +29,18 @@ export default createComponent({
}; };
}, },
computed: {
textKey() {
return this.fieldNames?.text || 'text';
},
valueKey() {
return this.fieldNames?.value || 'value';
},
childrenKey() {
return this.fieldNames?.children || 'children';
},
},
watch: { watch: {
options: { options: {
deep: true, deep: true,
@ -36,7 +49,9 @@ export default createComponent({
value(value) { value(value) {
if (value || value === 0) { if (value || value === 0) {
const values = this.tabs.map((tab) => tab.selectedOption?.value); const values = this.tabs.map(
(tab) => tab.selectedOption?.[this.valueKey]
);
if (values.indexOf(value) !== -1) { if (values.indexOf(value) !== -1) {
return; return;
} }
@ -54,13 +69,13 @@ export default createComponent({
for (let i = 0; i < options.length; i++) { for (let i = 0; i < options.length; i++) {
const option = options[i]; const option = options[i];
if (option.value === value) { if (option[this.valueKey] === value) {
return [option]; return [option];
} }
if (option.children) { if (option[this.childrenKey]) {
const selectedOptions = this.getSelectedOptionsByValue( const selectedOptions = this.getSelectedOptionsByValue(
option.children, option[this.childrenKey],
value value
); );
if (selectedOptions) { if (selectedOptions) {
@ -87,10 +102,10 @@ export default createComponent({
}; };
const next = optionsCursor.filter( const next = optionsCursor.filter(
(item) => item.value === option.value (item) => item[this.valueKey] === option[this.valueKey]
); );
if (next.length) { if (next.length) {
optionsCursor = next[0].children; optionsCursor = next[0][this.childrenKey];
} }
return tab; return tab;
@ -126,9 +141,9 @@ export default createComponent({
this.tabs = this.tabs.slice(0, tabIndex + 1); this.tabs = this.tabs.slice(0, tabIndex + 1);
} }
if (option.children) { if (option[this.childrenKey]) {
const nextTab = { const nextTab = {
options: option.children, options: option[this.childrenKey],
selectedOption: null, selectedOption: null,
}; };
@ -148,14 +163,14 @@ export default createComponent({
.filter((item) => !!item); .filter((item) => !!item);
const eventParams = { const eventParams = {
value: option.value, value: option[this.valueKey],
tabIndex, tabIndex,
selectedOptions, selectedOptions,
}; };
this.$emit('input', option.value); this.$emit('input', option[this.valueKey]);
this.$emit('change', eventParams); this.$emit('change', eventParams);
if (!option.children) { if (!option[this.childrenKey]) {
this.$emit('finish', eventParams); this.$emit('finish', eventParams);
} }
}, },
@ -182,7 +197,8 @@ export default createComponent({
renderOptions(options, selectedOption, tabIndex) { renderOptions(options, selectedOption, tabIndex) {
const renderOption = (option) => { const renderOption = (option) => {
const isSelected = const isSelected =
selectedOption && option.value === selectedOption.value; selectedOption &&
option[this.valueKey] === selectedOption[this.valueKey];
return ( return (
<li <li
@ -192,7 +208,7 @@ export default createComponent({
this.onSelect(option, tabIndex); this.onSelect(option, tabIndex);
}} }}
> >
<span>{option.text}</span> <span>{option[this.textKey]}</span>
{isSelected ? ( {isSelected ? (
<Icon name="success" class={bem('selected-icon')} /> <Icon name="success" class={bem('selected-icon')} />
) : null} ) : null}
@ -206,7 +222,7 @@ export default createComponent({
renderTab(item, tabIndex) { renderTab(item, tabIndex) {
const { options, selectedOption } = item; const { options, selectedOption } = item;
const title = selectedOption const title = selectedOption
? selectedOption.text ? selectedOption[this.textKey]
: this.placeholder || t('select'); : this.placeholder || t('select');
return ( return (

View File

@ -29,5 +29,14 @@ exports[`renders demo correctly 1`] = `
<!----></i> <!----></i>
</div> </div>
</div> </div>
<div>
<div role="button" tabindex="0" class="van-cell van-cell--clickable van-field">
<div class="van-cell__title van-field__label"><span>地区</span></div>
<div class="van-cell__value van-field__value">
<div class="van-field__body"><input type="text" readonly="readonly" placeholder="请选择所在地区" class="van-field__control"></div>
</div><i class="van-icon van-icon-arrow van-cell__right-icon">
<!----></i>
</div>
</div>
</div> </div>
`; `;