开发笔记07:vue3+datav+elementPlus滚动表格文字超出展示tooltip解决办法
vue3+datav+elementPlus滚动表格文字超出展示tooltip解决办法,附解决办法!
·
问题
使用datav时候有时候文本过长导致文字展示不全,但是又想要展示tooltip展示全部文字,所以看了一下源码,简单修改一下就OK
图片效果

提示:以下是本篇文章正文内容,下面案例可供参考
使用步骤
1.把scrollBoard文件取出来并修改文件
代码如下(直接复制拿来用就好):默认是用的v-html来渲染,所以不支持组件渲染,简单把v-html指令修改为可渲染的组件即可
<script setup lang="jsx">
import autoResize from '../utils/autoResize'
import { deepClone, deepMerge } from '../utils/index'
import { isElement, isString } from '@/utils/is'
const props = defineProps({
config: {
type: Object,
default: () => ({}),
},
})
const emitEvent = defineEmits(['mouseover', 'click', 'getFirstRow'])
const scrollBoard = ref(null)
const { width, height } = autoResize(scrollBoard, onResize, afterAutoResizeMixinInit)
const state = reactive({
defaultConfig: {
/**
* @description Board header
* @type {Array<string>}
* @default header = []
* @example header = ['column1', 'column2', 'column3']
*/
header: [],
/**
* @description Board data
* @type {Array<Array>}
* @default data = []
*/
data: [],
/**
* @description Row num
* @type {number}
* @default rowNum = 5
*/
rowNum: 5,
/**
* @description Header background color
* @type {string}
* @default headerBGC = '#00BAFF'
*/
headerBGC: '#00BAFF',
/**
* @description Odd row background color
* @type {string}
* @default oddRowBGC = '#003B51'
*/
oddRowBGC: '#003B51',
/**
* @description Even row background color
* @type {string}
* @default evenRowBGC = '#003B51'
*/
evenRowBGC: '#0A2732',
/**
* @description Scroll wait time
* @type {number}
* @default waitTime = 2000
*/
waitTime: 2000,
/**
* @description Header height
* @type {number}
* @default headerHeight = 35
*/
headerHeight: 35,
/**
* @description Column width
* @type {Array<number>}
* @default columnWidth = []
*/
columnWidth: [],
/**
* @description Column align
* @type {Array<string>}
* @default align = []
* @example align = ['left', 'center', 'right']
*/
align: [],
/**
* @description Show index
* @type {boolean}
* @default index = false
*/
index: false,
/**
* @description index Header
* @type {string}
* @default indexHeader = '#'
*/
indexHeader: '#',
/**
* @description Carousel type
* @type {string}
* @default carousel = 'single'
* @example carousel = 'single' | 'page'
*/
carousel: 'single',
/**
* @description Pause scroll when mouse hovered
* @type {boolean}
* @default hoverPause = true
* @example hoverPause = true | false
*/
hoverPause: true,
},
mergedConfig: null,
header: [],
rowsData: [],
rows: [],
widths: [],
heights: [],
avgHeight: 0,
aligns: [],
animationIndex: 0,
animationHandler: '',
updater: 0,
needCalc: false,
})
watch(() => props.config, () => {
stopAnimation()
// 此处打开后,config发生变化时,会从第一行开始重新轮播
// state.animationIndex = 0
calcData()
}, { deep: true })
onUnmounted(() => {
stopAnimation()
})
defineExpose({
updateRows,
})
function handleClick(ri, ci, row, ceil) {
const { ceils, rowIndex } = row
emitEvent('click', {
row: ceils,
ceil,
rowIndex,
columnIndex: ci,
})
}
function handleHover(enter, ri, ci, row, ceil) {
if (enter) {
const { ceils, rowIndex } = row
emitEvent('mouseover', {
row: ceils,
ceil,
rowIndex,
columnIndex: ci,
})
}
if (!state.mergedConfig.hoverPause)
return
if (enter)
stopAnimation()
else
animation(true)
}
function afterAutoResizeMixinInit() {
calcData()
}
function onResize() {
if (!state.mergedConfig)
return
calcWidths()
calcHeights()
}
function calcData() {
mergeConfig()
calcHeaderData()
calcRowsData()
calcWidths()
calcHeights()
calcAligns()
animation(true)
}
function mergeConfig() {
state.mergedConfig = deepMerge(deepClone(state.defaultConfig, true), props.config || {})
}
function calcHeaderData() {
let { header } = state.mergedConfig
const { index, indexHeader } = state.mergedConfig
if (!header.length) {
header = []
return
}
header = [...header]
if (index)
header.unshift(indexHeader)
state.header = header
}
function calcRowsData() {
let { data } = state.mergedConfig
const { index, headerBGC, rowNum } = state.mergedConfig
if (index) {
data = data.map((row, i) => {
row = [...row]
const indexTag = `<span class="index" style="background-color: ${headerBGC};">${i + 1}</span>`
row.unshift(indexTag)
return row
})
}
data = data.map((ceils, i) => ({ ceils, rowIndex: i }))
const rowLength = data.length
if (rowLength > rowNum && rowLength < 2 * rowNum)
data = [...data, ...data]
data = data.map((d, i) => ({ ...d, scroll: i }))
state.rowsData = data
state.rows = data
}
function calcWidths() {
const { columnWidth, header } = state.mergedConfig
const usedWidth = columnWidth.reduce((all, w) => all + w, 0)
let columnNum = 0
if (state.rowsData[0])
columnNum = state.rowsData[0].ceils.length
else if (header.length)
columnNum = header.length
const avgWidth = (width.value - usedWidth) / (columnNum - columnWidth.length)
const widths = Array.from({ length: columnNum }).fill(avgWidth)
state.widths = deepMerge(widths, columnWidth)
}
function calcHeights(onresize = false) {
const { headerHeight, rowNum, data } = state.mergedConfig
let allHeight = height.value
if (state.header.length)
allHeight -= headerHeight
const avgHeight = allHeight / rowNum
state.avgHeight = avgHeight
if (!onresize)
state.heights = Array.from({ length: data.length }).fill(avgHeight)
}
function calcAligns() {
const columnNum = state.header.length
const aligns = Array.from({ length: columnNum }).fill('left')
const { align } = state.mergedConfig
state.aligns = deepMerge(aligns, align)
}
async function animation(start = false) {
if (state.needCalc) {
calcRowsData()
calcHeights()
state.needCalc = false
}
const { waitTime, carousel, rowNum } = state.mergedConfig
const { updater } = state
const rowLength = state.rowsData.length
if (rowNum >= rowLength)
return
if (start)
await new Promise(resolve => setTimeout(resolve, waitTime))
if (updater !== state.updater)
return
const animationNum = carousel === 'single' ? 1 : rowNum
const rows = state.rowsData.slice(state.animationIndex)
rows.push(...state.rowsData.slice(0, state.animationIndex))
state.rows = rows.slice(0, carousel === 'page' ? rowNum * 2 : rowNum + 1)
state.heights = Array.from({ length: rowLength }).fill(state.avgHeight)
await new Promise(resolve => setTimeout(resolve, 300))
if (updater !== state.updater)
return
state.heights.splice(0, animationNum, ...Array.from({ length: animationNum }).fill(0))
state.animationIndex += animationNum
const back = state.animationIndex - rowLength
if (back >= 0)
state.animationIndex = back
// state.animationIndex = animationIndex
state.animationHandler = setTimeout(animation, waitTime - 300)
emitEvent('getFirstRow', rows[1])
}
function stopAnimation() {
state.updater = (state.updater + 1) % 999999
if (!state.animationHandler)
return
clearTimeout(state.animationHandler)
}
function updateRows(rows, animationIndex) {
state.mergedConfig = {
...state.mergedConfig,
data: [...rows],
}
state.needCalc = true
if (typeof animationIndex === 'number')
state.animationIndex = animationIndex
if (!state.animationHandler)
animation(true)
}
/**
*
* @param row { VNode | string }
*/
const renderCell = (row) => {
if (isString(row)) {
return h('div', {
innerHTML: row,
})
}
return row
}
</script>
<template>
<div ref="scrollBoard" class="dv-scroll-board">
<div v-if="state.header.length && state.mergedConfig" class="header" :style="`background-color: ${state.mergedConfig.headerBGC};`">
<div
v-for="(headerItem, i) in state.header"
:key="`${headerItem}${i}`"
class="header-item"
:style="`
height: ${state.mergedConfig.headerHeight}px;
line-height: ${state.mergedConfig.headerHeight}px;
width: ${state.widths[i]}px;
`"
:align="state.aligns[i]"
v-html="headerItem"
/>
</div>
<div
v-if="state.mergedConfig"
class="rows"
:style="`height: ${height - (state.header.length ? state.mergedConfig.headerHeight : 0)}px;`"
>
<div
v-for="(row, ri) in state.rows"
:key="`${row.toString()}${row.scroll}`"
class="row-item"
:style="`
height: ${state.heights[ri]}px;
line-height: ${state.heights[ri]}px;
background-color: ${state.mergedConfig[row.rowIndex % 2 === 0 ? 'evenRowBGC' : 'oddRowBGC']};
`"
>
<div
v-for="(ceil, ci) in row.ceils"
:key="`${ceil}${ri}${ci}`"
class="ceil"
:style="`width: ${state.widths[ci]}px;`"
:align="state.aligns[ci]"
@click="handleClick(ri, ci, row, ceil)"
@mouseenter="handleHover(true, ri, ci, row, ceil)"
@mouseleave="handleHover(false)"
>
<component :is="renderCell(ceil)" />
</div>
</div>
</div>
</div>
</template>
<style lang="scss">
.dv-scroll-board {
position: relative;
width: 100%;
height: 100%;
color: #fff;
.text {
padding: 0 10px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header {
display: flex;
flex-direction: row;
font-size: 15px;
.header-item {
padding: 0 10px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: all 0.3s;
}
}
.rows {
overflow: hidden;
.row-item {
display: flex;
font-size: 14px;
transition: all 0.3s;
}
.ceil {padding: 0 10px;
box-sizing: border-box;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.index {
border-radius: 3px;
padding: 0px 3px;
}
}
}
</style>
2.读入数据
代码如下(示例):
data = pd.read_csv(
'https://labfile.oss.aliyuncs.com/courses/1283/adult.data.csv')
print(data.head())
3.引入相关的其他文件
创建一个util文件夹把需要用的两个文件直接丢进去正确引入就好
autoResize.ts文件
import type { Ref } from 'vue'
import { useDebounceFn, useEventListener } from '@vueuse/core'
import { nextTick, onActivated, onDeactivated, onMounted, onUnmounted, ref } from 'vue'
import { observerDomResize } from './index'
function autoResize(dom: Ref<HTMLElement | null>, onResize?: () => void, afterAutoResizeMixinInit?: () => void) {
const width = ref(0)
const height = ref(0)
let debounceInitWHFun: () => void
let domObserver: MutationObserver | null = null
let domHtml: HTMLElement | null = null
const initWH = (resize = true) => {
return new Promise((resolve) => {
nextTick(() => {
domHtml = dom.value
width.value = dom.value ? dom.value.clientWidth : 0
height.value = dom.value ? dom.value.clientHeight : 0
if (!dom.value)
console.warn('DataV: Failed to get dom node, component rendering may be abnormal!')
else if (!width.value || !height.value)
console.warn('DataV: Component width or height is 0px, rendering abnormality may occur!')
if (typeof onResize === 'function' && resize)
onResize()
resolve(true)
})
})
}
const getDebounceInitWHFun = () => {
debounceInitWHFun = useDebounceFn(initWH, 200)
}
const bindDomResizeCallback = () => {
domObserver = observerDomResize(domHtml!, debounceInitWHFun)
useEventListener(window, 'resize', debounceInitWHFun)
}
const unbindDomResizeCallback = () => {
if (!domObserver)
return
domObserver.disconnect()
domObserver.takeRecords()
domObserver = null
}
const autoResizeMixinInit = async () => {
await initWH(false)
getDebounceInitWHFun()
bindDomResizeCallback()
if (typeof afterAutoResizeMixinInit === 'function')
afterAutoResizeMixinInit()
}
onMounted(() => {
autoResizeMixinInit()
})
onUnmounted(() => {
unbindDomResizeCallback()
})
onActivated(autoResizeMixinInit)
onDeactivated(unbindDomResizeCallback)
return {
width,
height,
initWH,
}
}
export default autoResize
index.ts文件
export interface Point {
x: number
y: number
}
/* eslint-disable prefer-rest-params */
export function randomExtend(minNum: number, maxNum: number) {
if (arguments.length === 1)
return Number.parseInt((Math.random() * minNum + 1).toString(), 10)
else
return Number.parseInt((Math.random() * (maxNum - minNum + 1) + minNum).toString(), 10)
}
export function debounce<T>(delay: number, callback: (...args: T[]) => void, vm: T) {
let lastTime: number | undefined
return function () {
clearTimeout(lastTime)
lastTime = setTimeout(() => {
callback.call(vm, ...arguments)
}, delay)
}
}
export function observerDomResize(dom: HTMLElement, callback: () => void) {
const MutationObserver = window.MutationObserver
const observer = new MutationObserver(callback)
observer.observe(dom, { attributes: true, attributeFilter: ['style'], attributeOldValue: true })
return observer
}
export function getPointDistance(pointOne: number[], pointTwo: number[]) {
const minusX = Math.abs(pointOne[0] - pointTwo[0])
const minusY = Math.abs(pointOne[1] - pointTwo[1])
return Math.sqrt(minusX * minusX + minusY * minusY)
}
/**
* @description Get the coordinates of the specified radian on the circle
* @param {number} x Circle x coordinate
* @param {number} y Circle y coordinate
* @param {number} radius Circle radius
* @param {number} radian Specfied radian
* @return {Array} Postion of point
*/
export function getCircleRadianPoint(x: number, y: number, radius: number, radian: number) {
return [x + Math.cos(radian) * radius, y + Math.sin(radian) * radius]
}
function filterNonNumber(array: Array<number>) {
return array.filter((n) => {
return typeof n === 'number'
})
}
function mulAdd(nums: Array<number>) {
nums = filterNonNumber(nums)
return nums.reduce((all, num) => {
return all + num
}, 0)
}
function getTwoPointDistance(pointOne: Point, pointTwo: Point) {
const minusX = Math.abs(pointOne.x - pointTwo.x)
const minusY = Math.abs(pointOne.y - pointTwo.y)
return Math.sqrt(minusX * minusX + minusY * minusY)
}
export function getPolylineLength(points: Array<Point>) {
const lineSegments = Array.from({ length: points.length - 1 }).fill(0).map((foo, i) => {
return [points[i], points[i + 1]]
})
const lengths = lineSegments.map((item) => {
return getTwoPointDistance(item[0], item[1])
})
return mulAdd(lengths)
}
function PointToString(point: Point) {
return `${point.x},${point.y}`
}
export function PointsToString(points: Array<Point>) {
return points.map(PointToString).join(' ')
}
export function uuid(hasHyphen?: boolean) {
return (hasHyphen ? 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' : 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx').replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0
const v = c === 'x' ? r : (r & 0x3 | 0x8)
return v.toString(16)
})
}
export function deepMerge(target: any, merged: any) {
for (const key in merged) {
if (target[key] && typeof target[key] === 'object') {
deepMerge(target[key], merged[key])
continue
}
if (typeof merged[key] === 'object') {
target[key] = deepClone(merged[key], true)
continue
}
target[key] = merged[key]
}
return target
}
/**
* @description Clone an object or array
* @param {object | Array} object Cloned object
* @param {boolean} recursion Whether to use recursive cloning
* @return {object | Array} Clone object
*/
export function deepClone(object: any, recursion: boolean) {
if (!object)
return object
const { parse, stringify } = JSON
if (!recursion)
return parse(stringify(object))
const clonedObj: Record<string, any> = Array.isArray(object) ? [] : {}
if (object && typeof object === 'object') {
for (const key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
if (object[key] && typeof object[key] === 'object')
clonedObj[key] = deepClone(object[key], true)
else
clonedObj[key] = object[key]
}
}
}
return clonedObj
}
4.使用
然后引入刚刚修改完文件使用就好,属性和官方文档还是一样的,就是在数据渲染时可以使用h函数渲染展示,贴一小片代码参考:
<script setup lang="ts" name="Defend">
import { reactive, ref } from 'vue'
import ScrollBoard from './scrollBoard.vue'
// 自己封装的宽度超出展示...,可以网上找一下案例,还是比较多的
import EllipsisToolTip from '@/components/EllipsisToolTip/index.vue'
const scrollBoard = ref<InstanceType<any>>(null)
// 配置 ScrollBoard
const config = reactive({
header: ['时间', '描述', '标签'],
headerBGC: '#16355D',
oddRowBGC: '#10233F',
evenRowBGC: '#02121C',
align: ['left', 'left', 'left'],
columnWidth: [130],
data: [], // 初始为空,待API调用后填充
})
// 获取数据并更新表格
const getList = async () => {
try {
const res = [
{ time: '2024-11-22', description: '这是一个描述这是一个描述', tag: '标签' },
]
// 处理数据并生成表格内容
const formattedData = res.map((item: any) => [
item.time,
h(EllipsisToolTip, {
block: true,
placement: 'top',
text: item.description,
}),
item.tag,
])
config.data = formattedData
}
catch (error) {
console.error('Error fetching duty risk data:', error)
}
}
getList()
</script>
<template>
<div class="safe" style="overflow: hidden;">
<ScrollBoard
ref="scrollBoard"
:config="config"
style="width:100%;height:100%;"
/>
</div>
</template>
<style scoped lang="scss">
</style>
总结
简单来讲就是讲v-html标签替换为组件渲染,添加了renderCell方法来判断进行回显,不过el-tooltip应该也是可以的,不想找的话可以直接渲染elemenet的组件试试。。
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)