mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
feat: migrate Tab component
This commit is contained in:
parent
4ad0c396ef
commit
d2291f3710
@ -4,8 +4,9 @@
|
||||
|
||||
以下改动是为了适配 Vue 3 的 v-model API 用法变更:
|
||||
|
||||
- Circle: `v-model` 重命名为 `v-model:currentRate`
|
||||
- Tabs: `v-model` 重命名为 `v-model:active`
|
||||
- Popup: `v-model` 重命名为 `v-model:show`
|
||||
- Circle: `v-model` 重命名为 `v-model:currentRate`
|
||||
- ShareSheet: `v-model` 重命名为 `v-model:show`
|
||||
- ActionSheet: `v-model` 重命名为 `v-model:show`
|
||||
- List: `v-model` 重命名为 `v-model:loading`,`error.sync` 重命名为 `v-model:error`
|
||||
|
@ -40,4 +40,7 @@ module.exports = [
|
||||
'tabbar',
|
||||
'tabbar-item',
|
||||
'list',
|
||||
'tab',
|
||||
'tabs',
|
||||
'sticky',
|
||||
];
|
||||
|
@ -14,10 +14,10 @@ Vue.use(Tabs);
|
||||
|
||||
### Basic Usage
|
||||
|
||||
The first tab is actived by default, you can set `v-model` to active specified tab.
|
||||
The first tab is actived by default, you can set `v-model:active` to active specified tab.
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active">
|
||||
<van-tabs v-model:active="active">
|
||||
<van-tab v-for="index in 4" :title="'tab' + index">
|
||||
content of tab {{ index }}
|
||||
</van-tab>
|
||||
@ -37,7 +37,7 @@ export default {
|
||||
### Match By Name
|
||||
|
||||
```html
|
||||
<van-tabs v-model="activeName">
|
||||
<van-tabs v-model:active="activeName">
|
||||
<van-tab title="tab 1" name="a">content of tab 1</van-tab>
|
||||
<van-tab title="tab 2" name="b">content of tab 2</van-tab>
|
||||
<van-tab title="tab 3" name="c">content of tab 3</van-tab>
|
||||
@ -129,7 +129,7 @@ export default {
|
||||
In sticky mode, the tab will be fixed to top when scroll to top.
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" sticky>
|
||||
<van-tabs v-model:active="active" sticky>
|
||||
<van-tab v-for="index in 4" :title="'tab ' + index">
|
||||
content {{ index }}
|
||||
</van-tab>
|
||||
@ -141,7 +141,7 @@ In sticky mode, the tab will be fixed to top when scroll to top.
|
||||
Use title slot to custom tab title.
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active">
|
||||
<van-tabs v-model:active="active">
|
||||
<van-tab v-for="index in 2" :key="index">
|
||||
<template #title> <van-icon name="more-o" />tab </template>
|
||||
content {{ index }}
|
||||
@ -154,7 +154,7 @@ Use title slot to custom tab title.
|
||||
Use `animated` props to change tabs with animation.
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" animated>
|
||||
<van-tabs v-model:active="active" animated>
|
||||
<van-tab v-for="index in 4" :title="'tab ' + index">
|
||||
content {{ index }}
|
||||
</van-tab>
|
||||
@ -166,7 +166,7 @@ Use `animated` props to change tabs with animation.
|
||||
In swipeable mode, you can switch tabs with swipe gestrue in the content.
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" swipeable>
|
||||
<van-tabs v-model:active="active" swipeable>
|
||||
<van-tab v-for="index in 4" :title="'tab ' + index">
|
||||
content {{ index }}
|
||||
</van-tab>
|
||||
@ -178,7 +178,7 @@ In swipeable mode, you can switch tabs with swipe gestrue in the content.
|
||||
In scrollspy mode, the list of content will be tiled.
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" scrollspy sticky>
|
||||
<van-tabs v-model:active="active" scrollspy sticky>
|
||||
<van-tab v-for="index in 8" :title="'tab ' + index">
|
||||
content {{ index }}
|
||||
</van-tab>
|
||||
@ -219,7 +219,7 @@ export default {
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model | Index of active tab | _number \| string_ | `0` |
|
||||
| v-model:active | Index of active tab | _number \| string_ | `0` |
|
||||
| type | Can be set to `line` `card` | _string_ | `line` |
|
||||
| color | Tab color | _string_ | `#ee0a24` |
|
||||
| background | Background color | _string_ | `white` |
|
||||
|
@ -14,10 +14,10 @@ Vue.use(Tabs);
|
||||
|
||||
### 基础用法
|
||||
|
||||
通过 `v-model` 绑定当前激活标签对应的索引值,默认情况下启用第一个标签。
|
||||
通过 `v-model:active` 绑定当前激活标签对应的索引值,默认情况下启用第一个标签。
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active">
|
||||
<van-tabs v-model:active="active">
|
||||
<van-tab title="标签 1">内容 1</van-tab>
|
||||
<van-tab title="标签 2">内容 2</van-tab>
|
||||
<van-tab title="标签 3">内容 3</van-tab>
|
||||
@ -37,10 +37,10 @@ export default {
|
||||
|
||||
### 通过名称匹配
|
||||
|
||||
在标签指定 `name` 属性的情况下,`v-model` 的值为当前标签的 `name`(此时无法通过索引值来匹配标签)。
|
||||
在标签指定 `name` 属性的情况下,`v-model:active` 的值为当前标签的 `name`(此时无法通过索引值来匹配标签)。
|
||||
|
||||
```html
|
||||
<van-tabs v-model="activeName">
|
||||
<van-tabs v-model:active="activeName">
|
||||
<van-tab title="标签 1" name="a">内容 1</van-tab>
|
||||
<van-tab title="标签 2" name="b">内容 2</van-tab>
|
||||
<van-tab title="标签 3" name="c">内容 3</van-tab>
|
||||
@ -133,7 +133,7 @@ export default {
|
||||
通过 `sticky` 属性可以开启粘性布局,粘性布局下,标签页滚动到顶部时会自动吸顶。
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" sticky>
|
||||
<van-tabs v-model:active="active" sticky>
|
||||
<van-tab v-for="index in 4" :title="'选项 ' + index">
|
||||
内容 {{ index }}
|
||||
</van-tab>
|
||||
@ -145,7 +145,7 @@ export default {
|
||||
通过 `title` 插槽可以自定义标签内容。
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active">
|
||||
<van-tabs v-model:active="active">
|
||||
<van-tab v-for="index in 2" :key="index">
|
||||
<template #title> <van-icon name="more-o" />选项 </template>
|
||||
内容 {{ index }}
|
||||
@ -158,7 +158,7 @@ export default {
|
||||
通过 `animated` 属性可以开启切换标签内容时的转场动画。
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" animated>
|
||||
<van-tabs v-model:active="active" animated>
|
||||
<van-tab v-for="index in 4" :title="'选项 ' + index">
|
||||
内容 {{ index }}
|
||||
</van-tab>
|
||||
@ -170,7 +170,7 @@ export default {
|
||||
通过 `swipeable` 属性可以开启滑动切换标签页。
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" swipeable>
|
||||
<van-tabs v-model:active="active" swipeable>
|
||||
<van-tab v-for="index in 4" :title="'选项 ' + index">
|
||||
内容 {{ index }}
|
||||
</van-tab>
|
||||
@ -182,7 +182,7 @@ export default {
|
||||
通过 `scrollspy` 属性可以开启滚动导航模式,该模式下,内容将会平铺展示。
|
||||
|
||||
```html
|
||||
<van-tabs v-model="active" scrollspy sticky>
|
||||
<van-tabs v-model:active="active" scrollspy sticky>
|
||||
<van-tab v-for="index in 8" :title="'选项 ' + index">
|
||||
内容 {{ index }}
|
||||
</van-tab>
|
||||
@ -226,7 +226,7 @@ export default {
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| v-model | 绑定当前选中标签的标识符 | _number \| string_ | `0` |
|
||||
| v-model:active | 绑定当前选中标签的标识符 | _number \| string_ | `0` |
|
||||
| type | 样式风格类型,可选值为`card` | _string_ | `line` |
|
||||
| color | 标签主题色 | _string_ | `#ee0a24` |
|
||||
| background | 标签栏背景色 | _string_ | `white` |
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="t('basicUsage')">
|
||||
<van-tabs v-model="active">
|
||||
<van-tabs v-model:active="active">
|
||||
<van-tab :title="t('tab') + index" v-for="index in tabs" :key="index">
|
||||
{{ t('content') }} {{ index }}
|
||||
</van-tab>
|
||||
@ -9,7 +9,7 @@
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="t('matchByName')">
|
||||
<van-tabs v-model="activeName">
|
||||
<van-tabs v-model:active="activeName">
|
||||
<van-tab name="a" :title="t('tab') + 1"> {{ t('content') }} 1 </van-tab>
|
||||
<van-tab name="b" :title="t('tab') + 2"> {{ t('content') }} 2 </van-tab>
|
||||
<van-tab name="c" :title="t('tab') + 3"> {{ t('content') }} 3 </van-tab>
|
||||
|
@ -53,11 +53,11 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const { slots, parent, isActive } = this;
|
||||
render() {
|
||||
const { parent, isActive } = this;
|
||||
const shouldRender = this.inited || parent.scrollspy || !parent.lazyRender;
|
||||
const show = parent.scrollspy || isActive;
|
||||
const Content = shouldRender ? slots() : h();
|
||||
const Content = shouldRender ? this.$slots.default?.() : null;
|
||||
|
||||
if (parent.animated) {
|
||||
return (
|
||||
|
@ -15,6 +15,8 @@ export default createComponent({
|
||||
currentIndex: Number,
|
||||
},
|
||||
|
||||
emits: ['change'],
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
if (this.animated) {
|
||||
@ -28,10 +30,10 @@ export default createComponent({
|
||||
listeners() {
|
||||
if (this.swipeable) {
|
||||
return {
|
||||
touchstart: this.touchStart,
|
||||
touchmove: this.touchMove,
|
||||
touchend: this.onTouchEnd,
|
||||
touchcancel: this.onTouchEnd,
|
||||
onTouchstart: this.touchStart,
|
||||
onTouchmove: this.touchMove,
|
||||
onTouchend: this.onTouchEnd,
|
||||
onTouchcancel: this.onTouchEnd,
|
||||
};
|
||||
}
|
||||
},
|
||||
@ -54,15 +56,16 @@ export default createComponent({
|
||||
},
|
||||
|
||||
genChildren() {
|
||||
const Content = this.$slots.default?.();
|
||||
if (this.animated) {
|
||||
return (
|
||||
<div class={bem('track')} style={this.style}>
|
||||
{this.slots()}
|
||||
{Content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.slots();
|
||||
return Content;
|
||||
},
|
||||
},
|
||||
|
||||
@ -70,7 +73,7 @@ export default createComponent({
|
||||
return (
|
||||
<div
|
||||
class={bem('content', { animated: this.animated })}
|
||||
{...{ on: this.listeners }}
|
||||
{...this.listeners}
|
||||
>
|
||||
{this.genChildren()}
|
||||
</div>
|
||||
|
@ -15,6 +15,7 @@ export default createComponent({
|
||||
disabled: Boolean,
|
||||
scrollable: Boolean,
|
||||
activeColor: String,
|
||||
renderTitle: Function,
|
||||
inactiveColor: String,
|
||||
swipeThreshold: [Number, String],
|
||||
},
|
||||
@ -59,7 +60,7 @@ export default createComponent({
|
||||
genText() {
|
||||
const Text = (
|
||||
<span class={bem('text', { ellipsis: this.ellipsis })}>
|
||||
{this.slots() || this.title}
|
||||
{this.renderTitle ? this.renderTitle() : this.title}
|
||||
</span>
|
||||
);
|
||||
|
||||
|
@ -40,10 +40,6 @@ export default createComponent({
|
||||
}),
|
||||
],
|
||||
|
||||
model: {
|
||||
prop: 'active',
|
||||
},
|
||||
|
||||
props: {
|
||||
color: String,
|
||||
sticky: Boolean,
|
||||
@ -90,7 +86,11 @@ export default createComponent({
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['rendered', 'input', 'change', 'disabled', 'click', 'scroll'],
|
||||
|
||||
data() {
|
||||
this.titleRefs = [];
|
||||
|
||||
return {
|
||||
position: '',
|
||||
currentIndex: null,
|
||||
@ -194,18 +194,18 @@ export default createComponent({
|
||||
const shouldAnimate = this.inited;
|
||||
|
||||
this.$nextTick(() => {
|
||||
const { titles } = this.$refs;
|
||||
const { titleRefs } = this;
|
||||
|
||||
if (
|
||||
!titles ||
|
||||
!titles[this.currentIndex] ||
|
||||
!titleRefs ||
|
||||
!titleRefs[this.currentIndex] ||
|
||||
this.type !== 'line' ||
|
||||
isHidden(this.$el)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = titles[this.currentIndex].$el;
|
||||
const title = titleRefs[this.currentIndex].$el;
|
||||
const { lineWidth, lineHeight } = this;
|
||||
const width = isDef(lineWidth) ? lineWidth : title.offsetWidth / 2;
|
||||
const left = title.offsetLeft + title.offsetWidth / 2;
|
||||
@ -302,14 +302,14 @@ export default createComponent({
|
||||
|
||||
// scroll active tab into view
|
||||
scrollIntoView(immediate) {
|
||||
const { titles } = this.$refs;
|
||||
const { titleRefs } = this;
|
||||
|
||||
if (!this.scrollable || !titles || !titles[this.currentIndex]) {
|
||||
if (!this.scrollable || !titleRefs || !titleRefs[this.currentIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { nav } = this.$refs;
|
||||
const title = titles[this.currentIndex].$el;
|
||||
const title = titleRefs[this.currentIndex].$el;
|
||||
const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
|
||||
|
||||
scrollLeftTo(nav, to, immediate ? 0 : +this.duration);
|
||||
@ -369,31 +369,32 @@ export default createComponent({
|
||||
render() {
|
||||
const { type, ellipsis, animated, scrollable } = this;
|
||||
|
||||
const Nav = this.children.map((item, index) => (
|
||||
<Title
|
||||
ref="titles"
|
||||
refInFor
|
||||
type={type}
|
||||
dot={item.dot}
|
||||
info={isDef(item.badge) ? item.badge : item.info}
|
||||
title={item.title}
|
||||
color={this.color}
|
||||
style={item.titleStyle}
|
||||
isActive={index === this.currentIndex}
|
||||
ellipsis={ellipsis}
|
||||
disabled={item.disabled}
|
||||
scrollable={scrollable}
|
||||
activeColor={this.titleActiveColor}
|
||||
inactiveColor={this.titleInactiveColor}
|
||||
swipeThreshold={this.swipeThreshold}
|
||||
scopedSlots={{
|
||||
default: () => item.slots('title'),
|
||||
}}
|
||||
onClick={() => {
|
||||
this.onClick(item, index);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
const Nav = this.children.map((item, index) => {
|
||||
return (
|
||||
<Title
|
||||
ref={(val) => {
|
||||
this.titleRefs[index] = val;
|
||||
}}
|
||||
dot={item.dot}
|
||||
type={type}
|
||||
info={isDef(item.badge) ? item.badge : item.info}
|
||||
title={item.title}
|
||||
color={this.color}
|
||||
style={item.titleStyle}
|
||||
isActive={index === this.currentIndex}
|
||||
ellipsis={ellipsis}
|
||||
disabled={item.disabled}
|
||||
scrollable={scrollable}
|
||||
renderTitle={item.$slots.title}
|
||||
activeColor={this.titleActiveColor}
|
||||
inactiveColor={this.titleInactiveColor}
|
||||
swipeThreshold={this.swipeThreshold}
|
||||
onClick={() => {
|
||||
this.onClick(item, index);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
const Wrap = (
|
||||
<div
|
||||
@ -409,12 +410,12 @@ export default createComponent({
|
||||
class={bem('nav', [type])}
|
||||
style={this.navStyle}
|
||||
>
|
||||
{this.slots('nav-left')}
|
||||
{this.$slots['nav-left']?.()}
|
||||
{Nav}
|
||||
{type === 'line' && (
|
||||
<div class={bem('line')} style={this.lineStyle} />
|
||||
)}
|
||||
{this.slots('nav-right')}
|
||||
{this.$slots['nav-right']?.()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -440,7 +441,7 @@ export default createComponent({
|
||||
currentIndex={this.currentIndex}
|
||||
onChange={this.setCurrentIndex}
|
||||
>
|
||||
{this.slots()}
|
||||
{this.$slots.default?.()}
|
||||
</Content>
|
||||
</div>
|
||||
);
|
||||
|
@ -269,10 +269,10 @@ module.exports = {
|
||||
path: 'steps',
|
||||
title: 'Steps 步骤条',
|
||||
},
|
||||
// {
|
||||
// path: 'sticky',
|
||||
// title: 'Sticky 粘性布局',
|
||||
// },
|
||||
{
|
||||
path: 'sticky',
|
||||
title: 'Sticky 粘性布局',
|
||||
},
|
||||
// {
|
||||
// path: 'swipe',
|
||||
// title: 'Swipe 轮播',
|
||||
@ -306,10 +306,10 @@ module.exports = {
|
||||
path: 'sidebar',
|
||||
title: 'Sidebar 侧边导航',
|
||||
},
|
||||
// {
|
||||
// path: 'tab',
|
||||
// title: 'Tab 标签页',
|
||||
// },
|
||||
{
|
||||
path: 'tab',
|
||||
title: 'Tab 标签页',
|
||||
},
|
||||
{
|
||||
path: 'tabbar',
|
||||
title: 'Tabbar 标签栏',
|
||||
@ -603,10 +603,10 @@ module.exports = {
|
||||
path: 'steps',
|
||||
title: 'Steps',
|
||||
},
|
||||
// {
|
||||
// path: 'sticky',
|
||||
// title: 'Sticky',
|
||||
// },
|
||||
{
|
||||
path: 'sticky',
|
||||
title: 'Sticky',
|
||||
},
|
||||
// {
|
||||
// path: 'swipe',
|
||||
// title: 'Swipe',
|
||||
@ -640,10 +640,10 @@ module.exports = {
|
||||
path: 'sidebar',
|
||||
title: 'Sidebar',
|
||||
},
|
||||
// {
|
||||
// path: 'tab',
|
||||
// title: 'Tab',
|
||||
// },
|
||||
{
|
||||
path: 'tab',
|
||||
title: 'Tab',
|
||||
},
|
||||
{
|
||||
path: 'tabbar',
|
||||
title: 'Tabbar',
|
||||
|
Loading…
x
Reference in New Issue
Block a user