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
### Props
@ -146,6 +184,7 @@ export default {
| placeholder | Placeholder of unselected tab | _string_ | `Select` |
| active-color | Active color | _string_ | `#ee0a24` |
| 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

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
### Props
| 参数 | 说明 | 类型 | 默认值 |
| ------------ | ------------------ | ------------------ | --------- |
| title | 顶部标题 | _string_ | - |
| value | 选中项的值 | _string \| number_ | - |
| options | 可选项数据源 | _Option[]_ | `[]` |
| placeholder | 未选中时的提示文案 | _string_ | `请选择` |
| active-color | 选中状态的高亮颜色 | _string_ | `#ee0a24` |
| closeable | 是否显示关闭图标 | _boolean_ | `true` |
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 顶部标题 | _string_ | - |
| value | 选中项的值 | _string \| number_ | - |
| options | 可选项数据源 | _Option[]_ | `[]` |
| placeholder | 未选中时的提示文案 | _string_ | `请选择` |
| active-color | 选中状态的高亮颜色 | _string_ | `#ee0a24` |
| closeable | 是否显示关闭图标 | _boolean_ | `true` |
| field-names `v2.12.4` | 自定义 `options` 结构中的字段 | _object_ | `{ text: 'text', value: 'value', children: 'children' }` |
### Events

View File

@ -79,12 +79,40 @@
/>
</van-popup>
</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>
</template>
<script>
import zhCNOptions from './area-zh-CN';
import enUSOptions from './area-en-US';
import { deepClone } from '../../utils/deep-clone';
export default {
i18n: {
@ -105,6 +133,7 @@ export default {
{ text: '杭州市', value: '330100' },
{ text: '宁波市', value: '330200' },
],
customFieldNames: '自定义字段名',
},
'en-US': {
area: 'Area',
@ -123,6 +152,7 @@ export default {
{ text: 'Hangzhou', value: '330100' },
{ text: 'Ningbo', value: '330200' },
],
customFieldNames: 'Custom Field Names',
},
},
@ -144,9 +174,43 @@ export default {
result: '',
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() {
this.async.options = this.t('asyncOptions1');
},
@ -161,7 +225,10 @@ export default {
},
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],
show: false,

View File

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

View File

@ -29,5 +29,14 @@ exports[`renders demo correctly 1`] = `
<!----></i>
</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>
`;