Browse Source

chore: merge code

wangchunyu11 5 years ago
parent
commit
fa2a1df28b

+ 1 - 1
package.json

@@ -18,7 +18,7 @@
     "clean": "nutui-cli clean",
     "add": "nutui-cli add",
     "lint": "nutui-cli lint",
-    "test": "cross-env NODE_ENV=test nyc mocha-webpack --webpack-config node_modules/@nutui/cli/dist_cli/webpack/test.config.js  --require node_modules/@nutui/cli/dist_cli/test/setup.js src/packages/*/__test__/**.spec.js",
+    "test": "cross-env NODE_ENV=test nyc mocha-webpack --webpack-config node_modules/@nutui/cli/dist_cli/webpack/test.config.js  --require node_modules/@nutui/cli/dist_cli/test/setup.js src/packages/tab/__test__/**.spec.js",
     "coveralls": "cat ./coverage/lcov.info | coveralls",
     "test:watch": "npm run test --watch",
     "prettier:check": "prettier -l src/**/*.{ts,js,vue,scss}",

BIN
src/assets/img/checked.png


BIN
src/assets/img/unchecked.png


+ 4 - 0
src/assets/svg/rate-default-img.svg

@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M18,27.4l-7.9,4.1c-0.7,0.4-1.6,0.1-2-0.6c-0.2-0.3-0.2-0.6-0.2-1l1.5-8.8l0,0
+	L3.1,15c-0.6-0.6-0.6-1.5,0-2.1c0.2-0.2,0.5-0.4,0.9-0.4l8.8-1.3l0,0l3.9-8c0.4-0.7,1.3-1,2-0.7c0.3,0.1,0.5,0.4,0.7,0.7l3.9,8l0,0
+	l8.8,1.3c0.8,0.1,1.4,0.9,1.3,1.7c0,0.3-0.2,0.6-0.4,0.9l-6.4,6.2l0,0l1.5,8.8c0.1,0.8-0.4,1.6-1.2,1.7c-0.3,0.1-0.7,0-1-0.2
+	L18,27.4L18,27.4z" fill-rule="evenodd"/></svg>

+ 55 - 43
src/packages/actionsheet/actionsheet.scss

@@ -1,64 +1,76 @@
 @import '../../styles/animation/fade';
 @import '../../styles/animation/slide-up';
+
 .nut-actionsheet-mask {
-  @include fix-fullscreen();
+	@include fix-fullscreen();
 }
+
 .nut-actionsheet-panel {
-  position: fixed;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  width: 100%;
-  max-height: 80%;
-  overflow: auto;
-  z-index: $zindex-actionsheet;
-  background-color: $body-background;
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	width: 100%;
+	max-height: 80%;
+	overflow: auto;
+	z-index: $zindex-actionsheet;
+	background-color: $body-background;
 }
+
 .nut-actionsheet-modal {
-  padding: 10px;
-  margin: 0;
-  text-align: center;
-  background-color: #fff;
-  border-bottom: 1px solid $light-color;
-  .nut-actionsheet-title,
-  .nut-actionsheet-sub-title {
-    padding: 5px 0;
-  }
-  .nut-actionsheet-title {
-    font-size: $font-size-base;
-    color: $title-color;
-  }
-  .nut-actionsheet-sub-title {
-    font-size: $font-size-small;
-    color: $title-color;
-    margin-inline-start: 0px;
-  }
+	padding: 10px;
+	margin: 0;
+	text-align: center;
+	background-color: #fff;
+	border-bottom: 1px solid $light-color;
+
+	.nut-actionsheet-title,
+	.nut-actionsheet-sub-title {
+		padding: 5px 0;
+	}
+
+	.nut-actionsheet-title {
+		font-size: $font-size-base;
+		color: $title-color;
+	}
+
+	.nut-actionsheet-sub-title {
+		font-size: $font-size-small;
+		color: $title-color;
+		margin-inline-start: 0;
+	}
 }
+
 .nut-actionsheet-menu {
-  list-style: none;
-  padding: 0;
-  margin: 0;
+	list-style: none;
+	padding: 0;
+	margin: 0;
 }
+
 .nut-actionsheet-cancel,
 .nut-actionsheet-item {
-  height: 24px;
-  padding: 10px;
-  line-height: 24px;
-  font-size: $font-size-base;
-  color: $title-color;
-  text-align: center;
-  background-color: #fff;
+	height: 24px;
+	padding: 10px;
+	line-height: 24px;
+	font-size: $font-size-base;
+	color: $title-color;
+	text-align: center;
+	background-color: #fff;
 }
+
 .nut-actionsheet-item {
-  border-bottom: 1px solid $light-color;
+	border-bottom: 1px solid $light-color;
 }
+
 .nut-actionsheet-item-active {
-  color: $primary-color;
+	color: $primary-color;
 }
+
 .nut-actionsheet-item-disabled {
-  color: #e1e1e1;
+	color: #e1e1e1;
 }
+
 .nut-actionsheet-cancel {
-  margin-top: 5px;
-  border-top: 1px solid $light-color;
+	margin-top: 5px;
+	border-top: 1px solid $light-color;
 }

+ 12 - 1
src/packages/rate/__test__/rate.spec.js

@@ -26,5 +26,16 @@ describe('Rate.vue', () => {
         wrapper.findAll('.nut-rate-item').at(2).trigger('click');
         expect(wrapper.findAll('.nut-rate-item').at(2).is('.nut-rate-active')).toBe(true)
     });
-
+    
+    //只读状态点击
+    it('只读点击评分', () => {
+        wrapper.setProps({ readOnly: true });
+        wrapper.findAll('.nut-rate-item').at(2).trigger('click');
+        expect(wrapper.findAll('.nut-rate-item').at(2).is('.nut-rate-active')).toBe(true)
+    });
+    //点击半星
+    it('点击半星', () => {
+        wrapper.findAll('.halfstar_contain').at(2).trigger('click');
+        expect(wrapper.findAll('.halfstar_contain').at(2).is('.nut-rate-half-active')).toBe(true)
+    });
 });

+ 25 - 7
src/packages/rate/demo.vue

@@ -27,6 +27,17 @@
       </nut-cell>
     </div>
 
+    <h4>自定义颜色</h4>
+    <div>
+      <nut-cell>
+        <span slot="title">
+          <nut-rate :checkedColor="checkedColor" v-model="val3" >
+            <span class="fontcolor" slot="nut-rate-font">{{ val3 }},满意</span>
+          </nut-rate>
+        </span>
+      </nut-cell>
+    </div>
+
     <h4>事件</h4>
     <div>
       <nut-cell>
@@ -50,12 +61,14 @@
 export default {
   data() {
     return {
-      val: 4,
-      val2: 2,
+      val: 4.5,
+      val2: 2.5,
+      val3:3.5,
       result: '',
       result2: '',
-      icon1: `url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='rgb(255,0,0)' d='M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zM6.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm7 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm2.16 3H4.34a6 6 0 0 0 11.32 0z'/%3E%3C/svg%3E")`,
-      icon2: `url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='rgb(255,0,0)' d='M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM6.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm7 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM7 13h6a1 1 0 0 1 0 2H7a1 1 0 0 1 0-2z'/%3E%3C/svg%3E")`
+      checkedColor:'#FFC200',
+      icon1: require('./../../assets/img/checked.png'),
+      icon2: require('./../../assets/img/unchecked.png')
     };
   },
 
@@ -72,10 +85,15 @@ export default {
 
 <style lang="scss" scoped>
 .demo {
-  padding-left: 0;
-  padding-right: 0;
+	padding-left: 0;
+	padding-right: 0;
 }
+
 h4 {
-  padding: 0 10px;
+	padding: 0 10px;
+}
+
+.fontcolor {
+	color: #ffc200;
 }
 </style>

+ 14 - 2
src/packages/rate/doc.md

@@ -39,6 +39,15 @@
 </nut-rate>
 ```
 
+自定义颜色
+
+```html
+<nut-rate 
+    :checkedColor="checkedColor"
+>
+</nut-rate>
+```
+
 自定义ICON
 
 ```html
@@ -56,8 +65,8 @@ export default {
             val2:2,
             result:'',
             result2:'',
-            icon1:`url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='rgb(255,0,0)' d='M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zM6.5 9a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm7 0a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3zm2.16 3H4.34a6 6 0 0 0 11.32 0z'/%3E%3C/svg%3E")`,
-            icon2:`url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath fill='rgb(255,0,0)' d='M10 20a10 10 0 1 1 0-20 10 10 0 0 1 0 20zm0-2a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM6.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm7 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zM7 13h6a1 1 0 0 1 0 2H7a1 1 0 0 1 0-2z'/%3E%3C/svg%3E")`
+            icon1: require('./../../assets/img/checked.png'),
+            icon2: require('./../../assets/img/unchecked.png')
         }
     },
    
@@ -83,6 +92,9 @@ export default {
 | readOnly | 是否只读 | Boolean | false
 | uncheckedIcon | 使用图标(未选中) | String | -
 | checkedIcon | 使用图标(选中) | String | -
+| checkedColor | 选中star颜色 | String | -
+| showHalf | 是否显示文字 | Boolean | true
+
 
 ## Event
 | 字段 | 说明 | 回调参数 

+ 43 - 28
src/packages/rate/rate.scss

@@ -1,32 +1,47 @@
-@function toRGB($color) {
-  @return 'rgb(' + red($color) + ', ' + green($color) + ', ' + blue($color) + ')';
-}
+.nut-rate {
+	.nut-rate-item {
+		display: inline-block;
+		position: relative;
+		vertical-align: bottom;
+		width: 30px;
+		height: 30px;
+		margin-right: 15px;
+		color: #f0f0f0;
+		background-size: cover;
 
-@mixin nut-rate-bg($color) {
-  $svgColor: toRGB($color);
-  background: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='16' height='16' viewBox='0 0 16 16'%3E %3Cpath fill='#{$svgColor}' d='M16 6.204l-5.528-0.803-2.472-5.009-2.472 5.009-5.528 0.803 4 3.899-0.944 5.505 4.944-2.599 4.944 2.599-0.944-5.505 4-3.899zM8 11.773l-3.492 1.836 0.667-3.888-2.825-2.753 3.904-0.567 1.746-3.537 1.746 3.537 3.904 0.567-2.825 2.753 0.667 3.888-3.492-1.836z'%3E%3C/path%3E %3C/svg%3E")
-    center no-repeat;
-}
+		svg {
+			vertical-align: top;
+			width: 100%;
+			height: 100%;
+			fill: currentColor;
+		}
 
-@mixin nut-rate-active-bg($color) {
-  $svgColor: toRGB($color);
-  background-image: url("data:image/svg+xml,%3Csvg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='16' height='16' viewBox='0 0 16 16'%3E %3Cpath fill='#{$svgColor}' d='M16 6.204l-5.528-0.803-2.472-5.009-2.472 5.009-5.528 0.803 4 3.899-0.944 5.505 4.944-2.599 4.944 2.599-0.944-5.505 4-3.899z'%3E%3C/path%3E %3C/svg%3E");
-}
+		.halfstar_contain {
+			width: 50%;
+			height: 100%;
+			display: inline-block;
+			position: absolute;
+			top: 0;
+			left: 0;
+			overflow: hidden;
+			color: #f0f0f0;
 
-.nut-rate {
-  .nut-rate-item {
-    display: inline-block;
-    vertical-align: bottom;
-    width: 30px;
-    height: 30px;
-    margin-right: 15px;
-    @include nut-rate-bg($primary-color);
-    background-size: cover;
-    &:last-child {
-      margin-right: 0;
-    }
-    &.nut-rate-active {
-      @include nut-rate-active-bg($primary-color);
-    }
-  }
+			svg.halfstar {
+				height: 100%;
+				width: 200%;
+			}
+
+			&.nut-rate-half-active {
+				color: $primary-color;
+			}
+		}
+
+		&:last-child {
+			margin-right: 0;
+		}
+
+		&.nut-rate-active {
+			color: $primary-color;
+		}
+	}
 }

+ 50 - 5
src/packages/rate/rate.vue

@@ -9,11 +9,25 @@
       :style="{
         height: size + 'px',
         width: size + 'px',
+        color:`${n <= current ? checkedColor : ''}`,
         marginRight: spacing + 'px',
-        backgroundImage: n <= current ? checkedIcon : uncheckedIcon
+        backgroundImage: n <= current ? `url(${checkedIcon})` : `url(${uncheckedIcon})`,
       }"
     >
+      <i v-if="!checkedIcon">
+        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M18,27.4l-7.9,4.1c-0.7,0.4-1.6,0.1-2-0.6c-0.2-0.3-0.2-0.6-0.2-1l1.5-8.8l0,0
+	      L3.1,15c-0.6-0.6-0.6-1.5,0-2.1c0.2-0.2,0.5-0.4,0.9-0.4l8.8-1.3l0,0l3.9-8c0.4-0.7,1.3-1,2-0.7c0.3,0.1,0.5,0.4,0.7,0.7l3.9,8l0,0
+	      l8.8,1.3c0.8,0.1,1.4,0.9,1.3,1.7c0,0.3-0.2,0.6-0.4,0.9l-6.4,6.2l0,0l1.5,8.8c0.1,0.8-0.4,1.6-1.2,1.7c-0.3,0.1-0.7,0-1-0.2
+	      L18,27.4L18,27.4z" fill-rule="evenodd"/></svg>
+        <i v-if="showHalf" class="halfstar_contain" @click="halfClick($event, n)" :class="[{ 'nut-rate-half-active': halfStarClick ? n <= current+1 : n <= current}]" :style="{color:`${halfStarClick ? (n <= current+1 ? checkedColor : '') : (n <= current ? checkedColor : '')}`}">
+          <svg class="halfstar" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="M18,27.4l-7.9,4.1c-0.7,0.4-1.6,0.1-2-0.6c-0.2-0.3-0.2-0.6-0.2-1l1.5-8.8l0,0
+	        L3.1,15c-0.6-0.6-0.6-1.5,0-2.1c0.2-0.2,0.5-0.4,0.9-0.4l8.8-1.3l0,0l3.9-8c0.4-0.7,1.3-1,2-0.7c0.3,0.1,0.5,0.4,0.7,0.7l3.9,8l0,0
+	        l8.8,1.3c0.8,0.1,1.4,0.9,1.3,1.7c0,0.3-0.2,0.6-0.4,0.9l-6.4,6.2l0,0l1.5,8.8c0.1,0.8-0.4,1.6-1.2,1.7c-0.3,0.1-0.7,0-1-0.2
+	        L18,27.4L18,27.4z" fill-rule="evenodd"/></svg>
+        </i>
+      </i>
     </span>
+    <slot name="nut-rate-font"></slot>
   </div>
 </template>
 <script>
@@ -48,26 +62,57 @@ export default {
     spacing: {
       type: [String, Number],
       default: 20
+    },
+    checkedColor: {
+      type: [String],
+      default: null
+    },
+    showHalf:{
+      type: Boolean,
+      default: true
     }
   },
   data() {
     return {
-      current: 3
+      current: 3,
+      defaultImage:require('./../../assets/svg/rate-default-img.svg'),
+      halfStarClick:false
     };
   },
   created() {
-    this.current = this.value;
+    if(parseInt(this.value) === parseFloat(this.value)){
+       this.current = this.value;
+    }else{
+       this.current = this.value;
+       this.showHalf = true;
+       this.halfStarClick = true;
+    }
   },
   methods: {
     onClick($event, idx) {
+     
       if (this.readOnly) {
-        this.$emit('input', this.current);
-        this.$emit('click', this.current);
+        return
       } else {
+        this.halfStarClick=false;
         this.current = idx;
         this.$emit('input', idx);
         this.$emit('click', idx);
       }
+    },
+    halfClick($event, idx){
+      $event.stopPropagation();
+      if(!this.showHalf){
+        return
+      }
+      if (this.readOnly) {
+        return
+      } else {
+        this.halfStarClick=true;
+        this.current = idx-0.5;
+        this.$emit('input', idx-0.5);
+        this.$emit('click', idx-0.5);
+      }
     }
   }
 };

+ 0 - 8
src/packages/switch/__test__/switch.spec.js

@@ -56,14 +56,6 @@ describe('Switch.vue', () => {
         })
     });
 
-    it('尺寸设置为large', () => {
-        wrapper.setProps({ size: 'large' });
-
-        return Vue.nextTick().then(function () {
-            expect(wrapper.contains('.nut-switch-large')).toBe(true);
-        })
-    });
-
     it('打开状态点击关闭', () => {
         wrapper.setProps({ active: true });
         wrapper.trigger('click');

+ 27 - 46
src/packages/switch/demo.vue

@@ -1,29 +1,36 @@
 <template>
   <div class="demo-list">
-    <h4>基本用法</h4>
+    <h4>基础样式</h4>
     <div>
-      <nut-cell>
-        <span slot="title"><nut-switch :active.sync="swActive"></nut-switch></span>
-        <div slot="desc">{{ swActive }}</div>
-      </nut-cell>
+        <nut-cell>
+            <div slot="title">{{ swActive ? '开' : '关' }}</div>
+            <span slot="desc"><nut-switch :active.sync="swActive"></nut-switch></span>
+        </nut-cell>
+        <nut-cell>
+            <div slot="title">开启禁用</div>
+            <span slot="desc"><nut-switch :disabled="true"></nut-switch></span>
+        </nut-cell>
+        <nut-cell>
+            <div slot="title">关闭禁用</div>
+            <span slot="desc"><nut-switch @change="onChange" :active="true" :disabled="true"></nut-switch></span>
+        </nut-cell>
     </div>
 
-    <h4>自定义尺寸</h4>
+    <h4>迷你开关</h4>
     <div>
-      <nut-cell>
-        <span slot="title"><nut-switch :active="true" size="small"></nut-switch></span>
-        <div slot="desc">small</div>
-      </nut-cell>
-      <nut-cell>
-        <span slot="title"><nut-switch :active="true" size="base"></nut-switch></span>
-        <div slot="desc">base</div>
-      </nut-cell>
-      <nut-cell>
-        <span slot="title"><nut-switch :active="true" size="large"></nut-switch></span>
-        <div slot="desc">large</div>
-      </nut-cell>
+        <nut-cell>
+            <div slot="title">{{ swActive2 ? '开' : '关' }}</div>
+            <span slot="desc"><nut-switch :active.sync="swActive2" size="small"></nut-switch></span>
+        </nut-cell>
+        <nut-cell>
+            <div slot="title">开启禁用</div>
+            <span slot="desc"><nut-switch :disabled="true" size="small"></nut-switch></span>
+        </nut-cell>
+        <nut-cell>
+            <div slot="title">关闭禁用</div>
+            <span slot="desc"><nut-switch @change="onChange" :active="true" :disabled="true" size="small"></nut-switch></span>
+        </nut-cell>
     </div>
-    <p>内置"small","base","large"三种规格</p>
 
     <h4>change事件</h4>
     <div>
@@ -31,25 +38,6 @@
         <span slot="title"><nut-switch @change="onChange"></nut-switch></span>
       </nut-cell>
     </div>
-
-    <h4>禁用状态</h4>
-    <div>
-      <nut-cell>
-        <span slot="title"><nut-switch :disabled="true"></nut-switch></span>
-      </nut-cell>
-      <nut-cell>
-        <span slot="title"><nut-switch @change="onChange" :active="true" :disabled="true"></nut-switch></span>
-      </nut-cell>
-    </div>
-    <p>禁用状态下,change事件参数永远为初始值</p>
-
-    <h4>循环场景</h4>
-    <div v-for="(item, index) of list" :key="index">
-      <nut-cell>
-        <span slot="title"> {{ item.name }}</span>
-        <span slot="desc"><nut-switch @change="onChangeLabel($event, index)" :active="true"></nut-switch></span>
-      </nut-cell>
-    </div>
     <h4>自定义Class</h4>
     <div>
       <nut-cell>
@@ -64,19 +52,12 @@ export default {
   data() {
     return {
       swActive: true,
-      list: [
-        { id: '1', name: 'a' },
-        { id: '2', name: 'b' },
-        { id: '3', name: 'c' }
-      ]
+      swActive2: true
     };
   },
   methods: {
     onChange(status) {
       alert(status);
-    },
-    onChangeLabel(status, index) {
-      alert('status:' + status + ',selected:' + index);
     }
   }
 };

+ 3 - 7
src/packages/switch/doc.md

@@ -2,7 +2,7 @@
 
 用来打开或关闭选项。
 
-## 基本用法
+## 基础样式
 
 ```html
 <nut-switch 
@@ -30,7 +30,7 @@ export default {
 
 ## 自定义尺寸
 
-内置 **small**,**base**,**large** 三种规格供使用。
+内置 **small**,**base**种规格供使用。
 ```html
 <nut-switch 
   :active="true" 
@@ -40,10 +40,6 @@ export default {
   :active="true" 
   size="base"
 >
-<nut-switch 
-  :active="true" 
-  size="large"
->
 ```
 
 ## change事件
@@ -87,5 +83,5 @@ export default {
 | 字段 | 说明 | 类型 | 默认值
 |----- | ----- | ----- | -----
 | active | 开关状态 | Boolean | false
-| size | 尺寸,可选值small/base/large | String | base
+| size | 尺寸,可选值small/base | String | base
 | disabled | 是否禁用 | Boolean | false

+ 18 - 32
src/packages/switch/switch.scss

@@ -1,70 +1,56 @@
 .nut-switch {
   position: relative;
   display: inline-block;
-  background: #fff;
-  border-radius: 1000px;
+  background: #eee;
+  border-radius: 20px;
   vertical-align: bottom;
   box-sizing: content-box;
-  border: 2px $border-style-base $border-color-base;
+  border: 0;
   transition: all $transition-duration $animation-timing-fun;
   .nut-switch-btn {
     position: absolute;
     left: 0;
-    background: $border-color-base;
-    border: 2px solid #fff;
+    background: #fff;
+    border: 0;
     border-radius: 50%;
     box-sizing: border-box;
     transition: all $transition-duration $animation-timing-fun;
   }
   &.nut-switch-active {
-    border-color: $border-color-active;
-    .nut-switch-btn {
-      background-color: $primary-color;
-    }
+    background-color: $primary-color-jd-red;
   }
   &.nut-switch-disabled {
     opacity: 0.6;
   }
 }
 
-.nut-switch-small {
-  height: 14px;
-  width: 32px;
+.nut-switch-base {
+  height: 28px;
+  width: 54px;
   .nut-switch-btn {
-    height: 14px;
-    width: 14px;
+    height: 28px;
+    width: 28px;
   }
   &.nut-switch-active {
     .nut-switch-btn {
-      left: 18px;
+      left: 28px;
     }
   }
 }
 
-.nut-switch-base {
+.nut-switch-small {
   height: 20px;
-  width: 46px;
+  width: 38px;
+  &.nut-switch{
+      border-radius: 11px;
+  }
   .nut-switch-btn {
     height: 20px;
     width: 20px;
   }
   &.nut-switch-active {
     .nut-switch-btn {
-      left: 26px;
-    }
-  }
-}
-
-.nut-switch-large {
-  height: 28px;
-  width: 58px;
-  .nut-switch-btn {
-    height: 28px;
-    width: 28px;
-  }
-  &.nut-switch-active {
-    .nut-switch-btn {
-      left: 30px;
+      left: 19px;
     }
   }
 }

+ 51 - 61
src/packages/tab/__test__/tab.spec.js

@@ -5,14 +5,24 @@ import Vue from 'vue';
 
 describe('Tab.vue', () => {
     const wrapper = mount(Tab);
-    
-    // it('页签类型为based', () => {
-    //     wrapper.setProps({ type: 'based' });
-    //     return Vue.nextTick().then(function () {
-    //         expect(wrapper.contains('.based')).toBe(true);
-    //     })
-    // });
-
+    wrapper.setData({ tabTitleList: [
+      {
+        tabTitle: "衣物",
+        content: "<p>衣物内容</p>"
+      },
+      {
+        tabTitle: "日用品",
+        content: "<p>日用品内容</p>"
+      },
+      {
+        tabTitle: "运动器材",
+        content: "<p>运动器材内容</p>"
+      },
+      {
+        tabTitle: "电影票",
+        content: "<p>电影票内容</p>"
+      }
+    ]});
     it('当前tab的位置', () => {
         wrapper.setProps({ positionNav: 'left' });
         return Vue.nextTick().then(function () {
@@ -20,64 +30,44 @@ describe('Tab.vue', () => {
             
         })
     });
-
-    // it('是否显示内容区域', () => {
-    //     wrapper.setProps({ contentShow: true });
-    //     return Vue.nextTick().then(function () {
-    //         expect(wrapper.contains('.nut-tab-item')).toBe(true);
-            
-    //     })
-    // });
-
-    it('禁止选择第一个标签', () => {
-        wrapper.setData({ tabTitleList: [
-            {
-              tabTitle: "衣物",
-              disable: true,
-              iconUrl:
-                "http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg",
-              content: "<p>衣物内容</p>"
-            },
-            {
-              tabTitle: "日用品",
-              iconUrl:
-                "http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg",
-              content: "<p>日用品内容</p>"
-            },
-            {
-              tabTitle: "运动器材",
-              iconUrl:
-                "http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg",
-              content: "<p>运动器材内容</p>"
-            },
-            {
-              tabTitle: "电影票",
-              iconUrl:
-                "http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg",
-              content: "<p>电影票内容</p>"
-            }
-          ] });
-          return Vue.nextTick().then(function () {
-              expect(wrapper.findAll('.nut-title-nav-leftnav').at(0).is('.nut-tab-disable')).toBe(true)
-          }) 
+    it('当前tab是否可以滑动', () => {
+      wrapper.setProps({ isScroll: 'true' ,positionNav:'top'});
+      return Vue.nextTick().then(function () {
+          expect(wrapper.contains('.nut-title-nav-scroll')).toBe(true);
+      })
     });
-    it('当前默认选中的tab', () => {
-        wrapper.setProps({ positionNav: 'top' });
-        return Vue.nextTick().then(function () {
-            expect(wrapper.findAll('.nut-title-nav-list').at(0).is('.nut-tab-active')).toBe(true)
-        })
+    it('当前tab包含滑动的底部导航条', () => {
+      wrapper.setProps({ scrollLine: 'true' ,positionNav:'top'});
+      return Vue.nextTick().then(function () {
+          expect(wrapper.contains('.nav-bar-top')).toBe(true);
+      })
+    });
+    it('当前tab包含点击式的底部导航条', () => {
+      wrapper.setProps({ tabLine: 'true' ,positionNav:'top'});
+      return Vue.nextTick().then(function () {
+          expect(wrapper.contains('.tab-line')).toBe(true);
+      })
+    });
+    it('当前tab默认活动的页签是否正确', () => {
+      wrapper.setProps({ tabLine: 'true', positionNav:'top'});
+      wrapper.setData({ activeIndex: 1});
+      return Vue.nextTick().then(function () {
+        expect(wrapper.findAll('.nut-title-nav').at(1).contains('.nut-tab-active')).toBe(true);
+      })
+    });
+    it('当前tab设置高度是否生效', () => {
+      wrapper.setProps({wrapperHeight:'250', positionNav:'left'});
+      return Vue.nextTick().then(function () {
+        expect(wrapper.findAll('.nut-tab-item').at(0).hasStyle('height','250px')).toBe(true);
+      })
     });
-    it('tab标签标题', () => {
+    it('点击tab事件', () => {
+      wrapper.setProps({positionNav:'top'});
       return Vue.nextTick().then(function () {
-         expect(wrapper.findAll('.nut-title-nav').at(0).text()).toBe('衣物');
+        wrapper.findAll('.nut-title-nav').at(2).trigger('click');
+        expect(wrapper.findAll('.nut-title-nav').at(1).contains('.nut-tab-active')).toBe(true);
       })
     });
-    // it('点击tab标签', () => {
-    // 	return Vue.nextTick().then(function () {
-    //         wrapper.findAll('.nut-title-nav-list').at(1).trigger('click');
-    //         expect(wrapper.findAll('.nut-title-nav-list').at(1).is('.nut-tab-active')).toBe(true)
-    //   })
-    // });
 });
 
 

+ 120 - 105
src/packages/tab/demo.vue

@@ -1,25 +1,24 @@
 <template>
   <div>
     <!-- DEMO区域 -->
-    <h4>默认用法</h4>
+    <h4>基础样式</h4>
     <nut-tab @tab-switch="tabSwitch">
-      <nut-tab-panel tab-title="页签1">页签1</nut-tab-panel>
-      <nut-tab-panel tab-title="页签2">页签2</nut-tab-panel>
-      <nut-tab-panel tab-title="页签3">页签3</nut-tab-panel>
-      <nut-tab-panel tab-title="页签4">页签4</nut-tab-panel>
+        <nut-tab-panel tab-title="页签一">这里是页签1内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签二">这里是页签2内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签三">这里是页签3内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签四">这里是页签4内容</nut-tab-panel>
     </nut-tab>
 
-    <h4>支持导航条在上下左右位置</h4>
+    <h4>禁用样式</h4>
     <nut-tab @tab-switch="tabSwitch">
-      <nut-tab-panel
-        v-for="value in editableTabs"
-        v-bind:key="value.tabTitle"
-        :tab-title="value.tabTitle"
-        :icon-url="value.iconUrl"
-        v-html="value.content"
-      ></nut-tab-panel>
+        <nut-tab-panel tab-title="页签一">这里是页签1内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签二">这里是页签2内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签三" :disable="true">这里是页签3内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签四">这里是页签4内容</nut-tab-panel>
     </nut-tab>
-    <nut-tab @tab-switch="tabSwitch" position-nav="left">
+
+    <h4>超出界面宽度</h4>
+    <nut-tab @tab-switch="tabSwitch" :is-scroll="true">
       <nut-tab-panel
         v-for="value in editableTabs"
         v-bind:key="value.tabTitle"
@@ -28,17 +27,20 @@
         v-html="value.content"
       ></nut-tab-panel>
     </nut-tab>
-    <nut-tab @tab-switch="tabSwitch" position-nav="right">
+
+    <h4>纵向tab切换</h4>
+    <nut-tab @tab-switch="tabSwitch" position-nav="left" :wrapper-height="350">
       <nut-tab-panel
         v-for="value in editableTabs"
         v-bind:key="value.tabTitle"
         :tab-title="value.tabTitle"
-        :iconUrl="value.iconUrl"
+        :icon-url="value.iconUrl"
         v-html="value.content"
       ></nut-tab-panel>
     </nut-tab>
 
-    <nut-tab @tab-switch="tabSwitch" position-nav="bottom">
+    <h4>纵向tab切换超出界面高度,设置tab区域高度</h4>
+    <nut-tab @tab-switch="tabSwitch" position-nav="left" :is-scroll="true" :wrapper-height="200">
       <nut-tab-panel
         v-for="value in editableTabs"
         v-bind:key="value.tabTitle"
@@ -48,36 +50,21 @@
       ></nut-tab-panel>
     </nut-tab>
 
-    <h4>支持滑动选择多个页签</h4>
-    <nut-tab @tab-switch="tabSwitch" :is-scroll="true">
-      <nut-tab-panel tab-title="页签1">页签1</nut-tab-panel>
-      <nut-tab-panel tab-title="页签2">页签2</nut-tab-panel>
-      <nut-tab-panel tab-title="页签3">页签3</nut-tab-panel>
-      <nut-tab-panel tab-title="页签4">页签4</nut-tab-panel>
-      <nut-tab-panel tab-title="页签5">页签5</nut-tab-panel>
-      <nut-tab-panel tab-title="页签6">页签6</nut-tab-panel>
-      <nut-tab-panel tab-title="页签7">页签7</nut-tab-panel>
+    <h4>设置可以滑动的线段</h4>
+    <nut-tab @tab-switch="tabSwitch" :tab-line="false" :scroll-line="true">
+        <nut-tab-panel tab-title="页签一">这里是页签1内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签二">这里是页签2内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签三">这里是页签3内容</nut-tab-panel>
+        <nut-tab-panel tab-title="页签四">这里是页签4内容</nut-tab-panel>
     </nut-tab>
-
-    <h4>支持滑动选择多个页签</h4>
-    <nut-tab @tab-switch="tabSwitch" :is-scroll="true" position-nav="left">
-      <nut-tab-panel tab-title="页签1">页签1</nut-tab-panel>
-      <nut-tab-panel tab-title="页签2">页签2</nut-tab-panel>
-      <nut-tab-panel tab-title="页签3">页签3</nut-tab-panel>
-      <nut-tab-panel tab-title="页签4">页签4</nut-tab-panel>
-      <nut-tab-panel tab-title="页签5">页签5</nut-tab-panel>
-      <nut-tab-panel tab-title="页签6">页签6</nut-tab-panel>
-      <nut-tab-panel tab-title="页签7">页签7</nut-tab-panel>
-    </nut-tab>
-
-    <h4>禁止选中,默认选中某个标签</h4>
-    <h4>如需要更新页面,请将监听变化的数据传入init-data</h4>
-
-    <nut-tab :def-index="defIndex" class="customer-css" @tab-switch="tabSwitch" :contentShow="true" :init-data="disableTabs" :is-show-line="false">
+    
+    <h4>若更新页面,将监听变化的数据传入init-data</h4>
+    <nut-tab :def-index="defIndex" class="customer-css" :tab-line="false" @tab-switch="tabSwitch" :init-data="disableTabs"  :scroll-line="true">
       <nut-tab-panel
         v-for="value in disableTabs"
         v-bind:key="value.tabTitle"
         :tab-title="value.tabTitle"
+        :icon-url="value.iconUrl"
         :disable="value.disable"
         v-html="value.content"
       ></nut-tab-panel>
@@ -91,112 +78,140 @@
 
 <script>
 export default {
-  components: {},
+  components:{
+  },
   data() {
     return {
-      defIndex: 1,
-      positionNavCurr: 'top',
+      defIndex:1,
+      positionNavCurr: "top",
       editableTabs: [
         {
-          tabTitle: '衣物',
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg',
-          content: '<p>衣物内容</p>'
+          tabTitle: "推荐分类",
+          iconUrl: "",
+          content: "<p>这里是推荐分类内容</p>"
+        },
+        {
+          tabTitle: "家电清洗",
+          iconUrl: "",
+          content: "<p>这里是家电清洗内容</p>"
+        },
+        {
+          tabTitle: "箱包养护",
+          iconUrl: "",
+          content: "<p>这里是箱包养护内容</p>"
         },
         {
-          tabTitle: '日用品',
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg',
-          content: '<p>日用品内容</p>'
+          tabTitle: "屏幕换新",
+          iconUrl: "",
+          content: "<p>这里是屏幕换新内容</p>"
         },
         {
-          tabTitle: '器材',
-          iconUrl: 'http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg',
-          content: '<p>运动器材内容</p>'
+          tabTitle: "电池换新",
+          iconUrl: "",
+          content: "<p>这里是电池换新内容</p>"
         },
         {
-          tabTitle: '电影票',
-          iconUrl: 'http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg',
-          content: '<p>电影票内容</p>'
+          tabTitle: "内存升级",
+          iconUrl: "",
+          content: "<p>这里是内存升级内容</p>"
+        },
+        {
+          tabTitle: "家电服务",
+          iconUrl: "",
+          content: "<p>这里是家电服务内容</p>"
         }
       ],
       disableTabs: [
         {
-          tabTitle: '衣物',
-          disable: false,
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg',
-          content: '<p>衣物内容</p>'
+          tabTitle: "衣物",
+          disable:true,
+          iconUrl:
+            "http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg",
+          content: "<p>衣物内容</p>"
         },
         {
-          tabTitle: '日用品',
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg',
-          content: '<p>日用品内容</p>'
+          tabTitle: "日用品",
+          iconUrl:
+            "http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg",
+          content: "<p>日用品内容</p>"
         },
         {
-          tabTitle: '运动器材',
-          iconUrl: 'http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg',
-          content: '<p>运动器材内容</p>'
+          tabTitle: "运动器材",
+          iconUrl:
+            "http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg",
+          content: "<p>运动器材内容</p>"
         },
         {
-          tabTitle: '电影票',
-          iconUrl: 'http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg',
-          content: '<p>电影票内容</p>'
+          tabTitle: "电影票",
+          iconUrl:
+            "http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg",
+          content: "<p>电影票内容</p>"
         }
       ]
     };
   },
   methods: {
     tabSwitch: function(index, event) {
-      console.log(index + '--' + event);
+      console.log(index + "--" + event);
       //this.defIndex = index;
     },
-    clickHandler: function() {
+    clickHandler:function(){
       let newEditableTabs = [
         {
-          tabTitle: '衣物2',
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg',
-          content: '<p>改变衣物内容</p>'
+          tabTitle: "衣物2",
+          iconUrl:
+            "http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg",
+          content: "<p>改变衣物内容</p>"
         },
         {
-          tabTitle: '日用品2',
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg',
-          content: '<p>改变日用品内容</p>'
+          tabTitle: "日用品2",
+          iconUrl:
+            "http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg",
+          content: "<p>改变日用品内容</p>"
         },
         {
-          tabTitle: '器材2',
-          iconUrl: 'http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg',
-          content: '<p>改变运动器材内容</p>'
+          tabTitle: "器材2",
+          iconUrl:
+            "http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg",
+          content: "<p>改变运动器材内容</p>"
         },
         {
-          tabTitle: '电影票2',
-          iconUrl: 'http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg',
-          content: '<p>改变电影票内容</p>'
+          tabTitle: "电影票2",
+          iconUrl:
+            "http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg",
+          content: "<p>改变电影票内容</p>"
         }
-      ];
+      ]
       this.disableTabs = newEditableTabs;
     },
-    resetHandler: function() {
-      let newEditableTabs = [
-        {
-          tabTitle: '衣物',
-          disable: false,
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg',
-          content: '<p>衣物内容</p>'
+    resetHandler:function(){
+        let newEditableTabs = [
+        {
+          tabTitle: "衣物",
+          disable: true,
+          iconUrl:
+            "http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg",
+          content: "<p>衣物内容</p>"
         },
         {
-          tabTitle: '日用品',
-          iconUrl: 'http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg',
-          content: '<p>日用品内容</p>'
+          tabTitle: "日用品",
+          iconUrl:
+            "http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg",
+          content: "<p>日用品内容</p>"
         },
         {
-          tabTitle: '运动器材',
-          iconUrl: 'http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg',
-          content: '<p>运动器材内容</p>'
+          tabTitle: "运动器材",
+          iconUrl:
+            "http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg",
+          content: "<p>运动器材内容</p>"
         },
         {
-          tabTitle: '电影票',
-          iconUrl: 'http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg',
-          content: '<p>电影票内容</p>'
+          tabTitle: "电影票",
+          iconUrl:
+            "http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg",
+          content: "<p>电影票内容</p>"
         }
-      ];
+      ]
       this.disableTabs = newEditableTabs;
     }
   }
@@ -206,17 +221,16 @@ export default {
 <style lang="scss">
 .customer-css {
   .nut-tab-active .nut-tab-link {
-    color: #fff;
+    color: #E1251B;
   }
   .nut-title-nav-list {
     background: #fff;
     border-left: 1px solid #e4e7ed;
-    &:first-child {
+    &:first-child{
       border-left: 0;
     }
   }
   .nut-tab-active {
-    background: $primary-color;
     border: 0;
     transition: all 0.3s ease-in-out;
   }
@@ -228,3 +242,4 @@ export default {
   }
 }
 </style>
+

+ 114 - 72
src/packages/tab/doc.md

@@ -1,18 +1,19 @@
 # Tab 选项卡
 
-常用于平级区域大块内容的的收纳和展现。
+常用于平级区域大块内容的的收纳和展现,支持内嵌标签形式和渲染循环数据形式
 
 > 注意,使用该组件需要在引入 nut-tab-panel 组件
 
 `import { TabPanel } from "@nutui/nutui";`
 
-## 基本用法
+## 基础样式
 
 ```html
 <nut-tab @tab-switch="tabSwitch">
-    <nut-tab-panel tab-title="页签1">页签1</nut-tab-panel>
-    <nut-tab-panel tab-title="页签2">页签2</nut-tab-panel>
-    <nut-tab-panel tab-title="页签3">页签3</nut-tab-panel>
+    <nut-tab-panel tab-title="页签一">这里是页签1内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签二">这里是页签2内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签三">这里是页签3内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签四">这里是页签4内容</nut-tab-panel>
 </nut-tab>
 ```
 ```javascript
@@ -25,97 +26,135 @@ export default {
 };
 ```
 
-## 使用数据渲染,支持上/下/左/右四个样式;
+## 禁用样式:
 
 ```html
-<nut-tab @tab-switch="tabSwitch"  position-nav="bottom">
-    <nut-tab-panel 
-      v-for="value in editableTabs" 
-      v-bind:key="value.tabTitle" 
-      :tab-title="value.tabTitle" 
-      :icon-url="value.tabUrl" 
-      v-html="value.content"
-    >
-    </nut-tab-panel>
+<nut-tab @tab-switch="tabSwitch">
+    <nut-tab-panel tab-title="页签一">这里是页签1内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签二">这里是页签2内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签三" :disable="true">这里是页签3内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签四">这里是页签4内容</nut-tab-panel>
+</nut-tab>
+```
+
+## 超出界面宽度
+```html
+<nut-tab @tab-switch="tabSwitch" :is-scroll="true">
+  <nut-tab-panel
+    v-for="value in editableTabs"
+    v-bind:key="value.tabTitle"
+    :tab-title="value.tabTitle"
+    :icon-url="value.iconUrl"
+    v-html="value.content"
+  ></nut-tab-panel>
 </nut-tab>
 ```
 ```javascript
 export default {
   data() {
-    return {
-      positionNavCurr:'top',
-      editableTabs:[
-        {
-          'tabTitle':'衣物',
-          'tabUrl':'http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg',
-          'content':'<p>衣物内容</p>'
-        },
-        {
-          'tabTitle':'日用品',
-          'tabUrl':'http://img13.360buyimg.com/uba/jfs/t30331/209/562746340/2190/6619973d/5bf763aaN6ff02099.jpg',
-          'content':'<p>日用品内容</p>'
-        },
-        {
-          'tabTitle':'器材',
-          'tabUrl':'http://img20.360buyimg.com/uba/jfs/t30346/262/553689202/2257/5dfa3983/5bf76407N72deabf4.jpg',
-          'content':'<p>运动器材内容</p>'
-        },
-        {
-          'tabTitle':'电影票',
-          'tabUrl':'http://img10.360buyimg.com/uba/jfs/t26779/215/2118525153/2413/470d1613/5bf767b2N075957b7.jpg',
-          'content':'<p>电影票内容</p>'
-        }
-      ]
-    };
-  },
-  methods: {
-      tabSwitch:function(index,event){
-        console.log(index+'--'+event);
+      return {
+          editableTabs: [
+          {
+            tabTitle: "推荐分类",
+            iconUrl: "",
+            content: "<p>这里是推荐分类内容</p>"
+          },
+          {
+            tabTitle: "家电清洗",
+            iconUrl: "",
+            content: "<p>这里是家电清洗内容</p>"
+          },
+          {
+            tabTitle: "箱包养护",
+            iconUrl: "",
+            content: "<p>这里是箱包养护内容</p>"
+          },
+          {
+            tabTitle: "屏幕换新",
+            iconUrl: "",
+            content: "<p>这里是屏幕换新内容</p>"
+          },
+          {
+            tabTitle: "电池换新",
+            iconUrl: "",
+            content: "<p>这里是电池换新内容</p>"
+          },
+          {
+            tabTitle: "内存升级",
+            iconUrl: "",
+            content: "<p>这里是内存升级内容</p>"
+          },
+          {
+            tabTitle: "家电服务",
+            iconUrl: "",
+            content: "<p>这里是家电服务内容</p>"
+          }
+        ]
       }
   }
 };
 ```
-## 支持滑动选择多个页签
+
+## 纵向tab切换
 
 ```html
-  <nut-tab @tab-switch="tabSwitch" :is-scroll="true">
-      <nut-tab-panel tab-title="页签1">页签1</nut-tab-panel>
-      <nut-tab-panel tab-title="页签2">页签2</nut-tab-panel>
-      <nut-tab-panel tab-title="页签3">页签3</nut-tab-panel>
-      <nut-tab-panel tab-title="页签4">页签4</nut-tab-panel>
-      <nut-tab-panel tab-title="页签5">页签5</nut-tab-panel>
-      <nut-tab-panel tab-title="页签6">页签6</nut-tab-panel>
-      <nut-tab-panel tab-title="页签7">页签7</nut-tab-panel>
-  </nut-tab>
+<nut-tab @tab-switch="tabSwitch" position-nav="left" :wrapper-height="350">
+  <nut-tab-panel
+    v-for="value in editableTabs"
+    v-bind:key="value.tabTitle"
+    :tab-title="value.tabTitle"
+    :icon-url="value.iconUrl"
+    v-html="value.content"
+  ></nut-tab-panel>
+</nut-tab>
 ```
 
+## 纵向tab切换超出界面高度,设置tab区域高度
+
 ```html
-  <nut-tab @tab-switch="tabSwitch" :is-scroll="true" position-nav="left">
-      <nut-tab-panel tab-title="页签1">页签1</nut-tab-panel>
-      <nut-tab-panel tab-title="页签2">页签2</nut-tab-panel>
-      <nut-tab-panel tab-title="页签3">页签3</nut-tab-panel>
-      <nut-tab-panel tab-title="页签4">页签4</nut-tab-panel>
-      <nut-tab-panel tab-title="页签5">页签5</nut-tab-panel>
-      <nut-tab-panel tab-title="页签6">页签6</nut-tab-panel>
-      <nut-tab-panel tab-title="页签7">页签7</nut-tab-panel>
-  </nut-tab>
+<nut-tab @tab-switch="tabSwitch" position-nav="left" :is-scroll="true" :wrapper-height="200">
+  <nut-tab-panel
+    v-for="value in editableTabs"
+    v-bind:key="value.tabTitle"
+    :tab-title="value.tabTitle"
+    :icon-url="value.iconUrl"
+    v-html="value.content"
+  ></nut-tab-panel>
+</nut-tab>
 ```
 
-## 禁止选中,默认选中某个标签,如需更新数组后,重新渲染Tab页面,请将更新数组传入init-data
+## 设置可以滑动的线段
+
+```html
+<nut-tab @tab-switch="tabSwitch" :tab-line="false" :scroll-line="true">
+    <nut-tab-panel tab-title="页签一">这里是页签1内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签二">这里是页签2内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签三">这里是页签3内容</nut-tab-panel>
+    <nut-tab-panel tab-title="页签四">这里是页签4内容</nut-tab-panel>
+</nut-tab>
+```
 
+## 若更新页面,将监听变化的数据传入init-data,且可以自定义class类名,样式覆盖
 ```html
-<nut-tab :def-index="1" class="customer-css" @tab-switch="tabSwitch" :contentShow="true" :init-data="disableTabs">
-    <nut-tab-panel
+<nut-tab 
+  :def-index="defIndex" 
+  class="customer-css" 
+  :tab-line="false" 
+  @tab-switch="tabSwitch" 
+  :init-data="disableTabs"  
+  :scroll-line="true">
+  <nut-tab-panel
     v-for="value in disableTabs"
     v-bind:key="value.tabTitle"
     :tab-title="value.tabTitle"
+    :icon-url="value.iconUrl"
     :disable="value.disable"
     v-html="value.content"
   ></nut-tab-panel>
 </nut-tab>
 <div style="width:100%;height=50px;text-align:center">
-  <Button @click="resetHandler" type="light">重置Tab页面</Button>
-  <Button @click="clickHandler">更新Tab页面</Button>
+  <nut-button @click="resetHandler" type="light">重置Tab页面</nut-button>
+  <nut-button @click="clickHandler">更新Tab页面</nut-button>
 </div>
 ```
 
@@ -128,7 +167,7 @@ export default {
       disableTabs:[
         {
           'tabTitle':'衣物',
-          'disable':false,
+          'disable':true,
           'tabUrl':'http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg',
           'content':'<p>衣物内容</p>'
         },
@@ -187,7 +226,7 @@ export default {
           let newEditableTabs = [
           {
             tabTitle: "衣物",
-            disable: false,
+            disable: true,
             iconUrl:
               "http://img13.360buyimg.com/uba/jfs/t27280/289/2061314663/2392/872e32ff/5bf76318Ndc80c1d8.jpg",
             content: "<p>衣物内容</p>"
@@ -227,8 +266,11 @@ export default {
 | position-nav | 页签栏的分布,可选值 top/bottom/left/right | String | top
 | def-index | 默认选中的页签栏 | String | 1
 | init-data | 监听数据变化,渲染更新页面 | Array | []
-| is-show-line|是否显示tab切换时的红条|Boolean|true|
-| is-scroll|是否支持滑动选择多个页签|Boolean|false|
+| scroll-line|是否显示tab切换时,滑动效果的红条|Boolean|false|
+| tab-line|是否显示tab切换时,无滑动效果的active红条|Boolean|true|
+| is-scroll|是否支持超出范围后,滑动选择多个页签|Boolean|false|
+| wrapper-height |设置tab区域的高度,只有在positionNav=left或者right 的情况下有效|Number/String|200|
+
 
 ### nut-tab-panel
 

+ 120 - 45
src/packages/tab/tab.scss

@@ -1,9 +1,5 @@
-.nut-tab {
-  position: relative;
-  border: 1px solid #eee;
-  padding: 10px;
-  font-size: 12px;
-  background: #eee;
+.nut-tab-part{
+  margin-bottom: 20px;
 }
 .nut-tab-horizontal {
   display: flex;
@@ -39,22 +35,45 @@
   top: 0px;
   border-radius: 50%;
 }
-.nut-tab-title {
+.nut-tab-title-topnav {
   //border:1px solid #fff;
   border-bottom: 1px solid #ededed;
+  scroll-behavior: smooth;
   width: 100%;
   display: flex;
-  height: 50px;
+  height: 48px;
   line-height: 48px;
   box-sizing: border-box;
   position: relative;
   overflow-x: auto;
   overflow-y: hidden;
   &::-webkit-scrollbar {
-    display: none;
+      display: none;
+  }
+  .nut-tab-active {
+    background: #fff;
+    position: relative;
+    border: 0;
+    .nut-tab-link{
+        color: #E1251B;
+        font-weight: bold;
+    }
+    .tab-line{
+      &:after{
+        content: "";
+        position: absolute;
+        width: 30%;
+        transform: translateX(-50%);
+        height: 2px;
+        background: #E1251B;
+        bottom: 0px;
+        left: 50%;
+        z-index: 20;
+      }
+    }
   }
 }
-.nav-bar {
+.nav-bar-top {
   position: absolute;
   height: 2px;
   bottom: 0px;
@@ -64,17 +83,17 @@
   transition: all 0.3s ease-in-out;
 }
 .nut-tab-title-leftnav {
-  border-right: 1px solid #ededed;
-  width: 100px;
+  border-right: 1px solid #fff;
+  scroll-behavior: smooth;
+  width: 90px;
   display: flex;
-  background: #fff;
   flex-direction: column;
   position: relative;
-  height: 200px;
+  // height: 200px;
   overflow-y: auto;
   overflow-x: hidden;
   &::-webkit-scrollbar {
-    display: none;
+      display: none;
   }
   .nav-bar-left {
     position: absolute;
@@ -87,42 +106,76 @@
   }
   .nut-title-nav {
     border: 0;
+    background: #F7F7F7;
     border-left: 1px solid #f5f7fa;
   }
   .nut-tab-active {
-    background: #fff;
-    //   a{
-    //     color: red;
-    //   }
+    .nut-tab-link{
+      position: relative;
+      background: #fff;
+      color: #E1251B;
+      font-weight: bold;
+    }
+    .tab-line{
+      &:after{
+        content: "";
+        position: absolute;
+        height: 100%;
+        width: 3px;
+        background: #E1251B;
+        left: 0px;
+        top: 0;
+        z-index: 2;
+      }
+    }
   }
 }
 .nut-tab-title-rightnav {
-  background: #fff;
+  background: #F7F7F7;
   // border: 1px solid #fff;
-  border-left: 1px solid #ededed;
+  scroll-behavior: smooth;
+  border-left: 1px solid #fff;
   width: 100px;
   display: flex;
   flex-direction: column;
   position: relative;
   overflow-y: auto;
   overflow-x: hidden;
-  height: 200px;
   box-sizing: border-box;
   &::-webkit-scrollbar {
-    display: none;
+      display: none;
   }
   .nav-bar-right {
     position: absolute;
     width: 2px;
     left: 0px;
     top: 0px;
-    z-index: 2;
-    background: $primary-color;
+    background: #E1251B;
+    z-index:2;
     transition: all 0.3s ease-in-out;
   }
+  .nut-tab-active {
+    .nut-tab-link{
+      background: #fff;
+      color: #E1251B;
+      font-weight: bold;
+    }
+    .tab-line{
+      &:after{
+        content: "";
+        position: absolute;
+        height: 100%;
+        width: 3px;
+        background: #E1251B;
+        right: 0px;
+        top: 0;
+        z-index: 2;
+      }
+    }
+  }
 }
 .nut-tab-link {
-  color: #333;
+  color: #969696;
   display: flex;
   align-items: center;
   justify-content: center;
@@ -130,21 +183,23 @@
   text-decoration: none;
   line-height: 1;
   width: 100%;
-  height: 100%;
+  height:100%;
+  font-size: 13px;
 }
 .nut-tab-title-bottomnav {
   border: 1px solid #fff;
+  scroll-behavior: smooth;
   border-top: 1px solid #ededed;
   width: 100%;
   display: flex;
-  height: 50px;
-  line-height: 49px;
+  height: 48px;
+  line-height: 48px;
   box-sizing: border-box;
   position: relative;
   overflow-x: auto;
   overflow-y: hidden;
   &::-webkit-scrollbar {
-    display: none;
+      display: none;
   }
   .nav-bar-bottom {
     position: absolute;
@@ -155,9 +210,31 @@
     background: $primary-color;
     transition: all 0.3s ease-in-out;
   }
+  .nut-tab-active {
+    background: #fff;
+    position: relative;
+    border: 0;
+    .nut-tab-link{
+        color: #E1251B;
+        font-weight: bold;
+    }
+    .tab-line{
+      &:after{
+        content: "";
+        position: absolute;
+        width: 30%;
+        transform: translateX(-50%);
+        height: 2px;
+        background: #E1251B;
+        top: 0px;
+        left: 50%;
+        z-index: 20;
+      }
+    }
+  }
 }
 
-.nut-title-nav-list {
+.nut-title-nav-topnav,.nut-title-nav-bottomnav{
   flex: 1;
   position: relative;
   flex-direction: row;
@@ -183,7 +260,7 @@
   align-items: center;
   position: relative;
 }
-.nut-title-nav-scroll {
+.nut-title-nav-scroll{
   min-width: 100px;
   position: relative;
   flex-direction: row;
@@ -193,14 +270,13 @@
   background: #fff;
   box-sizing: border-box;
 }
-.nut-title-vertical-scroll {
-  min-height: 55px;
+.nut-title-vertical-scroll{
+  min-height: 50px;
   position: relative;
   flex-direction: row;
   align-items: center;
   justify-content: center;
   display: flex;
-  background: #fff;
   box-sizing: border-box;
 }
 .nut-tab-icon {
@@ -211,26 +287,25 @@
   background-repeat: no-repeat;
   background-size: 100% 100%;
 }
-.nut-tab-active {
-  background: #fff;
-  border: 0;
-  // a{
-  //     color: red;
-  // }
-}
+
 .nut-tab-item {
-  height: 200px;
+  height: 88px;
   border: 1px solid #fff;
   background: #fff;
   width: 100%;
-  padding: 10px;
+  padding: 20px;
   box-sizing: border-box;
+  font-size: 14px;
+  color: #646464;
   .hide {
     display: none;
   }
 }
 .nut-tab-disable {
-  background: #e1e1e1 !important;
+  .nut-tab-link{
+    color: #C8C8C8 !important;
+    font-size: 15px;
+  }
 }
 .tabbar-nav-word {
   font-size: $font-size-small;

+ 62 - 72
src/packages/tab/tab.vue

@@ -1,23 +1,23 @@
 <template>
   <div class="nut-tab-part">
     <div class="nut-tab" :class="{ 'nut-tab-horizontal': positionNavCss }">
-      <div v-if="positionNav == 'right' || positionNav == 'bottom'" class="nut-tab-item" ref="items">
+      <div v-if="positionNav == 'right' || positionNav == 'bottom'" class="nut-tab-item" ref="items" :style="customHeight">
         <slot></slot>
       </div>
-      <div :class="titleClass" ref="navlist">
-        <b v-if="isShowLine" :class="navBarClass" :style="navBarStyle"></b>
-        <span
-          v-for="(value, index) in tabTitleList"
-          :key="index"
-          :class="[titleNavList, 'nut-title-nav', { 'nut-tab-disable': value.disable }, { 'nut-tab-active': activeIndex == index }]"
-        >
-          <a class="nut-tab-link" v-on:click="switchTab(index, $event, value.disable)">
-            <i class="nut-tab-icon" :style="{ backgroundImage: 'url(' + value.iconUrl + ')' }" v-if="value.iconUrl"></i>
-            {{ value.tabTitle }}
-          </a>
-        </span>
+      <div :class="titleClass"  ref="navlist" :style="customHeight">
+          <b v-if="scrollLine" :class="navBarClass" :style="navBarStyle"></b>
+          <span
+            v-for="(value, index) in tabTitleList"
+            :key="index"
+            :class="[titleNavList, 'nut-title-nav', { 'nut-tab-disable': value.disable }, { 'nut-tab-active': activeIndex == index }]"
+          >
+            <span class="nut-tab-link" v-on:click="switchTab(index, $event, value.disable)" :class="{'tab-line':tabLine}">
+              <i  v-if="value.iconUrl" class="nut-tab-icon" :style="{ backgroundImage: 'url(' + value.iconUrl + ')' }"></i>
+              {{ value.tabTitle }}
+            </span>
+          </span>
       </div>
-      <div v-if="positionNav == 'top' || positionNav == 'left'" class="nut-tab-item" ref="items">
+      <div v-if="positionNav == 'top' || positionNav == 'left'" class="nut-tab-item" ref="items" :style="customHeight">
         <slot></slot>
       </div>
     </div>
@@ -27,11 +27,15 @@
 export default {
   name: 'nut-tab',
   props: {
-    isScroll: {
+    isScroll:{
+        type:Boolean,
+        default:false
+    },
+    scrollLine: {
       type: Boolean,
       default: false
     },
-    isShowLine: {
+    tabLine:{
       type: Boolean,
       default: true
     },
@@ -48,14 +52,19 @@ export default {
       default: function() {
         return [];
       }
+    },
+    wrapperHeight:{
+      type: [String,Number],
+      default: '200'
     }
   },
   data() {
     return {
       tabTitleList: [],
       activeIndex: this.defIndex,
-      initX: '0',
-      navWidth: 0
+      initX: 0,
+      navWidth: 0,
+      tapWidth:0
     };
   },
   watch: {
@@ -70,52 +79,47 @@ export default {
     }
   },
   computed: {
-    //下面有些样式名称是为了兼容之前的版本
     positionNavCss: function() {
       if (this.positionNav === 'left' || this.positionNav === 'right') return true;
     },
     titleClass: function() {
-      if (this.positionNav == 'top') {
-        return 'nut-tab-title';
-      }
       return 'nut-tab-title-' + this.positionNav + 'nav';
     },
     navBarClass: function() {
-      if (this.positionNav == 'top') {
-        return 'nav-bar';
-      }
       return 'nav-bar-' + this.positionNav;
     },
     titleNavList: function() {
       if (this.positionNav == 'top' || this.positionNav == 'bottom') {
-        if (this.isScroll) {
-          return 'nut-title-nav-scroll';
-        }
-        return 'nut-title-nav-list';
-      } else {
-        if (this.isScroll) {
-          return 'nut-title-vertical-scroll';
-        }
-        return 'nut-title-nav-' + this.positionNav + 'nav';
+        if(this.isScroll) return 'nut-title-nav-scroll';
+      }else{
+        if(this.isScroll) return 'nut-title-vertical-scroll';
       }
+      return 'nut-title-nav-' + this.positionNav + 'nav';
     },
     navBarStyle: function() {
       if (this.positionNav === 'top' || this.positionNav === 'bottom') {
-        return {
-          transform: `translateX(${this.initX}px)`,
-          width: this.navWidth + 'px'
-        };
+          return {
+            transform: `translateX(${this.initX}px)`,
+            width: this.navWidth + 'px'
+          };
       }
       return {
-        transform: `translateY(${this.initX}px)`,
-        height: this.navWidth + 'px'
+          transform: `translateY(${this.initX}px)`,
+          height: this.navWidth + 'px'
       };
+    },
+    customHeight:function(){
+      if(this.positionNav === 'left' || this.positionNav === 'right'){
+        return {
+          height:this.wrapperHeight+'px'
+        }
+      }else{
+        return null;
+      }
     }
   },
   mounted() {
-    this.$nextTick(() => {
-      this.$slots.default && this.updeteTab(this.$slots.default);
-    });
+    this.$slots.default && this.updeteTab(this.$slots.default);
   },
   methods: {
     updeteTab: function() {
@@ -130,9 +134,10 @@ export default {
         let slotTag = slot[i].tag;
         if (typeof slotTag == 'string' && slotTag.indexOf('nut-tab-panel') != -1) {
           let attrs = slot[i].data.attrs;
+          console.log(attrs.disable);
           let item = {
             tabTitle: attrs['tab-title'] || attrs['tabTitle'],
-            disable: attrs.disable === false,
+            disable: attrs.disable === true,
             iconUrl: attrs['iconUrl'] || attrs['icon-url']
           };
           this.tabTitleList.push(item);
@@ -146,41 +151,27 @@ export default {
         }
       }
       this.$nextTick(() => {
+        let tapWidth;
         if (this.positionNav == 'top' || this.positionNav == 'bottom') {
-          this.navWidth = this.$refs.navlist.querySelector('.nut-title-nav').offsetWidth;
-        } else {
-          this.navWidth = this.$refs.navlist.querySelector('.nut-title-nav').offsetHeight;
+            this.navWidth = this.$refs.navlist.querySelector('.nut-title-nav').offsetWidth;
+            tapWidth = this.$refs.navlist.offsetWidth;
+        }else{
+            this.navWidth = this.$refs.navlist.querySelector('.nut-title-nav').offsetHeight;
+            tapWidth = this.$refs.navlist.offsetHeight;
         }
         this.initX = parseInt(this.navWidth * this.defIndex);
+        this.tapWidth = tapWidth/2-this.navWidth/2;
       });
     },
-    getStyle: function(obj, styleName) {
-      if (!obj) {
-        return '';
-      }
-      if (obj.currentStyle) {
-        return obj.currentStyle[styleName];
-      } else {
-        return getComputedStyle(obj, null)[styleName];
-      }
-    },
-    findParent(event, myclass) {
-      let parentCpt = event.target;
-      let flag = 0; //避免死循环
-      while (parentCpt && flag < 10) {
-        flag++;
-        if (parentCpt.className && parentCpt.className === myclass) {
-          break;
-        }
-        parentCpt = parentCpt.parentNode;
-      }
-      return parentCpt;
-    },
     switchTab: function(index, event, disable) {
       if (!disable) {
         this.activeIndex = index;
-        this.initX = parseInt(this.navWidth * index);
-        let nutTab = this.findParent(event, 'nut-tab-part');
+        this.initX = parseInt(this.navWidth * index); 
+        if(this.positionNav == 'top' || this.positionNav == 'bottom'){
+            this.$refs.navlist.scroll(this.initX-this.tapWidth,0);
+        }else{
+            this.$refs.navlist.scroll(0, this.initX-this.tapWidth);
+        }   
         let items = this.$refs.items.children;
         for (let i = 0; i < items.length; i++) {
           if (i == index) {
@@ -190,7 +181,6 @@ export default {
           }
         }
         this.$emit('tab-switch', index, event);
-        this.$emit('tabSwitch', index, event); //兼容以前驼峰法命名
       }
     }
   }

+ 12 - 1
src/packages/tabbar/tabbar.vue

@@ -3,7 +3,7 @@
     <a
       class="tabbar-nav"
       v-for="(value, index) in tabList"
-      :class="[{ curr: index == currIndex }, type]"
+      :class="[{ 'curr': index == currIndex }, type]"
       :key="value.tabTitle"
       v-on:click="switchTabs(value, index)"
       :href="value.href"
@@ -44,15 +44,26 @@ export default {
       tabList: this.tabbarList
     };
   },
+  mounted(){
+    this.initBar();
+  },
   watch: {
     tabbarList: {
       handler(value) {
         this.tabList = value;
+        this.initBar();
       },
       deep: true
     }
   },
   methods: {
+    initBar(){
+      this.tabList.forEach((item,index)=>{
+        if(item.curr){
+            this.currIndex = index;
+        }
+      })
+    },
     switchTabs: function(value, index) {
       this.currIndex = index;
       this.$emit('tab-switch', value, index);