element-ui是国内最流行的的vue开源框架,el-table组件在element-ui整个框架中是最复杂、最重要的部分。其中涉及到的知识有JSX,render渲染函数,组件间的状态管理等等。出于好奇和挑战,在网上受教于el的源代码以及网上相关内容资料。完成了一个简单基础的table组件 就叫 sd-table吧。
支持功能: 自定义列、支持自定义插槽、支持自定义排序、支持全选、多选、分页回调、宽高样式等。
el-table 源码传送门:element: Element 是一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库,提供了配套设计资源,帮助你的网站快速成型。由饿了么公司前端团队开源。 - Gitee.com
使用方法如下,看看是不是特别熟悉?与 el-table是否类似呢?
{{ scope.row.name }}
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 (
{tableData.map((row, index) =>
{columns.map(column =>
{column.renderCell(row, index)}
)}
)
}
)
}
}
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) => (
this.selectionChange($event, data) } id={index}/>
);
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.$checked
this.$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) ;
}
}
此组件是表头内容,组件拿到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 (
{
{columns.map(column=>
{
(column.type =='selection')?
( this.selectAll() }/>):
(column.label)
}
{
column.sortable ? (
this.handleSortClick($event, column, 'asc') }>
this.handleSortClick($event, column, 'desc') }>
) : ''
}
)}
}
);
},
}
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 ;
}
}
此工具方法为了计算列宽。
function calcColumnWidth (columns, table) {
let bodyWidth = table.$el.clientWidth - 24
let tableWidth = 0
let flexColumns = []
flexColumns = columns.filter(column => {
if (typeof column.originWidth !== 'string') return column
})
for (let column of columns) {
column.width = column.originWidth || 80
tableWidth += parseInt(column.width)
}
// 宽度有富余
if (tableWidth < bodyWidth) {
// 富余宽度
let flexWidth = bodyWidth - tableWidth
let 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 test-table.vue
test-table.vue 用于调用测试表格使用。mbutton 为我自定义的组件,请自行去掉或替换。
我们可以看到基本与el-table 的使用用法一致。当然目前功能不完善,功能性不如element-ui。但是自定义插槽、多选排序以及分页功能是我们经常用到的,足够用了。
{{ scope.row.name }}
感谢阅读!