Преглед изворни кода

角色菜单选中父亲节点问题

pm пре 4 месеци
родитељ
комит
c66ca13d8b
1 измењених фајлова са 74 додато и 41 уклоњено
  1. 74 41
      src/components/RoleAssignMenuForm.tsx

+ 74 - 41
src/components/RoleAssignMenuForm.tsx

@@ -32,17 +32,46 @@ const RoleAssignMenuForm = forwardRef<RoleAssignMenuFormRef, RoleAssignMenuFormP
   });
   const [menuOptions, setMenuOptions] = useState<DataNode[]>([]);
   const [checkedKeys, setCheckedKeys] = useState<React.Key[]>([]);
+  const [halfCheckedKeys, setHalfCheckedKeys] = useState<React.Key[]>([]);
   const [expandedKeys, setExpandedKeys] = useState<React.Key[]>([]);
   const [menuExpand, setMenuExpand] = useState(false);
   const [treeNodeAll, setTreeNodeAll] = useState(false);
   const [form] = Form.useForm();
 
+  // 获取所有节点 key(含父/子)
+  const getAllKeys = (nodes: DataNode[]): React.Key[] => {
+    let keys: React.Key[] = [];
+    nodes.forEach((node) => {
+      keys.push(node.key);
+      if (node.children) {
+        keys = keys.concat(getAllKeys(node.children));
+      }
+    });
+    return keys;
+  };
+
+  // 获取所有“父节点”key(有 children 的节点)
+  const getParentKeys = (nodes: DataNode[]): Set<React.Key> => {
+    const parents = new Set<React.Key>();
+    const walk = (ns: DataNode[]) => {
+      ns.forEach((n) => {
+        if (n.children && n.children.length > 0) {
+          parents.add(n.key);
+          walk(n.children);
+        }
+      });
+    };
+    walk(nodes);
+    return parents;
+  };
+
   // 将菜单数据转换为 Tree 组件需要的格式
-  const convertMenuToTreeData = (menus: MenuVO[]): DataNode[] => {
-    return menus.map((menu) => ({
-      title: menu.name,
-      key: menu.id!,
-      children: menu.children ? convertMenuToTreeData(menu.children) : undefined,
+  // 注意:handleTree 返回的节点类型不是 MenuVO(会带 children),这里用更宽松的类型兼容
+  const convertMenuToTreeData = (menus: any[]): DataNode[] => {
+    return (menus || []).map((menu: any) => ({
+      title: menu?.name,
+      key: menu?.id,
+      children: menu?.children ? convertMenuToTreeData(menu.children) : undefined,
     }));
   };
 
@@ -74,7 +103,16 @@ const RoleAssignMenuForm = forwardRef<RoleAssignMenuFormRef, RoleAssignMenuFormP
           const menuIds = await roleApi.getRoleMenuList(row.id!);
           const menuIdsData = (menuIds as any)?.data || menuIds;
           setFormData(prev => ({ ...prev, menuIds: menuIdsData || [] }));
-          setCheckedKeys((menuIdsData || []).map((id: number) => id));
+          // 回显:后端可能返回父节点ID(如 3127)。
+          // 若使用父子联动(checkStrictly=false),父节点会导致所有子节点被渲染为选中。
+          // 这里改用 checkStrictly=true(父子不联动),并将“父节点ID”放入 halfCheckedKeys,
+          // “叶子节点/实际选中的节点ID”放入 checkedKeys,避免回显误全选子节点。
+          const rawKeys = (menuIdsData || []).map((id: number) => id);
+          const parentKeySet = getParentKeys(treeNodes);
+          const checked = rawKeys.filter((k: number) => !parentKeySet.has(k));
+          const half = rawKeys.filter((k: number) => parentKeySet.has(k));
+          setCheckedKeys(checked);
+          setHalfCheckedKeys(half);
         } catch (error: any) {
           console.error(t('role.getRoleMenuFailed'), error);
         } finally {
@@ -92,12 +130,16 @@ const RoleAssignMenuForm = forwardRef<RoleAssignMenuFormRef, RoleAssignMenuFormP
     try {
       setFormLoading(true);
       
-      // 获取所有选中的节点(包括半选中的父节点)
-      const checked = checkedKeys as number[];
+      // 获取所有选中的节点(包含半选中的父节点)
+      // 说明:很多“一级菜单/父节点”在只勾选部分子节点时会处于半选中(halfChecked),
+      // 若后端需要父节点ID,也必须把 halfChecked 一并提交。
+      const allSelected = Array.from(new Set([...checkedKeys, ...halfCheckedKeys]));
+      const menuIds = allSelected.map(key => Number(key)).filter(id => !isNaN(id));
+      console.log('提交菜单权限,选中的节点IDs(含半选父节点):', menuIds);
       
       const data = {
         roleId: formData.id!,
-        menuIds: checked,
+        menuIds,
       };
 
       await roleApi.assignRoleMenu(data);
@@ -131,19 +173,11 @@ const RoleAssignMenuForm = forwardRef<RoleAssignMenuFormRef, RoleAssignMenuFormP
     setTreeNodeAll(checked);
     if (checked) {
       // 获取所有节点的 key
-      const getAllKeys = (nodes: DataNode[]): React.Key[] => {
-        let keys: React.Key[] = [];
-        nodes.forEach((node) => {
-          keys.push(node.key);
-          if (node.children) {
-            keys = keys.concat(getAllKeys(node.children));
-          }
-        });
-        return keys;
-      };
       setCheckedKeys(getAllKeys(menuOptions));
+      setHalfCheckedKeys([]);
     } else {
       setCheckedKeys([]);
+      setHalfCheckedKeys([]);
     }
   };
 
@@ -151,16 +185,6 @@ const RoleAssignMenuForm = forwardRef<RoleAssignMenuFormRef, RoleAssignMenuFormP
   const handleCheckedTreeExpand = (expanded: boolean) => {
     setMenuExpand(expanded);
     if (expanded) {
-      const getAllKeys = (nodes: DataNode[]): React.Key[] => {
-        let keys: React.Key[] = [];
-        nodes.forEach((node) => {
-          keys.push(node.key);
-          if (node.children) {
-            keys = keys.concat(getAllKeys(node.children));
-          }
-        });
-        return keys;
-      };
       setExpandedKeys(getAllKeys(menuOptions));
     } else {
       setExpandedKeys([]);
@@ -168,19 +192,25 @@ const RoleAssignMenuForm = forwardRef<RoleAssignMenuFormRef, RoleAssignMenuFormP
   };
 
   // 树节点选中变化
-  const onCheck = (checked: React.Key[]) => {
+  // 注意:Ant Design Tree 的 onCheck:
+  // - checkStrictly=false(父子联动):checkedKeysValue 为数组,半选节点在 info.halfCheckedKeys
+  // - checkStrictly=true(父子不联动):checkedKeysValue 为 { checked, halfChecked }
+  const onCheck = (
+    checkedKeysValue: React.Key[] | { checked: React.Key[]; halfChecked: React.Key[] },
+    info: any
+  ) => {
+    let checked: React.Key[] = [];
+    let half: React.Key[] = [];
+    if (Array.isArray(checkedKeysValue)) {
+      checked = checkedKeysValue;
+      half = info?.halfCheckedKeys || [];
+    } else {
+      checked = checkedKeysValue.checked || [];
+      half = checkedKeysValue.halfChecked || [];
+    }
     setCheckedKeys(checked);
+    setHalfCheckedKeys(half);
     // 如果全部选中,设置全选状态
-    const getAllKeys = (nodes: DataNode[]): React.Key[] => {
-      let keys: React.Key[] = [];
-      nodes.forEach((node) => {
-        keys.push(node.key);
-        if (node.children) {
-          keys = keys.concat(getAllKeys(node.children));
-        }
-      });
-      return keys;
-    };
     const allKeys = getAllKeys(menuOptions);
     setTreeNodeAll(checked.length === allKeys.length && allKeys.length > 0);
   };
@@ -246,12 +276,15 @@ const RoleAssignMenuForm = forwardRef<RoleAssignMenuFormRef, RoleAssignMenuFormP
             >
               <Tree
                 checkable
-                checkedKeys={checkedKeys}
+                // checkStrictly=true 时,checkedKeys 需要传 { checked, halfChecked }
+                checkedKeys={{ checked: checkedKeys, halfChecked: halfCheckedKeys } as any}
                 expandedKeys={expandedKeys}
                 onCheck={onCheck}
                 onExpand={setExpandedKeys}
                 treeData={menuOptions}
                 defaultExpandAll={false}
+                // 父子不联动:避免“回显包含父节点ID”时把所有子节点渲染为选中
+                checkStrictly
               />
             </Card>
           </Form.Item>