【前端】通过vantUI的van-row+van-col来模拟行和列,实现适配移动端的table组件

3,530 阅读4分钟

实现方式:vue + vantui

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第1天,点击查看活动详情

实现效果

image.png

实现步骤

1.表头部分

支持二级表头展示

  • 表格头部一个循环:循环渲染每一列的列名
    <van-row class="fake-table__head">
      <van-col
        v-for="(item, index) in headData"
        :key="index"
        class="col"
        :span="item.span"
      >
        <div class="header-col">
          <span class="title-parent">{{ item.label }}</span>
          <div
            v-if="item.children"
            class="title-children"
          >
            <span
              v-for="(headerChild,inx) in item.children"
              :key="headerChild+inx"
              :style="{width: headerChild.width ?         headerChild.width : '80px'}"
              class="title-child"
            >{{ headerChild.label }}</span>
          </div>
        </div>
      </van-col>
    </van-row>

2.主体部分

  • 表格内容两重循环:外层循环渲染行,内层循环渲染每一行的每一列的内容
  • 需求特殊:需要多个表格主体,故外层需要循环
    <div
      v-for="(unit, index) in bodyData"
      :key="index"
      class="fake-table-day"
    >
      <div class="time-tag">
        {{ unit.rq }}
      </div>
      <van-row
        v-for="(item, idx) in unit.children"
        :key="idx+item"
        class="fake-table__body"
      >
        <template v-for="(colItem, colIndex) in headData">
          <template v-if="colItem.children">
            <template v-for="(colChild,index) in colItem.children">
              <van-col
                v-if="colChild.prop"
                :key="colChild+index"
                class="col"
              >
                {{ item[colChild.prop] ? item[colChild.prop] : '-' }}
              </van-col>
            </template>
          </template>
          <template v-else>
            <van-col
              v-if="colItem.prop"
              :key="colIndex"
              class="col"
              :span="colItem.span"
            >
              {{ item[colItem.prop] || item[colItem.prop1] || item[colItem.prop2] }}
            </van-col>
          </template>
        </template>
      </van-row>
    </div>
    

3.实现固定第一列

实现方式:借鉴el-table,在原有表格上在复制一份dom元素放在最上面,然后隐藏最上面的第二列到最后一列,故可以实现第一列固定的效果

.vant-table{
  -webkit-overflow-scrolling: auto;
  width: 100%;
  overflow-x: hidden;
  position: relative;
  height: 100%;
  .time-tag{
    width: 90px;
    color: #333;
    margin-top: 12px;
    margin-bottom:12px;
    padding: 2px;
    text-align: center;
    font-size: 14px;
    background: #EAEAEA;
    border-radius: 8px;
    margin-left: 12px;
  }
  .fake-table-head-fixed{
    transition: all 0.1;
    position: fixed;
    display: flex;
    top: 136px;
    z-index: 999;
    text-align: center;
    background: #FFFFFF;
    width: 100%;
    box-shadow: 0px 4px 6px 0px rgba(0,0,0,0.08);
    flex-wrap: nowrap;
    .col{
      &:nth-child(1){
        z-index: 999999;
        background: #FFFFFF;
        box-shadow: 0px 0px 12px 1px rgba(0,0,0,0.05)
      }
    }
  }
  .col{
    min-width: 80px;
    flex:0 0 auto;
    white-space: nowrap;
    font-size: 16px;
    color: #333333;
    letter-spacing: 0;
    text-align: center;
    height: 100%;
  }
  .header-col{
    display: flex;
    flex-direction: column;
    padding:16px 0;
    align-items: center;
    justify-content: center;
    .title-parent{
      font-weight: 400px;
      font-size: 16px;
    }
    .title-children{
      display: flex;
      justify-content: space-around;
      .title-child{
        min-width: 80px;
        color: #999;
        font-size: 14px;
        margin-top: 6px;
      }
    }

  }

  .fake-table{
    position: relative;
    .col{
      white-space: nowrap;
      font-size: 16px;
      color: #333333;
      letter-spacing: 0;
    }
    .fake-table__head{
      scroll-behavior: smooth;
      text-align: center;
      background: #FFFFFF;
      width: 100%;
      flex-wrap: nowrap;
      .col{
        &:nth-child(1){
          z-index: 999;
          background: #FFFFFF;
          box-shadow: 0px 0px 12px 1px rgba(0,0,0,0.05)
        }
      }
      .col{
        font-weight: 500;
      }
    }
    .fake-table-day{
      .col{
        visibility:hidden;
        position: relative;
        z-index: 999;
        &:nth-child(1){
          visibility:visible;
          background: #FFFFFF;
          box-shadow: 0px 0px 12px 1px rgba(0,0,0,0.05)
        }
      }
    }
    .fake-table__body{
      height:54px;
      line-height: 54px;
      text-align: center;
      background: #FFFFFF;
      flex-wrap: nowrap;
      &:nth-child(n):not(:last-child){
        border-bottom: 1px solid #DEDEDE;
      }
      .col{
        font-weight: 500;
      }
    }
  }
  .table-fixed{
    position:absolute;
    top: 0;
    left: 0;
    width: 100%;
    overflow: scroll;
    .time-tag{
      visibility:hidden;
    }
    .fake-table__head{
      scroll-behavior: smooth;
      position:fixed;
      text-align: center;
      flex-wrap: nowrap;
      position: sticky;
      top: 0;
      .col{
        visibility:hidden;
      }

      .col{
        font-weight: 500;
      }
    }
    .fake-table-day{
      .fake-table__body{
        .col{
          &:nth-child(1){
            visibility:hidden;
          }
        }
      }
    }
    .fake-table__body{
      height:54px;
      line-height: 54px;
      text-align: center;
      flex-wrap: nowrap;
      &:nth-child(n):not(:last-child){
        border-bottom: 1px solid #DEDEDE;
      }
      .col{
        font-weight: 500;
      }
    }
  }
}

4.实现表头实现吸顶效果

利用js动态计算表头与窗口最上面的高度,动态生成表头,实现吸顶效果

function createdTableHeader () {
  const [...el_body] = document.querySelectorAll('.table-fixed .fake-table__body ')
  if (el_body[0].getBoundingClientRect().top < 200 && !document.querySelector('.fake-table-head-fixed')) {
    const el_wrapper = document.querySelector('.fake-table')
    const fake_table__head = document.querySelector('.table-fixed .fake-table__head')
    const el_fixed_header_top = fake_table__head.cloneNode(true)
    el_fixed_header_top.className = 'fake-table-head-fixed'
    document.querySelector('.vant-table').appendChild(el_fixed_header_top)
  } else if (el_body[0].getBoundingClientRect().top > 200 && document.querySelector('.fake-table-head-fixed')) {
    // 拿到父节点:
    const fixed_header = document.querySelector('.fake-table-head-fixed')
    const parent = fixed_header.parentElement
    // 删除:
    const removed = parent.removeChild(fixed_header)
  }
}
function headerTouched () {
  let el = []
  const [...el_body] = document.querySelectorAll('.table-fixed .fake-table__body ')
  if (el_body[0].getBoundingClientRect().top > 200) {
    [...el] = document.querySelectorAll('.fake-table .fake-table__head > .col:not(:first-child)')
  } else {
    [...el] = document.querySelectorAll('.vant-table .fake-table-head-fixed > .col:not(:first-child)')
  }

  const viewLeft = el_body[0].getBoundingClientRect().left
  el.forEach(unit => {
    // unit.style.position = 'relative'
    // unit.style.left = `${viewLeft}px`
    unit.style.transition = 'all .1s'
    unit.style.transform = `translateX(${viewLeft}px)`
  })
}

5.表格主体部分滑动时吸顶的表头也随之滑动

setHeaderStyle () {
      const [...el_header_col] = document.querySelectorAll('.fake-table .fake-table__head .col:nth-child(n)')
      const [...el_body_col] = document.querySelectorAll('.fake-table .fake-table-day .fake-table__body')
      // el_header_col.forEach(item => {
      //   const [...child] = el_body_col[0].children
      //   console.log(child)
      //   child.forEach(item => {
      //     item.style.width = item.offsetWidth + 'px'
      //   })
      // })
      const [...el_headerHd] = el_header_col.map(itemHd => {
        console.log(itemHd.children[0].children[1].children)
        return [...itemHd.children[0].children[1].children]
      })
      el_headerHd.flat().forEach((item, inx) => {
        el_body_col.forEach(unit => {
          const [...children] = unit.children
          children.forEach((child, index) => {
            if (inx === index) {
              child.style.width = item.style.width
            }
          })
        })
      })
    },
    fixedTable () {
      const THIS = this
      const el = document.querySelector('.fake-table')
      console.log(el.offsetHeight)
      const el_fixed = el.cloneNode(true)
      el_fixed.className = 'table-fixed'
      document.querySelector('.vant-table').appendChild(el_fixed)
      el.style.height = el_fixed.offsetHeight + 'px'
      if (!this.isFixedColumn) {
        const [...col_el] = document.querySelectorAll('.vant-table .fake-table .fake-table-day .col')
        col_el.forEach(item => {
          item.style.visibility = 'visible'
        })
      }
      if (this.isFixedHeader) {
        el_fixed.ontouchmove = function (e) {
          utils.createdTableHeader()
          utils.headerTouched()
        }
        el_fixed.ontouchend = function (e) {
        // 动态创建表头
          utils.createdTableHeader()
          utils.headerTouched()
        }
      }
    },