u-tooltip.vue
10.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
<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="{
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 {
color: $u-content-color;
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>