Element Plus封装el-dialog、el-table、el-select表格对话框,实现单选多选场景。

一、使用场景

首先我们要知道为什么封装一个有el-dialogel-tableel-select的组件
大家在开发后台管理项目中,肯定会遇到这样的一种情况

比如:我们项目中存在用户管理角色管理两个功能模块
我们在去添加用户的时候需要去绑定角色,且绑定的角色是可以多选的
如下图:

大多数我们是不是会去这样写
Element Plus封装el-dialog、el-table、el-select表格对话框,实现单选多选场景。_第1张图片
那么这样写有问题吗,当然是莫得问题了。

可是如果在选择角色的时候,我想看这个角色的描述、或者是头像、又或者是角色的权限,这时候应该怎么做呢。
我总不能,取消这次的编辑,去角色的功能模块看好了再去新增用户,这样是不是很麻烦。

接下来我们封装的组件就完美的解决了这个问题
我们先来展示效果图
Element Plus封装el-dialog、el-table、el-select表格对话框,实现单选多选场景。_第2张图片
查询、分页都有的。

Element Plus封装el-dialog、el-table、el-select表格对话框,实现单选多选场景。_第3张图片
接下来我们就来看代码吧

二、代码实现

1、主文件

主页面没有什么要说的,就一个form表单,与正常的用法一样。


<template>
    <div>
        <el-form :model="form">
            <el-form-item label="角色">
                
                <LTableDialog v-model="form.menuId" multiple method="roleList" checkLabel="roleName" title="角色选择"
                    :tableColumns="roleColumnsData" :tableQuery="roleQueryData" @change="roleChange">
                LTableDialog>
            el-form-item>
            <el-form-item label="菜单">
                <LTableDialog v-model="form.roleId" method="menuList" checkLabel="menuName" title="菜单选择"
                    :tableColumns="menuColumnsData" :tableQuery="menuQueryData">
                LTableDialog>
            el-form-item>
        el-form>
    div>
template>

<script setup>
import { reactive, toRefs, onMounted } from 'vue';

// 这里需要配置 表格字段和搜索条件
import { roleColumnsData, roleQueryData, menuColumnsData, menuQueryData } from "./tableDialog"

const state = reactive({
    // 提交表单
    form: {
        menuId: []
    }
})

const { form } = toRefs(state)

onMounted(() => {

})

// LTableDialog Change事件,可选可不选
function roleChange(e) {
    console.log(e)
}

script>

<style scoped>style>
2、表格数据和搜索条件配置文件
const roleColumnsData: any = [
    {
        prop: 'roleName', // 字段名称
        label: '角色名称', // 表格表头名称
        min_width: 120,	// 表格最小长度
        fixed: true // 表格是否固定
    },
    {
        prop: 'roleRemark',
        label: '角色备注',
        align: 'center', // 表格对齐方式
        width: 200, // 表格长度
    },
    {
        prop: 'createDate',
        label: ' 创建时间',
        align: 'center',
        type: 'time', // 表格类型
        width: 180,
    },
]

const roleQueryData: any = [
	// label:查询条件名称 、 prop:查询字段(传给后端的) 、 text:查询类型
    { label: "角色名称", prop: 'roleName', type: 'text' }, 
    { label: "创建时间", prop: 'dateTime', type: 'date' },
]

const menuColumnsData: any = [
    {
        prop: 'menuName',
        label: '菜单名称',
        min_width: 120,
        fixed: true
    },
    {
        prop: 'menuPath',
        label: '菜单路径',
        align: 'center',
        width: 200,
    },
    {
    	// 后端返回类型是 1、2 的情况,这里是做转换的
        prop: 'menuType',
        label: '菜单类型',
        align: 'center',
        // type 也是要传的
        type: 'status',
        // 不同类型对应的名称
        option: {
            '1': '目录',
            '2': '页面'
        },
        // 不同类型对应的颜色
        color: {
            '1': ' #a0cfff',
            '2': ' #b3e19d',
        },
        width: 100
    },
    {
        prop: 'parentName',
        label: '父级菜单名称',
        align: 'center',
        width: 200,
    },
    {
        prop: 'createDate',
        label: ' 创建时间',
        align: 'center',
        type: 'time',
        width: 180,
    },
]

const menuQueryData: any = [
    { label: "菜单名称", prop: 'menuName', type: 'text' },
]

export {
    roleColumnsData, // 角色表格数据
    roleQueryData, // 角色搜索条件
    menuColumnsData, // 菜单表格数据
    menuQueryData // 菜单搜索条件
}
3、LTableDialog 组件封装

<template>
    <div class="l-table-dialog">
        
        <el-select v-model="newValue" :multiple="props.multiple" collapse-tags placeholder="点击选择数据">
            <el-option :label="item[props.checkLabel]" :value="item[props.checkValue]" v-for="(item, index) in dataList"
                :key="item.id" />
        el-select>
        <el-button :icon="Search" @click="openDialog" />
        <el-dialog v-model="tableDialog" :title="props.title" :close-on-click-modal="false">
            
            <LQuery :queryModule="queryModule">LQuery>
            
            <el-table :data="dataList" border style="width: 100%; overflow-y: scroll" v-loading="loading"
                :current-row-key="props.checkValue" @select="selectChange" ref="tableRef"
                :class="props.multiple ? '' : 'multipleShow'">
                
                <el-table-column type="selection" width="50" align="center" />
                
                <el-table-column v-for="(item, index) in props.tableColumns" :key="item.prop" :prop="item.prop"
                    :label="item.label" :align="item.align || 'left'" :width="item.width" :min-width="item.min_width"
                    :fixed="item.fixed">
                    <template slot-scope="scope" #default="scope">
                        
                        <div v-if="item.type == 'status'">
                            <el-tag>{{
                                fieldChange(scope.row[item.prop], item.option) }}el-tag>
                        div>
                        <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>
                        <div v-else-if="item.type == 'time'">{{ formatDate(scope.row[item.prop]) }}div>
                        <div v-else>{{ scope.row[item.prop] }}div>
                    template>
                el-table-column>
            el-table>
            <div class="l-pages">
                
                <el-pagination :current-page="pages.page" :page-size.sync="pages.limit" :page-sizes="pageSizes"
                    :layout="layout" :total="pages.total" @size-change="sizeChange" @current-change="currentChange" />
            div>
            <template #footer>
                <span class="dialog-footer">
                    <el-button @click="tableDialog = false">取 消el-button>
                    <el-button type="primary" @click="Confirm">确 定el-button>
                span>
            template>
        el-dialog>
    div>
template>

<script setup>
import { defineProps, computed, nextTick, onMounted, reactive, ref, toRefs, defineEmits } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { ElTable } from 'element-plus';

import { formatDate } from '@/utils/index'
import { roleList } from '@/api/role/index'
import { menuList } from '@/api/menu/index'

const props = defineProps({
    tableColumns: { // 表格数据,类型:数组
        type: Array
    },
    tableQuery: { // 搜索条件,类型:数组
        type: Array
    },
    method: { // 这个是方法,下面会讲到
        type: String
    },
    modelValue: { // 选择的值,单选时类型为Number,多选时类型为Array
        type: [Number, Array]
    },
    layout: { // 分页组件配置
        type: String,
        default: "total, sizes, prev, pager, next, jumper",
    },
    pageSizes: { // 每页条数
        type: Array,
        default() {
            return [10, 20, 30, 50];
        },
    },
    // el-select 中的el-option需要两个属性分别是value:选择的值、label值对应的名称
    // 因为我们el-option肯定是循环出来的,每次请求列表拿回来的值也都不是一样的,所以我们需要两个变量来控制
    checkLabel: {
        type: String,
        default: "name"
    },
    checkValue: {
        type: String,
        default: "id"
    },
    // dialog 对话框标题
    title: {
        type: String,
        default: "表格对话框"
    },
    // 是否开启多选,默认不开启
    multiple: {
        type: Boolean,
        default: false
    }
})

// 子组件调用父组件方法
const emit = defineEmits(['update:modelValue', 'change'])

const tableRef = ref(ElTable);

// 利用计算属性修改父组件绑定的值
const newValue = computed({
    get() {
        return props.modelValue
    },
    set(value) {
        emit('update:modelValue', value)
    }
})

const state = reactive({
    tableDialog: false,
    loading: false,
    dataList: [], // 请求的数据

    queryForm: {},
    pages: {
        total: 0,
        limit: 20,
        page: 1,
    }
})

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

// 这个是 搜索组件需要用到的
const queryModule = ref({
    queryForm: queryForm,
    query: props.tableQuery,
    Search: tableSearch,
    Recover: tableRecover
})

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

// 打开表格对话框
function openDialog() {
    /*
        打开对话框,如果el-select有选中值的情况下在 dataList 中过滤出来选中的数据存放在 row 变量中
        接着使用 el-table 中的 toggleRowSelection 方法切换某一行的状态
    */
    let row = []
    state.tableDialog = true
    if (props.multiple) {
        state.dataList.filter(item => {
            props.modelValue.includes(item[props.checkValue]) && row.push(item);
        });
    } else {
        row = state.dataList.filter(i => i.id == props.modelValue)
    }
    nextTick(function () {
        row.forEach(item => {
            tableRef.value.toggleRowSelection(item, true)
        })
    })
}

// 获取表格数据
async function getDataList() {
    // eval(`${props.method}({ pages: state.pages, query: state.queryForm })`) 这里是我自己请求后台接口的方式
    // props.method 就是请求方法,后面则是传的参数,如果还看不懂的话,看下面例子
    /*
        例子:
        roleList({ pages: state.pages, query: state.queryForm }).then(res=>{}).catch(res=>{})
    */
    // eval:可以接收一个字符串作为脚本代码运行
    state.loading = true
    const res = await eval(`${props.method}({ pages: state.pages, query: state.queryForm })`)
    state.loading = false
    // 下面就是我接口返回的结果咯。
    state.dataList = res.data
    state.pages.total = res.total
    /*
        [
            {
                "id": 1,
                "roleName": "开发角色",
                "roleRemark": "用于开发阶段",
                "menus": "1,2,3,4,5,6",
                "menuPowers": "1,2,3,4,5,6,13,7,8,9,10,11,12",
                "roleStatus": 1,
                "createDate": 1682406566687
            },
            {
                "id": 3,
                "roleName": "管理权限角色",
                "roleRemark": "管理菜单权限",
                "menus": "",
                "menuPowers": "",
                "roleStatus": 1,
                "createDate": 1683614324526
            }
        ]
    */

    // 这样方便做假数据测试了吧
}

// 确认数据
function Confirm() {
    /*
        dialog 确认事件
        确认数据是判断当前组件是多选还是单选,多选返回数组,单选返回数字
        tableRef.value.getSelectionRows() getSelectionRows方法拿到当前表格选中的数据
        select.map(i => i[props.checkValue]) 将所需要的值返回成一个数组
        emit('change', select[0]) 调用父组件Change方法,将选中的整个对象或整个数据返回
    */
    let select = tableRef.value.getSelectionRows()
    if (props.multiple) {
        let ids = select.map(i => i[props.checkValue])
        emit('update:modelValue', ids)
        emit('change', select)
    } else {
        emit('update:modelValue', select[0][props.checkValue])
        emit('change', select[0])
    }
    state.tableDialog = false
}

// 处理表格数据
function fieldChange(row, option) {
    if (option[row]) {
        return option[row]
    }
}

// 表格
// el-table select事件,手动勾选数据行checkbox时触发
/*
    这里需要判断,组件为单选时
    需要先试用 clearSelection 方法清除用户的选择
    在使用 toggleRowSelection 方法根据select返回的第二个参数 row 去选中用户选择的行
*/
function selectChange(e, row) {
    if (!props.multiple) {
        tableRef.value.clearSelection()
        nextTick(function () {
            tableRef.value.toggleRowSelection(row, true)
        })
    }
}

// 下面就是一些分页和搜索触发的事件了

// 分页 页数事件
function sizeChange(item) {
    state.pages.limit = item
    getDataList()
}

// 分页条数事件
function currentChange(item) {
    state.pages.page = item
    getDataList()
}

function tableSearch() {
    getDataList()
}
function tableRecover() {
    state.queryForm = {}
    getDataList()
}
script>

<style lang="scss" scoped>
.l-table-dialog {

    .el-table {
        height: calc(100% - 76px);
    }

    .multipleShow {
        ::v-deep .el-table__header {
            .el-table-column--selection {
                .cell {
                    display: none;
                }
            }
        }
    }

    .el-select {
        width: 220px;
    }

    ::v-deep .el-dialog {
        width: 1000px;
        height: 640px;

        .el-dialog__body {
            height: calc(100% - 110px);
            padding: 5px 20px;
        }
    }

    .l-pages {
        margin-top: 10px;
    }
}
style>

结尾

LQuery组件链接Element Plus el-table表格查询封装

组件如果有哪些做的不合理的地方,还请大佬指正。
使用时注意请求列表数据的地方替换成自己的即可。
不懂的地方可以留言或私聊。

谢谢大家观看~

你可能感兴趣的:(Element,Vue,vue.js,elementui,前端)