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

11 篇文章 2 订阅

前言

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

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

效果展示

在这里插入图片描述

在这里插入图片描述

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

代码实现

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

首先我们的组件需要一个配置文件
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
}

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

注释依旧在代码中

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>

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

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>

在这里插入图片描述

结尾

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

Element Plus el-table表格查询封装

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

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

组件源文件下载

  • 10
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Vue3和Element Plus中封装表格组件,可以使用以下方法: 1. 首先,在Vue3中创建一个新的组件,可以命名为TableWrapper或者其他你喜欢的名称。 2. 在组件中引入Element Plus的Table组件,并在data中定义需要用到的数据,例如tableData和tableHeader。 3. 在template中使用Table组件,并通过props传递数据。使用v-for循环遍历tableHeader来动态生成表头信息。 4. 在Table组件中,可以通过slot插槽自定义表格中的内容,例如添加操作按钮或其他自定义内容。 5. 可以在Table组件中定义一些方法,例如handleEdit和handleDelete,用于处理编辑和删除操作。 6. 最后,在父组件中使用TableWrapper组件,并传递需要显示的数据和其他配置项。 以下是一个示例代码,供参考: ```vue <template> <Table :data="tableData" :columns="tableHeader" border > <template #ksType="{ row }"> <span>{{ row.type }}</span> </template> <template #isDisabled="{ row }"> <el-switch v-model="row.isDisabled" active-text="是" inactive-text="否" :active-value="0" :inactive-value="1" @change="switchChange(row)" ></el-switch> </template> <template #btn="{ row }"> <el-button v-for="(btn, index) in row.btn" :key="index" :type="btn.btnStyle || 'default'" @click="btn.func(row)" > {{ btn.name }} </el-button> </template> </Table> </template> <script> import { Table } from 'element-plus'; export default { components: { Table, }, data() { return { tableData: [], tableHeader: [ { prop: "name", label: "名称" }, { prop: "type", label: "科室类型", slot: "ksType" }, { prop: "isDisabled", label: "是否禁用", slot: "isDisabled" }, { prop: "btn", label: "操作", slot: "btn" }, ], }; }, methods: { switchChange(row) { // 处理开关切换操作 }, handleEdit(row) { // 处理编辑操作 }, handleDelete(row) { // 处理删除操作 }, }, }; </script> ``` 在这个示例中,我们使用了Element Plus的Table组件,并通过slot插槽自定义了表格中的内容。同时,我们在组件中定义了一些方法来处理具体的操作。你可以根据自己的需求进行修改和扩展。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [vue3+element-plus封装表格](https://blog.csdn.net/weixin_50041614/article/details/126702116)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [go语言恶意代码检测系统-对接前端可视化与算法检测部分](https://download.csdn.net/download/liufang_imei/88222624)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值