<template> <view class="mask" :class="!show?'':'mask-show'" :style="{backgroundColor:show?maskBg:'rgba(0,0,0,0)'}" @tap="tapMask"> <view class="popups" :class="[theme]" :style="{top: popupsTop ,left: popupsLeft,flexDirection:direction}"> <text :class="dynPlace" :style="{width:'0px',height:'0px'}" v-if="triangle"></text> <view v-for="(item,index) in popData" :key="index" @tap.stop="tapItem(item)" class="itemChild view" :class="[direction=='row'?'solid-right':'solid-bottom',item.disabled?'disabledColor':'']"> <image class="image" :src="item.icon" v-if="item.icon"></image>{{item.title}} </view> <slot></slot> </view> </view> </template> <script> export default{ emits: ["update:modelValue", "input", "tapPopup"], props:{ maskBg:{ type:String, default:'rgba(0,0,0,0)' }, placement:{ type:String, default:'default' //default top-start top-end bottom-start bottom-end }, direction:{ type:String, default:'column' //column row }, x:{ type:Number, default:0 }, y:{ type:Number, default:0 }, value:{ type:Boolean, default:false }, modelValue:{ type:Boolean, default:false }, popData:{ type:Array, default:()=>[] }, theme:{ type:String, default:'light' //light dark }, dynamic:{ type:Boolean, default:false }, gap:{ type:Number, default:20 }, triangle:{ type:Boolean, default:true } }, data(){ return{ popupsTop:'0px', popupsLeft:'0px', show:false, dynPlace:'' } }, mounted() { this.popupsPosition() }, methods:{ tapMask(){ this.$emit('input',!this.value) this.$emit('update:modelValue',!this.value) }, tapItem(item){ if(item.disabled) return this.$emit('tapPopup',item) this.$emit('input',!this.value) this.$emit('update:modelValue',!this.value) }, getStatusBar(){ let promise = new Promise((resolve,reject)=>{ uni.getSystemInfo({ success: function(e) { let customBar // #ifdef H5 customBar = e.statusBarHeight + e.windowTop; // #endif resolve(customBar) } }) }) return promise }, async popupsPosition(){ let statusBar = await this.getStatusBar() let promise = new Promise((resolve,reject)=>{ let popupsDom = uni.createSelectorQuery().in(this).select(".popups") popupsDom.fields({ size: true, }, (data) => { let width = data.width let height = data.height let y = this.dynamic?this.dynamicGetY(this.y,this.gap):this.transformRpx(this.y) let x = this.dynamic?this.dynamicGetX(this.x,this.gap):this.transformRpx(this.x) // #ifdef H5 y = this.dynamic?(this.y+statusBar): this.transformRpx(this.y+statusBar) // #endif this.dynPlace = this.placement=='default'?this.getPlacement(x,y):this.placement switch(this.dynPlace){ case 'top-start': this.popupsTop = `${y+9}px` this.popupsLeft = `${x-15}px` break; case 'top-end': this.popupsTop = `${y+9}px` this.popupsLeft = `${x+15-width}px` break; case 'bottom-start': this.popupsTop = `${y-18-height}px` this.popupsLeft = `${x-15}px` break; case 'bottom-end': this.popupsTop = `${y-9-height}px` this.popupsLeft = `${x+15-width}px` break; } resolve() }).exec(); }) return promise }, getPlacement(x,y){ let width = uni.getSystemInfoSync().windowWidth let height = uni.getSystemInfoSync().windowHeight if(x>width/2&&y>height/2){ return 'bottom-end' }else if(x<width/2&&y<height/2){ return 'top-start' }else if(x>width/2&&y<height/2){ return 'top-end' }else if(x<width/2&&y>height/2){ return 'bottom-start' }else if(x>width/2){ return 'top-end' }else{ return 'top-start' } }, dynamicGetY(y,gap){ let height = uni.getSystemInfoSync().windowHeight y = y<gap?gap:y y = height - y <gap? (height - gap) : y return y }, dynamicGetX(x,gap){ let width = uni.getSystemInfoSync().windowWidth x = x< gap?gap:x x = width - x <gap? (width - gap) : x return x }, transformRpx(params){ return params*uni.getSystemInfoSync().screenWidth/375 } }, watch:{ value:{ immediate:true, handler:async function (newVal,oldVal){ if(newVal) await this.popupsPosition() this.show = newVal } }, placement:{ immediate:true, handler(newVal,oldVal){ this.dynPlace = newVal } } } } </script> <style lang="scss" scoped> .mask{ position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 99999; transition: background 0.3s ease-in-out; visibility: hidden; &.mask-show{ visibility: visible; } } .popups{ position: absolute; padding: 20rpx; border-radius: 5px; display:flex; .view{ padding: 10rpx; } .image{ display: inline-block; vertical-align: middle; width: 40rpx; height: 40rpx; margin-right: 20rpx; } } .dark{ background-color: #4C4C4C; color: #fff; .top-start:after { content: ""; position: absolute; top: -18rpx; left: 10rpx; border-width: 0 20rpx 20rpx; border-style: solid; border-color: transparent transparent #4C4C4C; } .top-end:after { content: ""; position: absolute; top: -18rpx; right: 10rpx; border-width: 0 20rpx 20rpx; border-style: solid; border-color: transparent transparent #4C4C4C; } .bottom-start:after { content: ""; position: absolute; bottom: -18rpx; left: 10rpx; border-width: 20rpx 20rpx 0 ; border-style: solid; border-color: #4C4C4C transparent transparent ; } .bottom-end:after { content: ""; position: absolute; bottom: -18rpx; right: 10rpx; border-width: 20rpx 20rpx 0 ; border-style: solid; border-color: #4C4C4C transparent transparent ; } .disabledColor{ color: #c5c8ce; } } .light{ color: #515a6e; box-shadow: 0upx 0upx 30upx rgba(0,0,0,0.2); background: #fff; .top-start:after { content: ""; position: absolute; top: -18rpx; left: 10rpx; border-width: 0 20rpx 20rpx; border-style: solid; border-color: transparent transparent #fff; } .top-end:after { content: ""; position: absolute; top: -18rpx; right: 10rpx; border-width: 0 20rpx 20rpx; border-style: solid; border-color: transparent transparent #fff; } .bottom-start:after { content: ""; position: absolute; bottom: -18rpx; left: 10rpx; border-width: 20rpx 20rpx 0 ; border-style: solid; border-color: #fff transparent transparent ; } .bottom-end:after { content: ""; position: absolute; bottom: -18rpx; right: 10rpx; border-width: 20rpx 20rpx 0 ; border-style: solid; border-color: #fff transparent transparent ; } .disabledColor{ color: #c5c8ce; } } .solid-bottom{ border-bottom: 1px solid #ccc; } .solid-right{ border-right: 1px solid #ccc; } .popups .itemChild:last-child{ border: none; } </style>