YYGod0120
交通局Categories: Project     2023-10-24

丰都县交通局

记得下次好好评审项目以及拉黑胡*

项目地址市民端:账 123456 密 123456

项目概述

一个基本的后台管理,但是内容极其恶心,既要打车又要货拉拉还要 12306 买票

最恶心的就是没有正规的 12306,都是自己爬的接口用。不会被抓吧

技术栈

  1. React + Vite + arcodesign
  2. tailwind
  3. recoil
  4. moment

属于是只用上了基本的东西

负责模块

  1. 布局以及侧边栏
  2. 信息栏的用户端以及管理端
  3. 权限管理端

侧边栏

这个项目采用的是文件路由的方式,类似于 Next.js 的文件路由 所以通过将文件也就是路由扁平化,递归判断是否为父文件还是子文件来进行侧边栏渲染 通过权限判断一些选项是否渲染,从而达到隐藏的作用

1//文件路由实现
2const layout: Record<string, FC<any> | null> = {
3  default: (props) => (
4    <Layout>
5      <>{props.children}</>
6    </Layout>
7  ),
8  login: (props) => <>{props.children}</>,
9  systemManage: (props) => (
10    <Layout>
11      <SystemLayout>{props.children}</SystemLayout>
12    </Layout>
13  ),
14  companyManage: (props) => (
15    <Layout>
16      <CompanyLayout>{props.children}</CompanyLayout>
17    </Layout>
18  ),
19};
20layout;
21const routeModule: Record<number, () => JSX.Element> = import.meta.glob(
22  "./routes/**/*.tsx",
23  {
24    eager: true,
25    import: "default",
26  },
27);
28const modules = Object.entries(routeModule);
29const pathRegExp = /\.\/routes(.*).tsx/;
30const defaultRoutes: RouteObject[] = modules
31  .filter(([path]) => !path.includes("children"))
32  .map((v) => {
33    const [path, Element] = v;
34    const pathLike = path.replace(pathRegExp, "$1");
35    let routePath = /\/index/.test(pathLike)
36      ? pathLike.replace(/\/index/, "")
37      : pathLike;
38    if (/\[\w+\]/.test(pathLike)) {
39      const slug = pathLike.replace(/.*\[(\w+)\]/, "$1");
40      routePath = pathLike.replace(/\[\w+\]/, `:${slug}`);
41    }
42    // console.log(path.split("/").filter(Boolean));
43    const [, , login, key] = path.split("/").filter(Boolean);
44    let Layout = layout[key];
45    Layout ??= layout["default"];
46    // console.log(login);
47    if (login === "login") {
48      Layout = layout["login"];
49    }
50    const route: RouteObject = {
51      path: routePath,
52      element: Layout ? (
53        <Layout>
54          <Element />
55        </Layout>
56      ) : (
57        <Element />
58      ),
59      errorElement: <NotFoundPage />,
60    };
61    return route;
62  });
63const childrenRoutes = createChildren(routeModule);
64const { children = [] } = childrenToArray(childrenRoutes);
65defaultRoutes.forEach((v1) => {
66  children.forEach((v2) => {
67    if (v1.path?.endsWith(v2.path!)) v1.children = v2.children;
68  });
69});
70export const routes: RouteObject[] = [...defaultRoutes];
71export const router = createHashRouter(routes);
72
1///侧边栏渲染
2export default function Side() {
3  const [selectKey, setSelectKey] = useState<string[]>([])
4  function renderMenu(
5    routes: IRoutes[],
6    menu: MenuType,
7    routerFather: string,
8    isFirstSubmenu: boolean,
9  ) {
10    return routes.map((route) => {
11      const key = routerFather
12        ? `${routerFather}.${route.RouterFather}`
13        : `menu.${route.RouterFather}`;
14      const path = key
15        .replace(/^menu\./, "")
16        .split(".")
17        .join("/");
18      if (route.RoutesChildren.length > 0) {
19        // console.log(route.RouterFather!);
20        return (
21          <SubMenu
22            style={showSystem(localStorage.getItem('role')!).includes(key) ? undefined : { display: 'none' }}
23            key={key}
24            title={
25              isFirstSubmenu ? (
26                <div className=" flex items-center">
27                  <div className=" w-3 h-3 bg-circle rounded-full mr-4"></div>
28                  <div className=" ">{menu[key]}</div>
29                </div>
30              ) : (
31                menu[key]
32              )
33            }
34          >
35            {renderMenu(route.RoutesChildren as IRoutes[], menu, key, false)}
36          </SubMenu>
37        );
38      } else {
39        return (
40          <Menu.Item key={key} style={showSystem(localStorage.getItem('role')!).includes(key) ? undefined : { display: 'none' }} onClick={() => {
41            setSelectKey([key])
42          }}>
43            <Link to={"/" + path} style={{ color: "#424B5E99" }}>
44              {menu[key]}
45            </Link>
46          </Menu.Item>
47        );
48      }
49    });
50  }
51  const Routes = splitRoutes(menu);
52  const url = useLocation();
53  // if(url.pathname.startsWith('/reservation/user')){
54  //   const key = ['menu.reservation.']
55  // }
56
57
58  return (
59    <div className="text-white overflow-hidden overflow-y-scroll hide-scrollbar h-[92vh]">
60      <Menu
61        className=""
62        levelIndent={30}
63        selectedKeys={selectKey}
64        defaultOpenKeys={url.pathname.split('/')}
65      >
66        {renderMenu(Routes, menu, "", true)}
67      </Menu>
68    </div>
69  );
70}
71

优点:

  1. 方便,不需要编写路由表,只需要创建文件就会自动生成对应路由。
  2. 后期渲染侧边栏的 menu 文件相比路由表配置起来也简单

缺点:

  1. 文件一创建就会生成路由,先前通过文件渲染侧边栏就会导致不想渲染的子路由也出现在侧边栏。 解决方案是通过 menu 配置进行筛选渲染,但是如此一来就和配置路由表大差不差。

总结就是:想使用文件路由,可以直接上手Next.js,能方便很多。加上设置侧边栏也是通过menu.ts来进行配置,倒不如直接使用路由表来渲染

例子:

1//路由类型
2type Auth = {
3  resource: string | RegExp;
4  actions?: string[];
5};
6export interface AuthParams {
7  // 某操作需要的权限数组
8  requiredPermissions?: Array<Auth>;
9  // 是否需要满足一个即可,即是或还是且。
10  oneOfPerm?: boolean;
11}
12export type IRoute = AuthParams & {
13  name: string;
14  key: string;
15  breadcrumb?: boolean;
16  children?: IRoute[];
17  hideInMenu?: boolean; // 是否在菜单中隐藏子路由,为了实现某些三级路由不展示在菜单中的需求
18  icon?: React.ForwardRefExoticComponent<
19    IconProps & React.RefAttributes<unknown>
20  >;
21};
22
1export const generatePermission = (level: string) => {
2  const actions = level === "3" ? [] : ["*"];
3  const result = {};
4  routes.forEach((item) => {
5    if (item.children) {
6      item.children.forEach((child) => {
7        result[child.name] = actions;
8      });
9    }
10  });
11  return result;
12};
13const useRoute = (userPermission): [IRoute[], string] => {
14  const filterRoute = (routes: IRoute[], arr = []): IRoute[] => {
15    if (!routes.length) {
16      return [];
17    }
18    for (const route of routes) {
19      const { requiredPermissions, oneOfPerm } = route;
20      let visible = true;
21      if (requiredPermissions) {
22        visible = auth({ requiredPermissions, oneOfPerm }, userPermission);
23      }
24      if (!visible) {
25        continue;
26      }
27      if (route.children && route.children.length) {
28        const newRoute = { ...route, children: [] };
29        filterRoute(route.children, newRoute.children);
30        if (newRoute.children.length) {
31          arr.push(newRoute);
32        }
33      } else {
34        arr.push({ ...route });
35      }
36    }
37
38    return arr;
39  };
40  const [permissionRoute, setPermissionRoute] = useState(routes);
41  useEffect(() => {
42    const newRoutes = filterRoute(routes);
43    setPermissionRoute(newRoutes);
44    // eslint-disable-next-line react-hooks/exhaustive-deps
45  }, [userPermission]);
46
47  const defaultRoute = useMemo(() => {
48    const first = permissionRoute[0];
49    if (first) {
50      const firstRoute = first?.children?.[0]?.key || first.key;
51      return firstRoute;
52    }
53    return "";
54  }, [permissionRoute]);
55
56  return [permissionRoute, defaultRoute];
57};
58
59export default useRoute;
60
1//渲染
2function renderRoutes(locale: { [x: string]: any }) {
3  routeMap.current.clear();
4  return function travel(_routes: IRoute[], level: number, parentNode = []) {
5    return _routes.map((route) => {
6      const { breadcrumb = true, hideInMenu } = route;
7      const iconDom = getIcon(route);
8      const titleDom = (
9        <>
10          {iconDom} {locale[route.name] || route.name}
11        </>
12      );
13      routeMap.current.set(
14        `/${route.key}`,
15        breadcrumb ? [...parentNode, route.name] : [],
16      );
17      const visibleChildren = (route.children || []).filter((child) => {
18        const { hideInMenu, breadcrumb = true } = child;
19        if (hideInMenu || route.hideInMenu) {
20          routeMap.current.set(
21            `/${child.key}`,
22            breadcrumb ? [...parentNode, route.name, child.name] : [],
23          );
24        }
25        return !hideInMenu;
26      });
27      // console.log(visibleChildren);
28      if (hideInMenu) {
29        return [];
30      }
31      if (visibleChildren.length) {
32        return (
33          <SubMenu key={route.key} title={titleDom}>
34            {travel(visibleChildren, level + 1, [...parentNode, route.name])}
35          </SubMenu>
36        );
37      }
38      return (
39        <MenuItem key={route.key}>
40          <Link to={`/${route.key}`}>{titleDom}</Link>
41        </MenuItem>
42      );
43    });
44  };
45}
46

通过配置路由,可以分权配置,细分到页面和操作,适用于大型的后台管理

直接通过文件配置路由,适用于简单的后台,不需要配置路由表的情况下,用起来方便 当然你可以自己选择 Next.js

鄙人浅显的见解,大佬轻喷

剩下两个模块

简单的表格组件使用而已,没什么亮点

收获

对这种路由布局理解提高了一点 帮他们写 12306 的时候,学会了抓包工具 Fiddler Classic。说不定哪天能写一个有趣的东西

© 2023 - 2024
githubYYGod0120