本篇聚焦于角色菜單權限分配功能的實現,圍繞“給角色賦予菜單權限”這一核心場景,從接口設計、組件封裝到頁面集成展開完整技術方案的闡述。主要內容包括:
1. 角色權限接口開發:定義獲取角色權限、分配權限等接口,規范數據交互格式;
2. 權限分配組件封裝:實現基于樹形結構的可視化權限配置組件,支持全選、父子節點聯動控制等功能;
3. 角色管理頁面集成:新增“菜單權限”操作按鈕,通過組件化方式關聯權限分配邏輯。?
?1 角色權限 api
在 src/api/roleAccess.ts 中添加角色權限相關 api,代碼如下:
//src/api/roleAccess.ts
import service from "./config/request";
import type { ApiResponse } from "./type";export interface IRoleAccess {id: number;access_id: number;role_id: number;
}export type IRoleAccessList = IRoleAccess[];// 獲取角色對應權限
export const getRoleAccess = (id: number
): Promise<ApiResponse<IRoleAccessList>> => {return service.get(`/role_access/${id}`);
};// 給角色分配權限
export const allocRoleAccess = (id: number,data: number[]
): Promise<ApiResponse> => {return service.post(`/role_access/${id}`, {access: data});
};// 獲取角色對應權限
export const getRoleAccessByRoles = (roles: number[]
): Promise<ApiResponse<any>> => {return service.post(`/role_access/role/access`, {roles});
};
2 封裝 RoleMenu 組件
在?/src/views/system/role/components/roleMenu.vue 中封裝角色菜單分配組件,代碼如下:
//src/views/system/role/components/roleMenu.vue
<template><el-dialog v-model="dialogVisible"><!-- 權限樹組件,展示菜單結構并支持勾選 --><el-tree:data="treeData"show-checkbox:props="defaultProps":default-expand-all="true"highlight-currentref="menuTree"node-key="id":check-strictly="checkStrictly"></el-tree><template #footer><el-button type="primary" @click="handleCheckAll">全部選擇</el-button><el-button type="warning" @click="handleSubmit">確認分配</el-button></template></el-dialog>
</template><script lang="ts" setup>
import type { IRole } from "@/api/role";
import { useMenuStore } from "@/stores/menu";
import type { PropType } from "vue";
import { ElTree } from "element-plus";
import Node from "element-plus/es/components/tree/src/model/node.mjs";
import { allocRoleAccess, getRoleAccess } from "@/api/roleAccess";
import { useReloadPage } from "@/hooks/useReloadPage";// 是否父子節點關聯(true:不關聯,false:關聯)
const checkStrictly = ref(false);
const { reloadPage } = useReloadPage();
const store = useMenuStore();
// 從菜單存儲獲取樹形菜單數據
const treeData = computed(() => store.state.menuTreeData);// 生命周期鉤子:組件掛載后獲取所有菜單數據
onMounted(() => {store.getAllMenuList();
});// 樹組件配置項,指定子節點和標簽字段
const defaultProps = {children: "children",label: "title"
};// 樹組件實例類型和引用
type ElTreeInstance = InstanceType<typeof ElTree>;
const menuTree = ref<ElTreeInstance | null>(null);// 全選狀態標識
const isCheckAll = ref(false);/*** 全選/取消全選處理函數*/
const handleCheckAll = () => {if (!isCheckAll.value) {// 設置所有節點為選中狀態menuTree.value?.setCheckedNodes(treeData.value as unknown as Node[], false);} else {// 取消所有選中狀態menuTree.value?.setCheckedNodes([], false);}isCheckAll.value = !isCheckAll.value;
};const { proxy } = getCurrentInstance()!;/*** 提交權限分配處理函數*/
const handleSubmit = async () => {const tree = menuTree.value!;// 獲取所有選中的節點IDconst keys = tree.getCheckedKeys(false);// 獲取半選中的節點ID(部分子節點被選中)const halfKeys = tree.getHalfCheckedKeys();const selectKeys = [...keys, ...halfKeys];// 調用API分配權限const res = await allocRoleAccess(role.id, selectKeys as number[]);if (res.code === 0) {proxy?.$message.success("權限分配成功");reloadPage(); // 刷新頁面}
};// 定義組件接收的props
const { role, modelValue } = defineProps({role: {type: Object as PropType<IRole>,required: true},modelValue: {type: Boolean,default: false}
});// 對話框顯示狀態
const dialogVisible = ref(modelValue);
// 定義自定義事件
const emit = defineEmits(["update:modelValue"]);// 監聽對話框顯示狀態變化,觸發自定義事件
watch(() => dialogVisible.value,(newValue) => {emit("update:modelValue", newValue);}
);/*** 獲取角色已分配的權限列表*/
const getRoleAccessList = async () => {checkStrictly.value = true; // 臨時解除父子節點關聯,避免聯動問題const res = await getRoleAccess(role.id);if (res.code === 0) {const access = res.data.map((item) => item.access_id);// 設置已選中的權限節點menuTree.value?.setCheckedKeys(access);// 異步恢復父子節點關聯setTimeout(() => {checkStrictly.value = false;}, 0);}
};// 生命周期鉤子:組件掛載后獲取角色權限
onMounted(() => {getRoleAccessList();
});
</script>
3 修改角色管理頁面
修改 src/views/system/role/index.vue,添加角色菜單權限,如圖所示:
代碼如下:
//src/views/system/role/index.vue
<template><div p-30px><h2>角色管理</h2><el-button @click="hanleAddRole">角色添加</el-button><el-table :data="roles" style="width: 100%"><el-table-column prop="id" label="角色id" width="180" /><el-table-column prop="name" label="角色名稱" width="180" /><el-table-column prop="description" label="描述" /><el-table-columnprop="is_default"label=" 默認角色":formatter="formatter"/><el-table-column label="操作" fixed="right"><template #default="scope"><el-button link @click="handleRoleMenu(scope.row)">菜單權限</el-button><el-button link @click="handleEditRole(scope.row)">編輯</el-button><el-button link @click="handleRemove(scope.row)">刪除</el-button></template></el-table-column></el-table><el-pagination:page-sizes="[1, 5, 10, 20]"layout="prev, pager, next, sizes, total":total="count":page-size="pageSize"@size-change="handleSizeChange"@current-change="handleCurrentChange"/><!-- 右側面板組件,使用 v-model 綁定 visible 控制顯示隱藏,設置標題 --><right-panel v-model="visible" :title="panelTitle"><!-- 角色編輯組件,傳遞編輯類型和編輯數據,監聽 submit 事件 --><editor-role:type="editType":data="editData"@submit="handleSubmit"></editor-role></right-panel><role-menu:role="roleData"v-model="roleMenuVisible"v-if="roleMenuVisible && roleData"></role-menu></div>
</template><script lang="ts" setup>
import type { IRole } from "@/api/role";
import { useRoleStore } from "@/stores/role";
import { useRoleHelpers } from "./roleHelpers";
import { ref, toRefs, watchEffect } from "vue";const roleData = ref<IRole | null>(null);
const roleMenuVisible = ref(false);
const handleRoleMenu = (row: IRole) => {roleMenuVisible.value = true;roleData.value = row;
};// 獲取角色狀態管理倉庫實例
const store = useRoleStore();
// 當前頁碼,初始為 0
const pageNum = ref(0);
// 每頁顯示的記錄數,初始為 10
const pageSize = ref(10);// 從 useRoleHelpers 組合式函數中解構出所需的方法和狀態
const {handleSubmit,handleRemove,handleEditRole,hanleAddRole,panelTitle,editType,visible,editData
} = useRoleHelpers({ pageNum, pageSize });// 將倉庫狀態中的 count 和 roles 轉換為響應式引用
const { count, roles } = toRefs(store.state);// 監聽 pageNum 和 pageSize 的變化,當變化時重新獲取角色數據
watchEffect(() => {store.getRoles({ pageNum: pageNum.value, pageSize: pageSize.value, flag: 0 });
});// 處理每頁顯示記錄數改變的方法
const handleSizeChange = (val: number) => {pageSize.value = val;
};// 處理當前頁碼改變的方法
const handleCurrentChange = (val: number) => {pageNum.value = val - 1;
};// 格式化是否為默認角色的顯示內容
const formatter = (row: IRole) => {return row.is_default ? "是" : "否";
};
</script>
以上,就是角色菜單的相關內容。
下一篇將繼續探討 菜單權限實現,敬請期待~