跳转到内容

v-hover-column

获取鼠标悬浮的在那一列的指令(列数从1开始)

支持多级表头,垂直合并单元格。水平合并单元格会有问题

代码

ts
import type { Directive } from 'vue'

type EventHandler = (e: MouseEvent) => void
type SetHoverColumn = (column: number | null) => void
type Position = { left: number; right: number }

const handlerMap = new WeakMap<
  HTMLElement,
  { handleMouseOver: EventHandler; handleMouseLeave: EventHandler }
>()

const directive: Directive<HTMLElement, SetHoverColumn> = {
  mounted(el, { value: setHoverColumn }) {
    let lastValue: number | null = null

    const innerSetHoverColumn = (value: number | null) => {
      if (lastValue === value) return
      lastValue = value
      setHoverColumn(value)
    }
    const handleMouseOver: EventHandler = (e) => {
      const dom = e.target as HTMLElement | null
      const cell = dom?.closest<HTMLTableCellElement>('td, th')
      if (!cell || (cell.tagName === 'TH' && cell.colSpan > 1)) {
        return innerSetHoverColumn(null)
      }
      const { left, width } = cell.getBoundingClientRect()
      const cellCenterX = left + width / 2
      const positions = getPositions(el)
      if (positions.length === 0) {
        return innerSetHoverColumn(null)
      }
      for (let i = 0; i < positions.length; i++) {
        const { left, right } = positions[i]
        if (cellCenterX >= left && cellCenterX <= right) {
          innerSetHoverColumn(i + 1)
          break
        }
      }
    }

    const handleMouseLeave: EventHandler = () => {
      innerSetHoverColumn(null)
    }

    el.addEventListener('mouseover', handleMouseOver)
    el.addEventListener('mouseleave', handleMouseLeave)

    // 保存处理函数以便卸载时移除
    handlerMap.set(el, { handleMouseOver, handleMouseLeave })
  },
  unmounted(el) {
    const handler = handlerMap.get(el)
    if (handler) {
      const { handleMouseOver, handleMouseLeave } = handler
      el.removeEventListener('mouseover', handleMouseOver)
      el.removeEventListener('mouseleave', handleMouseLeave)
      handlerMap.delete(el)
    }
  },
}

const getPositions = (el: HTMLElement) => {
  const colgroup = el.querySelector('colgroup')
  if (!colgroup) return []
  const startLeft = colgroup.getBoundingClientRect().left
  let left = startLeft
  return Array.from(colgroup.children).map<Position>((col) => {
    // 兼容 col 堆叠在一起的情况
    return {
      left,
      right: (left += col.getBoundingClientRect().width),
    }
  })
}

export default directive

示例

vue
<template>
  <el-table v-hover-column="setHoverColumn" :data="tableData">
    <el-table-column label="基本信息">
      <el-table-column prop="name" label="姓名" />
      <el-table-column prop="age" label="年龄" :colspan="2" />
    </el-table-column>
    <el-table-column label="联系方式">
      <el-table-column prop="email" label="邮箱" />
      <el-table-column prop="phone" label="电话" />
    </el-table-column>
  </el-table>
  <div>当前悬浮列:{{ hoverColumn }}</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';

// 示例数据
const hoverColumn = ref<number | null>(null);
const setHoverColumn = (column: number | null) => {
    hoverColumn.value = column
}
const tableData = ref([
  { name: '张三', age: 30, email: 'zhangsan@example.com', phone: '13800138000' },
  { name: '李四', age: 28, email: 'lisi@example.com', phone: '13900139000' }
]);
</script>