目前实现的功能有:
- 支持输入列标题和数据自动生成表格,
- 支持每列的自定义样式,
- 支持双击横屏放大表格
- 支持自定义点击事件
- 支持动态合并表格(目前只支持根据一列数据动态合并)
注意:在表格数据为空时我应用了一张放在其他文件夹的背景图片,在这里图片并未给出。
使用方法:
<myTable :columns="columns" :dataSource="dataSource"/>
其中columns格式为:
// columns格式:
[
{
title:'',//列标题,必填
dataIndex:'',//填写dataSource相应的键名,必填
render:function,//可选,函数,用于处理数据
click:function,//可选,点击事件
isRowSpan:boolean,//可选,布尔值,true表示根据该列开启动态合并单元格
},
]
dataSouce格式:
[
{
a:'',//dataSouce为一个对象数组
b:''
}
]
完整代码:
<template>
<div @dblclick="horizontalZoom()" class="containTableStyle">
<!-- 双击横屏放大 -->
<table class="tableStyle">
<tr class="theadStyle">
<td v-for="(item,index) in $props.columns" :key="index">{{item['title']}}</td>
</tr>
<tr v-for="(item,index) in $props.dataSource" :key="index" class="tbodyStyle">
<td
v-for="(renderItem,index) of renderSequence"
:key="index"
v-html="renderItem['render']?renderItem['render'](item):item[renderItem['dataIndex']]"
@click="doClick(item,renderItem['dataIndex'])"
></td>
</tr>
</table>
<div class="noDataStyle" ref='noDataStyle'></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
// columns格式:[
// {title:'',dataIndex:'',render:function,click:function,isRowSpan:boolean},//render参数可选,click指点击事件,isRowSpan指是否根据该列开启动态合并单元格
// {title:'',dataIndex:'',render:function}
// ]
columns: {
required: true,
type: Array,
default: []
},
// dataSource格式:[
// {xx:'xx',xx:'xx'}
// ]
dataSource: {
required: true,
type: Array,
default: []
}
},
watch: {
//监听父组件传过来的dataSource
dataSource: {
handler(newVal, oldVal) {
this.count = newVal.length
// this.noData(this.count)//在这里运行会报错,不知道为什么
},
immediate: true, //immediate:true代表如果在 wacth 里声明了之后,就会立即先去执行里面的
// handler方法,如果为 false,不会在绑定的时候就执行。
deep: true //deep,默认值是 false,代表是否深度监听。
}
},
data() {
return {
renderSequence: [] as any,
isrowSpan: false,
count: 0,
ishorizontal: false,
clickEvents: {},
rowSpanInfo: {}
}
},
mounted() {
const columns = this.$props.columns as any
let colIndex = 0 //记录合并依据的列
for (let item of columns) {
this.renderSequence = this.renderSequence.concat({
render: item['render'],
dataIndex: item['dataIndex']
})
if (item['click']) {
this.clickEvents[item['dataIndex']] = item['click']
}
if (item['isRowSpan']) {
this.rowSpanInfo['index'] = colIndex //获取要合并的信息
this.isrowSpan = item.isRowSpan;
}
}
this.noData(this.count)
// this.mergeCell()
},
updated() {
this.noData(this.count)
this.initialTable()
this.mergeCell()
},
methods: {
noData(count: number) {
// const div = document.getElementsByClassName('noDataStyle')[0]
const div=this.$refs.noDataStyle as any;
if (count === 0) {
div['style'].display = 'block'
} else {
div['style'].display = 'none'
}
},
initialTable(){//先初始化表格再合并,要不然当第一次合并后再翻页,表格的rowSpan和display都变了
const tabs = document.getElementsByClassName('tableStyle')[0];
for(let i=0;i<tabs['rows'].length;i++){
for(let j=0;j<tabs['rows'][i].cells.length;j++){
tabs['rows'][i].cells[j].rowSpan=1;
tabs['rows'][i].cells[j].style.display='';
}
}
},
mergeCell() {
//根据某列动态合并单元格,目前仅支持根据一列合并
const tabs = document.getElementsByClassName('tableStyle')[0];
if(this.isrowSpan){
let value='',count=1,i,j;
const index=this.rowSpanInfo['index'];
for(i=1;i<tabs['rows'].length;i++){
if(value===tabs['rows'][i].cells[index].innerHTML){
count++;
}else{
if(count>1){
const start=i-count;
tabs['rows'][start].cells[index].rowSpan=count;
for( j=start+1;j<i;j++){
tabs['rows'][j].cells[index].rowSpan=1;
// tabs['rows'][j].removeChild(tabs['rows'][j].cells[index]);//移除后,对于下一行第一个元素就变了,所以不采用
tabs['rows'][j].cells[index].style.display='none';//会把所有的都变成这样,是因为是用v-for生成的表格吗
}
count=1
}
value=tabs['rows'][i].cells[index].innerHTML;
}
}
if(count>1){//最后几行相同
const start=i-count;
tabs['rows'][start].cells[index].rowSpan=count;
for( j=start+1;j<i;j++){
// tabs['rows'][j].removeChild(tabs['rows'][j].cells[index]);
tabs['rows'][j].cells[index].style.display='none'
}
}
}
},
doClick(item: any, index: any) {
// console.log(index)
const clickEvents = this.clickEvents
if (clickEvents[index]) {
clickEvents[index](item)
return
}
return
},
horizontalZoom() {
let myTable = document.getElementsByClassName('containTableStyle')[0]
this.ishorizontal = !this.ishorizontal
if (this.ishorizontal) {
// const tableHeight = myTable['offsetHeight']
myTable['style'].width = '100vh'
myTable['style'].height = '100vw'
myTable['style'].left = 'calc(-50vh + 50vw)'
myTable['style'].top = `calc(-50vw + 50vh)`
myTable['style'].position = 'absolute'
myTable['style'].zIndex = '200'
myTable['style'].overflow = 'scroll'
myTable['style'].transform = 'rotate(90deg)'
} else {
myTable['style'].width = '100vw'
myTable['style'].height = '100%'
myTable['style'].left = '0px'
myTable['style'].top = '0px'
myTable['style'].position = 'relative'
myTable['style'].zIndex = '1'
myTable['style'].overflow = 'auto'
myTable['style'].transform = 'rotate(0deg)'
}
}
}
})
</script>
<style lang="scss" scoped>
.containTableStyle {
background: rgba(248, 248, 248, 1);
}
.tableStyle {
width: 100%;
table-layout: fixed;
border: 1px solid #dddada;
.theadStyle {
width: 100%;
// font-size: 0.35rem;
font-size: 0.4rem;
background: #e8e8e8;
border-bottom: 0.01rem solid #dddada;
text-align: center;
}
.tbodyStyle {
width: 100%;
font-size: 0.4rem;
background: #fff;
border-bottom: 0.01rem solid #dddada;
text-align: center;
}
td {
padding: 0.1rem;
word-wrap: break-word;
width: auto !important;
vertical-align: middle;
}
}
.noDataStyle {
display: none;
width: 100%;
background-image: url('../../../views/noData/undraw_Empty_re_opql.svg');
background-size: cover;
height: 8rem;
}
</style>