Element-plus table 二次封装

前言

Element 已经提供简洁、样式优雅、易用的table组件,可快速上手。项目中可通过<el-table>、<el-table-column>标签及其属性快速生成一个个性化的独立table,项目中有较多标格展示的情况下,会出现一些问题:
1、每个页面都有一整套风格相似的table标签
2、对同类型数据做处理时,每个页面都需要引入公用的处理函数
3、例如align这种属性有默认值(左对齐),如果UI需要居中或居右,需要对每个<el-table-column>标签中的align属性设置。

针对上面的问题,尝试将el-table二次封装成全局公共组件

需要保证:
1、组件输入、输出数据格式清晰
2、可复用,贴合绝大部分应用场景
3、可扩展,1)可根据Element官方升级做对应调整 2)可添加对数据的处理能力

创建table/index.vue,并注册为全局组件

table/index.vue

<template>
  <el-table :data="tableData">
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

<script lang="ts" setup>
  const tableData = [
  {
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  },
  {
    name: 'Tom',
    address: 'No. 189, Grove St, Los Angeles',
  }
  ]
</script>

main.ts

import Table from '@/components/table/index.vue'

import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)

app.component('Table', Table)

app.mount('#app')

页面中引用

<template>
  <Table></Table>
</template>
上面Table组件中,展示了table的基础结构,引用时会展示固定的列表,需要对Table进行改造
  1. 接收不同的数据源tableData
<template>
  <el-table :data="tableData">
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

<script lang="ts" setup>
defineProps({
  tableData: {
    type: Array,
    default: function () {
      return []
    }
  }
})
</script>
  1. tableData只是全量源数据,我们需要一个可以描述el-table-column的集合,描述展示列的顺序、名称、字段、列宽等。
  • 例如下面的格式
[
    // 根据el-table提供的列属性进行扩展
    {
      prop: 'name', // 列数据参数
      label: '姓名', // 列名称
      width: 180, // 列宽度,空或不填写表示自适应
    },
]
  • 使用tableColumn接收该数据,并通过v-for渲染
<template>
  <el-table :data="tableData">
    <el-table-column v-for="item in tableColumn" :key="item.prop" :prop="item.prop" :label="item.label" :width="item.width" />
  </el-table>
</template>

<script lang="ts" setup>
defineProps({
  tableData: {
    type: Array,
    default: function () {
      return []
    }
  },
  tableColumn: {
    type: Array,
    default: function () {
      return []
    }
  },
})
</script>
  • 列表中数据往往需要一些格式处理后再展示,我们通过改造el-table-column,提供一些方案
    1)单纯对字段数据进行处理
<template>
  <el-table :data="tableData">
    <el-table-column
      v-for="item in tableColumn"
      :key="item.prop"
      :prop="item.prop"
      :label="item.label"
      :width="item.width"
    >
      <template v-slot="scope">
        {{ dealField(scope.row[item.prop], item.format) }}
      </template>
    </el-table-column>
  </el-table>
</template>

<script lang="ts" setup>
import timeFormat from '@/utils/timeFormat'

function dealField(value, format) {
  if (!value && value !== 0) {
    return '-'
  }
  if (!format) {
    return value
  }
  const { type, rules } = format
  // 自定义字段处理逻辑
  // 时间格式
  if (type === 'time') {
    // 时间处理的公共函数
    return timeFormat(value, rules)
  }
  // 数据字典
  if (type === 'dictionaries') {
    let find = rules.find(item => item.value == value)
    if (find) {
      return find.label
    }
    return ''
  }
}
</script>

对应fomart格式

  [
    {
      prop: 'name', // 列数据参数
      label: '姓名', // 列名称
      width: '', // 列宽度,空或不填写表示自适应
      format: {
        type: 'time|dictionaries', // 数据处理类型
        rules: [{ label: '', value: '' }], // 定义数据字典
        ...
      }
    },
  ]

2)展示数据需要根据多个字段计算,提供filter函数

<el-table-column
      v-for="item in tableColumn"
      :key="item.prop"
    >
      <template v-slot="scope">
        <div v-if="item.filter">{{ item.filter(scope.row) }}</div>
        <div v-else>
          {{ dealField(scope.row[item.prop], item.format) }}
        </div>
      </template>
    </el-table-column>

filte格式

[
   {
      filter: (row) => {
        let nickName = row.nickName ? `(曾用名:${row.nickName})`  : ''
        return row.name + nickName
      }
    },
]

3)单元格数据可点击

<el-table-column
      v-for="item in tableColumn"
      :key="item.prop"
    >
      <template v-slot="scope">
        <div v-if="item.event">
          <el-button link type="primary" @click="item.event(scope.row)">{{
            dealField(scope.row[item.prop], item.format)
          }}</el-button>
        </div>
        <div v-else>
          {{ dealField(scope.row[item.prop], item.format) }}
        </div>
      </template>
    </el-table-column>

event格式

// tableColumn
 [
    {
      event: (row) => { fn(row) }, // 事件,数据可点击;fn为触发的函数;row是当前列
    },
  ]

function fn(row) {
}

4)较复杂情况,如同时需要特定样式、触发事件
4-1)具名作用域插槽

<el-table-column
      v-for="item in tableColumn"
      :key="item.prop"
    >
      <template v-slot="scope">
        <div v-if="item.slotName">
          <slot :name="item.slotName" :row="scope.row"></slot>
        </div>
      </template>
</el-table-column>

父组件

<template>
  <Table :tableAttributes="tableAttributes" :tableColumn="tableColumn" :tableData="list" @handleSelectionChange="personSelect1">
      <template #age="props">
        <el-button type="primary" @click="getAge(props.row.age)">{{ props.row.age }}</el-button>
      </template>
  </Table>
<template>

<script setup lang="ts">
  import { ref } from 'vue'
  const tableColumn = ref({
    {
      prop: 'age',
      label: '年龄',
      slotName: 'age'
    },
  })
</script>

4-2)渲染函数 & JSX
创建render/index.ts

import { h } from 'vue'

export default {
  props: {
    render: Function,
    scope: Object,
  },
  setup(props) {
    // 返回渲染函数
    return () => h('div', {}, props.render(props.scope))
  }
}

table/index.vue引入

<template>
  <el-table
    :data="tableData"
  >
    <el-table-column
      v-for="item in tableColumn"
      :key="item.prop"
    >
      <template v-slot="scope">
        <div v-if="item.render">
          <HRender :render="item.render" :scope="scope"></HRender>
        </div>
      </template>
    </el-table-column>
  </el-table>
</template>

<script setup>
import HRender from '@/components/render/index.ts'
</script>

创建role.jsx

export function role(scope, event) {
  if (scope.row.perType == 1 || scope.row.perType == 0) {
    return (
      <el-tag type={ scope.row.perType == 1 ? 'info' : 'success' } onClick={ () => { event(scope.row) } }>{scope.row.perType == 1 ? '访客' : '员工'}</el-tag>
    )
  } else {
    return (
      <span>-</span>
    )
  }
}

父组件中引入role.jsx

<script setup lang="ts">
  import { role } from './jsx/role.jsx'
  import { ref } from 'vue'
  const tableColumn = ref({
    {
      prop: 'age',
      label: '年龄',
      render: (scope) => {
        // 传入数据和方法
        return role(scope, getPerType)
      }
    },
  }) 

 function getPerType(row) {
    console.log('获取角色', row)
  }
</script>

如果不想额外创建jsx文件,可以使用tsx,将jsx写在render函数内部

// lang="tsx" 满足同时使用ts和jsx
<script setup lang="tsx">
  import { ref } from 'vue'
  const tableColumn = ref({
    {
      prop: 'age',
      label: '年龄',
      render: (scope) => {
        if (scope.row.perType == 1 || scope.row.perType == 0) {
          return (
            <el-tag type={ scope.row.perType == 1 ? 'info' : 'success' } onClick={ () => { getPerType(scope.row) } }>{scope.row.perType == 1 ? '访客' : '员工'}</el-tag>
          )
        } else {
          return (
            <span>-</span>
          )
        }
      }
    },
  }) 

 function getPerType(row) {
    console.log('获取角色', row)
  }
</script>

5)针对操作列,使用默认插槽
Table组件

<template>
  <el-table
    v-loading="tableAttributes.loading"
    :data="tableData"
    :height="tableAttributes.height"
    :max-height="tableAttributes.maxHeight"
    :stripe="tableAttributes.stripe || false"
    :border="tableAttributes.border || false"
    :fit="tableAttributes.fit || true"
  >

    <!-- 内容 -->
    <el-table-column
      v-for="item in tableColumn"
      :key="item.prop"
      :fixed="item.fixed"
      :prop="item.prop"
      :label="item.label"
      :width="item.width"
      min-width="120"
      :align="item.align || 'center'"
    >
      <template v-slot="scope">
        <div v-if="item.event">
          <el-button link type="primary" @click="item.event(scope.row)">{{
            dealField(scope.row[item.prop], item.format)
          }}</el-button>
        </div>
        <div v-else-if="item.render">
          <HRender :render="item.render" :scope="scope"></HRender>
        </div>
        <div v-else-if="item.slotName">
          <slot :name="item.slotName" :row="scope.row"></slot>
        </div>
        <div v-else-if="item.filter">{{ item.filter(scope.row) }}</div>
        <div v-else>
          {{ dealField(scope.row[item.prop], item.format) }}
        </div>
      </template>
    </el-table-column>

    <!-- 插槽,用来自定义操作事件 -->
    <slot></slot>
  </el-table>
</template>

父组件

<template>
   <Table :tableAttributes="tableAttributes" :tableColumn="tableColumn" :tableData="list">
      <el-table-column
        label="操作"
      >
        <template v-slot="scope">
          <el-button @click="handleClick(scope.row)" link type="primary" size="small">查看</el-button>
          <el-button @click="edit(scope.row)" link type="primary" size="small">编辑</el-button>
        </template>
      </el-table-column>
    </Table>
</template>

<script setup lang="ts">
  function handleClick(row) {
    console.log('row', row.name)
  }
  function edit(row) {
    console.log('row', row)
  }
</script>

6)多选列
Table组件

<template>
  <el-table
    :data="tableData"
    @selection-change="handleSelectionChange"
  >
    <!-- 多选 -->
    <el-table-column
      v-if="tableAttributes.needSelection"
      type="selection"
      width="55"
      :align="tableAttributes.align || 'left'"
    >
    </el-table-column>
  </el-table>
</template>

<script setup>
defineProps({
  tableData: {
    type: Array,
    default: function () {
      return []
    }
  }
})

const emit = defineEmits(['handleSelectionChange'])

function handleSelectionChange(row) {
  emit('handleSelectionChange', row)
}
</script>

父组件

<template>
  <Table :tableAttributes="tableAttributes" :tableColumn="tableColumn" :tableData="list" @handleSelectionChange="personSelectChange">
  </Table>
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  const tableAttributes = ref({
    needSelection: true, // 是否启用多选
    align: 'center' // 多选列对其方式
  })

  // 处理表格选中更改
  function personSelectChange(row) {

  }
</script>
  1. 我们使用一个对象集合描述table的一些属性
{
    height: '500',
    maxHeight: '500',
    stripe: true,
    border: false,
    fit: true,
    needSelection: false, // 是否启用多选
    align: '', // 多选列对其方式,left-左对齐,center-居中,right-右对齐
    loading: false, // 表格加载时loading
  }
  • 使用tableAttributes接收
<template>
  <el-table 
    :data="tableData"
    v-loading="tableAttributes.loading"
    :height="tableAttributes.height"
    :max-height="tableAttributes.maxHeight"
    :stripe="tableAttributes.stripe || false"
    :border="tableAttributes.border || false"
    :fit="tableAttributes.fit || true"
  >
    <el-table-column v-for="item in tableColumn" :key="item.prop" :prop="item.prop" :label="item.label" :width="item.width" />
  </el-table>
</template>

<script lang="ts" setup>
defineProps({
  tableData: {
    type: Array,
    default: function () {
      return []
    }
  },
  tableColumn: {
    type: Array,
    default: function () {
      return []
    }
  },
  tableAttributes: {
    type: Object,
    default: function () {
      return {}
    }
  },
})
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容