在若依框架下,el-table顶部吸附后,左侧菜单收回弹出后,表头重新计算

前言

加入项目都三个月了,我才发现了这个问题,在若依框架下,左侧菜单可以收回或者弹出,这时候右侧的el-table的表头就会错位,然后看了一下代码,又从网上搜索了一下相关的代码,这个是一个表格顶部吸附的功能,网上有完整的代码,是这位前辈的:http://t.csdnimg.cn/kShSW,仔细看了一遍,我们项目中的代码和这篇文章中的是一样的,所以我想要的这个功能还需要自己优化了

思路

每次在左侧菜单弹出或者收回后,都需要重新判断一下设置一下表头宽度,而且需要判断是否需要显示滚动条,在原来的代码中,是有监听左侧弹出收回的事件的,所以只需要添加一个方法就行了

代码

  setHeader(data) {
    const { key, el, binding, className, className1, uid, vnode } = data;
	const headerWrapperDom = el.getElementsByClassName('el-table__header-wrapper')[0];
    setTimeout(() => {
      const bodyWrapperDom = el.getElementsByClassName('el-table__body-wrapper')[0];
      const width = getComputedStyle(bodyWrapperDom).width;
      headerWrapperDom.style.width = width;
      const tableFixedDom = el.getElementsByClassName(className);
      if (tableFixedDom.length) {
        const fixedDom = tableFixedDom[0];
        const arr = fixedDom.getElementsByClassName(className1); //
        const headW = getComputedStyle(fixedDom).width;
        if (arr.length) {
          // headDom右侧fixed表头
          const headDom = arr[0];
          const height = getComputedStyle(headDom).height;
          const inHeadDom = arr[0].getElementsByClassName('el-table__header')[0];
          headDom.style.width = headW;
          inHeadDom.style.width = headW;
          inHeadDom.style.position = 'absolute';
          inHeadDom.style.top = '0';
          inHeadDom.style.right = '0';
          headDom.style.height = height;
        }
      }
			fixTabScrollbar(el, null)
    }, 350);
  },

因为菜单弹出的动画是3s,所以我在3.5s后去获取宽度,没用nextTick,因为这个弹出不会导致dom更新的,也或许我还没有找到适合的方法,

源码

import Vue from 'vue'
const tableStickyObj = {}

import { getToken, getOther,getOutToken } from '@/utils/auth'

const __STICKY_TABLE = {
  // 给固定头设置样式
  doFix (dom, top, data) {
    const { uid, domType, isExist } = data
    const uObj = tableStickyObj[uid]
    const curObj = uObj[domType]
    const headerRect = tableStickyObj[uid].headerRect

    if (!isExist) {
      dom.style.position = 'fixed'
      dom.style.zIndex = '1999'
      dom.style.top = top + 'px'
    }
    uObj.tableWrapDom.style.marginTop = headerRect.height + 'px'

    if (domType === 'fixed') {
      dom.style.left = curObj.left + 'px'
    } else if (domType === 'fixedRight') {
      dom.style.left = curObj.left + 1 + 'px'
    }
  },
  // 给固定头取消样式
  removeFix (dom, data) {
    const { uid, domType } = data
    // dom.parentNode.style.paddingTop = 0
    const uObj = tableStickyObj[uid]
    const curObj = uObj[domType]
    dom.style.position = 'static'
    dom.style.top = '0'
    dom.style.zIndex = '0'

    uObj.tableWrapDom.style.marginTop = '0'

    if (domType === 'fixed') {
      curObj.dom.style.top = '0'
    } else if (domType === 'fixedRight') {
      curObj.dom.style.top = '0'
    }
  },
  // 给固定头添加class
  addClass (dom, fixtop, data) {
    fixtop = fixtop || 0
    const isExist = dom.classList.contains('fixed')
    data.isExist = !!isExist
    if (!isExist) { // 若有,就不再添加
      dom.classList.add('fixed')
    }
    this.doFix(dom, fixtop, data)
  },
  // 给固定头移除class
  removeClass (dom, data) {
    if (dom.classList.contains('fixed')) {
      dom.classList.remove('fixed')
      this.removeFix(dom, data)
    }
  },

  /**
   * 计算某元素距离相对父元素的top距离
   * @param {Nodes} e 某元素
   * @param {String} domId 父元素id
   * @param {Boolean} isParent 是否父元素
   * @returns {Number}
   */
  getPosY (el, domId) {
    let offset = 0
    const pDom = el.offsetParent
    if (pDom != null && '#' + el.id !== domId) {
      offset = el.offsetTop
      offset += this.getPosY(pDom, domId)
    }
    return offset
  },

  // 获取元素的横坐标(相对于窗口)
  getPosX (e) {
    var offset = e.offsetLeft
    if (e.offsetParent != null) offset += this.getPosX(e.offsetParent)
    return offset
  },
  fixHead (scrollDom, el, uid, binding) {
    this.fixHead1(this, { scrollDom, el, uid, binding })
  },
  // 具体判断是否固定头的主函数
  fixHead1: sticky_throttle((_this, { scrollDom, el, uid, binding }) => {
    let top = ''
    /** 本系统使用 传入的高度 其他系统引入页面 不需要设置高度 */
    if (getToken()&&getToken()!='undefined') {
        top = binding.value.top// 让每个请求携带自定义token 请根据实际情况自行修改
    }else if(getOutToken()){
    /** 其他系统内部打开使用token */
        top = 0
    }
    // const top = binding.value.top
    /**
     * myTop 当前元素距离滚动父容器的高度,
     * fixtop 当前元素需要设置的绝对定位的高度
     * parentHeight 滚动父容器的高度
     */
    // 表头DOM节点
    const headerWrapDom = el.children[1] // el-table__header-wrapper

    const headerTop = tableStickyObj[uid].headerRect.top
    const scrollTop = scrollDom.scrollTop

    const fixedHeadDom = tableStickyObj[uid].fixed.headerDom
    const fixedHeadRightDom = tableStickyObj[uid].fixedRight.headerDom
    // console.log(scrollTop,headerTop,headerTop-104, '滚动' )

    /** 如果出现遮罩层 */
    // let dialog =  document.getElementsByClassName('v-modal')[0]
    // if(dialog){ return false }


    if (scrollTop >= headerTop-104&&headerTop-104>0) {
      const fixtop = top + scrollDom.getBoundingClientRect().top
      // 如果表头滚动到 父容器顶部了。fixed定位
      _this.addClass(headerWrapDom, fixtop, { domType: 'mainBody', uid })
      fixedHeadDom && _this.addClass(fixedHeadDom, fixtop, { domType: 'fixed', uid })
      fixedHeadRightDom && _this.addClass(fixedHeadRightDom, fixtop, { domType: 'fixedRight', uid })
    } else {
      // 如果表格向上滚动 又滚动到父容器里。取消fixed定位
      _this.removeClass(headerWrapDom, { domType: 'mainBody', uid })
      fixedHeadDom && _this.removeClass(fixedHeadDom, { domType: 'fixed', uid })
      fixedHeadRightDom && _this.removeClass(fixedHeadRightDom, { domType: 'fixedRight', uid })
    }
  }, 0, { eventType: 'fixHead111' }),
  //
  setHeadWidth (data) {
    this.setHeadWidth1(this, data)
  },
  // 设置头部固定时表头外容器的宽度写死为表格body的宽度
  setHeadWidth1: sticky_debounce((_this, data) => {
    const { el, uid, binding, eventType } = data
    const { scrollDom } = tableStickyObj[uid]
    const headerWrapDom = el.children[1] // el-table__header-wrapper
    const headerH = headerWrapDom.offsetHeight
    const distTop = _this.getPosY(headerWrapDom, binding.value.parent)
    const scrollDistTop = _this.getPosY(scrollDom) // 滚动条距离顶部的距离

    // tableStickyObj[uid].headerRect.top = distTop + headerH - scrollDistTop / 3 // 表头距离顶部的距离 - 表头自身高度 - 滚动条距离顶部的距离
    tableStickyObj[uid].headerRect.top = distTop + headerH // 表头距离顶部的距离 - 表头自身高度 - 滚动条距离顶部的距离
    tableStickyObj[uid].headerRect.height = headerH
    // tableStickyObj[uid].headerRect.width = tableW

    // debugger
    // fixed left/right header
    // 确保每次刷新,只获取一次
    // tableStickyObj[uid].fixed.dom = ''
    // _this.initFixedWrap({ el, uid, eventType, key: 'fixed', className: 'el-table__fixed', className1: 'el-table__fixed-header-wrapper' })
    _this.initFixedWrap({ el, uid, eventType, key: 'fixedRight', className: 'el-table__fixed-right', className1: 'el-table__fixed-header-wrapper' })

    // 获取到当前表格个表格body的宽度
    const bodyWrapperDom = el.getElementsByClassName('el-table__body-wrapper')[0]
    const width = getComputedStyle(bodyWrapperDom).width
    // 给表格设置宽度。这里默认一个页面中的多个表格宽度是一样的。所以直接遍历赋值,也可以根据自己需求,单独设置
    const headerWrapperDom = el.getElementsByClassName('el-table__header-wrapper')
		for (let i = 0; i < headerWrapperDom.length; i++) {
			headerWrapperDom[i].style.width = width
		}
    _this.fixHead(scrollDom, el, uid, binding) // 判断顶部是否已吸顶的一个过程
  }),

  initFixedWrap (data) {
    const { key, el, eventType, className, className1, uid } = data
    // 确保每次刷新,只获取一次
    if (eventType === 'resize' || !tableStickyObj[uid][key].dom) {
      const tableFixedDom = el.getElementsByClassName(className)
      if (tableFixedDom.length) {
        const fixedDom = tableFixedDom[0]
        const arr = fixedDom.getElementsByClassName(className1) //
        const headW = getComputedStyle(fixedDom).width

        tableStickyObj[uid][key].dom = fixedDom
        if (arr.length) {
          const distLeft = this.getPosX(fixedDom) // 距离窗口左侧的距离
          const headDom = arr[0]
          headDom.style.width = headW
          tableStickyObj[uid][key].left = distLeft // 距离窗口左边像素

          if (key === 'fixedRight') { // right-fixed 的特别之处
            headDom.classList.add('scroll-bar-h0')
            headDom.style.overflow = 'auto'
            headDom.scrollLeft = headDom.scrollWidth
            headDom.style.overflow = 'hidden' // 设置了滚动到最后,设置不可滚动
          } else {
            headDom.style.overflow = 'hidden'
          }

          tableStickyObj[uid][key].headerDom = headDom // 取第一个
        }
      }
    }
  },

  setHeader(data) {
    const { key, el, binding, className, className1, uid, vnode } = data;
		const headerWrapperDom = el.getElementsByClassName('el-table__header-wrapper')[0];
    setTimeout(() => {
      const bodyWrapperDom = el.getElementsByClassName('el-table__body-wrapper')[0];
      const width = getComputedStyle(bodyWrapperDom).width;
      headerWrapperDom.style.width = width;
      const tableFixedDom = el.getElementsByClassName(className);
      if (tableFixedDom.length) {
        const fixedDom = tableFixedDom[0];
        const arr = fixedDom.getElementsByClassName(className1); //
        const headW = getComputedStyle(fixedDom).width;
        if (arr.length) {
          // headDom右侧fixed表头
          const headDom = arr[0];
          const height = getComputedStyle(headDom).height;
          const inHeadDom = arr[0].getElementsByClassName('el-table__header')[0];
          headDom.style.width = headW;
          inHeadDom.style.width = headW;
          inHeadDom.style.position = 'absolute';
          inHeadDom.style.top = '0';
          inHeadDom.style.right = '0';
          headDom.style.height = height;
        }
      }
			fixTabScrollbar(el, null)
    }, 350);
  },
	
  // 监听父级的某些变量(父级一定要有才能被监听到)
  watched({ el, binding, vnode, uid }) {
    // 监听tab覆盖,是否需要更新sticky的设置(解决:tab切换后覆盖el-table,然后调整了窗口大小,没有刷新页面,切回editTable,会出现表头直接浮起的bug)
    // isUpdateTableSticky: 更新el-table浮起的设置 0.不更新 大于0才触发更新
    vnode.context.$watch('isUpdateTableSticky', val => {
			this.setHeadWidth({ el, uid, binding, eventType: 'resize' });
    });
    // 监听左侧导航栏是否折叠
    vnode.context.$watch('isNavFold', val => {
			this.setHeader({
				el,
				uid,
				binding,
				className: 'el-table__fixed-right',
				className1: 'el-table__fixed-header-wrapper',
				vnode
			});
    });
  }
}

/**
 * 节流函数: 指定时间间隔内只会执行一次任务
 * @param {function} fn
 * @param {Number} interval
 */
function sticky_throttle (fn, interval = 100) {
  let canRun = true
  return function () {
    if (!canRun) return
    canRun = false
    setTimeout(() => {
      fn.apply(this, arguments)
      canRun = true
    }, interval)
  }
}

//app滚动->定位table的水平滚动条
function fixTabScrollbar(tabEle, appEle) {
  var tabEle = tabEle || document.getElementsByClassName('el-table')[0]; // table 元素
  var appEle = appEle || document.getElementById('app'); // 滚动条所在容器
  if (!(tabEle && appEle)) return;
  let tbody = tabEle.getElementsByClassName('el-table__body-wrapper')[0];
  let tbodyWidth = getComputedStyle(tbody).width.split('p')[0] * 1 + 1;
  let tableHeader = tabEle.getElementsByClassName('el-table__header-wrapper')[0];
  let tableBodyInner = tbody.getElementsByClassName('el-table__body')[0];
  let tbodyContentWidth = tableBodyInner.style.width.split('p')[0] * 1;
  tbody.style.overflowX = 'hidden';
  if (tbodyContentWidth > tbodyWidth) {
    tableHeader.style.overflowX = 'scroll';
    tableHeader.onscroll = function() {
      let target = event.target;
      let scrollLeft = target.scrollLeft;
      tbody.scrollLeft = scrollLeft;
    };
  } else {
    tableHeader.style.overflowX = 'hidden';
  }
}

/**
 * 防抖: 指定时间间隔内只会执行一次任务,并且该时间段内再触发,都会重新计算时间。(函数防抖的非立即执行版)
 * 在频繁触发某些事件,导致大量的计算或者非常消耗资源的操作的时候,防抖可以强制在一段连续的时间内只执行一次
 * */
function sticky_debounce (fn, delay, config) {
  const _delay = delay || 50
  config = config || {}
  // const _this = this // 该this指向common.js
  return function () {
    const th = this // 该this指向实例
    const args = arguments
    // debounceNum++
    // let str = `, label: ${th && th.listItem && th.listItem.label}`
    if (fn.timer) {
      clearTimeout(fn.timer)
      fn.timer = null
    } else {
      // fn.debounceNum = debounceNum
    }
    fn.timer = setTimeout(function () {
      // str = `, label: ${th && th.listItem && th.listItem.label}`
      fn.timer = null
      fn.apply(th, args)
    }, _delay)
  }
}

// 全局注册 自定义事件
Vue.directive('sticky', {
  // 当被绑定的元素插入到 DOM 中时……
  inserted (el, binding, vnode) {
    // 获取当前vueComponent的ID。作为存放各种监听事件的key
    const uid = vnode.componentInstance._uid
    // 获取当前滚动的容器是什么。如果是document滚动。则可默认不传入parent参数
    const scrollDom = document.querySelector(binding.value.parent) || document.body // TODO:得考虑没有 binding.value.parent 的情况,重新登录直接进到内页会出现
    if (!tableStickyObj[uid]) {
      tableStickyObj[uid] = {
        uid,
        fixFunObj: {}, // 用于存放滚动容器的监听scroll事件
        setWidthFunObj: {}, // 用于存放页面resize后重新计算head宽度事件
        autoMoveFunObj: {}, // 用户存放如果是DOM元素内局部滚动时,document滚动时,fix布局的表头也需要跟着document一起向上滚动
        scrollDomRect: {},
        headerRect: { top: 0, left: 0 },
        fixed: {}, // 表格左浮动
        fixedRight: {}, // 表格右浮动
        // binding,
        // el,
        tableWrapDom: el.getElementsByClassName('el-table__body-wrapper')[0],
        scrollDom
      }
    }

    __STICKY_TABLE.watched({ el, binding, vnode, uid }) // 监听父级的某些变量

    // 当window resize时 重新计算设置表头宽度,并将监听函数存入 监听函数对象中,方便移除监听事件
    window.addEventListener('resize', (tableStickyObj[uid].setWidthFunObj = () => {
      __STICKY_TABLE.setHeadWidth({ el, uid, binding, eventType: 'resize' }) // 首先设置表头宽度
    })
    )

    // 给滚动容器加scroll监听事件。并将监听函数存入 监听函数对象中,方便移除监听事件
    scrollDom.addEventListener('scroll', (tableStickyObj[uid].fixFunObj = (e) => {
      __STICKY_TABLE.fixHead(scrollDom, el, uid, binding)
    }))
  },
  // component 更新后。重新计算表头宽度
  componentUpdated (el, binding, vnode) {
    const uid = vnode.componentInstance._uid
    __STICKY_TABLE.setHeadWidth({ el, uid, binding, eventType: 'componentUpdated' })
    /** table 表头 出现滚动条 */
    fixTabScrollbar(el,null)
  },
  // 节点取消绑定时 移除各项监听事件。
  unbind (el, binding, vnode) {
    const uid = vnode.componentInstance._uid
    window.removeEventListener('resize', tableStickyObj[uid].setWidthFunObj)
    const scrollDom = document.querySelector(binding.value.parent) || document
    scrollDom.removeEventListener('scroll', tableStickyObj[uid].fixFunObj)
    if (binding.value.parent) {
      document.removeEventListener('scroll', tableStickyObj[uid].autoMoveFunObj)
    }
  }
})

提醒

那个‘isNavFold’,就是页面的菜单弹出与收回的变量,不监听可没效果,

最后的话

刚做前端没见年,可能代码还有优化的空间,欢迎交流,希望可以帮到有需要的人。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值