|
@@ -0,0 +1,121 @@
|
|
|
|
|
+import { useMemo, useState } from 'react';
|
|
|
|
|
+import { getPairId, groupPerms, matchesPerm, sortPerms } from './lib/permUtils';
|
|
|
|
|
+
|
|
|
|
|
+interface UsePermTreeOptions {
|
|
|
|
|
+ allPerms: API.PermItem[];
|
|
|
|
|
+ initialCheckedIds?: number[];
|
|
|
|
|
+ onChange?: (ids: number[]) => void;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export const usePermTree = ({ allPerms, initialCheckedIds = [], onChange }: UsePermTreeOptions) => {
|
|
|
|
|
+ const [keyword, setKeyword] = useState('');
|
|
|
|
|
+ const [checkedIds, setCheckedIds] = useState<Set<number>>(new Set(initialCheckedIds));
|
|
|
|
|
+
|
|
|
|
|
+ const sorted = useMemo(() => sortPerms(allPerms), [allPerms]);
|
|
|
|
|
+ const grouped = useMemo(() => groupPerms(sorted), [sorted]);
|
|
|
|
|
+
|
|
|
|
|
+ const filteredGroups = useMemo(
|
|
|
|
|
+ () =>
|
|
|
|
|
+ grouped
|
|
|
|
|
+ .map((g) => ({
|
|
|
|
|
+ ...g,
|
|
|
|
|
+ apiPerms: g.apiPerms.filter((p) => matchesPerm(p, keyword)),
|
|
|
|
|
+ dataPerms: g.dataPerms.filter((p) => matchesPerm(p, keyword)),
|
|
|
|
|
+ fieldPerms: g.fieldPerms.filter((p) => matchesPerm(p, keyword)),
|
|
|
|
|
+ }))
|
|
|
|
|
+ .filter((g) => g.apiPerms.length + g.dataPerms.length + g.fieldPerms.length > 0),
|
|
|
|
|
+ [grouped, keyword],
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const commit = (next: Set<number>) => {
|
|
|
|
|
+ setCheckedIds(next);
|
|
|
|
|
+ onChange?.([...next]);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const toggle = (perm: API.PermItem) => {
|
|
|
|
|
+ const next = new Set(checkedIds);
|
|
|
|
|
+ if (next.has(perm.id)) {
|
|
|
|
|
+ // 取消 api → 联动取消对应 data
|
|
|
|
|
+ next.delete(perm.id);
|
|
|
|
|
+ if (perm.code.startsWith('api:')) {
|
|
|
|
|
+ const pairId = getPairId(perm, allPerms);
|
|
|
|
|
+ if (pairId) next.delete(pairId);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 勾选 data → 联动勾选对应 api
|
|
|
|
|
+ next.add(perm.id);
|
|
|
|
|
+ if (perm.code.startsWith('data:') && perm.code.split(':').length === 3) {
|
|
|
|
|
+ const pairId = getPairId(perm, allPerms);
|
|
|
|
|
+ if (pairId) next.add(pairId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ commit(next);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 当前 Tab 可见权限(api tab 或 data tab)
|
|
|
|
|
+ const getTabPerms = (tab: 'api' | 'data') =>
|
|
|
|
|
+ filteredGroups.flatMap((g) => (tab === 'api' ? g.apiPerms : [...g.dataPerms, ...g.fieldPerms]));
|
|
|
|
|
+
|
|
|
|
|
+ const selectAll = (tab: 'api' | 'data') => {
|
|
|
|
|
+ const visible = getTabPerms(tab);
|
|
|
|
|
+ const allChecked = visible.every((p) => checkedIds.has(p.id));
|
|
|
|
|
+ const next = new Set(checkedIds);
|
|
|
|
|
+ if (allChecked) {
|
|
|
|
|
+ visible.forEach((p) => next.delete(p.id));
|
|
|
|
|
+ } else {
|
|
|
|
|
+ visible.forEach((p) => {
|
|
|
|
|
+ next.add(p.id);
|
|
|
|
|
+ // 勾选 data 联动 api
|
|
|
|
|
+ if (p.code.startsWith('data:') && p.code.split(':').length === 3) {
|
|
|
|
|
+ const pairId = getPairId(p, allPerms);
|
|
|
|
|
+ if (pairId) next.add(pairId);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ commit(next);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const invert = (tab: 'api' | 'data') => {
|
|
|
|
|
+ const visible = getTabPerms(tab);
|
|
|
|
|
+ const next = new Set(checkedIds);
|
|
|
|
|
+ visible.forEach((p) => {
|
|
|
|
|
+ if (next.has(p.id)) {
|
|
|
|
|
+ next.delete(p.id);
|
|
|
|
|
+ if (p.code.startsWith('api:')) {
|
|
|
|
|
+ const pairId = getPairId(p, allPerms);
|
|
|
|
|
+ if (pairId) next.delete(pairId);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ next.add(p.id);
|
|
|
|
|
+ if (p.code.startsWith('data:') && p.code.split(':').length === 3) {
|
|
|
|
|
+ const pairId = getPairId(p, allPerms);
|
|
|
|
|
+ if (pairId) next.add(pairId);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ commit(next);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const isAllSelected = (tab: 'api' | 'data') => {
|
|
|
|
|
+ const visible = getTabPerms(tab);
|
|
|
|
|
+ return visible.length > 0 && visible.every((p) => checkedIds.has(p.id));
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const isIndeterminate = (tab: 'api' | 'data') => {
|
|
|
|
|
+ const visible = getTabPerms(tab);
|
|
|
|
|
+ const count = visible.filter((p) => checkedIds.has(p.id)).length;
|
|
|
|
|
+ return count > 0 && count < visible.length;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ keyword,
|
|
|
|
|
+ setKeyword,
|
|
|
|
|
+ checkedIds,
|
|
|
|
|
+ filteredGroups,
|
|
|
|
|
+ toggle,
|
|
|
|
|
+ selectAll,
|
|
|
|
|
+ invert,
|
|
|
|
|
+ isAllSelected,
|
|
|
|
|
+ isIndeterminate,
|
|
|
|
|
+ };
|
|
|
|
|
+};
|