利用Vue3+Element Plus封装公共表格组件(带源码)
作者:YanaDH
最近公司项目中频繁会使用到table表格,而且前端技术这一块也用到了vue3来开发,所以基于element plus table做了一个二次封装的组件,这篇文章主要给大家介绍了关于利用Vue3+Element Plus封装公共表格组件的相关资料,需要的朋友可以参考下
1 前言
由于项目中有很多菜单都是列表数据的展示,为避免太多重复代码,故将 Element Plus 的 Table 表格进行封装,实现通过配置展示列表数据
2 功能
- 支持自动获取表格数据
- 支持数据列配置及插槽
- 支持操作列配置及插槽
- 支持多选框配置
- 支持表尾配置及插槽
- 支持分页显示
3 实现步骤
3.1 复制基本表格
到 Element Plus官网复制一份最简单的 Table 代码,并删除多余代码
<template> <el-table :data="tableData"> <el-table-column prop="date" label="Date" /> <el-table-column prop="name" label="Name" /> <el-table-column prop="state" label="State" /> <el-table-column prop="city" label="City" /> <el-table-column prop="address" label="Address" /> <el-table-column prop="zip" label="Zip" /> <el-table-column fixed="right" label="Operations"> <template #default> <el-button link type="primary" size="small" @click="handleClick">Detail</el-button> </template> </el-table-column> </el-table> </template> <script lang="ts" setup> const tableData = [ { date: '2016-05-03', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036', tag: 'Home' }, { date: '2016-05-02', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036', tag: 'Office' }, { date: '2016-05-04', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036', tag: 'Home' }, { date: '2016-05-01', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036', tag: 'Office' } ] const handleClick = () => { console.log('click') } </script>
3.2 支持自动获取表格数据
tableData 数据改为从 props.api 接口获取
3.3 支持数据列配置及插槽
3.3.1 自动生成列
数据列(除多选框与操作列外)通过 props.columns 自动生成
<el-table-column v-for="item in props.columns" :key="item.prop" :prop="item.prop" :label="item.label" :sortable="item.sortable ? 'custom' : false" :width="item.width" > </el-table-column>
interface TableConfigInterface { api: string // 表格数据获取接口 columns: { // 显示列 prop: string // 键名 label?: string // 表头显示名称 formatter?: (row: unknown) => string // 自定义单元格格式化方法,参数为当前行数据 tooltip?: string // 表头 tooltip sortable?: boolean // 是否可以排序 width?: number | string // 宽度 style?: string // 单元格样式 labelStyle?: string // 表头样式 }[] }
3.2.2 支持表头自定义及插槽
- el-table-column 使用
<template #header>
自定义表头<slot :name="item.prop + 'Header'">
支持插槽- labelStyle 支持样式自定义
- 支持 tooltip
<el-table-column v-for="item in props.columns" :key="item.prop" :prop="item.prop" :label="item.label" :sortable="item.sortable ? 'custom' : false" :width="item.width" > <template #header> <slot :name="item.prop + 'Header'"> <div class="inline-flex" :style="item.labelStyle"> <span>{{ item.label }}</span> <el-tooltip popper-class="table-tooltip" effect="dark" placement="top-start" :content="item.tooltip" v-if="item.tooltip" > <el-icon><i-ep-Warning /></el-icon> </el-tooltip> </div> </slot> </template> </el-table-column>
3.2.3 支持单元格自定义及插槽
- el-table-column 使用
<template #default="scope">
自定义单元格<slot :name="item.prop" :row="scope.row">
支持插槽- style 属性支持自定义样式
- formatter 方法支持自定义显示内容
<el-table-column v-for="item in props.columns" :key="item.prop" :prop="item.prop" :label="item.label" :sortable="item.sortable ? 'custom' : false" :width="item.width" > <template #header> <slot :name="item.prop + 'Header'"> <div class="inline-flex" :style="item.labelStyle"> <span>{{ item.label }}</span> <el-tooltip popper-class="table-tooltip" effect="dark" placement="top-start" :content="item.tooltip" v-if="item.tooltip" > <el-icon><i-ep-Warning /></el-icon> </el-tooltip> </div> </slot> </template> <template #default="scope"> <slot :name="item.prop" :row="scope.row"> <div :style="item.style"> <span v-if="item.formatter">{{ item.formatter(scope.row) }}</span> <span v-else>{{ scope.row[item.prop] }}</span> </div> </slot> </template> </el-table-column>
3.3 支持操作列配置及插槽
- el-table-column 使用
<template #default="scope">
自定义操作列<slot :name="item.prop" :row="scope.row">
支持插槽- visible 方法支持自定义按钮显示逻辑
<el-table-column fixed="right" label="操作" :width="props.operation?.width" v-if="props.operation?.columns" > <template #default="scope"> <slot name="operations" :row="scope.row"> <span v-for="item in props.operation?.columns" :key="item.text || item.icon"> <el-button v-if="setVisible(scope.row, item.visible)" :type="item.type" :link="item.link" :plain="item.plain" @click="item.click(scope.row)" size="small" class="margin-right: 4px" > <el-icon v-if="item.icon" :class="item.icon"></el-icon> {{ item.text }} </el-button> </span> </slot> </template> </el-table-column>
// 操作框逻辑 const setVisible = (row: unknown, visible?: (row: unknown) => boolean) => { if (!visible || visible(row)) { return true } return false }
3.4 支持多选框配置
<el-table>
增加 selection 列
<el-table-column fixed :selectable="setSelectable" type="selection" v-if="showSelectBox" />
TableConfigInterface 增加 rowKey、selectable
interface TableConfigInterface { // ...... rowKey?: string // 行数据的 Key selectable?: boolean | ((row: unknown) => boolean) // 当前行多选框是否可以勾选,参数为当前行数据,默认为 false } const props = withDefaults(defineProps<TableConfigInterface>(), { rowKey: 'id', }) // 多选框逻辑 const disabledList = reactive<string[]>([]) // 禁止勾选的数据 const showSelectBox = computed(() => props.selectable && disabledList.length < tableData.length) const setSelectable = (row: unknown) => { const selectable = typeof props.selectable === 'boolean' ? props.selectable : props.selectable?.(row) if (!selectable && !disabledList.includes(row?.[props.rowKey])) { disabledList.push(row?.[props.rowKey]) } return selectable }
3.5 支持表尾配置及插槽
<el-table>
增加 @selection-change 及 ref 配置<slot name="footer">
支持插槽- visible 方法支持自定义按钮显示逻辑
<el-table v-loading="loading" :data="tableData" @selection-change="handleSelectionChange" table-layout="auto" ref="tableRef" > <!-- ...... --> </el-table> <div v-if="showSelectBox" class="p-14"> <el-checkbox v-model="isSelected" @click="tableRef?.toggleAllSelection()" :indeterminate="indeterminate" label="全选" style="vertical-align: middle; margin-right: 10px" /> <slot name="footer" :rows="selectionRows"> <span v-for="item in props.footer?.operations" :key="item.text || item.icon"> <el-button v-if="item.visible ? item.visible() : true" :type="item.type || 'primary'" :link="item.link" :plain="item.plain" :disabled="!selectionRows.length" @click="item.click(selectionRows)" style="margin-left: 10px" > <el-icon v-if="item.icon" :class="item.icon"></el-icon> {{ item.text }} </el-button> </span> </slot> </div>
const tableRef = ref() const isSelected = ref(false) // 是否有选中数据 const selectionRows = ref<unknown[]>([]) // 当前选中的数据 const handleSelectionChange = (rows: unknown[]) => { selectionRows.value = rows isSelected.value = rows.length > 0 } const indeterminate = computed( () => selectionRows.value.length > 0 && selectionRows.value.length < tableData.length - disabledList.length )
3.6 支持分页显示
最底部增加
<el-pagination></el-pagination>
<el-pagination background :total="tableData.length" :layout="props.layout" v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize" @current-change="getTableData" @size-change="getTableData" class="p-y-20" > </el-pagination>
interface TableConfigInterface { // ...... layout?: string // 组件布局 } const pagination = ref({ currentPage: 1, pageSize: 10 })
4 使用方法
<template> <TableComponent v-bind="tableConfig"> <template #nameHeader> <div>姓名</div> </template> <template #name> <div>Yana</div> </template> </TableComponent> </template> <script setup lang="ts"> const tableConfig: TableConfigInterface = { api: 'getTableData', columns: [ { prop: 'date', label: 'Date', tooltip: 'This is Date' }, { prop: 'name', label: 'Name' }, { prop: 'state', label: 'State' }, { prop: 'city', label: 'City' }, { prop: 'address', label: 'Address' }, { prop: 'zip', label: 'Zip', style: 'color: red', labelStyle: 'color: red', sortable: true } ], operation: { columns: [ { text: '编辑', click: () => {}, visible: (row) => row.date === '2016-05-07' } ] }, rowKey: 'date', selectable: (row) => row.date === '2016-05-07', footer: { operations: [ { text: '删除', click: () => {} } ] } } </script>
5 源码
<template> <div v-loading="loading" class="table-wrapper"> <el-table :data="tableData" :max-height="props.maxHeight" :default-sort="props.defaultSort" @selection-change="handleSelectionChange" @sort-change="handleSortChange" @row-click="goDetail" :row-style="{ cursor: 'pointer' }" table-layout="auto" ref="tableRef" > <el-table-column fixed :selectable="setSelectable" type="selection" v-if="showSelectBox" /> <el-table-column v-for="item in props.columns" :key="item.prop" :prop="item.prop" :label="item.label" :sortable="item.sortable ? 'custom' : false" :width="item.width" > <template #header> <slot name="header"> <div class="inline-flex" :style="item.labelStyle"> <span>{{ item.label }}</span> <el-tooltip popper-class="table-tooltip" effect="dark" placement="top-start" :content="item.tooltip" v-if="item.tooltip" > <el-icon><i-ep-Warning /></el-icon> </el-tooltip> </div> </slot> </template> <template #default="scope"> <slot :name="item.prop" :row="scope.row"> <div :style="item.style"> <span v-if="item.formatter">{{ item.formatter(scope.row) }}</span> <span v-else>{{ scope.row[item.prop] }}</span> </div> </slot> </template> </el-table-column> <el-table-column fixed="right" label="操作" :width="props.operation?.width" v-if="props.operation?.columns" > <template #default="scope"> <slot name="operations" :row="scope.row"> <span v-for="item in props.operation?.columns" :key="item.text || item.icon"> <el-button v-if="setVisible(scope.row, item.visible)" :type="item.type" :link="item.link" :plain="item.plain" @click="item.click(scope.row)" size="small" style="margin-right: 4px" > <el-icon v-if="item.icon" :class="item.icon"></el-icon> {{ item.text }} </el-button> </span> </slot> </template> </el-table-column> </el-table> <div v-if="showSelectBox" class="p-14"> <el-checkbox v-model="isSelected" @click="tableRef?.toggleAllSelection()" :indeterminate="indeterminate" label="全选" style="vertical-align: middle; margin-right: 10px" /> <slot name="footer" :rows="selectionRows"> <span v-for="item in props.footer?.operations" :key="item.text || item.icon"> <el-button v-if="item.visible ? item.visible() : true" :type="item.type || 'primary'" :link="item.link" :plain="item.plain" :disabled="!selectionRows.length" @click="item.click(selectionRows)" style="margin-left: 10px" > <el-icon v-if="item.icon" :class="item.icon"></el-icon> {{ item.text }} </el-button> </span> </slot> </div> </div> <el-pagination background :total="tableData.length" :layout="props.layout" v-model:current-page="pagination.currentPage" v-model:page-size="pagination.pageSize" @current-change="getTableData" @size-change="getTableData" class="p-y-20" /> </template> <script lang="ts" setup> interface OperationInterface { click: (row: unknown) => void // 按钮点击方法,参数为当前行数据 text?: string // 按钮显示文字 icon?: string // 按钮 icon visible?: (row?: unknown) => boolean // 设置按钮是否可见,参数为当前行数据,默认为 true type?: string // 按钮类型['primary'| 'success'| 'warning'| 'danger'| 'info'] link?: boolean // 是否为链接按钮 plain?: boolean // 是否为朴素按钮 } interface TableConfigInterface { api: string // 表格数据获取接口 rowKey?: string // 行数据的 Key columns: { // 显示列 prop: string // 键名 label?: string // 表头显示名称 formatter?: (row: unknown) => string // 自定义单元格格式化方法,参数为当前行数据 tooltip?: string // 表头 tooltip sortable?: boolean // 是否可以排序 width?: number | string // 宽度 style?: string // 单元格样式 labelStyle?: string // 表头样式 }[] selectable?: boolean | ((row: unknown) => boolean) // 当前行多选框是否可以勾选,参数为当前行数据,默认为 false operation?: { // 操作列 columns: OperationInterface[] width?: number | string // 宽度 } footer?: { // 操作列 operations: OperationInterface[] } defaultSort?: { // 默认排序 prop: string // 默认排序的列 order?: string // ['ascending'| 'descending'], 没有指定 order, 则默认顺序是 ascending } maxHeight?: number | string // 表格最大高度 layout?: string } const props = withDefaults(defineProps<TableConfigInterface>(), { rowKey: 'id', layout: 'prev, pager, next, total' }) const pagination = ref({ currentPage: 1, pageSize: 10 }) const tableRef = ref() let tableData = reactive<unknown[]>([]) // 多选框逻辑 const isSelected = ref(false) // 是否有选中数据 const selectionRows = ref<unknown[]>([]) // 当前选中的数据 const handleSelectionChange = (rows: unknown[]) => { selectionRows.value = rows isSelected.value = rows.length > 0 } const disabledList = reactive<string[]>([]) // 禁止勾选的数据 const setSelectable = (row: unknown) => { const selectable = typeof props.selectable === 'boolean' ? props.selectable : props.selectable?.(row) if (!selectable && !disabledList.includes(row?.[props.rowKey])) { disabledList.push(row?.[props.rowKey]) } return selectable } const indeterminate = computed( () => selectionRows.value.length > 0 && selectionRows.value.length < tableData.length - disabledList.length ) const showSelectBox = computed(() => props.selectable && disabledList.length < tableData.length) // 操作框逻辑 const showOperation = ref(false) const setVisible = (row: unknown, visible?: (row: unknown) => boolean) => { if (!visible || visible(row)) { showOperation.value = true return true } return false } // 排序 const handleSortChange = (data: { prop: string; order: string | null }) => { const { prop, order } = data console.log(prop, order) // getTableData } // 跳转详情页 const goDetail = (row: unknown) => { console.log(row) } // 发送接口 const loading = ref(true) const getTableData = () => { loading.value = true showOperation.value = false tableData = [ { date: '2016-05-02', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036' }, { date: '2016-05-03', 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-05', 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' }, { date: '2016-05-08', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036' }, { date: '2016-05-09', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036' }, { date: '2016-05-10', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036' }, { date: '2016-05-11', name: 'Tom', state: 'California', city: 'Los Angeles', address: 'No. 189, Grove St, Los Angeles', zip: 'CA 90036' } ] loading.value = false } getTableData() </script> <style lang="scss" scoped> .table-wrapper { border-top: 1px solid #eaeaea; border-left: 1px solid #eaeaea; border-right: 1px solid #eaeaea; } .inline-flex { display: inline-flex; align-items: center; } .p-14 { border-bottom: 1px solid #eaeaea; padding: 14px; } .p-y-20 { padding-top: 20px; padding-bottom: 20px; justify-content: center; } </style> <style lang="scss"> .table-tooltip { max-width: 220px; } </style>
总结
到此这篇关于利用Vue3+Element Plus封装公共表格组件的文章就介绍到这了,更多相关Vue3 Element Plus封装公共表格组件内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!