SoFunction
Updated on 2025-04-07

React's most popular ecological alternative to antidpro builds lightweight backend management

Preface

Have you ever experienced the company's products and ui requirements that the menu bar on the left should be changed to the design drawing? Trouble The strongly bound pro-layout menu bar of antd-pro cannot be customized? You can use umi, but you have to develop according to its agreement, bundled with family buckets, etc. I will teach you step by step to build a lightweight background template, including routing permissions, dynamic menus, etc.

To facilitate the use of the antd component library, you can change it to whatever you like. Management and use of data requestsreact-query,similaruseRequest, but it will be even bigger. Style usagetailwindcssaddstyled-components, because antd v5 will use css in js. Route permissions and menu management usagereact-router-auth-plus。。。

Warehouse address

Project Initialization

vite

# npm 7+
npm create vite spirit-admin -- --template react-ts

antd

tailwindcss

styled-components

react-query

axios

react-router

react-router-auth-plus (Permission routing, dynamic menu solution)Warehouse address Article address

etc...

Data request + mock

Configure axios

Set up the interceptor and introduce this file in the entry file to make it effective globally

// src/
import axios, { AxiosError } from "axios";
import { history } from "./main";
// Set the response interceptor, the status code is 401 to clear the token, and return to the login page.(
  function (response) {
    return response;
  },
  function (error: AxiosError) {
    if (?.status === 401) {
      ("token");
      // Use the routing method outside the react component, and the usage method will be mentioned later when routing configuration      ("/login");
    }
    return (error);
  }
);
// Set the request interceptor, bring the tokens in the request(function (request) {
   = {
    authorization: ("token") || "",
  };
  return request;
});

Configure react-query

In the App outer layer wrapping QueryClientProvider, set default options, no re-requests are requested when the window is refocused and fails.

// 
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: false,
      retry: false,
    },
  },
});
(("root") as HTMLElement).render(
  <>
    <QueryClientProvider client={queryClient}>
        <App />
    </QueryClientProvider>
  </>
);

We only have two requests, log in and get the current user, create a new hooks folder under src, and then create a query and mutation folder respectively. query is used to request data, and mutation is used to initiate data operations. You can see for detailsreact-query documentation

Get the current user interface

// src/hooks/query/
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { queryClient } from "../../main";
// useQuery requires a unique key, react-query v4 is the array formatconst currentUserQueryKey = ["currentUser"];
// Query the current user. If there is no token in localStorage, no requestexport const useCurrentUserQuery = () =&gt;
  useQuery(currentUserQueryKey, () =&gt; ("/api/me"), {
    enabled: !!("token"),
  });
// You can get the data of useCurrentUserQuery on other pagesexport const getCurrentUser = () =&gt; {
  const data: any = (currentUserQueryKey);
  return {
    username: data?.,
  };
};

Login interface

// src/hooks/mutation/
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
export const useLoginMutation = () =>
  useMutation((data) => ("/api/login", data));

mock

Data requests use react-query + axios, because there are only two requests,/login(Login) and/me(Current user), use express local mock to directly use the data. Create a new mock folder and create it separatelyand

// Store two types of usersexport const users = [
  { username: "admin", password: "admin" },
  { username: "employee", password: "employee" },
];
// Main fileimport express from "express";
import { users } from "./";
const app = express();
const port = 3000;
const router = ();
// Log in to the interface. If you successfully return token, there are only two cases of simulated tokens.("/login", (req, res) =&gt; {
  setTimeout(() =&gt; {
    const username = ;
    const password = ;
    const user = ((user) =&gt;  === username);
    if (user &amp;&amp; password === ) {
      (200).json({
        code: 0,
        token:  === "admin" ? "admin-token" : "employee-token",
      });
    } else {
      (200).json({ code: -1, message: "Error in username or password" });
    }
  }, 2000);
});
// For the current user interface, you need to bring authorization in the headers when requesting, and if it is incorrect, return a 401 status code.  Return permissions and username according to user type("/me", (req, res) =&gt; {
  setTimeout(() =&gt; {
    const token = ;
    if (!["admin-token", "employee-token"].includes(token)) {
      (401).json({ code: -1, message: "Please log in" });
    } else {
      const auth = token === "admin-token" ? ["application", "setting"] : [];
      const username = token === "admin-token" ? "admin" : "employee";
      (200).json({ code: 0, data: { auth, username } });
    }
  }, 2000);
});
(());
// Add /api to the interface prefix uniformly("/api", router);
// Disable 304 cache("etag");
(port, () =&gt; {
  (`Example app listening on port ${port}`);
});

existIn-housescriptsAdd a mock command, and you need to install nodemon to hot update the mock file.npm run mockStart the express service.

"scripts": {
  ...
  "mock": "nodemon mock/"
}

It is not available in the project yet, so you need to configure the proxy proxy in vite

// 
export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changeOrigin: true,
      },
    },
  },
});

Routing permission configuration

The route and permissions are used inreact-router-auth-plus, see the detailsPrevious article

Routing files

Create a new one, introduce the page file, configure all routes used by the project, and configure permissions. Let's expand hereAuthRouterObjectType, customize some parameters, such as icon, name, etc. of the menu on the left. Settings/account/centerand/applicationThe routing requires corresponding permissions.

import {
  AppstoreOutlined,
  HomeOutlined,
  UserOutlined,
} from "@ant-design/icons";
import React from "react";
import { AuthRouterObject } from "react-router-auth-plus";
import { Navigate } from "react-router-dom";
import BasicLayout from "./layouts/BasicLayout";
import Application from "./pages/application";
import Home from "./pages/home";
import Login from "./pages/login";
import NotFound from "./pages/404";
import Setting from "./pages/account/setting";
import Center from "./pages/account/center";
export interface MetaRouterObject extends AuthRouterObject {
  name?: string;
  icon?: ;
  hideInMenu?: boolean;
  hideChildrenInMenu?: boolean;
  children?: MetaRouterObject[];
}
// Just configure auth on the route that requires permissionsexport const routers: MetaRouterObject[] = [
  { path: "/", element: &lt;Navigate to="/home" replace /&gt; },
  { path: "/login", element: &lt;Login /&gt; },
  {
    element: &lt;BasicLayout /&gt;,
    children: [
      {
        path: "/home",
        element: &lt;Home /&gt;,
        name: "Homepage",
        icon: &lt;HomeOutlined /&gt;,
      },
      {
        path: "/account",
        name: "personal",
        icon: &lt;UserOutlined /&gt;,
        children: [
          {
            path: "/account",
            element: &lt;Navigate to="/account/center" replace /&gt;,
          },
          {
            path: "/account/center",
            name: "Personal Center",
            element: &lt;Center /&gt;,
          },
          {
            path: "/account/setting",
            name: "Personal Settings",
            element: &lt;Setting /&gt;,
            // Permissions            auth: ["setting"],
          },
        ],
      },
      {
        path: "/application",
        element: &lt;Application /&gt;,
        // Permissions        auth: ["application"],
        name: "application",
        icon: &lt;AppstoreOutlined /&gt;,
      },
    ],
  },
  { path: "*", element: &lt;NotFound /&gt; },
];

Using HistoryRouter, you can route jumps outside the component, so that history jump routes can be introduced in the axios interceptor.

import { createBrowserHistory } from "history";
import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom";
export const history = createBrowserHistory({ window });
(("root") as HTMLElement).render(
  <>
    <QueryClientProvider client={queryClient}>
      <HistoryRouter history={history}>
        <App />
      </HistoryRouter>
    </QueryClientProvider>
  </>
);

import { useAuthRouters } from "react-router-auth-plus";
import { routers } from "./router";
import NotAuth from "./pages/403";
import { Spin } from "antd";
import { useEffect, useLayoutEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { useCurrentUserQuery } from "./hooks/query";
function App() {
  const navigate = useNavigate();
  const location = useLocation();
  // Get the current user and do not request it when there is no token in localStorage  const { data, isFetching } = useCurrentUserQuery();
  // The first time I entered the program, it was not a login page and there was no token, jump to the login page  useEffect(() =&gt; {
    if (!("token") &amp;&amp;  !== "/login") {
      navigate("/login");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);
  // If you enter the program for the first time, if it is a login page and the token has not expired (code is 0), you will automatically log in to the home page.  Use useLayoutEffect to avoid seeing the login page flash first and then jump to the home page.  useLayoutEffect(() =&gt; {
    if ( === "/login" &amp;&amp; data?. === 0) {
      navigate("/home");
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [data?.]);
  return useAuthRouters({
    // Pass in the current user's permissions    auth: data?. || [],
    // If you are getting the current user, display loading    render: (element) =&gt;
      isFetching ? (
        &lt;div className="flex justify-center items-center h-full"&gt;
          &lt;Spin size="large" /&gt;
        &lt;/div&gt;
      ) : (
        element
      ),
    // If you enter a page without permission, page 403 will be displayed    noAuthElement: () =&gt; &lt;NotAuth /&gt;,
    routers,
  });
}
export default App;

Page writing

login page

html omitted, antd Form form account password input box and a login button

// src/pages/login/
const Login: FC = () =&gt; {
  const navigate = useNavigate();
  const { mutateAsync: login, isLoading } = useLoginMutation();
  // Form Submit  const handleFinish = async (values: any) =&gt; {
    const { data } = await login(values);
    if ( === 0) {
      ("token", );
      // Request the current user      await (currentUserQueryKey);
      navigate("/home")
      ("Login successfully");
    } else {
      ();
    }
  };
  return ...
};

BasicLayout

BasicLayout Here is abbreviation, you can see the source code for details. BasicLayout will receive routers,The configured children will be automatically passed in routers, and there is no need to pass them manually like this<BasicLayout routers={[]} />OutletEquivalent tochildren,yesreact-router v6New.

Pass routers inOutletin the context. Then it can be used on the pageuseOutletContextGet routers.

// src/layouts
import { Layout } from "antd";
import { Outlet } from "react-router-dom";
import styled from "styled-components";
// Overwrite styles with styled-componentsconst Header = styled()`
  height: 48px;
  line-height: 48px;
  padding: 0 16px;
`;
// Same as aboveconst Slider = styled()`
  .ant-layout-sider-children {
    display: flex;
    flex-direction: column;
  }
`;
interface BasicLayoutProps {
  routers?: MetaRouterObject[];
}
const BasicLayout: FC&lt;BasicLayoutProps&gt; = ({ routers }) =&gt; {
  // Abbreviation of style omitted  return (
    &lt;Layout&gt;
      &lt;Header&gt;
        ...top
      &lt;/Header&gt;
      &lt;Layout hasSider&gt;
        &lt;Slider&gt;
          ...Left menu
        &lt;/Slider&gt;
        &lt;Layout&gt;
          &lt;&gt;
            &lt;Outlet context={{ routers }} /&gt;
          &lt;/&gt;
        &lt;/Layout&gt;
      &lt;/Layout&gt;
    &lt;/Layout&gt;
  );
};

Dynamic menu bar

Split the left menu bar into a component, introduce it in BasicLayout, and pass the routers parameter.

// src/layouts/BasicLayout/components/
import { Menu } from "antd";
import { FC, useEffect, useState } from "react";
import { useAuthMenus } from "react-router-auth-plus";
import { useNavigate } from "react-router-dom";
import { useLocation } from "react-router-dom";
import { MetaRouterObject } from "../../../router";
import { ItemType } from "antd/lib/menu/hooks/useItems";
// Convert to the format required by the antd Menu component.  Only those that are configured with name and not hidden will be displayedconst getMenuItems = (routers: MetaRouterObject[]): ItemType[] =&gt; {
  const menuItems = ((total: ItemType[], router) =&gt; {
    if ( &amp;&amp; !) {
      total?.push({
        key:  as string,
        icon: ,
        label: ,
        children:
           &amp;&amp;
           &gt; 0 &amp;&amp;
          !
            ? getMenuItems()
            : undefined,
      });
    }
    return total;
  }, []);
  return menuItems;
};
interface SlideMenuProps {
  routers: MetaRouterObject[];
}
const SlideMenu: FC&lt;SlideMenuProps&gt; = ({ routers }) =&gt; {
  const location = useLocation();
  const navigate = useNavigate();
  const [selectedKeys, setSelectedKeys] = useState&lt;string[]&gt;([]);
  // useAuthMenus filter out routes without permissions first.  Then use getMenuItems to obtain the format required by the antd Menu component  const menuItems = getMenuItems(useAuthMenus(routers));
  // The drop-down menu that opens by default  const defaultOpenKey = ((i) =&gt;
    (i?.key as string)
  )?.key as string;
  // Select menu  useEffect(() =&gt; {
    setSelectedKeys([]);
  }, []);
  return (
    &lt;Menu
      style={{ borderRightColor: "white" }}
      className="h-full"
      mode="inline"
      selectedKeys={selectedKeys}
      defaultOpenKeys={defaultOpenKey ? [defaultOpenKey] : []}
      items={menuItems}
      {/* Select the menu callback and navigate to its route */}
      onSelect={({ key }) =&gt; navigate(key)}
    /&gt;
  );
};
export default SlideMenu;

Package page universal bread crumbs

Encapsulate a bread crumb that is universally common under BasicLayout.

// src/components/
import { Breadcrumb } from "antd";
import { FC } from "react";
import {
  Link,
  matchRoutes,
  useLocation,
  useOutletContext,
} from "react-router-dom";
import { MetaRouterObject } from "../router";
const PageBreadcrumb: FC = () =&gt; {
  const location = useLocation();
  // Get the routers passed in BasicLayout  const { routers } = useOutletContext&lt;{ routers: MetaRouterObject[] }&gt;();
  // Use react-router's matchRoutes method to match the route array  const match = matchRoutes(routers, location);
  // Process it to generate a breadcrumb array  const breadcrumbs =
    (match || []).reduce((total: MetaRouterObject[], current) =&gt; {
      if (( as MetaRouterObject).name) {
        ();
      }
      return total;
    }, []);
  // The last bread crumb cannot be clicked, the previous one can be clicked and redirected  return (
    &lt;Breadcrumb&gt;
      {((i, index) =&gt; (
        &lt; key={}&gt;
          {index ===  - 1 ? (
            
          ) : (
            &lt;Link to={ as string}&gt;{}&lt;/Link&gt;
          )}
        &lt;/&gt;
      ))}
    &lt;/Breadcrumb&gt;
  );
};
export default PageBreadcrumb;

This will introduce this component to the page and use it. If you want to use it in each page, you can write it in the BasicLayout Content and add one in the routers configuration.hideBreadcrumbOptions to control whether to display breadcrumbs on the current routing page through configuration.

function Home() {
  return (
    <div>
      <PageBreadcrumb />
    </div>
  );
}

Summarize

The ecology of react is becoming more and more diverse, and we learn more and more (too much). In general, some of the libraries used above need to be more or less understood. Everyone should exercise their ability to build a simple version of the background management template.github address

The above is the detailed content of react's most popular ecological replacement for antidpro to build lightweight backend management. For more information about react's ecological lightweight backend management, please follow my other related articles!