فهرست منبع

feat: 角色编辑弹窗增加菜单权限分配功能

在角色新增/编辑弹窗中增加菜单树勾选,支持拖拽展开折叠,
保存时自动同步角色-菜单关联关系。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
skyline 1 هفته پیش
والد
کامیت
b6a91c92d8
2فایلهای تغییر یافته به همراه74 افزوده شده و 7 حذف شده
  1. 5 0
      haha-admin-web/src/api/system.ts
  2. 69 7
      haha-admin-web/src/views/system/role/utils/hook.tsx

+ 5 - 0
haha-admin-web/src/api/system.ts

@@ -103,3 +103,8 @@ export const getRoleMenu = (data?: object) => {
 export const getRoleMenuIds = (data?: object) => {
   return http.request<Result>("post", "/menu/role-menu-ids", { data });
 };
+
+/** 保存角色菜单关联 */
+export const saveRoleMenus = (data: { roleId: number; menuIds: number[] }) => {
+  return http.request<Result>("post", "/menu/save-role-menus", { data });
+};

+ 69 - 7
haha-admin-web/src/views/system/role/utils/hook.tsx

@@ -10,6 +10,8 @@ import {
   updateRole,
   deleteRole
 } from "@/api/role";
+import { getRoleMenu, getRoleMenuIds, saveRoleMenus } from "@/api/system";
+import { handleTree } from "@/utils/tree";
 import {
   ref,
   toRaw,
@@ -39,6 +41,9 @@ export function useRole() {
   const switchLoadMap = ref({});
   const isDataLoaded = ref(false);
   const { switchStyle } = usePublicHooks();
+  const menuTree = ref([]);
+  const checkedMenuIds = ref<number[]>([]);
+  const roleMenuTreeRef = ref();
   const pagination = reactive<PaginationProps>({
     total: 0,
     pageSize: 10,
@@ -213,21 +218,40 @@ export function useRole() {
     onSearch();
   };
 
+  // 加载角色菜单数据
+  async function loadRoleMenus(roleId: number) {
+    try {
+      const menuRes = await getRoleMenu();
+      menuTree.value = handleTree(menuRes.data || []);
+    } catch {
+      menuTree.value = [];
+    }
+    try {
+      const idsRes = await getRoleMenuIds({ id: roleId });
+      checkedMenuIds.value = (idsRes.data || []).map(String);
+    } catch {
+      checkedMenuIds.value = [];
+    }
+  }
+
   // 打开弹窗
-  function openDialog(title = "新增", row?: FormItemProps) {
+  async function openDialog(title = "新增", row?: FormItemProps) {
     // 将数据复制到表单对象
     if (row) {
       form.name = row.name ?? "";
       form.code = row.code ?? "";
       form.status = row.status ?? 1;
       form.remark = row.remark ?? "";
+      await loadRoleMenus(row.id);
     } else {
       form.name = "";
       form.code = "";
       form.status = 1;
       form.remark = "";
+      menuTree.value = [];
+      checkedMenuIds.value = [];
     }
-    
+
     addDialog({
       title: `${title}角色`,
       props: {
@@ -239,7 +263,7 @@ export function useRole() {
           remark: row?.remark ?? ""
         }
       },
-      width: "40%",
+      width: "50%",
       draggable: true,
       fullscreen: deviceDetection(),
       fullscreenIcon: true,
@@ -279,6 +303,32 @@ export function useRole() {
               rows={3}
             />
           </ElFormItem>
+          <ElFormItem label="菜单权限">
+            <div style={{
+              maxHeight: "300px",
+              overflow: "auto",
+              width: "100%",
+              border: "1px solid var(--el-border-color)",
+              borderRadius: "4px",
+              padding: "8px"
+            }}>
+              {menuTree.value.length > 0 ? (
+                <el-tree
+                  ref={roleMenuTreeRef}
+                  data={menuTree.value}
+                  props={{ children: "children", label: "title" }}
+                  node-key="id"
+                  show-checkbox
+                  default-expand-all
+                  default-checked-keys={checkedMenuIds.value}
+                />
+              ) : (
+                <div style={{ textAlign: "center", padding: "16px", color: "var(--el-text-color-placeholder)" }}>
+                  {row ? "暂无菜单数据" : "新增角色请先保存,再编辑分配菜单"}
+                </div>
+              )}
+            </div>
+          </ElFormItem>
         </ElForm>
       ),
       beforeSure: async (done) => {
@@ -286,25 +336,37 @@ export function useRole() {
         if (!valid) return;
 
         try {
+          let roleId = row?.id;
+
           if (title === "新增") {
             const res = await addRole(toRaw(form));
             if (res.code === 200) {
+              roleId = res.data?.id;
               message(`新增角色 ${form.name} 成功`, { type: "success" });
-              done();
-              onSearch();
             } else {
               message(res.message || "新增失败", { type: "error" });
+              return;
             }
           } else {
             const res = await updateRole({ ...toRaw(form), id: row.id });
             if (res.code === 200) {
               message(`修改角色 ${form.name} 成功`, { type: "success" });
-              done();
-              onSearch();
             } else {
               message(res.message || "修改失败", { type: "error" });
+              return;
             }
           }
+
+          // 保存角色菜单关联
+          if (roleId && roleMenuTreeRef.value) {
+            const checkedKeys = roleMenuTreeRef.value.getCheckedKeys(false);
+            const halfCheckedKeys = roleMenuTreeRef.value.getHalfCheckedKeys();
+            const allMenuIds = [...checkedKeys, ...halfCheckedKeys].map(Number);
+            await saveRoleMenus({ roleId, menuIds: allMenuIds });
+          }
+
+          done();
+          onSearch();
         } catch (error) {
           message("操作失败", { type: "error" });
         }