fix tabs initial with bug & improve docs

pull/2020/head
baiyaaaaa 2016-12-22 00:02:51 +08:00 committed by 杨奕
parent 4c2243a7ac
commit 2eea08af23
8 changed files with 198 additions and 169 deletions

View File

@ -2,7 +2,18 @@
export default { export default {
data() { data() {
return { return {
activeName: 'first' activeName: 'first',
activeName2: 'first',
tabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
} }
}, },
methods: { methods: {
@ -27,11 +38,11 @@ Divide data collections which are related yet belong to different types.
Basic and concise tabs. Basic and concise tabs.
:::demo Tabs provide a selective card functionality and it can be achieved by just using `el-tabs` and child element `el-tab-pane`. In these two elements, we provide a list of attributes. The `label` in `el-tab-pane` determines the label of selective cards, and you can write content in the label. In this example, we add a `active-name` attribute indicating the active card in `el-tabs`, which can take a `String` value. In the `el-tab-pane` you can set corresponding `name` attribute, and if there is no `name`, the default sequence is `1`/`2`/`3`/`4`. In this example, the selected card is card 2. If `name` is omitted, setting `active-name` to `2` can reach the same goal. :::demo Tabs provide a selective card functionality. By default the first tab is selected as active, and you can activate any tab by setting the `value` attribute.
```html ```html
<template> <template>
<el-tabs :active-name="activeName"> <el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="User" name="first">User</el-tab-pane> <el-tab-pane label="User" name="first">User</el-tab-pane>
<el-tab-pane label="Config" name="second">Config</el-tab-pane> <el-tab-pane label="Config" name="second">Config</el-tab-pane>
<el-tab-pane label="Role" name="third">Role</el-tab-pane> <el-tab-pane label="Role" name="third">Role</el-tab-pane>
@ -44,6 +55,11 @@ Basic and concise tabs.
return { return {
activeName: 'first' activeName: 'first'
}; };
},
methods: {
handleClick(tab, event) {
console.log(tab, event);
}
} }
}; };
</script> </script>
@ -58,7 +74,7 @@ Tabs styled as cards.
```html ```html
<template> <template>
<el-tabs type="card" @tab-click="handleClick" @tab-remove="handleRemove"> <el-tabs type="card" @tab-click="handleClick">
<el-tab-pane label="User">User</el-tab-pane> <el-tab-pane label="User">User</el-tab-pane>
<el-tab-pane label="Config">Config</el-tab-pane> <el-tab-pane label="Config">Config</el-tab-pane>
<el-tab-pane label="Role">Role</el-tab-pane> <el-tab-pane label="Role">Role</el-tab-pane>
@ -67,10 +83,12 @@ Tabs styled as cards.
</template> </template>
<script> <script>
export default { export default {
data() {
return {
activeName: 'first'
};
},
methods: { methods: {
handleRemove(tab) {
console.log(tab);
},
handleClick(tab, event) { handleClick(tab, event) {
console.log(tab, event); console.log(tab, event);
} }
@ -84,7 +102,7 @@ Tabs styled as cards.
Closable tabs. Closable tabs.
:::demo You can set `closable` attribute in `el-tabs`. It accept `Boolean` and Tab will be closable when the boolean is `true`. :::demo You can set the closable attribute in el-tabs to make all tabs closable. Also, closable can be set in a tab panel to make that specific tab closable.
```html ```html
<template> <template>
@ -157,7 +175,8 @@ You can use `label-content` property to customize the tab
|---------- |-------- |---------- |------------- |-------- | |---------- |-------- |---------- |------------- |-------- |
| type | type of Tab | string | card/border-card | — | | type | type of Tab | string | card/border-card | — |
| closable | whether Tab is closable | boolean | — | false | | closable | whether Tab is closable | boolean | — | false |
| active-name | name of the selected tab | string | — | name of first tab | | active-name(deprecated) | name of the selected tab | string | — | name of first tab |
| value | name of the selected tab | string | — | name of first tab |
### Tabs Events ### Tabs Events
| Event Name | Description | Parameters | | Event Name | Description | Parameters |
@ -172,3 +191,4 @@ You can use `label-content` property to customize the tab
| label-content | render function for tab title | Function(h, tab:vueInstance) | - | - | | label-content | render function for tab title | Function(h, tab:vueInstance) | - | - |
| disabled | whether Tab is disabled | boolean | - | false | | disabled | whether Tab is disabled | boolean | - | false |
| name | identifier corresponding to the activeName of Tabs, representing the alias of the tab-pane | string | — | ordinal number of the tab-pane in the sequence, i.e. the first tab-pane is '1' | | name | identifier corresponding to the activeName of Tabs, representing the alias of the tab-pane | string | — | ordinal number of the tab-pane in the sequence, i.e. the first tab-pane is '1' |
| closable | whether Tab is closable | boolean | — | false |

View File

@ -2,7 +2,18 @@
export default { export default {
data() { data() {
return { return {
activeName: 'first' activeName: 'first',
activeName2: 'first',
tabs: [{
title: 'Tab 1',
name: '1',
content: 'Tab 1 content'
}, {
title: 'Tab 2',
name: '2',
content: 'Tab 2 content'
}],
tabIndex: 2
} }
}, },
methods: { methods: {
@ -18,18 +29,20 @@
} }
} }
</script> </script>
## Tabs 标签页 ## Tabs 标签页
分隔内容上有关联但属于不同类别的数据集合。 分隔内容上有关联但属于不同类别的数据集合。
### 基础用法 ### 基础用法
基础的、简洁的标签页。 基础的、简洁的标签页。
:::demo Tabs 组件提供了选项卡功能,只需要使用`el-tabs`和子元素`el-tab-pane`即可,在两个元素中,我们分别提供了一系列的属性来方便使用,`el-tab-pane`中`label`决定了选项卡标题,标签内部写入内容即可。在下例中我们在`el-tabs`中设置了`active-name`属性,接受一个`String`值,表明选中的选项卡,在`el-tab-pane`中可以设置对应的`name`属性,如果没有设置`name`,则默认值为顺序的`1`/`2`/`3`/`4`。例子选中选项卡2如果不设置`name`,将`active-name`设为`2`,可以达成相同效果 :::demo Tabs 组件提供了选项卡功能,默认选中第一个标签页,你也可以通过 `value` 属性来指定当前选中的标签页
```html ```html
<template> <template>
<el-tabs :active-name="activeName"> <el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane> <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
<el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane> <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
<el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane> <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
@ -42,6 +55,11 @@
return { return {
activeName: 'first' activeName: 'first'
}; };
},
methods: {
handleClick(tab, event) {
console.log(tab, event);
}
} }
}; };
</script> </script>
@ -52,23 +70,25 @@
选项卡样式的标签页。 选项卡样式的标签页。
:::demo 只需要设置`type`属性即可,如果需要标签风格,将其设置为`card`。 :::demo 只需要设置 `type` 属性为 `card` 就可以使选项卡改变为标签风格
```html ```html
<template> <template>
<el-tabs type="card" @tab-click="handleClick" @tab-remove="handleRemove"> <el-tabs v-model="activeName2" type="card" @tab-click="handleClick">
<el-tab-pane label="用户管理">用户管理</el-tab-pane> <el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
<el-tab-pane label="配置管理">配置管理</el-tab-pane> <el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
<el-tab-pane label="角色管理">角色管理</el-tab-pane> <el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
<el-tab-pane label="定时任务补偿">定时任务补偿</el-tab-pane> <el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
</el-tabs> </el-tabs>
</template> </template>
<script> <script>
export default { export default {
data() {
return {
activeName: 'first'
};
},
methods: { methods: {
handleRemove(tab) {
console.log(tab);
},
handleClick(tab, event) { handleClick(tab, event) {
console.log(tab, event); console.log(tab, event);
} }
@ -82,11 +102,11 @@
可以关闭标签页。 可以关闭标签页。
:::demo 在`el-tabs`中设置`closable`属性,接受一个`Boolean`,设置为`true`时为可关闭 :::demo 通过设置 `closable` 属性来打开 `Tabs` 的可关闭标签效果, `closable` 也可以设置在 `Tab Panel` 中实现部分标签页的可关闭效果
```html ```html
<template> <template>
<el-tabs type="card" :closable="true" @tab-click="handleClick" @tab-remove="handleRemove"> <el-tabs type="card" closable @tab-click="handleClick" @tab-remove="handleRemove">
<el-tab-pane label="用户管理">用户管理</el-tab-pane> <el-tab-pane label="用户管理">用户管理</el-tab-pane>
<el-tab-pane label="配置管理">配置管理</el-tab-pane> <el-tab-pane label="配置管理">配置管理</el-tab-pane>
<el-tab-pane label="角色管理">角色管理</el-tab-pane> <el-tab-pane label="角色管理">角色管理</el-tab-pane>
@ -147,12 +167,28 @@
``` ```
::: :::
### 动态增加标签页
展示如何通过触发器来动态增加标签页
:::demo
```html
<div style="margin-bottom: 20px;">
<el-button size="small" @click="tabs.push({ name: 'Tab ' + ++tabIndex, title: 'new Tab', content: 'new Tab content' })">add tab</el-button>
</div>
<el-tabs type="card" closable>
<el-tab-pane v-for="(item, index) in tabs" :label="item.title" :name="item.name">{{item.content}}</el-tab-pane>
</el-tabs>
```
:::
### Tabs Attributes ### Tabs Attributes
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
|---------- |-------- |---------- |------------- |-------- | |---------- |-------- |---------- |------------- |-------- |
| type | 风格类型 | string | card/border-card | — | | type | 风格类型 | string | card/border-card | — |
| closable | 标签是否可关闭 | boolean | — | false | | closable | 标签是否可关闭 | boolean | — | false |
| active-name | 选中选项卡的 name | string | — | 第一个选项卡的 name | | active-name(deprecated) | 选中选项卡的 name | string | — | 第一个选项卡的 name |
| value | 绑定值,选中选项卡的 name | string | — | 第一个选项卡的 name |
### Tabs Events ### Tabs Events
| 事件名称 | 说明 | 回调参数 | | 事件名称 | 说明 | 回调参数 |
@ -167,3 +203,4 @@
| label-content | 选项卡的标题的渲染 Function | Function(h, tab:vueInstance) | - | - | | label-content | 选项卡的标题的渲染 Function | Function(h, tab:vueInstance) | - | - |
| disabled | 是否禁用 | boolean | - | false | | disabled | 是否禁用 | boolean | - | false |
| name | 与选项卡 activeName 对应的标识符,表示选项卡别名 | string | — | 该选项卡在选项卡列表中的顺序值,如第一个选项卡则为'1' | | name | 与选项卡 activeName 对应的标识符,表示选项卡别名 | string | — | 该选项卡在选项卡列表中的顺序值,如第一个选项卡则为'1' |
| closable | 标签是否可关闭 | boolean | — | false |

View File

@ -58,7 +58,7 @@
"babel-loader": "^6.2.5", "babel-loader": "^6.2.5",
"babel-plugin-module-resolver": "^2.2.0", "babel-plugin-module-resolver": "^2.2.0",
"babel-plugin-syntax-jsx": "^6.8.0", "babel-plugin-syntax-jsx": "^6.8.0",
"babel-plugin-transform-vue-jsx": "^3.1.0", "babel-plugin-transform-vue-jsx": "^3.3.0",
"babel-preset-es2015": "^6.14.0", "babel-preset-es2015": "^6.14.0",
"chai": "^3.5.0", "chai": "^3.5.0",
"cheerio": "^0.18.0", "cheerio": "^0.18.0",

View File

@ -1,3 +1,10 @@
<template>
<div class="el-tab-pane">
<div class="el-tab-pane__content" v-show="active">
<slot></slot>
</div>
</div>
</template>
<script> <script>
module.exports = { module.exports = {
name: 'el-tab-pane', name: 'el-tab-pane',
@ -12,73 +19,33 @@
data() { data() {
return { return {
counter: 0, index: null
transition: '',
paneStyle: {
position: 'relative'
},
isClosable: null,
index: ''
}; };
}, },
created() { computed: {
const propsData = this.$options.propsData; isClosable() {
if (propsData && typeof propsData.closable !== 'undefined') { return this.closable || this.$parent.closable;
this.isClosable = propsData.closable === '' || propsData.closable; },
} else { active() {
this.isClosable = this.$parent.closable; return this.$parent.currentName === (this.name || this.index);
}
if (!this.index) {
this.index = this.$parent.$children.indexOf(this) + 1 + '';
}
if (this.$parent.panes) {
this.$parent.panes.push(this);
} }
}, },
computed: { created() {
show() { this.$parent.$forceUpdate();
return this.$parent.currentName === this.index;
}
}, },
destroyed() { destroyed() {
if (this.$el && this.$el.parentNode) { if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el); this.$el.parentNode.removeChild(this.$el);
} }
const panes = this.$parent.panes;
if (panes) {
panes.splice(this, panes.indexOf(this));
}
}, },
watch: { watch: {
name: {
immediate: true,
handler(val) {
this.index = val;
}
},
closable(val) {
this.isClosable = val;
},
'$parent.currentName'(newValue, oldValue) {
if (this.index === newValue) {
this.transition = newValue > oldValue ? 'slideInRight' : 'slideInLeft';
}
if (this.index === oldValue) {
this.transition = oldValue > newValue ? 'slideInRight' : 'slideInLeft';
}
},
label() { label() {
this.$parent.$forceUpdate(); this.$parent.$forceUpdate();
} }
} }
}; };
</script> </script>
<template>
<div class="el-tab-pane" v-show="show && $slots.default">
<slot></slot>
</div>
</template>

View File

@ -4,69 +4,84 @@
props: { props: {
type: String, type: String,
tabPosition: String,
activeName: String, activeName: String,
closable: false, closable: {
tabWidth: 0 type: Boolean,
default: false
},
value: {}
}, },
data() { data() {
return { return {
children: null, children: null,
activeTab: null, currentName: this.value || this.activeName
currentName: 0,
panes: []
}; };
}, },
watch: { watch: {
activeName: { activeName(val) {
handler(val) { this.currentName = val;
this.currentName = val; },
} value(val) {
this.currentName = val;
},
currentName(val) {
this.$emit('input', val);
} }
}, },
methods: { methods: {
handleTabRemove(tab, event) { handleTabRemove(tab, event) {
event.stopPropagation(); event.stopPropagation();
let tabs = this.$children; const tabs = this.$children;
var index = tabs.indexOf(tab); let index = tabs.indexOf(tab);
tab.$destroy(true); tab.$destroy();
if (tab.index === this.currentName) {
let nextChild = tabs[index];
let prevChild = tabs[index - 1];
while (prevChild && prevChild.disabled) {
prevChild = tabs[tabs.indexOf(prevChild) - 1];
}
this.currentName = nextChild
? nextChild.index
: prevChild
? prevChild.index
: '-1';
}
this.$emit('tab-remove', tab); this.$emit('tab-remove', tab);
this.$forceUpdate(); this.$forceUpdate();
},
handleTabClick(tab, event) {
if (tab.disabled) return;
this.currentName = tab.index;
this.$emit('tab-click', tab, event);
},
calcBarStyle() {
if (this.type || !this.$refs.tabs) return {};
var style = {};
var offset = 0;
var tabWidth = 0;
this.$children.every((panel, index) => { this.$nextTick(_ => {
if (tab.active) {
let nextChild = tabs[index];
let prevChild = tabs[index - 1];
let nextActiveTab = nextChild || prevChild || null;
if (nextActiveTab) {
this.currentName = nextActiveTab.name || nextActiveTab.index;
}
}
});
},
handleTabClick(tab, tabName, event) {
if (tab.disabled) return;
this.currentName = tabName;
this.$emit('tab-click', tab, event);
}
},
mounted() {
this.$forceUpdate();
},
render(h) {
let {
type,
handleTabRemove,
handleTabClick,
currentName
} = this;
const getBarStyle = () => {
if (this.type || !this.$refs.tabs) return {};
let style = {};
let offset = 0;
let tabWidth = 0;
this.$children.every((tab, index) => {
let $el = this.$refs.tabs[index]; let $el = this.$refs.tabs[index];
if (!$el) { return false; } if (!$el) { return false; }
if (panel.index !== this.currentName) {
if (!tab.active) {
offset += $el.clientWidth; offset += $el.clientWidth;
return true; return true;
} else { } else {
@ -79,51 +94,45 @@
style.transform = `translateX(${offset}px)`; style.transform = `translateX(${offset}px)`;
return style; return style;
} };
},
mounted() {
this.currentName = this.activeName || this.$children[0] && this.$children[0].index || '1';
this.$nextTick(() => {
this.$forceUpdate();
});
},
render(h) {
let {
type,
panes, // eslint-disable-line
handleTabRemove,
handleTabClick,
currentName
} = this;
const barStyle = this.calcBarStyle();
const activeBar = !type
? <div class="el-tabs__active-bar" style={barStyle}></div>
: null;
const tabs = this.$children.map((tab, index) => { const tabs = this.$children.map((tab, index) => {
let btnClose = h('span', { let tabName = tab.name || tab.index || index;
class: { if (currentName === undefined && index === 0) {
'el-icon-close': true this.currentName = tabName;
}, }
on: { click: (ev) => { handleTabRemove(tab, ev); } }
}); tab.index = index;
const _tab = h('div', {
class: { const activeBar = !type && index === 0
'el-tabs__item': true, ? <div class="el-tabs__active-bar" style={getBarStyle()}></div>
'is-active': currentName === tab.index, : null;
'is-disabled': tab.disabled,
'is-closable': tab.isClosable const btnClose = tab.isClosable
}, ? <span class="el-icon-close" on-click={(ev) => { handleTabRemove(tab, ev); }}></span>
ref: 'tabs', : null;
refInFor: true,
on: { click: (ev) => { handleTabClick(tab, ev); } } const tabLabelContent = tab.labelContent
}, [ ? tab.labelContent.call(this._renderProxy, h, tab)
tab.labelContent ? tab.labelContent.call(this._renderProxy, h, tab) : tab.label, : tab.label;
tab.isClosable ? btnClose : null,
index === 0 ? activeBar : null return (
]); <div
return _tab; class={{
'el-tabs__item': true,
'is-active': tab.active,
'is-disabled': tab.disabled,
'is-closable': tab.isClosable
}}
ref="tabs"
refInFor
on-click={(ev) => { handleTabClick(tab, tabName, ev); }}
>
{tabLabelContent}
{btnClose}
{activeBar}
</div>
);
}); });
return ( return (
<div class={{ <div class={{

View File

@ -147,10 +147,6 @@
} }
} }
} }
@b tab-pane {
width: 100%;
display: inline-block;
}
} }
.slideInRight-transition, .slideInRight-transition,

View File

@ -188,7 +188,7 @@ describe('Tabs', () => {
it('closable edge', done => { it('closable edge', done => {
vm = createVue({ vm = createVue({
template: ` template: `
<el-tabs type="card" :closable="true"> <el-tabs type="card" :closable="true" ref="tabs">
<el-tab-pane label="用户管理">A</el-tab-pane> <el-tab-pane label="用户管理">A</el-tab-pane>
<el-tab-pane label="配置管理">B</el-tab-pane> <el-tab-pane label="配置管理">B</el-tab-pane>
<el-tab-pane label="角色管理">C</el-tab-pane> <el-tab-pane label="角色管理">C</el-tab-pane>
@ -199,7 +199,7 @@ describe('Tabs', () => {
let tabList = vm.$el.querySelector('.el-tabs__header').children; let tabList = vm.$el.querySelector('.el-tabs__header').children;
let paneList = vm.$el.querySelector('.el-tabs__content').children; let paneList = vm.$el.querySelector('.el-tabs__content').children;
setTimeout(_ => { vm.$nextTick(_ => {
tabList[0].querySelector('.el-icon-close').click(); tabList[0].querySelector('.el-icon-close').click();
vm.$nextTick(_ => { vm.$nextTick(_ => {
expect(tabList.length).to.be.equal(3); expect(tabList.length).to.be.equal(3);
@ -209,16 +209,16 @@ describe('Tabs', () => {
tabList[2].click(); tabList[2].click();
tabList[2].querySelector('.el-icon-close').click(); tabList[2].querySelector('.el-icon-close').click();
vm.$nextTick(_ => { setTimeout(_ => {
expect(tabList.length).to.be.equal(2); expect(tabList.length).to.be.equal(2);
expect(paneList.length).to.be.equal(2); expect(paneList.length).to.be.equal(2);
expect(tabList[1].classList.contains('is-active')).to.be.true; expect(tabList[1].classList.contains('is-active')).to.be.true;
expect(tabList[1].innerText.trim()).to.be.equal('角色管理'); expect(tabList[1].innerText.trim()).to.be.equal('角色管理');
expect(paneList[1].innerText.trim()).to.be.equal('C'); expect(paneList[1].innerText.trim()).to.be.equal('C');
done(); done();
}); }, 100);
}); });
}, 100); });
}); });
it('tab title render function', done => { it('tab title render function', done => {
vm = createVue({ vm = createVue({

View File

@ -620,9 +620,9 @@ babel-plugin-transform-strict-mode@^6.18.0:
babel-runtime "^6.0.0" babel-runtime "^6.0.0"
babel-types "^6.18.0" babel-types "^6.18.0"
babel-plugin-transform-vue-jsx@^3.1.0: babel-plugin-transform-vue-jsx@^3.3.0:
version "3.2.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.2.0.tgz#27d550d5fca27ef6f694cf294133179754d184dc" resolved "https://registry.yarnpkg.com/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-3.3.0.tgz#d6163f93f129e9fac3768813470f5eed9fd26f7e"
dependencies: dependencies:
esutils "^2.0.2" esutils "^2.0.2"