如何根据element-ui封装一个通用的table表格 实现动态设置列、自定义列、排序列、批量选中、等

使用场景

在一个风和日丽的天气里传来了被优化的消息 真开心 整理的一下最近写得有趣的代码 因为写中后台表格使用的比较多 但是每个人都没有统一风格导致 每个页面表格的currentPage pageNum pageSize 都不一样-(每一个前端对接的后端都有自己的写法) 上述问题只是其中一个问题 我就在想能不能 把公共的部分抽离写成组件 简化开发任务 下面就是根据项目剥离出来的组件 -仅供参考跟使用

目前只有这些属性如果想添加额外的功能可以自己按照下面的思路添加

<template>
  <!--v-table-full-height 是自己写的自定义指令 用来保证列表高度-->
  <el-table
   ref="sTable"
   :data="tableData"
   :height="tableHeight"
   :tooltip-effect="tooltipEffect"
   :border="border"
   :key="tableKey"
   v-table-full-height="tableFullHeight"
   v-bind="$attrs"
   v-on="$listeners"
   @header-dragend="headerDragend"
   >
    <template v-for="item in columns">
      <el-table-column v-if="item.type || item.formatter" :key="item.type || item.prop" v-bind="item">
      </el-table-column>
     <template v-else-if="item.children">
	   <el-table-column :label="item.label" :key="item.prop" v-bind="item">
   		 <template v-for="son in item.children">
   			<el-table-column :key="son.prop" v-bind="{ showOverflowTooltip:item.prop !=='action',resizable:resizable,...son }">
		      <template slot="header" slot-scope="scope">
		      	<slot :name="son.prop + '-header'" v-bind="scope">
		      	  {{ son.label }}
		      	</slot>
		      </template>
		      <template slot-scope="scope">
		      	<slot :name="son.prop" v-bind="scope">
		      	  {{ scope.row[son.prop]||'-'}}
		      	</slot>
		      </template>
		    </el-table-column>
   		 </template>
	   </el-table-column>
     </template>
     <el-table-column v-else :key="item.prop" v-bind="{ showOverflowTooltip:item.prop !=='action',resizable:resizable,...item}">
     	<template slot="header" slot-scope="scope">
		  <slot :name="item.prop + '-header'" v-bind="scope">
   	  		{{ item.label }}
     	  </slot>
     	</template>
     	<template slot-scope="scope">
		  <slot :name="item.prop" v-bind="scope">
   	  		{{ scope.row[item.prop] || '-' }}
     	  </slot>
     	</template>
     </el-table-column>
    </template>
    <div v-if="$slots.empty">
      <slot name="empty" />
    </div>
    <el-empty v-else description="暂无数据"></el-empty>
  </el-table>
</template>
<script>
export default {
  name:"AppTable",
  data(){
  	return {
  	  tableKey:1,
  	};
  },
  props:{
    /**动态表头数据*/
	columns:{
	  type:Array,
	  default:()=>[],
	  required:true,
	},
	/**数据*/
	tableData:{
	  type:Array,
	  default:()=>[],
	  required:true,
	},
	/** 自定义方法开关 列表高度方法*/
	tableFullHeight:{
	  type:Boolean,
	  default:false,
	},
	/**是否开启边框*/
	border:{
	  type:Boolean,
	  default:false,
	},
	/**对应列是否可以拖动改变宽度*/
	resizable:{
	  type:Boolean,
	  default:true,
	},
	/**文本超出提示框颜色*/
	tooltipEffect:{
	  type:String,
	  default:"light",
	},
	/**默认高度*/
	height:{
	  type:[String,Number],
	  default:"100%",
	},
  },
  computed: {
    tableHeight() {
      if(this.height === 'auto') {
        return undefined;
      }
      return this.height;
    }
  },
  watch: {
	columns: {
	  handler() {
		this.$nextTick(()=>{
		  this.changTable();
		  this.tableKey++;
		});
	  },
	},
	tableData: {
	  handler() {
		this.$nextTick(()=>{
		  this.changTable();
		});
	  },
	},
  },
  mounted() {
	this.changTable();
  },
  methods: {
	changTable() {
	  /**对table进行重新布局 防止在数据传入后或者动态变化后样式错乱*/
	  this.$refs.sTable && this.$refs.sTable.doLayout();
	},
	/**调节表头宽度后动态计算 内部元素剩余宽度 超出添加tooltip*/
    headerDragend(newWidth, oldWidth, column, event) {
      //列表改变宽度修改数据显示
      let columnItem = document.querySelectorAll(`.${column.id}`);
      for (var i = 0; i < columnItem.length; i++){
        // 对每个元素执行操作
        const title = columnItem[i].querySelector(".el-tooltip");
        if(title) {
          title.setAttribute("style", `width: ${newWidth}px;`);
        }
      }
      this.changTable()
    },
  }
}
</script>
<style lang="less" scoped>
:global(.el-tooltip__poper){
  max-width: 50vh !important;
}
</style>

搭配封装的pagination组件

<template>
  <div v-if="$attrs.total" class="app-pagination">
  	<el-pagination
  	  class="pagination-content"
  	  :background="background"
  	  :current-page="normalizedCurrentPage"
  	  :page-size="normalizedPageSize"
  	  :page-sizes="$attrs['page-sizes'] || [10, 20, 50, 100]"
  	  :layout="$attrs.layout || 'total, prev, pager, next, sizes'"
  	  v-bind="$attrs"
  	  v-on="$listeners"
  	  @size-change="onSizeChange"
  	  @current-change="onCurrentChange"
  	>
  </div>
  <
</template>
<script>
const EVENT_CHANGE = "change"
const EVENT_UPDATE_CURRENT_PAGE = "update:currentPage"
const EVENT_UPDATE_PAGE_SIZE = "update:pageSize"
export default {
  name:"AppPagination",
  props:{
	background: { type:Boolean, default: true },
	currentPage: { type:Number, default: 1},
	pageSize: { type:Number, default: 20},
	updateRouteOnChange: { type:Boolean, default: true },
  },
  computed: {
	normalizedCurrentPage() {
	  let { currentPage } = this
	  if(this.updateRouteOnChange && this.$route.query.currentPage) {
	 	currentPage = Number(this.$route.query.currentPage) || currentPage
	  }
	  return currentPage 
	},
	normalizedPageSize() {
	  let { pageSize} = this
	  if(this.updateRouteOnChange && this.$route.query.pageSize) {
	 	pageSize= Number(this.$route.query.pageSize) || pageSize
	  }
	  return pageSize
	},
  },
  onCurrentChange(value) {
	if(this.updateRouteOnChange) {
	  this.$router.push({
		query: {
		  ...this.$route.query,
		  currentPage: value === 1 ? undefined : value,
		}
	  })
	  this.$emit(EVENT_UPDATE_CURRENT_PAGE, value)
	  this.$emit(EVENT_CHANGE, {currentPage:value})
	}
  },
  onSizeChange(value) {
	if(this.updateRouteOnChange) {
	  this.$router.push({
		query: {
		  ...this.$route.query,
		  pageSize: value === 10 ? undefined : value,
		}
	  })
  	  this.$emit(EVENT_UPDATE_PAGE_SIZE , value)
	  this.$emit(EVENT_CHANGE, {pageSize:value})
  },
} 
</script>
<style lang="less" scoped>
.app-pagination {
  background-color: #FFF;
  padding: 8px;
}
.pagination-content {
  display:flex;
  justify-content: flex-end;
}
.el-pagination {
  padding:0px;
  ::v-deep .el-pagination__sizes,
  ::v-deep .el-select .el-input {
	margin-right:0;
  }
}
</style>

搭配Mixins 一起食用更佳

创建table-data.js
可以在这个全局混入里面添加针对 表格写的通用方法 只需要引入即可

import {mapState, mapActions} from "vuex"
export default {
  data() {
    return {
      loading:false,
      tableData:[],// 数据
      viewAllData:{}, //存储Api接口获取的data
      columnAllData:[],//全部的页头
      parameter:{}, // 传入参数
      total:0,
      pageNum:1,
      pageSize:20,
      sorted:undefined,
      orderBy:undefined,
    };
  },
  computed: {
    ...mapState("tablePage", ["pageName"])
  },
  methods: {
    ...mapActions("tablePage", ["clearPagesName"]),
    /**
     *接到一个需求就是 在表格中进行搜索或者改变分页后,进入详细或者编辑后回到上一个页面 表格展示的项还要跟进入其他页面之前一样
     *下述代码就是 清除页面信息的公共代码
     */
  	restorePage(name) {
  	  if(name == this.pageName.name) {
  	    this.pageNum = this.pageName.pageNum; 
 	    this.pageSize = this.pageName.pageSize;
 	    /**清空保存记录*/
 	    this.clearPagesName();
  	  }
  	},
    //排序
    sortChange({order, column}) {
      const orderBy = {
      	ascending:"ASC",
      	descending:"DESC",
      }[order];
      const sortField = orderBy ? column.sortable : undefined;
      this.$set(this.query, "order", orderBy);
      this.$set(this.query, "sortField", sortField);
      this.loadData();
    },
    // 获取table数据
    loadTableData(params = {}) {
      /** 传参存在的情况*/
      if(params.pageNum) {
        this.pageNum= Number(params.pageNum);
        delete params.pageNum;
      }
      if(params.pageSize) {
        this.pageSize= Number(params.pageSize);
        delete params.pageSize;
      }
      /**对传参进行更改赋值*/
      params.pageNum= this.pageNum;
      params.pageSize = this.pageSize;
      this.parameter = params;
      /**tableApiPromise 这个值是动态的 取决于引入的接口 具体使用看示例*/
      // 判断请求接口是否存在
      if(this.tableApiPromise) {
        this.loading = true;
        this.tableApiPromise(params).then(({ code, data }) => {
          if(code === 200 && data){
          	this.viewAllData = data;
          	//参数依据不同的结构进行变化 存在即展示不存在也要空数组防止报错
          	const dataList = data.records || [];
          	this.tableData = dataList;
          	this.total = data.total;
          }
        }).finally(()=>{ this.loading = false;})
      }
    },
    /**有一种情况需要自己对表格数据进行处理展示*/
    getTableData(params = {}) {
      return new Promise((res,rej) => {chua
        if(this.tableApiPromise) {
          this.tableApiPromise(params).then(({ code, data }) => {
            if(code === 200 && data){
         	  res(data)
            }
          }).catch(err => {rej(err)});
        }
      })
    },
    /**有种特殊情况需要因此表格内tooltip*/
    hideTooltip(){
      const tooltipList = document.getElementsByClassName("el-tooltip__popper");
      if(tooltipList.length > 0) {
        tooltipList[0].style.display = "none"
      }
    },
  }
}

创建common.js
编写通用的方法

export default {
  data() {
    return {};
  },
  methods: {
    /**通用的删除接口 */
    handleDel(row, status="删除", name="信息") {
	  this.$confirm("", `确定${status}当前${name}?`,{type:"question",version:"2"})
	    .then( async() => {
	      let { data, code } = await this.delApiPromise({id:row.id})
	      if (code=="200") {
			this.$message.success(`${status}成功`)
		  } else {
			this.$message.error(`${status}失败`)
		  }
 		  /**调用刷新*/
		  this.refresh()
	    }).catch(()=>{})
	},
	/**通用的启用/禁用*/
	handleStatus(row, status="启用", name="信息") {
	  this.$confirm("", `确定${status}当前${name}?`,{type:"question",version:"2"})
	    .then( async() => {
	      let { data, code } = await this.statusApiPromise({id:row.id})
	      if (code=="200") {
			this.$message.success(`${status}成功`)
		  } else {
			this.$message.error(`${status}失败`)
		  }
		  /**调用刷新*/
		  this.refresh()
	    }).catch(()=>{})
	},
  }
}

记得在Main.js中引入

import "@/directive/table-full-height"

自定义方法 table-full-height

这个方法也可以在任意使用 el-table 表格的地方添加

import Vue from 'vue';
import { debounce } from 'lodash';

Vue.directive('table-full-height', {
	inserted: function (el, binding) {
	  if(!binding.value) {
		return
	  }
	  const updateHeight = debounce(function () {
		const windowHeight = window.innerHeight;
		const elementTop = el.getBoundingClientRect().top;
		const tableHeight = windowHeight - elementTop -60;
		el.style.height = `${tableHeight}px`;
	  },100); // 防抖时间设置为100毫秒
	  window.addEventListener('resize', updateHeight);
	  updateHeight(); // 初始设置高度

	  // 设置一个定时器,每隔1000毫秒(1秒)执行一次doSomething函数
	  const intervalId = setInterval(updateHeight, 1000);
	
	  //解决依赖接口的界面引起高度的变化
	  setTimeout(() => {
	    clearInterval(intervalId); // 清除定时器
	  }, 5000);

	  // 组件销毁时移除事件监听
	  Vue.nextTick(() => {
	    el.__resizeHandler__ = updateHeight;
	  });

	  el.__vue__.$on('hook:beforeDestroy', () => {
	    window.removeEventListener('resize', el.__resizeHandler__);
	    clearInterval(intervalId); // 清除定时器
	  });
	}
});
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值