最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 后台系统table组件的封装和细节实现,提高开发效率

    正文概述 掘金(姓名只是代号)   2021-03-08   599

    前不久针对公司的后台系统,开发了一套后台系统的组件库。对于table组件的实现思路和细节想分享一下。如果大家有其它的思路和想法,可以评论区留言讨论。

    组件传递属性和事件方法

    在开始编写组件之前我们要先思考一下,el-table 组件可以传递很多属性和事件方法,我们不可能把它们都一个个罗列到组件中,那有什么办法可以解决这个问题呢?这就引申出vue中两个属性 $attrs$listeners,它们在封装高级别组件时非常有用。我们先来了解一下这两个属性:

    • $listeners:包含了父作用域中的(不含 .native 修饰器的)v-on 事件监听器,可以通过

      v-on="$listeners" 传入内部组件

    • $attrs: 包含了父作用域中不作为prop被识别和获取的attribute绑定(classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常会和 inheritAttrs 一起使用。

      inheritAttrs 属性默认值为 true$attrs 中的属性会被作为普通的属性回退给子组件的根元素。

      // 父组件
      <son name="张三" :age="18" say="Hello World"></son>
        
      // 子组件
      export default {
        props: {
          name: String,
        },
        created () {
          console.log(this.$attrs)
        }
      }
        
      

      后台系统table组件的封装和细节实现,提高开发效率

      当把 inheritAttrs 设置为 false 时,根元素上就不会绑定 agesay 属性了。

    组件的实现

    column 需要包含的属性

    因为 el-table 被封装起来,所以标题列就需要变成数组columns传递到组件内部进行遍历渲染了。

    column 中包含的属性可以分成 原始属性自定义属性

    • 原始属性就是 el-table-column 包含的属性,比如:prop、label、width 等。这些原始属性可以放在名为 attrs 的对象中。

      attrs: {
        prop: '',
        label: '',
        width: '',
        'render-header': function(){}
      }
      
    • 自定义属性包含的情况:

      • 标题列可以通过 slot 设置自定义展示内容,所以要设置一个 slot:'slotName' 属性
      • 标题列可能存在子项,所以要设置一个 children:[] 属性
      • 嵌套的子项也包含通过 slot 设置的自定义展示内容,所以要设置一个 slotChildren: ['slotName', 'slotName'] (ps:之后会解释如此设置的原因)
      • 通过 slot='header' 自定义表头内容,需要设置一个 slotHeader=true 属性

      所以标题列包含的属性有:

      columns: [
        {
          attrs: {},
          slot: slotName,
          children: [],
          slotChildren: [slotName],
          slotHeader: true
        }
      ]
      

    render渲染函数登场

    因为 column 存在子项的情况,使用 template 模板无法渲染子项,而 render 渲染函数可以发挥JavaScript的编程能力,可以很好的代替模板形式。不熟悉 render 渲染函数的,可以到官方文档了解一下使用教程。

    话不多说直接上代码:

    const RenderTableColumn = {
      props: {
        column: {
          type: Object,
        }
      },
      render(createElement) {
        // createElement函数的第二个参数是模板中 attribute 对应的数据对象,包含一个scopedSlots属性
        // createScopedSlots方法会生成这个属性的值
        const createScopedSlots = (slot, slotHeader) => {
          let scopedSlots = {}
          // 自定义header存在
          // 在element-ui中的el-table-column组件上,可以设置名为header的slot
          // 所以相应的数据对象中属性名要为header
          if (slotHeader) {
            Object.assign(scopedSlots, {
              // $scopedSlots.header是对应渲染时的v-slot:header
              header: scope => this.$scopedSlots.header(scope)
            })
          }
          // 自定义内容slot存在
          // 在element-ui中的el-table-column组件上,可以设置非具名slot
          // 解析时非具名的slot对应的名称是default,所以内部属性名就是default
          if (slot) {
            Object.assign(scopedSlots, {
              default: scope => {
                // 因为在调用组件时可能传入多个具名插槽,所以要使用动态属性名
                if (this.$scopedSlots[slot]) {
                  return this.$scopedSlots[slot](scope)
                }
              }
            })
          }
          return { scopedSlots }
        }
        // 生成最终column组件的方法
        const renderColumn = column => {
          // 获取属性
          const { attrs, slot, slotHeader, children } = column
          // 保存在createElement函数第二个对象参数中,经测试用props和attrs都可以
          let props = { props: { ...attrs } } // attrs: {...attrs}
          // 获取自定义slot
          Object.assign(props, createScopedSlots(slot, slotHeader))
          // 递归遍历嵌套表头
          const hasChildren = Array.isArray(children) && children.length
          // nodes是子级虚拟节点数组
          const nodes = hasChildren ? children.map(col => {
            return renderColumn(col)
          }) : []
          
          return createElement(TableColumn, props, nodes)
         }
         return renderColumn(this.column)
       }
    }
    

    CustomTable组件的实现

    先上 CustomTable 组件代码(如果大家要使用的话,可以在此基础上添加自己想要的功能):

    <template>
      <div class="CustomTable">
        <el-table v-bind="$attrs" v-on="$listeners" ref="CustomTable">
          <template v-for="(column, index) in columns">
            <render-table-column
            v-if="column.slotHeader || column.slot || column.slotChildren"
            :key="column.attrs.prop"
            :column="column">
            <!-- 自定义header -->
              <template
              v-if="column.slotHeader"
              v-slot:header="scope">
                <slot :name="column.attrs.prop + 'Header'" :scope="{...scope, index}" />
              </template>
              <!-- 设置slot属性 -->
              <template
              v-if="column.slot"
              v-slot:[column.slot]="scope">
                <slot :name="column.slot" :scope="{...scope, index}">
                  <!-- 没有传slot时的默认值 -->
                  {{scope.row[column.attrs.prop]}}
                </slot>
              </template>
              <!-- 子表头有自定义slot -->
              <template
              v-if="column.slotChildren && column.slotChildren.length"
              v-for="name in column.slotChildren"
              v-slot:[name]="scope">
                <slot :name="name" :scope="{...scope, index}"></slot>
              </template>
            </render-table-column>
            <render-table-column
            v-else
            :column="column"
            :key="column.attrs.prop">
            </render-table-column>
          </template>
        </el-table>
      </div>
    </template>
    
    <script>
    import { Table } from 'element-ui'
    
    export default {
      name: 'CustomTable',
      components: {
        ElTable: Table,
        RenderTableColumn
      },
      inheritAttrs: false,
      props: {
        // 传入属性同el-table-column组件属性
        columns: {
          type: Array,
          required: true,
          default: () => {
            return []
          }
        },
        tableRef: {
          type: Object
        },
      },
      mounted () {
        this.$emit('update:tableRef', this.$refs.CustomTable)
      }
    }
    </script>
    
    

    Ps: 对于 el-table 的ref可以在使用组件时,通过 :tableRef.sync="tableRef" 获取,然后通过 this.tableRef 调用 Table Methods里的方法。

    组件的使用方法如下:

    <template> 
      <custom-table
      :data="tableData"
      :columns="columns"
      :tableRef.sync="tableRef">
        <template v-slot:name="{scope}">
          <span style="color:#ff8f0a">{{scope.row.detail}}</span>
        </template>
        <template v-slot:city="{scope}">
          <el-tag>{{scope.row.city}}</el-tag>
        </template>
      </custom-table>
    </template>
    
    <script>
    export default {
      component: {
        CustomTable
      },
      data () {
        return {
          tableRef: null,
          tableData: [
            {
              date: '2016-05-03',
              name: '张三',
              province: 'XXX省',
              city: 'XXX市',
              detail: 'XXX区',
              address: 'XXX省XXX市XXX区'
            },
          ],
          columns: [
            {
              attrs: {prop: "date", label: "日期", width: "100"},
              slotHeader: true
            },
            {
              attrs: {prop: "name", label: "姓名", width: "80"},
              slot: 'name'
            },
            {
              attrs: {prop: "address", label: "地址", width: '260'},
              // 对应着子项中的slot属性
              slotChildren: ['city'],
              children: [
                {attrs: {prop: "province", label: "省份", width: "70"}},
                {
                  attrs: {prop: "city", label: "城市", width: "80"},
                  slot: 'city'
                }
              ]
            }
          ],
        }
      }
    }
    </script>
    

    生成的结果为:

    后台系统table组件的封装和细节实现,提高开发效率

    为什么子项有自定义内容时设置slotChildren

    先看一下上面使用组件时的代码,所有的 **slot **插槽都是在 CustomTable 的作用域下定义的,了解了它以后我们往下看不同的情况。

    • 不设置 slotChildren 属性:

      <template
      v-if="column.slotChildren && column.slotChildren.length"
      v-for="name in column.slotChildren"
      v-slot:[name]="scope">
        <slot :name="name" :scope="{...scope, index}"></slot>
      </template>
      

      上面这段代码不会执行。

      <template v-slot:city="{scope}">
        <el-tag>{{scope.row.city}}</el-tag>
      </template>
      

      v-slot=city 插槽是在address这个列的作用域中,在渲染 address 列时不存在 slotChildren 属性,那 city 的插槽会被忽略掉不会被渲染。在列循环渲染到 city 时,因为 city 的插槽没有被添加到 $scopedSlots 属性中取不到值,所以 city 列在页面中是没有数据的。

    • 设置 slotChildren 属性:

      如果设置了 slotChildren 属性,那么上面 v-if 条件成立,city 插槽就会被渲染并添加到 $scopedSlots 属性中。在列循环渲染到 city 时,就可以在 $scopedSlots 属性中找到对应的slot。

    Ps:在子项中使用 slotHeader: true 属性自定义头部是无效的,目前还没有想到解决办法。如果大家有解决的办法,欢迎指教。


    起源地下载网 » 后台系统table组件的封装和细节实现,提高开发效率

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元