feat: migrate Tab component

This commit is contained in:
chenjiahan 2020-07-26 15:29:52 +08:00
parent 4ad0c396ef
commit d2291f3710
10 changed files with 97 additions and 88 deletions

View File

@ -4,8 +4,9 @@
以下改动是为了适配 Vue 3 的 v-model API 用法变更: 以下改动是为了适配 Vue 3 的 v-model API 用法变更:
- Circle: `v-model` 重命名为 `v-model:currentRate` - Tabs: `v-model` 重命名为 `v-model:active`
- Popup: `v-model` 重命名为 `v-model:show` - Popup: `v-model` 重命名为 `v-model:show`
- Circle: `v-model` 重命名为 `v-model:currentRate`
- ShareSheet: `v-model` 重命名为 `v-model:show` - ShareSheet: `v-model` 重命名为 `v-model:show`
- ActionSheet: `v-model` 重命名为 `v-model:show` - ActionSheet: `v-model` 重命名为 `v-model:show`
- List: `v-model` 重命名为 `v-model:loading``error.sync` 重命名为 `v-model:error` - List: `v-model` 重命名为 `v-model:loading``error.sync` 重命名为 `v-model:error`

View File

@ -40,4 +40,7 @@ module.exports = [
'tabbar', 'tabbar',
'tabbar-item', 'tabbar-item',
'list', 'list',
'tab',
'tabs',
'sticky',
]; ];

View File

@ -14,10 +14,10 @@ Vue.use(Tabs);
### Basic Usage ### 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 ```html
<van-tabs v-model="active"> <van-tabs v-model:active="active">
<van-tab v-for="index in 4" :title="'tab' + index"> <van-tab v-for="index in 4" :title="'tab' + index">
content of tab {{ index }} content of tab {{ index }}
</van-tab> </van-tab>
@ -37,7 +37,7 @@ export default {
### Match By Name ### Match By Name
```html ```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 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 2" name="b">content of tab 2</van-tab>
<van-tab title="tab 3" name="c">content of tab 3</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. In sticky mode, the tab will be fixed to top when scroll to top.
```html ```html
<van-tabs v-model="active" sticky> <van-tabs v-model:active="active" sticky>
<van-tab v-for="index in 4" :title="'tab ' + index"> <van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }} content {{ index }}
</van-tab> </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. Use title slot to custom tab title.
```html ```html
<van-tabs v-model="active"> <van-tabs v-model:active="active">
<van-tab v-for="index in 2" :key="index"> <van-tab v-for="index in 2" :key="index">
<template #title> <van-icon name="more-o" />tab </template> <template #title> <van-icon name="more-o" />tab </template>
content {{ index }} content {{ index }}
@ -154,7 +154,7 @@ Use title slot to custom tab title.
Use `animated` props to change tabs with animation. Use `animated` props to change tabs with animation.
```html ```html
<van-tabs v-model="active" animated> <van-tabs v-model:active="active" animated>
<van-tab v-for="index in 4" :title="'tab ' + index"> <van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }} content {{ index }}
</van-tab> </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. In swipeable mode, you can switch tabs with swipe gestrue in the content.
```html ```html
<van-tabs v-model="active" swipeable> <van-tabs v-model:active="active" swipeable>
<van-tab v-for="index in 4" :title="'tab ' + index"> <van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }} content {{ index }}
</van-tab> </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. In scrollspy mode, the list of content will be tiled.
```html ```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"> <van-tab v-for="index in 8" :title="'tab ' + index">
content {{ index }} content {{ index }}
</van-tab> </van-tab>
@ -219,7 +219,7 @@ export default {
| Attribute | Description | Type | 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` | | type | Can be set to `line` `card` | _string_ | `line` |
| color | Tab color | _string_ | `#ee0a24` | | color | Tab color | _string_ | `#ee0a24` |
| background | Background color | _string_ | `white` | | background | Background color | _string_ | `white` |

View File

@ -14,10 +14,10 @@ Vue.use(Tabs);
### 基础用法 ### 基础用法
通过 `v-model` 绑定当前激活标签对应的索引值,默认情况下启用第一个标签。 通过 `v-model:active` 绑定当前激活标签对应的索引值,默认情况下启用第一个标签。
```html ```html
<van-tabs v-model="active"> <van-tabs v-model:active="active">
<van-tab title="标签 1">内容 1</van-tab> <van-tab title="标签 1">内容 1</van-tab>
<van-tab title="标签 2">内容 2</van-tab> <van-tab title="标签 2">内容 2</van-tab>
<van-tab title="标签 3">内容 3</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 ```html
<van-tabs v-model="activeName"> <van-tabs v-model:active="activeName">
<van-tab title="标签 1" name="a">内容 1</van-tab> <van-tab title="标签 1" name="a">内容 1</van-tab>
<van-tab title="标签 2" name="b">内容 2</van-tab> <van-tab title="标签 2" name="b">内容 2</van-tab>
<van-tab title="标签 3" name="c">内容 3</van-tab> <van-tab title="标签 3" name="c">内容 3</van-tab>
@ -133,7 +133,7 @@ export default {
通过 `sticky` 属性可以开启粘性布局,粘性布局下,标签页滚动到顶部时会自动吸顶。 通过 `sticky` 属性可以开启粘性布局,粘性布局下,标签页滚动到顶部时会自动吸顶。
```html ```html
<van-tabs v-model="active" sticky> <van-tabs v-model:active="active" sticky>
<van-tab v-for="index in 4" :title="'选项 ' + index"> <van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }} 内容 {{ index }}
</van-tab> </van-tab>
@ -145,7 +145,7 @@ export default {
通过 `title` 插槽可以自定义标签内容。 通过 `title` 插槽可以自定义标签内容。
```html ```html
<van-tabs v-model="active"> <van-tabs v-model:active="active">
<van-tab v-for="index in 2" :key="index"> <van-tab v-for="index in 2" :key="index">
<template #title> <van-icon name="more-o" />选项 </template> <template #title> <van-icon name="more-o" />选项 </template>
内容 {{ index }} 内容 {{ index }}
@ -158,7 +158,7 @@ export default {
通过 `animated` 属性可以开启切换标签内容时的转场动画。 通过 `animated` 属性可以开启切换标签内容时的转场动画。
```html ```html
<van-tabs v-model="active" animated> <van-tabs v-model:active="active" animated>
<van-tab v-for="index in 4" :title="'选项 ' + index"> <van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }} 内容 {{ index }}
</van-tab> </van-tab>
@ -170,7 +170,7 @@ export default {
通过 `swipeable` 属性可以开启滑动切换标签页。 通过 `swipeable` 属性可以开启滑动切换标签页。
```html ```html
<van-tabs v-model="active" swipeable> <van-tabs v-model:active="active" swipeable>
<van-tab v-for="index in 4" :title="'选项 ' + index"> <van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }} 内容 {{ index }}
</van-tab> </van-tab>
@ -182,7 +182,7 @@ export default {
通过 `scrollspy` 属性可以开启滚动导航模式,该模式下,内容将会平铺展示。 通过 `scrollspy` 属性可以开启滚动导航模式,该模式下,内容将会平铺展示。
```html ```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"> <van-tab v-for="index in 8" :title="'选项 ' + index">
内容 {{ index }} 内容 {{ index }}
</van-tab> </van-tab>
@ -226,7 +226,7 @@ export default {
| 参数 | 说明 | 类型 | 默认值 | | 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| v-model | 绑定当前选中标签的标识符 | _number \| string_ | `0` | | v-model:active | 绑定当前选中标签的标识符 | _number \| string_ | `0` |
| type | 样式风格类型,可选值为`card` | _string_ | `line` | | type | 样式风格类型,可选值为`card` | _string_ | `line` |
| color | 标签主题色 | _string_ | `#ee0a24` | | color | 标签主题色 | _string_ | `#ee0a24` |
| background | 标签栏背景色 | _string_ | `white` | | background | 标签栏背景色 | _string_ | `white` |

View File

@ -1,7 +1,7 @@
<template> <template>
<demo-section> <demo-section>
<demo-block :title="t('basicUsage')"> <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"> <van-tab :title="t('tab') + index" v-for="index in tabs" :key="index">
{{ t('content') }} {{ index }} {{ t('content') }} {{ index }}
</van-tab> </van-tab>
@ -9,7 +9,7 @@
</demo-block> </demo-block>
<demo-block :title="t('matchByName')"> <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="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="b" :title="t('tab') + 2"> {{ t('content') }} 2 </van-tab>
<van-tab name="c" :title="t('tab') + 3"> {{ t('content') }} 3 </van-tab> <van-tab name="c" :title="t('tab') + 3"> {{ t('content') }} 3 </van-tab>

View File

@ -53,11 +53,11 @@ export default createComponent({
}, },
}, },
render(h) { render() {
const { slots, parent, isActive } = this; const { parent, isActive } = this;
const shouldRender = this.inited || parent.scrollspy || !parent.lazyRender; const shouldRender = this.inited || parent.scrollspy || !parent.lazyRender;
const show = parent.scrollspy || isActive; const show = parent.scrollspy || isActive;
const Content = shouldRender ? slots() : h(); const Content = shouldRender ? this.$slots.default?.() : null;
if (parent.animated) { if (parent.animated) {
return ( return (

View File

@ -15,6 +15,8 @@ export default createComponent({
currentIndex: Number, currentIndex: Number,
}, },
emits: ['change'],
computed: { computed: {
style() { style() {
if (this.animated) { if (this.animated) {
@ -28,10 +30,10 @@ export default createComponent({
listeners() { listeners() {
if (this.swipeable) { if (this.swipeable) {
return { return {
touchstart: this.touchStart, onTouchstart: this.touchStart,
touchmove: this.touchMove, onTouchmove: this.touchMove,
touchend: this.onTouchEnd, onTouchend: this.onTouchEnd,
touchcancel: this.onTouchEnd, onTouchcancel: this.onTouchEnd,
}; };
} }
}, },
@ -54,15 +56,16 @@ export default createComponent({
}, },
genChildren() { genChildren() {
const Content = this.$slots.default?.();
if (this.animated) { if (this.animated) {
return ( return (
<div class={bem('track')} style={this.style}> <div class={bem('track')} style={this.style}>
{this.slots()} {Content}
</div> </div>
); );
} }
return this.slots(); return Content;
}, },
}, },
@ -70,7 +73,7 @@ export default createComponent({
return ( return (
<div <div
class={bem('content', { animated: this.animated })} class={bem('content', { animated: this.animated })}
{...{ on: this.listeners }} {...this.listeners}
> >
{this.genChildren()} {this.genChildren()}
</div> </div>

View File

@ -15,6 +15,7 @@ export default createComponent({
disabled: Boolean, disabled: Boolean,
scrollable: Boolean, scrollable: Boolean,
activeColor: String, activeColor: String,
renderTitle: Function,
inactiveColor: String, inactiveColor: String,
swipeThreshold: [Number, String], swipeThreshold: [Number, String],
}, },
@ -59,7 +60,7 @@ export default createComponent({
genText() { genText() {
const Text = ( const Text = (
<span class={bem('text', { ellipsis: this.ellipsis })}> <span class={bem('text', { ellipsis: this.ellipsis })}>
{this.slots() || this.title} {this.renderTitle ? this.renderTitle() : this.title}
</span> </span>
); );

View File

@ -40,10 +40,6 @@ export default createComponent({
}), }),
], ],
model: {
prop: 'active',
},
props: { props: {
color: String, color: String,
sticky: Boolean, sticky: Boolean,
@ -90,7 +86,11 @@ export default createComponent({
}, },
}, },
emits: ['rendered', 'input', 'change', 'disabled', 'click', 'scroll'],
data() { data() {
this.titleRefs = [];
return { return {
position: '', position: '',
currentIndex: null, currentIndex: null,
@ -194,18 +194,18 @@ export default createComponent({
const shouldAnimate = this.inited; const shouldAnimate = this.inited;
this.$nextTick(() => { this.$nextTick(() => {
const { titles } = this.$refs; const { titleRefs } = this;
if ( if (
!titles || !titleRefs ||
!titles[this.currentIndex] || !titleRefs[this.currentIndex] ||
this.type !== 'line' || this.type !== 'line' ||
isHidden(this.$el) isHidden(this.$el)
) { ) {
return; return;
} }
const title = titles[this.currentIndex].$el; const title = titleRefs[this.currentIndex].$el;
const { lineWidth, lineHeight } = this; const { lineWidth, lineHeight } = this;
const width = isDef(lineWidth) ? lineWidth : title.offsetWidth / 2; const width = isDef(lineWidth) ? lineWidth : title.offsetWidth / 2;
const left = title.offsetLeft + title.offsetWidth / 2; const left = title.offsetLeft + title.offsetWidth / 2;
@ -302,14 +302,14 @@ export default createComponent({
// scroll active tab into view // scroll active tab into view
scrollIntoView(immediate) { scrollIntoView(immediate) {
const { titles } = this.$refs; const { titleRefs } = this;
if (!this.scrollable || !titles || !titles[this.currentIndex]) { if (!this.scrollable || !titleRefs || !titleRefs[this.currentIndex]) {
return; return;
} }
const { nav } = this.$refs; 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; const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
scrollLeftTo(nav, to, immediate ? 0 : +this.duration); scrollLeftTo(nav, to, immediate ? 0 : +this.duration);
@ -369,31 +369,32 @@ export default createComponent({
render() { render() {
const { type, ellipsis, animated, scrollable } = this; const { type, ellipsis, animated, scrollable } = this;
const Nav = this.children.map((item, index) => ( const Nav = this.children.map((item, index) => {
<Title return (
ref="titles" <Title
refInFor ref={(val) => {
type={type} this.titleRefs[index] = val;
dot={item.dot} }}
info={isDef(item.badge) ? item.badge : item.info} dot={item.dot}
title={item.title} type={type}
color={this.color} info={isDef(item.badge) ? item.badge : item.info}
style={item.titleStyle} title={item.title}
isActive={index === this.currentIndex} color={this.color}
ellipsis={ellipsis} style={item.titleStyle}
disabled={item.disabled} isActive={index === this.currentIndex}
scrollable={scrollable} ellipsis={ellipsis}
activeColor={this.titleActiveColor} disabled={item.disabled}
inactiveColor={this.titleInactiveColor} scrollable={scrollable}
swipeThreshold={this.swipeThreshold} renderTitle={item.$slots.title}
scopedSlots={{ activeColor={this.titleActiveColor}
default: () => item.slots('title'), inactiveColor={this.titleInactiveColor}
}} swipeThreshold={this.swipeThreshold}
onClick={() => { onClick={() => {
this.onClick(item, index); this.onClick(item, index);
}} }}
/> />
)); );
});
const Wrap = ( const Wrap = (
<div <div
@ -409,12 +410,12 @@ export default createComponent({
class={bem('nav', [type])} class={bem('nav', [type])}
style={this.navStyle} style={this.navStyle}
> >
{this.slots('nav-left')} {this.$slots['nav-left']?.()}
{Nav} {Nav}
{type === 'line' && ( {type === 'line' && (
<div class={bem('line')} style={this.lineStyle} /> <div class={bem('line')} style={this.lineStyle} />
)} )}
{this.slots('nav-right')} {this.$slots['nav-right']?.()}
</div> </div>
</div> </div>
); );
@ -440,7 +441,7 @@ export default createComponent({
currentIndex={this.currentIndex} currentIndex={this.currentIndex}
onChange={this.setCurrentIndex} onChange={this.setCurrentIndex}
> >
{this.slots()} {this.$slots.default?.()}
</Content> </Content>
</div> </div>
); );

View File

@ -269,10 +269,10 @@ module.exports = {
path: 'steps', path: 'steps',
title: 'Steps 步骤条', title: 'Steps 步骤条',
}, },
// { {
// path: 'sticky', path: 'sticky',
// title: 'Sticky 粘性布局', title: 'Sticky 粘性布局',
// }, },
// { // {
// path: 'swipe', // path: 'swipe',
// title: 'Swipe 轮播', // title: 'Swipe 轮播',
@ -306,10 +306,10 @@ module.exports = {
path: 'sidebar', path: 'sidebar',
title: 'Sidebar 侧边导航', title: 'Sidebar 侧边导航',
}, },
// { {
// path: 'tab', path: 'tab',
// title: 'Tab 标签页', title: 'Tab 标签页',
// }, },
{ {
path: 'tabbar', path: 'tabbar',
title: 'Tabbar 标签栏', title: 'Tabbar 标签栏',
@ -603,10 +603,10 @@ module.exports = {
path: 'steps', path: 'steps',
title: 'Steps', title: 'Steps',
}, },
// { {
// path: 'sticky', path: 'sticky',
// title: 'Sticky', title: 'Sticky',
// }, },
// { // {
// path: 'swipe', // path: 'swipe',
// title: 'Swipe', // title: 'Swipe',
@ -640,10 +640,10 @@ module.exports = {
path: 'sidebar', path: 'sidebar',
title: 'Sidebar', title: 'Sidebar',
}, },
// { {
// path: 'tab', path: 'tab',
// title: 'Tab', title: 'Tab',
// }, },
{ {
path: 'tabbar', path: 'tabbar',
title: 'Tabbar', title: 'Tabbar',