vue jsx 二次封装可动态配置的element ui table组件

760 阅读3分钟

背景介绍

jsx二次封装table表格组件,通过数据控制属性并展示对应内容,无需重复定义组件标签,只需传入对应数据以及属性所需值,实现动态的table组件

图片预览

官方

官方组件

二次封装组件

二次封装组件

需求分析

需要支持:

  1. 动态配置表头
  2. 流体高度
  3. 多选
  4. 树形数据
  5. 动态内容
  6. 不断完善组件所支持的属性

先看一下官方文档的树形数据表格代码

树形数据

我们需要简化写法让el-table、el-table-column标签的属性可以动态化,实现可控属性,拓展属性支持,所以我们用数据代替属性

首先我们定义一份el-table、el-table-column的属性数据

export const domlist = {
    "componentName":"elMain",
    "class":["container"],
    "childrenNode":[
        {
            "componentName":"elMain",
            "class":["title"],
        },
        {
            "componentName":"elTable",
            "tableDataName":"tableData",
            "tableHeightName":"tableHeight",
            "refName":"multipleTable",
            "tooltipEffect":"dark",
            "style":"width: 100%;margin-bottom: 20px;",
            "class":["accountTable"],
            "stripe":false,
            "defaultSort":{"prop":"orderNum","order":"ascending"},
            "defaultExpandAll":true,
            "treeProp":{"children":"childrenNode","hasChildren":"hasChildren"},
            "rowKey":"id",
            "childrenNode":[
                {"componentName":"elTableColumn","label":"pageRule.componentName","value":"componentName","childrenNode":[]},
                {"componentName":"elTableColumn","label":"pageRule.attributeName","value":"attributeName","childrenNode":[]},
                {"componentName":"elTableColumn","label":"pageRule.attributeName","value":"attributeName","childrenNode":[]},
                {"componentName":"elTableColumn","label":"pageRule.attributeType","value":"attributeType","childrenNode":[]},
                {"componentName":"elTableColumn","label":"pageRule.attributeLabel","value":"attributeLabel","childrenNode":[]},
                {"componentName":"elTableColumn","label":"pageRule.attributeModel","value":"attributeModel","childrenNode":[]},
                {"componentName":"elTableColumn","label":"table.operation","width":"260","align":"center","operation":true,
                    "childrenNode":[
                        {"componentName":"elButton","class":["editBtn"],"btnType":"text","event":"editContent","title":["form.edit"],"params":false,"paramsName":"row","childrenNode":[]},
                        {"componentName":"elButton","class":["delBtn"],"btnType":"text","event":"delHandle","title":["form.delete"],"params":false,"paramsName":"row","childrenNode":[]}
                    ]
                }
            ]
        }
    ]
}

上图的数据html界面,如下代码所示

<main class="container">
    <div class="title"></div>
    <div class="el-table"></div>
</main>

这里我们可以对这些属性进行动态控制,这些属性都是一一对应el-table所需要的属性

然后我们开始利用jsx渲染表格,利用jsx渲染函数时需要对vue jsx有一定的了解,建议先打开官方渲染函数 & JSX

我们建立一个table.vue的文件,代码如下所示

<template>
    <elTableJsx :node="domlist"/>
</template>
<script>
import elTableJsx from '@/components/elTableJsx/index'
import { tableData }from './tableData'
import { domlist } from './domlist'
export default{
    provide(){
        return {
            superParams:this
        }
    },
    components:{
        elTableJsx
    },
    data(){
        return {
            tableData:tableData,
            domlist:domlist,
            tableHeight:'600'
        }
    },
    methods:{
        getDataList() {
            //获取数据
        }
    }
}
</script>

这里我们引入一个elTableJsx的组件,并引入一份属性数据domlist传递给组件,另外还需要一份表格数据tableData,创建可以向多层子组件注入的依赖provide用于用子组件提供数据和方法(调用)

创建elTableJsx子组件用于渲染table组件,代码如下所示

<script type="text/jsx">
import { filteri18n } from '@/utils/index'
import Template from './template'
export default {
    inject: ['superParams'],
    name: 'Index',
    props:{
        node:{
            default:function () {
                return {}
            }
        }
    },
    render(h){
        const node = this.node;
        if(!!node){
            return h(
                node.componentName,
                {
                    'class':node.class,
                },
                this.deepChildrenComponent(node,h)
            )
            
        }
    },
    components:{
        Template
    },
    methods:{
        filteri18n,
        deepChildrenComponent(node,h){
           var that = this;
           return node.childrenNode.map(function (item) {
                return h(
                    item.componentName == 'elMain' ? 'div' : item.componentName,
                    {
                        'class':item.class,
                        props: //compontent attribute
                            item.componentName == 'elTable' ? 
                            {
                                class:item.class,
                                data:that.superParams[item.tableDataName],
                                tooltipEffect:item.tooltipEffect,
                                style:[item.style],
                                stripe:item.stripe,
                                height:that.superParams[item.tableHeightName],
                                defaultSort:item.defaultSort || '',
                                rowKey:item.rowKey || '',
                                defaultExpandAll:item.defaultExpandAll || '',
                                treeProps:item.treeProp || '',
                            } : item.componentName == 'elTableColumn' ? 
                            {
                                type:item.type,
                                label:that.$t(item.label),
                                width:item.width,
                                align:'center'
                            }
                            : item,  
                        on:{
                            '&click':function (e) {
                                switch (item.componentName) {
                                    case "elButton" :
                                        const elButtonParent = that.$parent;
                                        that.btnClick(item);
                                        break;
                                
                                    default:
                                        break;
                                }
                            },
                            'selection-change':function (e) {
                                that.selectChange(e,item);
                            }
                        },
                        directives: [
                            {
                                name: 'loading',
                                value: item.componentName == 'elTable' ? that.tableLoading : false
                            }
                        ],
                        key:Math.random(), //elTable required
                        ref:item.refName || '',
                        scopedSlots: item.componentName == 'elTableColumn' && !item.operation && {
                            default: props => h('Template',{props,item}) //通过单文件组件展示对应的信息(组件需要的一切都是通过 context 参数传递)
                        } 
                    },
                    item.childrenNode && item.childrenNode.length > 0  
                        ? that.deepChildrenComponent(item,h) 
                            : item.componentName == 'elButton'
                                ? [h('span',that.filteri18n(item.title))] 
                                        : [h('h1','elTableJsx')]
                )
           })
        },
        btnClick(item){
            if(item.params){
                this.superParams[item.event](item.paramsName);
            }else if(item.tableParams){
                this.superParams[item.event](this.rowData);  
            } else{
                this.superParams[item.event]();  
            }
        },
        selectChange(val,item){ 
            var selectName = item.selectionEvent;
            this.superParams[selectName](val);
        },
        indexMethod(index) {
            if (index < 9) {
                return "0" + (index + 1);
            } else {
                return index + 1;
            }
        },
        getObjKey(row,key){
            return _.get(row, key);
        }
    },
    data(){
        return {
            tableLoading:true
        }
    },
    mounted(){
        var that = this;
        setTimeout(() => {
            that.tableLoading = false;
        }, 1500);
    }
}
</script>
<style lang="scss" scoped>
    @import "~@/styles/elComponent.scss";
</style>

代码解析:

  1. node:父组件传入的数据
  2. deepChildrenComponent:用于递归组件
  3. Template:elTableColumn单文件组件(没有响应式数据,只作展示数据用途)
  4. defaultSort、rowKey、defaultExpandAll、treeProps:在jsx props表示该组件的属性也就是该组件所支持的属性,这些名称都是element ui table组件中所支持的(查看组件源码)
  5. props动态化:因为处于递归我们每次循环都可得到自定义的组件(组件可以多样化)因此使用三元表达式来处理不同组件
  6. selection-change:表格勾选方法,写在on里面取到的是勾选的值
  7. directives:自定义vue指令,这里添加了自定义的loading(elment ui 支持),控制起关闭即可
  8. scopedSlots:作用域插槽这里可展示动态组件,代码中展示了elTableColumn的单文件组件
  9. 再次执行that.deepChildrenComponent(item,h):递归子组件
  10. h('span',that.filteri18n(item.title))]:用于设置按钮的标题
  11. superParams:最高级组件可以获取其属性以及调用该组件的方法(getDataList())
  12. btnClick:按钮点击事件

Template 单文件组件,代码如下所示

export default {
    functional: true,
    name:'Template',
    render(h,ctx){
        const props = ctx.props;
        const row = ctx.data.props.row;
        const item = ctx.data.item;
        const parent = ctx.parent;
        return (
            item.type == "index"
            ?   h('span',parent.indexMethod(props.$index))
                : item.value 
                    ?  item.formatDate 
                        ? <span>{ parent.formatDate(parent.getObjKey(row,item.value)) }</span>
                            :   <span>{ parent.getObjKey(row,item.value) }</span>
                                         :  <span>operation</span>
        )
    },
    data(){
        return {

        }
    }

}

代码解析:

  1. ctx:父组件传过来的数据
  2. index:表格的序号(进行+1处理)
  3. formatDate:是否支持时间格式转换
  4. getObjKey:父组件属性值获取方法

也不知道这样实现符不符合逻辑,但是用在开发上便利了许多,这个封装只是针对el-table组件的jsx二次封装,我的开源项目后面也会拓展更多的属性,更多的组件,希望能帮助到各位

自己写的一个小开源项目:基于自定义数据配置自定义/引入组件(目前已支持部分element ui组件)的管理后台

gitee地址

github地址