Browse Source

Merge branch 'v3-dev' of https://github.com/jdf2e/nutui into v3-dev

suzigang 5 years ago
parent
commit
b9b4a36164

+ 10 - 0
src/config.json

@@ -388,6 +388,16 @@
       "sort": "0",
       "showDemo": true,
       "author": "ivanwancy"
+    },
+    {
+      "version": "1.0.0",
+      "name": "Badge",
+      "sort": "0",
+      "chnName": "徽标",
+      "desc": "出现在图标或文字右上角的红色圆点、数字或者文字,表示有新内容或者待处理的信息",
+      "type": "component",
+      "showDemo": true,
+      "author": "jeffreyzhang23"
     }
   ]
 }

+ 48 - 0
src/mixins/touch.js

@@ -0,0 +1,48 @@
+import Vue from "vue";
+
+const MIN_DISTANCE = 10;
+
+function getDirection(x, y) {
+  if (x > y && x > MIN_DISTANCE) {
+    return "horizontal";
+  }
+
+  if (y > x && y > MIN_DISTANCE) {
+    return "vertical";
+  }
+
+  return "";
+}
+
+const TouchMixin = Vue.extend({
+  data() {
+    return { direction: "" };
+  },
+
+  methods: {
+    touchStart(event) {
+      this.resetTouchStatus();
+      this.startX = event.touches[0].clientX;
+      this.startY = event.touches[0].clientY;
+    },
+
+    touchMove(event) {
+      const touch = event.touches[0];
+      this.deltaX = touch.clientX - this.startX;
+      this.deltaY = touch.clientY - this.startY;
+      this.offsetX = Math.abs(this.deltaX);
+      this.offsetY = Math.abs(this.deltaY);
+      this.direction =
+        this.direction || getDirection(this.offsetX, this.offsetY);
+    },
+
+    resetTouchStatus() {
+      this.direction = "";
+      this.deltaX = 0;
+      this.deltaY = 0;
+      this.offsetX = 0;
+      this.offsetY = 0;
+    },
+  },
+});
+export default TouchMixin;

+ 4 - 1
src/nutui.js

@@ -80,6 +80,8 @@ import Swiper from './packages/swiper/index.js';
 import './packages/swiper/swiper.scss';
 import ImagePreview from './packages/imagepreview/index.js';
 import './packages/imagepreview/imagepreview.scss';
+import Badge from './packages/badge/index.js';
+import './packages/badge/badge.scss';
 
 const packages = {
   Cell,
@@ -122,7 +124,8 @@ const packages = {
   Address: Address,
   Tag,
   Swiper,
-  ImagePreview
+  ImagePreview,
+  Badge,
 };
 
 const components = {};

+ 38 - 0
src/packages/badge/__test__/badge.spec.js

@@ -0,0 +1,38 @@
+import { shallowMount, mount } from '@vue/test-utils'
+import Badge from '../badge.vue';
+import Vue from 'vue';
+
+describe('Badge.vue', () => {
+    const wrapper = shallowMount(Badge, {});
+    it('创建结构', () => {
+        wrapper.setProps({ value: '9'});
+        return Vue.nextTick().then(function () {
+            expect(wrapper.contains('sup')).toBe(true);            
+        })
+    });
+    it('字数设置', () => {
+        wrapper.setProps({ value: '9'});
+        return Vue.nextTick().then(function () {
+            expect(wrapper.find('.nut-badge__content').text()).toBe('9');            
+        })
+    });
+
+    it('最大值设置', () => {
+        wrapper.setProps({ value: 200, max: 99 });
+        return Vue.nextTick().then(function () {
+            expect(wrapper.find('.nut-badge__content').text()).toBe('99+');
+        })
+    });
+    it('文字设置',() => {
+        wrapper.setProps({value: 'new'});
+        return Vue.nextTick().then(function() {
+            expect(wrapper.find('.nut-badge__content').text()).toBe('new');
+        })
+    })
+    it('设置为点操作',() => {
+        wrapper.setProps({value: 'new', isDot: true});
+        return Vue.nextTick().then(function() {
+            expect(wrapper.find('.nut-badge__content').text()).toBe('');
+        })
+    })
+});

+ 39 - 0
src/packages/badge/badge.scss

@@ -0,0 +1,39 @@
+.nut-badge {
+  position: relative;
+  display: inline-block;
+  sup {
+    position: absolute;
+    height: 14px;
+    min-width: 8px;
+    line-height: 14px;
+    padding: 1px 7px;
+    background-color: $primary-color;
+    text-align: center;
+    color: $badge-font-color;
+    font-size: $badge-font-size;
+    border-radius: 9px 9px 9px 0px;
+    z-index: $zindex-mask;
+    font-family: PingFangSC-Regular;
+  }
+  .nut-badge__content {
+    transform: translateY(-50%) translateX(100%);
+  }
+  .is-dot {
+    width: 6px;
+    height: 6px;
+    min-width: 0;
+    padding: 0;
+    border-radius: 50%;
+    background: $primary-color;
+  }
+  .single-val {
+    width: 16px;
+    height: 16px;
+    line-height: 16px;
+    padding: 0;
+    border-radius: 50%;
+  }
+  .max-val {
+    padding: 1px 4px;
+  }
+}

+ 79 - 0
src/packages/badge/badge.vue

@@ -0,0 +1,79 @@
+<template>
+  <div class="nut-badge">
+    <slot></slot>
+    <sup
+      v-show="!hidden && (content || isDot)"
+      v-text="content"
+      class="nut-badge__content"
+      :class="{ 'is-dot': isDot, 'single-val': isSingleVal, 'max-val': isMaxVal }"
+      :style="stl"
+    ></sup>
+  </div>
+</template>
+<script>
+export default {
+  name: 'nut-badge',
+  props: {
+    value: {
+      type: [String, Number]
+    },
+    max: {
+      type: Number,
+      default: 10000
+    },
+    isDot: {
+      type: Boolean,
+      default: false
+    },
+    hidden: {
+      type: Boolean,
+      default: false
+    },
+    top: {
+      type: String,
+      default: '2px'
+    },
+    right: {
+      type: String,
+      default: '-5px'
+    },
+    zIndex: {
+      type: Number,
+      default: 10
+    }
+  },
+  data() {
+    return {
+      stl: {
+        top: this.top,
+        right: this.right,
+        zIndex: this.zIndex
+      }
+    };
+  },
+  computed: {
+    content() {
+      if (this.isDot) return;
+      const value = this.value;
+      const max = this.max;
+      if (typeof value === 'number' && typeof max === 'number') {
+        return max < value ? `${max}+` : value;
+      }
+      return value;
+    },
+    isSingleVal() {
+      if (this.isDot) return false;
+      return this.value && this.value.toString().length === 1;
+    },
+    isMaxVal() {
+      if (this.isDot) return false;
+      const value = this.value;
+      const max = this.max;
+      if (typeof value === 'number' && typeof max === 'number') {
+        return max < value;
+      }
+      return false;
+    }
+  }
+};
+</script>

+ 57 - 0
src/packages/badge/demo.vue

@@ -0,0 +1,57 @@
+<template>
+  <div class="container">
+    <h4>无内容样式</h4>
+    <div class="demo-w">
+      <nut-badge :isDot="true" class="item">拜访提醒</nut-badge>
+    </div>
+    <div class="demo-w">
+      <nut-badge :isDot="true" class="item"><div class="demo-svg"></div></nut-badge>
+    </div>
+
+    <h4>数字角标</h4>
+    <div class="demo-w">
+      <nut-badge :value="9" :max="99" class="item">拜访提醒</nut-badge>
+      <nut-badge :value="99" :max="99" class="item">拜访提醒</nut-badge>
+      <nut-badge :value="200" :max="99" class="item">拜访提醒</nut-badge>
+    </div>
+    <div class="demo-w">
+      <nut-badge :value="9" :max="99" class="item"><div class="demo-svg"></div></nut-badge>
+      <nut-badge :value="99" :max="99" class="item"><div class="demo-svg"></div></nut-badge>
+      <nut-badge :value="200" :max="99" class="item"><div class="demo-svg"></div></nut-badge>
+    </div>
+
+    <h4>自定义位置</h4>
+    <div class="demo-w">
+      <nut-badge :value="9" top="0" right="10px" class="item"><div class="demo-svg"></div></nut-badge>
+    </div>
+
+  </div>
+</template>
+
+<script>
+export default {
+  data() {
+    return {};
+  },
+  created() {},
+  methods: {}
+};
+</script>
+
+<style lang="scss" scoped>
+.item {
+  margin: 16px 26px 10px 10px;
+}
+.demo-w {
+  margin: 0;
+  background: white;
+}
+.demo-svg {
+  display: inline-block;
+  height: 17px;
+  width: 19px;
+  background-size: 100% 100%;
+  background-image: url('#{$assetsPath}/img/gift.png');
+  background-repeat: no-repeat;
+}
+</style>

+ 101 - 0
src/packages/badge/doc.md

@@ -0,0 +1,101 @@
+# Badge 徽标
+
+出现在图标或文字右上角的红色圆点、数字或者文字,表示有新内容或者待处理的信息。
+
+## 基本用法
+
+```html
+<nut-badge
+    :isDot="true"
+    class="item"
+>
+    拜访提醒
+</nut-badge>
+
+<nut-badge
+    :isDot="true"
+    class="item"
+>
+    <div class="demo-svg"></div>
+</nut-badge>
+
+```
+
+## 数字角标
+
+```html
+<nut-badge
+    :value="9"
+    :max="99"
+    class="item"
+>
+    拜访提醒
+</nut-badge>
+
+<nut-badge
+    :value="99"
+    :max="99"
+    class="item"
+>
+    拜访提醒
+</nut-badge>
+
+<nut-badge
+    :value="200"
+    :max="99"
+    class="item"
+>
+    拜访提醒
+</nut-badge>
+
+<nut-badge
+    :value="9"
+    :max="99"
+    class="item"
+>
+    <div class="demo-svg"></div>
+</nut-badge>
+
+<nut-badge
+    :value="99"
+    :max="99"
+    class="item"
+>
+    <div class="demo-svg"></div>
+</nut-badge>
+
+<nut-badge
+    :value="200"
+    :max="99"
+    class="item"
+>
+    <div class="demo-svg"></div>
+</nut-badge>
+
+```
+
+## 自定义位置
+
+```html
+<nut-badge 
+    :value="200" 
+    top="5px" 
+    right="10px" 
+    class="item"
+>
+    <div class="demo-svg"></div>
+</nut-badge>
+```
+
+
+## Prop
+
+| 字段 | 说明 | 类型 | 默认值
+|----- | ----- | ----- | ----- 
+| value | 显示的内容 | String | -
+| max | value为数值时,最大值 | Number | 10000
+| zIndex | 徽标的z-index值 | Number | 10
+| isDot | 是否为小点 | Boolean | false
+| hidden | 是否隐藏 | Boolean | false
+| top   | 上下偏移量,支持单位设置,可设置为:5px、5rem等 | String | 0
+| right  | 左右偏移量,支持单位设置,可设置为:5px、5rem等 | String | 0

+ 8 - 0
src/packages/badge/index.js

@@ -0,0 +1,8 @@
+import Badge from './badge.vue';
+import './badge.scss';
+
+Badge.install = function(Vue) {
+  Vue.component(Badge.name, Badge);
+};
+
+export default Badge;

+ 1 - 1
src/packages/button/doc.md

@@ -315,7 +315,7 @@ export default {
 | ----- | ---------------------------------------------------------------------------------------- | ------- | ------ |
 | type  | 按钮类型,可选类型包含:空/bottom/red/gray/light/lightred/primary/default/actived/dashed | String  | -      |
 | block | 是否为通栏                                                                               | Boolean | false  |
-| size  | large /normal/small                                                                      | String  | normal |
+| size  | big /middle/small                                                                      | String  | big |
 | shape | 形状配置,可选类型:空、circle                                                           | String  | -      |
 | icon  | 按钮前的图标,参考Icon组件                                                               | String  | -      |
 | color | 自定义颜色,包含文字与图片颜色                                                           | String  | -      |

+ 104 - 47
src/packages/popup/__test__/popup.spec.js

@@ -1,52 +1,109 @@
-import { mount } from '@vue/test-utils'
-import popup from '../popup.vue'
+import { mount } from '@vue/test-utils';
+import Popup ,{popupProps} from '../popup.vue';
+import { overlayProps } from '../overlay/overlay.vue';
 import Vue from 'vue';
-import overlay from "../overlay.vue";
-import Icon from '../../icon/icon.vue';
-describe('Menu.vue',() => {
-    const wrapper = mount(popup, {
-        
-    });
+let wrapper,allProps = {}; 
+Object.assign(allProps,overlayProps,popupProps)
+function getProps() {
+  let obj = {};
+
+  Object.keys(allProps).forEach(res => {
+    if (res !== 'value') {
+      obj[res] = this[res];
+    }
+  });
+  return obj;
+}
+describe('popup.vue', () => {
+  afterEach(function() {
+    wrapper.destroy();
+  });
+  const component = {
+    template: `<div>
+                <popup v-model='popupVal' v-bind="maskProps" ></popup>
+            </div>`,
+    components: {
+      Popup
+    },
+    data() {
+      return {
+        maskProps: getProps.apply(this),
+        popupVal: this.value
+      };
+    },
+    watch: {
+      value(v) {
+        this.popupVal = v;
+      }
+    },
+    props: { ...allProps, closeable: Boolean }
+  };
+
+    it('1.render popup && overlay', async function() {
+      wrapper = mount(component, { propsData: { value: true } });
+      wrapper.setProps({ value: true });
+      await Vue.nextTick();
+      expect(wrapper.find('.popup-box').isVisible()).toBe(true);
+      expect(wrapper.find('.nut-mask').isVisible()).toBe(true);
 
-    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('2.test props overlay', async function() {
+      wrapper = mount(component, { propsData: { value: true, overlay: false } });
+      await Vue.nextTick();
+      expect(wrapper.contains('.nut-mask')).toBe(false);
+
     });
-    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('3.test props lockScroll', async function() {
+      const wrapper1 = mount(component, { propsData: { value: true } });
+      const wrapper2 = mount(component, { propsData: { value: true } });
+      expect(document.body.classList.contains('nut-overflow-hidden')).toBe(true);
+
+      wrapper1.destroy();
+      await Vue.nextTick();
+      expect(document.body.classList.contains('nut-overflow-hidden')).toBe(true);
+      wrapper2.destroy();
+      await Vue.nextTick();
+      expect(document.body.classList.contains('nut-overflow-hidden')).toBe(false);
+
     });
-    // 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)           
-    //     }) 
-    // });
-
- 
- 
-});
+
+  it('4.test closeOnClickOverlay', async function() {
+    wrapper = mount(component, { propsData: { value: true, closeOnClickOverlay: true } });
+    await Vue.nextTick();
+    wrapper.find('.nut-mask').trigger('click');
+    await Vue.nextTick();
+    const duration = wrapper.vm.duration * 1500; 
+    await new Promise(resolve => {
+      setTimeout(() => resolve(), duration);
+    }); 
+    
+    expect(wrapper.find('.nut-mask').isVisible() || wrapper.find('.popup-box').isVisible()).toBe(false); 
+  });
+  it('5.test closeOnClickOverlay', async function() {
+    wrapper = mount(component, { propsData: { value: true, closeable: true } });
+    await Vue.nextTick();
+    let i = wrapper.find('.nutui-popup__close-icon');
+    i.trigger('click');
+    await Vue.nextTick();
+    await new Promise(resolve => {
+      setTimeout(() => resolve(), wrapper.duration * 1000);
+    });  
+     
+    expect(wrapper.find('.popup-box').isVisible()).toBe(false);
+  });
+
+   
+  it('6.test top ', async function()  {
+      wrapper = mount(component, { propsData: { value: true, position:"top"}});
+      await Vue.nextTick();  
+      expect(wrapper.find('.popup-box').classes()).toContain('popup-top')
+  });
+  it('7. zindex ',async function()  {
+    wrapper = mount(component, { propsData: { value: true,zIndex:999}});
+      const  wrapper2 = mount(component, { propsData: { value: true}});
+      await Vue.nextTick();  
+      expect(wrapper.find('.popup-box').vm.zIndex).toBe(999) 
+  });
+});

+ 116 - 22
src/packages/popup/demo.vue

@@ -2,43 +2,137 @@
   <div>
     <h2 class="title">基本用法</h2>
     <div>
-      <nut-cell isLink title="展示弹出层" :showIcon="true" @click.native="showBasic = true"> </nut-cell>
+        <nut-cell
+        isLink
+        title="展示弹出层"
+        :showIcon="true"
+        @click.native="showBasic = true"
+        >
+        </nut-cell>
     </div>
-    <nut-popup :style="{ padding: '30px 50px' }" v-model="showBasic">正文</nut-popup>
+    <nut-popup :style="{ padding: '30px 50px' }"   v-model="showBasic" >正文</nut-popup>
 
     <h2 class="title">弹出位置</h2>
     <div>
-      <nut-cell isLink title="顶部弹出" :showIcon="true" @click.native="showTop = true"> </nut-cell>
-      <nut-popup position="top" v-model="showTop" :style="{ height: '20%' }"> </nut-popup>
-      <nut-cell isLink title="底部弹出" :showIcon="true" @click.native="showBottom = true"> </nut-cell>
-      <nut-popup v-model="showBottom" position="bottom" :style="{ height: '20%' }"> </nut-popup>
-      <nut-cell isLink title="左侧弹出" :showIcon="true" @click.native="showLeft = true"> </nut-cell>
-      <nut-popup :style="{ width: '20%', height: '100%' }" v-model="showLeft" position="left"></nut-popup>
-      <nut-cell isLink title="右侧弹出" :showIcon="true" @click.native="showRight = true"> </nut-cell>
-      <nut-popup position="right" v-model="showRight" :style="{ width: '20%', height: '100%' }"></nut-popup>
+        <nut-cell
+        isLink
+        title="顶部弹出"
+        :showIcon="true"
+        @click.native="showTop = true"
+        >
+        </nut-cell>
+        <nut-popup position="top" v-model="showTop" :style="{ height: '20%' }">
+        </nut-popup>
+        <nut-cell
+        isLink
+        title="底部弹出"
+        :showIcon="true"
+        @click.native="showBottom = true"
+        >
+        </nut-cell>
+        <nut-popup
+        v-model="showBottom"
+        position="bottom"
+        :style="{ height: '20%' }"
+        >
+        </nut-popup>
+        <nut-cell
+        isLink
+        title="左侧弹出"
+        :showIcon="true"
+        @click.native="showLeft = true"
+        >
+        </nut-cell>
+        <nut-popup
+        :style="{ width: '20%', height: '100%' }"
+        v-model="showLeft"
+        position="left"
+        ></nut-popup>
+        <nut-cell
+        isLink
+        title="右侧弹出"
+        :showIcon="true"
+        @click.native="showRight = true"
+        >
+        </nut-cell>
+        <nut-popup
+        position="right"
+        v-model="showRight"
+        :style="{ width: '20%', height: '100%' }"
+        ></nut-popup>
     </div>
     <h2 class="title">关闭图标</h2>
     <div>
-      <nut-cell isLink title="关闭图标" :showIcon="true" @click.native="showIcon = true"> </nut-cell>
-      <nut-popup position="bottom" closeable v-model="showIcon" :style="{ height: '20%' }"></nut-popup>
+        <nut-cell
+        isLink
+        title="关闭图标"
+        :showIcon="true"
+        @click.native="showIcon = true">
+        </nut-cell>
+        <nut-popup
+        position="bottom"
+        closeable 
+        v-model="showIcon"
+        :style="{ height: '20%' }"
+        ></nut-popup>
 
-      <nut-cell isLink title="图标位置" :showIcon="true" @click.native="showIconPosition = true"> </nut-cell>
-      <nut-popup position="bottom" closeable close-icon-position="top-left" v-model="showIconPosition" :style="{ height: '20%' }"></nut-popup>
+        <nut-cell
+        isLink
+        title="图标位置"
+        :showIcon="true"
+        @click.native="showIconPosition = true">
+        </nut-cell>
+        <nut-popup
+        position="bottom"
+        closeable 
+        close-icon-position="top-left"
+        v-model="showIconPosition"
+        :style="{ height: '20%' }"
+        ></nut-popup>
 
-      <nut-cell isLink title="自定义图标" :showIcon="true" @click.native="showCloseIcon = true"> </nut-cell>
-      <nut-popup position="bottom" closeable close-icon="tick" v-model="showCloseIcon" :style="{ height: '20%' }"></nut-popup>
+
+        <nut-cell
+        isLink
+        title="自定义图标"
+        :showIcon="true"
+        @click.native="showCloseIcon = true">
+        </nut-cell>
+        <nut-popup
+        position="bottom"
+        closeable 
+        close-icon="tick"
+        v-model="showCloseIcon"
+        :style="{ height: '20%' }"
+        ></nut-popup>
     </div>
 
     <h2 class="title">圆角弹框</h2>
     <div>
-      <nut-cell isLink title="圆角弹框" :showIcon="true" @click.native="showRound = true"> </nut-cell>
-      <nut-popup round v-model="showRound" position="bottom" :style="{ height: '20%' }"></nut-popup>
+        <nut-cell
+        isLink
+        title="圆角弹框"
+        :showIcon="true"
+        @click.native="showRound = true"
+        >
+        </nut-cell>
+        <nut-popup
+        round
+        v-model="showRound"
+        position="bottom"
+        :style="{ height: '20%' }"
+        ></nut-popup>
     </div>
     <h2 class="title">指定挂载节点</h2>
     <div>
-      <nut-cell isLink title="指定挂载节点" :showIcon="true" @click.native="getContainer = true"> </nut-cell>
+        <nut-cell
+        isLink
+        title="指定挂载节点"
+        :showIcon="true"
+        @click.native="getContainer = true"
+        >
+        </nut-cell>
     </div>
-    <nut-popup :style="{ padding: '30px 50px' }" get-container="body" v-model="getContainer">body</nut-popup>
+    <nut-popup :style="{ padding: '30px 50px' }"  get-container="body"  v-model="getContainer" >body</nut-popup>
   </div>
 </template>
 <script>
@@ -55,10 +149,10 @@ export default {
       showRound: false,
       showIconPosition: false,
       showCloseIcon: false,
-      getContainer: false
+      getContainer:false
     };
   },
-  methods: {
+  methods: {     
     show() {
       this.isShow = true;
     }

+ 5 - 3
src/packages/popup/index.js

@@ -1,8 +1,10 @@
-import Popup from './popup.vue';
-import './popup.scss';
-
+import Popup from "./popup.vue";
+import "./popup.scss";
+import { getProps} from "./overlay/overlay-manager";
+import { overlayProps } from './overlay/overlay.vue';
 Popup.install = function(Vue) {
   Vue.component(Popup.name, Popup);
 };
 
 export default Popup;
+export {overlayProps, getProps};

+ 0 - 31
src/packages/popup/overlay.vue

@@ -1,31 +0,0 @@
-<template>
-  <transition name="popup-fade">
-    <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: {
-    lockScroll: { type: Boolean, default: true },
-    show: { type: Boolean, default: false },
-    duration: Number,
-    className: { type: String, default: '' },
-    customStyle: { type: String, default: '' }
-  },
-  methods: {
-    touchmove(e) {
-      if (this.lockScroll) {
-        e.preventDefault();
-      }
-    }
-  }
-};
-</script>

+ 142 - 0
src/packages/popup/overlay/overlay-manager.js

@@ -0,0 +1,142 @@
+import Vue from "vue";
+import overlayComponent from "./overlay.vue";
+
+let modalStack = [];
+let _zIndex = 2000;
+let overlay;
+const overlayManager = { 
+
+  lockCount: 0,
+
+  get zIndex() {
+    return ++_zIndex;
+  },
+  get topStack() {
+    return modalStack[modalStack.length - 1];
+  },
+
+  updateOverlay() {
+    const {  clickHandle, topStack } = overlayManager;
+    if (!overlay) {
+      overlay = mount(overlayComponent, {
+        nativeOn: {
+          click: clickHandle,
+        },
+      });
+    } 
+ 
+    if (topStack) {
+      const { vm, config } = topStack;
+      const el = vm.$el;
+      el && el.parentNode && el.parentNode.nodeType !== 11
+        ? el.parentNode.appendChild(overlay.$el)
+        : document.body.appendChild(overlay.$el);
+      
+      Object.assign(overlay, config, {
+        value: true,
+      }); 
+    } else { 
+      overlay.value = false;
+    }
+  },
+
+  //打开遮罩层
+  openModal(vm, config) {
+    let { zIndex, duration, overlayClass, overlayStyle} = config;
+ 
+    modalStack.push({
+      vm,
+      config: {
+        zIndex,
+        duration,
+        overlayClass,
+        overlayStyle,
+      },
+    });
+
+    overlayManager.updateOverlay();
+  },
+
+  clickHandle() {
+    const { topStack } = overlayManager;
+    
+    //防止多次点击
+    if (modalStack.length && topStack.vm.closeOnClickOverlay) { 
+      topStack.vm.$emit("click-overlay");
+      topStack.vm.close();
+    }
+  },
+
+  closeOverlay(vm) {
+    if (modalStack.length) {
+      if (overlayManager.topStack.vm === vm) {
+        modalStack.pop();
+        overlayManager.updateOverlay();
+      } else {
+        modalStack = modalStack.filter((item) => item.vm !== vm);
+      }
+    }
+  },
+};
+
+const overlayProps = {
+  value: {
+    type: Boolean,
+    default: false,
+  },
+  overlay: {
+    type: Boolean,
+    default: true,
+  },
+  lockScroll: {
+    type: Boolean,
+    default: true,
+  },
+  duration: {
+    type: Number,
+    default: 0.3,
+  },
+  closeOnClickOverlay: {
+    type: Boolean,
+    default: true,
+  },
+  overlayClass: {
+    type: String,
+    default: "",
+  },
+  overlayStyle: {
+    type: Object,
+    default: function () {
+      return null
+    },
+  },
+  zIndex: {
+    type: Number
+  },
+};
+
+function mount(Component, data) {
+
+  const instance = new Vue({ 
+    props: Component.props,
+    render(h) {    
+      return h(Component, {
+        props:this.$props,
+        ...data,
+      });
+    },
+  }).$mount();
+  return instance;
+}
+
+function getProps(){
+  
+  if(!this)return {}
+  let obj = {};
+    Object.keys(overlayProps).forEach(res=>{
+        obj[res] = this[res]
+  }) 
+  return obj
+}
+
+export  {overlayManager ,overlayProps, getProps};

+ 59 - 0
src/packages/popup/overlay/overlay.vue

@@ -0,0 +1,59 @@
+<template>
+  <transition name="popup-fade">
+    <div
+      @touchmove.stop="touchmove"
+      :style="{ animationDuration: `${duration}s`, ...overlayStyle, zIndex }"
+      v-show="value"
+      class="popup-bg nut-mask"
+      :class="overlayClass"
+    ></div>
+  </transition>
+</template>
+<script>
+const overlayProps = {
+  value: {
+    type: Boolean,
+    default: false,
+  },
+  overlay: {
+    type: Boolean,
+    default: true,
+  },
+  lockScroll: {
+    type: Boolean,
+    default: true,
+  },
+  duration: {
+    type: Number,
+    default: 0.3,
+  },
+  closeOnClickOverlay: {
+    type: Boolean,
+    default: true,
+  },
+  overlayClass: {
+    type: String,
+    default: "",
+  },
+  overlayStyle: {
+    type: Object,
+    default: ()=>{},
+  },
+  zIndex: {
+    type: Number
+  },
+};
+export { overlayProps };
+export default {
+  name: "nut-popup-overlay",
+  props: overlayProps,
+   
+  methods: {
+    touchmove(e) {
+      if (this.lockScroll) {
+        e.preventDefault();
+      }
+    },
+  },
+};
+</script>

+ 2 - 4
src/packages/popup/popup.scss

@@ -77,8 +77,7 @@ $popup-close-icon-margin: 16px;
   overflow-y: auto;
   background-color: #fff;
   transition: transform 0.3s;
-  -webkit-overflow-scrolling: touch;
-  z-index: 100;
+  -webkit-overflow-scrolling: touch; 
 }
 @keyframes nut-fade-in {
   from {
@@ -144,8 +143,7 @@ $popup-close-icon-margin: 16px;
   left: 0;
   width: 100%;
   height: 100%;
-  background-color: rgba(0, 0, 0, 0.7);
-  z-index: 99;
+  background-color: rgba(0, 0, 0, 0.7); 
 }
 
 @keyframes nut-fade-in {

+ 121 - 116
src/packages/popup/popup.vue

@@ -23,93 +23,81 @@
 </template>
 <script>
 import Vue from 'vue';
-import overlay from './overlay.vue';
 import Icon from '../icon/icon.vue';
+import touchMixins from '../../mixins/touch.js';
+import { overlayManager } from './overlay/overlay-manager.js';
+import { overlayProps } from './overlay/overlay.vue';
+import { on, off } from '../../utils/event';
 import '../icon/icon.scss';
+
+const overflowScrollReg = /scroll|auto/i;
+const popupProps = {
+  position: {
+    type: String,
+    default: 'center'
+  },
+
+  transition: String,
+
+  closeable: {
+    type: Boolean,
+    default: false
+  },
+  closeIconPosition: {
+    type: String,
+    default: 'top-right'
+  },
+  closeIcon: {
+    type: String,
+    default: 'cross'
+  },
+
+  closeOnClickOverlay: {
+    type: Boolean,
+    default: true
+  },
+
+  destroyOnClose: {
+    type: Boolean,
+    default: false
+  },
+  getContainer: String,
+  round: {
+    type: Boolean,
+    default: false
+  }
+};
 export default {
   name: 'nut-popup',
+  mixins: [touchMixins],
   components: {
     icon: Icon
   },
   props: {
-    value: {
-      type: Boolean,
-      default: false
-    },
-    position: {
-      type: String,
-      default: 'center'
-    },
-    duration: {
-      type: Number,
-      default: 0.3
-    },
-    transition: String,
-    overlay: {
-      type: Boolean,
-      default: true
-    },
-    closeable: {
-      type: Boolean,
-      default: false
-    },
-    closeIconPosition: {
-      type: String,
-      default: 'top-right'
-    },
-    closeIcon: {
-      type: String,
-      default: 'cross'
-    },
-    lockScroll: {
-      type: Boolean,
-      default: true
-    },
-    closeOnClickOverlay: {
-      type: Boolean,
-      default: true
-    },
-    overlayClass: {
-      type: String,
-      default: ''
-    },
-    overlayStyle: {
-      type: String,
-      default: ''
-    },
-    destroyOnClose: {
-      type: Boolean,
-      default: false
-    },
-    getContainer: String,
-    round: {
-      type: Boolean,
-      default: false
-    }
+    ...overlayProps,
+    ...popupProps
   },
   created() {
     this.transition ? (this.transitionName = this.transition) : (this.transitionName = `popup-slide-${this.position}`);
   },
   mounted() {
-    this.mountOverlay();
-    if (this.getContainer) {
-      this.portal();
-    }
     if (this.value) {
       this.open();
     }
   },
+  beforeDestroy() {
+    this.close();
+  },
   watch: {
     value(val) {
       const type = val ? 'open' : 'close';
-      if (this.overlay) {
-        this[type]();
-      }
+      this[type]();
     },
     position(val) {
       val === 'center' ? (this.transitionName = 'popup-fade') : (this.transitionName = `popup-slide-${this.position}`);
     },
-    getContainer: 'portal'
+    getContainer: 'portal',
+    overlay: 'renderOverlay'
   },
   data() {
     return {
@@ -123,75 +111,91 @@ export default {
       return this.duration ? this.duration + 's' : 'initial';
     }
   },
+
   methods: {
-    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);
-              }
-            }
-          }
-        });
+    open() {
+      if (this.opened) {
+        return;
       }
-    },
-    mount(Component, data) {
-      const instance = new Vue({
-        el: document.createElement('div'),
-        props: Component.props,
-        render(h) {
-          return h(Component, {
-            props: this.$props,
-            ...data
-          });
+
+      this.opened = true;
+      this.$emit('open');
+
+      const { duration, overlayClass, overlayStyle, lockScroll, closeOnClickOverlay } = this;
+      const config = {
+        zIndex: this.zIndex ? this.zIndex : overlayManager.zIndex,
+        duration,
+        overlayClass,
+        overlayStyle,
+        lockScroll,
+        closeOnClickOverlay
+      };
+
+      this.renderOverlay(config);
+
+      if (this.lockScroll) {
+        on(document, 'touchstart', this.touchStart);
+        on(document, 'touchmove', this.onTouchMove);
+
+        if (!overlayManager.lockCount) {
+          document.body.classList.add('nut-overflow-hidden');
         }
-      });
-      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);
+        overlayManager.lockCount++;
       }
-      return instance;
+
+      this.$el.style.zIndex = this.zIndex ? this.zIndex + 1 : overlayManager.zIndex;
     },
+    renderOverlay(config) {
+      if (!this.value) {
+        return;
+      }
 
-    open() {
-      if (!this.overlayInstant) {
-        this.mountOverlay();
+      if (this.overlay) {
+        overlayManager.openModal(this, config);
       } else {
-        this.overlayInstant.show = true;
-        this.showSlot = true;
+        overlayManager.closeOverlay(this);
       }
+    },
+    onTouchMove(event) {
+      this.touchMove(event);
+      const el = this.getScroller(event.target);
+      const { scrollHeight, offsetHeight, scrollTop } = el ? el : this.$el;
 
-      if (this.lockScroll && !this.locked) {
-        document.body.classList.add('nut-overflow-hidden');
-        this.locked = true;
+      if ((this.deltaY > 0 && scrollTop === 0) || (this.deltaY < 0 && scrollTop + offsetHeight >= scrollHeight)) {
+        //event.preventDefault();
       }
+    },
+    getScroller(el) {
+      let node = el;
+      while (node && node.tagName !== 'HTML' && node.nodeType === 1) {
+        const { overflowY } = window.getComputedStyle(node);
 
-      this.$emit('open', this);
+        if (overflowScrollReg.test(overflowY)) {
+          return node;
+        }
+
+        node = node.parentNode;
+      }
     },
     close() {
-      this.overlayInstant.show = false;
-      if (this.destroyOnClose) {
-        setTimeout(() => {
-          this.showSlot = false;
-        }, this.duration * 1000);
+      if (!this.opened) {
+        return;
       }
-
-      if (this.lockScroll && this.locked) {
-        document.body.classList.remove('nut-overflow-hidden');
-        this.locked = false;
+      this.$emit('close');
+      this.opened = false;
+      if (this.lockScroll) {
+        overlayManager.lockCount--;
+        off(document, 'touchstart', this.touchStart);
+        off(document, 'touchmove', this.onTouchMove);
+        if (!overlayManager.lockCount) {
+          document.body.classList.remove('nut-overflow-hidden');
+        }
       }
-      this.$emit('close', this);
+
+      overlayManager.closeOverlay(this);
+      this.$emit('input', false);
     },
+
     getElement(selector) {
       return document.querySelector(selector);
     },
@@ -212,4 +216,5 @@ export default {
     }
   }
 };
+export  {popupProps}
 </script>

+ 5 - 9
src/packages/toast/_toast.js

@@ -5,6 +5,7 @@ let ToastConstructor = Vue.extend(settings);
 let instance;
 let instanceArr = [];
 let defaultOptionsMap = {};
+const id = '0';
 const defaultOptions = {
   msg: '',
   visible: false,
@@ -37,15 +38,10 @@ function _showToast() {
     instance.visible = true;
   });
 }
-
+ 
 function _getInstance(obj) {
-  let opt = {
-    id: new Date().getTime(),
-    ...currentOptions,
-    ...defaultOptionsMap[obj.type],
-    ...obj
-  };
-
+  let opt = {id};
+  Object.assign(opt,currentOptions,defaultOptionsMap[obj.type],obj)
   //有相同id者共用一个实例,否则新增实例
   if (opt['id'] && instanceArr[opt['id']]) {
     instance = instanceArr[opt['id']];
@@ -87,7 +83,7 @@ let Toast = {
     return _getInstance({ ...obj, msg, type: 'warn' });
   },
   loading(msg, obj = {}) {
-    obj = { ...obj, id: obj.id || 'loading', msg, type: 'loading' };
+    obj = { ...obj, id: obj.id|| id , msg, type: 'loading' };
     obj.cover = typeof obj.cover !== 'undefined' ? obj.cover : true; //loading类型默认打开遮罩层
     obj.duration = obj.duration || 0; //loading类型默认不自动关闭
     return _getInstance(obj);

+ 38 - 102
src/packages/toast/demo.vue

@@ -1,113 +1,42 @@
 <template>
   <div class="demo-list">
     <h4>基本用法</h4>
-    <div>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="textToast1('我只传了文案一个参数')">
-        <span slot="title">
-          <label>文字提示</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="textToast2('我传了文案和显示时长两个参数,多行文字默认居中展示', 5000)">
-        <span slot="title">
-          <label>长文字提示</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="sucToast('操作成功')">
-        <span slot="title">
-          <label>成功提示</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="failToast('操作失败')">
-        <span slot="title">
-          <label>失败提示</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="warnToast('操作警告')">
-        <span slot="title">
-          <label>警告提示</label>
-        </span>
-      </nut-cell>
+    <div class="demo-content">
+      <nut-button size="middle" @click.native="textToast1('我只传了文案一个参数')">文字提示</nut-button>
+      <nut-button size="middle" @click.native="textToast2('我传了文案和显示时长两个参数,多行文字默认居中展示', 5000)">长文字提示</nut-button>
+      <nut-button size="middle" @click.native="sucToast('操作成功')">成功提示</nut-button>
+      <nut-button size="middle" @click.native="failToast('操作失败')">失败提示</nut-button>
+      <nut-button size="middle" @click.native="warnToast('操作警告')" >警告提示</nut-button>
     </div>
     <h4>加载提示</h4>
-    <div>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="showLoading()">
-        <span slot="title">
-          <label>Loading</label>
-        </span>
-        <span slot="desc">带文案+带透明遮罩(默认)+自动消失</span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="showLoading2()">
-        <span slot="title">
-          <label>Loading</label>
-        </span>
-        <span slot="desc">带文案+带半透明遮罩+自动消失</span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="showLoading3()">
-        <span slot="title">
-          <label>Loading</label>
-        </span>
-        <span slot="desc">不自动消失+不带遮罩</span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="hideLoading()">
-        <span slot="title">
-          <label>隐藏Loading</label>
-        </span>
-        <span slot="desc">点击手动隐藏上面的Loading</span>
-      </nut-cell>
+    <div class="demo-content">
+      <nut-button size="middle" @click.native="showLoading()">透明遮罩</nut-button>
+      <nut-button size="middle" @click.native="showLoading2()">半透明遮罩</nut-button>
+      <nut-button size="middle" @click.native="showLoading3()">不自动消失+不带遮罩</nut-button>
+      <nut-button size="middle" @click.native="hideLoading()">隐藏Loading</nut-button>
     </div>
+
     <h4>自定义样式</h4>
-    <div>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="cusBgToast('我修改了背景色和透明度')">
-        <span slot="title">
-          <label>自定义背景色和透明度</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="cusClassToast('我有一个名为 my-class 自定义class')">
-        <span slot="title">
-          <label>自定义class</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="cusIconToast('自定义Icon')">
-        <span slot="title">
-          <label>自定义Icon</label>
-        </span>
-      </nut-cell>
+    <div class="demo-content">
+      <nut-button size="middle" @click.native="cusBgToast('我修改了背景色和透明度')">自定义背景色和透明度</nut-button>
+      <nut-button size="middle" @click.native="cusClassToast('我有一个名为 my-class 自定义class')">自定义class</nut-button>
+      <nut-button size="middle" @click.native="cusIconToast('自定义Icon')">自定义Icon</nut-button>
     </div>
+
     <h4>共享实例</h4>
-    <div>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="idToast1('我设置了id为123')">
-        <span slot="title">
-          <label>我设置了id为123</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="idToast2('我设置了id为321')">
-        <span slot="title">
-          <label>我设置了id为321</label>
-        </span>
-      </nut-cell>
+
+    <div class="demo-content">
+      <nut-button size="middle" @click.native="idToast1('我设置了id为1')">我设置了id为1</nut-button>
+      <nut-button size="middle" @click.native="idToast2('我设置了id为2')">我设置了id为2</nut-button>
     </div>
+
     <h4>更改默认配置</h4>
-    <div>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="setDefaultOptions()">
-        <span slot="title">
-          <label>将所有Toast提示展示时长设置为5000毫秒</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="resetDefaultOptions()">
-        <span slot="title">
-          <label>恢复所有Toast提示默认配置</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="setDefaultOptions2()">
-        <span slot="title">
-          <label>更改文字提示默认配置</label>
-        </span>
-      </nut-cell>
-      <nut-cell :showIcon="true" :isLink="true" @click.native="resetDefaultOptions2()">
-        <span slot="title">
-          <label>恢复文字提示默认配置</label>
-        </span>
-      </nut-cell>
+
+    <div class="demo-content">
+      <nut-button size="middle" @click.native="setDefaultOptions()">将所有Toast提示展示时长设置为5000毫秒</nut-button>
+      <nut-button size="middle" @click.native="resetDefaultOptions()">恢复所有Toast提示默认配置</nut-button>
+      <nut-button size="middle" @click.native="setDefaultOptions2()">更改文字提示默认配置</nut-button>
+      <nut-button size="middle" @click.native="resetDefaultOptions2()">恢复文字提示默认配置</nut-button>
     </div>
   </div>
 </template>
@@ -172,10 +101,10 @@ export default {
       });
     },
     idToast1(msg) {
-      this.$toast.success(msg, { id: 123 });
+      this.$toast.success(msg, { id: 1 ,center:false,bottom:450});
     },
     idToast2(msg) {
-      this.$toast.text(msg, { id: 321, duration: 4000 });
+      this.$toast.fail(msg, { id: 2,center:false,bottom:300 });
     },
     setDefaultOptions() {
       this.$toast.setDefaultOptions({
@@ -208,4 +137,11 @@ export default {
 };
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.demo-content {
+  margin: 12px;
+}
+/deep/.nut-button {
+  margin: 4px;
+}
+</style>

+ 5 - 8
src/packages/toast/doc.md

@@ -90,14 +90,11 @@ this.$toast.text('自定义Icon',{
 
 ```javascript
 //二者id不同,不会共享一个实例
-this.$toast.success(msg,{
-    id:123
-});
-
-this.$toast.text(msg,{
-    id:321,
-    duration:4000
-});
+ 
+this.$toast.success(msg, { id: 1 ,center:false,bottom:450});
+ 
+this.$toast.fail(msg, { id: 2,center:false,bottom:300 });
+ 
 ```
 
 ## 支持在JS模块中导入使用

+ 20 - 17
src/packages/toast/toast.scss

@@ -1,28 +1,34 @@
 @import '../../styles/animation/rotate';
-.nut-toast {
-  position: fixed;
-  left: 0;
-  bottom: 150px;
+.nut-toast{
   width: 100%;
   text-align: center;
-  pointer-events: none;
-  z-index: 9999;
-  font-family: $font-family;
-  &.nut-toast-small {
+  &.popup-box{
+    background-color: transparent;
+  }
+  
+  &.nut-toast-buttom {
+    bottom: 150px;
+    top: auto;
+    left: 0;
+    transform: initial;
+  }
+}
+  .nut-toast-small {
     .nut-toast-inner {
       font-size: $font-size-small;
     }
   }
-  &.nut-toast-large {
+  .nut-toast-large {
     .nut-toast-inner {
       font-size: $font-size-large;
     }
   }
-  &.nut-toast-cover {
+  .nut-toast-cover {
     display: flex;
     align-items: center;
     justify-content: center;
     pointer-events: auto;
+    background-color: transparent;
     height: 100%;
   }
   .nut-toast-inner {
@@ -38,7 +44,7 @@
     border-radius: 7px;
     color: #fff;
   }
-  &.nut-toast-has-icon {
+  .nut-toast-has-icon {
     .nut-toast-inner {
       padding: 70px 50px 30px;
     }
@@ -74,11 +80,8 @@
       }
     }
   }
-  &.nut-toast-center {
-    top: 50%;
-    transform: translateY(-50%);
-  }
-  &.nut-loading {
+  
+  .nut-loading {
     .nut-toast-inner {
       padding: 25px;
       display: inline-flex;
@@ -102,7 +105,7 @@
       }
     }
   }
-}
+
 
 .toastfade-enter-active {
   transition: opacity 0.1s;

+ 12 - 12
src/packages/toast/toast.vue

@@ -1,13 +1,13 @@
 <template>
   <transition name="toastfade">
-    <div
-      :id="id"
+    <nut-popup
+      :overlay='cover'
       :class="toastClass"
-      v-if="visible"
-      :style="{
-        bottom: center ? 'auto' : bottom + 'px',
-        'background-color': coverColor
-      }"
+      v-model="visible"
+      :closeOnClickOverlay='closeOnClickOverlay'
+      :overlayStyle='{backgroundColor:coverColor}'
+      class="nut-toast"
+      :style="{ bottom: center?'auto':bottom + 'px'}"
       @click="clickCover"
     >
       <div
@@ -25,7 +25,7 @@
         </span>
         <span class="nut-toast-text" v-html="msg"></span>
       </div>
-    </div>
+    </nut-popup>
   </transition>
 </template>
 <script>
@@ -42,7 +42,7 @@ export default {
       center: true,
       type: '',
       customClass: '',
-      bottom: 30,
+      bottom: '',
       size: 'base',
       icon: null,
       textAlignCenter: true,
@@ -56,6 +56,7 @@ export default {
       closeOnClickOverlay: false
     };
   },
+  
   watch: {
     visible(val) {
       if (val) {
@@ -70,9 +71,8 @@ export default {
     toastClass() {
       return [
         'nut-toast',
-        { 'nut-toast-center': this.center },
-        { 'nut-toast-has-icon': this.hasIcon },
-        { 'nut-toast-cover': this.cover },
+        { 'nut-toast-buttom': !this.center },
+        { 'nut-toast-has-icon': this.hasIcon }, 
         { 'nut-loading': this.type == 'loading' },
         this.customClass,
         'nut-toast-' + this.size

+ 4 - 0
src/styles/variable.scss

@@ -132,3 +132,7 @@ $tag-height-big: 24px !default;
 $tag-border-radius: 2px !default;
 $tag-border-radius-circle: 10px !default;
 
+// ---- Badge ----
+$badge-font-size: 11px !default;
+$badge-font-color: #fff !default;
+

+ 36 - 0
src/utils/event.js

@@ -0,0 +1,36 @@
+export let passiveSupported = false;
+
+ 
+try {
+    var options = Object.defineProperty({}, "passive", {
+      get: function() {
+        passiveSupported = true;
+      }
+    });
+  
+    window.addEventListener("test", null, options);
+} catch(err) {}
+  
+ 
+export function on(
+  target,
+  event,
+  handler,
+  passive = false
+) {
+ 
+    target.addEventListener(
+      event,
+      handler,
+      passiveSupported ? { capture: false, passive } : false
+    );
+ 
+}
+
+export function off(target, event, handler) {
+    target.removeEventListener(event, handler); 
+}
+ 
+
+ 
+