diff --git a/docs/examples-dist/tab.vue b/docs/examples-dist/tab.vue index 962d70a10..fa8534e5d 100644 --- a/docs/examples-dist/tab.vue +++ b/docs/examples-dist/tab.vue @@ -4,7 +4,6 @@ <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> </example-block><example-block title="基础用法"> @@ -13,7 +12,6 @@ <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> </example-block><example-block title="设置切换tab的动画时间"> @@ -23,13 +21,24 @@ <van-tab title="选项三">内容三</van-tab> </van-tabs> - </example-block><example-block title="禁用tab"> + </example-block><example-block title="多于4个tab时"> <van-tabs> - <van-tab title="选项三">内容一</van-tab> - <van-tab title="选项二" disabled @disabled="popalert">内容二</van-tab> + <van-tab title="选项一">内容一</van-tab> + <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> <van-tab title="选项五">内容五</van-tab> + <van-tab title="选项六">内容六</van-tab> + <van-tab title="选项七">内容七</van-tab> + <van-tab title="选项八">内容八</van-tab> +</van-tabs> + + </example-block><example-block title="禁用tab"> + <van-tabs> + <van-tab title="选项一">内容一</van-tab> + <van-tab title="选项二" disabled @disabled="popalert">内容二</van-tab> + <van-tab title="选项三">内容三</van-tab> + <van-tab title="选项四">内容四</van-tab> </van-tabs> @@ -40,7 +49,6 @@ <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> </example-block><example-block title="自定义样式"> @@ -49,7 +57,6 @@ <van-tab title="选项二" class="custom-pane">内容二</van-tab> <van-tab title="选项三" class="custom-pane">内容三</van-tab> <van-tab title="选项四" class="custom-pane">内容四</van-tab> - <van-tab title="选项五" class="custom-pane">内容五</van-tab> </van-tabs> @@ -60,7 +67,6 @@ <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> diff --git a/docs/examples-docs/tab.md b/docs/examples-docs/tab.md index c8ba3ba11..a9e4e687e 100644 --- a/docs/examples-docs/tab.md +++ b/docs/examples-docs/tab.md @@ -97,7 +97,6 @@ export default { <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> ``` ::: @@ -113,7 +112,6 @@ export default { <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> ``` ::: @@ -132,6 +130,25 @@ export default { ``` ::: +#### 多于4个tab时 + +多于4个tab时,可以横向滚动tab。 + +:::demo 多于4个tab时 +```html +<van-tabs> + <van-tab title="选项一">内容一</van-tab> + <van-tab title="选项二">内容二</van-tab> + <van-tab title="选项三">内容三</van-tab> + <van-tab title="选项四">内容四</van-tab> + <van-tab title="选项五">内容五</van-tab> + <van-tab title="选项六">内容六</van-tab> + <van-tab title="选项七">内容七</van-tab> + <van-tab title="选项八">内容八</van-tab> +</van-tabs> +``` +::: + #### 禁用tab 在对应的`van-tab`上设置`disabled`属性即可,如果需要监听禁用事件,可以监听`disabled`事件。 @@ -139,11 +156,10 @@ export default { :::demo 禁用tab ```html <van-tabs> - <van-tab title="选项三">内容一</van-tab> + <van-tab title="选项一">内容一</van-tab> <van-tab title="选项二" disabled @disabled="popalert">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> <script> @@ -169,7 +185,6 @@ export default { <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> ``` ::: @@ -198,7 +213,6 @@ export default { <van-tab title="选项二" class="custom-pane">内容二</van-tab> <van-tab title="选项三" class="custom-pane">内容三</van-tab> <van-tab title="选项四" class="custom-pane">内容四</van-tab> - <van-tab title="选项五" class="custom-pane">内容五</van-tab> </van-tabs> <style> @@ -228,7 +242,6 @@ export default { <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三">内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> <script> diff --git a/packages/dialog/src/dialog.js b/packages/dialog/src/dialog.js index e3a92c7d8..962d8a950 100644 --- a/packages/dialog/src/dialog.js +++ b/packages/dialog/src/dialog.js @@ -32,6 +32,7 @@ const showNextDialog = () => { initInstance(); } + /* istanbul ignore else */ if (!instance.value && dialogQueue.length > 0) { currentDialog = dialogQueue.shift(); diff --git a/packages/image-preview/src/image-preview.js b/packages/image-preview/src/image-preview.js index 550258380..888693701 100644 --- a/packages/image-preview/src/image-preview.js +++ b/packages/image-preview/src/image-preview.js @@ -6,6 +6,7 @@ let instance; const ImagePreviewConstructor = Vue.extend(ImagePreview); const initInstance = () => { + /* istanbul ignore if */ if (Vue.prototype.$isServer) return; instance = new ImagePreviewConstructor({ el: document.createElement('div') @@ -13,6 +14,7 @@ const initInstance = () => { }; var ImagePreviewBox = images => { + /* istanbul ignore if */ if (Vue.prototype.$isServer) return; if (!instance) { initInstance(); diff --git a/packages/image-preview/src/image-preview.vue b/packages/image-preview/src/image-preview.vue index 0616ee2cb..570833330 100644 --- a/packages/image-preview/src/image-preview.vue +++ b/packages/image-preview/src/image-preview.vue @@ -92,6 +92,7 @@ export default { const supportTouch = !Vue.prototype.$isServer && 'ontouchstart' in window; const container = this.$refs.previewContainer; + /* istanbul ignore else */ if (supportTouch) { let touchStartTime; @@ -99,6 +100,7 @@ export default { touchStartTime = new Date(); }); container.addEventListener('touchend', () => { + /* istanbul ignore else */ if (new Date() - touchStartTime < 1500) { this.value = false; } diff --git a/packages/picker/src/picker-column.vue b/packages/picker/src/picker-column.vue index 01c831061..8dc8e8715 100644 --- a/packages/picker/src/picker-column.vue +++ b/packages/picker/src/picker-column.vue @@ -194,6 +194,7 @@ export default { }, end: () => { + /* istanbul ignore else */ if (this.isDragging) { this.isDragging = false; diff --git a/packages/tab/src/swipe.js b/packages/tab/src/swipe.js new file mode 100644 index 000000000..5b22acdf4 --- /dev/null +++ b/packages/tab/src/swipe.js @@ -0,0 +1,46 @@ +import Vue from 'vue'; + +let isSwiping = false; + +const supportTouch = !Vue.prototype.$isServer && 'ontouchstart' in window; + +export default function(element, options) { + const moveFn = function(event) { + if (options.drag) { + options.drag(supportTouch ? event.changedTouches[0] || event.touches[0] : event); + } + }; + + const endFn = function(event) { + if (!supportTouch) { + document.removeEventListener('mousemove', moveFn); + document.removeEventListener('mouseup', endFn); + } + + isSwiping = false; + + if (options.end) { + options.end(supportTouch ? event.changedTouches[0] || event.touches[0] : event); + } + }; + + element.addEventListener(supportTouch ? 'touchstart' : 'mousedown', function(event) { + if (isSwiping) return; + + if (!supportTouch) { + document.addEventListener('mousemove', moveFn); + document.addEventListener('mouseup', endFn); + } + isSwiping = true; + + if (options.start) { + options.start(supportTouch ? event.changedTouches[0] || event.touches[0] : event); + } + }); + + if (supportTouch) { + element.addEventListener('touchmove', moveFn); + element.addEventListener('touchend', endFn); + element.addEventListener('touchcancel', endFn); + } +}; diff --git a/packages/tab/src/tabs.vue b/packages/tab/src/tabs.vue index abb91a582..68c325819 100644 --- a/packages/tab/src/tabs.vue +++ b/packages/tab/src/tabs.vue @@ -1,8 +1,26 @@ <template> <div class="van-tabs" :class="[`van-tabs--${type}`]"> + <div class="van-tabs__nav-wrap" v-if="type === 'line' && tabs.length > 4"> + <div class="van-tabs__swipe" ref="swipe"> + <div class="van-tabs__nav van-tabs__nav--line"> + <div class="van-tabs__nav-bar" :style="navBarStyle"></div> + <div + v-for="(tab, index) in tabs" + :key="index" + class="van-tab" + :class="{'van-tab--active': index === curActive}" + ref="tabkey" + @click="handleTabClick(index, tab)" + > + {{ tab.title }} + </div> + </div> + </div> + </div> <div + v-else class="van-tabs__nav" - :class="[`van-tabs__nav--${this.type}`, `van-tabs--col-${this.tabs.length}`]" + :class="[`van-tabs__nav--${this.type}`]" > <div class="van-tabs__nav-bar" :style="navBarStyle" v-if="type === 'line'"></div> <div @@ -23,6 +41,9 @@ </template> <script> + import swipe from './swipe'; + import translateUtil from 'src/utils/transition'; + export default { name: 'van-tabs', @@ -48,13 +69,21 @@ return { tabs: [], isReady: false, - curActive: +this.active + curActive: +this.active, + isSwiping: false }; }, watch: { active(val) { this.curActive = +val; + }, + + curActive() { + /* istanbul ignore else */ + if (this.tabs.length > 4) { + this.doOnValueChange(); + } } }, @@ -72,12 +101,38 @@ return { width: offsetWidth, - transform: `translate3d(${offsetLeft}, 0px, 0px)`, + transform: `translate3d(${offsetLeft}, 0, 0)`, transitionDuration: `${this.duration}s` }; + }, + swipeWidth() { + return this.$refs.swipe && this.$refs.swipe.getBoundingClientRect().width; + }, + maxTranslate() { + /* istanbul ignore if */ + if (!this.$refs.tabkey) return; + + const lastTab = this.$refs.tabkey[this.tabs.length - 1]; + const lastTabWidth = lastTab.offsetWidth; + const lastTabOffsetLeft = lastTab.offsetLeft; + + return (lastTabOffsetLeft + lastTabWidth) - this.swipeWidth; } }, + mounted() { + // 页面载入完成 + this.$nextTick(() => { + // 可以开始触发在computed中关于nav-bar的css动画 + this.isReady = true; + this.initEvents(); + + if (this.tabs.length > 4) { + this.doOnValueChange(); + } + }); + }, + methods: { /** * tab点击事件 @@ -93,15 +148,67 @@ this.$emit('click', index); this.curActive = index; - } - }, + }, - mounted() { - // 页面载入完成 - this.$nextTick(() => { - // 可以开始触发在computed中关于nav-bar的css动画 - this.isReady = true; - }); + /** + * 将当前value值转换为需要translate的值 + */ + value2Translate(value) { + /* istanbul ignore if */ + if (!this.$refs.tabkey) return 0; + + const tab = this.$refs.tabkey[value]; + const maxTranslate = this.maxTranslate; + const tabWidth = tab.offsetWidth; + const tabOffsetLeft = tab.offsetLeft; + let translate = tabOffsetLeft + (tabWidth * 2.7) - this.swipeWidth; + if (translate < 0) { + translate = 0; + } + + return -1 * (translate > maxTranslate ? maxTranslate : translate); + }, + + initEvents() { + const el = this.$refs.swipe; + if (!el) return; + + let swipeState = {}; + + swipe(el, { + start: event => { + swipeState = { + start: new Date(), + startLeft: event.pageX, + startTranslateLeft: translateUtil.getElementTranslate(el).left + }; + }, + + drag: event => { + this.isSwiping = true; + + swipeState.left = event.pageX; + const deltaX = swipeState.left - swipeState.startLeft; + const translate = swipeState.startTranslateLeft + deltaX; + + /* istanbul ignore else */ + if (translate > 0 || (translate * -1) > this.maxTranslate ) return; + + translateUtil.translateElement(el, translate, null); + }, + + end: () => { + this.isSwiping = false; + } + }) + }, + + doOnValueChange() { + const value = +this.curActive; + const swipe = this.$refs.swipe; + + translateUtil.translateElement(swipe, this.value2Translate(value), null); + } } }; </script> diff --git a/packages/vant-css/src/tab.css b/packages/vant-css/src/tab.css index 2fe518cfe..f7ace23fe 100644 --- a/packages/vant-css/src/tab.css +++ b/packages/vant-css/src/tab.css @@ -5,27 +5,20 @@ @b tabs { position: relative; - @m col-2 { - .van-tab { - width: 50%; - } + @e nav-wrap { + overflow: hidden; } - @m col-3 { - .van-tab { - width: 33.33333333333333%; - } - } + @e swipe { + user-select: none; + transition: transform ease .3s; - @m col-4 { .van-tab { - width: 25%; + flex: 0 0 22%; } - } - @m col-5 { - .van-tab { - width: 20%; + .van-tabs__nav { + overflow: visible; } } @@ -33,16 +26,15 @@ overflow: hidden; transition: transform .5s cubic-bezier(.645, .045, .355, 1); position: relative; + display: flex; @m line { height: 44px; - background-color: $c-white; - &::after { - @mixin border-retina (top); - @mixin border-retina (bottom); - } - @b tabs-nav-bar { - display: block; + + .van-tab { + &::after { + @mixin border-retina (top, bottom); + } } } @@ -55,14 +47,16 @@ overflow: hidden; .van-tab { - color: #666666; + color: #666; line-height: 28px; - border-right: 1px solid #666666; + border-right: 1px solid #666; + &:last-child { border-right: none; } + &.van-tab--active { - background-color: #666666; + background-color: #666; color: $c-white; } } @@ -82,13 +76,16 @@ } @b tab { + position: relative; color: $c-black; + background-color: $c-white; font-size: 14px; line-height: 44px; box-sizing: border-box; cursor: pointer; text-align: center; - float: left; + flex: 1; + -webkit-tap-highlight-color: rgba(0,0,0,0); @m active { color: #FF4444; diff --git a/test/unit/components/more-tabs.vue b/test/unit/components/more-tabs.vue new file mode 100644 index 000000000..6313f92ca --- /dev/null +++ b/test/unit/components/more-tabs.vue @@ -0,0 +1,28 @@ +<template> + <van-tabs :active="active"> + <van-tab title="选项一">内容一</van-tab> + <van-tab title="选项二">内容二</van-tab> + <van-tab title="选项三">内容三</van-tab> + <van-tab title="选项四">内容四</van-tab> + <van-tab title="选项五">内容五</van-tab> + <van-tab title="选项六">内容六</van-tab> + <van-tab title="选项七">内容七</van-tab> + <van-tab title="选项八">内容八</van-tab> + </van-tabs> +</template> + +<script> +import Tab from 'packages/tab'; +import Tabs from 'packages/tabs'; + +export default { + components: { + 'van-tab': Tab, + 'van-tabs': Tabs + }, + + props: { + active: [String, Number] + } +}; +</script> diff --git a/test/unit/components/tabs.vue b/test/unit/components/tabs.vue index df871ba68..a63a15b0d 100644 --- a/test/unit/components/tabs.vue +++ b/test/unit/components/tabs.vue @@ -4,7 +4,6 @@ <van-tab title="选项二">内容二</van-tab> <van-tab title="选项三" disabled>内容三</van-tab> <van-tab title="选项四">内容四</van-tab> - <van-tab title="选项五">内容五</van-tab> </van-tabs> </template> diff --git a/test/unit/specs/dialog.spec.js b/test/unit/specs/dialog.spec.js index 2fad23a13..aa006b88f 100644 --- a/test/unit/specs/dialog.spec.js +++ b/test/unit/specs/dialog.spec.js @@ -1,4 +1,5 @@ import Dialog from 'packages/dialog'; +import Vue from 'vue'; describe('Dialog', () => { afterEach(() => { @@ -51,6 +52,6 @@ describe('Dialog', () => { document.querySelector('.van-dialog__cancel').click(); expect(dialogAction).to.equal('cancel'); done(); - }, 50); + }, 500); }); }); diff --git a/test/unit/specs/image-preview.spec.js b/test/unit/specs/image-preview.spec.js index 6475f4b81..d6ad06931 100644 --- a/test/unit/specs/image-preview.spec.js +++ b/test/unit/specs/image-preview.spec.js @@ -16,6 +16,7 @@ describe('ImagePreview', () => { it('create a image preview', (done) => { ImagePreview([ + 'https://img.yzcdn.cn/upload_files/2017/03/15/FkubrzN7AgGwLlTeb1E89-T_ZjBg.png', 'https://img.yzcdn.cn/upload_files/2017/03/14/FmTPs0SeyQaAOSK1rRe1sL8RcwSY.jpeg', 'https://img.yzcdn.cn/upload_files/2017/03/15/FvexrWlG_WxtCE9Omo5l27n_mAG_.jpeg' ]); @@ -40,6 +41,7 @@ describe('ImagePreview', () => { document.body.style.overflow = 'hidden'; ImagePreview([ + 'https://img.yzcdn.cn/upload_files/2017/03/15/FkubrzN7AgGwLlTeb1E89-T_ZjBg.png', 'https://img.yzcdn.cn/upload_files/2017/03/14/FmTPs0SeyQaAOSK1rRe1sL8RcwSY.jpeg', 'https://img.yzcdn.cn/upload_files/2017/03/15/FvexrWlG_WxtCE9Omo5l27n_mAG_.jpeg' ]); diff --git a/test/unit/specs/tabs.spec.js b/test/unit/specs/tabs.spec.js index 377ada337..eb6252a12 100644 --- a/test/unit/specs/tabs.spec.js +++ b/test/unit/specs/tabs.spec.js @@ -1,6 +1,7 @@ import Tabs from 'packages/tabs'; import { mount } from 'avoriaz'; import TabsTestComponent from '../components/tabs'; +import MoreTabsTestComponent from '../components/more-tabs'; describe('Tabs', () => { let wrapper; @@ -74,4 +75,75 @@ describe('Tabs', () => { expect(wrapper.style.transitionDuration != '').to.be.true; }); + + it('create a tabs greater then 4', (done) => { + wrapper = mount(MoreTabsTestComponent, { + attachToDocument: true + }); + + wrapper.vm.$nextTick(() => { + const nTab = wrapper.find('.van-tab')[4]; + nTab.trigger('click'); + done(); + }); + }); + + it('create a tabs greater then 4 then click last tab', (done) => { + wrapper = mount(MoreTabsTestComponent, { + attachToDocument: true, + propsData: { + active: 7 + } + }); + + wrapper.vm.$nextTick(() => { + const nTab = wrapper.find('.van-tab')[6]; + nTab.trigger('click'); + done(); + }); + }); + + it('test swipe', (done) => { + wrapper = mount(MoreTabsTestComponent, { + attachToDocument: true + }); + + setTimeout(() => { + const nSwipe = wrapper.find('.van-tabs__swipe')[0]; + + const eventMouseObject = new window.Event('mousedown'); + eventMouseObject.pageX = 200; + nSwipe.element.dispatchEvent(eventMouseObject); + + const eventTouchObject = new window.Event('touchstart'); + eventTouchObject.changedTouches = [{ pageX: 200 }]; + nSwipe.element.dispatchEvent(eventTouchObject); + }, 500); + + setTimeout(() => { + const nSwipe = wrapper.find('.van-tabs__swipe')[0]; + + const eventMouseMoveObject = new window.Event('mousemove'); + eventMouseMoveObject.pageX = 0; + document.dispatchEvent(eventMouseMoveObject); + + const eventObject = new window.Event('touchmove'); + eventObject.changedTouches = [{ pageX: 0 }]; + nSwipe.element.dispatchEvent(eventObject); + + // 结束滑动 + const eventMouseUpObject = new window.Event('mouseup'); + document.dispatchEvent(eventMouseUpObject); + const eventEndObject = new window.Event('touchend'); + eventEndObject.changedTouches = [{}]; + nSwipe.element.dispatchEvent(eventEndObject); + }, 1000); + + setTimeout(() => { + const nItem = wrapper.find('.van-tab')[0]; + expect(nItem.hasClass('van-tab--active')).to.be.true; + + done(); + }, 1200); + }); }); diff --git a/test/unit/specs/tag.spec.js b/test/unit/specs/tag.spec.js index c077e93ab..984beab2e 100644 --- a/test/unit/specs/tag.spec.js +++ b/test/unit/specs/tag.spec.js @@ -18,12 +18,4 @@ describe('Tag', () => { } }) }); - - it('create with wrong typeProps', () => { - wrapper = mount(Tag, { - propsData: { - type: 'wrong' - } - }) - }); });