封装组件库-表格
2023, Mar 12
实现表格的封装
基于Element-plus框架的Table表格组件进行封装。
实现的功能
- 可配置型,可维护性高
- 具备
element-plus
原有表格的所有功能 - 也可以自行拓展更多的功能
初始配置
- 注册路由组件table,配置为Container组件的子路由组件
- 采用路由懒加载,箭头函数异步引入组件。
- 注册通用组件table,为路由组件的子组件,以便组件间通信。
- 将通用组件table注册为全局组件
实现基础表格
- 设计接口类型:
export interface TableOptions {
//表头
label: string,
//字段名称
prop?: string,
//列宽度
width?: string | number,
//对齐方式
align?: 'left' | 'center' | 'right',
//自定义列表模版名称
slot?: string
}
- 配置基本数据及配置
tableData.value = [
{
date: '2016-05-03',
name: 'Tom1',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-02',
name: 'Tom2',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-04',
name: 'Tom3',
address: 'No. 189, Grove St, Los Angeles',
},
{
date: '2016-05-01',
name: 'Tom4',
address: 'No. 189, Grove St, Los Angeles',
},
]
let options: TableOptions[] = [
{
label: '日期',
prop: 'date',
align: 'center',
},
{
label: '姓名',
prop: 'name',
align: 'center',
},
{
label: '地址',
prop: 'address',
align: 'center',
}
]
- 子组件实现基本表格结构
let props = defineProps({
//表格的配置项
options: {
type: Array as PropType<TableOptions[]>,
required: true
},
//表格数据
data:{
type: Array as PropType<any[]>,
required: true
}
})
<el-table v-bind="$attrs" :data="data">
<template v-for="(item,index) in options" :key="index">
<el-table-column
:label="item.label"
:prop="item.prop"
:align="item.align"
:width="item.width">
</el-table-column>
</template>
</el-table>
实现添加操作项
- 定义接口类型
export interface TableOptions {
//是否代表操作项
action?: boolean,
}
- 父组件添加配置项
let options: TableOptions[] = [
{
label: '操作',
align: 'center',
action: true
}
]
- 子组件单独处理操作项:
//过滤操作选项之后的配置
let tableOptions = computed(()=>props.options.filter(item=>!item.action))
//找到操作项的配置
let actionOptions = computed(()=>props.options.find(item=>item.action))
<el-table v-bind="$attrs" :data="data">
<template v-for="(item,index) in tableOptions" :key="index">
<el-table-column
:label="item.label"
:prop="item.prop"
:align="item.align"
:width="item.width">
</el-table-column>
</template>
<el-table-column
:label="actionOptions?.label"
:align="actionOptions?.align"
:width="actionOptions?.width">
<!-- 定义作用域插槽给父组件传递scope -->
<template #default="scope">
<slot name="action" :scope="scope" v-else></slot>
</template>
</el-table-column>
</el-table>
- 父组件编辑操作项插槽:
<template #action="{scope}">
<el-button size="small" type="primary">编辑</el-button>
<el-button size="small" type="danger">删除</el-button>
</template>
实现加载功能
- 子组件:props配置项添加,同时绑定配置。
<el-table v-bind="$attrs" :data="data" v-loading="isLoading"
:element-loading-text="elementLoadingText"
:element-loading-background="elementLoadingBackground"
:element-loading-spinner="elementLoadingSpinner"
:element-loading-svg="elementLoadingSvg"
:element-loading-svg-view-box="elementLoadingSvgViewBox"
>
</el-table>
let props = defineProps({
//加载文案
elementLoadingText:{
type: String,
},
//加载图标名
elementLoadingSpinner:{
type: String,
},
//加载背景颜色
elementLoadingBackground:{
type: String,
},
//加载svg图标
elementLoadingSvg:{
type: String,
},
//加载svg图标配置
elementLoadingSvgViewBox:{
type: String,
}
})
- 父组件传值
<my-table :data="tableData" :options="options"
elementLoadingText="Loading..."
elementLoadingBackground="rgba(122, 122, 122, 0.8)"
:elementLoadingSvg="svg"
elementLoadingSvgViewBox="-10, -10, 50, 50"
>
</my-table>
let svg = `
<path class="path" d="
M 30 15
L 28 17
M 25.61 25.61
A 15 15, 0, 0, 1, 15 30
A 15 15, 0, 1, 1, 27.99 7.5
L 15 15
" style="stroke-width: 4px; fill: rgba(0, 0, 0, 0)"/>
`
实现自定义列功能
- 为某些列添加slot配置项
let options: TableOptions[] = [
{
label: '日期',
prop: 'date',
align: 'center',
slot: 'date',
},
{
label: '姓名',
prop: 'name',
align: 'center',
slot: 'name'
}
]
- 子组件: 对数据项遍历过程中,需要对options的配置是否含有slot进行判断,有的话显示插槽,否则显示scope.row[item.prop],即对应值 这里采用动态插槽的形式,对各自定义列进行单独处理
<el-table v-bind="$attrs" :data="data" v-loading="isLoading" :element-loading-text="elementLoadingText"
:element-loading-background="elementLoadingBackground"
:element-loading-spinner="elementLoadingSpinner"
:element-loading-svg="elementLoadingSvg"
:element-loading-svg-view-box="elementLoadingSvgViewBox"
>
<template v-for="(item,index) in tableOptions" :key="index">
<el-table-column
:label="item.label"
:prop="item.prop"
:align="item.align"
:width="item.width">
<template #default="scope">
<slot v-if="item.slot" :name="item.slot" :scope="scope"></slot>
<span v-if="!item.slot && item.prop"></span>
</template>
</el-table-column>
</template>
<el-table-column
:label="actionOptions?.label"
:align="actionOptions?.align"
:width="actionOptions?.width">
<template #default="scope">
<slot name="action" :scope="scope" v-else></slot>
</template>
</el-table-column>
</el-table>
- 父组件编写插槽,实现自定义列的定义化
<template #date="{scope}">
<qt-icon-timer></qt-icon-timer>
</template>
<template #name="{scope}">
<el-popover effect="light" trigger="hover" placement="top" width="auto">
<template #default>
<div>name: </div>
<div>address: </div>
</template>
<template #reference>
<el-tag></el-tag>
</template>
</el-popover>
</template>
实现可编辑单元格
- 定义接口类型
export interface TableOptions {
//是否是可编辑单元格
editable?: boolean
}
- 为某些列添加editable
let options: TableOptions[] = [
{
label: '日期',
prop: 'date',
align: 'center',
slot: 'date',
editable: true
},
{
label: '地址',
prop: 'address',
align: 'center',
editable: true
}
]
- 子组件: 需要实现点击显示输入框及勾、叉功能,如何确定点击的哪个单元格,打印scope发现对应单元格的scope.$index + scope.column.id的值唯一。定义变量currentEdit表示唯一标识。 这里,可以对勾、叉定义插槽,实现自定义的确认和取消的功能。
<el-table v-bind="$attrs" :data="tableData" v-loading="isLoading" :element-loading-text="elementLoadingText"
:element-loading-background="elementLoadingBackground"
:element-loading-spinner="elementLoadingSpinner"
:element-loading-svg="elementLoadingSvg"
:element-loading-svg-view-box="elementLoadingSvgViewBox">
<template v-for="(item,index) in tableOptions" :key="index">
<el-table-column
:label="item.label"
:prop="item.prop"
:align="item.align"
:width="item.width">
<template #default="scope">
<template>
<template v-if="scope.$index + scope.column.id === currentEdit">
<div style="display:flex;">
<el-input v-if="item.prop" size="small" v-model="scope.row[item.prop]"></el-input>
<div @click.stop="clickEditCell">
<!-- 用户定义check和close的内容,插槽写内容则显示 -->
<slot name="editCell" :scope="scope" v-if="$slots.editCell"></slot>
<div class="icons" v-else>
<qt-icon-check class="check" @click="check(scope)"></qt-icon-check>
<qt-icon-close class="close" @click="close(scope)"></qt-icon-close>
</div>
</div>
</div>
</template>
<template v-else>
<slot v-if="item.slot" :name="item.slot" :scope="scope"></slot>
<span v-if="!item.slot && item.prop"></span>
<component :is="`qt-icon-${toLine(editIcon)}`" v-if="item.editable" @click.stop="clickEdit(scope)"/>
</template>
</template>
</template>
</el-table-column>
</template>
<el-table-column
:label="actionOptions?.label"
:align="actionOptions?.align"
:width="actionOptions?.width">
<template #default="scope">
<slot name="action" :scope="scope"></slot>
</template>
</el-table-column>
</el-table>
let clickEdit=(scope: any)=>{
//唯一标识
currentEdit.value = scope.$index + scope.column.id
}
实现可编辑行
- 子组件添加props配置项,父组件传递isEditRow,表示配置可编辑行。editRowIndex表示确认点击的是编辑还是删除。
let props = defineProps({
//是否可以编辑行
isEditRow: {
type: Boolean,
default: false
},
//编辑行按钮标识
editRowIndex: {
type: String,
default: ''
},
})
- 子组件:el-table组件配置了@row-click事件,来确定点击的是哪行。为子组件添加该事件。
- 这里拷贝一份表格数据,为数据添加属性rowEdit,初始值为false,表示不可编辑。同时,要对父组件传递的表格数据进行监听,当变化时对tableData重新赋值。
- 这里需要对父组件传递的按钮标识和当前表示进行对比判断。
- 重置按钮标识,修复删除按钮可编辑状态
// 拷贝一份表格的数据
let tableData = ref<any[]>(cloneDeep(props.data))
onMounted(()=>{
tableData.value.map(item=>{
//标识当前是否为可编辑状态
item.rowEdit = false
})
})
//监听父组件数据
watch(()=>props.data,val=>{
tableData.value = cloneDeep(val)
//标识当前是否为可编辑状态
tableData.value.map(item=>{
//标识当前是否为可编辑状态
item.rowEdit = false
})
},{deep:true})
//拷贝一份按钮标识
let cloneEditRowIndex = ref<string>(props.editRowIndex)
//监听父组件传递过来的标识
watch(()=>props.editRowIndex,val=>{
if(val) cloneEditRowIndex.value = val
})
//点击每一行的事件
let rowClick = (row: any, column: any)=>{
//判断当前点击的是否是操作项的内容
if(column.label === actionOptions.value!.label){
//编辑行的操作
if(props.isEditRow && cloneEditRowIndex.value === props.editRowIndex){
//点击的按钮是可编辑的操作
row.rowEdit = !row.rowEdit
//重置其他数据的rowEdit
tableData.value.map(item=>{
if(item !== row) item.rowEdit = false
})
//重置按钮标识
if(!row.rowEdit){
emits('update:editRowIndex','')
}
}
}
}
实现分页功能
- mock生成随机数据:mock生成100条随机数据,传递index、size对数据进行分页
- 父组件获取数据
let getData = ()=>{
axios.post('/api/list',{
current:current.value,
pageSize:pageSize.value
}).then((res: any)=>{
tableData.value = res.data.data.rows
total.value = res.data.data.total
})
}
onMounted(()=>{
getData()
})
- 子组件配置props
let props = defineProps({
//是否显示分页
pagination: {
type: Boolean,
default: false
},
//当前是第几页的数据
currentPage: {
type: Number,
default: 1
},
//每页数据的选项
pageSizes: {
type: Array as PropType<number[]>,
default: [10,20,30,40]
},
//当前一页多少条数据
pageSize: {
type: Number,
default: 10
},
//数据总数
total: {
type: Number
},
//分页的排列方式
paginationAlign: {
type: String as PropType<'left' | 'center' | 'right'>,
default: 'left'
}
})
- 子组件分页器
<div class="pagination" :style="{justifyContent:justifyContent}" v-if="pagination">
<el-pagination
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="pageSizes"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
- 修改当前页及页面条数
//分页条数
let handleSizeChange = (val: number)=>{
emits('sizeChange',val)
}
//分页页数
let handleCurrentChange = (val:number)=>{
emits('currentChange',val)
}
<!-- 父组件 -->
let sizeChange = (val:number)=>{
pageSize.value = val
getData()
}
let currentChange = (val:number)=>{
current.value = val
getData()
}