|
|
<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-index,nvue无效(默认 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>
|