Kaynağa Gözat

2.3 菜单权限完成

xyh 2 yıl önce
ebeveyn
işleme
2606bfa768

+ 19 - 10
packages/app/src/components/jurisdiction/index.tsx

@@ -1,19 +1,28 @@
-import {NO_PERMISSION_PATH} from '@routes';
+import {LOGIN_PATH, NOT_FOUND_PATH, NO_PERMISSION_PATH} from '@routes';
 import {menuStore} from '@stores';
-import {ChildrenFC} from '@utils';
-import {useEffect, useMemo} from 'react';
-import {Navigate} from 'react-router-dom';
+import {ChildrenFC, shallowEqual} from '@utils';
+import {useMemo} from 'react';
+import {Navigate, useLocation} from 'react-router-dom';
 import {useStore} from 'zustand';
 
-const Jurisdiction: ChildrenFC<{id: string}> = function({children, id}) {
-  const menus = useStore(menuStore, state => state.menus);
+const Jurisdiction: ChildrenFC = function({children}) {
+  const {pathname} = useLocation();
+  const menus = useStore(menuStore, state => state.menus, shallowEqual);
 
   const pass = useMemo(function() {
-    const res = menus.findIndex(val => val.id === id);
-    console.log(res, id);
+    const passRoutes = [LOGIN_PATH, NOT_FOUND_PATH, NO_PERMISSION_PATH];
 
-    return res >= 0;
-  }, [id, menus]);
+    // 不考虑二级路由 只匹配一级路由
+    const route = `/${pathname.split('/')[1] ?? ''}`;
+
+    /**
+     * 如果是直接通过的路由返回ture
+     * 如果是需要校验的路由判断是否在返回的路由列表中
+     */
+    if (passRoutes.includes(route)) return true;
+
+    return menus.findIndex(val => val.url === route) >= 0;
+  }, [pathname, menus]);
 
   return (
     <>

+ 1 - 1
packages/app/src/index.tsx

@@ -14,7 +14,7 @@ const queryClient = new QueryClient({
   defaultOptions: {
     queries: {
       refetchOnWindowFocus: false,
-      retry: 2,
+      retry: false,
     },
   },
 });

+ 2 - 0
packages/app/src/models/request/role.ts

@@ -22,4 +22,6 @@ export type EditRoleMenuParams = {
   id: string,
   /** 角色菜单 1,2,3,4, */
   menu: string
+  /** 菜单管理 antd用 */
+  menuBefore: string;
 };

+ 1 - 0
packages/app/src/models/response/menu.ts

@@ -17,6 +17,7 @@ export type TreeMenuListData = {
   key: string;
   title: string;
   url: string;
+  pId: string;
 };
 
 /** 权限菜单属性 */

+ 2 - 0
packages/app/src/models/response/role.ts

@@ -11,6 +11,8 @@ export type RoleListData = {
   remarks: string;
   /** 分配菜单 */
   menu: null | string;
+  /** 菜单管理 antd用 */
+  menuBefore: null | string;
   page: number;
   limit: number;
 };

+ 6 - 3
packages/app/src/pages/home/hooks.ts

@@ -7,9 +7,9 @@ import {useStore} from 'zustand';
 
 export function useMenu() {
   const setMenus = useStore(menuStore, state => state.setMenu);
-  const {menus} = useStore(
+  const menus = useStore(
     userStore,
-    state => ({menus: state.menu, uid: state.id}),
+    state => state.menu,
   );
 
   const {data} = useQuery(
@@ -33,7 +33,10 @@ export function useMenu() {
   );
 
   const parseMenus = useMemo(function() {
-    return data ? sortMenu(data) : [];
+    if (data)
+      return sortMenu(data);
+
+    return [];
   }, [data]);
 
   return parseMenus;

+ 4 - 2
packages/app/src/pages/home/index.tsx

@@ -5,7 +5,7 @@ import logo from '@assets/images/logo.png';
 import Menu from './menu';
 import User from './user';
 import {useOutlet} from 'react-router-dom';
-import {Auth, ErrorBoundary, Footer, Loading} from '@components';
+import {Auth, ErrorBoundary, Footer, Jurisdiction, Loading} from '@components';
 import {useMenu} from './hooks';
 
 const Home: FC = function() {
@@ -26,7 +26,9 @@ const Home: FC = function() {
 
         <Layout.Content>
           <Suspense>
-            {Outlet}
+            <Jurisdiction>
+              {Outlet}
+            </Jurisdiction>
           </Suspense>
           <Footer color='#666' />
         </Layout.Content>

+ 3 - 9
packages/app/src/pages/home/menu/hooks.tsx

@@ -1,6 +1,6 @@
 import {menuStore} from '@stores';
 import {ParseMenuType} from '@utils';
-import {useEffect, useState} from 'react';
+import {useState} from 'react';
 import {useNavigate} from 'react-router-dom';
 import {useStore} from 'zustand';
 
@@ -14,14 +14,6 @@ export function useOpenKey(menus: ParseMenuType[]) {
   });
   const [openKeys, setOpenKey] = useState<string[]>(current);
 
-  useEffect(function() {
-    const currentKey = current[0];
-    const data = pages.find(val => val.id === currentKey);
-
-    if (!data) return;
-    navigate(data.url);
-  }, [current, navigate, pages]);
-
   function onOpenChange(keys: string[]) {
     setOpenKey([keys[keys.length - 1]]);
   }
@@ -29,6 +21,8 @@ export function useOpenKey(menus: ParseMenuType[]) {
   function onClick(e: {key: string, keyPath: string[]}) {
     setOpenKey(e.keyPath);
     setCurrent([e.key]);
+    const data = pages.find(val => val.id === e.key)!;
+    navigate(data.url);
   }
 
   return [{openKeys, current}, {onOpenChange, onClick}] as const;

+ 4 - 1
packages/app/src/pages/home/user/index.tsx

@@ -2,15 +2,18 @@ import {Avatar, Dropdown} from 'antd';
 import css from './index.module.css';
 import {FC} from 'react';
 import {items, useLogout} from './hook';
+import {useStore} from 'zustand';
+import {userStore} from '@stores';
 
 const User: FC = function() {
   const onClick = useLogout();
+  const name = useStore(userStore, state => state.realName);
 
   return (
     <Dropdown menu={{items, onClick}}>
       <div className={css.user}>
         <Avatar src='https://cdn.novenn.com/random/avatars/1595853167656.jpg' size='large' alt='face' />
-        <span>张全祥</span>
+        <span>{name}</span>
       </div>
     </Dropdown>
   );

+ 1 - 0
packages/app/src/pages/index.ts

@@ -1,2 +1,3 @@
 export {default as Home} from './home';
 export {default as Main} from './main';
+export {default as Login} from './login';

+ 50 - 6
packages/app/src/pages/role/table/tree-modal/hooks.ts

@@ -2,10 +2,10 @@ import {useContextSection} from '@hooks';
 import {context} from './context';
 import {FormEvent, useEffect} from 'react';
 import {useMutation, useQueryClient} from '@tanstack/react-query';
-import {editRoleMenu, getRoleList} from '@apis';
+import {editRoleMenu, getRoleList, getTreeMenu} from '@apis';
 import {message} from 'antd';
-import {RoleListData} from '@models';
-import {trimEnd} from 'lodash-es';
+import {RoleListData, TreeMenuListData} from '@models';
+import {useMap} from 'ahooks';
 
 function useEditMenu(onClose: () => void, onFetch: () => void) {
   const {isLoading, mutate} = useMutation(
@@ -24,18 +24,61 @@ function useEditMenu(onClose: () => void, onFetch: () => void) {
   return [isLoading, mutate] as const;
 }
 
+function useTreeMap() {
+  const client = useQueryClient();
+  const treeData = client.getQueryData<TreeMenuListData[]>([getTreeMenu.name]);
+  const [map, {set}] = useMap<string, string>();
+
+  useEffect(
+    function() {
+      if (!treeData || treeData.length === 0) return;
+
+      function deepTree(tree: TreeMenuListData[]) {
+        tree.forEach(function({pId, key, children}) {
+          // 一级菜单不参与寻找父级id
+          pId !== '0' && set(key, pId);
+
+          children.length && deepTree(children);
+        });
+      }
+
+      deepTree(treeData);
+    },
+    [set, treeData],
+  );
+
+  return map;
+}
+
 export function useSubmit(
   {id, onClose, onFetch}:
   {id: string, onClose: () => void, onFetch: () => void},
 ) {
   const list = useContextSection(context, ([list]) => list);
   const [isLoading, mutate] = useEditMenu(onClose, onFetch);
+  const treeMap = useTreeMap();
+
+  function fixMenuTreeList(list: string) {
+    const trees = list.split(','),
+          // 方式有重复问题
+          treeSet = new Set(trees);
+
+    trees.forEach(function(val) {
+      const mapPId = treeMap.get(val);
+
+      mapPId && treeSet.add(mapPId);
+    });
+
+    return [...treeSet].join(',') + ',';
+  }
 
   function onSubmit(e: FormEvent<HTMLFormElement>) {
     e.preventDefault();
 
-    const listStr = list.join(',') + ',';
-    mutate({id, menu: listStr});
+    const listStr = list.join(',');
+    const treeMenu = fixMenuTreeList(listStr);
+
+    mutate({id, menu: treeMenu, menuBefore: listStr});
   }
 
   return [isLoading, onSubmit] as const;
@@ -52,7 +95,8 @@ export function useWatchId(id: string, visible: boolean) {
     const res = data?.find(val => String(val.id) === id);
 
     if (res) {
-      const menuList = trimEnd(res?.menu ?? '', ',').split(',');
+      const menuList = res?.menuBefore ? res.menuBefore.split(',') : [];
+
       dispatch(menuList);
     }
   }, [id, client, dispatch, visible]);

+ 1 - 6
packages/app/src/routes/routes.tsx

@@ -1,5 +1,5 @@
 import {FC, lazy} from 'react';
-import {Home, Main} from '@pages';
+import {Home, Main, Login} from '@pages';
 import {
   DEPARTMENT_PATH,
   HOME_PATH,
@@ -12,11 +12,6 @@ import {
 } from './name';
 import {RouteObject, useRoutes} from 'react-router-dom';
 
-const Login = lazy(() => import(
-  /* webpackChunkName: "login" */
-  '@pages/login'
-),
-);
 const NotFound = lazy(() => import(
   /* webpackChunkName: "notFound" */
   /* webpackPrefetch: true */

+ 4 - 0
packages/app/src/stores/user.ts

@@ -9,6 +9,7 @@ type UserStoreState = UserLoginData;
 type UserStoreAction = {
   init(data: UserLoginData): void;
   logout(): void;
+  setMenu(menus: string[]): void;
 };
 
 function defaultValue(): UserStoreState {
@@ -39,5 +40,8 @@ export const userStore = createStore<UserStoreState & UserStoreAction>(function(
         queryCache.clear();
       });
     },
+    setMenu(menus) {
+      set({menu: menus.join(',') + ','});
+    },
   };
 });

+ 0 - 1
packages/app/src/utils/sortMenu.ts

@@ -31,7 +31,6 @@ export function sortMenu(menus: TreeRoleMenuData[]) {
   // 填充一级菜单
   childMenu.forEach(function(data) {
     const index = topMenu.findIndex(val => val.key === data.pid);
-
     if (topMenu[index]?.children)
       topMenu[index]!.children!.push(data);
     else