在开发企业级应用时,权限控制无疑是至关重要且不可或缺的一部分。合理的权限控制不仅能够有效保障系统的安全性,还能确保不同用户角色在系统中拥有合适的操作权限,从而提高系统的使用效率和稳定性。本文将详细介绍如何在 Vue3 项目中实现功能权限配置,为开发者提供一个可参考的实践方案。
实现步骤
1、后端返回的数据结构
在进行功能权限配置之前,我们首先需要了解后端返回的数据结构。这些数据将为前端的权限展示和处理提供基础。
1.1该角色可分配的所有权限
后端返回的 funtionsData1
数据结构中,包含了系统中各个菜单及其对应的功能权限信息。具体如下:
export const funtionsData1 = [
{
"menuId": 10000,
"menuName": "系统管理",
"listPermission": null,
"sonMenuButtonCustom": [
{
"menuId": 10100,
"menuName": "组织架构",
"listPermission": null,
"sonMenuButtonCustom": [
{
"menuId": 10101,
"menuName": "公司管理",
"listPermission": [
{
"id": 8,
"name": "增加公司",
"uri": "POST/organizes",
"menuId": 10101
},
{
"id": 9,
"name": "删除公司",
"uri": "DELETE/organizes/batch",
"menuId": 10101
},
{
"id": 10,
"name": "修改公司",
"uri": "PUT/organizes/{id}",
"menuId": 10101
}
],
"sonMenuButtonCustom": null
},
{
"menuId": 10102,
"menuName": "部门管理",
"listPermission": [
{
"id": 11,
"name": "增加部门",
"uri": "POST/departments",
"menuId": 10102
},
{
"id": 12,
"name": "删除部门",
"uri": "DELETE/departments/batch",
"menuId": 10102
},
{
"id": 13,
"name": "修改部门",
"uri": "PUT/departments/{id}",
"menuId": 10102
}
],
"sonMenuButtonCustom": null
},
{
"menuId": 10103,
"menuName": "用户管理",
"listPermission": [
{
"id": 1,
"name": "增加用户",
"uri": "POST/users",
"menuId": 10103
},
{
"id": 2,
"name": "删除用户",
"uri": "DELETE/users/batch",
"menuId": 10103
},
{
"id": 3,
"name": "修改用户",
"uri": "PUT/users/{id}",
"menuId": 10103
},
{
"id": 31,
"name": "用户角色分配",
"uri": "POST/users/{id}/role",
"menuId": 10103
}
],
"sonMenuButtonCustom": null
},
{
"menuId": 10104,
"menuName": "角色管理",
"listPermission": [
{
"id": 14,
"name": "增加角色",
"uri": "POST/roles",
"menuId": 10104
},
{
"id": 15,
"name": "删除角色",
"uri": "DELETE/roles/batch",
"menuId": 10104
},
{
"id": 16,
"name": "修改角色",
"uri": "PUT/roles/{id}",
"menuId": 10104
},
{
"id": 17,
"name": "角色菜单分配",
"uri": "POST/roles/{id}/menu",
"menuId": 10104
},
{
"id": 18,
"name": "角色功能按钮分配",
"uri": "POST/roles/{id}/permission",
"menuId": 10104
}
],
"sonMenuButtonCustom": null
}
]
},
{
"menuId": 10200,
"menuName": "入库管理",
"listPermission": [
{
"id": 4,
"name": "增加入库单",
"uri": "POST/regulations",
"menuId": 10200
},
{
"id": 5,
"name": "删除入库单",
"uri": "DELETE/regulations/batch",
"menuId": 10200
},
{
"id": 6,
"name": "上传入库附件",
"uri": "POST/regulations/uploadFile",
"menuId": 10200
},
{
"id": 7,
"name": "修改入库单",
"uri": "PUT/regulations/{id}",
"menuId": 10200
}
],
"sonMenuButtonCustom": []
},
{
"menuId": 10300,
"menuName": "出库管理",
"listPermission": [
{
"id": 19,
"name": "增加出库单",
"uri": "POST/meters",
"menuId": 10300
},
{
"id": 20,
"name": "删除出库单",
"uri": "DELETE/meters/batch",
"menuId": 10300
},
{
"id": 21,
"name": "修改出库单",
"uri": "PUT/meters/{id}",
"menuId": 10300
},
{
"id": 22,
"name": "上传出库单附件",
"uri": "POST/meters/uploadFile",
"menuId": 10300
},
{
"id": 23,
"name": "记录溯源信息",
"uri": "POST/meters/source",
"menuId": 10300
},
{
"id": 24,
"name": "删除溯源信息",
"uri": "DELETE/meters/source/{id}",
"menuId": 10300
}
],
"sonMenuButtonCustom": []
},
{
"menuId": 10400,
"menuName": "固定资产管理",
"listPermission": [
{
"id": 25,
"name": "增加固定资产",
"uri": "POST/substances",
"menuId": 10400
},
{
"id": 26,
"name": "删除固定资产",
"uri": "DELETE/substances/batch",
"menuId": 10400
},
{
"id": 27,
"name": "修改固定资产",
"uri": "PUT/substances/{id}",
"menuId": 10400
},
{
"id": 28,
"name": "上传固定资产附件",
"uri": "POST/substances/uploadFile",
"menuId": 10400
},
{
"id": 29,
"name": "记录溯源信息",
"uri": "POST/substances/source",
"menuId": 10400
},
{
"id": 30,
"name": "删除溯源信息",
"uri": "DELETE/substances/source/{id}",
"menuId": 10400
}
],
"sonMenuButtonCustom": []
},
{
"menuId": 10500,
"menuName": "日志管理",
"listPermission": null,
"sonMenuButtonCustom": []
}
]
},
{
"menuId": 20000,
"menuName": "财务管理",
"listPermission": null,
"sonMenuButtonCustom": [
{
"menuId": 20300,
"menuName": "对账单",
"listPermission": [
{
"id": 32,
"name": "增加对账单",
"uri": "POST/calibrationLists",
"menuId": 20300
},
{
"id": 33,
"name": "删除对账单",
"uri": "DELETE/calibrationLists/batch",
"menuId": 20300
}
],
"sonMenuButtonCustom": []
}
]
},
{
"menuId": 30000,
"menuName": "报价管理",
"listPermission": null,
"sonMenuButtonCustom": [
{
"menuId": 30300,
"menuName": "报价单",
"listPermission": [
{
"id": 323,
"name": "增加报价单",
"uri": "POST/calibrationLists",
"menuId": 20300
},
{
"id": 333,
"name": "删除报价单",
"uri": "DELETE/calibrationLists/batch",
"menuId": 20300
}
],
"sonMenuButtonCustom": []
},
{
"menuId": 30400,
"menuName": "委托服务管理",
"sonMenuButtonCustom": [
{
"menuId": 304000,
"menuName": "委托单",
"sonMenuButtonCustom": [
],
"listPermission": [
{
"id": 3233,
"name": "增加委托单",
"uri": "POST/calibrationLists",
"menuId": 203000
},
{
"id": 3333,
"name": "删除委托单",
"uri": "DELETE/calibrationLists/batch",
"menuId": 203000
}
],
},
{
"menuId": 304001,
"menuName": "下厂单",
"sonMenuButtonCustom": [
],
"listPermission": [
{
"id": 32333,
"name": "增加下厂单",
"uri": "POST/calibrationLists",
"menuId": 2030003
},
{
"id": 33333,
"name": "删除下厂单",
"uri": "DELETE/calibrationLists/batch",
"menuId": 2030003
}
],
}
]
}
]
}
]
该数据结构以树状形式呈现,每个菜单节点包含 menuId
(菜单 ID)、menuName
(菜单名称)、listPermission
(当前菜单的功能权限列表)以及 sonMenuButtonCustom
(子菜单及其功能权限)等信息。
1.2该角色已经分配的权限id
rolePermissionData
数组中记录了当前角色已经分配的权限 ID。通过这些 ID,前端可以判断哪些功能权限是当前角色所拥有的。
export const rolePermissionData = [
32,
8,
9,
10,
11,
12,
13,
1,
2,
3,
31,
14,
15,
16,
17,
18,
6,
19,
20,
21,
25,
26,
27,
28
]
2、 封装功能权限弹窗组件
为了更好地管理和展示功能权限配置,我们将功能权限弹窗相关逻辑封装成组件。
2.1 CustomFunctionsDialog.vue
CustomFunctionsDialog.vue
组件用于展示功能权限配置的弹窗界面。具体代码如下:
<template>
<el-dialog title="功能权限" width="1000" center destroy-on-close>
<div>
<div>角色名称:jiaberr</div>
<div>
<text>功能权限:</text>
<el-checkbox v-model="checkAll" @change="handleCheckAllChange">
全选/全不选
</el-checkbox>
</div>
<div class="w-p-100 containner">
<nested-menu :data="funtionsData" :checkedData="checkedData" />
</div>
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="close">取消</el-button>
<el-button type="primary" @click="handleSubmit"> 确定 </el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, provide, watchEffect } from "vue";
import {
funtionsData1,
funtionsData2,
rolePermissionData,
} from "./simulatedData.js";
import NestedMenu from "./NestedMenu.vue"; // 确保正确导入NestedMenu组件
const props = defineProps({
roleId: {
Type: Number,
required: true,
},
});
const checkAll = ref(false);
const funtionsData = ref();
watchEffect(() => {
// 仅模拟页面效果使用,真实项目funtionsData需根据id获取后台数据
if (props.roleId == 1) {
funtionsData.value = funtionsData1;
} else {
funtionsData.value = funtionsData2;
}
});
const emits = defineEmits(["close"]);
const close = () => {
emits("close");
};
const checkedData = ref(rolePermissionData);
const handleCheckAllChange = (val) => {
checkedData.value = val ? permissionIds : [];
console.log(checkedData.value);
};
// provide('checkedData', checkedData.value);
const handleCheckData = (id) => {
let index = checkedData.value.findIndex((val) => val == id);
if (index != -1) {
checkedData.value.splice(index, 1);
} else {
checkedData.value.push(id);
}
};
provide("handleEvent", handleCheckData);
// 提交
const handleSubmit = () => {
// 将选中的id数组上传至后台接口
console.log(checkedData.value);
emits("close");
};
// 收集所有功能权限的id
const extractPermissionIds = (data) => {
const ids = [];
// 递归遍历函数
function traverse(node) {
if (node.listPermission && Array.isArray(node.listPermission)) {
// 将 listPermission 中的 id 添加到 ids 数组
node.listPermission.forEach((permission) => {
ids.push(permission.id);
});
}
// 如果有子节点,继续递归遍历
if (node.sonMenuButtonCustom && Array.isArray(node.sonMenuButtonCustom)) {
node.sonMenuButtonCustom.forEach((child) => traverse(child));
}
}
// 遍历根节点
data.forEach((item) => traverse(item));
return ids;
};
const permissionIds = extractPermissionIds(funtionsData.value);
</script>
<style lang="scss" scoped>
.containner {
border-top: 1px solid #d3d3d3;
border-right: 1px solid #d3d3d3;
border-bottom: 1px solid #d3d3d3;
}
</style>
该组件通过接收 roleId
来获取对应的权限数据,并提供了全选、单选以及提交功能权限配置的功能。
2.2 NestedMenu.vue
由于后台返回的数据层级关系较为复杂,为了更方便地处理权限分配的展示,我们将权限展示部分单独抽离成 NestedMenu.vue
组件。该组件可以实现循环嵌套,以展示不同层级的菜单和权限。
<template>
<div class="flex1 border-left">
<div
v-for="(item, index) in data"
:key="item.menuId + index"
class="flex align-center"
:class="index == data.length - 1 ? '' : 'border-bottom'"
>
<div class="menuName text-center">{{ item.menuName }}</div>
<nested-menu
v-if="item.sonMenuButtonCustom && item.sonMenuButtonCustom.length"
:data="item.sonMenuButtonCustom"
:checked-data="checkedData"
/>
<div
v-else
class="flex flex-wrap flex1 border-left pl-10 pt-5 pr-10 pb-5 min-height"
>
<el-checkbox
v-for="(ite, idx) in item.listPermission"
:key="ite.id || idx"
:model-value="isChecked(ite.id)"
@change="handleChange(ite.id)"
>
{{ ite.name }}
</el-checkbox>
</div>
</div>
</div>
</template>
<script setup>
import { inject } from "vue";
const handleEvent = inject("handleEvent");
const props = defineProps({
data: Array,
checkedData: Array,
});
// 判断是否选中
const isChecked = (id) => {
return props.checkedData.includes(id);
};
// 调用父组件的方法,修改checkedData
const handleChange = (id) => {
handleEvent(id);
};
</script>
<style scoped>
.border-bottom {
border-bottom: 1px solid #d3d3d3;
}
.border-left {
border-left: 1px solid #d3d3d3;
}
.menuName {
width: 110px;
}
.min-height {
min-height: 40px;
}
</style>
该组件通过接收父组件传递的权限数据和已选中的权限数据,动态展示菜单和权限的勾选状态,并在权限勾选状态发生变化时通知父组件。
3、在角色管理页面中使用
我们在角色管理页面 role.vue
中引入上述封装的组件,以实现功能权限配置的具体应用。
3.1 role.vue
<template>
<div>
<CustomSearch
class="mb-20"
:searchConfig="roleSearchConfig"
@updateQueryData="updateQueryData"
></CustomSearch>
<CustomTable
:tableConfig="roleTableConfig"
:tableData="roleData"
:total="total"
@updateQueryData="updateQueryData"
>
<template #handle="row">
<el-button text type="success" size="small">编辑</el-button>
<el-button text type="danger" size="small">删除</el-button>
<el-button text type="primary" size="small">菜单权限</el-button>
<el-button text type="primary" size="small" @click="handleFunctionalAuthority(row.row.id)">功能权限</el-button>
</template>
</CustomTable>
</div>
<CustomFunctionsDialog v-model="functionsDialogVisible" @close = "functionsDialogVisible = false" :roleId="roleId"></CustomFunctionsDialog>
</template>
<script setup>
import CustomTable from "@/components/CustomTable/index.vue";
import CustomSearch from "@/components/CustomSearch/index.vue";
import CustomFunctionsDialog from "./CustomFunctionsDialog.vue"
import { roleTableConfig } from "./tableConfig.js";
import { roleSearchConfig } from "./searchConfig.js";
import { roleData } from "./simulatedData.js";
import { ref } from "vue";
const total = ref(0);
const updateQueryData = (params, bool) => {};
// 打开功能权限弹窗
const roleId = ref() // 根据不同的角色id获取不同的权限按钮
const functionsDialogVisible = ref(false)
const handleFunctionalAuthority = (id) => {
roleId.value = id
functionsDialogVisible.value = true
}
</script>
<style lang="scss" scoped>
</style>
在 role.vue
页面中,通过点击 “功能权限” 按钮,可以打开功能权限配置弹窗,并根据不同的角色 ID 获取相应的权限数据进行展示和配置。
4. 总结
通过上述步骤,我们在Vue3项目中实现了基本的功能权限配置。这种权限控制方式不仅提高了系统的安全性,还使得代码更加模块化和易于维护。在实际应用中,可以根据具体需求进一步完善和扩展权限管理系统。
完整代码实现请参考开源项目:vue3-pc-template
欢迎 Star 和 Fork 项目,一起构建更完善的权限管理体系!