mirror of https://github.com/ElemeFE/element
add tabs scroll controls
parent
0bec05ada3
commit
6702726dcf
|
@ -3,9 +3,12 @@
|
|||
</template>
|
||||
<script>
|
||||
export default {
|
||||
name: 'TabBar',
|
||||
|
||||
props: {
|
||||
tabs: Array
|
||||
},
|
||||
|
||||
computed: {
|
||||
barStyle: {
|
||||
cache: false,
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
<script>
|
||||
import TabBar from './tab-bar';
|
||||
|
||||
function noop() {}
|
||||
|
||||
export default {
|
||||
name: 'TabNav',
|
||||
|
||||
components: {
|
||||
TabBar
|
||||
},
|
||||
|
||||
props: {
|
||||
panes: Array,
|
||||
currentName: String,
|
||||
editable: Boolean,
|
||||
onTabClick: {
|
||||
type: Function,
|
||||
default: noop
|
||||
},
|
||||
onTabRemove: {
|
||||
type: Function,
|
||||
default: noop
|
||||
},
|
||||
type: String
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
scrollable: false,
|
||||
navStyle: {
|
||||
transform: ''
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
scrollPrev() {
|
||||
const containerWidth = this.$refs.navScroll.offsetWidth;
|
||||
const currentOffset = this.getCurrentScrollOffset();
|
||||
|
||||
if (!currentOffset) return;
|
||||
|
||||
let newOffset = currentOffset > containerWidth
|
||||
? currentOffset - containerWidth
|
||||
: 0;
|
||||
|
||||
this.setOffset(newOffset);
|
||||
},
|
||||
scrollNext() {
|
||||
const navWidth = this.$refs.nav.offsetWidth;
|
||||
const containerWidth = this.$refs.navScroll.offsetWidth;
|
||||
const currentOffset = this.getCurrentScrollOffset();
|
||||
|
||||
if (navWidth - currentOffset <= containerWidth) return;
|
||||
|
||||
let newOffset = navWidth - currentOffset > containerWidth * 2
|
||||
? currentOffset + containerWidth
|
||||
: (navWidth - containerWidth);
|
||||
|
||||
this.setOffset(newOffset);
|
||||
},
|
||||
scrollToActiveTab() {
|
||||
if (!this.scrollable) return;
|
||||
const nav = this.$refs.nav;
|
||||
const activeTab = this.$el.querySelector('.is-active');
|
||||
const navScroll = this.$refs.navScroll;
|
||||
const activeTabBounding = activeTab.getBoundingClientRect();
|
||||
const navScrollBounding = navScroll.getBoundingClientRect();
|
||||
const navBounding = nav.getBoundingClientRect();
|
||||
const currentOffset = this.getCurrentScrollOffset();
|
||||
let newOffset = currentOffset;
|
||||
|
||||
if (activeTabBounding.left < navScrollBounding.left) {
|
||||
newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
|
||||
}
|
||||
if (activeTabBounding.right > navScrollBounding.right) {
|
||||
newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
|
||||
}
|
||||
if (navBounding.right < navScrollBounding.right) {
|
||||
newOffset = nav.offsetWidth - navScrollBounding.width;
|
||||
}
|
||||
this.setOffset(Math.max(newOffset, 0));
|
||||
},
|
||||
getCurrentScrollOffset() {
|
||||
const { navStyle } = this;
|
||||
return navStyle.transform
|
||||
? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
|
||||
: 0;
|
||||
},
|
||||
setOffset(value) {
|
||||
this.navStyle.transform = `translateX(-${value}px)`;
|
||||
}
|
||||
},
|
||||
|
||||
updated() {
|
||||
const navWidth = this.$refs.nav.offsetWidth;
|
||||
const containerWidth = this.$refs.navScroll.offsetWidth;
|
||||
const currentOffset = this.getCurrentScrollOffset();
|
||||
|
||||
if (containerWidth < navWidth) {
|
||||
const currentOffset = this.getCurrentScrollOffset();
|
||||
this.scrollable = this.scrollable || {};
|
||||
this.scrollable.prev = currentOffset;
|
||||
this.scrollable.next = currentOffset + containerWidth < navWidth;
|
||||
if (navWidth - currentOffset < containerWidth) {
|
||||
this.setOffset(navWidth - containerWidth);
|
||||
}
|
||||
} else if (currentOffset > 0) {
|
||||
this.setOffset(0);
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const {
|
||||
type,
|
||||
panes,
|
||||
editable,
|
||||
onTabClick,
|
||||
onTabRemove,
|
||||
navStyle,
|
||||
scrollable,
|
||||
scrollNext,
|
||||
scrollPrev
|
||||
} = this;
|
||||
|
||||
const scrollBtn = scrollable
|
||||
? [
|
||||
<span class={['el-tabs__nav-prev', scrollable.prev ? '' : 'is-disabled']} on-click={scrollPrev}><i class="el-icon-arrow-left"></i></span>,
|
||||
<span class={['el-tabs__nav-next', scrollable.next ? '' : 'is-disabled']} on-click={scrollNext}><i class="el-icon-arrow-right"></i></span>
|
||||
] : null;
|
||||
|
||||
const tabs = this._l(panes, (pane, index) => {
|
||||
let tabName = pane.name || pane.index || index;
|
||||
const closable = pane.isClosable || editable;
|
||||
|
||||
pane.index = `${index}`;
|
||||
|
||||
const btnClose = closable
|
||||
? <span class="el-icon-close" on-click={(ev) => { onTabRemove(pane, ev); }}></span>
|
||||
: null;
|
||||
|
||||
const tabLabelContent = pane.$slots.label || pane.label;
|
||||
return (
|
||||
<div
|
||||
class={{
|
||||
'el-tabs__item': true,
|
||||
'is-active': pane.active,
|
||||
'is-disabled': pane.disabled,
|
||||
'is-closable': closable
|
||||
}}
|
||||
ref="tabs"
|
||||
refInFor
|
||||
on-click={(ev) => { onTabClick(pane, tabName, ev); }}
|
||||
>
|
||||
{tabLabelContent}
|
||||
{btnClose}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div class={['el-tabs__nav-wrap', scrollable ? 'is-scrollable' : '']}>
|
||||
{scrollBtn}
|
||||
<div class={['el-tabs__nav-scroll']} ref="navScroll">
|
||||
<div class="el-tabs__nav" ref="nav" style={navStyle}>
|
||||
{!type ? <tab-bar tabs={panes}></tab-bar> : null}
|
||||
{tabs}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -1,10 +1,11 @@
|
|||
<script>
|
||||
import TabBar from './tab-bar';
|
||||
import TabNav from './tab-nav';
|
||||
|
||||
module.exports = {
|
||||
name: 'ElTabs',
|
||||
|
||||
components: {
|
||||
TabBar
|
||||
TabNav
|
||||
},
|
||||
|
||||
props: {
|
||||
|
@ -18,7 +19,6 @@
|
|||
|
||||
data() {
|
||||
return {
|
||||
children: null,
|
||||
currentName: this.value || this.activeName,
|
||||
panes: []
|
||||
};
|
||||
|
@ -30,6 +30,13 @@
|
|||
},
|
||||
value(value) {
|
||||
this.setCurrentName(value);
|
||||
},
|
||||
currentName(value) {
|
||||
if (this.$refs.nav) {
|
||||
this.$nextTick(_ => {
|
||||
this.$refs.nav.scrollToActiveTab();
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -78,7 +85,7 @@
|
|||
const newButton = editable || addable
|
||||
? (
|
||||
<span
|
||||
class="el-tabs__new-button"
|
||||
class="el-tabs__new-tab"
|
||||
on-click={ handleTabAdd }
|
||||
>
|
||||
<i class="el-icon-plus"></i>
|
||||
|
@ -86,38 +93,17 @@
|
|||
)
|
||||
: null;
|
||||
|
||||
const tabs = this._l(panes, (pane, index) => {
|
||||
let tabName = pane.name || pane.index || index;
|
||||
const closable = pane.isClosable || editable;
|
||||
|
||||
if (currentName === undefined && index === 0) {
|
||||
this.setCurrentName(tabName);
|
||||
}
|
||||
|
||||
pane.index = index;
|
||||
|
||||
const btnClose = closable
|
||||
? <span class="el-icon-close" on-click={(ev) => { handleTabRemove(pane, ev); }}></span>
|
||||
: null;
|
||||
|
||||
const tabLabelContent = pane.$slots.label || pane.label;
|
||||
return (
|
||||
<div
|
||||
class={{
|
||||
'el-tabs__item': true,
|
||||
'is-active': pane.active,
|
||||
'is-disabled': pane.disabled,
|
||||
'is-closable': closable
|
||||
}}
|
||||
ref="tabs"
|
||||
refInFor
|
||||
on-click={(ev) => { handleTabClick(pane, tabName, ev); }}
|
||||
>
|
||||
{tabLabelContent}
|
||||
{btnClose}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
const navData = {
|
||||
props: {
|
||||
currentName,
|
||||
onTabClick: handleTabClick,
|
||||
onTabRemove: handleTabRemove,
|
||||
editable,
|
||||
type,
|
||||
panes
|
||||
},
|
||||
ref: 'nav'
|
||||
};
|
||||
|
||||
return (
|
||||
<div class={{
|
||||
|
@ -126,15 +112,19 @@
|
|||
'el-tabs--border-card': type === 'border-card'
|
||||
}}>
|
||||
<div class="el-tabs__header">
|
||||
{!type ? <tab-bar tabs={panes}></tab-bar> : null}
|
||||
{tabs}
|
||||
{newButton}
|
||||
<tab-nav { ...navData }></tab-nav>
|
||||
</div>
|
||||
<div class="el-tabs__content">
|
||||
{this.$slots.default}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
created() {
|
||||
if (!this.currentName) {
|
||||
this.setCurrentName('1');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
padding: 0;
|
||||
position: relative;
|
||||
margin: 0 0 15px;
|
||||
@utils-clearfix;
|
||||
}
|
||||
@e active-bar {
|
||||
position: absolute;
|
||||
|
@ -20,8 +19,8 @@
|
|||
transition: transform .3s cubic-bezier(.645,.045,.355,1);
|
||||
list-style: none;
|
||||
}
|
||||
@e new-button {
|
||||
float: left;
|
||||
@e new-tab {
|
||||
float: right;
|
||||
border: 1px solid #d3dce6;
|
||||
height: 18px;
|
||||
width: @height;
|
||||
|
@ -42,16 +41,46 @@
|
|||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
@e nav-wrap {
|
||||
overflow: hidden;
|
||||
margin-bottom: -1px;
|
||||
position: relative;
|
||||
|
||||
@when scrollable {
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
@e nav-scroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
@e nav-next, nav-prev {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
line-height: 44px;
|
||||
font-size: 12px;
|
||||
color: var(--color-base-silver);
|
||||
}
|
||||
@e nav-next {
|
||||
right: 0;
|
||||
}
|
||||
@e nav-prev {
|
||||
left: 0;
|
||||
}
|
||||
@e nav {
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
float: left;
|
||||
transition: transform .3s;
|
||||
}
|
||||
@e item {
|
||||
padding: 0 16px;
|
||||
height: 42px;
|
||||
box-sizing: border-box;
|
||||
line-height: @height;
|
||||
float: left;
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
font-size: 14px;
|
||||
color: var(--color-base-silver);
|
||||
margin-bottom: -1px;
|
||||
position: relative;
|
||||
|
||||
& .el-icon-close {
|
||||
|
@ -89,10 +118,10 @@
|
|||
position: relative;
|
||||
}
|
||||
@m card {
|
||||
&>.el-tabs__header>.el-tabs__active-bar {
|
||||
.el-tabs__nav .el-tabs__active-bar {
|
||||
display: none;
|
||||
}
|
||||
&>.el-tabs__header>.el-tabs__item .el-icon-close {
|
||||
.el-tabs__nav .el-tabs__item .el-icon-close {
|
||||
position: relative;
|
||||
font-size: 12px;
|
||||
width: 0;
|
||||
|
@ -104,7 +133,7 @@
|
|||
right: -2px;
|
||||
transform-origin: 100% 50%;
|
||||
}
|
||||
&>.el-tabs__header>.el-tabs__item {
|
||||
.el-tabs__nav .el-tabs__item {
|
||||
border: 1px solid transparent;
|
||||
transition: all .3s cubic-bezier(.645,.045,.355,1);
|
||||
|
||||
|
|
Loading…
Reference in New Issue