当前位置:   article > 正文

Element Plus 封装表格组件、表格查询、表格分页、动态表头,可编辑单元格【常用功能版本】_elementplus表格

elementplus表格

前言

在后台管理项目中,用到最多的就是表格了,当然也少不了查询、分页、等一些特殊数据的展示。
既然这些功能是比较常见且常用的,那么将它们封装成组件来复用,能节省不的事情。

接下来我们就来封装一个,具有表格、查询、分页、等多种功能的组件

效果展示

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码实现

接下来,我们就来一步一步的实现吧。

首先我们的组件需要一个配置文件
1、配置表格需要展示的字段
2、配置查询条件

详细的解释,都写在注释中。

我们这里就命名为table.ts
/*
    首先是我们的 表格的配置(数组对象)

    每个对象中有三个必须的属性
    prop:对应的字段名称
    label:表格表头展示名称
    width/min_width:列宽(width和min_width自选其一)(width就是固定款度,min_width最小宽度)

    扩展属性
    align:列的对齐方式(left、center、right)默认left
    isEdit:(默认false,为true时开始单元格双击编辑功能)
    type:(列展示格式)具体看以下举例
    show:控制列的显示或隐藏(这里不需要单独写出来,在组件里会自己去添加)

    type:time(后端返回的字段为时间戳,需要我们自己格式化时间)
    {
        prop: 'createDate',
        label: ' 创建时间',
        align: 'center',
        type: 'time',
        width: 180
    }

    type:image(改字段需要以图片的形式展示)
    {
        prop: 'avatar',
        label: '头像',
        align: 'center',
        type: 'image', 
        width: 80
    },

    type:switch(switch开关,一般用于控制该条数据的状态)
    {
        prop: 'userStatus',
        label: '用户状态',
        align: 'center',
        type: 'switch', 
        activeValue: 1, // switch为打开时的值
        inactiveValue: 2,// switch为关闭时的值
        width: 100
    },

    type:status(状态展示,比如这时候后端返回的状态对应的值时1或者是2,肯定是需要转为中文展示的)
    {
        prop: 'userStatus',
        label: '用户状态',
        align: 'center',
        width: 100,
        type: 'status', 
        option: {
            '1': '启用',
            '2': '禁用',
        },
        color: {
            '1': 'success',
            '2': 'info',
        }
    }

    以上就是表格配置信息
*/

const columnsData: any = [
    {
        prop: 'menuPowerName',
        label: '菜单权限名称',
        isEdit: true,
        min_width: 120,
    },
    {
        prop: 'menuPowerMark',
        label: '菜单权限标识',
        align: 'center',
        width: 180,
    },
    {
        prop: 'menuName',
        label: '所属菜单名称',
        align: 'center',
        width: 180,
    },
    {
        prop: 'createDate',
        label: ' 创建时间',
        align: 'center',
        type: 'time',
        width: 180
    }
]

// 表格查询配置中 type:select 的测试数据

let dataList = [
    {
        "id": 1,
        "menuName": "首页",
        "menuPath": "/",
        "menuType": 2,
        "menuStatus": 1,
        "parentId": 0,
        "parentName": null,
        "createDate": 1682435267868
    },
    {
        "id": 2,
        "menuName": "系统管理",
        "menuPath": "/system",
        "menuType": 1,
        "menuStatus": 1,
        "parentId": 0,
        "parentName": null,
        "createDate": 1682435338368
    },
    {
        "id": 3,
        "menuName": "用户管理",
        "menuPath": "user",
        "menuType": 2,
        "menuStatus": 1,
        "parentId": 2,
        "parentName": "系统管理",
        "createDate": 1682436009335
    },
    {
        "id": 4,
        "menuName": "角色管理",
        "menuPath": "role",
        "menuType": 2,
        "menuStatus": 1,
        "parentId": 2,
        "parentName": "系统管理",
        "createDate": 1682436073984
    },
    {
        "id": 5,
        "menuName": "菜单管理",
        "menuPath": "menu",
        "menuType": 2,
        "menuStatus": 1,
        "parentId": 2,
        "parentName": "系统管理",
        "createDate": 1682471621692
    }
]

/*
    接下来我们来说一下查询的配置
    我们查询时传给后端的数据大多是是这样的
    {
        name:'张三,
        age:1
    }

    下面我只配置了三种情况
    text 就是用户数据关键字查询
    select 是用户选择指定数据查询
    dateTime 根据时间查询

    text 和 dateTime 没什么可说的,重要的是select
    我们之间来解析一条
    { label: "所属菜单", prop: 'menuId', type: 'select', options: dataList, valueKey: 'id', labelKey: 'menuName' },

    label 对应的是我们的名称
    prop 对应的则是字段
    type 类型就不说了,上面说了,目前就三种
    options:(数组对象)上面我会贴出测试数据
    我们这个查询是作用在select下拉菜单上的,且每次数组对象中的数据都是不同的,我们就需要两个变量去存对应的字段
    valueKey:select选择的值
    labelKey:select选择值对应的名称
*/

const queryData: any = [
    { label: "权限名称", prop: 'menuPowerName', type: 'text' },
    { label: "所属菜单", prop: 'menuId', type: 'select', options: dataList, valueKey: 'id', labelKey: 'menuName' },
    { label: "创建时间", prop: 'dateTime', type: 'date' },
]
export {
    columnsData,
    queryData
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182

下面就来实现对整个组件的封装

注释依旧在代码中

components下命名为TableModule.vue
<template>
    <div class="table-module">
        <!-- 查询 -->
        <!-- 
            实现:通过我们配置好的 查询条件
                首先去创建form表单,根据我们配置的查询条件去做一个循环判断,展示出不用类型所对应不同的输入框
                比如:text对应普通的输入框,select对应下拉选择,dateTime对应日期时间选择器
                在使用时,父组件会传来一个queryForm空的对象,
                循环出来的输入框会绑定表格配置中的prop字段绑定在queryForm对象中
         -->
        <div class="query" ref="queryRef">
            <div class="query-content">
                <el-form :model="props.tableModule.queryForm" style="display: flex;" label-position="left"
                    class="query-form">
                    <el-form-item :label="item.label" style="margin-right: 20px; margin-bottom: 0px;"
                        v-for="item in props.tableModule.query" :key="item.prop">
                        <el-input v-model="props.tableModule.queryForm[item.prop]" :placeholder="'输入' + item.label + '关键字'"
                            clearable v-if="item.type == 'text'"></el-input>
                        <el-select v-model="props.tableModule.queryForm[item.prop]" clearable
                            :placeholder="'选择' + item.label + '关键字'" v-else-if="item.type == 'select'">
                            <el-option v-for="cItem in item.options" :key="cItem[item.valueKey]"
                                :label="cItem[item.labelKey]" :value="cItem[item.valueKey]" />
                        </el-select>
                        <el-date-picker v-model="props.tableModule.queryForm[item.prop]" clearable type="datetimerange"
                            format="YYYY/MM/DD hh:mm:ss" value-format="x" range-separator="" start-placeholder="开始时间"
                            end-placeholder="结束时间" v-else-if="item.type == 'date'" />
                    </el-form-item>
                </el-form>
            </div>
            <div class="slot">
                <el-button @click="Search" type="primary" plain>查询</el-button>
                <el-button @click="Recover">重置</el-button>
                <!-- slot插槽(用来添加表格其他操作,比如,新增数据,删除数据等其他操作) -->
                <slot name="event"></slot>
                <!-- 
                    动态表头显示,根据表格每条配置项中的show字段来决定改列是否显示或者隐藏 
                    columns 就是我们表格配置的数组对象
                -->
                <el-popover placement="bottom" title="表格配置" :width="200" trigger="click">
                    <div v-for="(item, index) in props.tableModule.columns" :key="index">
                        <el-checkbox v-model="item.show" :label="item.label" :true-label="1" :false-label="0" />
                    </div>
                    <template #reference>
                        <!-- 一个Element Plus中的图标 -->
                        <el-button :icon="Operation"></el-button>
                    </template>
                </el-popover>
            </div>
        </div>
        <!-- 表格 -->
        <!-- style中是计算表格的高度的 -->
        <el-table :data="props.tableModule.dataList" border height="100%"
            :style="{ 'height': `calc(100vh - ${queryHeight + 156}px)` }" v-loading="props.tableModule.loading"
            :row-class-name="tableRowClassName" :cell-class-name="tableCellClassName" @cell-dblclick="cellDblClick"
            id="el-table" ref="tableRef">
            <el-table-column type="selection" width="50" align="center" />
            <!-- columns表格配置数据 -->
            <template v-for="(item, index) in props.tableModule.columns">
                <!-- 循环 columns 紧接着判断每个字段的类型 -->
                <el-table-column :prop="item.prop" :label="item.label" :align="item.align || 'left'" :width="item.width"
                    :min-width="item.min_width" :fixed="item.fixed" v-if="item.show">
                    <template slot-scope="scope" #default="scope">
                        <!-- switch时使用switch开关组件,并且绑定好我们配置的属性 -->
                        <div v-if="item.type == 'switch'">
                            <el-switch v-model="scope.row[item.prop]" :active-value="item.activeValue"
                                :inactive-value="item.inactiveValue" @change="props.tableModule.switchChange(scope.row)">
                            </el-switch>
                        </div>
                        <!-- status 时 使用fieldChange方法匹配出值对应的名称并返回 -->
                        <div v-else-if="item.type == 'status'">
                            <el-tag>{{
                                fieldChange(scope.row[item.prop], item.option) }}</el-tag>
                        </div>
                        <!-- image 就是使用 el-image展示我们的图片咯 -->
                        <div v-else-if="item.type == 'image'">
                            <el-image style="width: 60px; height: 60px" :src="scope.row[item.prop]"
                                :preview-src-list="[scope.row[item.prop]]" :preview-teleported="true">
                            </el-image>
                        </div>
                        <!-- formatDate 方法将时间戳格式化为年月日时分秒的格式 -->
                        <div v-else-if="item.type == 'time'">{{ formatDate(scope.row[item.prop]) }}</div>
                        <!-- 可编辑单元格 -->
                        <div v-else-if="item.isEdit">
                            <el-input v-model="scope.row[item.prop]" :placeholder="'请输入' + item.label"
                                @blur="inputBlur(scope.row)" autofocus ref="inputRef"
                                v-if="scope.row['Indexs'] == rowIndex && scope.column['Indexs'] == columnIndex" />
                            <div v-else>{{ scope.row[item.prop] }}</div>
                        </div>
                        <!-- 类型都不匹配时直接展示 -->
                        <div v-else>{{ scope.row[item.prop] }}</div>
                    </template>
                </el-table-column>
            </template>
            <!-- 这里的插槽用于列表的操作列 -->
            <slot name="tableColumn"></slot>
        </el-table>
        <!-- 分页 -->
        <!-- 分页这里没什么可说的,父组件传三个参数,当前页,每页条数,总条数就可以了。 -->
        <div class="page">
            <el-pagination :current-page="props.tableModule.pages.page" :page-size.sync="props.tableModule.pages.limit"
                :page-sizes="pageSizes" :layout="layout" :total="props.tableModule.pages.total" @size-change="sizeChange"
                @current-change="currentChange" />
        </div>
    </div>
</template>

<script setup>
import { defineProps, onMounted, reactive, toRefs, ref } from 'vue'
import { formatDate } from '@/utils/index' // 自己的格式化时间的方法
import { ElTable } from 'element-plus';
import { Operation } from '@element-plus/icons-vue'

const props = defineProps({
    tableModule: Object, // 由父组件传递而来
    layout: { // 分页功能配置
        type: String,
        default: "total, sizes, prev, pager, next, jumper",
    },
    pageSizes: { // 分页:每页条数配置
        type: Array,
        default() {
            return [10, 20, 30, 50];
        },
    },
})

const state = reactive({
    rowIndex: 0, // 行索引 用于可编辑单元格
    columnIndex: 0, // 列索引 用于可编辑单元格
    queryHeight: 0,
})

const {
    rowIndex,
    columnIndex,
    queryHeight,
} = toRefs(state)

const queryRef = ref(null);

onMounted(() => {
    // 这里拿到query模块的高度,适配页面高度的
    setTimeout(() => {
        state.queryHeight = queryRef.value.clientHeight
    }, 100);

    // 为每个表格配置项添加show属性,默认1为显示状态
    props.tableModule.columns.forEach(item => {
        item.show = 1
    })
})

function fieldChange(row, option) {
    if (option[row]) {
        return option[row]
    }
}

// 编辑单元格 ----------
// 为每一行返回固定的className
function tableRowClassName({ row, rowIndex }) {
    row.Indexs = rowIndex;
}

// 为所有单元格设置一个固定的 className
function tableCellClassName({ column, columnIndex }) {
    column.Indexs = columnIndex;
}

// el-table单元格双击事件
function cellDblClick(row, column, cell, event) {
    state.rowIndex = row.Indexs
    state.columnIndex = column.Indexs
}

// input失去焦点
function inputBlur(row) {
    state.rowIndex = 0
    state.columnIndex = 0
    props.tableModule.editInputBlur() // 父组件的方法,
}

// 每页条数切换时触发
function sizeChange(item) {
    props.tableModule.pages.limit = item
    props.tableModule.callback() // 父组件绑定的回调
}

// 页数切换时触发
function currentChange(item) {
    props.tableModule.pages.page = item
    props.tableModule.callback()
}

// 点击查询按钮时触发
function Search() {
    props.tableModule.callback()
}

// 点击重制触发
function Recover() {
    props.tableModule.queryForm = {}
    props.tableModule.callback()
}
</script>

<style scoped lang="scss">
.table-module {
    .query {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        margin-bottom: 10px;

        .query-content {
            display: flex;
            align-items: flex-start;

            .query-form {
                .el-form-item {
                    margin: 0px;
                    margin-right: 20px;
                }

                .el-input {
                    width: 200px;
                }

                ::v-deep(.el-select) {
                    .el-input {
                        width: 200px;
                    }
                }
            }
        }

        .slot {
            display: flex;
            justify-content: flex-end;
            padding-right: 48px;
        }
    }

    .page {
        margin-top: 10px;
    }
}
</style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248

接下来就到我们组件的使用了。

index.vue
<template>
    <div class="">
        <!-- TableModule  tableModule是一个对象,下面有解释 -->
        <TableModule :tableModule="tableModule" ref="TableModuleRef">
            <!-- #event是插入的新增按钮和删除按钮,具体操作按照自己的业务流程实现 -->
            <template #event>
                <el-button type="primary" @click="Add">新增</el-button>
                <el-button type="danger" @click="Delete">删除</el-button>
            </template>
            <!-- #tableColumn 插入表格的操作列 -->
            <template #tableColumn>
                <el-table-column align="center" fixed="right" label="操作" width="120">
                    <template slot-scope="scope" #default="scope">
                        <el-button @click="Edit(scope)">编辑</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableModule>
    </div>
</template>

<script setup lang="ts">
import { reactive, toRefs, ref, onMounted } from 'vue'
import { columnsData, queryData } from './table' // 引入我们配置好的数据

import { menuPowerList } from '@/api/menuPower/index'

const state = reactive({
    columns: columnsData, // 表格配置
    query: queryData, // 查询条件配置
    queryForm: {}, // 查询form表单
    loading: false, // 加载状态
    dataList: [], // 列表数据
    pages: { // 分页数据
        total: 0,
        limit: 20,
        page: 1,
    }
})

const { loading, dataList, columns, pages, query, queryForm } = toRefs(state)

const TableModuleRef = ref()

onMounted(() => {
    getDataList()
})

// 传给子组件的
const tableModule = ref<Object>({
    editInputBlur: editInputBlur, // 可编辑单元的,失去焦点时的回调
    callback: getDataList, // 回调,子组件中可以看到很多调用callback的,这里对应的是获取列表数据的方法
    // 以下不说了,上面都给解释了
    queryForm: queryForm,
    columns: columns,
    dataList: dataList,
    loading: loading,
    pages: pages,
    query: query,
})

// 获取列表信息
async function getDataList() {
    state.loading = true
    // 掉自己的接口,切勿复制粘贴
    const res = await menuPowerList({ pages: state.pages, query: state.queryForm })
    state.loading = false
    state.dataList = res.data
    state.pages.total = res.total
}

function Add() { // 新增事件

}

function Edit(row: any) { // 编辑事件
    console.log(row)
}

function Delete() { // 删除事件

}

function editInputBlur() {
    console.log('失去焦点回调')
}

</script>

<style scoped></style>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90

在这里插入图片描述

结尾

之前几个功能模块都有单独封装过,下面我会贴出链接

Element Plus el-table表格查询封装

Element Plus二次封装el-table、可编辑表格

Element Plus二次封装el-table 扩展【表格动态列展示】

组件源文件下载

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/116824?site
推荐阅读
相关标签
  

闽ICP备14008679号