| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- <template>
- <view class="zbPopover" :style="{
- '--theme-bg-color': bgStyleColor
- }">
- <view class="mask" @click.stop="close" v-if="show" :style="{ backgroundColor: overlayBg }"></view>
- <view @click.stop="handleClick" class="zb-button-popover">
- <slot></slot>
- </view>
- <view class="zb-popover" v-show="inited" ref="zb-transition" :class="[classes, `zb-popover-${placement}`]"
- :style="[mergeStyle, { ...popoverStyle }]" @touchmove="noop">
- <view class="zb-popover-arrow" :style="[arrowStyle]" :class="[{
- 'zb_popper__up': placement.indexOf('bottom') === 0,
- 'zb_popper__arrow': placement.indexOf('top') === 0,
- 'zb_popper__right': placement.indexOf('right') === 0,
- 'zb_popper__left': placement.indexOf('left') === 0,
- }]" v-if="isArrow">
- </view>
- <slot name="content">
- <view :class="[{
- 'horizontal__action': actionsDirection === 'horizontal'
- }]">
- <view @click.stop="actionAction(item)" v-for="item, index in options" class="zb-popover__action" :class="[{
- 'dark__action': theme === 'dark'
- }]" :key="index">
- <slot name="item" :item="item">
- <i class="icon-font" :class="item.icon" :style="{ fontSize: iconSize }"></i>
- </slot>
- <view class="zb-popover__action-text">
- <trans class="text" :_t="keyName ? item[keyName] : item" />
- </view>
- </view>
- </view>
- </slot>
- </view>
- </view>
- </template>
- <script setup>
- import { ref, onMounted, computed, nextTick, watch } from "vue"
- const tranClass = {
- enter: "zb-fade-zoom-enter zb-fade-zoom-enter-active",
- 'enter-to': "zb-fade-zoom-enter-to zb-fade-zoom-enter-active",
- leave: "zb-fade-zoom-leave zb-fade-zoom-leave-active",
- 'leave-to': "zb-fade-zoom-leave-to zb-fade-zoom-leave-active",
- }
- const props = defineProps({
- options: {
- type: Array,
- default: () => []
- },
- placement: {
- type: String,
- default: 'bottom-start'
- },
- bgColor: {
- type: String,
- },
- theme: {
- type: String,
- default: 'light' // light dark
- },
- actionsDirection: {
- type: String,
- default: 'vertical' // horizontal vertical
- },
- keyName: {
- type: String,
- default: ''
- },
- overlayBg: {
- type: String,
- default: 'transparent'
- },
- iconSize: {
- type: String,
- default: '30px'
- },
- isArrow: Boolean
- })
- const emit = defineEmits(['handleClick', 'select'])
- const show = ref(false);
- const inited = ref(false);
- const classes = ref('');
- const duration = ref(100);
- const popoverStyle = ref({});
- const arrowOldStyle = ref({});
- const bgStyleColor = computed(() => {
- if (props.bgColor) {
- return props.bgColor
- }
- if (props.theme === 'light') {
- return 'white'
- }
- if (props.theme === 'dark') {
- return '#4a4a4a'
- }
- })
- const mergeStyle = computed(() => {
- return {
- transitionDuration: `${duration.value}ms`,
- transitionTimingFunction: `ease-out`,
- ...popoverStyle.value
- }
- })
- const arrowStyle = computed(() => {
- return { ...arrowOldStyle.value }
- })
- onMounted(() => {
- // // #ifdef H5
- // window.addEventListener('click', () => {
- // show.value = false
- // })
- // // #endif
- })
- const handleClick = () => {
- if (show.value) {
- show.value = false
- } else {
- show.value = true
- }
- emit('handleClick', show.value)
- }
- const close = () => {
- show.value = false
- }
- const actionAction = (item) => {
- emit('select', item)
- show.value = false
- }
- const sleep = (value) => {
- return new Promise((resolve) => {
- setTimeout(() => {
- resolve()
- }, value)
- })
- }
- const vueEnter = () => {
- inited.value = true
- getPosition()
- classes.value = tranClass.enter
- nextTick(async () => {
- await sleep(30)
- classes.value = tranClass['enter-to']
- })
- }
- const vueLeave = () => {
- classes.value = tranClass.leave
- nextTick(async () => {
- classes.value = tranClass['leave-to']
- await sleep(120)
- inited.value = false
- })
- }
- const preventEvent = (e) => {
- e && typeof (e.stopPropagation) === 'function' && e.stopPropagation()
- }
- const getPosition = () => {
- return new Promise((resolve) => {
- nextTick(() => {
- let selectorQuery = uni.createSelectorQuery().in(this).selectAll('.zb-button-popover,.zb-popover')
- let popoverStyles = {}
- let arrowOldStyles = {}
- selectorQuery.boundingClientRect(async (data) => {
- let { left, bottom, right, top, width, height } = data[0]
- let popoverClientRect = data[1]
- switch (props.placement) {
- case 'top':
- if (popoverClientRect.width > width) {
- popoverStyles.left = `-${(popoverClientRect.width - width) / 2}px`
- } else {
- popoverStyles.left = `${Math.abs(popoverClientRect.width - width) / 2}px`
- }
- popoverStyles.bottom = `${height + 8}px`
- arrowOldStyles.left = (popoverClientRect.width / 2 - 6) + 'px'
- break;
- case 'top-start':
- popoverStyles.left = `0px`
- popoverStyles.bottom = `${height + 8}px`
- arrowOldStyles.left = '16px'
- break;
- case 'top-end':
- popoverStyles.right = `0px`
- popoverStyles.bottom = `${height + 8}px`
- arrowOldStyles.right = '16px'
- break;
- case 'bottom':
- if (popoverClientRect.width > width) {
- popoverStyles.left = `-${(popoverClientRect.width - width) / 2}px`
- } else {
- popoverStyles.left = `${Math.abs(popoverClientRect.width - width) / 2}px`
- }
- popoverStyles.top = `${height + 8}px`
- arrowOldStyles.left = (popoverClientRect.width / 2 - 6) + 'px'
- break;
- case 'bottom-start':
- popoverStyles.top = `${height + 8}px`
- popoverStyles.left = `0px`
- arrowOldStyles.left = '16px'
- break;
- case 'bottom-end':
- popoverStyles.top = `${height + 8}px`
- popoverStyles.right = `0px`
- arrowOldStyles.right = '16px'
- break;
- case 'right':
- popoverStyles.left = `${width + 8}px`
- if (popoverClientRect.height > height) {
- popoverStyles.top = `-${(popoverClientRect.height - height) / 2}px`
- } else {
- popoverStyles.top = `${Math.abs((popoverClientRect.height - height) / 2)}px`
- }
- arrowOldStyles.top = `${popoverClientRect.height / 2 - 6}px`
- break;
- case 'right-start':
- popoverStyles.left = `${width + 8}px`
- popoverStyles.top = `0px`
- arrowOldStyles.top = `8px`
- break;
- case 'right-end':
- popoverStyles.left = `${width + 8}px`
- popoverStyles.bottom = `0px`
- arrowOldStyles.bottom = `8px`
- break;
- case 'left':
- popoverStyles.right = `${width + 8}px`
- if (popoverClientRect.height > height) {
- popoverStyles.top = `-${(popoverClientRect.height - height) / 2}px`
- } else {
- popoverStyles.top = `${Math.abs((popoverClientRect.height - height) / 2)}px`
- }
- arrowOldStyles.top = `${popoverClientRect.height / 2 - 6}px`
- break;
- case 'left-start':
- popoverStyles.right = `${width + 8}px`
- popoverStyles.top = `0px`
- arrowOldStyles.top = `8px`
- break;
- case 'left-end':
- popoverStyles.right = `${width + 8}px`
- popoverStyles.bottom = `0px`
- arrowOldStyles.bottom = `8px`
- break;
- }
- popoverStyle.value = popoverStyles;
- arrowOldStyle.value = arrowOldStyles
- resolve()
- }).exec()
- })
- })
- }
- const noop = (e) => {
- preventEvent(e)
- }
- watch(show, (newVal) => {
- newVal ? vueEnter() : vueLeave()
- }, { immediate: true })
- </script>
- <style lang="scss" scoped>
- $theme-bg-color: var(--theme-bg-color);
- .mask {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 2044;
- }
- .zbPopover {
- position: relative;
- }
- .zb-button-popover {
- display: flex;
- align-items: center;
- }
- .zb-popover {
- border-radius: 8px;
- z-index: 2144;
- position: absolute;
- background-color: $theme-bg-color;
- box-shadow: 0 2px 12px #3232331f;
- overflow: hidden;
- }
- .zb-popover-top {
- transform-origin: 50% bottom;
- }
- .zb-popover-top-start {
- transform-origin: 50% bottom;
- }
- .zb-popover-top-end {
- transform-origin: 0 bottom;
- }
- .zb-popover-bottom {
- transform-origin: 50% 0;
- }
- .zb-popover-bottom-end {
- transform-origin: 100% 0;
- }
- .zb-popover-bottom-start {
- transform-origin: 0 0;
- }
- .zb-popover-right {
- transform-origin: left 50%;
- }
- .zb-popover-right-start {
- transform-origin: left 0;
- }
- .zb-popover-right-end {
- transform-origin: left 100%;
- }
- .zb-popover-left {
- transform-origin: right 50%;
- }
- .zb-popover-left-start {
- transform-origin: right 0;
- }
- .zb-popover-left-end {
- transform-origin: right 100%;
- }
- .zb-popover-arrow {
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
- border-width: 6px;
- color: $theme-bg-color;
- }
- .zb_popper__up {
- border-top-width: 0;
- border-bottom-color: currentColor;
- top: -5px;
- }
- .zb_popper__right {
- border-left-width: 0;
- border-right-color: currentColor;
- left: -5px;
- }
- .zb_popper__left {
- border-right-width: 0;
- border-left-color: currentColor;
- right: -5px;
- }
- .zb_popper__arrow {
- border-bottom-width: 0;
- border-top-color: currentColor;
- bottom: -6px;
- }
- .zb-popover__action {
- position: relative;
- display: flex;
- align-items: center;
- box-sizing: border-box;
- height: 88rpx;
- padding: 0 24rpx;
- font-size: 28rpx;
- cursor: pointer;
- }
- .zb-popover__action-text {
- display: flex;
- flex: 1;
- align-items: center;
- height: 100%;
- padding: 0 24rpx;
- // border-bottom: 1rpx solid #ebedf0;
- word-wrap: break-word;
- white-space: nowrap;
- .text{
- display: inline-block;
- word-wrap: break-word;
- white-space: nowrap;
- color: #62708c;
- }
- }
- .zb-popover__action:last-child {
- .zb-popover__action-text {
- border-bottom: none;
- }
- }
- .dark__action {
- color: white;
- .zb-popover__action-text {
- // border-bottom: 1rpx solid #ebedf033
- }
- }
- .horizontal__action {
- display: flex;
- .zb-popover__action {
- padding: 0 20rpx;
- border-right: 1rpx solid #ebedf0;
- }
- .zb-popover__action-text {
- padding: 0;
- //border-right:1rpx solid #ebedf0;
- }
- }
- $u-zoom-scale: scale(0.95);
- .zb-fade-enter-active,
- .zb-fade-leave-active {
- transition-property: opacity;
- }
- .zb-fade-enter,
- .zb-fade-leave-to {
- opacity: 0
- }
- .zb-fade-zoom-enter,
- .zb-fade-zoom-leave-to {
- transform: $u-zoom-scale;
- opacity: 0;
- }
- .zb-fade-zoom-enter-active,
- .zb-fade-zoom-leave-active {
- transition-property: transform, opacity;
- }
- .zb-fade-down-enter-active,
- .zb-fade-down-leave-active,
- .zb-fade-left-enter-active,
- .zb-fade-left-leave-active,
- .zb-fade-right-enter-active,
- .zb-fade-right-leave-active,
- .zb-fade-up-enter-active,
- .zb-fade-up-leave-active {
- transition-property: opacity, transform;
- }
- .zb-fade-up-enter,
- .zb-fade-up-leave-to {
- transform: translate3d(0, 100%, 0);
- opacity: 0
- }
- .zb-fade-down-enter,
- .zb-fade-down-leave-to {
- transform: translate3d(0, -100%, 0);
- opacity: 0
- }
- .zb-fade-left-enter,
- .zb-fade-left-leave-to {
- transform: translate3d(-100%, 0, 0);
- opacity: 0
- }
- .zb-fade-right-enter,
- .zb-fade-right-leave-to {
- transform: translate3d(100%, 0, 0);
- opacity: 0
- }
- .zb-popover__action {
- .icon-font {
- color: #62708c;
- }
- &:active {
- background-color: rgba(0, 0, 0, 0.2);
- }
- &--disabled {
- color: var(--van-popover-dark-action-disabled-text-color);
- &:active {
- background-color: transparent;
- }
- }
- }
- </style>
|