mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-26 11:26:35 +08:00
[new feature] Tabs: support switch tabs with swipe gestrue in the content (#694)
This commit is contained in:
parent
f13a0779fd
commit
5cdf3b1d79
@ -58,6 +58,14 @@
|
|||||||
</van-tab>
|
</van-tab>
|
||||||
</van-tabs>
|
</van-tabs>
|
||||||
</demo-block>
|
</demo-block>
|
||||||
|
|
||||||
|
<demo-block :title="$t('title8')">
|
||||||
|
<van-tabs :active="active" swipeable>
|
||||||
|
<van-tab :title="$t('tab') + index" v-for="index in tabs" :key="index">
|
||||||
|
{{ $t('content') }} {{ index }}
|
||||||
|
</van-tab>
|
||||||
|
</van-tabs>
|
||||||
|
</demo-block>
|
||||||
</demo-section>
|
</demo-section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -71,7 +79,8 @@ export default {
|
|||||||
title4: '样式风格',
|
title4: '样式风格',
|
||||||
title5: '点击事件',
|
title5: '点击事件',
|
||||||
title6: '粘性布局',
|
title6: '粘性布局',
|
||||||
title7: '自定义标签'
|
title7: '自定义标签',
|
||||||
|
title8: '滑动切换'
|
||||||
},
|
},
|
||||||
'en-US': {
|
'en-US': {
|
||||||
tab: 'Tab ',
|
tab: 'Tab ',
|
||||||
@ -81,7 +90,8 @@ export default {
|
|||||||
title4: 'Card Style',
|
title4: 'Card Style',
|
||||||
title5: 'Click Event',
|
title5: 'Click Event',
|
||||||
title6: 'Sticky',
|
title6: 'Sticky',
|
||||||
title7: 'Custom Tab'
|
title7: 'Custom Tab',
|
||||||
|
title8: 'Swipeable'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -124,6 +124,18 @@ Use title slot to custom tab title
|
|||||||
</van-tabs>
|
</van-tabs>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Swipeable
|
||||||
|
|
||||||
|
In swipeable mode, you can switch tabs with swipe gestrue in the content
|
||||||
|
|
||||||
|
```html
|
||||||
|
<van-tabs :active="active" swipeable>
|
||||||
|
<van-tab v-for="index in 4" :title="'tab ' + index">
|
||||||
|
content {{ index }}
|
||||||
|
</van-tab>
|
||||||
|
</van-tabs>
|
||||||
|
```
|
||||||
|
|
||||||
### Tabs API
|
### Tabs API
|
||||||
|
|
||||||
| Attribute | Description | Type | Default | Accepted Values |
|
| Attribute | Description | Type | Default | Accepted Values |
|
||||||
@ -132,6 +144,8 @@ Use title slot to custom tab title
|
|||||||
| active | Index of active tab | `String` `Number` | `0` | - |
|
| active | Index of active tab | `String` `Number` | `0` | - |
|
||||||
| duration | Toggle tab's animation time | `Number` | `0.2` | - | - |
|
| duration | Toggle tab's animation time | `Number` | `0.2` | - | - |
|
||||||
| swipe-threshold | Set swipe tabs threshold | `Number` | `4` | - | - |
|
| swipe-threshold | Set swipe tabs threshold | `Number` | `4` | - | - |
|
||||||
|
| sticky | Whether to use sticky mode | `Boolean` | `false` | - |
|
||||||
|
| swipeable | Whether to switch tabs with swipe gestrue in the content | `Boolean` | `false` | - |
|
||||||
|
|
||||||
### Tab API
|
### Tab API
|
||||||
|
|
||||||
|
@ -124,6 +124,18 @@ export default {
|
|||||||
</van-tabs>
|
</van-tabs>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 滑动切换
|
||||||
|
|
||||||
|
通过`swipeable`属性可以开启滑动切换tab
|
||||||
|
|
||||||
|
```html
|
||||||
|
<van-tabs :active="active" swipeable>
|
||||||
|
<van-tab v-for="index in 4" :title="'选项 ' + index">
|
||||||
|
内容 {{ index }}
|
||||||
|
</van-tab>
|
||||||
|
</van-tabs>
|
||||||
|
```
|
||||||
|
|
||||||
### Tabs API
|
### Tabs API
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 默认值 | 可选 |
|
| 参数 | 说明 | 类型 | 默认值 | 可选 |
|
||||||
@ -133,6 +145,7 @@ export default {
|
|||||||
| duration | 切换 tab 的动画时间 | `Number` | `0.2` | - |
|
| duration | 切换 tab 的动画时间 | `Number` | `0.2` | - |
|
||||||
| swipe-threshold | 滚动阀值,设置 Tab 超过多少个可滚动 | `Number` | `4` | - |
|
| swipe-threshold | 滚动阀值,设置 Tab 超过多少个可滚动 | `Number` | `4` | - |
|
||||||
| sticky | 是否使用粘性定位布局 | `Boolean` | `false` | - |
|
| sticky | 是否使用粘性定位布局 | `Boolean` | `false` | - |
|
||||||
|
| swipeable | 是否可以滑动内容切换 | `Boolean` | `false` | - |
|
||||||
|
|
||||||
### Tab API
|
### Tab API
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ export default create({
|
|||||||
onInput(event) {
|
onInput(event) {
|
||||||
const { value } = event.target;
|
const { value } = event.target;
|
||||||
this.currentValue = value ? this.correctValue(+value) : value;
|
this.currentValue = value ? this.correctValue(+value) : value;
|
||||||
|
event.target.value = this.currentValue;
|
||||||
this.emitInput();
|
this.emitInput();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="van-tabs__content">
|
<div class="van-tabs__content" ref="content">
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -63,7 +63,8 @@ export default create({
|
|||||||
swipeThreshold: {
|
swipeThreshold: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 4
|
default: 4
|
||||||
}
|
},
|
||||||
|
swipeable: Boolean
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -71,7 +72,11 @@ export default create({
|
|||||||
tabs: [],
|
tabs: [],
|
||||||
position: 'content-top',
|
position: 'content-top',
|
||||||
curActive: 0,
|
curActive: 0,
|
||||||
navBarStyle: {}
|
navBarStyle: {},
|
||||||
|
pos: {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -115,6 +120,9 @@ export default create({
|
|||||||
if (this.sticky) {
|
if (this.sticky) {
|
||||||
this.scrollHandler(true);
|
this.scrollHandler(true);
|
||||||
}
|
}
|
||||||
|
if (this.swipeable) {
|
||||||
|
this.swipeableHandler(true);
|
||||||
|
}
|
||||||
this.scrollIntoView();
|
this.scrollIntoView();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -124,6 +132,10 @@ export default create({
|
|||||||
if (this.sticky) {
|
if (this.sticky) {
|
||||||
this.scrollHandler(false);
|
this.scrollHandler(false);
|
||||||
}
|
}
|
||||||
|
/* istanbul ignore next */
|
||||||
|
if (this.swipeable) {
|
||||||
|
this.swipeableHandler(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
@ -136,6 +148,46 @@ export default create({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// whether to bind content swipe listener
|
||||||
|
swipeableHandler(init) {
|
||||||
|
const swipeableEl = this.$refs.content;
|
||||||
|
|
||||||
|
this.touchMoveHandler = scrollUtils.debounce(this.watchTouchMove.bind(this), 500);
|
||||||
|
|
||||||
|
(init ? on : off)(swipeableEl, 'touchstart', this.recordTouchStartPosition, true);
|
||||||
|
(init ? on : off)(swipeableEl, 'touchmove', this.touchMoveHandler, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
// record swipe touch start position
|
||||||
|
recordTouchStartPosition(e) {
|
||||||
|
this.pos = {
|
||||||
|
x: e.touches[0].clientX,
|
||||||
|
y: e.touches[0].clientY
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
// watch swipe touch move
|
||||||
|
watchTouchMove(e) {
|
||||||
|
const { pos } = this;
|
||||||
|
const dx = e.touches[0].clientX - pos.x;
|
||||||
|
const dy = e.touches[0].clientY - pos.y;
|
||||||
|
const isForward = dx > 0;
|
||||||
|
const isHorizontal = Math.abs(dy) < Math.abs(dx);
|
||||||
|
const minSwipeDistance = 50;
|
||||||
|
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (isHorizontal && Math.abs(dx) >= minSwipeDistance) {
|
||||||
|
const active = +this.curActive;
|
||||||
|
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (isForward && active !== 0) {
|
||||||
|
this.curActive = active - 1;
|
||||||
|
} else if (!isForward && active !== this.tabs.length - 1) {
|
||||||
|
this.curActive = active + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// adjust tab position
|
// adjust tab position
|
||||||
onScroll() {
|
onScroll() {
|
||||||
const scrollTop = scrollUtils.getScrollTop(this.scrollEl);
|
const scrollTop = scrollUtils.getScrollTop(this.scrollEl);
|
||||||
|
BIN
packages/vant-css/src/vant-icon-743c0e.ttf
Normal file
BIN
packages/vant-css/src/vant-icon-743c0e.ttf
Normal file
Binary file not shown.
@ -1,5 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<van-tabs :active="active" :duration="duration" @click="handleTabClick" @disabled="handleTabDisabledClick" :sticky="sticky">
|
<van-tabs
|
||||||
|
:active="active"
|
||||||
|
:duration="duration"
|
||||||
|
:sticky="sticky"
|
||||||
|
:swipeable="swipeable"
|
||||||
|
@click="handleTabClick"
|
||||||
|
@disabled="handleTabDisabledClick"
|
||||||
|
>
|
||||||
<van-tab :title="firstTabTitle" :disabled="firstTabDisabled">内容一</van-tab>
|
<van-tab :title="firstTabTitle" :disabled="firstTabDisabled">内容一</van-tab>
|
||||||
<van-tab title="选项二">内容二</van-tab>
|
<van-tab title="选项二">内容二</van-tab>
|
||||||
<van-tab title="选项三" disabled>内容三</van-tab>
|
<van-tab title="选项三" disabled>内容三</van-tab>
|
||||||
@ -17,7 +24,8 @@ export default {
|
|||||||
firstTabDisabled: {
|
firstTabDisabled: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
},
|
},
|
||||||
sticky: Boolean
|
sticky: Boolean,
|
||||||
|
swipeable: Boolean
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
|
@ -2,6 +2,7 @@ import Tabs from 'packages/tabs';
|
|||||||
import { mount } from 'avoriaz';
|
import { mount } from 'avoriaz';
|
||||||
import TabsTestComponent from '../components/tabs';
|
import TabsTestComponent from '../components/tabs';
|
||||||
import MoreTabsTestComponent from '../components/more-tabs';
|
import MoreTabsTestComponent from '../components/more-tabs';
|
||||||
|
import { triggerTouch } from '../utils';
|
||||||
|
|
||||||
describe('Tabs', () => {
|
describe('Tabs', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
@ -114,7 +115,7 @@ describe('Tabs', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('sticky', (done) => {
|
it('create a sticky tabs', (done) => {
|
||||||
wrapper = mount(TabsTestComponent, {
|
wrapper = mount(TabsTestComponent, {
|
||||||
attachToDocument: true,
|
attachToDocument: true,
|
||||||
propsData: {
|
propsData: {
|
||||||
@ -129,4 +130,34 @@ describe('Tabs', () => {
|
|||||||
done();
|
done();
|
||||||
}, 30);
|
}, 30);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('create a swipeable tabs', (done) => {
|
||||||
|
wrapper = mount(TabsTestComponent, {
|
||||||
|
attachToDocument: true,
|
||||||
|
propsData: {
|
||||||
|
swipeable: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabsContainer = wrapper.find('.van-tabs')[0];
|
||||||
|
const tabContent = wrapper.find('.van-tabs__content')[0];
|
||||||
|
|
||||||
|
expect(tabsContainer.vNode.child.curActive).to.equal(0);
|
||||||
|
|
||||||
|
wrapper.vm.$nextTick(() => {
|
||||||
|
triggerTouch(tabContent, 'touchstart', 0, 0);
|
||||||
|
triggerTouch(tabContent, 'touchmove', -100, 0);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(tabsContainer.vNode.child.curActive).to.equal(1);
|
||||||
|
|
||||||
|
triggerTouch(tabContent, 'touchstart', 0, 0);
|
||||||
|
triggerTouch(tabContent, 'touchmove', 100, 0);
|
||||||
|
setTimeout(() => {
|
||||||
|
expect(tabsContainer.vNode.child.curActive).to.equal(0);
|
||||||
|
done();
|
||||||
|
}, 500);
|
||||||
|
}, 500);
|
||||||
|
})
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user