Commit be3fbd2499703ac2abbe727c171c12e83e337767
1 parent
2e8add2f
Tabs add scroll
Showing
3 changed files
with
161 additions
and
6 deletions
Show diff stats
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 { |