feat(Tabs): add before-change prop (#6817)

This commit is contained in:
neverland 2020-07-19 16:57:27 +08:00 committed by GitHub
parent 839f65e533
commit 9903230cb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 196 additions and 19 deletions

View File

@ -126,7 +126,7 @@ export default {
### Sticky
In sticky mode, the tab will be fixed to top when scroll to top
In sticky mode, the tab will be fixed to top when scroll to top.
```html
<van-tabs v-model="active" sticky>
@ -138,7 +138,7 @@ In sticky mode, the tab will be fixed to top when scroll to top
### Custom title
Use title slot to custom tab title
Use title slot to custom tab title.
```html
<van-tabs v-model="active">
@ -151,7 +151,7 @@ Use title slot to custom tab title
### Switch Animation
Use `animated` props to change tabs with animation
Use `animated` props to change tabs with animation.
```html
<van-tabs v-model="active" animated>
@ -163,7 +163,7 @@ Use `animated` props to change tabs with animation
### Swipeable
In swipeable mode, you can switch tabs with swipe gestrue in the content
In swipeable mode, you can switch tabs with swipe gestrue in the content.
```html
<van-tabs v-model="active" swipeable>
@ -175,7 +175,7 @@ In swipeable mode, you can switch tabs with swipe gestrue in the content
### Scrollspy
In scrollspy mode, the list of content will be tiled
In scrollspy mode, the list of content will be tiled.
```html
<van-tabs v-model="active" scrollspy sticky>
@ -185,6 +185,34 @@ In scrollspy mode, the list of content will be tiled
</van-tabs>
```
### Before Change
```html
<van-tabs :before-change="beforeChange">
<van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }}
</van-tab>
</van-tabs>
```
```js
export default {
methods: {
beforeChange(index) {
// prevent change
if (index === 1) {
return false;
}
// async
return new Promise((resolve) => {
resolve(index !== 3);
});
},
},
};
```
## API
### Tabs Props
@ -209,6 +237,7 @@ In scrollspy mode, the list of content will be tiled
| swipe-threshold | Set swipe tabs threshold | _number \| string_ | `4` | - |
| title-active-color | Title active color | _string_ | - |
| title-inactive-color | Title inactive color | _string_ | - |
| before-change `v2.9.3` | Callback function before changing tabsreturn `false` to prevent changesupport return Promise | _(name) => boolean \| Promise_ | - |
### Tab Props

View File

@ -14,7 +14,7 @@ Vue.use(Tabs);
### 基础用法
通过`v-model`绑定当前激活标签对应的索引值,默认情况下启用第一个标签
通过 `v-model` 绑定当前激活标签对应的索引值,默认情况下启用第一个标签
```html
<van-tabs v-model="active">
@ -37,7 +37,7 @@ export default {
### 通过名称匹配
在标签指定`name`属性的情况下,`v-model`的值为当前标签的`name`(此时无法通过索引值来匹配标签)
在标签指定 `name` 属性的情况下,`v-model` 的值为当前标签的 `name`(此时无法通过索引值来匹配标签)
```html
<van-tabs v-model="activeName">
@ -59,7 +59,7 @@ export default {
### 标签栏滚动
标签数量超过 4 个时,标签栏可以在水平方向上滚动,切换时会自动将当前标签居中
标签数量超过 4 个时,标签栏可以在水平方向上滚动,切换时会自动将当前标签居中
```html
<van-tabs>
@ -71,7 +71,7 @@ export default {
### 禁用标签
设置`disabled`属性即可禁用标签。如果需要监听禁用标签的点击事件,可以在`van-tabs`上监听`disabled`事件
设置 `disabled` 属性即可禁用标签,如果需要监听禁用标签的点击事件,可以在 `van-tabs` 上监听`disabled` 事件
```html
<van-tabs @disabled="onClickDisabled">
@ -95,7 +95,7 @@ export default {
### 样式风格
`Tab`支持两种样式风格:`line``card`,默认为`line`样式,可以通过`type`属性修改样式风格
`Tab` 支持两种样式风格:`line``card`,默认为 `line` 样式,可以通过 `type` 属性切换样式风格。
```html
<van-tabs type="card">
@ -107,7 +107,7 @@ export default {
### 点击事件
可以在`van-tabs`上绑定`click`事件,事件传参为标签对应的索引和标题
可以在 `van-tabs` 上绑定 `click` 事件,事件传参为标签对应的标识符和标题。
```html
<van-tabs @click="onClick">
@ -130,7 +130,7 @@ export default {
### 粘性布局
通过`sticky`属性可以开启粘性布局,粘性布局下,当 Tab 滚动到顶部时会自动吸顶
通过 `sticky` 属性可以开启粘性布局,粘性布局下,标签页滚动到顶部时会自动吸顶。
```html
<van-tabs v-model="active" sticky>
@ -142,7 +142,7 @@ export default {
### 自定义标签
通过 title 插槽可以自定义标签内容
通过 `title` 插槽可以自定义标签内容
```html
<van-tabs v-model="active">
@ -155,7 +155,7 @@ export default {
### 切换动画
通过`animated`属性可以开启切换标签内容时的转场动画
通过 `animated` 属性可以开启切换标签内容时的转场动画
```html
<van-tabs v-model="active" animated>
@ -167,7 +167,7 @@ export default {
### 滑动切换
通过`swipeable`属性可以开启滑动切换标签页
通过 `swipeable` 属性可以开启滑动切换标签页
```html
<van-tabs v-model="active" swipeable>
@ -179,7 +179,7 @@ export default {
### 滚动导航
通过`scrollspy`属性可以开启滚动导航模式,该模式下,内容将会平铺展示
通过 `scrollspy` 属性可以开启滚动导航模式,该模式下,内容将会平铺展示
```html
<van-tabs v-model="active" scrollspy sticky>
@ -189,6 +189,37 @@ export default {
</van-tabs>
```
### 异步切换
通过 `before-change` 属性可以在切换标签前执行特定的逻辑。
```html
<van-tabs :before-change="beforeChange">
<van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }}
</van-tab>
</van-tabs>
```
```js
export default {
methods: {
beforeChange(index) {
// 返回 false 表示阻止此次切换
if (index === 1) {
return false;
}
// 返回 Promise 来执行异步逻辑
return new Promise((resolve) => {
// 在 resolve 函数中返回 true 或 false
resolve(index !== 3);
});
},
},
};
```
## API
### Tabs Props
@ -213,6 +244,7 @@ export default {
| swipe-threshold | 滚动阈值,标签数量超过阈值时开始横向滚动 | _number \| string_ | `4` |
| title-active-color | 标题选中态颜色 | _string_ | - |
| title-inactive-color | 标题默认态颜色 | _string_ | - |
| before-change `v2.9.3` | 切换标签前的回调函数,返回 `false` 可阻止切换,支持返回 Promise | _(name) => boolean \| Promise_ | - |
### Tab Props

View File

@ -93,6 +93,14 @@
</van-tab>
</van-tabs>
</demo-block>
<demo-block v-if="!isWeapp" :title="t('beforeChange')">
<van-tabs :before-change="beforeChange">
<van-tab :title="t('tab') + index" v-for="index in 4" :key="index">
{{ t('content') }} {{ index }}
</van-tab>
</van-tabs>
</demo-block>
</demo-section>
</template>
@ -112,6 +120,7 @@ export default {
title10: '滚动导航',
disabled: ' 已被禁用',
matchByName: '通过名称匹配',
beforeChange: '异步切换',
},
'en-US': {
tab: 'Tab ',
@ -127,6 +136,7 @@ export default {
title10: 'Scrollspy',
disabled: ' is disabled',
matchByName: 'Match By Name',
beforeChange: 'Before Change',
},
},
@ -146,6 +156,16 @@ export default {
onClick(index, title) {
this.$toast(title);
},
beforeChange(name) {
if (name === 1) {
return false;
}
return new Promise((resolve) => {
resolve(name !== 3);
});
},
},
};
</script>

View File

@ -318,5 +318,32 @@ exports[`renders demo correctly 1`] = `
</div>
</div>
</div>
<div>
<div class="van-tabs van-tabs--line">
<div class="van-tabs__wrap van-hairline--top-bottom">
<div role="tablist" class="van-tabs__nav van-tabs__nav--line">
<div role="tab" aria-selected="true" class="van-tab van-tab--active"><span class="van-tab__text van-tab__text--ellipsis">标签 1</span></div>
<div role="tab" class="van-tab"><span class="van-tab__text van-tab__text--ellipsis">标签 2</span></div>
<div role="tab" class="van-tab"><span class="van-tab__text van-tab__text--ellipsis">标签 3</span></div>
<div role="tab" class="van-tab"><span class="van-tab__text van-tab__text--ellipsis">标签 4</span></div>
<div class="van-tabs__line" style="width: 0px; transform: translateX(0px) translateX(-50%);"></div>
</div>
</div>
<div class="van-tabs__content">
<div role="tabpanel" class="van-tab__pane" style="">
内容 1
</div>
<div role="tabpanel" class="van-tab__pane" style="display: none;">
<!---->
</div>
<div role="tabpanel" class="van-tab__pane" style="display: none;">
<!---->
</div>
<div role="tabpanel" class="van-tab__pane" style="display: none;">
<!---->
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -362,3 +362,51 @@ test('should not trigger rendered event when disable lazy-render', async () => {
await later();
expect(onRendered).toHaveBeenCalledTimes(0);
});
test('before-change prop', async () => {
const onChange = jest.fn();
const wrapper = mount({
template: `
<van-tabs @change="onChange" :before-change="beforeChange">
<van-tab title="title1">Text</van-tab>
<van-tab title="title2">Text</van-tab>
<van-tab title="title3">Text</van-tab>
<van-tab title="title4">Text</van-tab>
<van-tab title="title5">Text</van-tab>
</van-tabs>
`,
methods: {
onChange,
beforeChange(name) {
switch (name) {
case 1:
return false;
case 2:
return true;
case 3:
return Promise.resolve(false);
case 4:
return Promise.resolve(true);
}
},
},
});
await later();
const tabs = wrapper.findAll('.van-tab');
tabs.at(1).trigger('click');
expect(onChange).toHaveBeenCalledTimes(0);
tabs.at(2).trigger('click');
expect(onChange).toHaveBeenCalledTimes(1);
expect(onChange).toHaveBeenLastCalledWith(2, 'title3');
tabs.at(3).trigger('click');
expect(onChange).toHaveBeenCalledTimes(1);
tabs.at(4).trigger('click');
await later();
expect(onChange).toHaveBeenCalledTimes(2);
expect(onChange).toHaveBeenLastCalledWith(4, 'title5');
});

View File

@ -1,5 +1,5 @@
// Utils
import { createNamespace, isDef, addUnit } from '../utils';
import { createNamespace, isDef, addUnit, isPromise } from '../utils';
import { scrollLeftTo, scrollTopTo } from './utils';
import { route } from '../utils/router';
import { isHidden } from '../utils/dom/style';
@ -53,6 +53,7 @@ export default createComponent({
background: String,
lineWidth: [Number, String],
lineHeight: [Number, String],
beforeChange: Function,
titleActiveColor: String,
titleInactiveColor: String,
type: {
@ -266,14 +267,34 @@ export default createComponent({
}
},
callBeforeChange(name, done) {
if (this.beforeChange) {
const returnVal = this.beforeChange(name);
if (isPromise(returnVal)) {
returnVal.then((value) => {
if (value) {
done();
}
});
} else if (returnVal) {
done();
}
} else {
done();
}
},
// emit event when clicked
onClick(item, index) {
const { title, disabled, computedName } = this.children[index];
if (disabled) {
this.$emit('disabled', computedName, title);
} else {
this.setCurrentIndex(index);
this.scrollToCurrentContent();
this.callBeforeChange(computedName, () => {
this.setCurrentIndex(index);
this.scrollToCurrentContent();
});
this.$emit('click', computedName, title);
route(item.$router, item);
}