记录el-table 开启 列 fixed 后引发的内部组件重复渲染问题

本文讨论了在el-table中实现列固定和复选框功能时遇到的问题,包括复选框列头部按钮插入和展开行组件的渲染控制。作者提供了针对这两个问题的解决方案,涉及DOM操作和高度同步策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

el-table 对于列的居左、居右(即<el-table-column fixed='left'/> )功能实现 采用的方式是将多个el-table 进行拼凑,也就是说设置了居左、居右后,将出现三个el-table, 分别为 【居左】、【中间滚动区域】、【居右】 的 el-table拼凑出浮动功能。这将可能引起重复渲染组件带来的bug。此文章将记录在此背景下的两个需求引发的问题及解决方案。

需求一:对列是复选框的表头插入按钮,

问题:

1、当列为复选框时存在一个特殊性,其列的头部存在一个全选功能的checkBox,采用插槽方式无法直接替换内容

 <el-table-column
            label-class-name="table-checkbox"
            :key="`selection`"
            type="selection"
            fixed="left"
          >
            <!--  此插槽无效   -->
            <template v-slot:header>
            
            </template>
          </el-table-column>

2、开启列 fixed 后 将导致出现重复的 el-table,需要所有el-table 都插入才行

解决思路:

1、采用 js 获取 el-talbe 的复选框列表头 操作 dom
2、对每个找到的el-table 都进行需要的dom操作

解:

复选框列上添加自定义class名 table-checkbox

<el-table-column
    label-class-name="table-checkbox"
    :key="`selection`"
    type="selection"
    fixed="left"
>
</el-table-column>

js 中检索 此 class 名且属于表头的元素

setBtn() {
        // 创建元素方法
      function createElement(icon, callback) {
        const button = document.createElement('i')
        button.className = `${icon}`
        let timeout
        button.onclick = function ($event) {
          $event.preventDefault()
          clearTimeout(timeout)
          timeout = setTimeout(callback, 0)
        }
        return button
      }
      this.$nextTick(() => {
      // 获取该 el-table ref 
        const tableEl = this.$refs[`test_table`]
        if (!tableEl) return
        // el-table 可能存在多个表用于处理不同场景,因此每个 div.cell.table-checkbox 都要同步设置
        // 检索 此 class 名且属于表头的元素
        const headCellList = tableEl.$el.querySelectorAll(
          'th > div.cell.table-checkbox'
        )
        if (!isEmpty(headCellList)) {
            // 循环所有的元素进行插入
          headCellList.forEach(headCell => {
            // 0、创建div包裹按钮
            let box = headCell.querySelector('.heard-search-icons')
            if (!box) {
              box = document.createElement('div')
              box.className = 'btn-box'
            }
            // 1、按钮
            let searchEl = box.querySelector('.icon-lcdp-Search')
            if (!searchEl) {
              // 不存在,创建此元素 并绑定上方法 load
              searchEl = createElement.bind(this)(
                'el-icon-star-on',
                this.load
              )
              box.appendChild(searchEl)
            }
            // 按需控制隐藏显示
            if (true) {
              searchEl.hidden = false
            } else {
              //  未开启 隐藏
              searchEl.hidden = true
            }
            //  3、插入包裹的 box
            box && headCell.appendChild(box)
          })
        }
      })
    },

Complate the task 1

需求二:列开启展开行后插入组件

问题:

问题1、 因存在别的列开启了fixd ,导致出现多个table 且在展开行时组件被渲染多次。

解决思路:

解1、在需要插入展开行的组件内部created() 时判断其父级table class 是否 存在 ‘fixed’ 标识,及 this.$parent.$el.offsetParent.className 方式获取,被居左、居右的table class会携带 ‘fixed’,根据此办法判断是否进行 v-if 判断是否加载组件内的内容。
思路参考:https://github.com/ElemeFE/element/issues/12177

问题2、此方式同时也带来新问题:即使滞空了另外两个fixed的 table 的展开行内容,也需要保持三个table 的展开行高度一致,否则展开后,居左、居右的table 其列与 中间内容区的table 不对齐。如下图:

【图待补充】

此问题可采用解决方案:在行内要显示的组件内部进行渲染控制
解2、对不属于fixed 的table内的组件进行监听自身高度变化,变化后同步高度给另外两个fixed 下的组件,保证三个table 展开后高度保持一致。

解:

<!-- el-table -->

 <el-table-column type="expand" width="55">
      <template slot-scope="{ row, $index }">
          <!--某个组件-->
          <demoCompent />
      </template
 </el-table-column>

<!--展开行内组件 demoCompent HTML-->

<template>
  <div ref="expandRowBox" class="draggable-component-box">
  <!--此处空白区高度占位 也可以采用直接设置在draggable-component-box 的高度样式里实现-->
    <div
      v-if="!isLive"
      :style="`height:${
        expandRowHeight?.val && !isLive
          ? expandRowHeight?.val + 'px'
          : undefined
      }`"
    >
      占位
    </div>
    
        <!-- 本要渲染的组件 -->
    <div v-else>
        <actualComponent />
    </div>
</template>

<!--展开行内组件 demoCompent JS -->

 props: {
    // 展开行内容高度,传入引用地址若改变将导致组件被重新渲染,导致监听高度变化出现无线循环,采用对象方式接收高度不改变引用地址可避免此问题
    expandRowHeight: {
      type: Object,
      default: () => ({
        val: 0
      })
    },
 }data(){
    return {
       // 是否允许存在组件
      isLive: true,
      // 内容区el
      contentEl: '',
      // 高度监听器
      resizeObserver: '',
    }
},

created() {
    <!--1 实现代码-->
    // fix table 不再显示组件,用于解决table fixed 出现多个table重复渲染组件
    if (this.isFixTable()) {
      this.isLive = false
    }
},
mounted() {
    <!--2 实现代码-->
    if (!this.isFixTable()) {
      // 非fix Table 的展开行才监听高度变化
      this.observerHeight()
    }
},

beforeDestroy() {
    this.destroyObserver()
},

methods(){
    // 判断当前组件位置是否在 fixed的 el-table中 
    isFixTable() {
      const className = this.$parent.$el.offsetParent.className
      return className.includes('fixed')
    },
    
     // 1、监听高度变化
    observerHeight() {
      // 获取要监听高度变化的元素
      this.contentEl = this.$refs.expandRowBox
      this.emitHeight()
      // 创建 ResizeObserver 实例,并传入回调函数
      this.resizeObserver = new ResizeObserver(
        throttle(() => this.emitHeight(), 1000)
      )
      // 开始监听元素的大小变化
      this.contentEl && this.resizeObserver.observe(this.contentEl)
    },
    
    // 2、向外传递有效组件内的高度,同步其他table行内高度
    emitHeight() {
      if (!this.isFixTable()) {
        this.$emit('setHeight', this.$el.offsetHeight)
        // 传递给外部后,由外部接收 this.$el.offsetHeight 的值后重新赋值给本组件 expandRowHeight.val 进行逻辑判断渲染另外两个fixed 的组件的内容高度,形成闭环。
      }
    },

    // 销毁监听
    destroyObserver() {
      if (this.resizeObserver) {
        // 在组件销毁前停止监听
        this.contentEl && this.resizeObserver.unobserve(this.contentEl)
      }
    },
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值