提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、实现基本table的封装
- 二、如何食用
- 总结
前言
最近一个技术考核要实现 使用vue不依赖任何UI实现 封装一个公共的
table组件,基本的table功能,表头,行,列功能,api用法参看elementUI/antUI,实现固定表头和列实现按照列排序,实现合并单元格,花了我三天时间,也在网上看了很多资料,废话不多少,看手法
一、实现基本table的封装
先看文件结构
index就是我们的主入口 接下来上index.vue的代码
<template>
<!-- 把tableData,表格数据传入table组件, -->
<!-- -->
<my-table
:model="tableData"
:prop="label"
:fixed="'right'"
:merge="mergeData"
>
<!--:height="200"
sortable:是否排序,点击标题内的button触发
table-cluomn:MyTableColumn组件传来的生成的列的顺序
prop:每个格子的属性名
label:每个格子的中文标题
width:给每一个表头设置宽度
height:是否传递高度 传递高度代表固定表头
fixed:是否固定列
-->
<!-- <my-table-column
:sortable="false"
@table-cluomn="cluomn($event)"
:prop="'big'"
:fixed="'top'"
:label="'大小'"
></my-table-column>
<my-table-column
:sortable="true"
@table-cluomn="cluomn($event)"
:prop="'name'"
:label="'姓名'"
></my-table-column>
<my-table-column
:sortable="true"
@table-cluomn="cluomn($event)"
:prop="'eat'"
:label="'食物'"
></my-table-column>
<my-table-column
:sortable="true"
@table-cluomn="cluomn($event)"
:prop="'date'"
:label="'日期'"
></my-table-column>
<my-table-column
:sortable="true"
@table-cluomn="cluomn($event)"
:prop="'phoneNum'"
:label="'电话'"
></my-table-column>
<my-table-column
:sortable="true"
@table-cluomn="cluomn($event)"
:prop="'hobbit'"
:label="'兴趣'"
></my-table-column>
<my-table-column
:sortable="true"
@table-cluomn="cluomn($event)"
:prop="'address'"
:label="'地址'"
></my-table-column> -->
<!-- 进行精简化 -->
<my-table-column
v-for="(item, index) in tableHeader"
:key="index"
:sortable="true"
@table-cluomn="cluomn($event)"
:prop="item.property"
:label="item.name"
:fixed="'rightTop'"
></my-table-column>
<!--如果传递fixed 则为固定顶部行 -->
</my-table>
</template>
<script>
// 导入组件
import MyTable from "./MyTable.vue";
import MyTableColumn from "@/components/MyTable/MyTableColumn.vue";
export default {
components: { MyTable, MyTableColumn },
data() {
return {
// 动态获取表格 标题的顺序 big,address,name,date
label: [],
mergeData: ["hobbit", "eat"], //合并单元格
tableHeader: [
{ name: "大小", property: "big" },
{ name: "姓名", property: "name" },
{ name: "食物", property: "eat" },
{ name: "日期", property: "date" },
{ name: "电话", property: "phoneNum" },
{ name: "兴趣", property: "hobbit" },
{ name: "地址", property: "address" }
], //表头对应字段
// 表格数据
tableData: [
{
big: 1,
date: "2016-05-02",
name: "王小虎",
address: "上海市普陀区金沙江路 1517 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 2,
date: "2016-05-04",
name: "王小虎",
address: "上海市普陀区金沙江路 1518 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 3,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1519 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 4,
date: "2016-05-03",
name: "王小虎",
address: "上海市普陀区金沙江路 1520 弄"
},
{
big: 5,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1521 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 6,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1522 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 7,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1523 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 8,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1524 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 9,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1525 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 10,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1526 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 11,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1527 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 12,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1528 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 13,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1529 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 14,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1530 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 15,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1531 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 16,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1532 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 17,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1533 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 18,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1534 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 19,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1535 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 20,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1536 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 21,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1537 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
},
{
big: 22,
date: "2016-04-05",
name: "王小虎",
address: "上海市普陀区金沙江路 1538 弄",
eat: "面条",
hobbit: "唱跳",
phoneNum: "13133996644"
}
]
};
},
mounted() {
this.merge();
},
methods: {
// 通过MyTableColumn组件的this.$emit() 把每一个列的标题传过来,不然生成列的顺序会是data 里面tableData的顺序
cluomn(e) {
// 每次生成一个列的标题就push一个标题到label
this.label.push(e);
},
// 合并单元格
merge() {
let tdArr = [];
this.tableData.forEach((item, index) => {
for (let i in item) {
}
// let key = Object.key(item);
});
}
}
};
</script>
下面的methods方法可以先不看 这里面是后面介绍的方法
接下来是我们引用的 myTable 文件
<template>
<div class="table">
<!-- 表格整体组件 -->
<table border="1" width="130%" class="table-body">
<thead>
<tr :class="fixed === 'top' ? 'table-head' : ''">
<!-- 标题行插槽 用来接收 MyTableColumn组件 -->
<!-- 进行固定表头 -->
<slot></slot>
</tr>
</thead>
<tbody>
<!-- 每一行的组件,根据index过来的数组生成行内数据 -->
<!--
model:传入每一个数组元素
prop:标题的顺序,用来传给下一级组件,来实现标题和列的对应
-->
<my-line
v-for="(item, index) in model"
:model="item"
:prop="prop"
:fixed="fixed"
:key="index"
:merge="merge"
></my-line>
</tbody>
</table>
</div>
</template>
<script>
import MyLine from "./MyLine.vue";
export default {
components: { MyLine },
props: {
// tableData
model: {
type: Array
},
// 标题顺序
prop: {
type: Array
},
height: {
type: Number
},
fixed: {
type: String
},
merge: {
type: Array
}
},
data() {
return {
listData: this.model
};
},
watch: {
//! 在watch中使用箭头函数会导致this访问不到,需改成function
listData: function(newVal, oldVal) {
this.combineCell(this.listData);
immediate: true;
deep: true;
}
},
methods: {
// 合并单元格
// 1.找到相同的数据
// 2.针对相同的数据并且合并
/** 数据处理-合并单元格 */
/** list: 后台传回的数据 */
combineCell(list) {
// 获取数据中的字段,也就是table中的column,只需要取其中一条记录的就可以了
// 定义数据list的index
for (let field in list[0]) {
let k = 0;
let i = 0;
while (k < list.length) {
// 增加字段-用于统计有多少重复值
list[k][field + "span"] = 1;
// 增加字段-用于控制显示与隐藏
list[k][field + "dis"] = "";
for (i = k + 1; i <= list.length - 1; i++) {
// 判断第k条数据的field字段,与下一条是否重复
if (list[k][field] === list[i][field] && list[k][field] !== "") {
// 如果重复,第k条数据的字段统计+1
list[k][field + "span"]++;
// 设置为显示
list[k][field + "dis"] = "";
// 重复的记录,则设置为1,表示不跨行
list[i][field + "span"] = 1;
// 并且该字段设置为隐藏
list[i][field + "dis"] = "none";
} else {
break;
}
}
// 跳转到第i条数据的索引
k = i;
}
}
return list;
}
}
};
</script>
可以看到我们用了slot进行 插槽 这个插槽里的内容就是 MyLine.vue
下面贴上 MyLine 文件的代码 MyLine文件主要的作用是做成行 也就是表格
<template>
<tr class="body-row fixed-left">
<!--
生成一行数据,
遍历生成的标题顺序,
从每一个对象中取出用户选定的标题的数据
-->
<td
v-for="(td, index) in prop"
ref="td"
colspan="1"
:rowspan="merge.indexOf(td) !== -1 ? model.eatspan : 1"
:style="getDisPlay(td)"
:key="index"
:class="
fixed === 'left'
? 'table-cell fixed-left'
: fixed === 'right'
? 'table-cell fixed-right'
: 'table-cell'
"
>
<div class="content">{{ model[td] }}</div>
</td>
</tr>
</template>
<script>
export default {
props: {
// 绑定的tableData
model: {
type: Object
},
// 标题顺序
prop: {
type: Array
},
fixed: {
type: String
},
merge: {
type: Array
}
},
data() {
return {
data: this.model
};
},
created() {},
mounted() {},
methods: {
// 根据td来进行动态表格行合并
getDisPlay(td) {
if (this.merge.indexOf(td) !== -1) {
return { display: this.model.eatdis };
} else {
return {};
}
}
}
};
</script>
可以看到这个文件非常简单,主要也就是进行一些行合并的操作
接下来是 MyTableColumn 文件 这个文件 主要是表头的操作
<template>
<th colspan="1" rowspan="1" :class="className">
<div class="top-content">
<!-- 展示列名 -->
<span>{{ label }}</span>
<!-- button 实现升降排序 -->
<button @click="sort('asc')" v-if="sortable">升序</button>
<button @click="sort('desc')" v-if="sortable">降序</button>
</div>
</th>
</template>
<script>
export default {
props: {
// 当前行的标签属性名
prop: {
type: String,
default: ""
},
// 当前行的标签属性文本
label: {
type: String,
default: ""
},
// 是否排序
sortable: {
type: Boolean,
default: true
},
fixed: {
type: String,
default: ""
}
},
data() {
return {
// 实现排序功能
"asc-n": (a, b) => a - b,
"desc-n": (a, b) => b - a,
//js字符串对比
// 升序
"asc-s": (a, b) => a.localeCompare(b),
// 降序
"desc-s": (a, b) => b.localeCompare(a),
// th 类型 =》是否固定
className: ""
};
},
watch: {
fixed: {
handler(newVal, oldVal) {
switch (this.fixed) {
case "top":
this.className = "cell fixed-top";
break;
case "leftTop":
this.className = "cell fixed-left-top";
break;
case "rightTop":
this.className = "cell fixed-right-top";
break;
case "left":
this.className = "cell fixed-left";
break;
case "right":
this.className = "cell fixed-right";
break;
default:
this.className = "cell";
break;
}
},
immediate: true
}
},
mounted() {
// 传回父组件,通知父组件把字符串加入顺序数组
this.$emit("table-cluomn", this.prop);
},
methods: {
sort(type) {
// 是否排序
this.$nextTick(() => {
if (this.sortable) {
// 对父父亲元素进行排序
this.$parent.$parent.tableData.sort((a, b) => {
// 调用排序规则,传入排序方式asc、desc,然后判断数据类型进行拼接后调用方法实现排序
return this[type + this.getSortType(a[this.prop])](
a[this.prop],
b[this.prop]
);
});
}
});
},
// 处理排序类型
getSortType(val) {
// 返回标识串进行拼接
return typeof val === "string" ? "-s" : "-n";
}
}
};
</script>
二、如何食用
可能看起来不太明白?先讲一下 我的的index.vue数据结构也是模仿的elementUI 进行传输 但是我的column进行了循环而不是死板的写一个是一个,虽然只是技术考核但是咱也得考虑实际情况不是。 需要注意的是我这里没有贴出来css代码 这里固定列和行使用的是 css的 position:sticky 方法,有需要的可以去百度了解下,放一下效果图。
总结
其实写的时候还是遇到很多困难的,甚至有一段时间甚至想凑活凑活得了。结果还是写了下来