一个由面试题引起的案例分享
需求如下:
在不使用任何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存入数组,通过遍历来记录和读取该项选中与否