gaoluyang
3 天以前 92230c9a97dc9ce9df3313d11d26999c04bb6b26
src/uni_modules/uni-popup/components/uni-popup/uni-popup.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,474 @@
<template>
   <view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
      <view @touchstart="touchstart">
         <uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
            :duration="duration" :show="showTrans" @click="onTap" />
         <uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
            :show="showTrans" @click="onTap">
            <view class="uni-popup__wrapper" :style="{ backgroundColor: bg }" :class="[popupstyle]" @click="clear">
               <slot />
            </view>
         </uni-transition>
      </view>
      <!-- #ifdef H5 -->
      <keypress v-if="maskShow" @esc="onTap" />
      <!-- #endif -->
   </view>
</template>
<script>
   // #ifdef H5
   import keypress from './keypress.js'
   // #endif
   /**
    * PopUp å¼¹å‡ºå±‚
    * @description å¼¹å‡ºå±‚组件,为了解决遮罩弹层的问题
    * @tutorial https://ext.dcloud.net.cn/plugin?id=329
    * @property {String} type = [top|center|bottom|left|right|message|dialog|share] å¼¹å‡ºæ–¹å¼
    *    @value top é¡¶éƒ¨å¼¹å‡º
    *    @value center ä¸­é—´å¼¹å‡º
    *    @value bottom åº•部弹出
    *    @value left      å·¦ä¾§å¼¹å‡º
    *    @value right  å³ä¾§å¼¹å‡º
    *    @value message æ¶ˆæ¯æç¤º
    *    @value dialog å¯¹è¯æ¡†
    *    @value share åº•部分享示例
    * @property {Boolean} animation = [true|false] æ˜¯å¦å¼€å¯åŠ¨ç”»
    * @property {Boolean} maskClick = [true|false] è’™ç‰ˆç‚¹å‡»æ˜¯å¦å…³é—­å¼¹çª—(废弃)
    * @property {Boolean} isMaskClick = [true|false] è’™ç‰ˆç‚¹å‡»æ˜¯å¦å…³é—­å¼¹çª—
    * @property {String}  backgroundColor ä¸»çª—口背景色
    * @property {String}  maskBackgroundColor è’™ç‰ˆé¢œè‰²
    * @property {Boolean} safeArea         æ˜¯å¦é€‚配底部安全区
    * @event {Function} change æ‰“开关闭弹窗触发,e={show: false}
    * @event {Function} maskClick ç‚¹å‡»é®ç½©è§¦å‘
    */
   export default {
      name: 'uniPopup',
      components: {
         // #ifdef H5
         keypress
         // #endif
      },
      emits: ['change', 'maskClick'],
      props: {
         // å¼€å¯åŠ¨ç”»
         animation: {
            type: Boolean,
            default: true
         },
         // å¼¹å‡ºå±‚类型,可选值,top: é¡¶éƒ¨å¼¹å‡ºå±‚ï¼›bottom:底部弹出层;center:全屏弹出层
         // message: æ¶ˆæ¯æç¤º ; dialog : å¯¹è¯æ¡†
         type: {
            type: String,
            default: 'center'
         },
         // maskClick
         isMaskClick: {
            type: Boolean,
            default: null
         },
         // TODO 2 ä¸ªç‰ˆæœ¬åŽåºŸå¼ƒå±žæ€§ ï¼Œä½¿ç”¨ isMaskClick
         maskClick: {
            type: Boolean,
            default: null
         },
         backgroundColor: {
            type: String,
            default: 'none'
         },
         safeArea: {
            type: Boolean,
            default: true
         },
         maskBackgroundColor: {
            type: String,
            default: 'rgba(0, 0, 0, 0.4)'
         },
      },
      watch: {
         /**
          * ç›‘听type类型
          */
         type: {
            handler: function(type) {
               if (!this.config[type]) return
               this[this.config[type]](true)
            },
            immediate: true
         },
         isDesktop: {
            handler: function(newVal) {
               if (!this.config[newVal]) return
               this[this.config[this.type]](true)
            },
            immediate: true
         },
         /**
          * ç›‘听遮罩是否可点击
          * @param {Object} val
          */
         maskClick: {
            handler: function(val) {
               this.mkclick = val
            },
            immediate: true
         },
         isMaskClick: {
            handler: function(val) {
               this.mkclick = val
            },
            immediate: true
         },
         // H5 ä¸‹ç¦æ­¢åº•部滚动
         showPopup(show) {
            // #ifdef H5
            // fix by mehaotian å¤„理 h5 æ»šåŠ¨ç©¿é€çš„é—®é¢˜
            document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
            // #endif
         }
      },
      data() {
         return {
            duration: 300,
            ani: [],
            showPopup: false,
            showTrans: false,
            popupWidth: 0,
            popupHeight: 0,
            config: {
               top: 'top',
               bottom: 'bottom',
               center: 'center',
               left: 'left',
               right: 'right',
               message: 'top',
               dialog: 'center',
               share: 'bottom'
            },
            maskClass: {
               position: 'fixed',
               bottom: 0,
               top: 0,
               left: 0,
               right: 0,
               backgroundColor: 'rgba(0, 0, 0, 0.4)'
            },
            transClass: {
               position: 'fixed',
               left: 0,
               right: 0
            },
            maskShow: true,
            mkclick: true,
            popupstyle: this.isDesktop ? 'fixforpc-top' : 'top'
         }
      },
      computed: {
         isDesktop() {
            return this.popupWidth >= 500 && this.popupHeight >= 500
         },
         bg() {
            if (this.backgroundColor === '' || this.backgroundColor === 'none') {
               return 'transparent'
            }
            return this.backgroundColor
         }
      },
      mounted() {
         const fixSize = () => {
            const {
               windowWidth,
               windowHeight,
               windowTop,
               safeArea,
               screenHeight,
               safeAreaInsets
            } = uni.getSystemInfoSync()
            this.popupWidth = windowWidth
            this.popupHeight = windowHeight + (windowTop || 0)
            // TODO fix by mehaotian æ˜¯å¦é€‚配底部安全区 ,目前微信ios ã€å’Œ app ios è®¡ç®—有差异,需要框架修复
            if (safeArea && this.safeArea) {
               // #ifdef MP-WEIXIN
               this.safeAreaInsets = screenHeight - safeArea.bottom
               // #endif
               // #ifndef MP-WEIXIN
               this.safeAreaInsets = safeAreaInsets.bottom
               // #endif
            } else {
               this.safeAreaInsets = 0
            }
         }
         fixSize()
         // #ifdef H5
         // window.addEventListener('resize', fixSize)
         // this.$once('hook:beforeDestroy', () => {
         //    window.removeEventListener('resize', fixSize)
         // })
         // #endif
      },
      // #ifndef VUE3
      // TODO vue2
      destroyed() {
         this.setH5Visible()
      },
      // #endif
      // #ifdef VUE3
      // TODO vue3
      unmounted() {
         this.setH5Visible()
      },
      // #endif
      created() {
         // this.mkclick =  this.isMaskClick || this.maskClick
         if (this.isMaskClick === null && this.maskClick === null) {
            this.mkclick = true
         } else {
            this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
         }
         if (this.animation) {
            this.duration = 300
         } else {
            this.duration = 0
         }
         // TODO å¤„理 message ç»„件生命周期异常的问题
         this.messageChild = null
         // TODO è§£å†³å¤´æ¡å†’泡的问题
         this.clearPropagation = false
         this.maskClass.backgroundColor = this.maskBackgroundColor
      },
      methods: {
         setH5Visible() {
            // #ifdef H5
            // fix by mehaotian å¤„理 h5 æ»šåŠ¨ç©¿é€çš„é—®é¢˜
            document.getElementsByTagName('body')[0].style.overflow = 'visible'
            // #endif
         },
         /**
          * å…¬ç”¨æ–¹æ³•,不显示遮罩层
          */
         closeMask() {
            this.maskShow = false
         },
         /**
          * å…¬ç”¨æ–¹æ³•,遮罩层禁止点击
          */
         disableMask() {
            this.mkclick = false
         },
         // TODO nvue å–消冒泡
         clear(e) {
            // #ifndef APP-NVUE
            e.stopPropagation()
            // #endif
            this.clearPropagation = true
         },
         open(direction) {
            // fix by mehaotian å¤„理快速打开关闭的情况
            if (this.showPopup) {
               clearTimeout(this.timer)
               this.showPopup = false
            }
            let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
            if (!(direction && innerType.indexOf(direction) !== -1)) {
               direction = this.type
            }
            if (!this.config[direction]) {
               console.error('缺少类型:', direction)
               return
            }
            this[this.config[direction]]()
            this.$emit('change', {
               show: true,
               type: direction
            })
         },
         close(type) {
            this.showTrans = false
            this.$emit('change', {
               show: false,
               type: this.type
            })
            clearTimeout(this.timer)
            // // è‡ªå®šä¹‰å…³é—­äº‹ä»¶
            // this.customOpen && this.customClose()
            this.timer = setTimeout(() => {
               this.showPopup = false
            }, 300)
         },
         // TODO å¤„理冒泡事件,头条的冒泡事件有问题 ï¼Œå…ˆè¿™æ ·å…¼å®¹
         touchstart() {
            this.clearPropagation = false
         },
         onTap() {
            if (this.clearPropagation) {
               // fix by mehaotian å…¼å®¹ nvue
               this.clearPropagation = false
               return
            }
            this.$emit('maskClick')
            if (!this.mkclick) return
            this.close()
         },
         /**
          * é¡¶éƒ¨å¼¹å‡ºæ ·å¼å¤„理
          */
         top(type) {
            this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
            this.ani = ['slide-top']
            this.transClass = {
               position: 'fixed',
               left: 0,
               right: 0,
               backgroundColor: this.bg
            }
            // TODO å…¼å®¹ type å±žæ€§ ï¼ŒåŽç»­ä¼šåºŸå¼ƒ
            if (type) return
            this.showPopup = true
            this.showTrans = true
            this.$nextTick(() => {
               if (this.messageChild && this.type === 'message') {
                  this.messageChild.timerClose()
               }
            })
         },
         /**
          * åº•部弹出样式处理
          */
         bottom(type) {
            this.popupstyle = 'bottom'
            this.ani = ['slide-bottom']
            this.transClass = {
               position: 'fixed',
               left: 0,
               right: 0,
               bottom: 0,
               paddingBottom: this.safeAreaInsets + 'px',
               backgroundColor: this.bg
            }
            // TODO å…¼å®¹ type å±žæ€§ ï¼ŒåŽç»­ä¼šåºŸå¼ƒ
            if (type) return
            this.showPopup = true
            this.showTrans = true
         },
         /**
          * ä¸­é—´å¼¹å‡ºæ ·å¼å¤„理
          */
         center(type) {
            this.popupstyle = 'center'
            this.ani = ['zoom-out', 'fade']
            this.transClass = {
               position: 'fixed',
               /* #ifndef APP-NVUE */
               display: 'flex',
               flexDirection: 'column',
               /* #endif */
               bottom: 0,
               left: 0,
               right: 0,
               top: 0,
               justifyContent: 'center',
               alignItems: 'center'
            }
            // TODO å…¼å®¹ type å±žæ€§ ï¼ŒåŽç»­ä¼šåºŸå¼ƒ
            if (type) return
            this.showPopup = true
            this.showTrans = true
         },
         left(type) {
            this.popupstyle = 'left'
            this.ani = ['slide-left']
            this.transClass = {
               position: 'fixed',
               left: 0,
               bottom: 0,
               top: 0,
               backgroundColor: this.bg,
               /* #ifndef APP-NVUE */
               display: 'flex',
               flexDirection: 'column'
               /* #endif */
            }
            // TODO å…¼å®¹ type å±žæ€§ ï¼ŒåŽç»­ä¼šåºŸå¼ƒ
            if (type) return
            this.showPopup = true
            this.showTrans = true
         },
         right(type) {
            this.popupstyle = 'right'
            this.ani = ['slide-right']
            this.transClass = {
               position: 'fixed',
               bottom: 0,
               right: 0,
               top: 0,
               backgroundColor: this.bg,
               /* #ifndef APP-NVUE */
               display: 'flex',
               flexDirection: 'column'
               /* #endif */
            }
            // TODO å…¼å®¹ type å±žæ€§ ï¼ŒåŽç»­ä¼šåºŸå¼ƒ
            if (type) return
            this.showPopup = true
            this.showTrans = true
         }
      }
   }
</script>
<style lang="scss">
   .uni-popup {
      position: fixed;
      /* #ifndef APP-NVUE */
      z-index: 99;
      /* #endif */
      &.top,
      &.left,
      &.right {
         /* #ifdef H5 */
         top: var(--window-top);
         /* #endif */
         /* #ifndef H5 */
         top: 0;
         /* #endif */
      }
      .uni-popup__wrapper {
         /* #ifndef APP-NVUE */
         display: block;
         /* #endif */
         position: relative;
         /* iphonex ç­‰å®‰å…¨åŒºè®¾ç½®ï¼Œåº•部安全区适配 */
         /* #ifndef APP-NVUE */
         // padding-bottom: constant(safe-area-inset-bottom);
         // padding-bottom: env(safe-area-inset-bottom);
         /* #endif */
         &.left,
         &.right {
            /* #ifdef H5 */
            padding-top: var(--window-top);
            /* #endif */
            /* #ifndef H5 */
            padding-top: 0;
            /* #endif */
            flex: 1;
         }
      }
   }
   .fixforpc-z-index {
      /* #ifndef APP-NVUE */
      z-index: 999;
      /* #endif */
   }
   .fixforpc-top {
      top: 0;
   }
</style>