react + umi + antd pro动态获取菜单

997 阅读2分钟

react + umi + antd pro动态获取菜单

前言:

当用户输入账号密码后登录通过不同的账号获取不同的菜单

正文:

1. 添加获取菜单请求

export async function fetchMenuData(){
    return request<Models.FRestResult>('/api/xxx/xxx/xxx');
}

2. 修改app.tsx

1) 修改getInitialState方法

/**
 * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
 * */
export async function getInitialState(): Promise<{
  settings?: Partial<LayoutSettings>;
  currentUser?: Models.CurrentUser;
  loading?: boolean;
  fetchUserInfo?: () => Promise<Models.CurrentUser | undefined>;
  menuRouteInfo?: () => Promise<Models.FRestResult | undefined>;
  menuList?: Models.FRestResult;
}> {
  // 获取个人信息
  const fetchUserInfo = async () => {
    try {
      const msg = await queryCurrentUser();
      return msg.dataEntry;
    } catch (error) {
      history.push(loginPath);
    }
    return undefined;
  };
  // 获取菜单
  const menuRouteInfo = async () => {
    try {
      const msg = await fetchMenuData();
      return msg;
    } catch (error) {
      history.push(loginPath);
    }
    return undefined;
  };
  // 如果不是登录页面,执行
  if (history.location.pathname !== loginPath) {
    const currentUser = await fetchUserInfo();
    const menuList = await menuRouteInfo();
    return {
      fetchUserInfo,
      currentUser,
      settings: defaultSettings,
      menuRouteInfo,
      menuList,
    };
  }
  return {
    fetchUserInfo,
    menuRouteInfo,
    settings: defaultSettings,
  };
}

2) 修改layout方法

// ProLayout 支持的api https://procomponents.ant.design/components/layout
export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }: any) => {
  return {
    // 遍历菜单
    menuDataRender: () => loopMenuItem(initialState?.menuList?.data),
    rightContentRender: () => <RightContent />,
    disableContentMargin: false,
    footerRender: () => <Footer />,
    onPageChange: () => {
      const { location } = history;
      // 如果没有登录,重定向到 login
      if (!initialState?.currentUser && location.pathname !== loginPath) {
        history.push(loginPath);
      }
    },
    // links: isDev
    //   ? [
    //       <Link key="openapi" to="/umi/plugin/openapi" target="_blank">
    //         <LinkOutlined />
    //         <span>OpenAPI 文档</span>
    //       </Link>,
    //       <Link to="/~docs" key="docs">
    //         <BookOutlined />
    //         <span>业务组件文档</span>
    //       </Link>,
    //     ]
    //   : [],
    menuHeaderRender: undefined,
    // 自定义 403 页面
    // unAccessible: <div>unAccessible</div>,
    // 增加一个 loading 的状态
    childrenRender: (children: any, props: any) => {
      return (
        <>
          {children}
        </>
      );
    },
    ...initialState?.settings,
  };
};

3) 添加loopMenuItem方法

// 默认路由
const loginPath = '/user/login';
// 菜单的展示,我这里的icon是按需引入
const IconMap = {
  smile: <SmileOutlined />,
  heart: <HeartOutlined />,
};
const loopMenuItem = (menus: any[]): MenuDataItem[] => {
  if (menus) {
    return menus.map(({ icon, routes, ...item }) => ({
      ...item,
      icon: icon && IconMap[icon as string],
      // children: routes && loopMenuItem(routes),
      routes: loopMenuItem(routes),
    }));
  }
  return [];
}

3. 登录模块

user/login

const [userLoginState, setUserLoginState] = useState<API.LoginResult>({});
  const [type, setType] = useState<string>('account');
  const { initialState, setInitialState } = useModel('@@initialState');

  const intl = useIntl();
  // 调用getInitialState内的获取个人信息
  const fetchUserInfo = async () => {
    const userInfo = await initialState?.fetchUserInfo?.();
    if (userInfo) {
      await setInitialState((s: any) => ({
        ...s,
        currentUser: userInfo,
      }));
    }
  };
  // 调用getInitialState内的菜单方法
  const menuRouteInfo = async () => {
    const menuInfo = await initialState?.menuRouteInfo?.();
    if (menuInfo) {
      await setInitialState((s: any) => ({
        ...s,
        menuList: menuInfo,
      }));
    }
  };

  const handleSubmit = async (values: API.LoginParams) => {
    try {
      // 登录
      const msg = await userLogin({ ...values, type });
      if (msg.status === 'ok') {
        const defaultLoginSuccessMessage = intl.formatMessage({
          id: 'pages.login.success',
          defaultMessage: '登录成功!',
        });
        if(msg.token!=''||msg.token!=null||msg.token!=undefined){
          await localStorage.setItem('token',msg.token);
        }else{
          message.error("未获取到Token!");
          return;
        }
        message.success(defaultLoginSuccessMessage);
        await fetchUserInfo();
        await menuRouteInfo();
        /** 此方法会跳转到 redirect 参数所在的位置 */
        if (!history) return;
        const { query } = history.location;
        const { redirect } = query as { redirect: string };
        history.push(redirect || '/');
        return;
      }
      console.log(msg);
      // 如果失败去设置用户错误信息
      setUserLoginState(msg);
      message.error(msg.message);
    } catch (error) {
      const defaultLoginFailureMessage = intl.formatMessage({
        id: 'pages.login.failure',
        defaultMessage: '登录失败,请重试!',
      });
      message.error(defaultLoginFailureMessage);
    }
  };

4. 路由文件routes.tsx

路由文件内容最好要和获取菜单内容的对应。

但还是有bug:当menu没有而routes内有时,在浏览器中修改url时也会跳转,两种方式