Commit ec1a5e268486e40269f82d36b47e8c0a05d87abc

Authored by Aresn
Committed by GitHub
2 parents 758bd768 be3fbd24

Merge pull request #2156 from marxy/tabs-scroll

Tabs add scroll
examples/routers/tabs.vue
@@ -70,7 +70,7 @@ @@ -70,7 +70,7 @@
70 <!--</script>--> 70 <!--</script>-->
71 71
72 72
73 -<template> 73 +<!-- <template>
74 <Tabs type="card" closable @on-tab-remove="handleTabRemove"> 74 <Tabs type="card" closable @on-tab-remove="handleTabRemove">
75 <TabPane label="标签一" v-if="tab0">标签一的内容</TabPane> 75 <TabPane label="标签一" v-if="tab0">标签一的内容</TabPane>
76 <TabPane label="标签二" v-if="tab1">标签二的内容</TabPane> 76 <TabPane label="标签二" v-if="tab1">标签二的内容</TabPane>
@@ -92,4 +92,41 @@ @@ -92,4 +92,41 @@
92 } 92 }
93 } 93 }
94 } 94 }
  95 +</script> -->
  96 +
  97 +<template>
  98 + <div>
  99 + <Button type="ghost" @click="toFirst" size="small">to first</Button>
  100 + <Button type="ghost" @click="toLast" size="small">to last</Button>
  101 + <Tabs type="card" :animated="animated" v-model="activeTab">
  102 + <TabPane v-for="tab in tabs" :key="tab" :label="'标签' + tab" :name="tab+''" closable>标签{{ tab }}</TabPane>
  103 + <div slot="extra">
  104 + <Button type="ghost" @click="handleTabsAdd" size="small">增加</Button>
  105 + </div>
  106 + </Tabs>
  107 + </div>
  108 +</template>
  109 +<script>
  110 + export default {
  111 + data () {
  112 + return {
  113 + tabs: 2,
  114 + activeTab:"2",
  115 + animated:true
  116 + }
  117 + },
  118 + methods: {
  119 + handleTabsAdd () {
  120 + this.tabs ++;
  121 + this.activeTab = this.tabs + '';
  122 + },
  123 + toFirst () {
  124 + this.activeTab = '1';
  125 + },
  126 + toLast () {
  127 + this.activeTab = this.tabs+'';
  128 + }
  129 + }
  130 + }
95 </script> 131 </script>
  132 +
src/components/tabs/tabs.vue
1 <template> 1 <template>
2 <div :class="classes"> 2 <div :class="classes">
3 <div :class="[prefixCls + '-bar']"> 3 <div :class="[prefixCls + '-bar']">
  4 + <div :class="[prefixCls + '-nav-right']" v-if="showSlot"><slot name="extra"></slot></div>
4 <div :class="[prefixCls + '-nav-container']"> 5 <div :class="[prefixCls + '-nav-container']">
5 - <div :class="[prefixCls + '-nav-wrap']">  
6 - <div :class="[prefixCls + '-nav-scroll']">  
7 - <div :class="[prefixCls + '-nav']" ref="nav"> 6 + <div ref="navWrap" :class="[prefixCls + '-nav-wrap', scrollable ? prefixCls + '-nav-scrollable' : '']" >
  7 + <span :class="[prefixCls + '-nav-prev', scrollable ? '' : prefixCls + '-nav-scroll-disabled']" @click="scrollPrev"><Icon type="chevron-left"></Icon></span>
  8 + <span :class="[prefixCls + '-nav-next', scrollable ? '' : prefixCls + '-nav-scroll-disabled']" @click="scrollNext"><Icon type="chevron-right"></Icon></span>
  9 + <div ref="navScroll" :class="[prefixCls + '-nav-scroll']">
  10 + <div ref="nav" :class="[prefixCls + '-nav']" class="nav-text" :style="navStyle">
8 <div :class="barClasses" :style="barStyle"></div> 11 <div :class="barClasses" :style="barStyle"></div>
9 <div :class="tabCls(item)" v-for="(item, index) in navList" @click="handleChange(index)"> 12 <div :class="tabCls(item)" v-for="(item, index) in navList" @click="handleChange(index)">
10 <Icon v-if="item.icon !== ''" :type="item.icon"></Icon> 13 <Icon v-if="item.icon !== ''" :type="item.icon"></Icon>
@@ -13,7 +16,6 @@ @@ -13,7 +16,6 @@
13 <Icon v-if="showClose(item)" type="ios-close-empty" @click.native.stop="handleRemove(index)"></Icon> 16 <Icon v-if="showClose(item)" type="ios-close-empty" @click.native.stop="handleRemove(index)"></Icon>
14 </div> 17 </div>
15 </div> 18 </div>
16 - <div :class="[prefixCls + '-nav-right']" v-if="showSlot"><slot name="extra"></slot></div>  
17 </div> 19 </div>
18 </div> 20 </div>
19 </div> 21 </div>
@@ -26,6 +28,7 @@ @@ -26,6 +28,7 @@
26 import Render from '../base/render'; 28 import Render from '../base/render';
27 import { oneOf } from '../../utils/assist'; 29 import { oneOf } from '../../utils/assist';
28 import Emitter from '../../mixins/emitter'; 30 import Emitter from '../../mixins/emitter';
  31 + import elementResizeDetectorMaker from 'element-resize-detector';
29 32
30 const prefixCls = 'ivu-tabs'; 33 const prefixCls = 'ivu-tabs';
31 34
@@ -65,7 +68,11 @@ @@ -65,7 +68,11 @@
65 barWidth: 0, 68 barWidth: 0,
66 barOffset: 0, 69 barOffset: 0,
67 activeKey: this.value, 70 activeKey: this.value,
68 - showSlot: false 71 + showSlot: false,
  72 + navStyle:{
  73 + transform: ''
  74 + },
  75 + scrollable:false
69 }; 76 };
70 }, 77 },
71 computed: { 78 computed: {
@@ -163,6 +170,7 @@ @@ -163,6 +170,7 @@
163 } else { 170 } else {
164 this.barOffset = 0; 171 this.barOffset = 0;
165 } 172 }
  173 + this.updateNavScroll();
166 }); 174 });
167 }, 175 },
168 updateStatus () { 176 updateStatus () {
@@ -222,6 +230,85 @@ @@ -222,6 +230,85 @@
222 } else { 230 } else {
223 return false; 231 return false;
224 } 232 }
  233 + },
  234 + scrollPrev() {
  235 + const containerWidth = this.$refs.navScroll.offsetWidth;
  236 + const currentOffset = this.getCurrentScrollOffset();
  237 +
  238 + if (!currentOffset) return;
  239 +
  240 + let newOffset = currentOffset > containerWidth
  241 + ? currentOffset - containerWidth
  242 + : 0;
  243 +
  244 + this.setOffset(newOffset);
  245 + },
  246 + scrollNext() {
  247 + const navWidth = this.$refs.nav.offsetWidth;
  248 + const containerWidth = this.$refs.navScroll.offsetWidth;
  249 + const currentOffset = this.getCurrentScrollOffset();
  250 + if (navWidth - currentOffset <= containerWidth) return;
  251 +
  252 + let newOffset = navWidth - currentOffset > containerWidth * 2
  253 + ? currentOffset + containerWidth
  254 + : (navWidth - containerWidth);
  255 +
  256 + this.setOffset(newOffset);
  257 + },
  258 + getCurrentScrollOffset() {
  259 + const { navStyle } = this;
  260 + return navStyle.transform
  261 + ? Number(navStyle.transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1])
  262 + : 0;
  263 + },
  264 + setOffset(value) {
  265 + this.navStyle.transform = `translateX(-${value}px)`;
  266 + },
  267 + scrollToActiveTab() {
  268 + if (!this.scrollable) return;
  269 + const nav = this.$refs.nav;
  270 + const activeTab = this.$el.querySelector(`.${prefixCls}-tab-active`);
  271 + if(!activeTab) return;
  272 +
  273 + const navScroll = this.$refs.navScroll;
  274 + const activeTabBounding = activeTab.getBoundingClientRect();
  275 + const navScrollBounding = navScroll.getBoundingClientRect();
  276 + const navBounding = nav.getBoundingClientRect();
  277 + const currentOffset = this.getCurrentScrollOffset();
  278 + let newOffset = currentOffset;
  279 +
  280 + if (navBounding.right < navScrollBounding.right) {
  281 + newOffset = nav.offsetWidth - navScrollBounding.width;
  282 + }
  283 +
  284 + if (activeTabBounding.left < navScrollBounding.left) {
  285 + newOffset = currentOffset - (navScrollBounding.left - activeTabBounding.left);
  286 + }else if (activeTabBounding.right > navScrollBounding.right) {
  287 + newOffset = currentOffset + activeTabBounding.right - navScrollBounding.right;
  288 + }
  289 +
  290 + if(currentOffset !== newOffset){
  291 + this.setOffset(Math.max(newOffset, 0));
  292 + }
  293 + },
  294 + updateNavScroll(){
  295 + const navWidth = this.$refs.nav.offsetWidth;
  296 + const containerWidth = this.$refs.navScroll.offsetWidth;
  297 + const currentOffset = this.getCurrentScrollOffset();
  298 + if (containerWidth < navWidth) {
  299 + this.scrollable = true;
  300 + if (navWidth - currentOffset < containerWidth) {
  301 + this.setOffset(navWidth - containerWidth);
  302 + }
  303 + } else {
  304 + this.scrollable = false;
  305 + if (currentOffset > 0) {
  306 + this.setOffset(0);
  307 + }
  308 + }
  309 + },
  310 + handleResize(){
  311 + this.updateNavScroll();
225 } 312 }
226 }, 313 },
227 watch: { 314 watch: {
@@ -232,10 +319,18 @@ @@ -232,10 +319,18 @@
232 this.updateBar(); 319 this.updateBar();
233 this.updateStatus(); 320 this.updateStatus();
234 this.broadcast('Table', 'on-visible-change', true); 321 this.broadcast('Table', 'on-visible-change', true);
  322 + this.$nextTick(function(){
  323 + this.scrollToActiveTab();
  324 + });
235 } 325 }
236 }, 326 },
237 mounted () { 327 mounted () {
238 this.showSlot = this.$slots.extra !== undefined; 328 this.showSlot = this.$slots.extra !== undefined;
  329 + this.observer = elementResizeDetectorMaker();
  330 + this.observer.listenTo(this.$refs.navWrap, this.handleResize);
  331 + },
  332 + beforeDestroy() {
  333 + this.observer.removeListener(this.$refs.navWrap, this.handleResize);
239 } 334 }
240 }; 335 };
241 </script> 336 </script>
src/styles/components/tabs.less
@@ -56,6 +56,29 @@ @@ -56,6 +56,29 @@
56 56
57 &-nav-right{ 57 &-nav-right{
58 float: right; 58 float: right;
  59 + margin-left: 5px;
  60 + }
  61 +
  62 + &-nav-prev{
  63 + position:absolute;
  64 + line-height: 32px;
  65 + cursor: pointer;
  66 + left:0;
  67 + }
  68 +
  69 + &-nav-next{
  70 + position:absolute;
  71 + line-height: 32px;
  72 + cursor: pointer;
  73 + right:0;
  74 + }
  75 +
  76 + &-nav-scrollable{
  77 + padding: 0 12px;
  78 + }
  79 +
  80 + &-nav-scroll-disabled{
  81 + display: none;
59 } 82 }
60 83
61 &-nav { 84 &-nav {