基于vue2.0简单二次封装el-table

324 阅读4分钟

el-table 表格渲染封装公共组件(提供三种格式化渲染模板)

单纯的想记录一下第一次封装的成果,写着玩,希望对你也有帮助。。。

示例效果

1709689477013.jpg 可以根据自己喜好自定义选择格式化渲染方式

1.render函数式渲染

自定义函数式组件expandDom用于渲染render函数,相对复杂(忘了借鉴哪位大佬的代码)

/**
* // 用于渲染表格的基础内容,必传字段,表头数据 Table-column Attributes
* @example 使用 render 函数更灵活,可生成 elementUI 元素(formatter 函数无法办到),同时可以格式化展示内容
* js:
* columns:[
* {
*   prop: 'name',
*   label: '姓名',
*   width: '120px',
*   // 使用 render 函数更灵活,可生成 elementUI 元素,formatter 无法办到
*   render: (h, params) => {
*     return h(
*       // 元素或组件名
*       'el-link',
*       // 属性
*       {
*         // 组件的 props
*         props: {
*           // 需要使用-立即执行函数-并返回字符串,type 属性不接收 Function
*           type: (function () {
*             if (params.row.name === '王小虎1') {
*               return 'success'
*             } else if (params.row.name === '王虎') {
*               return 'warning'
*             } else {
*               return ''
*             }
*           })(),
*         },
*         // 添加事件
*         on: {
*           click: () => {
*             console.log('row', params.row, 'column', params.column)
*           },
*         },
*       },
*       // 组件内容
*       params.row.name
*     )
*   },
* },
* ]
*/
2.formatter函数式渲染

使用 v-html 指令渲染内容

/**
* @example 使用 formatter 函数可以格式化改动展示内容
* @param row 行对象
* @param column 列对象
* @param cellValue 单元格的值
* @param $index 单元格的行索引
* @returns {string|number} 格式化后的数据,可返回 html 标签元素,但是无法返回 elementUI 标签元素(因为 v-html 不会解析标签)
* js:
* columns:[
* {
*   prop: 'address',
*   label: '地址',
*   minWidth: '120px',
*   sortable: true,
*   ['sort-method']: this.sortMethod,
*   formatter: ({row, column, cellValue, $index}) => {
*     return `<span style="color: red">cellValue:</span>${cellValue}<span style="color: red">,index:</span>${$index}`
*   },
* },
* ]
*/
3.插槽式渲染

自定义插槽名(不能使用append作为自定义插槽名)向外提供具名(作用域)插槽,插槽名需要和 props 传入的 columns 内 slotName 的值全等

/**
* @example slotName 提供插槽名,对外生成对应具名作用于插槽,自定义列的内容,参数为 { row, column, cellValue, $index }
* @param row 行对象
* @param column 列对象
* @param cellValue 单元格的值
* @param $index 单元格的行索引
* @important 注:slotName 不能使用 'append', append 为 el-table 内部插槽
* html:
* <template #operate="scope"> // #??? => 需要和下方的 slotName 一致
*   <el-button type="primary" @click="handleAdd(scope)">新增</el-button>
*   <el-button type="warning" @click="handleDel(scope)">删除</el-button>
* </template>
* js:
* columns:[
* {
*   prop: 'operate',
*   label: '操作',
*   width: '160',
*   fixed: 'right',
*   slotName: 'operate', // 使用 slotName 提供插槽名,对外生成对应具名作用于插槽
* },
* {
*   prop: 'expand',
*   width: 40,
*   type: 'expand', //* '展开行'内容也可以使用插槽写入,对外生成对应具名作用于插槽
*   slotName: 'expand',
* },
* ]
*/
完整代码

y-table.vue代码:

<template>
  <el-table ref="table" v-bind="$attrs" v-on="$listeners">
    <!--   多选和索引渲染插槽   -->
    <slot />
    <template v-for="(column, key) in columns">
      <el-table-column
        v-bind="column"
        :key="column.prop + key"
        show-overflow-tooltip
      >
        <template #default="scope">
          <!--      render函数式渲染      -->
          <template v-if="column.render">
            <expand-dom
              :column="column"
              :row="scope.row"
              :render="column.render"
              :index="key"
            />
          </template>
          <!--      formatter函数式渲染      -->
          <template v-else-if="column.formatter">
            <span
              v-html="
                heightLight(
                  column.formatter({
                    row: scope.row,
                    column: column,
                    cellValue: scope.row[column.prop],
                    $index: scope.$index,
                  })
                )
              "
            />
          </template>
          <!--      插槽式渲染      -->
          <template v-else-if="column.slotName">
            <slot
              :name="column.slotName"
              v-bind="{ cellValue: scope.row[column.prop], ...scope } || {}"
            />
          </template>
          <!--      无任何处理      -->
          <template v-else>
            <span v-html="heightLight(scope.row[column.prop])" />
          </template>
        </template>
      </el-table-column>
    </template>
    <!--   插入至表格最后一行之后的内容,如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。   -->
    <template #append><slot name="append" /></template>
  </el-table>
</template>

<script>
export default {
  name: 'YTable',
  components: {
    // 生成元素自定义组件
    expandDom: {
      // 函数式组件,无状态 (没有响应式数据),也没有实例 (没有 this 上下文)
      functional: true,
      props: {
        row: Object,
        render: Function,
        index: Number,
        column: {
          type: Object,
          default: null,
        },
      },
      render: (h, ctx) => {
        const params = {
          row: ctx.props.row,
          index: ctx.props.index,
        }
        if (ctx.props.column) params.column = ctx.props.column
        return ctx.props.render(h, params)
      },
    },
  },
  props: {
    // 用于渲染表格的基础内容,必传字段,表头数据 Table-column Attributes
    columns: {
      validator: function (value) {
        return value.map((item) => {
          return (
            typeof item.prop === 'string' && typeof item.label === 'string'
          )
        })
      },
      required: true,
    },
    // 用于搜索匹配高亮关键字
    search: {
      type: String,
      default: '',
    },
    // ...其余属性同 ElementUI 表格 Attributes 直接绑定传入即可
  },
  updated() {
    // 更新时,解决错位问题,修复滚动条丢失的 bug
    this.$refs.table.doLayout()
  },
  activated() {
    // 激活时,解决错位问题,修复滚动条丢失的 bug
    this.$refs.table.doLayout()
  },
  mounted() {
    // 暴露 el-table 组件原有方法给外层 $refs,或者使用 this.$refs[外层ref名].$refs.table.xxx() 调用
    const entries = Object.entries(this.$refs.table)
    for (const [key, value] of entries) {
      // 排除含有 $ 或 _ 的属性,防止浏览器页面卡顿
      if (!(key.includes('$') || key.includes('_'))) {
        this[key] = value
      }
    }
  },
  methods: {
      /**
       * 高亮搜索关键字,暂时仅支持无任何处理的渲染方式和 formatter 函数式渲染还需要测验
       * 不支持 render 函数式渲染和插槽式渲染 ‘自动高亮‘
       * @param {number|string} value 源数据值
       * @return {*|string}
       */
      heightLight(value) {
        if (this.search !== '') {
          return String(value).replace(this.regex, (match) => {
            return `<mark style="color: red;font-weight: bold;">${match}</mark>`
          })
        } else {
          return value
        }
      },
    },
 }
</script>

使用方法

1709689795594.jpg

示例代码:
<template>
  <section>
    <y-table
      v-loading="loading"
      :data="table"
      :columns="columns"
      :row-class-name="tableRowClassName"
      border
      @selection-change="handleSelectionChange"
    >
      <!--   columns中自定义插槽   -->
      <template #operate="scope">
        <el-button type="primary" @click="handleAdd(scope)">新增</el-button>
        <el-button type="warning" @click="handleDel(scope)">删除</el-button>
      </template>
      <!--   el-table内置插槽   -->
      <template #append>el-table 内置插槽:append 插槽</template>
    </y-table>
  </section>
</template>

<script>
  import YTable from '@/components/y-table.vue'

  export default {
    name: 'Test',
    components: { YTable },
    data() {
      return {
        loading: false,
        columns: [
          {
            prop: 'date',
            label: '时间',
            width: '120px',
            filters: [
              { text: '2016-05-04', value: '2016-05-04' },
              { text: '2016-05-00', value: '2016-05-00' },
              { text: '2016-05-03', value: '2016-05-03' },
            ],
            ['filter-multiple']: false, // 是否多选
            ['filter-method']: this.filterMethod,
            ['filter-placement']: 'bottom', // 筛选框展示的位置
            ['filtered-value']: ['2016-05-04'], // 默认筛选的值
          },
          {
            prop: 'name',
            label: '姓名',
            width: '120px',
            // 使用 render 函数更灵活,可生成 elementUI 元素,formatter 无法办到
            render: (h, params) => {
              return h(
                // 元素或组件名
                'el-link',
                // 属性
                {
                  // 组件的 props
                  props: {
                    // 需要使用-立即执行函数-并返回字符串,type 属性不接收 Function
                    type: (function () {
                      if (params.row.name === '王小虎1') {
                        return 'success'
                      } else if (params.row.name === '王虎') {
                        return 'warning'
                      } else {
                        return ''
                      }
                    })(),
                  },
                  // 添加事件
                  on: {
                    click: () => {
                      console.log('row', params.row, 'column', params.column)
                    },
                  },
                },
                // 组件内容
                params.row.name
              )
            },
          },
          {
            prop: 'address',
            label: '地址',
            minWidth: '120px',
            sortable: true,
            ['sort-method']: this.sortMethod,
            formatter: ({row, column, cellValue, $index}) => {
              return `<span style="color: red">cellValue:</span>${cellValue}<span style="color: red">,index:</span>${$index}`
            },
          },
          {
            prop: 'operate',
            label: '操作',
            width: '160',
            fixed: 'right',
            slotName: 'operate', // 使用 slotName 提供插槽名,对外生成对应具名作用于插槽
          },
        ],
        table: [
          {
            date: '2016-05-04',
            name: '王小虎1',
            address: '上海市普陀区金沙江路 1518 弄',
          },
          {
            date: '2016-05-04',
            name: '小虎',
            address: '普陀区金沙江路 1517 弄',
          },
          {
            date: '2016-05-01',
            name: '王虎',
            address: '上海市金沙江路 1519 弄',
          },
          {
            date: '2016-05-03',
            name: '王小',
            address: '上海市普陀区 1516 弄',
          },
        ],
      }
    },
    methods: {
      onSubmit() {
        console.log('submit!')
      },
      handleAdd(...args) {
        console.log('右侧固定栏新增按钮', args)
      },
      handleDel(...args) {
        console.log('右侧固定栏删除按钮', args)
      },
      /**
       * 筛选方法
       * @param value
       * @param row
       * @param column
       * @returns {boolean}
       */
      filterMethod(value, row, column) {
        return row[column.property] === value
      },
      /**
       * 修改行的背景颜色(添加class类名)受 scoped 限制,如果要修改颜色,在 App.vue 中添加下列class类名对应颜色
       * @param row 行对象
       * @param rowIndex 行的索引
       * @returns {String} ''|'primary-row'|success-row'|'warning-row'|'danger-row'|'info-row'
       */
      tableRowClassName({ row, rowIndex }) {
        if (rowIndex === 1) {
          return 'warning-row'
        } else if (rowIndex === 3) {
          return 'success-row'
        }
        return ''
      },
      // 多选框变化
      handleSelectionChange(val) {
        console.log('多选框变化', val)
      },
    },
  }
</script>
App.vue
<style lang="scss">
  //* el-tooltip 文字提示最大宽度
  .el-tooltip__popper {
    max-width: 600px;
  }
  .el-table__fixed-right {
    height: 100% !important;
  }
  //* 公共组件行带状态表格样式
  .el-table .primary-row {
    background: #79bbff;
  }
  .el-table .success-row {
    background: #95d475;
  }
  .el-table .warning-row {
    background: #eebe77;
  }
  .el-table .danger-row {
    background: #f89898;
  }
  .el-table .info-row {
    background: #b1b3b8;
  }
  //* el-table hover 状态的行高亮背景颜色修改
  .el-table__body tr.hover-row > td.el-table__cell {
    background-color: #dedfe0 !important;
  }
  //* el-table 单选状态高亮背景颜色修改
  .el-table__body tr.current-row > td.el-table__cell {
    background-color: #a0cfff !important;
  }
  //* 修复 el-table 单选框溢出隐藏
  .el-table-filter__list {
    overflow-y: scroll;
    max-height: 400px;
    &::-webkit-scrollbar {
      width: 13px;
      height: 13px;
    }
    &::-webkit-scrollbar-thumb {
      background-color: rgba(0, 0, 0, 0.4);
      background-clip: padding-box;
      border: 3px solid transparent;
      border-radius: 7px;
    }
    &::-webkit-scrollbar-track {
      background-color: transparent;
    }
  }
</style>

总结

最开始也有想过将各种参数功能内置在公共组件中,例如:把各种elementUI组件加入组件中,但是实际写业务的时候发现需要兼容的东西太多了,需求不断增加,需要兼容的东西也越来越多,还不如放权到每个业务下自定义,更加通用方便。毕竟二次封装也只是为了在使用时更加便捷,而不是平白增加工作量的是吧^~^!