mirror of https://github.com/ElemeFE/element
dynamic tabs (#812)
parent
cb5572f9d7
commit
8eae476f7f
|
@ -12,6 +12,7 @@
|
|||
- 新增 Dropdown 的 command api #432
|
||||
- 修复 Slider 在 Form 中的显示问题
|
||||
- 修复 Upload 在 onSuccess、onError 钩子无法拿到服务端返回信息的问题
|
||||
- 改善 tabs 现在支持动态更新
|
||||
|
||||
#### 非兼容性更新
|
||||
|
||||
|
|
|
@ -3,7 +3,13 @@
|
|||
data() {
|
||||
return {
|
||||
activeName: 'first',
|
||||
activeName2: ''
|
||||
activeName2: '',
|
||||
tabs: [
|
||||
{label: '用户管理', content: ''},
|
||||
{label: '配置管理', content: ''},
|
||||
{label: '角色管理', content: ''},
|
||||
{label: '定时任务补偿', content: ''}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -28,17 +34,19 @@
|
|||
```html
|
||||
<template>
|
||||
<el-tabs>
|
||||
<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 v-for="tab in tabs" :label="tab.label">{{tab.content}}</el-tab-pane>
|
||||
</el-tabs>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeName: 'first'
|
||||
tabs: [
|
||||
{label: '用户管理', content: ''},
|
||||
{label: '配置管理', content: ''},
|
||||
{label: '角色管理', content: ''},
|
||||
{label: '定时任务补偿', content: ''}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,19 +17,19 @@
|
|||
paneStyle: {
|
||||
position: 'relative'
|
||||
},
|
||||
key: ''
|
||||
index: ''
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
if (!this.key) {
|
||||
this.key = this.$parent.$children.indexOf(this) + 1 + '';
|
||||
if (!this.index) {
|
||||
this.index = this.$parent.$children.indexOf(this) + 1 + '';
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
show() {
|
||||
return this.$parent.currentName === this.key;
|
||||
return this.$parent.currentName === this.index;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -41,21 +41,20 @@
|
|||
name: {
|
||||
immediate: true,
|
||||
handler(val) {
|
||||
this.key = val;
|
||||
this.index = val;
|
||||
}
|
||||
},
|
||||
'$parent.currentName'(newValue, oldValue) {
|
||||
if (this.key === newValue) {
|
||||
if (this.index === newValue) {
|
||||
this.transition = newValue > oldValue ? 'slideInRight' : 'slideInLeft';
|
||||
}
|
||||
if (this.key === oldValue) {
|
||||
if (this.index === oldValue) {
|
||||
this.transition = oldValue > newValue ? 'slideInRight' : 'slideInLeft';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="el-tab-pane" v-show="show && $slots.default">
|
||||
<slot></slot>
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
<script>
|
||||
module.exports = {
|
||||
name: 'el-tab',
|
||||
|
||||
props: {
|
||||
tab: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
closable: Boolean
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="el-tabs__item"
|
||||
:class="{
|
||||
'is-active': $parent.currentName === tab.key,
|
||||
'is-disabled': tab.disabled,
|
||||
'is-closable': closable
|
||||
}">
|
||||
{{tab.label}}
|
||||
<span
|
||||
class="el-icon-close"
|
||||
v-if="closable"
|
||||
@click="$emit('remove', tab, $event)">
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
|
@ -1,13 +1,7 @@
|
|||
<script>
|
||||
import ElTab from './tab';
|
||||
|
||||
module.exports = {
|
||||
name: 'el-tabs',
|
||||
|
||||
components: {
|
||||
ElTab
|
||||
},
|
||||
|
||||
props: {
|
||||
type: String,
|
||||
tabPosition: String,
|
||||
|
@ -18,11 +12,9 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
tabs: [],
|
||||
children: null,
|
||||
activeTab: null,
|
||||
currentName: 0,
|
||||
barStyle: ''
|
||||
currentName: 0
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -31,45 +23,40 @@
|
|||
handler(val) {
|
||||
this.currentName = val;
|
||||
}
|
||||
},
|
||||
|
||||
'currentName'() {
|
||||
this.calcBarStyle();
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleTabRemove(tab, ev) {
|
||||
ev.stopPropagation();
|
||||
handleTabRemove(tab, event) {
|
||||
event.stopPropagation();
|
||||
let tabs = this.$children;
|
||||
|
||||
var index = tabs.indexOf(tab);
|
||||
tab.$destroy(true);
|
||||
|
||||
var index = this.tabs.indexOf(tab);
|
||||
if (tab.index === this.currentName) {
|
||||
let nextChild = tabs[index];
|
||||
let prevChild = tabs[index - 1];
|
||||
|
||||
if (index !== -1) {
|
||||
this.tabs.splice(index, 1);
|
||||
}
|
||||
|
||||
if (tab.key === this.currentName) {
|
||||
let nextChild = this.tabs[index];
|
||||
let prevChild = this.tabs[index - 1];
|
||||
|
||||
this.currentName = nextChild ? nextChild.key : prevChild ? prevChild.key : '-1';
|
||||
this.currentName = nextChild ? nextChild.index : prevChild ? prevChild.index : '-1';
|
||||
}
|
||||
this.$emit('tab-remove', tab);
|
||||
this.$forceUpdate();
|
||||
},
|
||||
handleTabClick(tab, event) {
|
||||
this.currentName = tab.key;
|
||||
this.currentName = tab.index;
|
||||
this.$emit('tab-click', tab, event);
|
||||
},
|
||||
calcBarStyle(firstRendering) {
|
||||
calcBarStyle() {
|
||||
if (this.type || !this.$refs.tabs) return {};
|
||||
var style = {};
|
||||
var offset = 0;
|
||||
var tabWidth = 0;
|
||||
|
||||
this.tabs.every((tab, index) => {
|
||||
let $el = this.$refs.tabs[index].$el;
|
||||
if (tab.key !== this.currentName) {
|
||||
this.$children.every((panel, index) => {
|
||||
let $el = this.$refs.tabs[index];
|
||||
if (!$el) { return false; }
|
||||
if (panel.index !== this.currentName) {
|
||||
offset += $el.clientWidth;
|
||||
return true;
|
||||
} else {
|
||||
|
@ -81,41 +68,66 @@
|
|||
style.width = tabWidth + 'px';
|
||||
style.transform = `translateX(${offset}px)`;
|
||||
|
||||
if (!firstRendering) {
|
||||
style.transition = 'transform .3s cubic-bezier(.645,.045,.355,1), -webkit-transform .3s cubic-bezier(.645,.045,.355,1)';
|
||||
}
|
||||
this.barStyle = style;
|
||||
return style;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
var fisrtKey = this.$children[0].key || '1';
|
||||
this.currentName = this.activeName || fisrtKey;
|
||||
this.$children.forEach(tab => this.tabs.push(tab));
|
||||
this.$nextTick(() => this.calcBarStyle(true));
|
||||
this.$nextTick(() => {
|
||||
this.currentName = this.activeName || this.$children[0].index || '1';
|
||||
});
|
||||
},
|
||||
render(h) {
|
||||
let {
|
||||
type,
|
||||
closable,
|
||||
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) => {
|
||||
let btnClose = h('span', {
|
||||
class: {
|
||||
'el-icon-close': true
|
||||
},
|
||||
on: { click: (ev) => { handleTabRemove(tab, ev); } }
|
||||
});
|
||||
const _tab = h('div', {
|
||||
class: {
|
||||
'el-tabs__item': true,
|
||||
'is-active': currentName === tab.index,
|
||||
'is-disabled': tab.disabled,
|
||||
'is-closable': closable
|
||||
},
|
||||
ref: 'tabs',
|
||||
refInFor: true,
|
||||
on: { click: (ev) => { handleTabClick(tab, ev); } }
|
||||
}, [
|
||||
tab.label,
|
||||
closable ? btnClose : null,
|
||||
index === 0 ? activeBar : null
|
||||
]);
|
||||
return _tab;
|
||||
});
|
||||
return (
|
||||
<div class={{
|
||||
'el-tabs': true,
|
||||
'el-tabs--card': type === 'card',
|
||||
'el-tabs--border-card': type === 'border-card'
|
||||
}}>
|
||||
<div class="el-tabs__header">
|
||||
{tabs}
|
||||
</div>
|
||||
<div class="el-tabs__content">
|
||||
{this.$slots.default}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="el-tabs" :class="[type ? 'el-tabs--' + type : '']">
|
||||
<div class="el-tabs__header">
|
||||
<el-tab
|
||||
v-for="tab in tabs"
|
||||
ref="tabs"
|
||||
:tab="tab"
|
||||
:closable="closable"
|
||||
@remove="handleTabRemove"
|
||||
@click.native="handleTabClick(tab, $event)">
|
||||
</el-tab>
|
||||
<div
|
||||
class="el-tabs__active-bar"
|
||||
:style="barStyle"
|
||||
v-if="!this.type && tabs.length > 0">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="el-tabs__content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
}
|
||||
@e active-bar {
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background-color: var(--color-primary);
|
||||
z-index: 1;
|
||||
/*transition: transform .3s cubic-bezier(.645,.045,.355,1);*/
|
||||
transition: transform .3s cubic-bezier(.645,.045,.355,1);
|
||||
list-style: none;
|
||||
}
|
||||
@e item {
|
||||
|
@ -44,7 +45,6 @@
|
|||
}
|
||||
}
|
||||
@e content {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -9,22 +9,27 @@ describe('Tabs', () => {
|
|||
it('create', done => {
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-tabs>
|
||||
<el-tabs ref="tabs">
|
||||
<el-tab-pane label="用户管理">A</el-tab-pane>
|
||||
<el-tab-pane label="配置管理">B</el-tab-pane>
|
||||
<el-tab-pane label="角色管理">C</el-tab-pane>
|
||||
<el-tab-pane label="角色管理" ref="pane-click">C</el-tab-pane>
|
||||
<el-tab-pane label="定时任务补偿">D</el-tab-pane>
|
||||
</el-tabs>
|
||||
`
|
||||
}, true);
|
||||
let tabList = vm.$el.querySelector('.el-tabs__header').children;
|
||||
let paneList = vm.$el.querySelector('.el-tabs__content').children;
|
||||
let spy = sinon.spy();
|
||||
|
||||
vm.$refs.tabs.$on('tab-click', spy);
|
||||
|
||||
setTimeout(_ => {
|
||||
expect(tabList[0].classList.contains('is-active')).to.be.true;
|
||||
expect(paneList[0].style.display).to.not.ok;
|
||||
|
||||
tabList[2].click();
|
||||
vm.$nextTick(_ => {
|
||||
expect(spy.withArgs(vm.$refs['pane-click']).calledOnce).to.true;
|
||||
expect(tabList[2].classList.contains('is-active')).to.be.true;
|
||||
expect(paneList[2].style.display).to.not.ok;
|
||||
done();
|
||||
|
@ -98,33 +103,27 @@ describe('Tabs', () => {
|
|||
it('closable', done => {
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-tabs type="card" closable @tab-remove="handleRemove">
|
||||
<el-tabs type="card" :closable="true" ref="tabs">
|
||||
<el-tab-pane label="用户管理">A</el-tab-pane>
|
||||
<el-tab-pane label="配置管理">B</el-tab-pane>
|
||||
<el-tab-pane label="角色管理">C</el-tab-pane>
|
||||
<el-tab-pane label="定时任务补偿">D</el-tab-pane>
|
||||
</el-tabs>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
removeTabName: ''
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleRemove(tab) {
|
||||
this.removeTabName = tab.label;
|
||||
}
|
||||
}
|
||||
`
|
||||
}, true);
|
||||
|
||||
let tabList = vm.$el.querySelector('.el-tabs__header').children;
|
||||
let paneList = vm.$el.querySelector('.el-tabs__content').children;
|
||||
let spy = sinon.spy();
|
||||
|
||||
vm.$refs.tabs.$on('tab-remove', spy);
|
||||
|
||||
setTimeout(_ => {
|
||||
tabList[1].querySelector('.el-icon-close').click();
|
||||
vm.$nextTick(_ => {
|
||||
expect(tabList.length).to.be.equal(3);
|
||||
expect(paneList.length).to.be.equal(3);
|
||||
expect(vm.removeTabName).to.be.equal('配置管理');
|
||||
expect(spy.calledOnce).to.true;
|
||||
expect(tabList[1].innerText.trim()).to.be.equal('角色管理');
|
||||
expect(paneList[0].innerText.trim()).to.be.equal('A');
|
||||
done();
|
||||
|
@ -134,7 +133,7 @@ describe('Tabs', () => {
|
|||
it('closable edge', done => {
|
||||
vm = createVue({
|
||||
template: `
|
||||
<el-tabs type="card" closable>
|
||||
<el-tabs type="card" :closable="true">
|
||||
<el-tab-pane label="用户管理">A</el-tab-pane>
|
||||
<el-tab-pane label="配置管理">B</el-tab-pane>
|
||||
<el-tab-pane label="角色管理">C</el-tab-pane>
|
||||
|
|
Loading…
Reference in New Issue