You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

366 lines
10 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view
class="u-tooltip"
:style="[$u.addStyle(customStyle)]"
>
<u-overlay
:show="showTooltip && tooltipTop !== -10000 && overlay"
customStyle="backgroundColor: rgba(0, 0, 0, 0)"
@click="overlayClickHandler"
></u-overlay>
<view class="u-tooltip__wrapper">
<text
class="u-tooltip__wrapper__text"
:id="textId"
:ref="textId"
:userSelect="false"
:selectable="false"
@longpress.stop="longpressHandler"
:style="{
color: color,
backgroundColor: bgColor && showTooltip && tooltipTop !== -10000 ? bgColor : 'transparent'
}"
>{{ text }}</text>
<u-transition
mode="fade"
:show="showTooltip"
duration="300"
:customStyle="{
position: 'absolute',
top: $u.addUnit(tooltipTop),
zIndex: zIndex,
...tooltipStyle
}"
>
<view
class="u-tooltip__wrapper__popup"
:id="tooltipId"
:ref="tooltipId"
>
<view
class="u-tooltip__wrapper__popup__indicator"
hover-class="u-tooltip__wrapper__popup__indicator--hover"
v-if="showCopy || buttons.length"
:style="[indicatorStyle, {
width: $u.addUnit(indicatorWidth),
height: $u.addUnit(indicatorWidth),
}]"
>
<!-- 由于nvue不支持三角形绘制这里就做一个四方形再旋转45deg得到露出的一个三角 -->
</view>
<view class="u-tooltip__wrapper__popup__list">
<view
v-if="showCopy"
class="u-tooltip__wrapper__popup__list__btn"
hover-class="u-tooltip__wrapper__popup__list__btn--hover"
@tap="setClipboardData"
>
<text
class="u-tooltip__wrapper__popup__list__btn__text"
>复制</text>
</view>
<u-line
direction="column"
color="#8d8e90"
v-if="showCopy && buttons.length > 0"
length="18"
></u-line>
<block v-for="(item , index) in buttons" :key="index">
<view
class="u-tooltip__wrapper__popup__list__btn"
hover-class="u-tooltip__wrapper__popup__list__btn--hover"
>
<text
class="u-tooltip__wrapper__popup__list__btn__text"
@tap="btnClickHandler(index)"
>{{ item }}</text>
</view>
<u-line
direction="column"
color="#8d8e90"
v-if="index < buttons.length - 1"
length="18"
></u-line>
</block>
</view>
</view>
</u-transition>
</view>
</view>
</template>
<script>
import props from './props.js';
// #ifdef APP-NVUE
const dom = uni.requireNativePlugin('dom')
// #endif
// #ifdef H5
import ClipboardJS from "./clipboard.min.js"
// #endif
/**
* Tooltip
* @description
* @tutorial https://www.uviewui.com/components/tooltip.html
* @property {String | Number} text 需要显示的提示文字
* @property {String | Number} copyText 点击复制按钮时复制的文本为空则使用text值
* @property {String | Number} size 文本大小(默认 14
* @property {String} color 字体颜色(默认 '#606266'
* @property {String} bgColor 弹出提示框时,文本的背景色(默认 'transparent'
* @property {String} direction 弹出提示的方向top-上方bottom-下方(默认 'top'
* @property {String | Number} zIndex 弹出提示的z-indexnvue无效默认 10071
* @property {Boolean} showCopy 是否显示复制按钮(默认 true
* @property {Array} buttons 扩展的按钮组
* @property {Boolean} overlay 是否显示透明遮罩以防止触摸穿透(默认 true
* @property {Object} customStyle 定义需要用到的外部样式
*
* @event {Function}
* @example
*/
export default {
name: 'u-tooltip',
mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
data() {
return {
// 是否展示气泡
showTooltip: true,
// 生成唯一id防止一个页面多个组件造成干扰
textId: uni.$u.guid(),
tooltipId: uni.$u.guid(),
// 初始时甚至为很大的值,让其移到屏幕外面,为了计算元素的尺寸
tooltipTop: -10000,
// 气泡的位置信息
tooltipInfo: {
width: 0,
left: 0
},
// 文本的位置信息
textInfo: {
width: 0,
left: 0
},
// 三角形指示器的样式
indicatorStyle: {},
// 气泡在可能超出屏幕边沿范围时,重新定位后,距离屏幕边沿的距离
screenGap: 12,
// 三角形指示器的宽高,由于对元素进行了角度旋转,精确计算指示器位置时,需要用到其尺寸信息
indicatorWidth: 14,
}
},
watch: {
propsChange() {
this.getElRect()
}
},
computed: {
// 特别处理H5的复制因为H5浏览器是自带系统复制功能的在H5环境
// 当一些依赖参数变化时,需要重新计算气泡和指示器的位置信息
propsChange() {
return [this.text, this.buttons]
},
// 计算气泡和指示器的位置信息
tooltipStyle() {
const style = {
transform: `translateY(${this.direction === 'top' ? '-100%' : '100%'})`,
},
sys = uni.$u.sys(),
getPx = uni.$u.getPx,
addUnit = uni.$u.addUnit
if (this.tooltipInfo.width / 2 > this.textInfo.left + this.textInfo.width / 2 - this.screenGap) {
this.indicatorStyle = {}
style.left = `-${addUnit(this.textInfo.left - this.screenGap)}`
this.indicatorStyle.left = addUnit(this.textInfo.width / 2 - getPx(style.left) - this.indicatorWidth /
2)
} else if (this.tooltipInfo.width / 2 > sys.windowWidth - this.textInfo.right + this.textInfo.width / 2 -
this.screenGap) {
this.indicatorStyle = {}
style.right = `-${addUnit(sys.windowWidth - this.textInfo.right - this.screenGap)}`
this.indicatorStyle.right = addUnit(this.textInfo.width / 2 - getPx(style.right) - this
.indicatorWidth / 2)
} else {
const left = Math.abs(this.textInfo.width / 2 - this.tooltipInfo.width / 2)
style.left = this.textInfo.width > this.tooltipInfo.width ? addUnit(left) : -addUnit(left)
this.indicatorStyle = {}
}
if (this.direction === 'top') {
style.marginTop = '-10px'
this.indicatorStyle.bottom = '-4px'
} else {
style.marginBottom = '-10px'
this.indicatorStyle.top = '-4px'
}
return style
}
},
mounted() {
this.init()
},
methods: {
init() {
this.getElRect()
},
// 长按触发事件
async longpressHandler() {
this.tooltipTop = 0
this.showTooltip = true
},
// 点击透明遮罩
overlayClickHandler() {
this.showTooltip = false
},
// 点击弹出按钮
btnClickHandler(index) {
this.showTooltip = false
// 如果需要展示复制按钮此处index需要加1因为复制按钮在第一个位置
this.$emit('click', this.showCopy ? index + 1 : index)
},
// 查询内容高度
queryRect(ref) {
// #ifndef APP-NVUE
// $uGetRect为uView自带的节点查询简化方法详见文档介绍https://www.uviewui.com/js/getRect.html
// 组件内部一般用this.$uGetRect对外的为uni.$u.getRect二者功能一致名称不同
return new Promise(resolve => {
this.$uGetRect(`#${ref}`).then(size => {
resolve(size)
})
})
// #endif
// #ifdef APP-NVUE
// nvue下使用dom模块查询元素高度
// 返回一个promise让调用此方法的主体能使用then回调
return new Promise(resolve => {
dom.getComponentRect(this.$refs[ref], res => {
resolve(res.size)
})
})
// #endif
},
// 元素尺寸
getElRect() {
// 调用之前,先将指示器调整到屏幕外,方便获取尺寸
this.showTooltip = true
this.tooltipTop = -10000
uni.$u.sleep(500).then(() => {
this.queryRect(this.tooltipId).then(size => {
this.tooltipInfo = size
// 获取气泡尺寸之后,将其隐藏,为了让下次切换气泡显示与隐藏时,有淡入淡出的效果
this.showTooltip = false
})
this.queryRect(this.textId).then(size => {
this.textInfo = size
})
})
},
// 复制文本到粘贴板
setClipboardData() {
// 关闭组件
this.showTooltip = false
this.$emit('click', 0)
// #ifndef H5
uni.setClipboardData({
// 优先使用copyText字段如果没有则默认使用text字段当做复制的内容
data: this.copyText || this.text,
success: () => {
this.showToast && uni.$u.toast('复制成功')
},
fail: () => {
this.showToast && uni.$u.toast('复制失败')
},
complete: () => {
this.showTooltip = false
}
})
// #endif
// #ifdef H5
let event = window.event || e || {}
let clipboard = new ClipboardJS('', {
text: () => this.copyText || this.text
})
clipboard.on('success', (e) => {
this.showToast && uni.$u.toast('复制成功')
clipboard.off('success')
clipboard.off('error')
// 在单页应用中需要销毁DOM的监听
clipboard.destroy()
})
clipboard.on('error', (e) => {
this.showToast && uni.$u.toast('复制失败')
clipboard.off('success')
clipboard.off('error')
// 在单页应用中需要销毁DOM的监听
clipboard.destroy()
})
clipboard.onClick(event)
// #endif
}
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-tooltip {
position: relative;
@include flex;
&__wrapper {
@include flex;
justify-content: center;
/* #ifndef APP-NVUE */
white-space: nowrap;
/* #endif */
&__text {
font-size: 14px;
}
&__popup {
@include flex;
justify-content: center;
&__list {
background-color: #060607;
position: relative;
flex: 1;
border-radius: 5px;
padding: 0px 0;
@include flex(row);
align-items: center;
overflow: hidden;
&__btn {
padding: 11px 13px;
&--hover {
background-color: #58595B;
}
&__text {
line-height: 12px;
font-size: 13px;
color: #FFFFFF;
}
}
}
&__indicator {
position: absolute;
background-color: #060607;
width: 14px;
height: 14px;
bottom: -4px;
transform: rotate(45deg);
border-radius: 2px;
z-index: -1;
&--hover {
background-color: #58595B;
}
}
}
}
}
</style>