最近公司项目中频繁会使用到 table 表格,而且前端技术这一块也用到了 vue3 和 TypeScript 来开发,所以基于 element plus table 做了一个二次封装的组件。
效果图
1. Table 组件封装
src/components/Table/index.vue
<template> <div> <el-table :data="tableData" v-bind="_options" @selection-change="handleSelectionChange" @row-click="handleRowClick" @cell-click="handleCellClick"> <template v-for="(col, index) in columns" :key="index"> <!---复选框, 序号 (START)--> <el-table-column v-if="col.type === 'index' || col.type === 'selection' || col.type === 'expand'" :align="col.align" :label="col.label" :type="col.type" :index="indexMethod" :width="col.width" /> <!---复选框, 序号 (END)--> <!---图片 (START)--> <el-table-column v-else-if="col.type === 'image'" :align="col.align" :label="col.label" :width="col.width"> <template #default="{ row }"> <!-- 如需更改图片 size,可自行配置参数 --> <el-image preview-teleported :hide-on-click-modal="true" :preview-src-list="[row[col.prop!]]" :src="row[col.prop!]" fit="cover" style="width:40px;height:40px;border-radius:8px" /> </template> </el-table-column> <!---图片 (END)--> <!-- 自定义 slot (START) --> <el-table-column :show-overflow-tooltip="col.showOverflowTooltip" v-else-if="col.slot" :align="col.align" :label="col.label" :width="col.width"> <template #default="scope"> <slot :name="col.slot" :row="scope.row" :index="scope.$index"></slot> </template> </el-table-column> <!-- 自定义 slot (END) --> <!-- 如果传递按钮数组,就展示按钮组 START--> <el-table-column v-else-if="col.buttons?.length" :align="col.align" :label="col.label" :width="col.width"> <template #default="scope"> <el-button-group> <el-button v-for="(btn, index) in col.buttons" size="small" :key="index" :type="btn.type" @click="handleAction(btn.command, scope)" >{{ btn.name }}</el-button > </el-button-group> </template> </el-table-column> <!-- 如果传递按钮数组,就展示按钮组 END--> <!-- 默认渲染列 (START) --> <el-table-column :show-overflow-tooltip="col.showOverflowTooltip" v-else :label="col.label" :prop="col.prop" :align="col.align" :width="col.width" /> <!-- 默认渲染列 (END) --> </template> </el-table> <!-- 分页器 --> <div v-if="_options.showPagination" > <el-pagination v-bind="_paginationConfig" @size-change="pageSizeChange" @current-change="currentPageChange" /> </div> </div> </template> <script lang="ts" setup> interface TableProps { tableData: Array<object> // table 的数据 columns: Table.Column[] // 每列的配置项 options?: Table.Options } const props = defineProps<TableProps>() // 设置 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) }) interface EmitEvent { (e: 'selection-change', params: any): void // 当选择项发生变化时会触发该事件 (e: 'row-click', row: any, column: any, event: Event): void // 当某一行被点击时会触发该事件 (e: 'cell-click', row: any, column: any, cell: any, event: Event): void // 当某个单元格被点击时会触发该事件 (e: 'command', command: Table.command, row: any): void // 按钮组事件 (e: 'size-change', pageSize: number): void // pageSize 事件 (e: 'current-change', currentPage: number): void // currentPage 按钮组事件 (e: 'pagination-change', currentPage: number, pageSize: number): void // currentPage 或者 pageSize 改变触发 } const emit = defineEmits<EmitEvent>() // 自定义索引 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, scope: any) => { emit('command', command, scope.row) } // 多选事件 const handleSelectionChange = (val: any) => { emit('selection-change', val) } // 当某一行被点击时会触发该事件 const handleRowClick = (row: any, column: any, event: Event) => { emit('row-click', row, column, event) } // 当某个单元格被点击时会触发该事件 const handleCellClick = (row: any, column: any, cell: any, event: Event) => { emit('cell-click', row, column, cell, event) } </script> <style lang="scss" scoped> :deep(.el-image__inner) { transition: all 0.3s; cursor: pointer; &:hover { transform: scale(1.2); } } </style>
2. 页面引用
src/views/table/index.vue
基本表格
<template> <div > <h1 > element-plus table 表格二次封装 <span >(此标题样式使用 Tailwind Css 生成)</span> </h1> <el-card > <template #header> <div > <span>基本表格</span> </div> </template> <Table :columns="tableColumn" :table-data="tableData" @selection-change="handleSelection" @command="handleAction"> <template #date="{ row }"> <span> {{ row.date }}</span> </template> <!-- 如果不传入按钮组的数据就使用自定义插槽的方式 --> <!-- <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' // 本项目 Table 组件自动引入,如复制此代码,需引入 Table 组件后使用 interface User { date: string name: string address: string } // 基本表格数据 const tableData: User[] = [ { date: '2016-05-022016-05-022016-05-022016-05-022016-05-02', name: '佘太君', address: '上海市普陀区金沙江路 1516 弄' }, { date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { date: '2016-05-01', name: '王小帅', address: '上海市普陀区金沙江路 1519 弄' }, { date: '2016-05-03', name: '王小呆', address: '上海市普陀区金沙江路 1516 弄' } ] 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 > <h1 > element-plus table 表格二次封装 <span >(此标题样式使用 Tailwind Css 生成)</span> </h1> <!-- 带有分页的表格 --> <el-card > <template #header> <div > <span>带有分页的表格</span> </div> </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' // 本项目 Table 组件自动引入,如复制此代码,需引入 Table 组件后使用 const route = useRoute() const router = useRouter() interface State { tableDemoList: TableDemoItem[] pageInfo: PageInfo options: Table.Options } const state = reactive<State>({ pageInfo: { currentPage: 1, pageSize: 10, total: 0 }, 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>
3.表格配置项
src/config/table.ts
参数可参考下面参数介绍:
// 基本表格配置 export const tableColumn = [ { type: 'selection', width: '50', label: 'No.' }, { type: 'index', width: '50', label: 'No.' }, { prop: 'name', label: '名字' }, { prop: 'date', slot: 'date', label: '日期', showOverflowTooltip: true }, { prop: 'address', label: '地址', showOverflowTooltip: true }, { width: '120', label: '操作', buttons: [ { name: '编辑', type: 'success', command: 'edit' }, { name: '删除', type: 'danger', command: 'delete' } ] } ] as Table.Column[] // 带有分页的表格配置 export const tableDemoColumn = [ { 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' } ] } ] as Table.Column[]
4.参数类型声明
src/typings/table/index.d.ts
声明为全局的类型,方便使用。
// table 表格 declare namespace Table { type Type = 'selection' | 'index' | 'expand' | 'image' type Size = 'large' | 'default' | 'small' type Align = 'center' | 'left' | 'right' type command = string | number interface ButtonItem { name: string, command: command, type?: 'primary' | 'success' | 'warning' | 'danger' | 'info', } interface Column { type?: Type, // 对应列的类型。 如果设置了 selection 则显示多选框; 如果设置了 index 则显示该行的索引(从 1 开始计算); 如果设置了 expand 则显示为一个可展开的按钮 label: string, prop?: string, slot?: string width?: string, align?: Align, showOverflowTooltip?: boolean, buttons?: ButtonItem[], } 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。 } interface Pagination { total?: number, // 总条目数 currentPage: number, // 当前页数,支持 v-model 双向绑定 pageSize: number, // 每页显示条目个数,支持 v-model 双向绑定 pageSizes?: number[], // 每页显示个数选择器的选项设置 layout?: string, // 组件布局,子组件名用逗号分隔 background?: boolean // 是否为分页按钮添加背景色 } }
参数介绍
Table 属性
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
tableData | table 表格的数据 | Array<object> | 是 | — |
options | 自定义配置 | object | 否 | — |
columns | 列 column 的配置数组 | object | 是 | — |
Options 配置项
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
height | Table 的高度, 默认为自动高度。 如果 height 为 number 类型,单位 px;如果 height 为 string 类型,则这个高度会设置为 Table 的 style.height 的值,Table 的高度会受控于外部样式。 | string / number | 否 | — |
maxHeight | Table 的最大高度。 合法的值为数字或者单位为 px 的高度。 | string / number | 否 | — |
size | 字段名称 对应列内容的字段名 | large / default /small | 否 | — |
showHeader | 是否显示表头 | string | 否 | true |
tooltipEffect | tooltip effect 属性 | dark / light | 否 | dark |
stripe | 是否为斑马纹 table | boolean | 否 | false |
showPagination | 是否展示分页器 | boolean | 否 | false |
paginationConfig | 分页器配置项,详情见下方 paginationConfig 配置,(当 showPagination 为 true,该配置项必传) | Pagination | 否 | — |
rowStyle | 行的 style 的回调方法,也可以使用一个固定的 Object 为所有行设置一样的 Style。 | function({ row, rowIndex }) / object | 否 | () => ‘cursor:pointer’ |
本项目中 rowStyle 需默认设置为 cursor:pointer,不需要可删除此默认选项
Column 配置项
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
type | 对应列的类型。 如果设置了 selection 则显示多选框; 如果设置了 index 则显示该行的索引(从 1 开始计算); 如果设置了 expand 则显示为一个可展开的按钮;如果设置 image,则显示图片。(Column 可以设置多个 type) | selection / index / expand / image | 否 | — |
label | 每一列的标题 | string | 是 | — |
prop | 字段名称 对应列内容的字段名 | string | 否 | — |
slot | 插槽名称,自定义列的内容 作用域参数为 { row, $index } |
string | 否 | — |
width | 对应列的宽度 | string / number | 否 | — |
align | 对齐方式 | left / center / right | 否 | left |
showOverflowTooltip | 当内容过长被隐藏时显示 tooltip | boolean | 否 | false |
buttons | 按钮组的内容 | Array<object> | 否 | — |
paginationConfig 配置项
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
total | 总条目数 | number | 是 | 0 |
currentPage | 当前页数,支持 v-model 双向绑定 | number | 是 | 1 |
pageSize | 每页显示条目个数,支持 v-model 双向绑定 | number | 是 | 10 |
pageSizes | 每页显示个数选择器的选项设置 | number[] | 否 | [10, 20, 30, 40, 50, 100] |
layout | 组件布局,子组件名用逗号分隔 | string | 否 | ‘total, sizes, prev, pager, next, jumper’ |
background | 是否为分页按钮添加背景色 | boolean | 否 | false |
Buttons 配置项
参数 | 说明 | 类型 | 是否必填 | 默认值 |
---|---|---|---|---|
name | 按钮显示的名称 | string | 是 | — |
command | 派发到 command 回调函数的指令参数 | string/number | 是 | — |
type | 当前按钮的类型 | primary / success / warning / danger / info | 否 | — |
Table 事件
事件名 | 说明 | 回调参数 |
---|---|---|
selection-change | 当选择项发生变化时会触发该事件 | selection |
row-click | 当某一行被点击时会触发该事件 | row, column, event |
cell-click | 当某个单元格被点击时会触发该事件 | row, column, cell, event |
command | 点击按钮组某个按钮触发的事件回调 | command, row |
size-change | pageSize 改变时触发 | pageSize |
current-change | currentPage 改变时触发 | currentPage |
pagination-change | currentPage 或者 pageSize 改变时触发 | currentPage ,pageSize |
其他
此文档只提供基本的封装思路,如需使用到更多的业务场景,可自行扩展。如: 搜索,api 请求等
作者:zhfy 啊