文章目录
1 前言2 功能3 实现步骤3.1 复制基本表格3.2 支持自动获取表格数据3.3 支持数据列配置及插槽3.3.1 自动生成列3.2.2 支持表头自定义及插槽3.2.3 支持单元格自定义及插槽 3.3 支持操作列配置及插槽3.4 支持多选框配置3.5 支持表尾配置及插槽3.6 支持分页显示 4 使用方法5 源码
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>