|
@@ -1,4 +1,4 @@
|
|
|
-import { useCallback, useMemo, useRef, useState } from 'react';
|
|
|
|
|
|
|
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
import { buildPairMap, getPairId, groupPerms, matchesPerm, sortPerms } from '../lib/permUtils';
|
|
import { buildPairMap, getPairId, groupPerms, matchesPerm, sortPerms } from '../lib/permUtils';
|
|
|
|
|
|
|
|
interface UsePermTreeOptions {
|
|
interface UsePermTreeOptions {
|
|
@@ -13,6 +13,11 @@ export const usePermTree = ({ allPerms, initialCheckedIds = [], onChange }: UseP
|
|
|
const onChangeRef = useRef(onChange);
|
|
const onChangeRef = useRef(onChange);
|
|
|
onChangeRef.current = onChange;
|
|
onChangeRef.current = onChange;
|
|
|
|
|
|
|
|
|
|
+ const checkedKey = initialCheckedIds.join(',');
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ setCheckedIds(new Set(initialCheckedIds));
|
|
|
|
|
+ }, [checkedKey]);
|
|
|
|
|
+
|
|
|
const sorted = useMemo(() => sortPerms(allPerms), [allPerms]);
|
|
const sorted = useMemo(() => sortPerms(allPerms), [allPerms]);
|
|
|
const grouped = useMemo(() => groupPerms(sorted), [sorted]);
|
|
const grouped = useMemo(() => groupPerms(sorted), [sorted]);
|
|
|
const pairMap = useMemo(() => buildPairMap(allPerms), [allPerms]);
|
|
const pairMap = useMemo(() => buildPairMap(allPerms), [allPerms]);
|
|
@@ -54,80 +59,85 @@ export const usePermTree = ({ allPerms, initialCheckedIds = [], onChange }: UseP
|
|
|
[pairMap],
|
|
[pairMap],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- const getTabPerms = useCallback(
|
|
|
|
|
- (tab: 'api' | 'data') =>
|
|
|
|
|
- filteredGroups.flatMap((g) =>
|
|
|
|
|
- tab === 'api' ? g.apiPerms : [...g.dataPerms, ...g.fieldPerms],
|
|
|
|
|
- ),
|
|
|
|
|
- [filteredGroups],
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ const selectAll = useCallback(() => {
|
|
|
|
|
+ setCheckedIds((prev) => {
|
|
|
|
|
+ const allChecked = sorted.every((p) => prev.has(p.id));
|
|
|
|
|
+ const next = new Set(prev);
|
|
|
|
|
+ if (allChecked) {
|
|
|
|
|
+ sorted.forEach((p) => next.delete(p.id));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ sorted.forEach((p) => next.add(p.id));
|
|
|
|
|
+ }
|
|
|
|
|
+ onChangeRef.current?.([...next]);
|
|
|
|
|
+ return next;
|
|
|
|
|
+ });
|
|
|
|
|
+ }, [sorted]);
|
|
|
|
|
|
|
|
- const selectAll = useCallback(
|
|
|
|
|
|
|
+ const invert = useCallback(
|
|
|
(tab: 'api' | 'data') => {
|
|
(tab: 'api' | 'data') => {
|
|
|
- const visible = getTabPerms(tab);
|
|
|
|
|
setCheckedIds((prev) => {
|
|
setCheckedIds((prev) => {
|
|
|
- const allChecked = visible.every((p) => prev.has(p.id));
|
|
|
|
|
- const next = new Set(prev);
|
|
|
|
|
- if (allChecked) {
|
|
|
|
|
- visible.forEach((p) => next.delete(p.id));
|
|
|
|
|
- } else {
|
|
|
|
|
- visible.forEach((p) => {
|
|
|
|
|
- next.add(p.id);
|
|
|
|
|
- if (p.code.startsWith('data:') && p.code.split(':').length === 3) {
|
|
|
|
|
|
|
+ const next = new Set(sorted.map((p) => p.id).filter((id) => !prev.has(id)));
|
|
|
|
|
+ if (tab === 'data') {
|
|
|
|
|
+ // 补选所有选中的 data:model:action 对应的 api 配对
|
|
|
|
|
+ sorted.forEach((p) => {
|
|
|
|
|
+ if (next.has(p.id) && p.code.startsWith('data:') && p.code.split(':').length === 3) {
|
|
|
const pairId = getPairId(p, pairMap);
|
|
const pairId = getPairId(p, pairMap);
|
|
|
if (pairId) next.add(pairId);
|
|
if (pairId) next.add(pairId);
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 取消所有未选中的 api:model:action 对应的 data 配对
|
|
|
|
|
+ sorted.forEach((p) => {
|
|
|
|
|
+ if (!next.has(p.id) && p.code.startsWith('api:')) {
|
|
|
|
|
+ const pairId = getPairId(p, pairMap);
|
|
|
|
|
+ if (pairId) next.delete(pairId);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
}
|
|
}
|
|
|
onChangeRef.current?.([...next]);
|
|
onChangeRef.current?.([...next]);
|
|
|
return next;
|
|
return next;
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
- [getTabPerms, pairMap],
|
|
|
|
|
|
|
+ [sorted, pairMap],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
- const invert = useCallback(
|
|
|
|
|
- (tab: 'api' | 'data') => {
|
|
|
|
|
- const visible = getTabPerms(tab);
|
|
|
|
|
|
|
+ const isAllSelected = useMemo(
|
|
|
|
|
+ () => sorted.length > 0 && sorted.every((p) => checkedIds.has(p.id)),
|
|
|
|
|
+ [sorted, checkedIds],
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const isIndeterminate = useMemo(() => {
|
|
|
|
|
+ const count = sorted.filter((p) => checkedIds.has(p.id)).length;
|
|
|
|
|
+ return count > 0 && count < sorted.length;
|
|
|
|
|
+ }, [sorted, checkedIds]);
|
|
|
|
|
+
|
|
|
|
|
+ const toggleGroup = useCallback(
|
|
|
|
|
+ (perms: API.PermItem[]) => {
|
|
|
setCheckedIds((prev) => {
|
|
setCheckedIds((prev) => {
|
|
|
|
|
+ const allChecked = perms.every((p) => prev.has(p.id));
|
|
|
const next = new Set(prev);
|
|
const next = new Set(prev);
|
|
|
- visible.forEach((p) => {
|
|
|
|
|
- if (next.has(p.id)) {
|
|
|
|
|
|
|
+ if (allChecked) {
|
|
|
|
|
+ perms.forEach((p) => {
|
|
|
next.delete(p.id);
|
|
next.delete(p.id);
|
|
|
if (p.code.startsWith('api:')) {
|
|
if (p.code.startsWith('api:')) {
|
|
|
const pairId = getPairId(p, pairMap);
|
|
const pairId = getPairId(p, pairMap);
|
|
|
if (pairId) next.delete(pairId);
|
|
if (pairId) next.delete(pairId);
|
|
|
}
|
|
}
|
|
|
- } else {
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ } else {
|
|
|
|
|
+ perms.forEach((p) => {
|
|
|
next.add(p.id);
|
|
next.add(p.id);
|
|
|
if (p.code.startsWith('data:') && p.code.split(':').length === 3) {
|
|
if (p.code.startsWith('data:') && p.code.split(':').length === 3) {
|
|
|
const pairId = getPairId(p, pairMap);
|
|
const pairId = getPairId(p, pairMap);
|
|
|
if (pairId) next.add(pairId);
|
|
if (pairId) next.add(pairId);
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
onChangeRef.current?.([...next]);
|
|
onChangeRef.current?.([...next]);
|
|
|
return next;
|
|
return next;
|
|
|
});
|
|
});
|
|
|
},
|
|
},
|
|
|
- [getTabPerms, pairMap],
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const isAllSelected = useCallback(
|
|
|
|
|
- (tab: 'api' | 'data') => {
|
|
|
|
|
- const visible = getTabPerms(tab);
|
|
|
|
|
- return visible.length > 0 && visible.every((p) => checkedIds.has(p.id));
|
|
|
|
|
- },
|
|
|
|
|
- [getTabPerms, checkedIds],
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- const isIndeterminate = useCallback(
|
|
|
|
|
- (tab: 'api' | 'data') => {
|
|
|
|
|
- const visible = getTabPerms(tab);
|
|
|
|
|
- const count = visible.filter((p) => checkedIds.has(p.id)).length;
|
|
|
|
|
- return count > 0 && count < visible.length;
|
|
|
|
|
- },
|
|
|
|
|
- [getTabPerms, checkedIds],
|
|
|
|
|
|
|
+ [pairMap],
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
@@ -137,6 +147,7 @@ export const usePermTree = ({ allPerms, initialCheckedIds = [], onChange }: UseP
|
|
|
grouped,
|
|
grouped,
|
|
|
filteredGroups,
|
|
filteredGroups,
|
|
|
toggle,
|
|
toggle,
|
|
|
|
|
+ toggleGroup,
|
|
|
selectAll,
|
|
selectAll,
|
|
|
invert,
|
|
invert,
|
|
|
isAllSelected,
|
|
isAllSelected,
|