前言
Element 已經(jīng)提供簡潔轨帜、樣式優(yōu)雅魄咕、易用的table組件,可快速上手蚌父。項目中可通過<el-table>哮兰、<el-table-column>標(biāo)簽及其屬性快速生成一個個性化的獨立table毛萌,項目中有較多標(biāo)格展示的情況下,會出現(xiàn)一些問題:
1喝滞、每個頁面都有一整套風(fēng)格相似的table標(biāo)簽
2阁将、對同類型數(shù)據(jù)做處理時,每個頁面都需要引入公用的處理函數(shù)
3右遭、例如align這種屬性有默認值(左對齊)做盅,如果UI需要居中或居右,需要對每個<el-table-column>標(biāo)簽中的align屬性設(shè)置窘哈。
針對上面的問題言蛇,嘗試將el-table二次封裝成全局公共組件
需要保證:
1、組件輸入宵距、輸出數(shù)據(jù)格式清晰
2、可復(fù)用吨拗,貼合絕大部分應(yīng)用場景
3满哪、可擴展,1)可根據(jù)Element官方升級做對應(yīng)調(diào)整 2)可添加對數(shù)據(jù)的處理能力
創(chuàng)建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的基礎(chǔ)結(jié)構(gòu),引用時會展示固定的列表娇妓,需要對Table進行改造
- 接收不同的數(shù)據(jù)源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只是全量源數(shù)據(jù)像鸡,我們需要一個可以描述el-table-column的集合,描述展示列的順序哈恰、名稱只估、字段、列寬等着绷。
- 例如下面的格式
[
// 根據(jù)el-table提供的列屬性進行擴展
{
prop: 'name', // 列數(shù)據(jù)參數(shù)
label: '姓名', // 列名稱
width: 180, // 列寬度蛔钙,空或不填寫表示自適應(yīng)
},
]
- 使用tableColumn接收該數(shù)據(jù),并通過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>
- 列表中數(shù)據(jù)往往需要一些格式處理后再展示荠医,我們通過改造el-table-column吁脱,提供一些方案
1)單純對字段數(shù)據(jù)進行處理
<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') {
// 時間處理的公共函數(shù)
return timeFormat(value, rules)
}
// 數(shù)據(jù)字典
if (type === 'dictionaries') {
let find = rules.find(item => item.value == value)
if (find) {
return find.label
}
return ''
}
}
</script>
對應(yīng)fomart格式
[
{
prop: 'name', // 列數(shù)據(jù)參數(shù)
label: '姓名', // 列名稱
width: '', // 列寬度,空或不填寫表示自適應(yīng)
format: {
type: 'time|dictionaries', // 數(shù)據(jù)處理類型
rules: [{ label: '', value: '' }], // 定義數(shù)據(jù)字典
...
}
},
]
2)展示數(shù)據(jù)需要根據(jù)多個字段計算彬向,提供filter函數(shù)
<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)單元格數(shù)據(jù)可點擊
<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) }, // 事件,數(shù)據(jù)可點擊;fn為觸發(fā)的函數(shù);row是當(dāng)前列
},
]
function fn(row) {
}
4)較復(fù)雜情況兼贡,如同時需要特定樣式、觸發(fā)事件
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)渲染函數(shù) & JSX
創(chuàng)建render/index.ts
import { h } from 'vue'
export default {
props: {
render: Function,
scope: Object,
},
setup(props) {
// 返回渲染函數(shù)
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>
創(chuàng)建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) => {
// 傳入數(shù)據(jù)和方法
return role(scope, getPerType)
}
},
})
function getPerType(row) {
console.log('獲取角色', row)
}
</script>
如果不想額外創(chuàng)建jsx文件娃胆,可以使用tsx,將jsx寫在render函數(shù)內(nèi)部
// 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"
>
<!-- 內(nèi)容 -->
<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>