Przeglądaj źródła

feat: add usePermTree hook

BaiLuoYan 3 dni temu
rodzic
commit
61aa3a58d3
1 zmienionych plików z 121 dodań i 0 usunięć
  1. 121 0
      src/pages/Admin/_shared/PermTree/usePermTree.ts

+ 121 - 0
src/pages/Admin/_shared/PermTree/usePermTree.ts

@@ -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,
+  };
+};