仿照antd-vue实现的自定义table组件,包含以下基本点
1.边框 2.斑马纹 3.全选 4.排序 5.固定表头
一.总体效果
二.基本代码
1.父组件
<!-- Discription: table组件, author: ydj, Date: 2021-01-14 09:36:34 -->
<template>
<div>
<h1>仿antd-vue实现自定义表格</h1>
<span>1.边框</span>
<span>2.斑马纹</span>
<span>3.全选</span>
<span>4.排序</span>
<span>5.固定表头</span>
{{selectedItems}}
<cus-table
:columns="columns"
:data="data"
:height="200"
border
stripe
:selectedItems.sync="selectedItems"
:orderBy.sync="orderBy"
@sort="sort"
></cus-table>
</div>
</template>
<script>
import CusTable from "@/components/Table";
export default {
components: {
CusTable
},
data() {
return {
orderBy: {
name: 'asc',
age: 'asc',
address: 'desc'
},
selectedItems: [],
columns: [
{
title: "Name",
key: "name"
},
{
title: "Age",
key: "age"
},
{
title: "Address",
key: "address"
}
],
data: [
{
id: 1,
name: "张三丰",
age: 18,
address: "北京西城",
date: "2017-11-12"
},
{
id: 2,
name: "韦小宝",
age: 18,
address: "北京东城",
date: "2018-11-12"
},
{
id: 3,
name: "乔峰",
age: 18,
address: "北京海定",
date: "2019-11-12"
},
{
id: 4,
name: "蔡徐坤",
age: 18,
address: "北京朝阳",
date: "2020-11-12"
},
{
id: 5,
name: "肖战",
age: 18,
address: "北京昌平",
date: "2021-01-12"
},
{
id: 6,
name: "贾冰",
age: 18,
address: "北京昌平",
date: "2021-01-12"
},
{
id: 7,
name: "宋小宝",
age: 18,
address: "北京昌平",
date: "2021-01-12"
},
{
id: 8,
name: "齐秦",
age: 18,
address: "北京昌平",
date: "2021-01-12"
}
]
};
},
computed: {},
methods: {
sort(data1,data2){
// 处理排序的逻辑
console.log('value',data1,data2)
}
}
};
</script>
<style lang='scss' scoped>
</style>
2.子组件
<!-- Discription: 自定义table, author: ydj, Date: 2021-01-14 09:56:26 -->
<template>
<div class="table-wrapper" ref="tableWrapper">
<div :style="{'height': Height}" class="scroll-box" ref="scrollBox">
<table class="table" :class="{border,stripe}" ref="table">
<!-- 表头 -->
<thead>
<tr>
<th style="width:50px;">
<input type="checkbox" :checked="checkAll" ref="checkAll" @change="handCheckAll" />
</th>
<th v-for="column in columns" :key="column.key">
<div class="th-head">
{{column.title}}
<span
v-if="column.key in orderBy"
class="th-icon"
@click="handleSort(column)"
>
<i class="iconfont icon-up" :class="{active:orderBy[column.key]==='asc'}"></i>
<i class="iconfont icon-down" :class="{active:orderBy[column.key]==='desc'}"></i>
</span>
</div>
</th>
</tr>
</thead>
<!-- 表体 -->
<tbody>
<tr v-for="row in data" :key="row.id">
<td style="width:50px;">
<input type="checkbox" @change="handleChange(row,$event)" :checked="isChecked(row)" />
</td>
<td v-for="column in columns" :key="column.key">{{row[column.key]}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
export default {
props: {
height: {
type: Number,
default: 240
},
orderBy: {
type: Object,
default: () => {}
},
selectedItems: {
type: Array,
default: () => []
},
columns: {
type: Array,
default: () => []
},
data: {
type: Array,
default: () => []
},
border: {
type: Boolean,
default: false
},
stripe: {
type: Boolean,
default: false
}
},
data() {
return {};
},
mounted() {
// 如果传递了高度,才会固定表头
if (this.height) {
const table = this.$refs.table,
tableWrapper = this.$refs.tableWrapper,
copyTable = this.$refs.table.cloneNode(),
thead = table.children[0];
// 使用appenChild之后之前的表头就没有了
tableWrapper.style.paddingTop =
thead.getBoundingClientRect().height + "px";
copyTable.appendChild(thead);
tableWrapper.appendChild(copyTable);
copyTable.classList.add("fied-header");
}
},
watch: {
// 设置全选按钮的显示,有个indeterminate 属性是关键
selectedItems() {
if (this.selectedItems.length !== this.data.length) {
if (this.selectedItems.length !== 0) {
return (this.$refs.checkAll.indeterminate = true);
}
}
this.$refs.checkAll.indeterminate = false;
}
},
computed: {
checkAll() {
// 判断是否显示全选
return this.data.length === this.selectedItems.length;
},
Height() {
// 设置高度
return this.height + "px";
}
},
methods: {
handleSort(column) {
// 排序的逻辑
let orderBy = JSON.parse(JSON.stringify(this.orderBy));
if (orderBy[column.key] === "asc") {
orderBy[column.key] = "desc";
} else if (orderBy[column.key] === "desc") {
orderBy[column.key] = true;
} else {
orderBy[column.key] = "asc";
}
this.$emit("update:orderBy", orderBy);
// 传统给父组件sort事件,可以用来调用后端接口
this.$emit("sort", column, orderBy);
},
isChecked(row) {
// 全选之后,每一项的选中情况
return this.selectedItems.some(item => item.id === row.id);
},
handCheckAll(e) {
// 全选时,将所有数据传给父组件
this.$emit("update:selectedItems", e.target.checked ? this.data : []);
},
handleChange(row, e) {
// 选中某项,添加到数组,否则从数组中删除
let selectArray = JSON.parse(JSON.stringify(this.selectedItems));
if (e.target.checked) {
selectArray.push(row);
} else {
const id = selectArray.findIndex(item => item.id === row.id);
selectArray.splice(id, 1);
}
this.$emit("update:selectedItems", selectArray);
}
}
};
</script>
<style lang='scss' scoped>
* {
margin: 0;
padding: 0;
}
.table-wrapper {
position: relative;
width: 80%;
margin: 20px auto;
.scroll-box {
overflow-y: scroll;
}
// 固定表头时增加的类名
.fied-header {
position: absolute;
top: 0;
left: 0;
}
table {
width: 100%;
border-collapse: collapse;
border-spacing: 0;
// 设置边框,主要通过选择器的权重
&.border {
border: 1px solid #ccc;
th,
td {
border: 1px solid #ccc;
}
}
th {
background: #ccc;
}
// 设置斑马纹也是同理
&.stripe {
tbody {
tr:nth-child(even) {
background: #ccc;
}
}
}
th,
td {
padding: 5px;
border-bottom: 10px solid #ccc;
text-align: left;
}
.th-head {
display: flex;
align-items: center;
cursor: pointer;
.th-icon {
display: flex;
margin: 0 3px;
flex-direction: column;
.iconfont {
margin: 0;
color: #aaa;
&.active {
color: #000;
}
&.icon-down {
margin-top: -10px;
}
}
}
}
}
}
</style>