1
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
export default {
|
||||
props: {
|
||||
// 是否显示弹出层
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: ()=> uni.$u.props.popover.show
|
||||
},
|
||||
// 弹出层内容
|
||||
content: {
|
||||
type: String,
|
||||
default: ()=> uni.$u.props.popover.content
|
||||
},
|
||||
// 弹出方向:top, bottom, left, right, auto
|
||||
position: {
|
||||
type: String,
|
||||
default: ()=> uni.$u.props.popover.position
|
||||
},
|
||||
// 是否显示箭头
|
||||
showArrow: {
|
||||
type: Boolean,
|
||||
default: ()=> uni.$u.props.popover.showArrow
|
||||
},
|
||||
// 箭头大小
|
||||
arrowSize: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.arrowSize
|
||||
},
|
||||
// 箭头颜色
|
||||
arrowColor: {
|
||||
type: String,
|
||||
default: ()=> uni.$u.props.popover.arrowColor
|
||||
},
|
||||
// 弹出层背景色
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: ()=> uni.$u.props.popover.bgColor
|
||||
},
|
||||
// 文字颜色
|
||||
color: {
|
||||
type: String,
|
||||
default: ()=> uni.$u.props.popover.color
|
||||
},
|
||||
// 字体大小
|
||||
fontSize: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.fontSize
|
||||
},
|
||||
// 内边距
|
||||
padding: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.padding
|
||||
},
|
||||
// 圆角
|
||||
round: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.round
|
||||
},
|
||||
// 弹出层宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.width
|
||||
},
|
||||
// 弹出层最大宽度
|
||||
maxWidth: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.maxWidth
|
||||
},
|
||||
// 弹出层最小宽度
|
||||
minWidth: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.minWidth
|
||||
},
|
||||
// 层级
|
||||
zIndex: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.zIndex
|
||||
},
|
||||
// 动画时长
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: ()=> uni.$u.props.popover.duration
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: ()=> uni.$u.props.popover.disabled
|
||||
},
|
||||
// 自定义弹出层样式
|
||||
popoverStyle: {
|
||||
type: Object,
|
||||
default: ()=> uni.$u.props.popover.popoverStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
<template>
|
||||
<view class="u-popover" :style="[$u.addStyle(customStyle)]">
|
||||
<view class="u-popover__trigger" id="popover-trigger" ref="popoverTrigger" @click.stop="clickHandler">
|
||||
<slot/>
|
||||
</view>
|
||||
<u-transition
|
||||
mode="fade"
|
||||
:duration="duration"
|
||||
:show="showPopover"
|
||||
:custom-style="transitionStyle"
|
||||
>
|
||||
<view class="u-popover__popup" :style="{
|
||||
minWidth:$u.addUnit(minWidth),
|
||||
maxWidth:$u.addUnit(maxWidth),
|
||||
width:popoverWidth,
|
||||
...popoverStyle
|
||||
}">
|
||||
<view v-if="showArrow" class="u-popover__popup__indicator"
|
||||
:class="[`u-popover__popup__indicator__${actualPosition}`]"
|
||||
:style="{
|
||||
backgroundColor: (arrowColor || bgColor),
|
||||
width: $u.addUnit(arrowSize),
|
||||
height: $u.addUnit(arrowSize),
|
||||
}"
|
||||
></view>
|
||||
<view
|
||||
class="u-popover__popup__content"
|
||||
:class="[`u-popover__popup__content__${actualPosition}`]"
|
||||
:style="{
|
||||
backgroundColor: bgColor,
|
||||
padding: $u.addUnit(padding),
|
||||
borderRadius: $u.addUnit(round)
|
||||
}"
|
||||
>
|
||||
<slot name="content">
|
||||
<text :style="{
|
||||
color: color,
|
||||
fontSize: $u.addUnit(fontSize)
|
||||
}">{{ content }}</text>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
</u-transition>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import props from './props.js';
|
||||
import mixin from '../../libs/mixin/mixin'
|
||||
import mpMixin from '../../libs/mixin/mpMixin';
|
||||
|
||||
/**
|
||||
* Popover
|
||||
* @description
|
||||
* @tutorial https://uview.d3u.cn/components/popover.html
|
||||
* @property {Boolean} show 是否显示(默认 false )
|
||||
* @property {String} position 弹出方向:top, bottom, left, right, auto
|
||||
* @property {Boolean} showArrow 是否显示箭头(默认 true )
|
||||
* @property {String | Number} arrowSize 箭头大小(默认 12px )
|
||||
* @property {String} arrowColor 箭头颜色(默认 '#000' )
|
||||
* @property {String} bgColor 弹出层背景色(默认 '#060607' )
|
||||
* @property {String} color 文字颜色(默认 '#fff' )
|
||||
* @property {String | Number} fontSize 字体大小(默认 14px )
|
||||
* @property {String | Number} padding 内边距(默认 8px 12px )
|
||||
* @property {String | Number} round 圆角(默认 4 )
|
||||
* @property {String | Number} width 弹出层宽度(默认 '' )
|
||||
* @property {String | Number} maxWidth 弹出层最大宽度(默认 200 )
|
||||
* @property {String | Number} minWidth 弹出层最小宽度(默认 50 )
|
||||
* @property {String | Number} zIndex 层级(默认 999 )
|
||||
* @property {Number} duration 动画时长(默认 300 )
|
||||
* @property {Boolean} disabled 是否禁用(默认 false )
|
||||
* @property {Object} popoverStyle 自定义弹出层样式
|
||||
|
||||
* @example
|
||||
* <u-popover position="top" :content="content">
|
||||
* <u-button type="primary">点击触发</u-button>
|
||||
* </u-popover>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-popover',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
showPopover: false,
|
||||
popoverWidth: '',
|
||||
autoPosition: '', // 自动计算的位置
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取实际使用的position值
|
||||
actualPosition() {
|
||||
return this.position === 'auto' ? this.autoPosition : this.position
|
||||
},
|
||||
// 计算气泡和指示器的位置信息
|
||||
transitionStyle() {
|
||||
let style = {
|
||||
position: 'absolute',
|
||||
zIndex: this.zIndex
|
||||
}
|
||||
|
||||
// 根据actualPosition设置位置
|
||||
switch(this.actualPosition) {
|
||||
case 'top-left':
|
||||
style.left = '0'
|
||||
style.transform = 'translate(0, -100%)'
|
||||
break
|
||||
case 'top':
|
||||
case 'top-center':
|
||||
style.left = '50%'
|
||||
style.transform = 'translate(-50%, -100%)'
|
||||
break
|
||||
case 'top-right':
|
||||
style.right = '0'
|
||||
style.transform = 'translate(0, -100%)'
|
||||
break
|
||||
case 'bottom-left':
|
||||
style.bottom = '0'
|
||||
style.left = '0'
|
||||
style.transform = 'translate(0, 100%)'
|
||||
break
|
||||
case 'bottom':
|
||||
case 'bottom-center':
|
||||
style.bottom = '0'
|
||||
style.left = '50%'
|
||||
style.transform = 'translate(-50%, 100%)'
|
||||
break
|
||||
case 'bottom-right':
|
||||
style.bottom = '0'
|
||||
style.right = '0'
|
||||
style.transform = 'translate(0, 100%)'
|
||||
break
|
||||
case 'left-top':
|
||||
style.transform = 'translate(-100%, 0)'
|
||||
break
|
||||
case 'left':
|
||||
case 'left-center':
|
||||
style.top = '50%'
|
||||
style.transform = 'translate(-100%, -50%)'
|
||||
break
|
||||
case 'left-bottom':
|
||||
style.bottom = '0'
|
||||
style.transform = 'translate(-100%, 0)'
|
||||
break
|
||||
case 'right-top':
|
||||
style.right = '0'
|
||||
style.transform = 'translate(100%, 0)'
|
||||
break
|
||||
case 'right':
|
||||
case 'right-center':
|
||||
style.right = '0'
|
||||
style.top = '50%'
|
||||
style.transform = 'translate(100%, -50%)'
|
||||
break
|
||||
case 'right-bottom':
|
||||
style.right = '0'
|
||||
style.bottom = '0'
|
||||
style.transform = 'translate(100%, 0)'
|
||||
break
|
||||
default:
|
||||
// 默认为top
|
||||
style.left = '50%'
|
||||
style.transform = 'translate(-50%, -100%)'
|
||||
break
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
// #ifdef VUE3
|
||||
emits: ["click"],
|
||||
// #endif
|
||||
methods: {
|
||||
init() {
|
||||
this.addClickOutsideListener()
|
||||
},
|
||||
// 元素尺寸
|
||||
getElRect() {
|
||||
const windowInfo = uni.$u.window();
|
||||
if(this.width) {
|
||||
this.popoverWidth = uni.$u.addUnit(this.width)
|
||||
} else {
|
||||
this.$uGetRect('#popover-trigger').then(size => {
|
||||
// 确保popover宽度不超出屏幕范围
|
||||
let targetWidth = size.width
|
||||
if(this.actualPosition.startsWith('left')) {
|
||||
targetWidth = size.left
|
||||
} else if(this.actualPosition.startsWith('right')) {
|
||||
targetWidth = windowInfo.windowWidth - size.right
|
||||
}
|
||||
|
||||
targetWidth -= 10;
|
||||
|
||||
// 如果position为auto,自动计算最佳位置
|
||||
if(this.position == 'auto') {
|
||||
this.autoPosition = this.calculateBestPosition(targetWidth,size, windowInfo)
|
||||
}
|
||||
|
||||
this.popoverWidth = uni.$u.addUnit(targetWidth)
|
||||
})
|
||||
}
|
||||
},
|
||||
// 计算最佳显示位置
|
||||
calculateBestPosition(popoverWidth, triggerRect, windowInfo) {
|
||||
const popoverHeight = 80 // 预估popover高度
|
||||
const margin = 10 // 安全边距
|
||||
|
||||
// 计算各个方向的可用空间
|
||||
const spaceTop = triggerRect.top
|
||||
const spaceBottom = windowInfo.windowHeight - triggerRect.bottom
|
||||
const spaceLeft = triggerRect.left
|
||||
const spaceRight = windowInfo.windowWidth - triggerRect.right
|
||||
|
||||
// 优先级:top > bottom > right > left
|
||||
if (spaceTop >= popoverHeight + margin) {
|
||||
// 上方有足够空间
|
||||
if (triggerRect.left + popoverWidth <= windowInfo.windowWidth - margin) {
|
||||
return 'top-left'
|
||||
} else if (triggerRect.right - popoverWidth >= margin) {
|
||||
return 'top-right'
|
||||
} else {
|
||||
return 'top'
|
||||
}
|
||||
} else if (spaceBottom >= popoverHeight + margin) {
|
||||
// 下方有足够空间
|
||||
if (triggerRect.left + popoverWidth <= windowInfo.windowWidth - margin) {
|
||||
return 'bottom-left'
|
||||
} else if (triggerRect.right - popoverWidth >= margin) {
|
||||
return 'bottom-right'
|
||||
} else {
|
||||
return 'bottom'
|
||||
}
|
||||
} else if (spaceRight >= popoverWidth + margin) {
|
||||
// 右侧有足够空间
|
||||
if (triggerRect.top + popoverHeight <= windowInfo.windowHeight - margin) {
|
||||
return 'right-top'
|
||||
} else if (triggerRect.bottom - popoverHeight >= margin) {
|
||||
return 'right-bottom'
|
||||
} else {
|
||||
return 'right'
|
||||
}
|
||||
} else if (spaceLeft >= popoverWidth + margin) {
|
||||
// 左侧有足够空间
|
||||
if (triggerRect.top + popoverHeight <= windowInfo.windowHeight - margin) {
|
||||
return 'left-top'
|
||||
} else if (triggerRect.bottom - popoverHeight >= margin) {
|
||||
return 'left-bottom'
|
||||
} else {
|
||||
return 'left'
|
||||
}
|
||||
} else {
|
||||
// 所有方向空间都不够,选择空间最大的方向
|
||||
const maxSpace = Math.max(spaceTop, spaceBottom, spaceLeft, spaceRight)
|
||||
if (maxSpace === spaceTop) return 'top'
|
||||
if (maxSpace === spaceBottom) return 'bottom'
|
||||
if (maxSpace === spaceRight) return 'right'
|
||||
return 'left'
|
||||
}
|
||||
},
|
||||
clickHandler(e) {
|
||||
if(this.disabled) return
|
||||
this.getElRect()
|
||||
// #ifndef H5
|
||||
uni.$emit('u-popover-close')
|
||||
// #endif
|
||||
// 然后切换当前组件状态
|
||||
this.showPopover = !this.showPopover
|
||||
},
|
||||
// 添加全局点击监听
|
||||
addClickOutsideListener() {
|
||||
// #ifdef H5
|
||||
document.addEventListener('click', this.handleClickOutside, true)
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.$on('u-popover-close', () => {
|
||||
this.handleClickOutside()
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 移除全局点击监听
|
||||
removeClickOutsideListener() {
|
||||
// #ifdef H5
|
||||
document.removeEventListener('click', this.handleClickOutside, true)
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.$off('u-popover-close')
|
||||
// #endif
|
||||
},
|
||||
handleClickOutside(e) {
|
||||
if (!this.showPopover) return
|
||||
// #ifdef H5
|
||||
// 检查点击的目标是否在popover组件内部
|
||||
if (e && this.$el && this.$el.contains(e.target)) {
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
this.showPopover = false
|
||||
}
|
||||
},
|
||||
// #ifdef VUE2
|
||||
beforeDestroy() {
|
||||
this.removeClickOutsideListener()
|
||||
},
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE3
|
||||
beforeUnmount() {
|
||||
this.removeClickOutsideListener()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "../../libs/css/components.scss";
|
||||
|
||||
.u-popover {
|
||||
position: relative;
|
||||
@include flex;
|
||||
|
||||
&__popup {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
&__indicator {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
border-radius: 2px;
|
||||
transform: rotate(45deg);
|
||||
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.1);
|
||||
&__top,
|
||||
&__top-center {
|
||||
bottom: 5px;
|
||||
}
|
||||
|
||||
&__top-left {
|
||||
bottom: 5px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
&__top-right {
|
||||
bottom: 5px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
&__bottom-left {
|
||||
top: 5px;
|
||||
left: 15px;
|
||||
}
|
||||
|
||||
&__bottom,
|
||||
&__bottom-center {
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
&__bottom-right {
|
||||
top: 5px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
&__left-top {
|
||||
right: 5px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
&__left,
|
||||
&__left-center {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
&__left-bottom {
|
||||
right: 5px;
|
||||
bottom: 15px;
|
||||
}
|
||||
|
||||
&__right-top {
|
||||
left: 5px;
|
||||
top: 15px;
|
||||
}
|
||||
|
||||
&__right,
|
||||
&__right-center {
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
&__right-bottom {
|
||||
left: 5px;
|
||||
bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 6px 30px 5px rgba(0, 0, 0, .05), 0 10px 10px 2px rgba(0, 0, 0, .04), 0 10px 10px -5px rgba(0, 0, 0, .08);
|
||||
|
||||
&__top,
|
||||
&__top-left,
|
||||
&__top-center,
|
||||
&__top-right {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&__bottom,
|
||||
&__bottom-left,
|
||||
&__bottom-center,
|
||||
&__bottom-right {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&__left,
|
||||
&__left-top,
|
||||
&__left-center,
|
||||
&__left-bottom {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&__right,
|
||||
&__right-top,
|
||||
&__right-center,
|
||||
&__right-bottom {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user