前言
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进行改造
- 接收不同的数据源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>
- 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>
- 我们使用一个对象集合描述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>