前言
在table数据请求的过程 过于频繁,从而导致页面代码多,封装的目的就是减少相同代码,提高在开发中的效率
封装的目的及好处
- 隐藏实现细节,逻辑统一处理,只需传递参数内容
- 低耦合,利于全局扩展,继承所有elemenet plus table的属性及方法
- 只需配置一个参数及可控制列的显示和隐藏
源码地址
源码
import { ExtractPropTypes, PropType } from 'vue'
export const tableProps = {
/** 是否显示分页 */
pagination: {
type: Boolean,
default: true
},
/** 请求地址 */
url: {
type: String,
default: ''
},
/** 默认数据源 */
data: {
type: Array,
default: () => []
},
/** 请求参数 */
params: {
type: Object,
default: () => ({})
},
/** 是否立即请求 */
init: {
type: Boolean,
default: true
},
/** 是否重新加载 */
reload: {
type: Boolean,
default: false
},
/** 默认显示列 */
defaultColumns: {
type: Array as PropType<string[]>,
default: () => []
}
}
export type TableProps = ExtractPropTypes<typeof tableProps>
import { SetupContext } from 'vue'
import { ElTable, ElPagination, vLoading } from 'element-plus'
import { TableProps, tableProps } from './table-props'
import './table.scss'
import { useDataSource } from './composable/userDataSource'
import tableColumn from './TableColumn'
import initMethods from './tableMethods'
export default defineComponent({
directives: { loading: vLoading },
props: tableProps,
emits: ['load', 'update:reload'],
setup(props: TableProps, context: SetupContext) {
const { slots, attrs, expose } = context
const tableHeader = slots['table-header']
const tableFooter = slots['table-footer']
const { paginationState, table, onSortChange } = useDataSource(props, context)
const { renderVNode } = tableColumn(props, context)
// 初始化 el-table 方法
const refTable = ref()
const methodObj = initMethods(refTable)
expose(methodObj)
return () => {
// 分页器
const paginationEl = (
<ElPagination
v-model:current-page={paginationState.currentPage}
v-model:page-size={paginationState.pageSize}
page-sizes={[10, 20, 30, 50]}
total={paginationState.total}
layout="total, sizes, prev, pager, next, jumper"
class="mt-10px justify-end"
/>
)
return (
<div class="c-table h-full flex-1 flex flex-col">
{/* 表格头区域 */}
<div class="c-table-header mb-10px">{tableHeader && tableHeader()}</div>
{/* 表格区域 */}
<ElTable
ref={refTable}
v-loading={table.loading}
data={table.dataSource}
{...attrs}
onSort-change={onSortChange}>
{renderVNode.value}
</ElTable>
{/* 表格尾区域 */}
<div class="c-table-footer">
{tableFooter && tableFooter()}
{props.pagination && paginationEl}
</div>
</div>
)
}
}
})
import { SetupContext, VNode } from 'vue'
import { ElPopover, ElCheckbox, ElCheckboxGroup, ElButton } from 'element-plus'
import { Icon } from '@/components'
import { TableProps } from './table-props'
// 默认固定列
const fixedColumn = ['selection', 'index', 'operation']
export default function useColumn(props: TableProps, context: SetupContext) {
// 默认列
const defaultVNode = ref<VNode[]>([])
// 需要渲染的列
const renderVNode = ref<VNode[]>([])
// 复选框选中的列名
const checkboxGroupValue = ref<string[]>([])
const getProps = (node: VNode) => node.props as any
/** 数据列 */
const dataColumn = computed(() =>
defaultVNode.value.filter((node) => !fixedColumn.includes(getProps(node).type))
)
/** 表格列 用作 作为 多选框数据源 */
const checkboxData = computed(() => {
return dataColumn.value.map((item) => {
const props = getProps(item)
return {
field: props.prop,
label: props.label
}
})
})
/** 设置渲染列 */
const setColumn = () => {
const result = defaultVNode.value.filter((node) => {
const props = getProps(node)
if (fixedColumn.includes(props.type)) return node
return checkboxGroupValue.value.includes(getProps(node).prop)
})
renderVNode.value = result
}
/** 重置渲染列 */
const resetColumn = () => {
// 如果设置默认显示列
if (props.defaultColumns.length > 0) {
checkboxGroupValue.value = props.defaultColumns
} else {
checkboxGroupValue.value = dataColumn.value.map((node) => getProps(node).prop)
}
setColumn()
}
const init = () => {
if (context.slots.default) {
defaultVNode.value = context.slots.default()
resetColumn()
}
}
init()
// 获取操作列
const operation = defaultVNode.value.filter((node) => getProps(node).type === 'operation')
if (operation.length > 0) {
// 获取操作列 所有插槽
const operationSorts = operation[0].children as any
// 通过操作 header插槽 自定义内容
operationSorts.header = () => {
const popoverSlots = {
reference: () => <Icon name="ic:round-more-vert" class="ml-5px cursor-pointer" />
}
const popoverContent = () => {
return (
<>
<ElCheckboxGroup v-model={checkboxGroupValue.value}>
{checkboxData.value.map((item) => {
return <ElCheckbox label={item.field}>{item.label}</ElCheckbox>
})}
</ElCheckboxGroup>
<div class="mt-5px">
<ElButton type="primary" size="small" onClick={setColumn}>
确定
</ElButton>
<ElButton class="ml-5px" size="small" onClick={resetColumn}>
重置
</ElButton>
</div>
</>
)
}
return (
<div class="flex flex-y-center">
{(operation[0].props as any).label}
<ElPopover v-slots={popoverSlots}>{popoverContent()}</ElPopover>
</div>
)
}
} else {
renderVNode.value = defaultVNode.value
}
return {
renderVNode
}
}
import { Ref } from 'vue'
export const methods = [
'clearSelection',
'getSelectionRows',
'toggleRowSelection',
'toggleAllSelection',
'toggleRowExpansion',
'setCurrentRow',
'clearSort',
'clearFilter',
'doLayout',
'sort',
'scrollTo',
'setScrollTop',
'setScrollLeft'
]
const initMethods = (refTable: Ref) => {
const methodObj: Record<string, (...args: any) => void> = {}
methods.forEach((method) => {
methodObj[method] = (...args: any) => {
if (refTable.value && refTable.value[method]) {
refTable.value[method](...args)
}
}
})
return methodObj
}
export default initMethods
文档(继承eltable所有属性)
参数 | 说明 | 类型 | 可选值 | 默认值 |
---|
pagination | 是否分页 | boolean | true/false | true |
url | 后台请求地址 | string | - | - |
data | 静态数据源 | array | - | - |
params | 后台请求参数 | object | - | - |
init | 是否已经发送请求 | boolean | true/false | true |
v-model:reload | 是否重新加载数据 | boolean | true/false | true |
v-model:firstPage | 是否回到第一页 | boolean | true/false | false |
defaultColumns(用于控制列的显示) | 默认显示列 | array | - | - |
事件(继承eltable所有事件)
事件名 | 说明 | 事件参数 |
---|
load | 数据加载完成事件 | (data:any)=>void |
插槽
插槽名 | 说名 | 子标签 |
---|
table-header | table 顶部插槽 | - |
table-footer | table 底部插槽 | - |
基本用法
<template>
<PageContainer>
<c-table :data="tableData" :pagination="false" style="width: 100%">
<template #table-header>
<el-button type="primary">
<icon name="ep:plus">新增</icon>
</el-button>
</template>
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</c-table>
</PageContainer>
</template>
<script lang="ts" setup>
import { PageContainer, CTable, Icon } from '@/components'
const tableData = [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
}
]
</script>

进阶用法
<template>
<PageContainer>
<template #header>
<el-form inline @submit.prevent>
<el-form-item label="姓名:">
<el-input v-model="queryParams.name" class="w-250px" @change="onQuery" />
</el-form-item>
<el-form-item label="性别:">
<el-select v-model="queryParams.sex" class="w-250px">
<el-option label="男" value="男" />
<el-option label="女" value="女" />
</el-select>
</el-form-item>
<el-form-item label="是否已婚:">
<el-select v-model="queryParams.married" class="w-250px">
<el-option label="是" value="是" />
<el-option label="否" value="否" />
</el-select>
</el-form-item>
<el-form-item label="生日:">
<el-date-picker v-model="queryParams.birth" type="date" />
</el-form-item>
<el-form-item label="地址:">
<el-input v-model="queryParams.addr" class="w-250px" @change="onQuery" />
</el-form-item>
<el-form-item label="邮箱:">
<el-input v-model="queryParams.email" class="w-250px" @change="onQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click.stop="onQuery">
<icon name="ep:search"> 查询 </icon>
</el-button>
<el-button>
<icon name="ep:brush">重置</icon>
</el-button>
<el-button @click="onClick">全选/反选</el-button>
</el-form-item>
</el-form>
</template>
<c-table ref="refTable" v-model:reload="reload" url="/user" :params="queryParams">
<template #table-header>
<el-button type="primary">
<icon name="ep:plus">新增用户</icon>
</el-button>
</template>
<el-table-column type="selection" width="55" />
<el-table-column type="index" width="50" />
<el-table-column label="id" prop="id" sortable show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="name" sortable></el-table-column>
<el-table-column label="年龄" prop="age"></el-table-column>
<el-table-column label="财产" prop="asset"></el-table-column>
<el-table-column label="是否已婚" prop="married"></el-table-column>
<el-table-column label="生日" prop="birth" show-overflow-tooltip></el-table-column>
<el-table-column label="地址" prop="addr" show-overflow-tooltip></el-table-column>
<el-table-column label="邮箱" prop="email" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" type="operation">
<el-button link type="primary" size="small">修改</el-button>
<el-button link type="danger" size="small">删除</el-button>
</el-table-column>
</c-table>
</PageContainer>
</template>
<script lang="ts" setup>
import { CTable, PageContainer, Icon } from '@/components'
defineOptions({ name: 'Workbench' })
const queryParams = reactive({
name: undefined,
birth: undefined,
sex: undefined,
married: undefined,
addr: '',
email: ''
})
const reload = ref(false)
const onQuery = () => {
reload.value = true
}
const refTable = ref()
const onClick = () => {
refTable.value.toggleAllSelection()
}
</script>
