Merge branch '2.x' into dev

This commit is contained in:
chenjiahan 2020-12-21 11:02:14 +08:00
commit 8bbd944ece
28 changed files with 1373 additions and 0 deletions

178
src/cascader/README.md Normal file
View File

@ -0,0 +1,178 @@
# Cascader
### Install
```js
import Vue from 'vue';
import { Cascader } from 'vant';
Vue.use(Cascader);
```
## Usage
### Basic Usage
```html
<van-field
is-link
readonly
label="Area"
:value="fieldValue"
placeholder="Select Area"
@click="show = true"
/>
<van-popup v-model="show" round position="bottom">
<van-cascader
v-model="cascaderValue"
title="Select Area"
@close="show = false"
@finish="onFinish"
/>
</van-popup>
```
```js
export default {
data() {
return {
show: false,
fieldValue: '',
cascaderValue: '',
options: [
{
text: 'Zhejiang',
value: '330000',
children: [{ text: 'Hangzhou', value: '330100' }],
},
{
text: 'Jiangsu',
value: '320000',
children: [{ text: 'Nanjing', value: '320100' }],
},
],
};
},
methods: {
onFinish({ selectedOptions }) {
this.show = false;
this.fieldValue = selectedOptions.map((option) => option.text).join('/');
},
},
};
```
### Custom Color
```html
<van-cascader
v-model="cascaderValue"
title="Select Area"
active-color="#1989fa"
@close="show = false"
@finish="onFinish"
/>
```
### Async Options
```html
<van-field
is-link
readonly
label="Area"
:value="fieldValue"
placeholder="Select Area"
@click="show = true"
/>
<van-popup v-model="show" round position="bottom">
<van-cascader
v-model="cascaderValue"
title="Select Area"
@close="show = false"
@change="onChange"
@finish="onFinish"
/>
</van-popup>
```
```js
export default {
data() {
return {
show: false,
fieldValue: '',
cascaderValue: '',
options: [
{
text: 'Zhejiang',
value: '330000',
children: [],
},
],
};
},
methods: {
onChange({ value }) {
if (value === this.options[0].value) {
setTimeout(() => {
this.options[0].children = [
{ text: 'Hangzhou', value: '330100' },
{ text: 'Ningbo', value: '330200' },
];
}, 500);
}
},
onFinish({ selectedOptions }) {
this.show = false;
this.fieldValue = selectedOptions.map((option) => option.text).join('/');
},
},
};
```
## API
### Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| title | Title | _string_ | - |
| value | Value of selected option | _string \| number_ | - |
| options | Options | _Option[]_ | `[]` |
| placeholder | Placeholder of unselected tab | _string_ | `Select` |
| active-color | Active color | _string_ | `#ee0a24` |
| closeable | Whether to show close icon | _boolean_ | `true` |
### Events
| Event | Description | Arguments |
| --- | --- | --- |
| change | Emitted when active option changed | `{ value, selectedOptions, tabIndex }` |
| finish | Emitted when all options is selected | `{ value, selectedOptions, tabIndex }` |
| close | Emmitted when the close icon is clicked | - |
### Slots
| Name | Description |
| ----- | ------------ |
| title | Custom title |
### Less Variables
How to use: [Custom Theme](#/en-US/theme).
| Name | Default Value | Description |
| --------------------------------- | --------------- | ----------- |
| @cascader-header-height | `48px` | - |
| @cascader-title-font-size | `@font-size-lg` | - |
| @cascader-title-line-height | `20px` | - |
| @cascader-close-icon-size | `22px` | - |
| @cascader-close-icon-color | `@gray-5` | - |
| @cascader-close-icon-active-color | `@gray-6` | - |
| @cascader-selected-icon-size | `18px` | - |
| @cascader-tabs-height | `48px` | - |
| @cascader-active-color | `@red` | - |
| @cascader-options-height | `384px` | - |
| @cascader-tab-color | `@text-color` | - |
| @cascader-unselected-tab-color | `@gray-6` | - |

View File

@ -0,0 +1,190 @@
# Cascader 级联选择
### 介绍
级联选择框用于多层级数据的选择典型场景为省市区选择2.12 版本开始支持此组件。
### 引入
```js
import Vue from 'vue';
import { Cascader } from 'vant';
Vue.use(Cascader);
```
## 代码演示
### 基础用法
级联选择组件可以搭配 Field 和 Popup 组件使用,示例如下:
```html
<van-field
is-link
readonly
label="地区"
:value="fieldValue"
placeholder="请选择地区"
@click="show = true"
/>
<van-popup v-model:show="show" round position="bottom">
<van-cascader
v-model="cascaderValue"
title="请选择地区"
@close="show = false"
@finish="onFinish"
/>
</van-popup>
```
```js
export default {
data() {
return {
show: false,
fieldValue: '',
cascaderValue: '',
// 选项列表children 代表子选项,支持多级嵌套
options: [
{
text: '浙江省',
value: '330000',
children: [{ text: '杭州市', value: '330100' }],
},
{
text: '江苏省',
value: '320000',
children: [{ text: '南京市', value: '320100' }],
},
],
};
},
methods: {
// 全部选项选择完毕后,会触发 finish 事件
onFinish({ selectedOptions }) {
this.show = false;
this.fieldValue = selectedOptions.map((option) => option.text).join('/');
},
},
};
```
### 自定义颜色
通过 `active-color` 属性来设置选中状态的高亮颜色。
```html
<van-cascader
v-model="cascaderValue"
title="请选择地区"
active-color="#1989fa"
@close="show = false"
@finish="onFinish"
/>
```
### 异步加载选项
可以监听 `change` 事件并动态设置 `options`,实现异步加载选项。
```html
<van-field
is-link
readonly
label="地区"
:value="fieldValue"
placeholder="请选择地区"
@click="show = true"
/>
<van-popup v-model:show="show" round position="bottom">
<van-cascader
v-model="cascaderValue"
title="请选择地区"
@close="show = false"
@change="onChange"
@finish="onFinish"
/>
</van-popup>
```
```js
export default {
data() {
return {
show: false,
fieldValue: '',
cascaderValue: '',
options: [
{
text: '浙江省',
value: '330000',
children: [],
},
],
};
},
methods: {
onChange({ value }) {
if (value === this.options[0].value) {
setTimeout(() => {
this.options[0].children = [
{ text: '杭州市', value: '330100' },
{ text: '宁波市', value: '330200' },
];
}, 500);
}
},
onFinish({ selectedOptions }) {
this.show = false;
this.fieldValue = selectedOptions.map((option) => option.text).join('/');
},
},
};
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| ------------ | ------------------ | ------------------ | --------- |
| title | 顶部标题 | _string_ | - |
| value | 选中项的值 | _string \| number_ | - |
| options | 可选项数据源 | _Option[]_ | `[]` |
| placeholder | 未选中时的提示文案 | _string_ | `请选择` |
| active-color | 选中状态的高亮颜色 | _string_ | `#ee0a24` |
| closeable | 是否显示关闭图标 | _boolean_ | `true` |
### Events
| 事件 | 说明 | 回调参数 |
| ------ | ---------------------- | -------------------------------------- |
| change | 选中项变化时触发 | `{ value, selectedOptions, tabIndex }` |
| finish | 全部选项选择完成后触发 | `{ value, selectedOptions, tabIndex }` |
| close | 点击关闭图标时触发 | - |
### Slots
| 名称 | 说明 |
| ----- | -------------- |
| title | 自定义顶部标题 |
### 样式变量
组件提供了下列 Less 变量,可用于自定义样式,使用方法请参考[主题定制](#/zh-CN/theme)。
| 名称 | 默认值 | 描述 |
| --------------------------------- | --------------- | ---- |
| @cascader-header-height | `48px` | - |
| @cascader-title-font-size | `@font-size-lg` | - |
| @cascader-title-line-height | `20px` | - |
| @cascader-close-icon-size | `22px` | - |
| @cascader-close-icon-color | `@gray-5` | - |
| @cascader-close-icon-active-color | `@gray-6` | - |
| @cascader-selected-icon-size | `18px` | - |
| @cascader-tabs-height | `48px` | - |
| @cascader-active-color | `@red` | - |
| @cascader-options-height | `384px` | - |
| @cascader-tab-color | `@text-color` | - |
| @cascader-unselected-tab-color | `@gray-6` | - |

View File

@ -0,0 +1,122 @@
export default [
{
text: 'Zhejiang',
value: '330000',
children: [
{
text: 'Hangzhou',
value: '330100',
children: [
{
text: 'Shangcheng',
value: '330102',
},
{
text: 'Xiacheng',
value: '330103',
},
{
text: 'Jianggan',
value: '330104',
},
],
},
{
text: 'Ningbo',
value: '330200',
children: [
{
text: 'Haishu',
value: '330203',
},
{
text: 'Jiangbei',
value: '330205',
},
{
text: 'Beilun',
value: '330206',
},
],
},
{
text: 'Wenzhou',
value: '330300',
children: [
{
text: 'Lucheng',
value: '330302',
},
{
text: 'Longwan',
value: '330303',
},
{
text: 'Ouhai',
value: '330304',
},
],
},
],
},
{
text: 'Jiangsu',
value: '320000',
children: [
{
text: 'Nanjing',
value: '320100',
children: [
{
text: 'Xuanwu',
value: '320102',
},
{
text: 'Qinghuai',
value: '320104',
},
{
text: 'Jianye',
value: '320105',
},
],
},
{
text: 'Wuxi',
value: '320200',
children: [
{
text: 'Xishan',
value: '320205',
},
{
text: 'Huishan',
value: '320206',
},
{
text: 'Binhu',
value: '320211',
},
],
},
{
text: 'Xuzhou',
value: '320300',
children: [
{
text: 'Gulou',
value: '320302',
},
{
text: 'Yunlong',
value: '320303',
},
{
text: 'Jiawang',
value: '320305',
},
],
},
],
},
];

View File

@ -0,0 +1,122 @@
export default [
{
text: '浙江省',
value: '330000',
children: [
{
text: '杭州市',
value: '330100',
children: [
{
text: '上城区',
value: '330102',
},
{
text: '下城区',
value: '330103',
},
{
text: '江干区',
value: '330104',
},
],
},
{
text: '宁波市',
value: '330200',
children: [
{
text: '海曙区',
value: '330203',
},
{
text: '江北区',
value: '330205',
},
{
text: '北仑区',
value: '330206',
},
],
},
{
text: '温州市',
value: '330300',
children: [
{
text: '鹿城区',
value: '330302',
},
{
text: '龙湾区',
value: '330303',
},
{
text: '瓯海区',
value: '330304',
},
],
},
],
},
{
text: '江苏省',
value: '320000',
children: [
{
text: '南京市',
value: '320100',
children: [
{
text: '玄武区',
value: '320102',
},
{
text: '秦淮区',
value: '320104',
},
{
text: '建邺区',
value: '320105',
},
],
},
{
text: '无锡市',
value: '320200',
children: [
{
text: '锡山区',
value: '320205',
},
{
text: '惠山区',
value: '320206',
},
{
text: '滨湖区',
value: '320211',
},
],
},
{
text: '徐州市',
value: '320300',
children: [
{
text: '鼓楼区',
value: '320302',
},
{
text: '云龙区',
value: '320303',
},
{
text: '贾汪区',
value: '320305',
},
],
},
],
},
];

158
src/cascader/demo/index.vue Normal file
View File

@ -0,0 +1,158 @@
<template>
<demo-block card :title="t('basicUsage')">
<van-field
v-model="base.result"
is-link
readonly
:label="t('area')"
:placeholder="t('selectArea')"
@click="base.show = true"
/>
<van-popup v-model:show="base.show" round position="bottom">
<van-cascader
v-model="base.value"
:title="t('selectArea')"
:options="t('options')"
@close="base.show = false"
@finish="onFinish('base', $event)"
/>
</van-popup>
</demo-block>
<demo-block card :title="t('customColor')">
<van-field
v-model="customColor.result"
is-link
readonly
:label="t('area')"
:placeholder="t('selectArea')"
@click="customColor.show = true"
/>
<van-popup v-model:show="customColor.show" round position="bottom">
<van-cascader
v-model="customColor.value"
:title="t('selectArea')"
:options="t('options')"
active-color="#1989fa"
@close="customColor.show = false"
@finish="onFinish('customColor', $event)"
/>
</van-popup>
</demo-block>
<demo-block card :title="t('asyncOptions')">
<van-field
v-model="async.result"
is-link
readonly
:label="t('area')"
:placeholder="t('selectArea')"
@click="async.show = true"
/>
<van-popup v-model:show="async.show" round position="bottom">
<van-cascader
v-model="async.value"
:title="t('selectArea')"
:options="async.options"
@close="async.show = false"
@change="loadDynamicOptions"
@finish="onFinish('async', $event)"
/>
</van-popup>
</demo-block>
</template>
<script>
import zhCNOptions from './area-zh-CN';
import enUSOptions from './area-en-US';
import { reactive, toRefs } from 'vue';
import { useTranslate } from '@demo/use-translate';
const i18n = {
'zh-CN': {
area: '地区',
options: zhCNOptions,
selectArea: '请选择地区',
customColor: '自定义颜色',
asyncOptions: '异步加载选项',
asyncOptions1: [
{
text: '浙江省',
value: '330000',
children: [],
},
],
asyncOptions2: [
{ text: '杭州市', value: '330100' },
{ text: '宁波市', value: '330200' },
],
},
'en-US': {
area: 'Area',
options: enUSOptions,
selectArea: 'Select Area',
customColor: 'Custom Color',
asyncOptions: 'Async Options',
asyncOptions1: [
{
text: 'Zhejiang',
value: '330000',
children: [],
},
],
asyncOptions2: [
{ text: 'Hangzhou', value: '330100' },
{ text: 'Ningbo', value: '330200' },
],
},
};
export default {
setup() {
const t = useTranslate(i18n);
const state = reactive({
base: {
show: false,
value: '',
result: '',
},
customColor: {
show: false,
value: null,
result: '',
},
async: {
show: false,
value: null,
result: '',
options: t('asyncOptions1'),
},
});
const loadDynamicOptions = ({ value }) => {
if (value === '330000') {
setTimeout(() => {
state.async.options[0].children = t('asyncOptions2');
}, 500);
}
};
const onFinish = (type, { value, selectedOptions }) => {
const result = selectedOptions.map((option) => option.text).join('/');
state[type] = {
...state[type],
show: false,
value,
result,
};
};
return {
...toRefs(state),
t,
onFinish,
loadDynamicOptions,
};
},
};
</script>

252
src/cascader/index.js Normal file
View File

@ -0,0 +1,252 @@
import { createNamespace } from '../utils';
import Tab from '../tab';
import Tabs from '../tabs';
import Icon from '../icon';
const [createComponent, bem, t] = createNamespace('cascader');
export default createComponent({
props: {
title: String,
modelValue: [Number, String],
placeholder: String,
activeColor: String,
options: {
type: Array,
default: () => [],
},
closeable: {
type: Boolean,
default: true,
},
},
emits: ['close', 'change', 'finish', 'update:modelValue'],
data() {
return {
tabs: [],
activeTab: 0,
};
},
watch: {
options: {
deep: true,
handler: 'updateTabs',
},
modelValue(value) {
if (value || value === 0) {
const values = this.tabs.map((tab) => tab.selectedOption?.value);
if (values.indexOf(value) !== -1) {
return;
}
}
this.updateTabs();
},
},
created() {
this.updateTabs();
},
methods: {
getSelectedOptionsByValue(options, value) {
for (let i = 0; i < options.length; i++) {
const option = options[i];
if (option.value === value) {
return [option];
}
if (option.children) {
const selectedOptions = this.getSelectedOptionsByValue(
option.children,
value
);
if (selectedOptions) {
return [option, ...selectedOptions];
}
}
}
},
updateTabs() {
if (this.modelValue || this.modelValue === 0) {
const selectedOptions = this.getSelectedOptionsByValue(
this.options,
this.modelValue
);
if (selectedOptions) {
let optionsCursor = this.options;
this.tabs = selectedOptions.map((option) => {
const tab = {
options: optionsCursor,
selectedOption: option,
};
const next = optionsCursor.filter(
(item) => item.value === option.value
);
if (next.length) {
optionsCursor = next[0].children;
}
return tab;
});
if (optionsCursor) {
this.tabs.push({
options: optionsCursor,
selectedOption: null,
});
}
this.$nextTick(() => {
this.activeTab = this.tabs.length - 1;
});
return;
}
}
this.tabs = [
{
options: this.options,
selectedOption: null,
},
];
},
onSelect(option, tabIndex) {
this.tabs[tabIndex].selectedOption = option;
if (this.tabs.length > tabIndex + 1) {
this.tabs = this.tabs.slice(0, tabIndex + 1);
}
if (option.children) {
const nextTab = {
options: option.children,
selectedOption: null,
};
if (this.tabs[tabIndex + 1]) {
this.$set(this.tabs, tabIndex + 1, nextTab);
} else {
this.tabs.push(nextTab);
}
this.$nextTick(() => {
this.activeTab++;
});
}
const selectedOptions = this.tabs
.map((tab) => tab.selectedOption)
.filter((item) => !!item);
const eventParams = {
value: option.value,
tabIndex,
selectedOptions,
};
this.$emit('update:modelValue', option.value);
this.$emit('change', eventParams);
if (!option.children) {
this.$emit('finish', eventParams);
}
},
onClose() {
this.$emit('close');
},
renderHeader() {
return (
<div class={bem('header')}>
<h2 class={bem('title')}>
{this.$slots.title ? this.$slots.title() : this.title}
</h2>
{this.closeable ? (
<Icon
name="cross"
class={bem('close-icon')}
onClick={this.onClose}
/>
) : null}
</div>
);
},
renderOptions(options, selectedOption, tabIndex) {
const renderOption = (option) => {
const isSelected =
selectedOption && option.value === selectedOption.value;
return (
<li
class={bem('option', { selected: isSelected })}
style={{ color: isSelected ? this.activeColor : null }}
onClick={() => {
this.onSelect(option, tabIndex);
}}
>
<span>{option.text}</span>
{isSelected ? (
<Icon name="success" class={bem('selected-icon')} />
) : null}
</li>
);
};
return <ul class={bem('options')}>{options.map(renderOption)}</ul>;
},
renderTab(item, tabIndex) {
const { options, selectedOption } = item;
const title = selectedOption
? selectedOption.text
: this.placeholder || t('select');
return (
<Tab
title={title}
titleClass={bem('tab', {
unselected: !selectedOption,
})}
>
{this.renderOptions(options, selectedOption, tabIndex)}
</Tab>
);
},
renderTabs() {
return (
<Tabs
v-model={[this.activeTab, 'active']}
animated
swipeable
swipeThreshold={0}
class={bem('tabs')}
color={this.activeColor}
>
{this.tabs.map(this.renderTab)}
</Tabs>
);
},
},
render() {
return (
<div class={bem()}>
{this.renderHeader()}
{this.renderTabs()}
</div>
);
},
});

76
src/cascader/index.less Normal file
View File

@ -0,0 +1,76 @@
@import '../style/var';
.van-cascader {
&__header {
display: flex;
align-items: center;
justify-content: space-between;
height: @cascader-header-height;
padding: 0 @padding-md;
}
&__title {
font-weight: @font-weight-bold;
font-size: @cascader-title-font-size;
line-height: @cascader-title-line-height;
}
&__close-icon {
color: @cascader-close-icon-color;
font-size: @cascader-close-icon-color;
&:active {
color: @cascader-close-icon-active-color;
}
}
&__tabs {
.van-tab {
flex: none;
padding: 0 10px;
}
&.van-tabs--line .van-tabs__wrap {
height: @cascader-tabs-height;
padding: 0 6px;
}
}
&__tab {
color: @cascader-tab-color;
font-weight: @font-weight-bold;
&--unselected {
color: @cascader-unselected-tab-color;
}
}
&__option {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px @padding-md;
font-size: @font-size-md;
line-height: @line-height-md;
&:active {
background-color: @active-color;
}
&--selected {
color: @cascader-active-color;
}
}
&__selected-icon {
font-size: @cascader-selected-icon-size;
}
&__options {
box-sizing: border-box;
height: @cascader-options-height;
padding-top: 6px;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
}

View File

@ -0,0 +1,85 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should render demo and match snapshot 1`] = `
<div>
<div class="van-cell van-cell--clickable van-field"
role="button"
tabindex="0"
>
<div class="van-cell__title van-field__label">
<span>
Area
</span>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
class="van-field__control"
readonly
placeholder="Select Area"
>
</div>
</div>
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
</i>
</div>
<transition-stub>
</transition-stub>
<transition-stub>
</transition-stub>
</div>
<div>
<div class="van-cell van-cell--clickable van-field"
role="button"
tabindex="0"
>
<div class="van-cell__title van-field__label">
<span>
Area
</span>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
class="van-field__control"
readonly
placeholder="Select Area"
>
</div>
</div>
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
</i>
</div>
<transition-stub>
</transition-stub>
<transition-stub>
</transition-stub>
</div>
<div>
<div class="van-cell van-cell--clickable van-field"
role="button"
tabindex="0"
>
<div class="van-cell__title van-field__label">
<span>
Area
</span>
</div>
<div class="van-cell__value van-field__value">
<div class="van-field__body">
<input type="text"
class="van-field__control"
readonly
placeholder="Select Area"
>
</div>
</div>
<i class="van-badge__wrapper van-icon van-icon-arrow van-cell__right-icon">
</i>
</div>
<transition-stub>
</transition-stub>
<transition-stub>
</transition-stub>
</div>
`;

View File

@ -0,0 +1,4 @@
import Demo from '../demo';
import { snapshotDemo } from '../../../test/demo';
snapshotDemo(Demo);

View File

@ -0,0 +1,124 @@
import Cascader from '..';
import { mount, later } from '../../../test';
import options from '../demo/area-en-US';
test('should emit change event when active option changed', async () => {
const wrapper = mount(Cascader, {
propsData: {
options,
},
});
await later();
wrapper.find('.van-cascader__option').trigger('click');
const firstOption = options[0];
expect(wrapper.emitted('change')[0]).toEqual([
{
value: firstOption.value,
tabIndex: 0,
selectedOptions: [firstOption],
},
]);
await later();
wrapper
.findAll('.van-cascader__options')
.at(1)
.find('.van-cascader__option')
.trigger('click');
const secondOption = options[0].children[0];
expect(wrapper.emitted('change')[1]).toEqual([
{
value: secondOption.value,
tabIndex: 1,
selectedOptions: [firstOption, secondOption],
},
]);
});
test('should emit finish event when all options is selected', async () => {
const option = { value: '1', text: 'foo' };
const wrapper = mount(Cascader, {
propsData: {
options: [option],
},
});
await later();
wrapper.find('.van-cascader__option').trigger('click');
expect(wrapper.emitted('finish')[0]).toEqual([
{
value: option.value,
tabIndex: 0,
selectedOptions: [option],
},
]);
});
test('should emit close event when close icon is clicked', () => {
const wrapper = mount(Cascader);
wrapper.find('.van-cascader__close-icon').trigger('click');
expect(wrapper.emitted('close')[0]).toBeTruthy();
});
test('should not render close icon when closeable is false', () => {
const wrapper = mount(Cascader, {
propsData: {
closeable: false,
},
});
expect(wrapper.contains('.van-cascader__close-icon')).toBeFalsy();
});
test('should render title slot correctly', () => {
const wrapper = mount(Cascader, {
scopedSlots: {
title: () => 'Custom Title',
},
});
expect(wrapper.find('.van-cascader__title').html()).toMatchSnapshot();
});
test('should select correct option when value changed', async () => {
const wrapper = mount(Cascader, {
propsData: {
options,
},
});
await later();
wrapper.setProps({ value: '330304' });
await later();
const selectedOptions = wrapper.findAll('.van-cascader__option--selected');
const lastSelectedOption = selectedOptions.at(selectedOptions.length - 1);
expect(lastSelectedOption).toMatchSnapshot();
});
test('should reset selected options when value is set to emtpy', async () => {
const wrapper = mount(Cascader, {
propsData: {
value: '330304',
options,
},
});
await later();
wrapper.setProps({ value: '' });
await later();
expect(wrapper.contains('.van-cascader__option--selected')).toBeFalsy();
});
test('should update tabs when previous tab is clicked', async () => {
const wrapper = mount(Cascader, {
propsData: {
value: '330304',
options,
},
});
await later();
wrapper.findAll('.van-cascader__option').at(1).trigger('click');
await later();
expect(wrapper.html()).toMatchSnapshot();
});

View File

@ -21,6 +21,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}/${month}`,
rangePrompt: (maxRange: number) => `Wähle nicht mehr als ${maxRange} Tage`,
},
vanCascader: {
select: 'Wählen',
},
vanContactCard: {
addText: 'Kontaktinformationen hinzufügen',
},

View File

@ -21,6 +21,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}/${month}`,
rangePrompt: (maxRange: number) => `Wähle nicht mehr als ${maxRange} Tage`,
},
vanCascader: {
select: 'Wählen',
},
vanContactCard: {
addText: 'Kontaktinformationen hinzufügen',
},

View File

@ -21,6 +21,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}/${month}`,
rangePrompt: (maxRange: number) => `Choose no more than ${maxRange} days`,
},
vanCascader: {
select: 'Select',
},
vanContactCard: {
addText: 'Add contact info',
},

View File

@ -21,6 +21,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}/${month}`,
rangePrompt: (maxRange: number) => `Elija no más de ${maxRange} días`,
},
vanCascader: {
select: 'Seleccione',
},
vanContactCard: {
addText: 'Añadir información de contacto',
},

View File

@ -22,6 +22,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}${month}`,
rangePrompt: (maxRange: number) => `${maxRange}日以内を選択してください`,
},
vanCascader: {
select: '選択する',
},
vanContactCard: {
addText: '連絡先を追加',
},

View File

@ -21,6 +21,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}/${month}`,
rangePrompt: (maxRange: number) => `Maks. ${maxRange} dager`,
},
vanCascader: {
select: 'Plukke ut',
},
vanContactCard: {
addText: 'Legg til kontakt info',
},

View File

@ -21,6 +21,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}/${month}`,
rangePrompt: (maxRange: number) => `Alege maxim ${maxRange} zile`,
},
vanCascader: {
select: 'Selectați',
},
vanContactCard: {
addText: 'Adaugă informațiile de contact',
},

View File

@ -21,6 +21,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}/${month}`,
rangePrompt: (maxRange: number) => `En fazla ${maxRange} gün seçin`,
},
vanCascader: {
select: 'Seçiniz',
},
vanContactCard: {
addText: 'Kişi bilgisi ekle',
},

View File

@ -22,6 +22,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}${month}`,
rangePrompt: (maxRange: number) => `选择天数不能超过 ${maxRange}`,
},
vanCascader: {
select: '请选择',
},
vanContactCard: {
addText: '添加联系人',
},

View File

@ -22,6 +22,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}${month}`,
rangePrompt: (maxRange: number) => `選擇天數不能超過 ${maxRange}`,
},
vanCascader: {
select: '請選擇',
},
vanContactCard: {
addText: '添加聯系人',
},

View File

@ -22,6 +22,9 @@ export default {
monthTitle: (year: number, month: number) => `${year}${month}`,
rangePrompt: (maxRange: number) => `選擇天數不能超過 ${maxRange}`,
},
vanCascader: {
select: '請選擇',
},
vanContactCard: {
addText: '新增聯絡人',
},

View File

@ -201,6 +201,20 @@
@card-price-integer-font-size: @font-size-lg;
@card-price-font-family: @price-integer-font-family;
// Cascader
@cascader-header-height: 48px;
@cascader-title-font-size: @font-size-lg;
@cascader-title-line-height: 20px;
@cascader-close-icon-size: 22px;
@cascader-close-icon-color: @gray-5;
@cascader-close-icon-active-color: @gray-6;
@cascader-selected-icon-size: 18px;
@cascader-tabs-height: 48px;
@cascader-active-color: @red;
@cascader-options-height: 384px;
@cascader-tab-color: @text-color;
@cascader-unselected-tab-color: @gray-6;
// Cell
@cell-font-size: @font-size-md;
@cell-line-height: 24px;

View File

@ -267,6 +267,7 @@ export default {
| to | Target route of the link, same as to of vue-router | _string \| object_ | - |
| replace | If true, the navigation will not leave a history record | _boolean_ | `false` |
| title-style | Custom title style | _any_ | - |
| title-class | Custom title class name | _any_ | - |
### Tabs Events

View File

@ -274,6 +274,7 @@ export default {
| to | 点击后跳转的目标路由对象,同 vue-router 的 [to 属性](https://router.vuejs.org/zh/api/#to) | _string \| object_ | - |
| replace | 是否在跳转时替换当前页面历史 | _boolean_ | `false` |
| title-style | 自定义标题样式 | _any_ | - |
| title-class | 自定义标题类名 | _any_ | - |
### Tabs Events

View File

@ -19,6 +19,7 @@ export default createComponent({
badge: [Number, String],
title: String,
titleStyle: null,
titleClass: null,
disabled: Boolean,
},
@ -56,6 +57,7 @@ export default createComponent({
() => props.title,
() => {
parent.setLine();
parent.scrollIntoView();
}
);

View File

@ -317,6 +317,7 @@ export default createComponent({
title={item.title}
color={props.color}
style={item.titleStyle}
class={item.titleClass}
isActive={index === state.currentIndex}
disabled={item.disabled}
scrollable={scrollable.value}
@ -418,6 +419,7 @@ export default createComponent({
props,
setLine,
currentName,
scrollIntoView,
});
return () => (

1
types/index.d.ts vendored
View File

@ -37,6 +37,7 @@ export class AddressList extends VanComponent {}
export class Badge extends VanComponent {}
export class Button extends VanComponent {}
export class Card extends VanComponent {}
export class Cascader extends VanComponent {}
export class Cell extends VanComponent {}
export class CellGroup extends VanComponent {}
export class Circle extends VanComponent {}

View File

@ -149,6 +149,10 @@ module.exports = {
path: 'calendar',
title: 'Calendar 日历',
},
{
path: 'cascader',
title: 'Cascader 级联选择',
},
{
path: 'checkbox',
title: 'Checkbox 复选框',
@ -498,6 +502,10 @@ module.exports = {
path: 'calendar',
title: 'Calendar',
},
{
path: 'cascader',
title: 'Cascader',
},
{
path: 'checkbox',
title: 'Checkbox',