原生table表头固定写法和checkbox样式重写

一个由面试题引起的案例分享

需求如下:

 

 在不使用任何UI框架的前提下完成上图效果

提供数据源的函数如下:

function generate () {
  let data = [['Index', `Data1`, `Data2`, `Data3`]]

  for (let i = 0; i < 15; i++) {
    data.push([
      i+1, 
      parseInt(Date.now() + Math.random() * 10000000).toString(16), 
      parseInt(Date.now() + Math.random() * 10000000).toString(16),
      parseInt(Date.now() + Math.random() * 10000000).toString(16)
    ])
  }
  return data
}

细节要求:

1. 表头固定:当表格高度超出外部容器高度时,表格内部出现垂直滚动条,表头始终处于顶部可见

2. 第一列为多选列,支持单行勾选和全选。当部分行选中时,表头的勾选状态为“不确定”(即勾选框填充为横线),参考描述中的视图

 

代码实现如下(vue):

页面代码:

<template>
  <div class="container">
    <table cellspacing="0">
      <thead>
      <tr>
        <th class="w2">
          <span class="checkbox-span"
                :class="[isAllChecked ? 'is-checked' : (isIndeterminate ? 'is-indeterminate' : '')]">
            <span class="checkbox-inner" @click="getAllChecked()"></span>
            <input type="checkbox" class="checkbox-original" ref="allCheckedRef"/>
          </span>
        </th>
        <th v-for="item in allData[0]" class="tl">{{item}}</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="items in contentData">
        <td class="w2">
          <input type="checkbox" :value="items[0]" v-model="checkedId"/></td>
        <td v-for="item in items" class="tl">
          {{item}}
        </td>
      </tr>
      </tbody>
    </table>
  </div>
</template>

JS代码:

<script>
  export default {
    name: "Home",
    data() {
      return {
        // 所有的源数据(包括表头和内容)
        allData: [],
        // 内容数据
        contentData: [],
        // 是否全部选中
        isAllChecked: false,
        // 是否部分选中
        isIndeterminate: false,
        // 所有的id
        ids: [],
        // 选中的id
        checkedId: [],
      }
    },
    mounted() {
      // 获取源数据
      this.generate();
      // 获取所有的ids
      this.getIds();
    },
    watch: {
      // 监听选中的id
      checkedId: {
        handler: function (val, oldVal) {
          // 如果选中的id和全部id长度相等,表示全选中
          if (val.length === this.ids.length) {
            // 设置全选状态为true
            this.isAllChecked = true;
            // 设置部分选中状态为false
            this.$refs.allCheckedRef.indeterminate = false;
            this.isIndeterminate = false;
            // 表示部分选中
          } else if (val.length != 0 && val.length != this.ids.length) {
            this.isAllChecked = false;
            this.$refs.allCheckedRef.indeterminate = true;
            this.isIndeterminate = true;
          } else {
            // 表示一个都没有选中
            this.isAllChecked = false;
            this.$refs.allCheckedRef.indeterminate = false;
            this.isIndeterminate = false;
          }

          console.log("提交到后台的id");
          console.log(val);
        },
        deep: true
      }
    },
    methods: {
      // 点击全选按钮事件
      getAllChecked() {
        this.isAllChecked = !this.isAllChecked;
        // 全选了
        if (this.isAllChecked) {
          this.checkedId = [];
          // 将选中的id设置为所有的id,提交到后台
          for (let i = 0; i < this.ids.length; i++) {
            this.checkedId.push(this.ids[i]);
          }
        // 没有全选
        } else {
          this.checkedId = [];
        }
      },
      // 获取所有的ids
      getIds() {
        if (this.allData && this.allData.length > 0) {
          if (this.contentData && this.contentData.length > 0) {
            for (let i = 0; i < this.contentData.length; i++) {
              this.ids.push(this.contentData[i][0]);
            }
          }
        }
      },
      // 获取源数据
      generate() {
        let data = [['Index', `Data1`, `Data2`, `Data3`]]
        for (let i = 0; i < 15; i++) {
          data.push([
            i + 1,
            parseInt(Date.now() + Math.random() * 10000000).toString(16),
            parseInt(Date.now() + Math.random() * 10000000).toString(16),
            parseInt(Date.now() + Math.random() * 10000000).toString(16)
          ])
        }
        this.allData = data;
        // 获取内容数据
        this.contentData = this.allData.slice(1);
      }
    },

  }
</script>

样式代码:

<style scoped lang="scss">

  table {
    margin: 3rem auto;
    width: 20rem;
    overflow: scroll;
  }
  /***************重写 checkbox 样式 start*****************/
  .checkbox-span {
    white-space: nowrap;
    cursor: pointer;
    outline: none;
    display: inline-block;
    line-height: 1;
    position: relative;
    vertical-align: middle;
  }



  .checkbox-inner {
    display: inline-block;
    position: relative;
    border: 1px solid rgb(44, 62, 80);
    border-radius: 2px;
    box-sizing: border-box;
    width: 14px;
    height: 14px;
    background-color: #fff;
    z-index: 1;
    transition: border-color .25s cubic-bezier(.71, -.46, .29, 1.46), background-color .25s cubic-bezier(.71, -.46, .29, 1.46);

    /*鼠标悬浮样式*/
    &:hover {
      border-color: #409eff;
    }

    &:after {
      box-sizing: content-box;
      content: "";
      border: 1px solid #fff;
      border-left: 0;
      border-top: 0;
      height: 7px;
      left: 4px;
      position: absolute;
      top: 1px;
      transform: rotate(
          45deg
      ) scaleY(0);
      width: 3px;
      transition: transform .15s ease-in .05s;
      transform-origin: center;
    }

  }

  /*默认checkbox样式*/
  .checkbox-original {
    opacity: 0;
    outline: none;
    position: absolute;
    margin: 0;
    width: 0;
    height: 0;
    z-index: -1;
  }

  /*部分选中*/
  .checkbox-span.is-indeterminate .checkbox-inner {
    background-color: #409eff;
    border-color: #409eff;

    &:before {
      content: "";
      position: absolute;
      display: block;
      background-color: #fff;
      height: 2px;
      transform: scale(.5);
      left: 0;
      right: 0;
      top: 5px;
    }
  }

  /*全部选中*/
  .checkbox-span.is-checked .checkbox-inner {
    background-color: #409eff;
    border-color: #409eff;

    &:after {
      transform: rotate(
          45deg
      ) scaleY(1);
    }
  }

  /***************重写 checkbox 样式 end*****************/

  th, td {
    border: 1px solid rgb(225, 225, 225);
    min-width: 10rem;
    height: 0.5rem;
    padding: 0.3rem;
  }

  thead {
    display: block;
  }

  tbody {
    display: block;
    height: 23rem;
    overflow-y: scroll;
  }

  .tl {
    text-align: left;
  }
  .w2 {
    min-width: 2rem;
  }
  th, tr:nth-child(even){
    background-color: rgb(249,249,249);
  }

  /********** 设置滚动条的样式 start************ */
  ::-webkit-scrollbar {
    width: 5px;
  }

  /* 滚动槽 */
  ::-webkit-scrollbar-track {
    border-radius: 10px;
  }

  /* 滚动条滑块 */
  ::-webkit-scrollbar-thumb {
    border-radius: 10px;
    background: rgba(0, 0, 0, 0.1);
  }

  /********** 设置滚动条的样式 end ************ */
</style>

该demo是在vue环境中写的,使用的时候需要vue环境,样式使用了scss编译器

主要参考价值在于:

1.原生table实现表头固定,内容出现滚动条

2.重写checkbox样式,使最上方checkbox在部分选中是展示蓝色背景、白色横线样式效果

3.因为时间有限,没有改写所有的checkbox,只改写了最上方的一个

4.将所有的ID和选中的ID存入数组,通过遍历来记录和读取该项选中与否

 

posted @ 2021-06-17 16:51  栗子米  阅读(747)  评论(0编辑  收藏  举报