|
|
@@ -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>
|