记得下次好好评审项目以及拉黑胡*
项目地址市民端:账 123456 密 123456
一个基本的后台管理,但是内容极其恶心,既要打车又要货拉拉还要 12306 买票
最恶心的就是没有正规的 12306,都是自己爬的接口用。不会被抓吧
属于是只用上了基本的东西
这个项目采用的是文件路由的方式,类似于 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
优点:
缺点:
总结就是:想使用文件路由,可以直接上手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。说不定哪天能写一个有趣的东西