Element Ui Table 表头功能自定义

<template>
  <div style="position: relative; padding: 20px;">
    <!-- 表格组件,绑定过滤后的数据 filteredData -->
    <el-table
      :data="filteredData"
      row-key="date"
      style="width: 100%"
      height="320"
    >
      <!-- 普通列,显示日期,支持排序 -->
      <el-table-column prop="date" label="Date" sortable width="180" />

      <!-- 自定义表头列,筛选“Name” -->
      <el-table-column prop="name" label="Name" width="220">
        <!-- 自定义表头插槽 -->
        <template #header>
          <span>Name</span>
          <!-- 点击触发下拉筛选菜单 -->
          <span
            ref="dropdownTrigger"
            @click="toggleDropdown"
            tabindex="0"
            @keydown.enter.prevent="toggleDropdown"
            aria-haspopup="listbox"
            :aria-expanded="dropdownVisible.toString()"
            class="custom-dropdown-trigger"
          >
            <el-icon><arrow-down /></el-icon>
          </span>
          <!-- 显示已选项数量 -->
          <small v-if="selectedNames.length" style="margin-left: 6px; color: #409eff;">
            {{ selectedNames.length }} 项已选
          </small>
        </template>

        <!-- 默认单元格内容插槽,显示行的 name -->
        <template #default="{ row }">
          {{ row.name }}
        </template>
      </el-table-column>

      <!-- 普通列,地址,使用 formatter 格式化显示 -->
      <el-table-column prop="address" label="Address" :formatter="formatter" />

      <!-- 使用 Element Plus 自带过滤功能的列 -->
      <el-table-column
        prop="tag"
        label="Tag"
        width="100"
        :filters="[
          { text: 'Home', value: 'Home' },
          { text: 'Office', value: 'Office' }
        ]"
        :filter-method="filterTag"
        filter-placement="bottom-end"
      >
        <!-- 自定义单元格内容,标签显示不同颜色 -->
        <template #default="scope">
          <el-tag
            :type="scope.row.tag === 'Home' ? 'primary' : 'success'"
            disable-transitions
          >{{ scope.row.tag }}</el-tag>
        </template>
      </el-table-column>
    </el-table>

    <!-- 下拉筛选菜单使用 teleport 渲染到 body 避免遮挡 -->
    <teleport to="body">
      <transition name="fade">
        <div
          v-if="dropdownVisible"
          class="custom-dropdown-menu"
          :style="dropdownStyle"
          role="listbox"
          @click.stop
        >
          <!-- 搜索输入框,双向绑定 search -->
          <el-input
            v-model="search"
            placeholder="输入搜索关键词"
            size="small"
            clearable
            @clear="search = ''"
            style="margin: 6px 5px; width: 200px;"
          />
          <!-- 下拉列表 -->
          <ul class="dropdown-list">
            <!-- 无匹配提示 -->
            <li v-if="filteredNames.length === 0" class="no-data">无匹配项</li>
            <!-- 选项列表 -->
            <li
              v-for="name in filteredNames"
              :key="name"
              class="dropdown-item"
            >
              <!-- 多选复选框,控制临时选中状态 -->
              <el-checkbox
                :model-value="tempSelectedNames.includes(name)"
                @change="(checked, ev) => {
                  ev.stopPropagation()
                  toggleSelect(name)
                }"
              >
                {{ name }}
              </el-checkbox>
            </li>
          </ul>
          <!-- 底部操作按钮 -->
          <div class="dropdown-footer">
            <!-- 清除选择 -->
            <el-button type="text" size="small" @click="clearSelection">清除筛选</el-button>
            <!-- 应用选择 -->
            <el-button type="primary" size="small" @click="applySelection">确定</el-button>
          </div>
        </div>
      </transition>
    </teleport>
  </div>
</template>

<script setup>
import { ref, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { ArrowDown } from '@element-plus/icons-vue'

// 表格数据源
const tableData = ref([
  { date: '2016-05-03', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', tag: 'Home' },
  { date: '2016-05-02', name: 'Jerry', address: 'No. 189, Grove St, Los Angeles', tag: 'Office' },
  { date: '2016-05-04', name: 'Spike', address: 'No. 189, Grove St, Los Angeles', tag: 'Home' },
  { date: '2016-05-01', name: 'Tom', address: 'No. 189, Grove St, Los Angeles', tag: 'Office' }
])

// 下拉触发元素引用
const dropdownTrigger = ref(null)

// 下拉菜单显示状态
const dropdownVisible = ref(false)

// 下拉菜单样式(位置固定)
const dropdownStyle = ref({
  position: 'fixed',
  left: '0px',
  top: '0px',
  minWidth: '220px',
  zIndex: 3000
})

// 搜索关键词
const search = ref('')

// 已确定的筛选名字列表
const selectedNames = ref([])

// 临时选择的名字列表(用于下拉中选择,未确认前不影响表格筛选)
const tempSelectedNames = ref([])

// 从表格数据中提取所有唯一的名字,排序后返回,用于展示下拉选项
const nameOptions = computed(() => {
  const set = new Set()
  tableData.value.forEach(item => set.add(item.name))
  return Array.from(set).sort()
})

// 根据搜索关键词过滤名字选项
const filteredNames = computed(() => {
  if (!search.value.trim()) return nameOptions.value
  const kw = search.value.trim().toLowerCase()
  return nameOptions.value.filter(name => name.toLowerCase().includes(kw))
})

// 根据已选的名字筛选表格数据
const filteredData = computed(() => {
  let data = tableData.value
  if (selectedNames.value.length > 0) {
    data = data.filter(item => selectedNames.value.includes(item.name))
  }
  return data
})

// 切换下拉菜单显示隐藏
function toggleDropdown() {
  dropdownVisible.value = !dropdownVisible.value
  if (dropdownVisible.value) {
    // 打开时同步临时选中数据,重置搜索框
    tempSelectedNames.value = [...selectedNames.value]
    search.value = ''
    nextTick(() => updateDropdownPosition()) // 更新下拉位置
  }
}

// 根据触发元素位置更新下拉菜单的定位
function updateDropdownPosition() {
  if (!dropdownTrigger.value) return
  const rect = dropdownTrigger.value.getBoundingClientRect()
  dropdownStyle.value = {
    position: 'fixed',
    left: `${rect.left}px`,
    top: `${rect.bottom + 6}px`, // 触发元素底部向下偏移6px
    minWidth: '220px',
    maxHeight: '300px',
    overflowY: 'auto',
    zIndex: 3000
  }
}

// 切换某个名字的选中状态(临时选中)
function toggleSelect(name) {
  const index = tempSelectedNames.value.indexOf(name)
  if (index > -1) {
    tempSelectedNames.value.splice(index, 1)
  } else {
    tempSelectedNames.value.push(name)
  }
}

// 清除临时选择和搜索关键词
function clearSelection() {
  tempSelectedNames.value = []
  search.value = ''
}

// 确认筛选,应用临时选择到正式选择,并关闭下拉
function applySelection() {
  const kw = search.value.trim().toLowerCase()

  // 如果搜索框中有关键词,自动将匹配项加入临时选择
  if (kw) {
    const matched = nameOptions.value.filter(name => name.toLowerCase().includes(kw))
    const merged = new Set([...tempSelectedNames.value, ...matched])
    tempSelectedNames.value = Array.from(merged)
  }

  selectedNames.value = [...tempSelectedNames.value]
  dropdownVisible.value = false
}

// 地址列格式化函数,直接返回地址文本
function formatter(row) {
  return row.address
}

// tag 列过滤方法,只显示选中标签的数据
function filterTag(value, row) {
  return row.tag === value
}

// 点击页面其他位置时关闭下拉菜单
function onClickOutside(event) {
  if (
    dropdownTrigger.value &&
    !dropdownTrigger.value.contains(event.target) &&
    !event.target.closest('.custom-dropdown-menu')
  ) {
    dropdownVisible.value = false
  }
}

// 组件挂载时注册事件监听
onMounted(() => {
  document.addEventListener('click', onClickOutside)
  // 页面滚动时更新下拉位置
  window.addEventListener('scroll', () => {
    if (dropdownVisible.value) updateDropdownPosition()
  }, true)
})

// 组件卸载时移除事件监听
onBeforeUnmount(() => {
  document.removeEventListener('click', onClickOutside)
  window.removeEventListener('scroll', () => {
    if (dropdownVisible.value) updateDropdownPosition()
  }, true)
})
</script>

<style scoped>
/* 下拉触发图标样式 */
.custom-dropdown-trigger {
  font-size: 14px;
  color: #909399;
  vertical-align: middle;
  transition: color 0.3s;
  display: inline-flex;
  align-items: center;
  margin-left: 6px;
  cursor: pointer;
  user-select: none;
}
.custom-dropdown-trigger:hover {
  color: #409eff;
}

/* 下拉菜单整体样式 */
.custom-dropdown-menu {
  padding: 8px 0 4px 0;
  background: #fff;
  border: 1px solid #ebeef5;
  box-shadow: 0 2px 12px 0 rgb(0 0 0 / 0.1);
  border-radius: 4px;
  max-height: 300px;
  overflow-y: auto;
  min-width: 220px;
  font-size: 14px;
}

/* 下拉列表 */
.dropdown-list {
  list-style: none;
  margin: 0;
  padding: 0 10px;
  max-height: 220px;
  overflow-y: auto;
}

/* 下拉项 */
.dropdown-item {
  padding: 4px 0;
  cursor: pointer;
  display: flex;
  align-items: center;
}
.dropdown-item:hover {
  background-color: #f5f7fa;
}

/* 无匹配项提示 */
.no-data {
  padding: 8px 10px;
  color: #999;
  text-align: center;
}

/* 下拉底部操作栏 */
.dropdown-footer {
  padding: 8px 10px 4px;
  border-top: 1px solid #ebeef5;
  display: flex;
  justify-content: space-between;
}

/* 淡入淡出动画 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值