element-ui是国内最流行的的vue开源框架,el-table组件在element-ui整个框架中是最复杂、最重要的部分。其中涉及到的知识有JSX,render渲染函数,组件间的状态管理等等。出于好奇和挑战,在网上受教于el的源代码以及网上相关内容资料。完成了一个简单基础的table组件 就叫 sd-table吧。

支持功能: 自定义列、支持自定义插槽、支持自定义排序、支持全选、多选、分页回调、宽高样式等。

el-table 源码传送门:element: Element 是一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库,提供了配套设计资源,帮助你的网站快速成型。由饿了么公司前端团队开源。 - Gitee.com

1.效果及使用方法

1.1 使用方法:

使用方法如下,看看是不是特别熟悉?与 el-table是否类似呢?

<sd-table :data = "tableData"ref="sdTable":width="'800'":height="'500'":total="1000":current-page="currentPage"@selection-change="handleSelectionChange"@loadMore = 'loadMore'><sd-table-column  type="selection" width="20"></sd-table-column><sd-table-column  width="100" label="solt列"><template slot-scope="scope"><img src="../assets/vip-icon-hui-5.png"  style="width: 25px"><span>{{ scope.row.name }}</span></template></sd-table-column><sd-table-column prop="name" label="姓名" width="200" sortable></sd-table-column><sd-table-column prop="date" label="日期" width="200" sortable></sd-table-column><sd-table-column  prop="address" label="地址" ></sd-table-column></sd-table>

1.2效果图

2.代码

2.1  sd-table-body.js

sd-table-body 主要是渲染整体表格,使用render 函数 循环二维数组 遍历tr行 遍历 td列。

export default {name:'sd-table-body',computed:{table(){return this.$parent}},props:{store: {require: true}},methods:{getBodyCellStyle(column){return {width: column.width + 'px','text-align': column.align}},},render(h) {let tableData = this.table.tableData ;let columns = this.store.getColumns() ;let tableWidth = this.store.getTableWidth() ;return (<table cellspacing="0" border='0' cellpadding="0" style={{width: tableWidth + 'px'}}>{tableData.map((row, index) =><tr  class={['sd-table-tr',row.$checked?'sd-table-tr-choose':'']}>{columns.map(column => <td class={'sd-table-body__td'} style={this.getBodyCellStyle(column)}><div>{column.renderCell(row, index)}</div></td>)}</tr>)}</table>)}
}

2.2  sd-table-column.js

sd-table-column.js 此组件主要是渲染 列相关内容,判断type 是selection或 index 或者是自定义插槽。若type=“selection” 为多选框,type=“index” 为 行序号 自定义插槽则直接显示自定义插槽内容,插槽内容可以为组件或html。

代码如下:

let columnIdSeed = 0 ;
export default  {name: 'sd-table-column',computed:{tableColumnId(){return 'columnId-' + (columnIdSeed++)},table(){return this.$parent},},props:{label: String,prop: String,width: String,align: String,type: String,sortable:{type:Boolean,default:false,}},data(){return{column:{},value_2: false,}},methods:{renderCell(data,index){let curIndex = index + 1 ;if(this.type ==='index') {return curIndex ;}if(this.type ==='selection') {this.column.renderCell = (data,index) => (<div class="cell"><input type="checkbox"   checked={data.$checked}  on-click={ ($event) => this.selectionChange($event, data) }  id={index}/></div>);return ;}if(this.prop) return data[this.prop]return this.$scopedSlots.default({$index: curIndex,row: data})},selectionChange(event,row){let isExist = false ;if(row.$checked){//row.$checked = !row.$checkedthis.$set(row,'$checked',!row.$checked);}else{this.$set(row,'$checked',true);//row.$checked = true ;}if(row.$checked){this.table.selectRowData.forEach((item)=>{if(item == row){isExist = true ;}})if(!isExist){this.table.selectRowData.push(row) ;}}else{for(let i = 0 ; i <  this.table.selectRowData.length ; i ++){if(this.table.selectRowData[i] == row){this.table.selectRowData.splice(i,1);}}}this.table.$emit("selection-change",this.table.selectRowData) ;},test(event,data){console.log(event.target.id);}},watch:{value_2(newVal){console.log(newVal)}},created(){this.column = {tableId: this.table.tableId,columnId: this.tableColumnId,label: this.label,prop: this.prop,originWidth: this.width,width: 0 ,slots: this.$slots.default,align: this.align,renderCell: this.renderCell,type: this.type,sortable:this.sortable,}this.table.store.insertColumn(this.column)},render(h){return h('div', this.$slots.default) ;}
}

2.3  sd-table-head.js

此组件是表头内容,组件拿到columns 这个表头数组,通过render函数内遍历可得到表头行,这里涉及到一个chekbox排序。 不涉及复杂表头,复杂表头以后研究。

export default {name: 'sd-table-head',data(){return{columns:[],// 实现排序功能"asc-n": (a, b) => a - b,"desc-n": (a, b) => b - a,"asc-s": (a, b) => a.localeCompare(b),"desc-s": (a, b) => b.localeCompare(a),}},computed:{table(){return this.$parent}},props:{store:{require: true}},methods:{getHeaderCellStyle(column){return {width: column.width + 'px'}},handleSortClick(event, column, givenOrder) {event.stopPropagation();if (!column.sortable) return;this.$set(this.table,'order',givenOrder);console.log(this.table.order)this.sort(givenOrder,column) ;this.$emit('sort-change',givenOrder);},sort(type,column) {// 是否排序if (column.sortable) {// 对父父亲元素进行排序this.$parent.tableData.sort((a, b) => {// 调用排序规则,传入排序方式asc、desc,然后判断数据类型进行拼接后调用方法实现排序return this[type + this.getSortType(a[column.prop])](a[column.prop],b[column.prop]);});}},selectAll(){this.table.isSelectedAll = !this.table.isSelectedAll;if(this.table.isSelectedAll ){this.table.toggleRowSelection(this.table.tableData) ;}else{this.table.clearSelection();}},// 处理排序类型getSortType(val) {// 返回标识串进行拼接return typeof val === "string" ? "-s" : "-n";},},created(){this.columns = this.store.getColumns() ;},render(h) {let columns = this.store.getColumns() ;let tableWidth = this.store.getTableWidth() ;return (<table cellspacing="0" border="0" cellpadding="0" style={{width: tableWidth+'px'}}>{<tr>{columns.map(column=><td  style={this.getHeaderCellStyle(column)}>{(column.type =='selection')?(<input  type='checkbox' checked={this.table.isSelectedAll} on-click={ ($event) => this.selectAll() }/>):(column.label)}{column.sortable ? (<spanclass="caret-wrapper"><i class={['sort-caret ascending',this.table.order=='asc'?'ascending ':'']}on-click={ ($event) => this.handleSortClick($event, column, 'asc') }></i><i class={['sort-caret descending',this.table.order=='desc'?'descending':'']}on-click={ ($event) => this.handleSortClick($event, column, 'desc') }></i></span>) : ''}</td>)}</tr>}</table>);},
}

2.4  sd-table-store.js

store 主要是保存列数据以及表的基本数据信息,方便以上各个组件初始化使用

export default class SdTableStore {constructor(tableId) {this.storeid = 'store-' + tableId ;this.columns = [] ;this.columnLabelMap = {} ;this.realTableWidth = 0 ;}insertColumn (column){this.columns.push(column) ;this.columnLabelMap[column.columnId] = column.label}getColumns(){return this.columns ;}updateTableWidth (width) {this.realTableWidth = width ;}getTableWidth () {return this.realTableWidth ;}
}

2.5 util.js

此工具方法为了计算列宽。


function  calcColumnWidth (columns, table) {let bodyWidth = table.$el.clientWidth - 24let tableWidth = 0let flexColumns = []flexColumns = columns.filter(column => {if (typeof column.originWidth !== 'string') return column})for (let column of columns) {column.width = column.originWidth || 80tableWidth += parseInt(column.width)}// 宽度有富余if (tableWidth < bodyWidth) {// 富余宽度let flexWidth = bodyWidth - tableWidthlet flexSpaceWidth = parseInt(flexWidth / flexColumns.length)flexColumns[0].width += flexWidth - flexSpaceWidth * (flexColumns.length - 1)for (let i = 1; i < flexColumns.length; i++) {flexColumns[i].width += flexSpaceWidth}table.store.updateTableWidth(bodyWidth)} else {table.store.updateTableWidth(tableWidth)}return columns ;
}
export {calcColumnWidth}

2.6 sd-table.vue

此组件是以上组件的父组件,负责组织和调用以上组件。

代码如下:

<template><div class="sd-table":class="[{'sd-table--border': border}]":style="tableStyle"><!-- 隐藏列 --><div class="hidden-columns"><slot></slot></div><div class="pos-relative"><div class="header-warp" :ref="headRef"><sd-table-head class="sd-table-head" :store="store"></sd-table-head></div><!-- 遮挡竖直滚动条 --><div class="hiddenBlock"></div></div>
<!--    <div v-loading="loadingFlag" class="pos-relative" :class="textAlign">--><div  class="pos-relative" :class="textAlign"><div :style="bodyWrapperStyle" class="body-wrap" :ref="bodyRef" @scroll="handleScroll"><sd-table-body :store="store"></sd-table-body></div><div class="noData flex-midcenter" v-show="!data.length">暂无数据</div></div></div>
</template><script>
import SdTableStore from "./sd-table-store";
import SdTableHead  from "./sd-table-head";
import SdTableBody  from "./sd-table-body";
import {calcColumnWidth} from '@/utils/util';
let tableIdSeed = 0 ;
export default {name: "sd-table",data () {return {store: null,curPage: 1,allPages: 0,bodyWrapperStyle: {height: this.height+'px','text-align': this.align,overflow: 'auto'},loadingFlag: true,tableStyle: {width: this.width},fixedHeight: 1,tableData: [],selectRowData:[],isSelectedAll:false,order:'asc',}},computed: {tableId () {return 'tableId-' + (tableIdSeed++)},bodyRef () {return 'tableBody-' + tableIdSeed},tBody () {return this.$refs[this.bodyRef]},headRef () {return 'tableHead-' + tableIdSeed},tHead () {return this.$refs[this.headRef]},'textAlign' () {return 'align-' + this.align}},components:{SdTableHead,SdTableBody,},props: {data: {type: Array,default () {return []}},width: {type: String,default: '100%'},total: {type: [Number, String],default: 0},align: {type: String,default: 'center'},height: {type: String,default: '300'},border: Boolean,currentPage: {type: Number,default: 1}},methods: {initData () {this.curPage = 1this.tableData = []this.allPages = Math.ceil(parseInt(this.total) / this.data.length)//this.tBody.scrollTop = 0},handleScroll (e) {this.tHead.scrollLeft = this.tBody.scrollLeft// fixedHeight修正由水平滚动条带来的高度计算误差if ((this.tBody.scrollTop + this.tBody.clientHeight + this.fixedHeight >= this.tBody.scrollHeight) && (this.curPage < this.allPages)) {this.loadingFlag = truethis.$emit('loadMore', ++this.curPage)}},toggleRowSelection(rows){// 交集this.tableData.forEach((item)=>{rows.forEach((itm)=>{if(item == itm){this.$set(item,"$checked",true);}})})this.selectRowData = rows ;console.log(this.selectRowData) ;},clearSelection(){this.selectRowData = [] ;this.isSelectedAll = false;this.tableData.forEach((item)=>{this.$set(item,"$checked",false);})},doLayout () {calcColumnWidth(this.store.columns, this)}},watch: {data: {deep: true,immediate: true,handler (n, o) {this.loadingFlag = falseif (this.currentPage === 1) {this.initData()}this.tableData = [...this.tableData, ...n]}}},created () {let store = new SdTableStore(this.tableId)this.store = store;},mounted () {if (!this.data.length) {this.loadingFlag = false}let self = this;self.doLayout()window.addEventListener('resize', function () {self.doLayout()})},
}
</script><style>.sd-table{width: 100%;padding: 5px 20px 5px 20px;display: flex;flex-direction: column;box-sizing: border-box;
}
.sd-table .header-warp{width: 100%;display: flex;flex-direction: row;align-items: center;font-size: 12px;padding: 10px 0 10px 0;
}.body-wrap {position: relative;
}.sd-table-tr{height: 60px;background-color: white;border-top: 1px solid #eeeeee;display: flex;align-items: center;font-size: 14px;transition: border-color .3s,background-color .3s,color .3s;flex-shrink: 0;
}.sd-table-tr:hover{padding: 0 0 0 0;background-color: #e8f8f7;
}/**当选中,item背景状态**/
.sd-table-tr-choose{padding: 0 0 0 0;background-color: #e8f8f7 !important;
}.checkboxshow{visibility:visible !important;
}.sd-table .item input[type=checkbox]{visibility:visible;
}.sd-table .item:hover .share{display: block;
}.sd-table .item:hover .itesd-time>span{display: none;
}.hiddenBlock {position: absolute;background: $head-bg;width: 25px;height: calc(100% - 1px);top: 0;right: 0;border-bottom: $border;
}.forbidden-child-pointer-events >* {pointer-events: none;
}.sd-table .caret-wrapper {display: inline-flex;flex-direction: column;align-items: center;height: 34px;width: 24px;vertical-align: middle;overflow: initial;position: relative;
}
.sort-caret.ascending {border-bottom-color: #c0c4cc;top: 5px;
}
.sort-caret-ascending {border-bottom-color: #409eff;
}.sort-caret.descending {border-top-color: #409eff;
}
.sort-caret.descending {border-top-color: #c0c4cc;bottom: 7px;
}.sort-caret {width: 0;height: 0;border: 5px solid transparent;position: absolute;left: 7px;
}</style>

2.6 test-table.vue

test-table.vue 用于调用测试表格使用。mbutton 为我自定义的组件,请自行去掉或替换。

我们可以看到基本与el-table 的使用用法一致。当然目前功能不完善,功能性不如element-ui。但是自定义插槽、多选排序以及分页功能是我们经常用到的,足够用了。

<template>
<div><sd-table :data = "tableData"ref="sdTable":width="'800'":height="'500'":total="1000":current-page="currentPage"@selection-change="handleSelectionChange"@loadMore = 'loadMore'><sd-table-column  type="selection" width="20"></sd-table-column><sd-table-column  width="100" label="solt列"><template slot-scope="scope"><img src="../assets/vip-icon-hui-5.png"  style="width: 25px"><span>{{ scope.row.name }}</span></template></sd-table-column><sd-table-column prop="name" label="姓名" width="200" sortable></sd-table-column><sd-table-column prop="date" label="日期" width="200" sortable></sd-table-column><sd-table-column  prop="address" label="地址" ></sd-table-column></sd-table><button style="width: 100px;height: 50px" @click="add">添加数据</button><button style="width: 100px;height: 50px" @click="figureChoose">指定选择</button><button style="width: 100px;height: 50px" @click="cancel">取消选择</button>
</div>
</template><script>
import SdTable from "../components/table/sd-table";
import SdTableColumn from "../components/table/sd-table-column";
import Mbutton from "../components/mbutton";
export default {name: "test-table",components: {Mbutton, SdTableColumn, SdTable},data(){return {tableData: [{no: 0,date: "2022-01-01",name: "徐凤年",address: "北凉国",},{no: 1,date: "2022-01-02",name: "姜泥",address: "楚国",},{no: 2,date: "2022-01-03",name: "王仙芝",address: "武帝城",},{no: 3,date: "2022-01-04",name: "李淳罡",address: "剑仙一键开天门",}],currentPage: 1,}},methods:{add(){let one = {no: 5,date: "2022-01-05",name: "呵呵姑娘",address: "不详",};this.tableData.push(one);},figureChoose(){this.$refs.sdTable.toggleRowSelection([this.tableData[1],this.tableData[2]])},handleSelectionChange(val){console.log("handleSelectionChange",val);},cancel(){this.$refs.sdTable.clearSelection();console.log("cancel");},loadMore(val){console.log(val);this.currentPage = val ;}}
}
</script><style scoped></style>

感谢阅读!

vue 仿el-table原理实现表格相关推荐

  1. Vue + Element-ui 实现table表格 数据相同项合并

    Vue + Element-ui 实现table表格 数据相同项合并 通过给table传入span-method方法可以实现合并行或列,方法的参数是一个对象 data() {return {table ...

  2. vue 批量打印功能 打印自定义表格table

    vue 批量打印功能 打印自定义表格table 批量打印功能 需求图 1.用excle先画出来 2.然后变为代码 点击文件 - - 另存为 - - 浏览 - - 保存类型(网页) - - 保存 然后在 ...

  3. vue+elementui 项目 table表格自定义排序规则

    vue+elementui 项目 table表格自定义排序规则 官方解释: 在列中设置 sortable 属性即可实现以该列为基准的排序,接受一个 Boolean,默认为 false.可以通过 Tab ...

  4. Vue中 对Table表格中的输入项进行校验

    项目开发中,经常会遇到的场景:对table表格的输入项字段进行校验,同时提交时整体校验. 这个坑真的爬了好久,几个需求做完了,校验也没实现,挫败!后来看到一个博主Element UI from实现校验 ...

  5. elementui 表格英文加数字排序_解决vue elementUI中table里数字、字母、中文混合排序问题...

    1.使用场景 使用elementUI中的table时,给包含数字字母中文的名称等字段排序 例如:数字(0->9)->大写字母(A->Z)->小写字母(a->z)-> ...

  6. 【重学Vue】数据响应原理真的是双向绑定吗?

    最近 Ant Design Vue 作者 - 唐金州,在某平台开课了,在整个课程中系统的讲述了Vue的开发实战.在第八讲中介绍了Vue双向绑定的问题,这里我整理一些资料客观的分析一下 Vue数据响应原 ...

  7. Vue之element table 后端排序实现

    Vue之element table 后端排序实现 1.如果需要后端排序,需将sortable设置为custom,同时在 Table 上监听sort-change事件,在事件回调中可以获取当前排序的字段 ...

  8. vue created 调用方法_深入解析 Vue 的热更新原理,偷学尤大的秘籍?

    大家都用过 Vue-CLI 创建 vue 应用,在开发的时候我们修改了 vue 文件,保存了文件,浏览器上就自动更新出我们写的组件内容,非常的顺滑流畅,大大提高了开发效率.想知道这背后是怎么实现的吗, ...

  9. vue指令写在html中的原理,详解Vue中的MVVM原理和实现方法

    对Vue中的MVVM原理解析和实现首先你对Vue需要有一定的了解,知道MVVM.这样才能更有助于你顺利的完成下面原理的阅读学习和编写下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家 ...

  10. 西安电话面试:谈谈Vue数据双向绑定原理,看看你的回答能打几分

    最近我参加了一次来自西安的电话面试(第二轮,技术面),是大厂还是小作坊我在这里按下不表,先来说说这次电面给我留下印象较深的几道面试题,这次先来谈谈Vue的数据双向绑定原理. 情景再现: 当我手机铃声响 ...

最新文章

  1. 将本地的MS SQL Server数据导入到远程服务器上
  2. Linux磁盘空间被占满?清空回收站试试!
  3. 后端技术:SpringBoot 中实现跨域的5种方式
  4. java 优酷视频缩略图_java获取优酷等视频缩略图
  5. leetcode 关于树的题目
  6. ROS调用ORB-SLAM2
  7. opencv 仿射变换与透视变换详解
  8. CCF 2022:DPU评测技术白皮书发布【附白皮书下载】
  9. 微型计算机原理与应用简称为啥,微型计算机原理与应用知识点总结.pdf
  10. 虹科解决方案 | 如何快速解决CAN与CAN FD之间通信的问题
  11. SIM800C的使用心得
  12. 设置边框大小html,css border-width属性设置边框宽度
  13. 新浪云python示例_在新浪云安装Python应用
  14. 比特精灵是计算机病毒吗,[病毒防治]靠,我居然也中木马?
  15. 尚硅谷李立超老师讲解web前端---笔记(持续更新)
  16. 用U盘打造专属个人的微型护航系统--winpe
  17. 计算机操作系统学习(六)设备管理
  18. php 蓝奏网盘上传文件,教给大家一个蓝奏云高级玩法,如何把文件一键秒上传到蓝奏云网盘...
  19. BAT面试经验分享(机器学习算法岗)
  20. 2022前端面试需要掌握的面试题

热门文章

  1. 【Code Pratice】—— 第几天、K倍区间、煤球数量
  2. 金蝶K3系统BOM新增BOM编号与父项物料编码同步需求实现
  3. C++核心准则CP.26:不要分离线程
  4. C语言快速排序-qsort函数
  5. 快速设置指南/FeistyFawn
  6. 10.EVE-NG镜像来啦!打造国内最大的EVE交流圈
  7. 编译原理-正则文法与正则表达式的相互转化
  8. 最高月薪20K?平均薪资近万...在华为子公司工作是什么体验?
  9. 矩阵树定理(Kirchhoff || Laplace)初探——Part 1(无向图计数)
  10. MFC中获取、改变Edict文本框和static静态文本框的值的方法