更新

  1. 新增 h函数、tsx语法渲染自定义列。
    如果使用render函数渲染自定义列的话,建议你对 vue 的内置 h 函数有一定了解, 当然也可以使用插槽的方式实现自定义列。
    也可以参考下面 Render函数自定义列的使用示例。
  2. 多级表头渲染、自定义表头。
  3. 增加在线 demo 分支说明

前言

因为最近项目中频繁会使用到table表格,所以基于element plus table 做了一个二次封装的组件。

效果图

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

预览地址

在线代码分支说明:
template_phase_1.0分支: 使用 tempalte 封装, 最基本的功能,代码简单,方便自己扩展

template_phase_2.0分支: 使用 tempalte 封装, 在1.0 基础上做了优化。在编码时更好的类型支持,支持原 element table 的属性和方法,并新增部分参数和事件。具体可查看文档 @/components/table/doc.md

master分支: 使用 h 函数封装,功能有搜索, 编辑单元格等

1.0分支(本文章列出的示例)是最基本的封装,仅仅是一个大致的思路。
个人建议使用 2.0 分支的代码或者 h 函数版本的,因为这个两个分支有更好的类型提示,并支持 element plus table 的属性和方法。在此就不贴代码了,有兴趣的朋友可到仓库查看源码。

1. Table 组件封装

src/components/Table/index.vue

<template>
    <div>
        <el-table
       		ref="tableRef"
            :data="tableData"
           	v-bind="_options"
            @selection-change="handleSelectionChange"
            @row-click="handleRowClick"
            @cell-click="handleCellClick"
            @sort-change="handleSortChange">
            <template v-for="(col, index) in columns" :key="index">
                <!---复选框, 序号 (START)-->
                <el-table-column
              		v-if="col.type === 'index' || col.type === 'selection' || col.type === 'expand'"
                   	:index="indexMethod"
                    v-bind="col">
                    <!-- 当type等于expand时, 配置通过h函数渲染、txs语法或者插槽自定义内容 -->
                    <template #default="{ row, $index }">
                        <!-- render函数 (START) : 使用内置的component组件可以支持h函数渲染和txs语法 -->
                        <component v-if="col.render" :is="col.render" :row="row" :index="$index" />
                        <!-- render函数 (END) -->
                        <!-- 自定义slot (START) -->
                        <slot v-else-if="col.slot" name="expand" :row="row" :index="$index"></slot>
                        <!-- 自定义slot (END) -->
                    </template>
                </el-table-column>
                <!---复选框, 序号 (END)-->
                <!-- 渲染插槽 START -->
                <TableColumn :col="col" v-else @command="handleAction">
                     <template v-for="slot in Object.keys($slots)" #[slot]="scope: Record<string, any>">
                         <slot :name="slot" v-bind="scope" />
                     </template>
                 </TableColumn>
                 <!-- 渲染插槽 END -->
            </template>
        </el-table>
        <!-- 分页器 -->
        <div v-if="_options.showPagination" class="mt20">
            <el-pagination
                v-bind="_paginationConfig"
                @size-change="pageSizeChange"
                @current-change="currentPageChange" />
        </div>
    </div>
</template>
<script lang="ts" setup>
import { ComputedRef, computed, ref } from 'vue'
import type { TableColumnCtx } from 'element-plus/es/components/table/src/table-column/defaults'
import TableColumn from './TableColumn.vue'
import { ElTable } from 'element-plus'
export type SortParams<T> = {
    column: TableColumnCtx<T | any>
    prop: string
    order: Table.Order
}
interface TableProps {
    tableData: Array<object> // table的数据
    columns: Table.Column[] // 每列的配置项
    options?: Table.Options
}
const props = defineProps<TableProps>()
const tableRef = ref<InstanceType<typeof ElTable>>()
// 设置option默认值,如果传入自定义的配置则合并option配置项
const _options: ComputedRef<Table.Options> = computed(() => {
    const option = {
        stripe: false,
        tooltipEffect: 'dark',
        showHeader: true,
        showPagination: false,
        rowStyle: () => 'cursor:pointer' // 行样式
    }
    return Object.assign(option, props?.options)
})
// 合并分页配置
const _paginationConfig = computed(() => {
    const config = {
        total: 0,
        currentPage: 1,
        pageSize: 10,
        pageSizes: [10, 20, 30, 40, 50, 100],
        layout: 'total, sizes, prev, pager, next, jumper'
    }
    return Object.assign(config, _options.value.paginationConfig)
}) 
const emit = defineEmits([
    'selection-change', // 当选择项发生变化时会触发该事件
    'row-click', // 当某一行被点击时会触发该事件
    'cell-click', // 当某个单元格被点击时会触发该事件
    'command', // 按钮组事件
    'size-change', // pageSize事件
    'current-change', // currentPage按钮组事件
    'pagination-change', // currentPage或者pageSize改变触发 
    'sort-change', // 列排序发生改变触发 
])
// 自定义索引
const indexMethod = (index: number) => {
        const tabIndex = index + (_paginationConfig.value.currentPage - 1) * _paginationConfig.value.pageSize + 1
        return tabIndex
    }
// 切换pageSize
const pageSizeChange = (pageSize: number) => {
    emit('size-change', pageSize)
    emit('pagination-change', 1, pageSize)
}
// 切换currentPage
const currentPageChange = (currentPage: number) => {
    emit('current-change', currentPage)
    emit('pagination-change', currentPage, _paginationConfig.value.pageSize)
}
// 按钮组事件
const handleAction = (command: Table.Command, row: any, index: number) => {
    emit('command', command, row, index)
}
// 多选事件
const handleSelectionChange = (val: any) => {
    emit('selection-change', val)
}
// 当某一行被点击时会触发该事件
const handleRowClick = (row: any, column: any, event: MouseEvent) => {
    emit('row-click', row, column, event)
}
// 当某个单元格被点击时会触发该事件
const handleCellClick = (row: any, column: any, cell: any, event: MouseEvent) => {
    emit('cell-click', row, column, cell, event)
}
/**
	*  当表格的排序条件发生变化的时候会触发该事件
	 * 在列中设置 sortable 属性即可实现以该列为基准的排序, 接受一个 Boolean,默认为 false。
	 * 可以通过 Table 的 default-sort 属性设置默认的排序列和排序顺序。 
	 * 如果需要后端排序,需将 sortable 设置为 custom,同时在 Table 上监听 sort-change 事件,
	 * 在事件回调中可以获取当前排序的字段名和排序顺序,从而向接口请求排序后的表格数据。
 	*/
const handleSortChange = ({ column, prop, order }: SortParams<any>) => {
    emit('sort-change', { column, prop, order })
}
// 暴露给父组件参数和方法,如果外部需要更多的参数或者方法,都可以从这里暴露出去。
defineExpose({ element: tableRef })
</script>
<style lang="scss" scoped>
:deep(.el-image__inner) {
    transition: all 0.3s;
     cursor: pointer;
     &:hover {
         transform: scale(1.2);
     }
}
</style>

src/components/Table/TableColumn.vue

<script lang="ts" setup>
import dayjs from 'dayjs'
defineProps<{ col: Table.Column }>()
const emit = defineEmits(['command'])
// 按钮组事件
const handleAction = (command: Table.Command, { row, $index }: { row: any; $index: number }) => {
    emit('command', command, row, $index)
}
</script>
<template>
    <!-- 如果有配置多级表头的数据,则递归该组件 -->
    <template v-if="col.children?.length">
        <el-table-column :label="col.label" :width="col.width" :align="col.align">
            <TableColumn v-for="item in col.children" :col="item" :key="item.prop">
                <template v-for="slot in Object.keys($slots)" #[slot]="scope: Record<string, any>">
                    <slot :name="slot" v-bind="scope" />
                </template>
            </TableColumn>
            <template #header="{ column, $index }">
                <component v-if="col.headerRender" :is="col.headerRender" :column="column" :index="$index" />
                <slot v-else-if="col.headerSlot" :name="col.headerSlot" :column="column" :index="$index"></slot>
                <span v-else>{{ column.label }}</span>
            </template>
        </el-table-column>
    </template>
    <!-- 其他正常列 -->
    <el-table-column v-else v-bind="col">
        <!-- 自定义表头 -->
        <template #header="{ column, $index }">
            <component v-if="col.headerRender" :is="col.headerRender" :column="column" :index="$index" />
            <slot v-else-if="col.headerSlot" :name="col.headerSlot" :column="column" :index="$index"></slot>
            <span v-else>{{ column.label }}</span>
        </template>
        <template #default="{ row, $index }">
            <!---图片 (START)-->
            <!-- 如需更改图片size,可自行配置参数 -->
            <el-image
                v-if="col.type === 'image'"
                preview-teleported
                :hide-on-click-modal="true"
                :preview-src-list="[row[col.prop!]]"
                :src="row[col.prop!]"
                fit="cover"
                class="w-9 h-9 rounded-lg" />
            <!---图片 (END)-->
            <!--- 格式化日期 (本项目日期是时间戳,这里日期格式化可根据你的项目来更改) (START)-->
            <template v-else-if="col.type === 'date'">
                <!---十位数时间戳-->
                <span v-if="String(row[col.prop!])?.length <= 10">
                    {{ dayjs.unix(row[col.prop!]).format(col.dateFormat ?? 'YYYY-MM-DD') }}
                </span>
                <!---十三位数时间戳-->
                <span v-else>{{ dayjs(row[col.prop!]).format(col.dateFormat ?? 'YYYY-MM-DD') }}</span>
            </template>
            <!--- 格式化日期 (本项目日期是时间戳,这里日期格式化可根据你的项目来更改) (END)-->
            <!-- 如果传递按钮数组,就展示按钮组 START-->
            <el-button-group v-else-if="col.buttons?.length">
                <el-button
                    v-for="(btn, index) in col.buttons"
                    :key="index"
                    :size="btn.size"
                    :type="btn.type"
                    @click="handleAction(btn.command, { row, $index })"
                    >{{ btn.name }}</el-button
                >
            </el-button-group>
            <!-- 如果传递按钮数组,就展示按钮组 END-->
            <!-- render函数 (START) 使用内置的component组件可以支持h函数渲染和txs语法-->
            <component v-else-if="col.render" :is="col.render" :row="row" :index="$index" />
            <!-- render函数 (END) -->
            <!-- 自定义slot (START) -->
            <slot v-else-if="col.slot" :name="col.slot" :row="row" :index="$index"></slot>
            <!-- 自定义slot (END) -->
            <!-- 默认渲染 (START) -->
            <span v-else>{{ row[col.prop!] }}</span>
            <!-- 默认渲染 (END) -->
        </template>
    </el-table-column>
</template>

TableColunm这个组件本打算用tsx语法写,因为tsx更适合封装这种判断条件很多场景的组件。但是可能有些朋友项目中没有使用tsx,最终还是使用了template模版的方式。

复制完Table组件的代码后,会报红线,Table.XXXXX 找不到,这个是全局类型声明。 声明文件已在下方贴出来,直接复制进项目中, 红色警告自然消失。

2. 页面引用

src/views/table/index.vue

基本表格
<template>
   <div class="p-5">
        <h1 class="mb-5 text-base text-gray-100 bg-red-400 p-2 border-4 border-green-400 rounded-md">
            element-plus table表格二次封装 <span class="ml-6">(此标题样式使用Tailwind Css生成)</span>
        </h1>
        <el-card>
            <template #header>
               <span>基本表格</span>
            </template>
            <Table
                :columns="tableColumn"
                :table-data="tableData"
                :options="{ defaultSort: { prop: 'name', order: 'ascending' } }"
                @selection-change="handleSelection"
                @command="handleAction"
                @sort-change="handleSortChange">
                <template #address="{ row }">
                   <span>演示slot使用--->{{ row.address }}</span>
                </template>
                <!-- 如果不传入按钮组的数据就使用自定义插槽的方式, 自定义插槽需在配置项里配置slot -->
                <!-- <template #action="{ row, index }">
                    <div>
                        <el-button type="success">添加</el-button>
                        <el-button type="warning" @click="handleDelete(row, index)">删除</el-button>
                    </div>
                </template> -->
            </Table>
        </el-card>
    </div>
</template>
<script lang="ts" setup>
    import { tableColumn } from '@/config/table'
    import { ElMessageBox, ElMessage } from 'element-plus'
    import type { SortParams } from '@/components/Table/index.vue'
    // import Table from '@/components/Table/index.vue'
    // 本项目Table组件自动引入,如复制此代码,需根据路径引入Table组件后使用
    interface User {
        date: number
        name: string
        address: string
    }
    // 基本表格数据
    const tableData: User[] = [
        {
            date: 1660737564000,
            name: '佘太君',
            address: '上海市普陀区金沙江路 1516 弄'
        },
        {
            date: 1462291200000,
            name: '王小虎',
            address: '上海市普陀区金沙江路 1517 弄'
        },
        {
            date: 1462032000000,
            name: '王小帅',
            address: '上海市普陀区金沙江路 1519 弄'
        },
        {
            date: 1462204800000,
            name: '王小呆',
            address: '上海市普陀区金沙江路 1516 弄'
        }
    ]
   /**
    * 排序
    * 在列中设置 sortable 属性即可实现以该列为基准的排序, 接受一个 Boolean,默认为 false。
    * 可以通过 Table 的 default-sort 属性设置默认的排序列和排序顺序。 
    * 如果需要后端排序,需将 sortable 设置为 custom,同时在 Table 上监听 sort-change 事件,
    * 在事件回调中可以获取当前排序的字段名和排序顺序,从而向接口请求排序后的表格数据。
    */
	const handleSortChange = ({ column, prop, order }: SortParams<User>) => {
	    console.log(column, order)
	    if (prop) {
	        ElMessage.success(`点击了【${prop}】排序`)
	    }
	}
    const handleSelection = (val: User[]) => {
        console.log('父组件接收的多选数据', val)
    }
    const handleAction = (command: Table.Command, row: User, index: number) => {
        switch (command) {
            case 'edit':
                alert('点击了编辑')
                break
            case 'delete':
                console.log('row', row)
                console.log('index', index)
                ElMessageBox.confirm('确认删除吗?', '提示').then(() => {
                    ElMessage(JSON.stringify(row))
                }).catch(() => null)
                break
            default:
                break
        }
    }
</script>
<style lang="scss" scoped></style>
Render 函数自定义列、自定义表头

如果列内容需要自定义设置,个人更推荐使用render函数,或者tsx语法,感觉比插槽更加方便。

<template>
    <el-card>
        <template #header> 
              <span>Render 函数自定义列、自定义表头</span> 
        </template>
        <Table
            :columns="tableColumn"
            :table-data="tableData"
            @selection-change="handleSelection"
            @command="handleAction">
             <!-- 插槽自定义表头  addressHeader就是tableColumn中地址那一列定义的 -->
           	<template #addressHeader="{ column }">
               <span>{{ column.label }}(插槽自定义表头)</span>
            </template>
        </Table>
    </el-card>
</template>
<script lang="ts" setup>
    import { ElMessageBox, ElMessage } from 'element-plus'
	import dayjs from 'dayjs'
    // import { h } from 'vue'
    // import Table from '@/components/Table/index.vue'
    // 本项目Table组件自动引入,如复制此代码,需根据路径引入Table组件后使用
    interface User {
        date: number
        name: string
        address: string
    }
    const tableData: User[] = [
        {
            date: 1660737564000,
            name: '佘太君',
            address: '上海市普陀区金沙江路 1516 弄'
        },
        {
            date: 1462291200000,
            name: '王小虎',
            address: '上海市普陀区金沙江路 1517 弄'
        },
        {
            date: 1462032000000,
            name: '王小帅',
            address: '上海市普陀区金沙江路 1519 弄'
        },
        {
            date: 1462204800000,
            name: '王小呆',
            address: '上海市普陀区金沙江路 1516 弄'
        }
    ]
    const tableColumn: Table.Column[] = [
        { type: 'selection', width: '50' },
        { type: 'index', width: '50', label: 'No.' },
        { prop: 'name', label: '名字' },
        // 日期使用render函数格式化
        {
	        prop: 'date',
	        label: '日期',
	        headerRender: ({ column }) => h(ElTag, { type: 'danger' }, () => `${column.label}(渲染函数自定义表头)`),
	        render: ({ row }) => h('span', dayjs(row.date).format('YYYY-MM-DD HH:mm'))
	    },
  		{ prop: 'address', label: '地址', headerSlot: 'addressHeader', showOverflowTooltip: true },
        // 按钮使用render函数渲染
        {
            width: '140',
            label: '操作',
            render: (row: User, index: number) =>
                // 渲染单个元素
                //   h(
                //             ElButton,
                //             {
                //                 type: 'primary',
                //                 size: 'small',
                //                 onClick: () => handleRenderEdit(row, index)
                //             },
                //             { default: () => '编辑' }
                //         ),
                // 渲染多个元素
                h('div', null, [
                    h(
                        ElButton,
                        {
                            type: 'primary',
                            size: 'small',
                            onClick: () => handleRenderEdit(row, index)
                        },
                        { default: () => '编辑' }
                    ),
                    h(
                        ElButton,
                        {
                            type: 'danger',
                            size: 'small',
                            onClick: () => handleRenderDelete(row, index)
                        },
                        { default: () => '删除' }
                    )
                ])
        }
    ]
    const handleRenderEdit = (row: User, index: number) => {
        ElMessage.success(`${row.name} ----- ${index}`)
    }
    const handleRenderDelete = (row: User, index: number) => {
        ElMessage.error(`${row.name} ----- ${index}`)
    }
    const handleSelection = (val: User[]) => {
        console.log('父组件接收的多选数据', val)
    }
    const handleAction = (command: Table.Command, row: User) => {
        switch (command) {
            case 'edit':
                alert('点击了编辑')
                break
            case 'delete':
                console.log('row', row)
                ElMessageBox.confirm('确认删除吗?', '提示').then(() => {
                    ElMessage(JSON.stringify(row))
                })
                break
            default:
                break
        }
    }
</script>
<style lang="scss" scoped></style>
展开行
<template>
   <div class="p-5">
       <el-card>
            <template #header>
                <span>展开行</span>
            </template>
            <Table :columns="tableColumn" :table-data="tableData">
                <!-- 展开行插槽内容 -->
                <!-- <template #expand="{ row }">
                    <div class="p-5">
                        <p>名字: {{ row.name }}</p>
                        <p>地址: {{ row.address }}</p>
                        <p>日期: {{ row.date }}</p>
                    </div>
                </template> -->
                <!-- 普通列插槽内容 -->
                <template #address="{ row }">
                    <span>演示slot使用--->{{ row.address }}</span>
                </template>
            </Table>
        </el-card>
    </div>
</template>
<script lang="ts" setup>
// import { h } from 'vue'
// import Table from '@/components/Table/index.vue'
// 本项目Table组件自动引入,如复制此代码,需根据路径引入Table组件后使用
interface User {
    date: number
    name: string
    address: string
}
const renderExpandContent = (row: User) =>
    h('div', { class: 'p-5' }, [
        h('p', `名字:${row.name}`),
        h('p', `地址:${row.address}`),
        h('p', `日期:${row.date}`)
    ])
const tableColumn: Table.Column[] = [
    // 使用render函数渲染展开行的内容
    { type: 'expand', width: '50', render: ({ row }) => renderExpandContent(row) },
    // 使用expand插槽自定义展开行的内容。注意:如果使用展开行的插槽, slot名字必须是 ‘expand’。
    // { type: 'expand', width: '50', slot: 'expand' },
    { type: 'index', width: '50', label: 'No.' },
    { prop: 'name', label: '名字', sortable: true },
    { prop: 'date', label: '日期', type: 'date' },
    { prop: 'address', label: '地址', slot: 'address', showOverflowTooltip: true },
    {
        width: '180',
        label: '操作',
        buttons: [
            { name: '编辑', type: 'success', command: 'edit' },
            { name: '删除', type: 'danger', command: 'delete' }
        ]
    }
]
const tableData: User[] = [
    {
        date: 1660737564000,
        name: '佘太君',
        address: '上海市普陀区金沙江路 1516 弄'
    },
    {
        date: 1462291200000,
        name: '王小虎',
        address: '上海市普陀区金沙江路 1517 弄'
    },
    {
        date: 1462032000000,
        name: '王小帅',
        address: '上海市普陀区金沙江路 1519 弄'
    },
    {
        date: 1462204800000,
        name: '王小呆',
        address: '上海市普陀区金沙江路 1516 弄'
    }
]
</script>
带有分页的表格
<template>
    <div class="p-5">
        <h1 class="mb-5 text-base text-gray-100 bg-red-400 p-2 border-4 border-green-400 rounded-md">
            element-plus table表格二次封装 <span class="ml-6">(此标题样式使用Tailwind Css生成)</span>
        </h1>
        <!-- 带有分页的表格 -->
        <el-card>
            <template #header>
                 <span>带有分页的表格</span>
            </template>
            <Table
                :columns="tableDemoColumn"
                :table-data="tableDemoList"
                :options="options"
                @pagination-change="handlePaginationChange"
                @command="handleAction"> 
               	<!-- 使用插槽格式化年龄 -->
                <template #gender="{ row }">
                    <span> {{ row.gender ? '男' : '女' }}</span>
                </template>
            </Table>
        </el-card>
    </div>
</template>
<script lang="ts" setup>
    import { tableDemoColumn } from '@/config/table'
    import { ElMessageBox, ElMessage } from 'element-plus'
    import { getDemoList } from '@/service/api/table'
    // import Table from '@/components/Table/index.vue'
    // 本项目Table组件自动引入,如复制此代码,需根据路径引入Table组件后使用
    const route = useRoute()
    const router = useRouter()
 
    interface State {
        tableDemoList: TableDemoItem[]
        options: Table.Options
    }
    const state = reactive<State>({
        tableDemoList: [],
        options: { showPagination: true, height: 600 }
    })
    const params: TableDemoParams = {
        page: 1,
        pageSize: 10
    }
    watch(
        () => route.query,
        async (newval) => {
            const { page, pageSize } = newval
            params.page = Number(page) || params.page
            params.pageSize = Number(pageSize) || params.pageSize
            const { items, pageInfo } = await getDemoList(params)
            state.tableDemoList = items
            state.options.paginationConfig = pageInfo
        },
        { immediate: true }
    )
   	// pageSize或者currentPage改变触发
    const handlePaginationChange = (page: number, pageSize: number) => {
        router.push({
            path: route.path,
            query: {
                page,
                pageSize
            }
        })
    }
    // 这里的UserInfo类型是api声明文件里定义的类型, 请忽略 
    const handleAction = (command: Table.Command, row: UserInfo) => {
        switch (command) {
            case 'edit':
                alert('点击了编辑')
                break
            case 'delete':
                console.log('row', row)
                ElMessageBox.confirm('确认删除吗?', '提示').then(() => {
                    ElMessage(JSON.stringify(row.name))
                })
                break
            default:
                break
        }
    }
    const { tableDemoList, options } = toRefs(state)
</script>
<style lang="scss" scoped></style>
多级表头
<script lang="ts" setup>
import { ElTag } from 'element-plus'
import { h } from 'vue'
import Table from '@/components/Table/index.vue'
const tableColumns: Table.Column[] = [
      {
        label: 'Date',
        prop: 'date',
        width: '220',
        headerRender: ({ column }) => h(ElTag, { type: 'danger' }, () => `${column.label}(渲染函数自定义表头)`)
    },
    {
        label: 'Delivery Info',
        headerSlot: 'deliveryHeader',
        children: [
            { label: 'Name', prop: 'name', width: '120' },
            {
                label: 'Address Info',
                headerRender: ({ column }) =>
                    h(ElTag, { type: 'success' }, () => `${column.label}(渲染函数自定义表头)`),
                children: [
                    { label: 'State', prop: 'state', width: '240', headerSlot: 'stateHeader' },
                    { label: 'City', prop: 'city', width: '120' },
                    {
                        label: 'Address',
                        prop: 'address',
                        headerRender: ({ column }) =>
                            h(ElTag, { type: 'warning' }, () => `${column.label}(渲染函数自定义表头)`)
                    },
                    { label: 'Zip', width: '120', render: ({ row }) => h(ElTag, { type: 'success' }, () => row.zip) }
                ]
            }
        ]
    }
]
const tableData = [
    {
        date: '2016-05-03',
        name: 'Tom',
        state: 'California',
        city: 'Los Angeles',
        address: 'No. 189, Grove St, Los Angeles',
        zip: 'CA 90036'
    },
    {
        date: '2016-05-02',
        name: 'Tom',
        state: 'California',
        city: 'Los Angeles',
        address: 'No. 189, Grove St, Los Angeles',
        zip: 'CA 90036'
    },
    {
        date: '2016-05-04',
        name: 'Tom',
        state: 'California',
        city: 'Los Angeles',
        address: 'No. 189, Grove St, Los Angeles',
        zip: 'CA 90036'
    },
    {
        date: '2016-05-01',
        name: 'Tom',
        state: 'California',
        city: 'Los Angeles',
        address: 'No. 189, Grove St, Los Angeles',
        zip: 'CA 90036'
    },
    {
        date: '2016-05-08',
        name: 'Tom',
        state: 'California',
        city: 'Los Angeles',
        address: 'No. 189, Grove St, Los Angeles',
        zip: 'CA 90036'
    },
    {
        date: '2016-05-06',
        name: 'Tom',
        state: 'California',
        city: 'Los Angeles',
        address: 'No. 189, Grove St, Los Angeles',
        zip: 'CA 90036'
    },
    {
        date: '2016-05-07',
        name: 'Tom',
        state: 'California',
        city: 'Los Angeles',
        address: 'No. 189, Grove St, Los Angeles',
        zip: 'CA 90036'
    }
]
</script>
<template>
    <div class="p-5">
        <el-card class="mb-5">
            <template #header>多级表头</template>
            <Table :columns="tableColumns" :table-data="tableData">
				 <!-- 插槽自定义表头 -->
                <template #deliveryHeader="{ column }: any">
                    <p class="bg-red-400 text-white">{{ column.label }}(插槽自定义表头)</p>
                </template>
                <template #stateHeader="{ column }">
                    <p class="bg-green-400 text-white">{{ column.label }}(插槽自定义表头)</p>
                </template>
			</Table>
        </el-card>
    </div>
</template>

3.表格配置项

src/config/table.ts 参数可参考下面参数介绍

// 基本表格配置
export const tableColumn: Table.Column[] = [
    { type: 'selection', width: '50' },
    { type: 'index', width: '50', label: 'No.' },
    { prop: 'name', label: '名字', sortable: true  },
    { type: 'date', prop: 'date', label: '日期'},
  	{ prop: 'address', label: '地址', slot: 'address', showOverflowTooltip: true },
   	{
        width: '120',
        label: '操作',
        buttons: [
            { name: '编辑', type: 'success', command: 'edit' },
            { name: '删除', type: 'danger', command: 'delete' }
        ]
    }
]
// 带有分页的表格配置
export const tableDemoColumn: Table.Column[] = [
    { type: 'index', width: '65', label: 'No.', align: 'center' },
    { prop: 'avatar', type: 'image', label: '头像', width: '100', align: 'center' },
    { prop: 'name', label: '姓名', width: '100' },
    { prop: 'age', label: '年龄', width: '90', align: 'center' },
    { prop: 'gender', label: '性别', width: '90', slot: 'gender', align: 'center' },
    { prop: 'mobile', label: '手机号', width: '180' },
    { prop: 'email', label: '邮箱', showOverflowTooltip: true },
    { prop: 'address', label: '地址', showOverflowTooltip: true },
    {
        width: '120',
        label: '操作',
        buttons: [
            { name: '编辑', type: 'success', command: 'edit' },
            { name: '删除', type: 'danger', command: 'delete' }
        ]
    }
]

4.参数类型声明

src/typings/table/index.d.ts 声明为全局的类型,方便使用

// table表格
declare namespace Table {
	type VNodeChild = import('vue').VNodeChild
	type Type = 'selection' | 'index' | 'expand' | 'image' | 'date'
    type Size = 'large' | 'default' | 'small'
    type Align = 'center' | 'left' | 'right'
    type Command = string | number
    type DateFormat = 'YYYY-MM-DD' | 'YYYY-MM-DD HH:mm:ss' | 'YYYY-MM-DD HH:mm' | 'YYYY-MM'
    type Order = 'ascending' | 'descending'
    interface ButtonItem {
        name: string,
        command: Command,
        size?: Size
        type?: 'primary' | 'success' | 'warning' | 'danger' | 'info',
    }
    interface Sort {
        prop: string
        order: Order
        init?: any
        silent?: any
    }
    interface Column {
   		// 对应列的类型。 如果设置了selection则显示多选框; 如果设置了 index 则显示该行的索引(从 1 开始计算); 如果设置了 expand 则显示为一个可展开的按钮
        type?: Type,
        label?: string,
        prop?: string,
        slot?: string
        width?: string,
        align?: Align,
        dateFormat?: DateFormat // 显示在页面中的日期格式,简单列举了几种格式, 可自行配置
        showOverflowTooltip?: boolean,
        buttons?: ButtonItem[],
        render?: (row?: any, index?: number) => VNodeChild // 渲染函数,渲染这一列的每一行的单元格
        sortable?: boolean | 'custom', // 对应列是否可以排序, 如果设置为 'custom',则代表用户希望远程排序,需要监听 Table 的 sort-change 事件
        headerRender?: ({ column, index }) => VNodeChild, // 渲染函数,渲染列表头
        headerSlot?: string, // 自定义表头插槽名字
        children?: Column[] // 配置多级表头的数据集合, 具体用法可参考多级表头使用示例。
    }
    interface Options {
        height?: string | number,
        // Table 的高度, 默认为自动高度。 如果 height 为 number 类型,单位 px;如果 height 为 string 类型,则这个高度会设置为 Table 的 style.height 的值,Table 的高度会受控于外部样式。
        stripe?: boolean, // 是否为斑马纹 table
        maxHeight?: string | number, // Table 的最大高度。 合法的值为数字或者单位为 px 的高度。
        size?: Size // Table 的尺寸
        showHeader?: boolean // 是否显示表头,
        tooltipEffect?: 'dark' | 'light' // tooltip effect 属性
        showPagination?: boolean, // 是否展示分页器
        paginationConfig?: Pagination, // 分页器配置项,详情见下方 paginationConfig 属性,
   	 	rowStyle?: ({ row, rowIndex }) => stirng | object // 行的 style 的回调方法,也可以使用一个固定的 Object 为所有行设置一样的 Style。
   	 	headerCellStyle?: import('vue').CSSProperties, // 表头单元格的style样式,是一个object为所有表头单元格设置一样的 Style。注:CSSProperties类型就是一个对象,像正常在style中写css一样 {color: #f00}
 	 	defaultSort?: Sort // 默认的排序列的 prop 和顺序。 它的 prop 属性指定默认的排序的列,order 指定默认排序的顺序。
 	 	rowKey?: string // 行数据的 Key,用来优化 Table 的渲染。
    }
    interface Pagination {
        total?: number, // 总条目数
        currentPage: number, // 当前页数,支持 v-model 双向绑定
        pageSize: number, // 每页显示条目个数,支持 v-model 双向绑定
        pageSizes?: number[], // 每页显示个数选择器的选项设置
        layout?: string, // 组件布局,子组件名用逗号分隔
        background?: boolean // 是否为分页按钮添加背景色
    }
}

参数介绍

Table 属性
参数说明类型是否必填默认值
tableDatatable 表格的数据Array<object>
options自定义配置object
columns列 column 的配置数组Array<object>
Options 配置项
参数说明类型是否必填默认值
heightTable 的高度, 默认为自动高度。 如果 height 为 number 类型,单位 px;如果 height 为 string 类型,则这个高度会设置为 Table 的 style.height 的值,Table 的高度会受控于外部样式。string / number
maxHeightTable 的最大高度。 合法的值为数字或者单位为 px 的高度。string / number
size字段名称 对应列内容的字段名large / default /small
showHeader是否显示表头stringtrue
tooltipEffecttooltip effect 属性dark / lightdark
stripe是否为斑马纹 tablebooleanfalse
showPagination是否展示分页器booleanfalse
paginationConfig分页器配置项,详情见下方 paginationConfig 配置,(当 showPagination 为 true,该配置项必传)Pagination
rowStyle行的 style 的回调方法,也可以使用一个固定的 Object 为所有行设置一样的 Style。function({ row, rowIndex }) / object() => ‘cursor:pointer’
headerCellStyle表头单元格的style样式,是一个object为所有表头单元格设置一样的 StyleCSSProperties
defaultSort默认的排序列的 prop 和顺序。 它的 prop 属性指定默认的排序的列,order 指定默认排序的顺序{prop: string, order:{‘ascending’ / ‘descending’}, init?: any, silent?: any}
rowKey行数据的 Key,用来优化 Table 的渲染string

本项目中rowStyle需默认设置为cursor:pointer,不需要可删除此默认选项

Column 配置项
参数说明类型是否必填默认值
type对应列的类型。 如果设置了 selection 则显示多选框; 如果设置了 index 则显示该行的索引(从 1 开始计算); 如果设置了 expand 则显示为一个可展开的按钮;如果设置 image,则显示图片; 如是设置 date,则显示格式化后的日期selection / index / expand / image / date
label每一列的标题string
prop字段名称 对应列内容的字段名string
slot插槽名称,自定义列的内容 作用域参数为 { row, $index }string
width对应列的宽度string / number
align对齐方式left / center / rightleft
dateFormat显示在页面中的日期格式,当 type === date 时,可更改日期显示格式‘YYYY-MM-DD’ / ‘YYYY-MM-DD HH:mm:ss’ / ‘YYYY-MM-DD HH:mm’ / ‘YYYY-MM’YYYY-MM-DD
showOverflowTooltip当内容过长被隐藏时显示 tooltipbooleanfalse
buttons按钮组的内容Array<object>
render渲染函数,渲染这一列的每一行的单元格(row:object, index:number) => VNodeChild
headerRender渲染函数,渲染列表头({ column, index }) => VNodeChild
headerSlot自定义表头插槽名字string
sortable对应列是否可以排序, 如果设置为 ‘custom’,则代表用户希望远程排序,需要监听 Table 的 sort-change 事件boolean / ‘custom’false
children配置多级表头的数据集合, 具体用法可参考多级表头使用示例。Array<object>
paginationConfig 配置项
参数说明类型是否必填默认值
total总条目数number0
currentPage当前页数,支持 v-model 双向绑定number1
pageSize每页显示条目个数,支持 v-model 双向绑定number10
pageSizes每页显示个数选择器的选项设置number[][10, 20, 30, 40, 50, 100]
layout组件布局,子组件名用逗号分隔string‘total, sizes, prev, pager, next, jumper’
background是否为分页按钮添加背景色booleanfalse
Buttons 配置项
参数说明类型是否必填默认值
name按钮显示的名称string
command派发到 command 回调函数的指令参数string/number
size用于控制该按钮组内按钮的大小large / default / small
type用于控制该按钮组内按钮的类型primary / success / warning / danger / info
Table 插槽
插槽名说明插槽作用域
expand当列的 type 等于 expand 时,自定义展开行区域的内容{ row, index }
Table 事件
事件名说明回调参数
selection-change当选择项发生变化时会触发该事件selection
row-click当某一行被点击时会触发该事件row, column, event
cell-click当某个单元格被点击时会触发该事件row, column, cell, event
command点击按钮组某个按钮触发的事件回调command, row
size-changepageSize 改变时触发pageSize
current-changecurrentPage 改变时触发currentPage
pagination-changecurrentPage 或者 pageSize 改变时触发currentPage ,pageSize
sort-change当表格的排序条件发生变化的时候会触发该事件{ column, prop, order }

其他

此文档只提供基本的封装思路,如需使用到更多的业务场景,可自行扩展。如: 搜索,api 请求等

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐