ソースを参照

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

songchenglin3 5 年 前
コミット
7d5501b695

+ 19 - 0
src/config.js

@@ -137,6 +137,15 @@ module.exports = {
           show: true,
           desc: '较长页面快捷回到顶部',
           author: 'liqiong43'
+        },
+        {
+          name: 'Toast',
+          sort: '1',
+          cName: '吐司',
+          type: 'component',
+          show: true,
+          desc: '轻提示',
+          author: 'undo'
         }
       ]
     },
@@ -265,6 +274,16 @@ module.exports = {
           sort: 11,
           show: false,
           author: 'Ymm0008'
+        },
+        {
+          version: '3.0.0',
+          name: 'OverLay',
+          type: 'component',
+          cName: '遮罩层',
+          desc: '创建一个遮罩层,通常用于阻止用户进行其他操作',
+          sort: 14,
+          show: true,
+          author: 'szg2008'
         }
       ]
     },

+ 16 - 16
src/packages/icon/index.vue

@@ -1,5 +1,5 @@
 <script lang="ts">
-import { h, PropType, computed, toRefs } from 'vue';
+import { h, PropType, computed } from 'vue';
 import { createComponent } from '@/utils/create';
 const { componentName, create } = createComponent('icon');
 
@@ -14,34 +14,34 @@ export default create({
   emits: ['click'],
 
   setup(props, { emit, slots }) {
-    const { name, size, classPrefix, color, tag } = toRefs(props);
-
     const handleClick = (event: Event) => {
       emit('click', event);
     };
 
-    const isImage = computed(() => {
-      return name.value ? name.value.indexOf('/') !== -1 : false;
-    }).value;
+    const isImage = () => {
+      return props.name ? props.name.indexOf('/') !== -1 : false;
+    };
 
-    return () =>
-      h(
-        isImage ? 'img' : tag.value,
+    return () => {
+      const _isImage = isImage();
+      return h(
+        _isImage ? 'img' : props.tag,
         {
-          class: isImage
+          class: _isImage
             ? `${componentName}__img`
-            : `${classPrefix.value} ${componentName}-${name.value}`,
+            : `${props.classPrefix} ${componentName}-${props.name}`,
           style: {
-            color: color.value,
-            fontSize: size.value,
-            width: isImage ? size.value : '',
-            height: isImage ? size.value : ''
+            color: props.color,
+            fontSize: props.size,
+            width: _isImage ? props.size : '',
+            height: _isImage ? props.size : ''
           },
           onClick: handleClick,
-          src: isImage ? name.value : ''
+          src: _isImage ? props.name : ''
         },
         slots.default?.()
       );
+    };
   }
 });
 </script>

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

@@ -0,0 +1,59 @@
+<template>
+  <div class="demo">
+    <h2>基础用法</h2>
+    <nut-cell>
+      <nut-button type="primary" @click="state.show = true"
+        >显示遮罩层</nut-button
+      >
+      <nut-overlay v-model:show="state.show" :z-index="2000"></nut-overlay>
+    </nut-cell>
+    <h2>嵌套内容</h2>
+    <nut-cell>
+      <nut-button type="success" @click="state.show2 = true"
+        >嵌套内容</nut-button
+      >
+      <nut-overlay v-model:show="state.show2" :z-index="2000">
+        <div class="wrapper">
+          <div class="content">这里是正文</div>
+        </div>
+      </nut-overlay>
+    </nut-cell>
+  </div>
+</template>
+
+<script lang="ts">
+import { reactive } from 'vue';
+import { createComponent } from '@/utils/create';
+const { createDemo } = createComponent('overlay');
+export default createDemo({
+  props: {},
+  setup() {
+    const state = reactive({
+      show: false,
+      show2: false
+    });
+    return {
+      state
+    };
+  }
+});
+</script>
+
+<style lang="scss" scoped>
+.wrapper {
+  display: flex;
+  height: 100%;
+  align-items: center;
+  justify-content: center;
+  .content {
+    display: flex;
+    width: 150px;
+    height: 150px;
+    background: #fff;
+    border-radius: 8px;
+    align-items: center;
+    justify-content: center;
+    color: red;
+  }
+}
+</style>

+ 54 - 0
src/packages/overlay/doc.md

@@ -0,0 +1,54 @@
+# overlay 组件
+
+### 介绍
+
+创建一个遮罩层,通常用于阻止用户进行其他操作
+
+### 安装
+
+```javascript
+import { createApp } from 'vue';
+import { OverLay } from '@nutui/nutui';
+
+const app = createApp();
+app.use(OverLay);
+```
+
+## 代码演示
+
+### 基础用法
+
+```html
+<nut-overlay v-model:show="state.show" :z-index="2000"></nut-overlay>
+```
+
+### 嵌套内容
+
+```html
+<nut-overlay v-model:show="state.show2" :z-index="2000">
+  <div class="wrapper">
+    <div class="content">这里是正文</div>
+  </div>
+</nut-overlay>
+```
+
+## API
+
+### Props
+
+| 参数                   | 说明             | 类型           | 默认值 |
+| ---------------------- | ---------------- | -------------- | ------ |
+| show                   | 当前组件是否显示 | Boolean        | false  |
+| z-index                | 遮罩层级         | String、Number | 2000   |
+| duration               | 动画时长,单位秒 | String、Number | 0.3    |
+| overlay-class          | 自定义遮罩类名   | String         | -      |
+| overlay-style          | 自定义遮罩样式   | CSSProperties  | -      |
+| lock-scroll            | 背景是否锁定     | Boolean        | false  |
+| overlay                | 是否显示遮罩     | Boolean        | true   |
+| close-on-click-overlay | 是否点击遮罩关闭 | Boolean        | true   |
+
+### Events
+
+| 事件名 | 说明       | 回调参数     |
+| ------ | ---------- | ------------ |
+| click  | 点击时触发 | event: Event |

+ 36 - 0
src/packages/overlay/index.scss

@@ -0,0 +1,36 @@
+.overlay-fade-enter-active {
+  animation: nut-fade-in;
+}
+
+.overlay-fade-leave-active {
+  animation: nut-fade-out;
+}
+
+.nut-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.7);
+}
+
+@keyframes nut-fade-in {
+  from {
+    opacity: 0;
+  }
+
+  to {
+    opacity: 1;
+  }
+}
+
+@keyframes nut-fade-out {
+  from {
+    opacity: 1;
+  }
+
+  to {
+    opacity: 0;
+  }
+}

+ 29 - 9
src/packages/popup/overlay/index.vue

@@ -1,18 +1,20 @@
 <template>
-  <Transition name="popup-fade">
+  <Transition name="overlay-fade">
     <view
       @touchmove.stop="touchmove"
+      @click="onClick"
       :style="{ animationDuration: `${duration}s`, ...overlayStyle, zIndex }"
       v-show="show"
-      class="popup-bg nut-mask"
-      :class="overlayClass"
-    ></view>
+      :class="classes"
+    >
+      <slot></slot>
+    </view>
   </Transition>
 </template>
 <script lang="ts">
-import { toRefs, CSSProperties, PropType, Transition } from 'vue';
+import { CSSProperties, PropType, computed } from 'vue';
 import { createComponent } from '@/utils/create';
-const { componentName, create } = createComponent('popup-overlay');
+const { componentName, create } = createComponent('overlay');
 const overlayProps = {
   show: {
     type: Boolean,
@@ -50,14 +52,32 @@ export { overlayProps };
 
 export default create({
   props: overlayProps,
-  emits: [],
-  setup(props) {
+  emits: ['click', 'update:show'],
+  setup(props, { emit }) {
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true,
+        [props.overlayClass]: true
+      };
+    });
     const touchmove = e => {
       if (props.lockScroll) {
         e.preventDefault();
       }
     };
-    return { touchmove };
+
+    const onClick = e => {
+      emit('click', e);
+      if (props.closeOnClickOverlay) {
+        emit('update:show', false);
+      }
+    };
+
+    return { classes, touchmove, onClick };
   }
 });
 </script>
+<style lang="scss">
+@import 'index.scss';
+</style>

+ 1 - 1
src/packages/popup/demo.vue

@@ -96,7 +96,7 @@
       :style="{ height: '30%' }"
       v-model:show="state.showRound"
     ></nut-popup>
-    <h2>组合弹框</h2>
+    <!-- <h2>组合弹框</h2> -->
     <!-- <nut-cell title="组合弹框" is-link @click="state.showCombination = true"></nut-cell>
     <nut-popup
       id="combination"

+ 93 - 13
src/packages/popup/doc.md

@@ -16,28 +16,108 @@ app.use(Popup);
 
 ## 代码演示
 
-### 基础用法 1
+### 基础用法
 
-`Icon` 的 `name` 属性支持传入图标名称或图片链接。
+`show` 控制显示/隐藏
 
 ```html
-<nut-popup name="wifi"></nut-popup> <nut-popup name="mail"></nut-popup>
+<nut-popup :style="{ padding: '30px' }" v-model:show="show">正文</nut-popup>
+```
+
+### 弹出位置
+
+```html
+<nut-popup
+  position="top"
+  v-model:show="show"
+  :style="{ height: '20% }"
+></nut-popup>
+
+<nut-popup
+  position="left"
+  v-model:show="show"
+  :style="{ height: '100%', width: '20%' }"
+></nut-popup>
+```
+
+### 图标
+
+```html
+<nut-popup
+  position="bottom"
+  closeable
+  :style="{ height: '20%' }"
+  v-model:show="show"
+></nut-popup>
+
+<nut-popup
+  position="bottom"
+  closeable
+  close-icon-position="top-left"
+  :style="{ height: '20%' }"
+  v-model:show="show"
+></nut-popup>
+
+<nut-popup
+  position="bottom"
+  closeable
+  close-icon-position="top-left"
+  close-icon="heart"
+  :style="{ height: '20%' }"
+  v-model:show="show"
+></nut-popup>
+```
+
+### 圆角弹框
+
+```html
+<nut-popup
+  position="bottom"
+  closeable
+  round
+  :style="{ height: '30%' }"
+  v-model:show="show"
+></nut-popup>
+```
+
+### 指定挂载节点
+
+```html
+<nut-popup :style="{ padding: '30px' }" teleport="#app" v-model:show="show">app</nut-popup
 ```
 
 ## API
 
 ### Props
 
-| 参数         | 说明                             | 类型   | 默认值           |
-| ------------ | -------------------------------- | ------ | ---------------- |
-| name         | 图标名称或图片链接               | String | -                |
-| color        | 图标颜色                         | String | -                |
-| size         | 图标大小,如 `20px` `2em` `2rem` | String | -                |
-| class-prefix | 类名前缀,用于使用自定义图标     | String | `nutui-iconfont` |
-| tag          | HTML 标签                        | String | `i`              |
+| 参数                   | 说明                                                        | 类型           | 默认值      |
+| ---------------------- | ----------------------------------------------------------- | -------------- | ----------- |
+| show                   | 当前组件是否显示                                            | Boolean        | false       |
+| z-index                | 遮罩层级                                                    | String、Number | 2000        |
+| duration               | 动画时长,单位秒                                            | String、Number | 0.3         |
+| overlay-class          | 自定义遮罩类名                                              | String         | -           |
+| overlay-style          | 自定义遮罩样式                                              | CSSProperties  | -           |
+| lock-scroll            | 背景是否锁定                                                | Boolean        | false       |
+| overlay                | 是否显示遮罩                                                | Boolean        | true        |
+| close-on-click-overlay | 是否点击遮罩关闭                                            | Boolean        | true        |
+| position               | 弹出位置(top,bottom,left,right,center)                    | String         | "center"    |
+| transition             | 动画名                                                      | String         | -           |
+| style                  | 自定义弹框样式                                              | CSSProperties  | -           |
+| closeable              | 是否显示关闭按钮                                            | Boolean        | true        |
+| close-icon-position    | 关闭按钮位置(top-left,top-right,bottom-left,bottom-right) | String         | "top-right" |
+| close-icon             | 自定义 Icon                                                 | String         | "close"     |
+| destroy-on-close       | 组件销毁后是否关闭                                          | Boolean        | true        |
+| round                  | 是否显示圆角                                                | Boolean        | false       |
+| teleport               | 指定挂载节点                                                | String         | "body"      |
 
 ### Events
 
-| 事件名 | 说明           | 回调参数     |
-| ------ | -------------- | ------------ |
-| click  | 点击图标时触发 | event: Event |
+| 事件名           | 说明                   | 回调参数     |
+| ---------------- | ---------------------- | ------------ |
+| click            | 点击弹框时触发         | event: Event |
+| click-close-icon | 点击关闭图标时触发     | event: Event |
+| open             | 打开弹框时触发         | -            |
+| close            | 关闭弹框时触发         | -            |
+| opend            | 遮罩打开动画结束时触发 | event: Event |
+| closed           | 遮罩关闭动画结束时触发 | event: Event |
+| click-overlay    | 点击遮罩触发           | event: Event |

+ 1 - 18
src/packages/popup/index.scss

@@ -1,11 +1,3 @@
-.popup-fade-enter-active {
-  animation: nut-fade-in;
-}
-
-.popup-fade-leave-active {
-  animation: nut-fade-out;
-}
-
 .popup-slide {
   &-center-enter-active {
     animation: nut-fade-in;
@@ -79,7 +71,7 @@
   }
 }
 
-.popup-box {
+.nut-popup {
   position: fixed;
   max-height: 100%;
   overflow-y: auto;
@@ -145,12 +137,3 @@
     }
   }
 }
-
-.popup-bg {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(0, 0, 0, 0.7);
-}

+ 14 - 17
src/packages/popup/index.vue

@@ -1,8 +1,7 @@
 <template>
   <Teleport :to="teleport">
-    <nut-popup-overlay
+    <nut-overlay
       :show="show && overlay"
-      v-if="state.overLayCount === 1"
       :class="overlayClass"
       :style="overlayStyle"
       :zIndex="state.zIndex"
@@ -14,13 +13,7 @@
       @after-enter="onOpened"
       @after-leave="onClosed"
     >
-      <view
-        v-show="show"
-        class="popup-box"
-        :class="[`popup-${position}`, { round }]"
-        :style="popStyle"
-        @click="onClick"
-      >
+      <view v-show="show" :class="classes" :style="popStyle" @click="onClick">
         <slot v-if="state.showSlot"></slot>
         <nut-icon
           v-if="closeable"
@@ -49,7 +42,7 @@ import {
   CSSProperties
 } from 'vue';
 import { useLockScroll } from './use-lock-scroll';
-import Overlay, { overlayProps } from './overlay/index.vue';
+import { overlayProps } from './../overlay/index.vue';
 import { createComponent } from '@/utils/create';
 const { componentName, create } = createComponent('popup');
 
@@ -92,7 +85,7 @@ const popupProps = {
 
   destroyOnClose: {
     type: Boolean,
-    default: false
+    default: true
   },
 
   teleport: {
@@ -107,9 +100,6 @@ const popupProps = {
 };
 
 export default create({
-  Component: {
-    'nut-popup-overlay': Overlay
-  },
   props: {
     ...overlayProps,
     ...popupProps
@@ -139,6 +129,15 @@ export default create({
 
     const [lockScroll, unlockScroll] = useLockScroll(() => props.lockScroll);
 
+    const classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        [prefixCls]: true,
+        ['round']: props.round,
+        [`popup-${props.position}`]: true
+      };
+    });
+
     const popStyle = computed(() => {
       return {
         zIndex: state.zIndex,
@@ -262,10 +261,8 @@ export default create({
       onClosed,
       state,
       popStyle,
-      componentName
+      classes
     };
-
-    // return renderOverlay();
   }
 });
 </script>

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

@@ -1,142 +0,0 @@
-import { createApp, h } from 'vue';
-import overlayComponent from './overlay.vue';
-
-let modalStack = [];
-let _zIndex = 2000;
-let overlay;
-
-function mountd(Component, data) {
-  const instance = createApp(Component);
-
-  instance
-    .component(Component.name, {
-      render() {
-        return h(Component.name, ...data, this.$slots.default());
-      }
-    })
-    .mount();
-  console.log(instance);
-  return instance;
-}
-const overlayManager = {
-  lockCount: 0,
-
-  get topStack() {
-    return modalStack[modalStack.length - 1];
-  },
-  getZIndex(id) {
-    if (!id) return ++_zIndex;
-    const overlay = modalStack.find(res => {
-      return res.config.id === id;
-    });
-    if (overlay) {
-      return overlay.config.zIndex;
-    } else {
-      return ++_zIndex;
-    }
-  },
-
-  updateOverlay() {
-    const { clickHandle, topStack } = overlayManager;
-    if (!overlay) {
-      overlay = mountd(overlayComponent, {
-        nativeOn: {
-          click: clickHandle
-        }
-      });
-    }
-
-    console.log(topStack.vm);
-
-    if (topStack) {
-      const { vm, config } = topStack;
-      const el = vm.ctx.$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) {
-    const { zIndex, duration, overlayClass, overlayStyle, id } = config;
-
-    modalStack.push({
-      vm,
-      config: {
-        id,
-        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
-  }
-};
-
-export { overlayManager, overlayProps };

+ 4 - 2
src/packages/toast/demo.vue

@@ -52,8 +52,10 @@ export default createDemo({
       Toast.warn(msg);
     };
     const loadingToast = msg => {
-      Toast.loading(msg, { duration: 0 });
-      setTimeout(Toast.hide, 3000);
+      Toast.loading(msg, { duration: 0, id: 'test' });
+      setTimeout(() => {
+        Toast.success('加载完成', { id: 'test', duration: 2000 });
+      }, 2000);
     };
     return {
       textToast,

+ 28 - 107
src/packages/toast/doc.md

@@ -2,154 +2,75 @@
 
 轻提示。
 
-## 基本用法
-文字提示
-```javascript
-export default {
-  mounted() {
-    this.$toast.text('提示信息');
-  }
-}
-```
+### 介绍
 
-成功提示
+用于轻提示。
 
-```javascript
-export default {
-  mounted() {
-    this.$toast.success('操作成功!');
-  }
-}
+### 安装
+
+``` javascript
+import { createApp } from 'vue';
+import { Toast } from '@nutui/nutui';
+
+const app = createApp();
+app.use(Toast);
 ```
 
-失败提示
+## 代码演示
+
+### 基本用法
 
+文字提示
 ```javascript
-export default {
-  mounted() {
-    this.$toast.fail('操作失败!');
-  }
-}
+Toast.text(msg);
 ```
 
-警告提示
+成功提示
 
 ```javascript
-export default {
-  mounted() {
-    this.$toast.warn('确定删除?');
-  }
-}
+Toast.success(msg);
 ```
 
-## 加载提示
+失败提示
 
 ```javascript
-// 带文案,显示透明遮罩层(默认),自动消失
-this.$toast.loading('加载中...',{ 
-    duration:3000
-});
-
-//带文案,显示半透明遮罩层,自动消失,点击遮罩层后消失
-this.$toast.loading('加载中...',{ 
-    duration:3000,
-    coverColor: "rgba(0,0,0,0.5)",
-    closeOnClickOverlay: true
-});
-
-//不会自动消失(默认),不带遮罩层
-this.loading = this.$toast.loading({
-    cover: false
-});
-
-//手动关闭上面的Loading
-this.loading.hide();
+Toast.fail(msg);
 ```
 
-## 自定义样式
+警告提示
 
 ```javascript
-//自定义背景颜色/透明度
-this.$toast.text('自定义背景颜色/透明度',{
-    bgColor:'rgba(50, 50, 50, 0.9)'
-});
-
-//自定义class
-this.$toast.text('自定义class',{
-    customClass:'my-class'
-});
-
-//自定义Icon
-this.$toast.text('自定义Icon',{
-    icon:"https://img13.360buyimg.com/imagetools/jfs/t1/98294/28/14470/22072/5e65ba08E865683aa/ded7441bdd098511.png"
-});
+Toast.warn(msg);
 ```
-## 共享实例
 
-如果不设置id,多个Toast将默认拥有相同的id,**它们将共享一个实例**(loading类型与其他类型实例不共享),新的内容和设置将取代旧的,多个Toast不能同时出现。如果不需要共享实例,可以给其设置id。
+### 动态更新
 
 ```javascript
-//二者id不同,不会共享一个实例
-this.$toast.success(msg,{
-    id:123
-});
-
-this.$toast.text(msg,{
-    id:321,
-    duration:4000
-});
+Toast.loading(msg, { duration: 0, id: 'test' });
+setTimeout(() => {
+  Toast.success('加载完成', { id: 'test', duration: 2000 });
+}, 2000);
 ```
 
-## 支持在JS模块中导入使用
+##¥ 支持在JS模块中导入使用
 ```javascript
 import { Toast } from "@nutui/nutui";
-
 Toast.text('在js模块中使用');
 // 返回实例,可以手动调用实例中的hide方法关闭提示
 const toast = Toast.loading('在js模块中使用');
 toast.hide();
 ```
 
-## 修改默认配置
-
-通过**Toast.setDefaultOptions**函数可以全局修改 Toast 的默认配置,**值得注意的是,loading无法支持默认配置的修改和重置**。
-
-```js
-// 更改所有Toast展示时长设置为5000毫秒
-Toast.setDefaultOptions({
-    duration: 5000,
-    coverColor: "rgba(0, 0, 0, 0.2)",
-    closeOnClickOverlay: true,
-    cover: true
-});
-
-// 重置所有 Toast 的默认配置
-Toast.resetDefaultOptions();
-
-// 更改所有文字提示默认设置
-Toast.setDefaultOptions("text", {
-    size: "large",
-    cover: true,
-    coverColor: "rgba(0, 0, 0, 0.2)",
-    duration: 3000,
-    closeOnClickOverlay: true
-});
-
-// 重置 text Toast 的默认配置
-Toast.resetDefaultOptions("text");
-```
 
 ### API
 | 方法名                    | 说明                                                                    | 参数            | 返回值     |
 | ------------------------- | ----------------------------------------------------------------------- | --------------- | ---------- |
 | Toast.text                | 展示文字提示                                                            | options/message | toast 实例 |
-| Toast.loading             | 展示加载提示                                                            | options/message | toast 实例 |
 | Toast.success             | 展示成功提示                                                            | options/message | toast 实例 |
 | Toast.fail                | 展示失败提示                                                            | options/message | toast 实例 |
 | Toast.warn                | 展示警告提示                                                            | options/message | toast 实例 |
 | Toast.hide                | 关闭提示                                                                | force:boolean   | void       |
-| Toast.setDefaultOptions   | 修改默认配置,对所有 Toast 生效<br>传入 type 可以修改指定类型的默认配置 | type/options    | void       |
-| Toast.resetDefaultOptions | 重置默认配置,对所有 Toast 生效<br>传入 type 可以重置指定类型的默认配置 | type            | void       |
+| Toast.loading             | 展示加载提示                                                            | options/message | toast 实例 |
 
 ## Options
 

+ 5 - 5
src/packages/toast/index.scss

@@ -85,15 +85,15 @@
   }
 }
 
-.toastfade-enter-active {
-  transition: opacity 0.1s;
+.toast-fade-enter-active {
+  transition: opacity 0.3s;
 }
 
-.toastfade-leave-active {
+.toast-fade-leave-active {
   transition: opacity 0.3s;
 }
 
-.toastfade-enter,
-.toastfade-leave-active {
+.toast-fade-enter-from,
+.toast-fade-leave-to {
   opacity: 0;
 }

+ 33 - 21
src/packages/toast/index.vue

@@ -1,9 +1,8 @@
 <template>
-  <transition name="toastfade">
+  <Transition name="toast-fade" @after-leave="onAfterLeave">
     <view
-      :id="id"
       :class="toastBodyClass"
-      v-if="visible"
+      v-show="state.mounted"
       :style="{
         bottom: center ? 'auto' : bottom + 'px',
         'background-color': coverColor
@@ -23,21 +22,17 @@
         <view class="nut-toast-text" v-html="msg"></view>
       </view>
     </view>
-  </transition>
+  </Transition>
 </template>
 <script>
 import Icon from '../icon';
-import { toRefs, reactive, computed, watch } from 'vue';
+import { toRefs, toRef, reactive, computed, watch, onMounted } from 'vue';
 import { createComponent } from '@/utils/create';
 const { create } = createComponent('toast');
 export default create({
   props: {
     id: String,
     msg: String,
-    visible: {
-      type: Boolean,
-      default: false
-    },
     duration: {
       type: Number,
       default: 2000
@@ -89,31 +84,31 @@ export default create({
     'nut-icon': Icon
   },
   setup(props) {
-    console.log('props', props);
+    let timer;
     const state = reactive({
-      timer: null
+      mounted: false
+    });
+    onMounted(() => {
+      state.mounted = true;
     });
     const clearTimer = () => {
-      if (state.timer) {
-        clearTimeout(state.timer);
-        state.timer = null;
+      if (timer) {
+        clearTimeout(timer);
+        timer = null;
       }
     };
     const hide = () => {
-      clearTimer();
-      props.unmount(props.id);
-      props.onClose && props.onClose();
+      state.mounted = false;
     };
     const show = () => {
       clearTimer();
       if (props.duration) {
-        state.timer = setTimeout(() => {
+        timer = setTimeout(() => {
           hide();
         }, props.duration);
       }
     };
     const clickCover = () => {
-      console.log('click');
       if (props.closeOnClickOverlay) {
         hide();
       }
@@ -123,8 +118,16 @@ export default create({
       show();
     }
 
+    watch(
+      () => props.duration,
+      val => {
+        if (val) {
+          show();
+        }
+      }
+    );
+
     const hasIcon = computed(() => {
-      console.log(props.type);
       if (props.type !== 'text') {
         return true;
       } else {
@@ -142,11 +145,20 @@ export default create({
         'nut-toast-' + props.size
       ];
     });
+
+    const onAfterLeave = () => {
+      clearTimer();
+      props.unmount(props.id);
+      props.onClose && props.onClose();
+    };
+
     return {
       state,
+      hide,
       clickCover,
       hasIcon,
-      toastBodyClass
+      toastBodyClass,
+      onAfterLeave
     };
   }
 });

+ 51 - 19
src/packages/toast/toast.ts

@@ -1,10 +1,9 @@
-import { createVNode, render, App } from 'vue';
+import { createVNode, defineComponent, render, App } from 'vue';
 import VueToast from './index.vue';
-
+const ToastConstructor = defineComponent(VueToast);
 const defaultOptions = {
   msg: '',
   id: '',
-  visible: false,
   duration: 2000, //显示时间(毫秒)
   center: true,
   type: 'text',
@@ -22,33 +21,66 @@ const defaultOptions = {
   closeOnClickOverlay: false
 };
 
-let currentOptions = {
-  ...defaultOptions
+let idsMap: string[] = [];
+let optsMap: any[] = [];
+const clearToast = (id?: string) => {
+  if (id) {
+    const container = document.getElementById(id);
+    optsMap = optsMap.filter(item => item.id !== id);
+    idsMap = idsMap.filter(item => item !== id);
+    if (container) {
+      document.body.removeChild(container);
+    }
+  } else {
+    idsMap.forEach(item => {
+      const container = document.getElementById(item);
+      if (container) {
+        document.body.removeChild(container);
+      }
+    });
+    optsMap = [];
+    idsMap = [];
+  }
 };
 
-const clearToast = (id = currentOptions.id) => {
-  const container = document.getElementById(id);
+const updateToast = opts => {
+  const container = document.getElementById(opts.id);
   if (container) {
-    document.body.removeChild(container);
+    const currentOpt = optsMap.find(item => item.id === opts.id);
+    if (currentOpt) {
+      opts = { ...defaultOptions, ...currentOpt, ...opts };
+    } else {
+      opts = { ...defaultOptions, ...opts };
+    }
+    const instance: any = createVNode(ToastConstructor, opts);
+    render(instance, container);
+    return instance.component.ctx;
   }
 };
 
 const mountToast = opts => {
-  // checkExitToast();
-  opts = { ...defaultOptions, ...opts };
-  opts.visible = true;
   opts.unmount = clearToast;
-  opts.id = new Date().getTime() + '';
-  console.log(opts);
-  currentOptions = { ...opts };
-
+  let _id;
+  // 如果是更新已有的toast
+  if (opts.id) {
+    _id = opts.id;
+    if (idsMap.find(item => item === opts.id)) {
+      return updateToast(opts);
+    }
+  } else {
+    _id = new Date().getTime() + '';
+  }
+  opts = { ...defaultOptions, ...opts };
+  opts.id = _id;
+  idsMap.push(opts.id);
+  optsMap.push(opts);
   const container = document.createElement('div');
   container.id = opts.id;
-  const vm = createVNode(VueToast, opts);
-  render(vm, container);
+  const instance: any = createVNode(ToastConstructor, opts);
+  render(instance, container);
   document.body.appendChild(container);
-  console.log(vm);
-  return vm;
+  console.log(instance.component);
+  return instance.component.ctx;
 };
 
 const errorMsg = msg => {

+ 7 - 2
src/sites/doc/router.ts

@@ -3,7 +3,7 @@ import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
 import Index from './views/Index.vue';
 import Resource from './views/Resource.vue';
 import Main from './views/Main.vue';
-
+import { HttpClient } from '../service/HttpClient';
 const pagesRouter: Array<RouteRecordRaw> = [];
 const files = require.context('@/packages', true, /doc\.md$/);
 files.keys().forEach(component => {
@@ -57,5 +57,10 @@ const router = createRouter({
     }
   }
 });
-
+router.afterEach((to, from) => {
+  new HttpClient().request('/user/saveVisitInfo', 'post', {
+    headers: '',
+    componentName: to.path.split('/')[1]
+  });
+});
 export default router;

+ 7 - 0
src/sites/service/ArticleApiService.ts

@@ -13,4 +13,11 @@ export class ArticleApiService {
   getArticle() {
     return this.httpClient.request('/article/list', 'get', {});
   }
+  /**
+   * 保存用户访问数据
+   * @returns
+   */
+  saveUserInfo(parmas) {
+    return this.httpClient.request('/user/saveVisitInfo', 'post', parmas);
+  }
 }

+ 1 - 1
vue.config.js

@@ -1,6 +1,6 @@
 // vue.config.js
 const path = require('path');
-
+//target: 'http://localhost:7004',
 module.exports = {
   productionSourceMap: process.env.NODE_ENV != 'production',
   publicPath: './',