首先我们要知道为什么封装一个有el-dialog
、el-table
、el-select
的组件
大家在开发后台管理项目
中,肯定会遇到这样的一种情况
比如:我们项目中存在
用户管理
和角色管理
两个功能模块
我们在去添加用户的时候需要去绑定角色,且绑定的角色是可以多选的
如下图:
大多数我们是不是会去这样写
那么这样写有问题吗,当然是莫得问题了。
可是如果在选择角色的时候,我想看这个角色的描述、或者是头像、又或者是角色的权限,这时候应该怎么做呢。
我总不能,取消这次的编辑,去角色的功能模块看好了再去新增用户,这样是不是很麻烦。
接下来我们封装的组件就完美的解决了这个问题
我们先来展示效果图
查询、分页都有的。
主页面没有什么要说的,就一个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>
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 // 菜单搜索条件
}
<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表格查询封装
组件如果有哪些做的不合理的地方,还请大佬指正。
使用时注意请求列表数据的地方替换成自己的即可。
不懂的地方可以留言或私聊。
谢谢大家观看~