浏览代码

feat: 重构 Popover,去除第三方依赖 (#1873)

* fix: 修复 ImagePreview 在Taro编译成H5后报错的问题

* fix: 地址关闭时, Close 事件触发两次问题解决

* feat: 组件DatePicker 添加双向绑定

* docs: 组件Picker文档修改

* feat: 组件Picker与DatePicker新增属性safe-area-inset-bottom

* feat: imagepreview

* fix: 组件imagepreview点击视频遮罩关闭(#1729)

* fix: 解决 Picker 在微信小程序中无法使用问题 (#1774)

* fix: 修改 Picker 组件 v-model 失效问题

* fix: 组件NoticeBar修改height之后,垂直轮播会卡顿

* fix: 删除Datepicker Demo演示多余内容

* fix: 组件Picker在JD小程序上适配

* fix: 组件Address京东小程序适配

* feat: 京东小程序适配

* fix: 删除空格

* feat: 删除console

* fix: 京东小程序imagepreview适配

* fix: 修复 imagepreview 动态设置 initNo 显示不正确问题

* fix: 组件 InfiniteLoading 某些情况下会错误触发下拉刷新#1819

* fix: 删除pullrefresh

* feat: 组件 imagepreview瘦身

* feat: 组件Picker 瘦身

* feat: popover

* feat: 新增

* feat: 组件 Popover 重构

* feat: 重构 popover

* fix: 删除多余文件

* fix: address线上问题修改

* fix: 完善imagepreview

* feat: 公共函数提取

* feat: 函数式改用 createComponent

* feat: 组件冲突解决

* feat: 更换实现方式
yangxiaolu1993 3 年之前
父节点
当前提交
c0bc148fa5

+ 0 - 1
package.json

@@ -83,7 +83,6 @@
   "devDependencies": {
     "@commitlint/cli": "^10.0.0",
     "@commitlint/config-conventional": "^10.0.0",
-    "@popperjs/core": "^2.11.5",
     "@tarojs/taro": "3.5.6",
     "@types/jest": "^26.0.22",
     "@types/node": "^17.0.16",

+ 80 - 66
src/packages/__VUE/popover/demo.vue

@@ -4,19 +4,14 @@
 
     <nut-row type="flex">
       <nut-col :span="8">
-        <nut-popover
-          v-model:visible="visible.lightTheme"
-          :list="iconItemList"
-          location="bottom-start"
-          @choose="chooseItem"
-        >
+        <nut-popover v-model:visible="lightTheme" :list="iconItemList" location="bottom-start" @choose="chooseItem">
           <template #reference>
             <nut-button type="primary" shape="square">{{ translate('light') }}</nut-button>
           </template>
         </nut-popover>
       </nut-col>
       <nut-col :span="8">
-        <nut-popover v-model:visible="visible.darkTheme" theme="dark" :list="iconItemList">
+        <nut-popover v-model:visible="darkTheme" theme="dark" :list="iconItemList">
           <template #reference>
             <nut-button type="primary" shape="square">{{ translate('dark') }}</nut-button>
           </template>
@@ -28,14 +23,14 @@
 
     <nut-row type="flex">
       <nut-col :span="8">
-        <nut-popover v-model:visible="visible.showIcon" theme="dark" :list="itemList">
+        <nut-popover v-model:visible="showIcon" theme="dark" :list="itemList">
           <template #reference>
             <nut-button type="primary" shape="square">{{ translate('showIcon') }}</nut-button>
           </template>
         </nut-popover>
       </nut-col>
       <nut-col :span="8">
-        <nut-popover v-model:visible="visible.disableAction" :list="itemListDisabled">
+        <nut-popover v-model:visible="disableAction" :list="itemListDisabled" location="bottom-end">
           <template #reference>
             <nut-button type="primary" shape="square">{{ translate('disableAction') }}</nut-button>
           </template>
@@ -44,7 +39,7 @@
     </nut-row>
 
     <h2>{{ translate('title2') }}</h2>
-    <nut-popover v-model:visible="visible.Customized" location="bottom-start">
+    <nut-popover v-model:visible="Customized" location="top-start" custom-class="customClass">
       <template #reference>
         <nut-button type="primary" shape="square">{{ translate('content') }}</nut-button>
       </template>
@@ -61,29 +56,22 @@
 
     <h2>{{ translate('title3') }}</h2>
 
-    <nut-row type="flex" justify="center">
-      <nut-col :span="24" style="text-align: center">
-        <nut-popover
-          v-model:visible="visible.customPositon"
-          :location="curPostion"
-          theme="dark"
-          :list="positionList"
-          customClass="brickBox"
-        >
-          <template #reference>
-            <div class="brick"></div>
-          </template>
-        </nut-popover>
-      </nut-col>
-    </nut-row>
-
-    <nut-radiogroup v-model="curPostion" direction="horizontal" class="radiogroup">
-      <nut-radio shape="button" :label="pos" v-for="(pos, i) in position" :key="i">{{ pos }}</nut-radio>
-    </nut-radiogroup>
+    <nut-cell title="点击查看更多方向" @click="handlePicker"></nut-cell>
+    <nut-picker v-model:visible="showPicker" :columns="columns" title="" @change="change" :swipe-duration="500">
+      <template #top>
+        <div class="brickBox">
+          <nut-popover v-model:visible="customPositon" :location="curPostion" theme="dark" :list="positionList">
+            <template #reference>
+              <div class="brick"></div>
+            </template>
+          </nut-popover>
+        </div>
+      </template>
+    </nut-picker>
   </div>
 </template>
 <script lang="ts">
-import { reactive, ref } from 'vue';
+import { reactive, ref, toRefs } from 'vue';
 import { createComponent } from '@/packages/utils/create';
 const { createDemo, translate } = createComponent('popover');
 import { useTranslate } from '@/sites/assets/util/useTranslate';
@@ -116,7 +104,7 @@ const initTranslate = () =>
 export default createDemo({
   setup() {
     initTranslate();
-    const visible = ref({
+    const state = reactive({
       showIcon: false,
       placement: false,
       darkTheme: false,
@@ -126,22 +114,25 @@ export default createDemo({
       topLocation: false, //向上弹出
       rightLocation: false, //向右弹出
       leftLocation: false, //向左弹出
-      customPositon: false
+      customPositon: false,
+
+      showPicker: false
     });
     const curPostion = ref('top');
-    const position = ref([
-      'top',
-      'top-start',
-      'top-end',
-      'right',
-      'right-start',
-      'right-end',
-      'bottom',
-      'bottom-start',
-      'bottom-end',
-      'left',
-      'left-start',
-      'left-end'
+
+    const columns = ref([
+      { text: 'top', value: 'top' },
+      { text: 'top-start', value: 'top-start' },
+      { text: 'top-end', value: 'top-end' },
+      { text: 'right', value: 'right' },
+      { text: 'right-start', value: 'right-start' },
+      { text: 'right-end', value: 'right-end' },
+      { text: 'bottom', value: 'bottom' },
+      { text: 'bottom-start', value: 'bottom-start' },
+      { text: 'bottom-end', value: 'bottom-end' },
+      { text: 'left', value: 'left' },
+      { text: 'left-start', value: 'left-start' },
+      { text: 'left-end', value: 'left-end' }
     ]);
 
     const iconItemList = reactive([
@@ -223,20 +214,32 @@ export default createDemo({
 
     const chooseItem = (item: unknown, index: number) => {
       console.log(item, index);
-      alert('selected');
     };
 
+    const handlePicker = () => {
+      state.showPicker = true;
+      setTimeout(() => {
+        state.customPositon = true;
+      });
+    };
+
+    const change = ({ selectedValue }) => {
+      curPostion.value = selectedValue[0];
+      state.customPositon = true;
+    };
     return {
       iconItemList,
       itemList,
-      visible,
+      ...toRefs(state),
       itemListDisabled,
       selfContent,
       chooseItem,
-      position,
       curPostion,
       positionList,
-      translate
+      translate,
+      columns,
+      change,
+      handlePicker
     };
   }
 });
@@ -247,10 +250,12 @@ export default createDemo({
 }
 .brickBox {
   margin: 80px 0;
+  display: flex;
+  justify-content: center;
   .brick {
     width: 60px;
     height: 60px;
-    background: #1989fa;
+    background: linear-gradient(135deg, #fa2c19 0%, #fa6419 100%);
     border-radius: 10px;
   }
 }
@@ -271,23 +276,32 @@ export default createDemo({
   }
 }
 
-.self-content {
-  width: 195px;
-  display: flex;
-  flex-wrap: wrap;
-  &-item {
-    margin-top: 10px;
-    margin-bottom: 10px;
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    flex-direction: column;
+.nut-popover-content {
+  width: 120px;
+}
+
+.customClass {
+  .nut-popover-content {
+    width: auto;
   }
-  &-desc {
-    margin-top: 5px;
-    width: 60px;
-    font-size: 10px;
-    text-align: center;
+  .self-content {
+    width: 195px;
+    display: flex;
+    flex-wrap: wrap;
+    &-item {
+      margin-top: 10px;
+      margin-bottom: 10px;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      flex-direction: column;
+    }
+    &-desc {
+      margin-top: 5px;
+      width: 60px;
+      font-size: 10px;
+      text-align: center;
+    }
   }
 }
 </style>

+ 8 - 0
src/packages/__VUE/popover/doc.en-US.md

@@ -296,6 +296,14 @@ export default {
 | offset `v3.1.21`       | the offset of the occurrence position  | [number, number]   | [0, 12]  |
 | show-arrow `v3.1.21`       | whether to show small arrows  | boolean  | true  |
 | custom-class `v3.1.21`       | custom class   | string  | ''  |
+| duration `v3.1.21`       | Transition duration  |  [number, number]  | 0.3  |
+| iconPrefix `v3.1.21`       | Icon className prefix | string  | 'nut-icon''  |
+| overlay `v3.2.8`       | Whether to show overlay  | Boolean  | false  |
+| overlay-class `v3.2.8`       | Custom overlay class | string  | ''  |
+| overlay-style `v3.2.8`       | Custom overlay style  | string  | ''  |
+| close-on-click-overlay `v3.2.8`       | Whether to close when clicking overlay  | boolean  | true  |
+| close-on-click-action `v3.2.8`       | Whether to close when clicking action  | boolean  | true |
+| close-on-click-outside `v3.2.8`       | Whether to close when clicking outside | boolean  | true  |
 
 ### List data structure  
 

+ 8 - 0
src/packages/__VUE/popover/doc.md

@@ -295,6 +295,14 @@ export default {
 | offset `v3.1.21`       | 出现位置的偏移量  | [number, number]   | [0, 12]  |
 | show-arrow `v3.1.21`       | 是否显示小箭头  | boolean  | true  |
 | custom-class `v3.1.21`       | 自定义 class 值  | string  | ''  |
+| duration `v3.1.21`       | 动画时长  |  [number, number]  | 0.3  |
+| iconPrefix `v3.1.21`       | 图标自定义类值,等同于 Icon 组件的[ class-prefix 属性](https://nutui.jd.com/#/zh-CN/component/icon)  | string  | 'nut-icon''  |
+| overlay `v3.2.8`       | 是否显示遮罩层  | Boolean  | false  |
+| overlay-class `v3.2.8`       | 自定义遮罩层类名 | string  | ''  |
+| overlay-style `v3.2.8`       | 自定义遮罩层样式  | string  | ''  |
+| close-on-click-overlay `v3.2.8`       | 是否在点击遮罩层后关闭菜单  | boolean  | true  |
+| close-on-click-action `v3.2.8`       | 是否在点击选项后关闭  | boolean  | true |
+| close-on-click-outside `v3.2.8`       | 是否在点击外部元素后关闭菜单 | boolean  | true  |
 
 ### List 数据结构  
 

+ 174 - 364
src/packages/__VUE/popover/index.scss

@@ -1,455 +1,265 @@
 .nut-popover {
-  overflow-y: inherit;
-  transform: inherit;
-
-  // 遮罩
-  .more-background {
-    opacity: 0;
-    position: fixed;
-    width: 100%;
-    height: 100%;
-    z-index: 10;
-    left: 0;
-    top: 0;
+  position: relative;
+  display: inline-block;
+  word-break: normal;
+
+  > .nut-popover-wrapper {
+    display: inline-block;
+    vertical-align: top;
   }
 
-  .popover-arrow {
+  &-arrow {
     position: absolute;
     width: 0;
     height: 0;
     border: 8px solid transparent;
-  }
-  // top
-  &[data-popper-placement^='top'] {
-    .popover-arrow {
+
+    &-top {
       bottom: 0;
       border-top-color: $popover-white-background-color;
       border-bottom-width: 0;
       margin-bottom: -8px;
     }
-  }
 
-  &[data-popper-placement='top'] {
-    .popover-arrow {
-      left: 50%;
-      transform: translateX(-50%);
-    }
-  }
+    &-bottom {
+      top: 0px;
+      border-bottom-color: $popover-white-background-color;
+      border-top-width: 0;
+      margin-top: -8px;
 
-  &[data-popper-placement='top-start'] {
-    .popover-arrow {
-      left: 16px;
-      transform: translateX(0%);
-    }
-  }
+      &.nut-popover-arrow--bottom {
+        left: 50%;
+        transform: translateX(-50%);
+      }
+
+      &.nut-popover-arrow--bottom-start {
+        left: 16px;
+        transform: translateX(0%);
+      }
 
-  &[data-popper-placement='top-end'] {
-    .popover-arrow {
-      right: 16px;
-      transform: translateX(0%);
+      &.nut-popover-arrow--bottom-end {
+        right: 16px;
+        transform: translateX(0%);
+      }
     }
-  }
 
-  // left
-  &[data-popper-placement^='left'] {
-    .popover-arrow {
+    &-left {
       right: 0px;
       border-left-color: $popover-white-background-color;
       border-right-width: 0;
       margin-right: -8px;
-    }
-  }
 
-  &[data-popper-placement='left'] {
-    .popover-arrow {
-      top: 50%;
-      transform: translateY(-50%);
-    }
-  }
+      &.nut-popover-arrow--left {
+        top: 50%;
+        transform: translateY(-50%);
+      }
 
-  &[data-popper-placement='left-start'] {
-    .popover-arrow {
-      top: 16px;
-      transform: translateY(0%);
-    }
-  }
+      &.nut-popover-arrow--left-start {
+        top: 16px;
+        transform: translateY(0%);
+      }
 
-  &[data-popper-placement='left-end'] {
-    .popover-arrow {
-      bottom: 16px;
-      transform: translateY(0%);
+      &.nut-popover-arrow--left-end {
+        bottom: 16px;
+        transform: translateY(0%);
+      }
     }
-  }
 
-  // right
-
-  &[data-popper-placement^='right'] {
-    .popover-arrow {
+    &-right {
       left: 0px;
       border-right-color: $popover-white-background-color;
       border-left-width: 0;
       margin-left: -8px;
-    }
-  }
-
-  &[data-popper-placement='right'] {
-    .popover-arrow {
-      top: 50%;
-      transform: translateY(-50%);
-    }
-  }
-
-  &[data-popper-placement='right-start'] {
-    .popover-arrow {
-      top: 16px;
-      transform: translateY(0%);
-    }
-  }
-
-  &[data-popper-placement='right-end'] {
-    .popover-arrow {
-      bottom: 16px;
-      transform: translateY(0%);
-    }
-  }
-  // bottom
-  &[data-popper-placement^='bottom'] {
-    .popover-arrow {
-      top: 0px;
-      border-bottom-color: $popover-white-background-color;
-      border-top-width: 0;
-      margin-top: -8px;
-    }
-  }
 
-  &[data-popper-placement='bottom'] {
-    .popover-arrow {
-      left: 50%;
-      transform: translateX(-50%);
-    }
-  }
+      &.nut-popover-arrow--right {
+        top: 50%;
+        transform: translateY(-50%);
+      }
 
-  &[data-popper-placement='bottom-start'] {
-    .popover-arrow {
-      left: 16px;
-      transform: translateX(0%);
-    }
-  }
+      &.nut-popover-arrow--right-start {
+        top: 16px;
+        transform: translateY(0%);
+      }
 
-  &[data-popper-placement='bottom-end'] {
-    .popover-arrow {
-      right: 16px;
-      transform: translateX(0%);
+      &.nut-popover-arrow--right-end {
+        bottom: 16px;
+        transform: translateY(0%);
+      }
     }
   }
 
-  .popover-menu {
-    display: block;
-    background: $popover-white-background-color;
+  &-content {
+    position: absolute;
+    z-index: 9999;
+    background: #fff;
     border-radius: 5px;
     font-size: 14px;
     font-family: PingFangSC;
     font-weight: normal;
-    color: $popover-primary-text-color;
-    box-shadow: 0 2px 12px rgb(50 50 51 / 12%);
-    .popover-menu-item {
-      display: flex;
-      align-items: center;
+    color: #333;
+    box-shadow: 0 2px 12px #3232331f;
+    opacity: 1;
+    transition: opacity 0.15s, transform 0.15s;
+    max-height: initial;
+    overflow-y: initial;
+
+    &-group {
+      display: block;
+      // position: relative;
+      height: 100%;
+      width: 100%;
+    }
+
+    .nut-popover-menu-item {
+      display: block;
       padding-bottom: 8px;
-      padding: $popover-menu-item-padding;
-      margin: $popover-menu-item-margin;
+      margin: 8px;
       border-bottom: 1px solid $popover-border-bottom-color;
 
+      &:first-child {
+        margin-top: 15px;
+      }
+
       &:last-child {
+        margin-bottom: 2px;
         border-bottom: none;
       }
-      .popover-menu-name {
-        display: block;
+
+      .nut-popover-menu-item-name {
         margin-right: 12px;
         margin-left: 8px;
-        line-height: $popover-menu-name-line-height;
+        width: 100%;
       }
-    }
-  }
-
-  .disabled {
-    color: $popover-disable-color;
-    cursor: not-allowed;
-  }
-}
-
-.nut-popover--dark {
-  color: $popover-white-background-color;
-
-  &[data-popper-placement^='top'] {
-    .popover-arrow {
-      border-top-color: $popover-dark-background-color;
-    }
-  }
 
-  &[data-popper-placement^='left'] {
-    .popover-arrow {
-      border-left-color: $popover-dark-background-color;
+      &.nut-popover-menu-disabled {
+        color: $popover-disable-color;
+        cursor: not-allowed;
+      }
     }
-  }
 
-  &[data-popper-placement^='right'] {
-    .popover-arrow {
-      border-right-color: $popover-dark-background-color;
-    }
-  }
+    &--top {
+      left: 50%;
+      transform: translateX(-50%);
 
-  &[data-popper-placement^='bottom'] {
-    .popover-arrow {
-      border-bottom-color: $popover-dark-background-color;
+      .nut-popover-arrow--top {
+        left: 50%;
+        transform: translateX(-50%);
+      }
     }
-  }
 
-  .popover-menu {
-    background: $popover-dark-background-color;
-    color: $popover-white-background-color;
-  }
-}
-
-// Taro
-.nut-popover-taro {
-  position: relative;
-  display: inline-block;
-  .more-background {
-    opacity: 0;
-    position: fixed;
-    width: 100%;
-    height: 100%;
-    z-index: 10;
-    left: 0;
-    top: 0;
-  }
+    &--top-end {
+      .nut-popover-arrow--top-end {
+        right: 16px;
+        transform: translateX(0%);
+      }
 
-  .popover-content {
-    position: absolute;
-    z-index: 12;
-    min-width: 80px;
-
-    .popover-arrow {
-      position: absolute;
-      width: 0;
-      height: 0;
-      border: 8px solid transparent;
+      right: 0;
     }
 
-    .popover-menu {
-      box-shadow: 0 2px 12px rgb(50 50 51 / 12%);
-      border-radius: 5px;
-      font-size: 14px;
-      font-family: PingFangSC;
-      font-weight: normal;
-      color: $popover-primary-text-color;
-      background: $popover-white-background-color;
-      .popover-menu-item {
-        display: flex;
-        align-items: center;
-        padding: 8px 0;
-        margin: 0 8px;
-        border-bottom: 1px solid $popover-border-bottom-color;
-        &:last-child {
-          border-bottom: none;
-        }
-        .popover-menu-name {
-          margin-right: 12px;
-          margin-left: 8px;
-          width: 100%;
-        }
+    &--top-start {
+      left: 0;
+
+      .nut-popover-arrow--top-start {
+        left: 16px;
+        transform: translateX(0%);
       }
     }
-  }
-  // bottom
-  .popover-content--bottom {
-    left: 50%;
-    transform: translateX(-50%);
-    .popover-arrow {
-      top: 0px;
-      border-bottom-color: $popover-white-background-color;
-      border-top-width: 0;
-      margin-top: -8px;
+
+    // bottom
+    &--bottom {
       left: 50%;
       transform: translateX(-50%);
+      // transform-origin:50% 0 ;
     }
-  }
 
-  .popover-content--bottom-start {
-    .popover-arrow {
-      top: 0px;
-      border-bottom-color: $popover-white-background-color;
-      border-top-width: 0;
-      margin-top: -8px;
-      left: 16px;
-      transform: translateX(0%);
+    &--bottom-end {
+      right: 0;
     }
-  }
 
-  .popover-content--bottom-end {
-    right: 0;
-    .popover-arrow {
-      top: 0px;
-      border-bottom-color: $popover-white-background-color;
-      border-top-width: 0;
-      margin-top: -8px;
-      right: 16px;
-      transform: translateX(0%);
+    &--bottom-start {
+      left: 0;
+      // transform-origin:0 0;
     }
-  }
-
-  // top
-  .popover-content--top {
-    left: 50%;
-    transform: translateX(-50%);
-    .popover-arrow {
-      bottom: 0;
-      border-top-color: $popover-white-background-color;
-      border-bottom-width: 0;
-      margin-bottom: -8px;
 
-      left: 50%;
-      transform: translateX(-50%);
+    // left
+    &--left {
+      top: 50%;
+      transform: translateY(-50%);
     }
-  }
 
-  .popover-content--top-start {
-    .popover-arrow {
+    &--left-end {
       bottom: 0;
-      border-top-color: $popover-white-background-color;
-      border-bottom-width: 0;
-      margin-bottom: -8px;
-
-      left: 16px;
-      transform: translateX(0%);
     }
-  }
-
-  .popover-content--top-end {
-    right: 0;
-    .popover-arrow {
-      bottom: 0;
-      border-top-color: $popover-white-background-color;
-      border-bottom-width: 0;
-      margin-bottom: -8px;
 
-      right: 16px;
-      transform: translateX(0%);
-    }
-  }
-  // left
-  .popover-content--left {
-    top: 50%;
-    transform: translateY(-50%);
-    .popover-arrow {
-      right: 0px;
-      border-left-color: $popover-white-background-color;
-      border-right-width: 0;
-      margin-right: -8px;
-      top: 50%;
-      transform: translateY(-50%);
-    }
-  }
-  .popover-content--left-start {
-    top: 0%;
-    .popover-arrow {
-      right: 0px;
-      border-left-color: $popover-white-background-color;
-      border-right-width: 0;
-      margin-right: -8px;
-      top: 16px;
-      transform: translateX(0%);
+    &--left-start {
+      top: 0;
     }
-  }
 
-  .popover-content--left-end {
-    bottom: 0%;
-    .popover-arrow {
-      right: 0px;
-      border-left-color: $popover-white-background-color;
-      border-right-width: 0;
-      margin-right: -8px;
-      bottom: 16px;
-      transform: translateX(0%);
-    }
-  }
-  // right
-  .popover-content--right {
-    top: 50%;
-    transform: translateY(-50%);
-    .popover-arrow {
-      left: 0px;
-      border-right-color: $popover-white-background-color;
-      border-left-width: 0;
-      margin-left: -8px;
+    // right
+    &--right {
       top: 50%;
       transform: translateY(-50%);
     }
-  }
 
-  .popover-content--right-start {
-    top: 0%;
-    .popover-arrow {
-      left: 0px;
-      border-right-color: $popover-white-background-color;
-      border-left-width: 0;
-      margin-left: -8px;
-      top: 16px;
-      transform: translateY(0%);
+    &--right-end {
+      bottom: 0;
     }
-  }
 
-  .popover-content--right-end {
-    bottom: 0%;
-    .popover-arrow {
-      left: 0px;
-      border-right-color: $popover-white-background-color;
-      border-left-width: 0;
-      margin-left: -8px;
-      bottom: 16px;
-      transform: translateY(0%);
+    &--right-start {
+      top: 0;
     }
   }
-
-  .disabled {
-    color: $popover-disable-color;
-    cursor: not-allowed;
-  }
 }
 
-.nut-popover-taro--dark {
-  color: $popover-white-background-color;
-  .popover-content--bottom,
-  .popover-content--bottom-start,
-  .popover-content--bottom-end {
-    .popover-arrow {
-      border-bottom-color: $popover-dark-background-color;
+.nut-popover--dark {
+  .nut-popover-content {
+    background: $popover-dark-background-color !important;
+    color: $popover-white-background-color !important;
+
+    &--bottom,
+    &--bottom-start,
+    &--bottom-end {
+      .nut-popover-arrow {
+        border-bottom-color: $popover-dark-background-color;
+      }
     }
-  }
-  .popover-content--top,
-  .popover-content--top-start,
-  .popover-content--top-end {
-    .popover-arrow {
-      border-top-color: $popover-dark-background-color;
+
+    &--top,
+    &--top-start,
+    &--top-end {
+      .nut-popover-arrow {
+        border-top-color: $popover-dark-background-color;
+      }
     }
-  }
-  .popover-content--left,
-  .popover-content--left-start,
-  .popover-content--left-end {
-    .popover-arrow {
-      border-left-color: $popover-dark-background-color;
+
+    &--left,
+    &--left-start,
+    &--left-end {
+      .nut-popover-arrow {
+        border-left-color: $popover-dark-background-color;
+      }
     }
-  }
-  .popover-content--right,
-  .popover-content--right-start,
-  .popover-content--right-end {
-    .popover-arrow {
-      border-right-color: $popover-dark-background-color;
+
+    &--right,
+    &--right-start,
+    &--right-end {
+      .nut-popover-arrow {
+        border-right-color: $popover-dark-background-color;
+      }
     }
   }
-  .popover-menu {
-    background: $popover-dark-background-color !important;
-    color: $popover-white-background-color !important;
-  }
+}
+
+.nut-popover-enter-from,
+.nut-popover-leave-active {
+  // transform: scale(0.8);
+  opacity: 0;
+}
+
+.nut-popover-enter-active {
+  transition-timing-function: ease-out;
+}
+
+.nut-popover-leave-active {
+  transition-timing-function: ease-in;
 }

+ 110 - 164
src/packages/__VUE/popover/index.vue

@@ -1,214 +1,160 @@
 <template>
-  <!-- 气泡弹出层  按钮 -->
-  <view style="display: inline-block" :class="customClass" @click.stop="openPopover" ref="reference">
-    <slot name="reference"></slot
-  ></view>
-
-  <nut-popup
-    ref="popoverRef"
-    :pop-class="classes"
-    v-model:visible="showPopup"
-    :overlay="false"
-    @clickOverlay="clickOverlay"
-  >
-    <!-- 气泡弹出层  箭头 -->
-    <view :class="popoverArrow" v-if="showArrow"> </view>
-    <!-- 气泡弹出层  内容 -->
-    <slot name="content"></slot>
-    <view class="popover-menu" :class="popoverContent" ref="popoverMenu">
-      <view
-        v-for="(item, index) in list"
-        :key="index"
-        :class="[item.className, { 'popover-menu-item': true, disabled: item.disabled }]"
-        @click.stop="chooseItem(item, index)"
-      >
-        <slot v-if="item.icon"> <nut-icon v-bind="$attrs" class="item-img" :name="item.icon"></nut-icon></slot>
-        <view class="popover-menu-name">{{ item.name }}</view>
+  <view :class="['nut-popover', `nut-popover--${theme}`, `${customClass}`]">
+    <view class="nut-popover-wrapper" @click="openPopover" ref="popoverRef"><slot name="reference"></slot></view>
+
+    <nut-popup
+      :popClass="`nut-popover-content nut-popover-content--${location}`"
+      :style="getStyles"
+      v-model:visible="showPopup"
+      position=""
+      transition="nut-popover"
+      :overlay="overlay"
+      :duration="duration"
+      :overlayStyle="overlayStyle"
+      :overlayClass="overlayClass"
+      :closeOnClickOverlay="closeOnClickOverlay"
+    >
+      <view ref="popoverContentRef" class="nut-popover-content-group">
+        <view :class="popoverArrow" v-if="showArrow"> </view>
+        <slot name="content"></slot>
+        <view
+          v-for="(item, index) in list"
+          :key="index"
+          :class="[item.className, item.disabled && 'nut-popover-menu-disabled', 'nut-popover-menu-item']"
+          @click.stop="chooseItem(item, index)"
+        >
+          <slot v-if="item.icon">
+            <nut-icon v-bind="$attrs" class="item-img" :classPrefix="iconPrefix" :name="item.icon"></nut-icon
+          ></slot>
+          <view class="nut-popover-menu-name">{{ item.name }}</view>
+        </view>
       </view>
-    </view>
-  </nut-popup>
+    </nut-popup>
+  </view>
 </template>
 <script lang="ts">
-import { onMounted, computed, watch, ref, PropType, toRefs, nextTick, onUnmounted } from 'vue';
+import { computed, watch, ref, PropType, CSSProperties, reactive } from 'vue';
 import { createComponent } from '@/packages/utils/create';
-const { componentName, create } = createComponent('popover');
-import Popup from '../popup/index.vue';
-import { popupProps } from '../popup/props';
-import Button from '../button/index.vue';
-import { createPopper } from '@popperjs/core/lib/popper-lite';
-import offsetModifier from '@popperjs/core/lib/modifiers/offset';
-import type { Instance, Placement } from '@popperjs/core';
-
+const { create } = createComponent('popover');
 export default create({
-  inheritAttrs: false,
-  components: {
-    [Popup.name]: Popup,
-    [Button.name]: Button
-  },
+  components: {},
   props: {
-    ...popupProps,
-    list: {
-      type: Array,
-      default: []
-    },
-
-    theme: {
-      type: String as PropType<import('./type').PopoverTheme>,
-      default: 'light'
-    },
-
-    location: {
-      type: String as PropType<import('./type').PopoverLocation>,
-      default: 'bottom'
-    },
-    // 位置出现的偏移量
-    offset: {
-      type: Array,
-      default: [0, 12]
-    },
-    customClass: {
-      type: String,
-      default: ''
-    },
-    showArrow: {
-      type: Boolean,
-      default: true
-    }
+    visible: { type: Boolean, default: false },
+    list: { type: Array, default: [] },
+    theme: { type: String as PropType<import('./type').PopoverTheme>, default: 'light' },
+    location: { type: String as PropType<import('./type').PopoverLocation>, default: 'bottom' },
+    offset: { type: Array, default: [0, 12] },
+    customClass: { type: String, default: '' },
+    showArrow: { type: Boolean, default: true },
+    iconPrefix: { type: String, default: 'nut-icon' },
+    duration: { type: [Number, String], default: 0.3 },
+    overlay: { type: Boolean, default: false },
+    overlayClass: { type: String, default: '' },
+    overlayStyle: { type: Object as PropType<CSSProperties> },
+    closeOnClickOverlay: { type: Boolean, default: true },
+    closeOnClickAction: { type: Boolean, default: true },
+    closeOnClickOutside: { type: Boolean, default: true }
   },
   emits: ['update', 'update:visible', 'close', 'choose', 'open'],
   setup(props, { emit }) {
-    let popper: Instance | null;
-    const reference = ref();
     const popoverRef = ref();
-
+    const popoverContentRef = ref();
     const showPopup = ref(props.visible);
-
-    const { theme, location } = toRefs(props);
-
-    const classes = computed(() => {
-      const prefixCls = componentName;
-
-      return `${prefixCls} ${prefixCls}--${theme.value}`;
-    });
-
-    const popoverContent = computed(() => {
-      const prefixCls = 'popover-content';
-      return {
-        [prefixCls]: true,
-        [`${prefixCls}--${location.value}`]: location.value
-      };
+    const state = reactive({
+      rootWidth: 0,
+      rootHeight: 0
     });
 
     const popoverArrow = computed(() => {
-      const prefixCls = 'popover-arrow';
-      return {
-        [prefixCls]: true,
-        [`${prefixCls}--${location.value}`]: location.value
-      };
+      const prefixCls = 'nut-popover-arrow';
+      const loca = props.location;
+      const direction = loca.split('-')[0];
+      return `${prefixCls} ${prefixCls}-${direction} ${prefixCls}--${loca}`;
     });
-
-    const createPopperInstance = () => {
-      if (reference.value && popoverRef.value) {
-        return createPopper(reference.value, popoverRef.value.popupRef, {
-          placement: props.location,
-          modifiers: [
-            {
-              name: 'computeStyles',
-              options: {
-                adaptive: false,
-                gpuAcceleration: false
-              }
-            },
-            Object.assign({}, offsetModifier, {
-              options: {
-                offset: props.offset
-              }
-            })
-          ]
-        });
+    const getStyles = computed(() => {
+      let cross = +state.rootHeight;
+      let lengthways = +state.rootWidth;
+      let { offset, location } = props;
+      if (Array.isArray(offset) && offset.length == 2) {
+        cross += +offset[1];
+        lengthways += +offset[1];
       }
-      return null;
-    };
-
-    const clickOverlay = () => {
-      // console.log('关闭');
-    };
-
-    const uploadLocation = () => {
-      nextTick(() => {
-        if (!showPopup.value) return;
-        if (!popper) {
-          popper = createPopperInstance();
-        } else {
-          popper.setOptions({
-            placement: props.location
-          });
-        }
-      });
-    };
-
-    const clickAway = (event: Event) => {
-      const element = reference.value;
-      if (element && !element.contains(event.target)) {
-        closePopover();
+      const direction = location.split('-')[0];
+      const style: CSSProperties = {};
+      const mapd: any = {
+        top: 'bottom',
+        bottom: 'top',
+        left: 'right',
+        right: 'left'
+      };
+      if (['top', 'bottom'].includes(direction)) {
+        style[mapd[direction]] = `${cross}px`;
+        style.marginLeft = `${offset[0]}px`;
+      } else {
+        style[mapd[direction]] = `${lengthways}px`;
+        style.marginTop = `${offset[0]}px`;
       }
-    };
-    onMounted(() => {
-      window.addEventListener('click', clickAway, true);
-    });
-
-    onUnmounted(() => {
-      window.removeEventListener('click', clickAway, true);
+      return style;
     });
-
+    // 获取宽度
+    const getContentWidth = () => {
+      const { offsetHeight, offsetWidth } = popoverRef.value;
+      state.rootHeight = offsetHeight;
+      state.rootWidth = offsetWidth;
+    };
     watch(
       () => props.visible,
       (value) => {
         showPopup.value = value;
-        uploadLocation();
-      }
-    );
-
-    watch(
-      () => props.location,
-      (value) => {
-        uploadLocation();
+        if (value) {
+          window.addEventListener('touchstart', clickAway, true);
+          getContentWidth();
+        } else {
+          window.removeEventListener('touchstart', clickAway, true);
+        }
       }
     );
-
     const update = (val: boolean) => {
       emit('update', val);
       emit('update:visible', val);
     };
-
     const openPopover = () => {
       update(!props.visible);
       emit('open');
     };
-
     const closePopover = () => {
-      emit('close');
       emit('update:visible', false);
+      emit('close');
     };
-
     const chooseItem = (item: any, index: number) => {
-      if (item.disabled) {
-        return;
-      }
       emit('choose', item, index);
+      if (props.closeOnClickAction) {
+        closePopover();
+      }
+    };
+    const clickAway = (event: Event) => {
+      const element = popoverRef.value;
+      const elContent = popoverContentRef.value;
+      if (
+        element &&
+        !element.contains(event.target) &&
+        elContent &&
+        !elContent.contains(event.target) &&
+        props.closeOnClickOutside
+      ) {
+        closePopover();
+      }
     };
 
     return {
-      classes,
       showPopup,
       openPopover,
-      popoverContent,
       popoverArrow,
       closePopover,
       chooseItem,
-      reference,
       popoverRef,
-      clickOverlay
+      getStyles,
+      popoverContentRef
     };
   }
 });

+ 1 - 1
src/packages/__VUE/popup/common.ts

@@ -1,4 +1,4 @@
-import { computed, ComputedRef, watchEffect, reactive, toRefs } from 'vue';
+import { computed, ComputedRef, watchEffect, reactive, toRefs, ref } from 'vue';
 
 import { popupProps } from './props';
 

+ 9 - 0
src/packages/styles/variables.scss

@@ -542,6 +542,15 @@ $badge-dot-border-radius: 7px !default;
 $badge-dot-padding: 0px !default;
 
 //popover
+// $popover-white-background-color: rgba(255, 255, 255, 1) !default;
+// $popover-dark-background-color: rgba(75, 76, 77, 1) !default;
+// $popover-border-bottom-color: rgba(229, 229, 229, 1) !default;
+// $popover-primary-text-color: rgba(51, 51, 51, 1) !default;
+// $popover-disable-color: rgba(154, 155, 157, 1) !default;
+// $popover-menu-item-padding: 8px 0 !default;
+// $popover-menu-item-margin: 0 8px !default;
+// $popover-menu-name-line-height: normal !default;
+
 $popover-white-background-color: rgba(255, 255, 255, 1) !default;
 $popover-dark-background-color: rgba(75, 76, 77, 1) !default;
 $popover-border-bottom-color: rgba(229, 229, 229, 1) !default;