vxe-table
Version:
一个基于 vue 的 PC 端表格组件,支持增删改查、虚拟滚动、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、虚拟列表、模态窗口、自定义模板、渲染器、贼灵活的配置项、扩展接口等...
237 lines (234 loc) • 6.94 kB
JavaScript
import XEUtils from 'xe-utils/ctor'
import GlobalConfig from '../../conf'
import vSize from '../../mixins/size'
import { UtilTools, DomTools } from '../../tools'
function updateTipStyle (_vm) {
const { $el: wrapperElem, tipTarget, tipStore } = _vm
const { scrollTop, scrollLeft, visibleWidth } = DomTools.getDomNode()
const { top, left } = DomTools.getAbsolutePos(tipTarget)
const marginSize = 6
const offsetHeight = wrapperElem.offsetHeight
const offsetWidth = wrapperElem.offsetWidth
let tipLeft = left
let tipTop = top - offsetHeight - marginSize
tipLeft = Math.max(marginSize, left + Math.floor((tipTarget.offsetWidth - offsetWidth) / 2))
if (tipLeft + offsetWidth + marginSize > scrollLeft + visibleWidth) {
tipLeft = scrollLeft + visibleWidth - offsetWidth - marginSize
}
if (top - offsetHeight < scrollTop + marginSize) {
tipStore.placement = 'bottom'
tipTop = top + tipTarget.offsetHeight + marginSize
}
tipStore.style.top = `${tipTop}px`
tipStore.style.left = `${tipLeft}px`
tipStore.arrowStyle.left = `${left - tipLeft + tipTarget.offsetWidth / 2}px`
}
export default {
name: 'VxeTooltip',
mixins: [vSize],
props: {
value: Boolean,
size: { type: String, default: () => GlobalConfig.tooltip.size || GlobalConfig.size },
trigger: { type: String, default: () => GlobalConfig.tooltip.trigger },
theme: { type: String, default: () => GlobalConfig.tooltip.theme },
content: [String, Number],
zIndex: [String, Number],
isArrow: { type: Boolean, default: true },
enterable: Boolean,
leaveDelay: { type: Number, default: GlobalConfig.tooltip.leaveDelay },
leaveMethod: Function
},
data () {
return {
isUpdate: false,
isHover: false,
visible: false,
message: '',
tipTarget: null,
tipZindex: 0,
tipStore: {
style: {},
placement: '',
arrowStyle: null
}
}
},
watch: {
content (value) {
this.message = value
},
value (value) {
if (!this.isUpdate) {
this[value ? 'open' : 'close']()
}
this.isUpdate = false
}
},
mounted () {
const { $el, trigger, content, value } = this
const parentNode = $el.parentNode
let target
this.message = content
this.tipZindex = UtilTools.nextZIndex()
XEUtils.arrayEach($el.children, (elem, index) => {
if (index > 1) {
parentNode.insertBefore(elem, $el)
if (!target) {
target = elem
}
}
})
parentNode.removeChild($el)
this.target = target
if (target) {
if (trigger === 'hover') {
target.onmouseleave = this.targetMouseleaveEvent
target.onmouseenter = this.targetMouseenterEvent
} else if (trigger === 'click') {
target.onclick = this.clickEvent
}
}
if (value) {
this.open()
}
},
beforeDestroy () {
const { $el, target, trigger } = this
const parentNode = $el.parentNode
if (parentNode) {
parentNode.removeChild($el)
}
if (target) {
if (trigger === 'hover') {
target.onmouseenter = null
target.onmouseleave = null
} else if (trigger === 'click') {
target.onclick = null
}
}
},
render (h) {
const { vSize, theme, message, isHover, isArrow, visible, tipStore, enterable } = this
let on
if (enterable) {
on = {
mouseenter: this.wrapperMouseenterEvent,
mouseleave: this.wrapperMouseleaveEvent
}
}
return h('div', {
class: ['vxe-table--tooltip-wrapper', `theme--${theme}`, `placement--${tipStore.placement}`, {
[`size--${vSize}`]: vSize,
'is--enterable': enterable,
'is--visible': visible,
'is--arrow': isArrow,
'is--hover': isHover
}],
style: tipStore.style,
ref: 'tipWrapper',
on
}, [
h('div', {
class: 'vxe-table--tooltip-content'
}, this.$slots.content || message),
h('div', {
class: 'vxe-table--tooltip-arrow',
style: tipStore.arrowStyle
})
].concat(this.$slots.default))
},
methods: {
open (target, message) {
return this.toVisible(target || this.target, message)
},
close () {
this.tipTarget = null
Object.assign(this.tipStore, {
style: {},
placement: '',
arrowStyle: null
})
this.update(false)
return this.$nextTick()
},
update (value) {
if (value !== this.visible) {
this.visible = value
this.isUpdate = true
if (this.$listeners.input) {
this.$emit('input', this.visible)
}
}
},
updateZindex () {
if (this.tipZindex < UtilTools.getLastZIndex()) {
this.tipZindex = UtilTools.nextZIndex()
}
},
toVisible (target, message) {
this.targetActive = true
if (target) {
const { $el, tipStore, zIndex } = this
const parentNode = $el.parentNode
if (!parentNode) {
document.body.appendChild($el)
}
if (message) {
this.message = message
}
this.tipTarget = target
this.update(true)
this.updateZindex()
tipStore.placement = 'top'
tipStore.style = { width: 'auto', left: 0, top: 0, zIndex: zIndex || this.tipZindex }
tipStore.arrowStyle = { left: '50%' }
return this.updatePlacement()
}
return this.$nextTick()
},
updatePlacement () {
return this.$nextTick().then(() => {
const { $el: wrapperElem, tipTarget } = this
if (tipTarget && wrapperElem) {
updateTipStyle(this)
return this.$nextTick().then(() => updateTipStyle(this))
}
})
},
clickEvent () {
this[this.visible ? 'close' : 'open']()
},
targetMouseenterEvent () {
this.open()
},
targetMouseleaveEvent () {
const { trigger, enterable, leaveDelay } = this
this.targetActive = false
if (enterable && trigger === 'hover') {
setTimeout(() => {
if (!this.isHover) {
this.close()
}
}, leaveDelay)
} else {
this.close()
}
},
wrapperMouseenterEvent () {
this.isHover = true
},
wrapperMouseleaveEvent (evnt) {
const { leaveMethod, trigger, enterable, leaveDelay } = this
this.isHover = false
if (!leaveMethod || leaveMethod({ $event: evnt }) !== false) {
if (enterable && trigger === 'hover') {
setTimeout(() => {
if (!this.targetActive) {
this.close()
}
}, leaveDelay)
}
}
}
}
}