Browse Source

Merge branch 'v2' of https://github.com/jdf2e/nutui into v2

yewenwen 5 years ago
parent
commit
df53503786

+ 2 - 1
package.json

@@ -63,6 +63,7 @@
         "vue": "^2.6.10"
     },
     "devDependencies": {
+        "vue-qr": "^2.2.1",
         "@babel/cli": "7.1.2",
         "@babel/core": "7.1.2",
         "@babel/plugin-syntax-dynamic-import": "7.0.0",
@@ -164,4 +165,4 @@
         "instrument": false,
         "sourceMap": false
     }
-}
+}

+ 4 - 0
src/nutui.js

@@ -120,6 +120,8 @@ import SideNavBarItem from "./packages/sidenavbaritem/index.js";
 import "./packages/sidenavbaritem/sidenavbaritem.scss";
 import Drag from "./packages/drag/index.js";
 import "./packages/drag/drag.scss";
+import VueQr from "./packages/qart/index.js";
+import "./packages/qart/qart.scss";
 import Address from "./packages/address/index.js";
 import "./packages/address/address.scss";
 
@@ -262,6 +264,8 @@ export default {
   locale,
   install,
   Lazyload,
+  VueQr,
+
   ...components,
   ...filters,
   ...directives,

+ 1 - 0
src/packages/actionsheet/actionsheet.scss

@@ -30,6 +30,7 @@
     .nut-actionsheet-sub-title{
         font-size: $font-size-small;
         color: $title-color;
+        margin-inline-start: 0px;
     }
 }
 .nut-actionsheet-menu{

+ 3 - 0
src/packages/countdown/countdown.vue

@@ -122,6 +122,9 @@ const countdownTimer = {
     },
     endTime() {
       this.initTimer();
+    },
+    startTime() {
+      this.initTimer();
     }
   },
   methods: {

+ 52 - 0
src/packages/popup/__test__/popup.spec.js

@@ -0,0 +1,52 @@
+import { mount } from '@vue/test-utils'
+import popup from '../popup.vue'
+import Vue from 'vue';
+import overlay from "../overlay.vue";
+import Icon from '../../icon/icon.vue';
+describe('Menu.vue',() => {
+    const wrapper = mount(popup, {
+        
+    });
+
+    it('1.判断是否显示内容',() => {
+        wrapper.setData({
+            value:true
+        }); 
+        return Vue.nextTick().then(function() {
+            expect(wrapper.classes()).toContain('popup-box')
+        }) 
+    });
+    it('2.判断弹出位置',() => {
+        wrapper.setData({ 
+            position:"top"
+        }); 
+        return Vue.nextTick().then(function() {
+            expect(wrapper.classes()).toContain('popup-top')
+        }) 
+    });
+    it('3.判断是否有关闭图标',() => {
+        wrapper.setData({ 
+            closeable:true
+        });  Icon
+        let i = wrapper.find('.nutui-popup__close-icon')
+        return Vue.nextTick().then(function() {
+            expect(i.is(Icon)).toBe(true)
+        }) 
+    });
+    it('4.判断点击关闭按钮',() => {
+        wrapper.setData({ 
+            closeable:true
+        }); 
+        let i = wrapper.find('.nutui-popup__close-icon');
+        i.trigger('click')
+        console.log()
+        return Vue.nextTick().then(function() {
+            setTimeout(()=>{
+                expect(wrapper.contains(popup)).toBe(false)
+            },wrapper.duration*1000)           
+        }) 
+    });
+
+ 
+ 
+});

+ 16 - 2
src/packages/popup/doc.md

@@ -51,19 +51,33 @@ export default {
 <nut-popup v-model="show" round position="bottom" :style="{ height: '20%' }" />
 ```
 
+## 指定挂载位置
+
+弹出层默认挂载到组件所在位置,可以通过get-container属性指定挂载位置
+
+```html
+<!-- 挂载到 body 节点下 -->
+<nut-popup v-model="show" get-container="body" />
+
+<!-- 挂载到 #app 节点下 -->
+<nut-popup v-model="show" get-container="#app" />
+
+```
+
 ## API
 
 | 字段       | 说明                                     | 类型    | 默认值 |
 | ---------- | ---------------------------------------- | ------- | ------ |
 | v-model    | 当前组件是否显示                         | boolean | -      |
 | overlay    | 是否显示遮罩层                           | boolean | true   |
-| position   | 弹出位置,可选值为 top bottom right left | string  | center |
+| position   | 弹出位置,可选值为 top bottom right left center| string  | center |
 | duration   | 动画时长,单位秒                         | Number  | -      |
 | round      | 是否显示圆角                             | boolean | -      |
 | transition | 动画类名,等价于 transtion 的 name 属性  | string  | -      |
 | closeable  | 是否显示关闭图标                        | Boolean  | false     |
 | close-icon | 关闭图标名称                  | string  | cross     |
-| close-icon-position | 关闭图标位置,可选值为top-left bottom-left bottom-right | string  | top-right  |
+| close-icon-position | 关闭图标位置,可选值为top-left top-right bottom-left bottom-right | string  | top-right  |
+| destroy-on-close| 控制是否在关闭 popop 之后将子元素全部销毁 | boolean | false |
 | overlay-class | 自定义遮罩层类名 | string  |   |
 | overlay-style | 自定义遮罩层样式 | object  |   |
 | lock-scroll | 是否锁定背景滚动 | boolean  |  true |

+ 19 - 3
src/packages/popup/overlay.vue

@@ -1,15 +1,31 @@
 <template>
   <transition name="popup-fade">
-    <div @touchmove.prevent.stop v-show="show" class="popup-bg nut-mask" :class="className"></div>
+    <div 
+      @touchmove.stop="touchmove"
+      :style="{ animationDuration: `${duration}s`, customStyle }"
+      v-show="show"
+      class="popup-bg nut-mask"
+      :class="className"
+    ></div>
   </transition>
 </template>
 <script>
 export default {
   name: "nut-popup-mask",
+
   props: {
-    show: { type: Boolean, default: true },
+    lockScroll: { type: Boolean, default: true },
+    show: { type: Boolean, default: false },
+    duration: Number,
     className: { type: String, default: "" },
-    customStyle: { type: String, default: "" },    	
+    customStyle: { type: String, default: "" },
+  },
+  methods:{
+    touchmove(e){
+      if(this.lockScroll){
+        e.preventDefault();
+      }
+    }
   }
 };
 </script>

+ 125 - 127
src/packages/popup/popup.scss

@@ -1,144 +1,143 @@
-$popup-close-icon-margin: 16px; 
+$popup-close-icon-margin: 16px;
 .popup-fade-enter-active {
-    animation: 0.3s nut-fade-in;
+  animation: nut-fade-in;
+}
+.popup-fade-leave-active {
+  animation: nut-fade-out;
+}
+
+.popup-slide {
+  &-center-enter-active {
+    animation: nut-fade-in;
   }
-  .popup-fade-leave-active {
-    animation: 0.3s nut-fade-out;
+  &-center-leave-active {
+    animation: nut-fade-out;
   }
-  
-  .popup-slide {
-    &-top-enter,
-    &-top-leave-active {
-      transform: translate(0, -100%);
-    }
-    &-right-enter,
-    &-right-leave-active {
-      transform: translate(100%, 0);
-    }
-  
-    &-bottom-enter,
-    &-bottom-leave-active {
-      transform: translate(0, 100%);
-    }
-  
-    &-left-enter,
-    &-left-leave-active {
-      transform: translate(-100%, 0);
-    }
+
+  &-top-enter,
+  &-top-leave-active {
+    transform: translate(0, -100%);
   }
-  
-  .popup-center {
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-  }
-  
-  .popup-bottom {
-    bottom: 0;
-    left: 0;
-    width: 100%;
-    &.round {
-      border-radius: 25px 25px 0 0;
-    }
+  &-right-enter,
+  &-right-leave-active {
+    transform: translate(100%, 0);
   }
-  .popup-right {
-    top: 0;
-    right: 0;
-    &.round {
-      border-radius: 25px 0 0 25px;
-    }
+
+  &-bottom-enter,
+  &-bottom-leave-active {
+    transform: translate(0, 100%);
   }
-  
-  .popup-left {
-    top: 0;
-    left: 0;
-    &.round {
-      border-radius: 0 25px 25px 0;
-    }
+
+  &-left-enter,
+  &-left-leave-active {
+    transform: translate(-100%, 0);
   }
-  .popup-top {
-    top: 0;
-    left: 0;
-    width: 100%;
-    &.round {
-      border-radius: 0 0 25px 25px;
-    }
+}
+
+.popup-center {
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+.popup-bottom {
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  &.round {
+    border-radius: 25px 25px 0 0;
+  }
+}
+.popup-right {
+  top: 0;
+  right: 0;
+  &.round {
+    border-radius: 25px 0 0 25px;
+  }
+}
+
+.popup-left {
+  top: 0;
+  left: 0;
+  &.round {
+    border-radius: 0 25px 25px 0;
+  }
+}
+.popup-top {
+  top: 0;
+  left: 0;
+  width: 100%;
+  &.round {
+    border-radius: 0 0 25px 25px;
+  }
+}
+.popup-box {
+  position: fixed;
+  max-height: 100%;
+  overflow-y: auto;
+  background-color: #fff;
+  transition: transform 0.3s;
+  -webkit-overflow-scrolling: touch;
+  z-index: 100;
+}
+@keyframes nut-fade-in {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
   }
-  .popup-box {
-    position: fixed;
-    max-height: 100%;
-    overflow-y: auto;
-    background-color: #fff;
-    transition: transform 0.3s;
-    -webkit-overflow-scrolling: touch;
-    z-index: 100;
-  }
-  @keyframes nut-fade-in {
-    from {
-      opacity: 0;
+}
+
+@keyframes nut-fade-out {
+  from {
+    opacity: 1;
+  }
+
+  to {
+    opacity: 0;
+  }
+}
+
+.nut-overflow-hidden {
+  overflow: hidden !important;
+}
+
+.nutui-popup {
+  &__close-icon {
+    position: absolute;
+    z-index: 1;
+    color: #969799;
+    font-size: 18px;
+    cursor: pointer;
+
+    &:active {
+      opacity: 0.7;
     }
-  
-    to {
-      opacity: 1;
+
+    &--top-left {
+      top: $popup-close-icon-margin;
+      left: $popup-close-icon-margin;
     }
-  }
-  
-  @keyframes nut-fade-out {
-    from {
-      opacity: 1;
+
+    &--top-right {
+      top: $popup-close-icon-margin;
+      right: $popup-close-icon-margin;
     }
-  
-    to {
-      opacity: 0;
+
+    &--bottom-left {
+      bottom: $popup-close-icon-margin;
+      left: $popup-close-icon-margin;
     }
-  }
- 
-  .nut-overflow-hidden{
-    overflow: hidden !important;
-  }
- 
-
-.nutui-popup{
-    &__close-icon{
-        position: absolute;
-        z-index: 1;
-        color: #969799;
-        font-size: 18px;
-        cursor: pointer;
-       
-        &:active {
-            opacity: .7;
-        }
-
-        &--top-left {
-            top: $popup-close-icon-margin;
-            left: $popup-close-icon-margin;
-        }
-
-        &--top-right {
-            top: $popup-close-icon-margin;
-            right: $popup-close-icon-margin;
-        }
-
-        &--bottom-left {
-            bottom: $popup-close-icon-margin;
-            left: $popup-close-icon-margin;
-        }
-
-        &--bottom-right {
-            right: $popup-close-icon-margin;
-            bottom: $popup-close-icon-margin;
-        }
+
+    &--bottom-right {
+      right: $popup-close-icon-margin;
+      bottom: $popup-close-icon-margin;
     }
+  }
 }
- 
 
- .popup-fade-enter-active {
-  animation: 0.3s nut-fade-in;
-}
-.popup-fade-leave-active {
-  animation: 0.3s nut-fade-out;
-}
 .popup-bg {
   position: fixed;
   top: 0;
@@ -168,4 +167,3 @@ $popup-close-icon-margin: 16px;
     opacity: 0;
   }
 }
- 

+ 63 - 42
src/packages/popup/popup.vue

@@ -7,11 +7,12 @@
     <div
       ref="popupBox"
       v-show="value"
+      :style="{animationDuration:transitionDuration}"
       class="popup-box"
       :class="[`popup-${position}`, { round }]"
       @click="$emit('click', this)"
     >
-      <slot></slot>
+      <slot v-if="showSlot"></slot>
       <icon
         v-if="closeable"
         @click.native="$emit('input', false)"
@@ -35,12 +36,18 @@ export default {
       "icon":Icon
   },
   props: {
-    value: Boolean,
+    value: {
+      type: Boolean,
+      default: false
+    },
     position: {
       type: String,
       default: "center"
     },
-    duration: Number,
+    duration: {
+      type: Number,
+      default:0.3
+    },
     transition: String,
     overlay: {
       type: Boolean,
@@ -74,23 +81,21 @@ export default {
         type:String,
         default:""
     },
+    destroyOnClose:{
+      type: Boolean,
+      default: false
+    },
     getContainer:String,
-    round: Boolean
-  },
-  beforeCreate() {},
-  created() {
-    if (this.transition) {
-      this.transitionName = this.transition;
-    } else if (this.position === "center") {
-      this.transitionName = "popup-fade";
-    } else {
-      this.transitionName = `popup-slide-${this.position}`;
+    round: {
+      type: Boolean,
+      default: false
     }
+  }, 
+  created() { 
+    this.transition ? this.transitionName = this.transition :this.transitionName = `popup-slide-${this.position}`;
   },
   mounted() {
-    if (this.duration) {
-      this.$refs.popupBox.style.transitionDuration = this.duration + "s";
-    }    
+    this.mountOverlay();
     if (this.getContainer) {
         this.portal();
     }
@@ -101,25 +106,44 @@ export default {
   watch: {
     value(val) {
       const type = val ? "open" : "close";
-      if (this.overlay) this[type]();
+      if (this.overlay) {
+        this[type]();
+      }
     },
     position(val) {
-      if (val === "center") {
-        this.transitionName = "popup-fade";
-      } else {
-        this.transitionName = `popup-slide-${this.position}`;
-      }
+      val === "center" ? this.transitionName = "popup-fade" :this.transitionName = `popup-slide-${this.position}`;
     },
     getContainer: 'portal'
   },
   data() {
     return {
-      transitionName: "popup-fade",
+      showSlot:true,
+      transitionName: "popup-fade-center",
       overlayInstant: null
     };
   },
+  computed:{
+    transitionDuration(){ 
+      return this.duration ? this.duration + 's' : 'initial';
+    }
+  },
   methods: {    
-    mount(Component, data) {
+    mountOverlay(){
+      if (!this.overlayInstant) {
+        this.overlayInstant = this.mount(overlay, { 
+          duration: this.duration,
+          nativeOn: {
+            click: () => { 
+              this.$emit("click-overlay", this);
+              if(this.closeOnClickOverlay){
+                  this.$emit("input", false);
+              }              
+            }
+          }
+        });
+      } 
+    },
+    mount(Component, data) {   
       const instance = new Vue({
         el: document.createElement("div"),
         props: Component.props,
@@ -130,31 +154,25 @@ export default {
           });
         }
       });
+      instance.duration = this.duration;
+      instance.lockScroll = this.lockScroll;
+      instance.className = this.overlayClass;
+      instance.customStyle = this.overlayStyle;
       const el = this.$refs.popupBox;
       if (el && el.parentNode) {
         el.parentNode.insertBefore(instance.$el, el);
       } else {
         document.body.appendChild(instance.$el);
-      }
+      } 
       return instance;
     },
 
     open() {
       if (!this.overlayInstant) {
-        this.overlayInstant = this.mount(overlay, {
-          className: this.overlayClass,
-          customStyle: this.overlayStyle,
-          nativeOn: {
-            click: () => {
-              this.$emit("click-overlay", this);
-              if(this.closeOnClickOverlay){
-                  this.$emit("input", false);
-              }              
-            }
-          }
-        });
+        this.mountOverlay();
       } else {
         this.overlayInstant.show = true;
+        this.showSlot = true;
       }
    
      if (this.lockScroll && !this.locked) {
@@ -167,17 +185,20 @@ export default {
     },
     close() {
       this.overlayInstant.show = false;
+      if(this.destroyOnClose){
+        setTimeout(()=>{ 
+        this.showSlot = false;
+      }, this.duration * 1000)
+      }
+      
       if (this.lockScroll && this.locked) {                
         document.body.classList.remove('nut-overflow-hidden');  
         this.locked = false;      
       }
       this.$emit("close", this);
     },
-    getElement(selector){   
-      if (typeof selector === "string") {
-        return document.querySelector(selector);
-      }
-      return selector();
+    getElement(selector){    
+      return document.querySelector(selector);
     },
     portal() {
         const { getContainer } = this;

+ 14 - 1
src/packages/range/demo.vue

@@ -45,6 +45,18 @@
         <span slot="title">{{val2[0]}},{{val2[1]}}</span>
       </nut-cell>
     </div>
+
+    <h4>控制区间步长</h4>
+    <div>
+      <nut-cell>
+        <span slot="title">
+          <nut-range color="#31ccec" :rangeValues.sync="val4" :range="[0,200]" :stage="20" :showLabel="true"></nut-range>
+        </span>
+      </nut-cell>
+      <nut-cell>
+        <span slot="title">{{val4[0]}},{{val4[1]}}</span>
+      </nut-cell>
+    </div>
   </div>
 </template>
 
@@ -54,7 +66,8 @@ export default {
     return {
       val1: [-52, 120],
       val2: [0, 120],
-      val3: [0, 5]
+      val3: [0, 5],
+      val4: [20, 100]
     };
   },
   methods: {}

+ 14 - 5
src/packages/range/movebar.vue

@@ -47,7 +47,7 @@ export default {
     };
   },
   watch: {
-    initLeft() {
+    initLeft(val) {
       this.posi = this.initLeft;
     }
   },
@@ -71,17 +71,26 @@ export default {
             document.documentElement.scrollLeft || document.body.scrollLeft;
           this.boxLeft = this.box.getBoundingClientRect().left;
           const posi = evt.pageX - this.boxLeft - pageScrollLeft;
-          this.setPosi(posi);
+          this.setPosi(posi, false);
       });
     },
-    setPosi(posi) {
+    setPosi(posi, isEnd) {
       if (posi < 0 || posi > this.box.clientWidth) return;
       this.posi = posi;
-      this.$emit('getPos', posi);
+      this.$emit('getPos', posi, isEnd);
     },
     onTouchEnd(event) {
       event.preventDefault();
-      this.$emit('update:ani', false);
+      const evt = event.changedTouches[0];
+      const pageScrollLeft =
+        document.documentElement.scrollLeft || document.body.scrollLeft;
+      this.boxLeft = this.box.getBoundingClientRect().left;
+      const posi = evt.pageX - this.boxLeft - pageScrollLeft;
+      setTimeout(() => {
+        this.setPosi(posi, true);
+        this.$emit('update:ani', false);
+      }, 50);
+      
     },
     onClick(event) {
       event.preventDefault();

+ 42 - 11
src/packages/range/range.vue

@@ -101,7 +101,8 @@ export default {
 			barleft1: 0,
 			barleft2: 0,
       level: null,
-			ani: false
+      ani: false,
+      prevValues: []
     };
   },
   watch: {
@@ -110,6 +111,11 @@ export default {
     },
     rangeValues() {
       this.init();
+    },
+    ani(flag) {
+      if (flag) {
+        this.prevValues = this.rangeValues;
+      }
     }
   },
   computed: {
@@ -135,22 +141,47 @@ export default {
       this.propInit();
     },
 		updateRangeValues() {
-			let rangeValues = this.currentLeft > this.currentRight? [this.currentRight, this.currentLeft]: [this.currentLeft, this.currentRight];
-			this.$emit("update:rangeValues", rangeValues);
+      let rangeValues = this.currentLeft > this.currentRight? [this.currentRight, this.currentLeft]: [this.currentLeft, this.currentRight];
+      this.$emit("update:rangeValues", rangeValues);
 		},
-    getPosLeft(pos) {
-			this.currentLeft = this.setCurrent(pos);
-			this.barleft1 = pos;
-			this.updateRangeValues();
+    getPosLeft(pos, isEnd) {
+      let currentLeft = this.setCurrent(pos);
+      if (isEnd && this.stage) {
+        let prevLeft = this.prevValues[0];
+        if (currentLeft >= (prevLeft + (this.stage / 2))) {
+          this.currentLeft = prevLeft + this.stage;
+        } else if (currentLeft < (prevLeft - (this.stage / 2))) {
+          this.currentLeft = prevLeft - this.stage;
+        } else {
+          this.currentLeft = prevLeft;
+        }
+      } else {
+        this.currentLeft = currentLeft;
+      }
+      this.barleft1 = pos;
+      this.updateRangeValues();
+    
     },
-    getPosRight(pos) {
-			this.currentRight = this.setCurrent(pos);
+    getPosRight(pos, isEnd) {
+      let currentRight = this.setCurrent(pos);
+      if (isEnd && this.stage) {
+        let prevRight = this.prevValues[1];
+        if (currentRight >= (prevRight + (this.stage / 2))) {
+          this.currentRight = prevRight + this.stage;
+        } else if (currentRight < (prevRight - (this.stage / 2))) {
+          this.currentRight = prevRight - this.stage;
+        } else {
+          this.currentRight = prevRight;
+        }
+      } else {
+        this.currentRight = currentRight;
+      }
 			this.barleft2 = pos;
 			this.updateRangeValues();
     },
     setCurrent(posi) {
 			const trans = posi / this.box.clientWidth * this.total;
-			let current = (trans / this.cell) * this.cell + this.range[0];
+      let current = (trans / this.cell) * this.cell + this.range[0];
 			return current > this.range[1] - 1? this.range[1]: current < this.range[0] + 1? this.range[0]: Math.round(current);
     },
     setVal(posi) {
@@ -187,7 +218,7 @@ export default {
 			this.initLeft1 = this.valToPosi(this.currentLeft);
 			this.initLeft2 = this.valToPosi(this.currentRight);
 			this.barleft1 = this.initLeft1;
-			this.barleft2 = this.initLeft2;
+      this.barleft2 = this.initLeft2;
     }
   },
   mounted() {