基于VUE实现的复杂表头的响应式表格组件

无论是后台管理系统,还是数据报表类项目,表格总是少不了的,尤其是在手机上做APP的时候,复杂报表也是很常见,每遇到一个都用代码做一遍会不会觉得很烦,程序员最喜欢的可是–懒,但是要想偷懒必须先勤快,把组件做了,后面copy。

项目中做中国式表格会很常见,特点就是:列特别多、多行复合表头、表头可冻结、列可冻结、要支持很多行-几千几万行那种;如果只是普通的表格,完全不用任何组件,结合Bootstrap的样式,使用VUE强大的数据绑定功能就可实现,比如下面这样:

<table class="table table-bordered table-striped" v-loading="loading">
	<thead>
		<tr>
            <th>系统</th>
            <th>应用名称</th>
            <th>姓名</th>
            <th>访问时间</th>
            <th class="text-left">page_ID</th>
			<th>页面名称</th>
		</tr>
	</thead>
	<tbody>
		<tr v-for="row in logs">
          <td>{
   
   {
   
   row.platformId}}</td>
          <td>{
   
   {
   
   row.appName}}</td>
          <td class="text-center">{
   
   {
   
   row.userName}}</td>
          <td>{
   
   {
   
   new Date(row.createTime).toLocaleString()}}</td>
          <td>{
   
   {
   
   row.pageId}}</td>
          <td>{
   
   {
   
   row.pageName}}</td>
		</tr>
	</tbody>
</table>

注:上面没有列出分页组件的使用,这个自己查下文档就知道怎么用了。

不过,上面的方式并不通用,因为我们经常遇到复杂表头,还有的需要让表头固定,让表内容单独滚动,有时还要固定左侧某些列,让右侧滚动,有的需要支持编辑功能,这种情况下,再用原始报表去做就比较麻烦了,每一个都得写一遍很苦恼有木有?大多数人都会想到ElementUI里的表格组件,使用el-row这些拼,不过我觉得Elements的表格做的并不好用,稍微复杂点就得写很多代码,甚至踩很多坑。因此,我放弃了Elements的表格,自已基于原生的table实现了表格和对应的分页。

前端要实现一个表格组件无非有两种方式:

1、基于DIV实现一个纯div式的表格,这种实现方式完全摆脱了table的束缚,功能会比较强大,缺点就是实现复杂,嵌套很多的DIV会拖累性能,而且基于scroll事件的滚动不会很流畅。如果格子里的内容较多,溢出的话就没法支持自动换行,行高需要固定死。

2、基于table进行封装,这种方式实现相对简单,能结合table的轻量、兼容性好的特点,但如果要实现复杂格式,也很麻烦,做冻结也要基于scroll事件做滚动,但整体比较轻量,而且行高可以根据内容自动适应。

这两种方式中第二中是主流,国内很少有第一种实现的好用组件。本文也是基于第2种方案实现的。

废话不多说,直接上代码,下面代码可以直接使用,后面有使用样例。

1、表格组件

<template>
<div class="response-table">
	<div class="fixed-table-head">
      <div class="fixed-head">
        <table class="table table-bordered">
          <thead>
            <tr v-for="cols in sourceColumns">
              <th v-for="col in cols" v-if="col.vh!==false" @click="columnClick(col)"  :class="[col.colclass||'',orderIndex===col.id?'active':'']" :style="{width:col.w}" :colspan="col.colspan||1" :rowspan="col.rowspan||1">{
   
   {
   
   col.name}}<a v-if="hasOrder(col)" class="arrow-order" :class="{ceil:col.order===1,floor:col.order===-1}"><i class="fa fa-caret-up"></i><i class="fa fa-caret-down"></i></a></th>
            </tr>
          </thead>
        </table>
      </div>
      <div class="response-body">
          <div v-if="isLoading" class="text-center"><i class="fa fa-3x fa-spinner fa-spin" ></i></div>
          <div v-if="emptytip && (sourceList==null||sourceList.length==0)" class="emptytip">
          		{
   
   {
   
   emptytip}}
          </div>
          <table class="table table-bordered table-striped">
          <tbody>
              <tr v-if="!bodyrender" v-for="(row,index) in sourceList" :class="rowStyle(row,index)">
                  <td v-for="col in fields" 
                  v-if="(row.$R==null)||(row.$R!=null && row.$R[col.field].render)"
                  :rowspan="row.$R==null?1:row.$R[col.field].rowspan"
                  :colspan="row.$R==null?1:row.$R[col.field].colspan||1"
                  :style="{width:col.w}" 
                  @click="cellClick(row,col,index,$event)" 
                  :class="c(row,col,index)">
                  	<span v-if="!col.template" v-html="v(row,col,index)" :class="cc(row,col,index)"></span>
                  	<span v-if="col.template" v-html="parsetemplate(row,col,index)" :class="cc(row,col,index)" @click="templateClick(row,col,index,$event)"></span>
                  </td>
              </tr>
              <tr v-if="bodyrender"  v-for="(row,index) in sourceList" :class="rowStyle(row,index)">
                <td v-for="col in fields" v-if="row.$R[col.field].render" :rowspan="row.$R[col.field].rowspan" :style="{width:col.w}" :class="c(row,col,index)" :title="col.name">
                  <span v-html="v(row,col,index)" :class="cc(row,col,index)"></span>
                </td>
              </tr>
          </tbody>
        </table>
      </div>
      
    </div>
</div>
</template>
<script>
export default{
   
   
	name: 'ResponseTable',
	props: {
   
   
		emptytip:{
   
   
			type:String,
			required:false
		},
	    list:{
   
   
	      type: Array,
	          required: true
	    },
	    columns: {
   
   
	      type: Array,
	      required: true
	    },
	    rowclass:{
   
   
	      type:Function,
	      required:false
	    },
	    bodyrender:{
   
   
	      type:Boolean,//每行数据可维护一个$R字段列表,指出是否要渲染,以及rowspan和colspan,用于合并单元格
	      default:false
	    },
	    selectable:{
   
   
	    	type:Boolean, default:false
	    },
	    multiselect:{
   
   
	    	type:Boolean, default:false
	    }
	},
	data(){
   
   
      return {
   
   
  		inited:false,
      	orderIndex:null,
      	isLoading:false,
      	sourceList:this.list,
      	sourceColumns:this.columns
      }
    },
    mounted(){
   
   
	    //如果初始化进来的数据有值,则执行默认排序
	    if(this.sourceList.length>0){
   
   
	      //进行默认排序:找出columns中order不为0或null的第一个字段进行排序
	      for(var i=0;i<this.columns.length;i++){
   
   
	        if(this.columns[i].order===1||this.columns[i].order===-1){
   
   
	          this.orderIndex=this.columns[i].id;
	          this.reOrder(this.columns[i]);
	          break;
	        }
	      }
	    }
	},
	watch:{
   
   
		'list':function(n,o){
   
   
			this.sourceList=n;
			this.clearSelected();
		}
	},
    computed: {
   
   
    	fields()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值