chat-app/src/components/deep-bubble/deep-bubble.vue

349 lines
8.3 KiB
Vue

<template>
<div id="popover" ref="bubbleRef" class="ui-popover">
<view
:id="popoverBoxId"
class="popover-box"
v-passive-end="onTouchend"
v-passive-touch="onTouchstart"
>
<slot></slot>
</view>
<!-- mode -->
<view
@click.stop="close(50)"
:id="popoverContentId"
class="popover-content"
:style="data.showStyle"
>
<div class="menu">
<div
class="w-full h-[132rpx] text-[24rpx] text-[#FFFFFF] flex items-center justify-around"
>
<div
v-if="props.isShowCopy"
@click="() => itemClick('actionCopy')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
<div class="mt-1">复制</div>
</div>
<div
v-if="props.isShowConvertText"
@click="() => itemClick('convertText')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="copy07"></tm-image>
<div class="mt-1">转文字</div>
</div>
<div
@click="() => itemClick('multipleChoose')"
class="flex flex-col items-center justify-center"
>
<tm-image
:width="40"
:height="40"
:src="multipleChoices"
></tm-image>
<div class="mt-1">多选</div>
</div>
<div
v-if="props.isShowCite"
@click="() => itemClick('actionCite')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="cite"></tm-image>
<div class="mt-1">引用</div>
</div>
<div
v-if="props.isShowWithdraw"
@click="() => itemClick('actionWithdraw')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="withdraw"></tm-image>
<div class="mt-1">撤回</div>
</div>
<div
@click="() => itemClick('actionDelete')"
class="flex flex-col items-center justify-center"
>
<tm-image :width="40" :height="40" :src="delete07"></tm-image>
<div class="mt-1">删除</div>
</div>
</div>
<div :style="data.iconStyle" class="icon"></div>
</div>
</view>
<view
v-show="data.popoverShow"
v-passive-touch="close"
@click="close"
class="popover-bg"
></view>
</div>
</template>
<script setup>
// 组件
// uniapp & vue
import { onLoad, onReady } from '@dcloudio/uni-app'
import { defineEmits, defineProps } from 'vue'
import {
reactive,
ref,
watch,
computed,
nextTick,
getCurrentInstance,
onMounted,
onBeforeUnmount,
} from 'vue'
import copy07 from '@/static/image/chatList/copy07@2x.png'
import multipleChoices from '@/static/image/chatList/multipleChoices@2x.png'
import cite from '@/static/image/chatList/cite@2x.png'
import withdraw from '@/static/image/chatList/withdraw@2x.png'
import delete07 from '@/static/image/chatList/delete@2x.png'
// pinia
const systemInfo = uni.getSystemInfoSync()
const bubbleRef = ref(null)
const props = defineProps({
isShowCopy: {
type: Boolean,
default: true,
},
isShowCite: {
type: Boolean,
default: true,
},
isShowWithdraw: {
type: Boolean,
default: true,
},
isShowConvertText: {
//是否显示转文字
type: Boolean,
default: false,
},
})
const emits = defineEmits(['clickMenu'])
/**
* @name 生成UUID
*/
const uuid = () => {
const reg = /[xy]/g
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'
.replace(reg, function (c) {
var r = (Math.random() * 16) | 0,
v = c == 'x' ? r : (r & 0x3) | 0x8
return v.toString(16)
})
.replace(/-/g, '')
}
const popoverBoxId = `ID${uuid()}`
const popoverContentId = `ID${uuid()}`
const instance = getCurrentInstance()
const data = reactive({
popoverShow: false,
defaultStyle: {},
showStyle: {
left: 0,
right: '',
transform: '',
},
iconStyle: {
left: '',
right: '',
transform: '',
},
})
/**
* @name 获取DOM
*/
const getDom = (dom) => {
return new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(instance)
let select = query.select(dom)
const boundingClientRect = select.boundingClientRect((data) => {
resolve(data)
})
boundingClientRect.exec()
})
}
const itemClick = (item) => {
emits('clickMenu', item)
}
// 弹起 长按5
let pressDownTime = 0
let time = null
const onTouchstart = () => {
time && clearTimeout(time)
time = setTimeout(open, 500)
}
const onTouchend = () => {
time && clearTimeout(time)
}
const open = async () => {
let popoverContent = await getDom(`#${popoverContentId}`)
let popoverBox = await getDom(`#${popoverBoxId}`)
// 缩放中心点
let originX = popoverBox.width / 2
// 根据距离 初始化位置 判断是顶部还是底部
let isTop = popoverBox.top - 50 > popoverContent.height
// 上面还是下面
data.defaultStyle = {
top: isTop ? '60rpx' : 'auto',
bottom: !isTop ? '-20rpx' : 'auto',
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(.8)`,
}
// 左边还是右边
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
data.defaultStyle.right = 0
// 动画缩放中心点
data.defaultStyle['transform-origin'] = `${
popoverContent.width - originX
}px ${isTop ? '100%' : '0%'}`
} else {
data.defaultStyle.left = 0
// 动画缩放中心点
data.defaultStyle['transform-origin'] = `${originX}px ${
isTop ? '100%' : '0%'
}`
}
data.showStyle = { ...data.defaultStyle }
// icon位置样式
let iconDefsultStyle = {
transform: `translate(0%, ${isTop ? '20%' : '-20%'})`,
'border-top-color': isTop ? '#333333' : '',
'border-bottom-color': !isTop ? '#333333' : '',
top: !isTop ? '-20rpx' : 'auto',
bottom: isTop ? '-20rpx' : 'auto',
}
setTimeout(() => {
if (popoverBox.left > systemInfo.windowWidth - popoverBox.right) {
data.showStyle = {
// 位置
...data.defaultStyle,
// 显示
opacity: 1,
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
'pointer-events': 'auto',
}
data.iconStyle = {
right: `${originX}px`,
left: 'auto',
...iconDefsultStyle,
}
} else {
data.showStyle = {
// 位置
...data.defaultStyle,
// 显示
opacity: 1,
transform: `translateY(${isTop ? '-100%' : '100%'}) scale(1)`,
'pointer-events': 'auto',
}
data.iconStyle = {
left: `${originX}px`,
right: 'auto',
...iconDefsultStyle,
}
}
if (!data.popoverShow) data.popoverShow = true
}, 200)
}
const close = (time) => {
setTimeout(() => {
data.popoverShow = false
data.showStyle = data.defaultStyle
}, time || 0)
}
const handleClickOutside = (event) => {
if ((data.popoverShow = false)) {
return false
}
if (bubbleRef.value && !bubbleRef.value.contains(event.target)) {
close()
}
}
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
onBeforeUnmount(() => {
document.removeEventListener('click', handleClickOutside)
})
</script>
<style lang="scss" scoped>
.ui-popover {
position: relative;
.popover-box {
width: fit-content;
}
.popover-content {
width: fit-content;
transition: all 0.2s;
z-index: 5;
position: absolute;
top: 0;
opacity: 0;
pointer-events: none;
}
.popover-bg {
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
z-index: 4;
}
.menu {
width: 526rpx;
height: 132rpx;
display: flex;
background-color: #333333;
display: flex;
color: #fff;
border-radius: 15rpx;
font-size: 26rpx;
padding: 0 10rpx;
box-sizing: border-box;
position: relative;
.icon {
border: 15rpx solid transparent;
position: absolute;
}
.item {
flex: 1;
height: 120rpx;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
image {
width: 26rpx;
height: 26rpx;
margin-bottom: 8rpx;
}
}
}
</style>