feat(Cascader): add options-top slot (#9732)

* feat(Cascader): add option-top slot

* refactor: use watch to listen for activeTab

* test(Cascader): Add the options-top slot test

* docs(Cascader): update the document

完成新功能https://github.com/youzan/vant/issues/9716

* chore(Cascader): api modification
This commit is contained in:
lihai 2021-10-30 19:53:06 +08:00 committed by GitHub
parent 33d777d6b2
commit 94539d8f74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 164 additions and 4 deletions

View File

@ -258,6 +258,9 @@ export default defineComponent({
unselected: !selected, unselected: !selected,
})} })}
> >
{slots['options-top']
? slots['options-top']({ activeTab: activeTab.value })
: null}
{renderOptions(options, selected, tabIndex)} {renderOptions(options, selected, tabIndex)}
</Tab> </Tab>
); );
@ -278,7 +281,6 @@ export default defineComponent({
); );
updateTabs(); updateTabs();
watch(() => props.options, updateTabs, { deep: true }); watch(() => props.options, updateTabs, { deep: true });
watch( watch(
() => props.modelValue, () => props.modelValue,

View File

@ -197,6 +197,54 @@ export default {
}; };
``` ```
### Custom Content
```html
<van-cascader
v-model="code"
title="Select Area"
:options="options"
:field-names="fieldNames"
>
<template #options-top="{ activeTab }">
<span>Current level {{activeTab}}</span>
</template>
</van-cascader>
```
```js
import { ref } from 'vue';
export default {
setup() {
const code = ref('');
const fieldNames = {
text: 'name',
value: 'code',
children: 'items',
};
const options = [
{
name: 'Zhejiang',
code: '330000',
items: [{ name: 'Hangzhou', code: '330100' }],
},
{
name: 'Jiangsu',
code: '320000',
items: [{ name: 'Nanjing', code: '320100' }],
},
];
return {
code,
options,
fieldNames,
};
},
};
```
## API ## API
### Props ### Props
@ -239,6 +287,7 @@ export default {
| --- | --- | --- | | --- | --- | --- |
| title | Custom title | - | | title | Custom title | - |
| option `v3.1.4` | Custom option text | _{ option: Option, selected: boolean }_ | | option `v3.1.4` | Custom option text | _{ option: Option, selected: boolean }_ |
| options-top | Custom the content above options | - |
### Types ### Types

View File

@ -207,6 +207,54 @@ export default {
}; };
``` ```
### 自定义选项上方内容
```html
<van-cascader
v-model="code"
title="请选择所在地区"
:options="options"
:field-names="fieldNames"
>
<template #options-top="{ activeTab }">
<span>当前为第{{ tabIndex }}级</span>
</template>
</van-cascader>
```
```js
import { ref } from 'vue';
export default {
setup() {
const code = ref('');
const fieldNames = {
text: 'name',
value: 'code',
children: 'items',
};
const options = [
{
name: '浙江省',
code: '330000',
items: [{ name: '杭州市', code: '330100' }],
},
{
name: '江苏省',
code: '320000',
items: [{ name: '南京市', code: '320100' }],
},
];
return {
code,
options,
fieldNames,
};
},
};
```
## API ## API
### Props ### Props
@ -247,10 +295,11 @@ export default {
### Slots ### Slots
| 名称 | 说明 | 参数 | | 名称 | 说明 | 参数 |
| --------------- | -------------- | --------------------------------------- | | --- | --- | --- |
| title | 自定义顶部标题 | - | | title | 自定义顶部标题 | - |
| option `v3.1.4` | 自定义选项文字 | _{ option: Option, selected: boolean }_ | | option `v3.1.4` | 自定义选项文字 | _{ option: Option, selected: boolean }_ |
| options-top | 自定义选项上方的内容 | - |
### 类型定义 ### 类型定义

View File

@ -27,6 +27,7 @@ const t = useTranslate({
{ text: '宁波市', value: '330200' }, { text: '宁波市', value: '330200' },
], ],
customFieldNames: '自定义字段名', customFieldNames: '自定义字段名',
customContent: '自定义内容',
}, },
'en-US': { 'en-US': {
area: 'Area', area: 'Area',
@ -46,6 +47,7 @@ const t = useTranslate({
{ text: 'Ningbo', value: '330200' }, { text: 'Ningbo', value: '330200' },
], ],
customFieldNames: 'Custom Field Names', customFieldNames: 'Custom Field Names',
customContent: 'Custom Content',
}, },
}); });
@ -54,6 +56,7 @@ type StateItem = {
value: string | number | null; value: string | number | null;
result: string; result: string;
options?: CascaderOption[]; options?: CascaderOption[];
tabIndex?: number;
}; };
const baseState = reactive<StateItem>({ const baseState = reactive<StateItem>({
@ -84,6 +87,12 @@ const fieldNames = {
children: 'items', children: 'items',
}; };
const customContentState = reactive<StateItem>({
show: false,
value: null,
result: '',
});
const customFieldOptions = computed(() => { const customFieldOptions = computed(() => {
const options = deepClone(t('options')); const options = deepClone(t('options'));
const adjustFieldName = (item: CascaderOption) => { const adjustFieldName = (item: CascaderOption) => {
@ -234,4 +243,35 @@ const onFinish = (
/> />
</van-popup> </van-popup>
</demo-block> </demo-block>
<demo-block card :title="t('customContent')">
<van-field
v-model="customContentState.result"
is-link
readonly
:label="t('area')"
:placeholder="t('selectArea')"
@click="customContentState.show = true"
/>
<van-popup
v-model:show="customContentState.show"
round
teleport="body"
position="bottom"
safe-area-inset-bottom
>
<van-cascader
v-model="customContentState.value"
:title="t('selectArea')"
:options="customFieldOptions"
:field-names="fieldNames"
@close="customContentState.show = false"
@finish="onFinish(customContentState, $event)"
>
<template #options-top="{ activeTab }">
<span>当前为第{{ activeTab }}</span>
</template>
</van-cascader>
</van-popup>
</demo-block>
</template> </template>

View File

@ -241,3 +241,23 @@ test('should allow to custom the color of option', async () => {
const option = wrapper.find('.van-cascader__option'); const option = wrapper.find('.van-cascader__option');
expect(option.style.color).toEqual('red'); expect(option.style.color).toEqual('red');
}); });
test(' should allow more custom content', async () => {
const wrapper = mount(Cascader, {
slots: {
'options-top': ({ activeTab }) => activeTab,
},
props: {
options,
},
});
await later();
wrapper
.findAll('.van-cascader__options')[0]
.find('.van-cascader__option')
.trigger('click');
wrapper.vm.$nextTick(() => {
const top = wrapper.find('.van-tab__pane');
expect(top.text()).toBe('1');
});
});