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