diff --git a/config/defaultSettings.ts b/config/defaultSettings.ts
index 04f5d81..be8e4a5 100644
--- a/config/defaultSettings.ts
+++ b/config/defaultSettings.ts
@@ -12,13 +12,14 @@ const Settings: ProLayoutProps & {
colorPrimary: "#1890ff",
layout: "mix",
contentWidth: "Fluid",
- fixedHeader: false,
+ fixedHeader: true,
fixSiderbar: true,
colorWeak: false,
- title: "tashow - 管理后台",
+ title: "百业到家云控台",
pwa: true,
- logo: "https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg",
+ logo: "/logo.svg",
iconfontUrl: "",
+ splitMenus: true,
token: {
// 参见ts声明,demo 见文档,通过token 修改样式
//https://procomponents.ant.design/components/layout#%E9%80%9A%E8%BF%87-token-%E4%BF%AE%E6%94%B9%E6%A0%B7%E5%BC%8F
diff --git a/config/routes.ts b/config/routes.ts
index ba9c580..70941b3 100644
--- a/config/routes.ts
+++ b/config/routes.ts
@@ -10,6 +10,7 @@
* @param icon 配置路由的图标,取值参考 https://ant.design/components/icon-cn, 注意去除风格后缀和大小写,如想要配置图标为 则取值应为 stepBackward 或 StepBackward,如想要配置图标为 则取值应为 user 或者 User
* @doc https://umijs.org/docs/guides/routes
*/
+
export default [
{
path: "/user",
@@ -28,6 +29,24 @@ export default [
icon: "smile",
component: "./Welcome",
},
+ // {
+ // path: "/system1",
+ // name: "system1",
+ // icon: "smile",
+ // routes: [
+ // {
+ // name: "tenant",
+ // path: "/system1/tenant",
+ // routes: [
+ // {
+ // name: "package",
+ // path: "/system1/tenant/package",
+ // component: "system/tenant/package",
+ // },
+ // ],
+ // },
+ // ],
+ // },
{
path: "/admin",
name: "admin",
@@ -58,6 +77,6 @@ export default [
{
component: "404",
layout: false,
- path: "./*",
+ path: "*",
},
];
diff --git a/src/app.tsx b/src/app.tsx
index 9b47b58..318080d 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -1,9 +1,9 @@
-import { LinkOutlined } from "@ant-design/icons";
+import React, { Children, Component, JSX, Suspense } from "react";
+import { Spin } from "antd";
import type { Settings as LayoutSettings } from "@ant-design/pro-components";
import { SettingDrawer } from "@ant-design/pro-components";
import type { RequestConfig, RunTimeLayoutConfig } from "@umijs/max";
-import { history, Link } from "@umijs/max";
-import React from "react";
+import { history, Link, Navigate } from "@umijs/max";
import {
AvatarDropdown,
AvatarName,
@@ -16,13 +16,20 @@ import type { UserVO, TokenType, UserInfoVO } from "@/services/login/types";
import defaultSettings from "../config/defaultSettings";
import { errorConfig } from "./requestErrorConfig";
import "@ant-design/v5-patch-for-react-19";
-import { getAccessToken, getRefreshToken, getTenantId } from "./utils/auth";
+import { getAccessToken, getRefreshToken, getTenantId } from "@/utils/auth";
import { CACHE_KEY, useCache } from "./hooks/web/useCache";
+import { MenuVO } from "./services/system/menu";
+import {
+ transformBackendMenuToFlatRoutes,
+ transformMenuToRoutes,
+} from "@/utils/menuUtils";
const isDev = process.env.NODE_ENV === "development";
const isDevOrTest = isDev || process.env.CI;
const loginPath = "/user/login";
+// 全局存储菜单数据和路由映射
+
/**
* @see https://umijs.org/docs/api/runtime-config#getinitialstate
* */
@@ -43,6 +50,9 @@ export async function getInitialState(): Promise<{
const { data } = await getInfo();
wsCache.set(CACHE_KEY.USER, data);
wsCache.set(CACHE_KEY.ROLE_ROUTERS, data.menus);
+
+ // 转换菜单格式
+
return data;
} catch (_error) {
history.push(loginPath);
@@ -51,12 +61,17 @@ export async function getInitialState(): Promise<{
};
// 如果不是登录页面,执行
const { location } = history;
+
if (
![loginPath, "/user/register", "/user/register-result"].includes(
location.pathname
)
) {
- const currentUser = await fetchUserInfo();
+ const currentUser = wsCache.get(CACHE_KEY.USER);
+ if (getAccessToken() && !currentUser) {
+ fetchUserInfo();
+ }
+
return {
fetchUserInfo,
currentUser,
@@ -79,6 +94,10 @@ export const layout: RunTimeLayoutConfig = ({
,
,
],
+ menu: {
+ locale: false,
+ // 关闭国际化-
+ },
avatarProps: {
src: initialState?.currentUser?.user.avatar,
title: ,
@@ -117,17 +136,9 @@ export const layout: RunTimeLayoutConfig = ({
width: "331px",
},
],
- links: isDevOrTest
- ? [
-
-
- OpenAPI 文档
- ,
- ]
- : [],
menuHeaderRender: undefined,
// 自定义 403 页面
- // unAccessible:
unAccessible
,
+ unAccessible: unAccessible
,
// 增加一个 loading 的状态
childrenRender: (children) => {
// if (initialState?.loading) return ;
@@ -187,3 +198,65 @@ export const request: RequestConfig = {
},
],
};
+// umi 4 使用 modifyRoutes
+export function patchClientRoutes({ routes }: { routes: any }) {
+ const { wsCache } = useCache();
+ const globalMenus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
+ const routerIndex = routes.findIndex((item: any) => item.path === "/");
+ const parentId = routes[routerIndex].id;
+
+ if (globalMenus) {
+ routes[routerIndex]["routes"].push(...loopMenuItem(globalMenus, parentId));
+ }
+}
+
+const loopMenuItem = (menus: any[], pId: number | string): any[] => {
+ return menus.flatMap((item) => {
+ let Component: React.ComponentType | null = null;
+ if (item.component && item.component.length > 0) {
+ // 防止配置了路由,但本地暂未添加对应的页面,产生的错误
+ console.log(item.component);
+ Component = React.lazy(() => {
+ const importComponent = () => import(`@/pages/${item.component}`);
+ const import404 = () => import("@/pages/404");
+ return importComponent().catch(import404);
+ });
+ }
+ if (item.children && item.children.length > 0) {
+ return [
+ {
+ path: item.path,
+ name: item.name,
+ icon: item.icon,
+ id: item.id,
+ parentId: pId,
+ children: [
+ {
+ path: item.url,
+ element: ,
+ },
+ ...loopMenuItem(item.children, item.menuID),
+ ],
+ },
+ ];
+ } else {
+ return [
+ {
+ path: item.path,
+ name: item.name,
+ icon: item.icon,
+ id: item.menuID,
+ parentId: pId,
+ element: (
+ }
+ >
+ {Component && }
+
+ ),
+ children: [], // 添加缺失的 children 属性
+ },
+ ];
+ }
+ });
+};
diff --git a/src/global.less b/src/global.less
index 1959f4f..a39376d 100644
--- a/src/global.less
+++ b/src/global.less
@@ -45,9 +45,9 @@ body,
height: 100%;
margin: 0;
padding: 0;
- font-family:
- AlibabaSans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans',
- sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
+ font-family: AlibabaSans, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif,
+ "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
.colorWeak {
@@ -55,7 +55,7 @@ body,
}
.ant-layout {
- min-height: 100vh;
+ min-height: 100vh !important;
}
.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
left: unset;
diff --git a/src/locales/zh-CN/menu.ts b/src/locales/zh-CN/menu.ts
index fecb70a..33f73db 100644
--- a/src/locales/zh-CN/menu.ts
+++ b/src/locales/zh-CN/menu.ts
@@ -1,52 +1,53 @@
export default {
- 'menu.welcome': '欢迎',
- 'menu.more-blocks': '更多区块',
- 'menu.home': '首页',
- 'menu.admin': '管理页',
- 'menu.admin.sub-page': '二级管理页',
- 'menu.login': '登录',
- 'menu.register': '注册',
- 'menu.register-result': '注册结果',
- 'menu.dashboard': 'Dashboard',
- 'menu.dashboard.analysis': '分析页',
- 'menu.dashboard.monitor': '监控页',
- 'menu.dashboard.workplace': '工作台',
- 'menu.exception.403': '403',
- 'menu.exception.404': '404',
- 'menu.exception.500': '500',
- 'menu.form': '表单页',
- 'menu.form.basic-form': '基础表单',
- 'menu.form.step-form': '分步表单',
- 'menu.form.step-form.info': '分步表单(填写转账信息)',
- 'menu.form.step-form.confirm': '分步表单(确认转账信息)',
- 'menu.form.step-form.result': '分步表单(完成)',
- 'menu.form.advanced-form': '高级表单',
- 'menu.list': '列表页',
- 'menu.list.table-list': '查询表格',
- 'menu.list.basic-list': '标准列表',
- 'menu.list.card-list': '卡片列表',
- 'menu.list.search-list': '搜索列表',
- 'menu.list.search-list.articles': '搜索列表(文章)',
- 'menu.list.search-list.projects': '搜索列表(项目)',
- 'menu.list.search-list.applications': '搜索列表(应用)',
- 'menu.profile': '详情页',
- 'menu.profile.basic': '基础详情页',
- 'menu.profile.advanced': '高级详情页',
- 'menu.result': '结果页',
- 'menu.result.success': '成功页',
- 'menu.result.fail': '失败页',
- 'menu.exception': '异常页',
- 'menu.exception.not-permission': '403',
- 'menu.exception.not-find': '404',
- 'menu.exception.server-error': '500',
- 'menu.exception.trigger': '触发错误',
- 'menu.account': '个人页',
- 'menu.account.center': '个人中心',
- 'menu.account.settings': '个人设置',
- 'menu.account.trigger': '触发报错',
- 'menu.account.logout': '退出登录',
- 'menu.editor': '图形编辑器',
- 'menu.editor.flow': '流程编辑器',
- 'menu.editor.mind': '脑图编辑器',
- 'menu.editor.koni': '拓扑编辑器',
+ "menu.welcome": "欢迎",
+ "menu.more-blocks": "更多区块",
+ "menu.home": "首页",
+ "menu.admin": "管理页",
+ "menu.admin.sub-page": "二级管理页",
+ "menu.login": "登录",
+ "menu.register": "注册",
+ "menu.register-result": "注册结果",
+ "menu.dashboard": "Dashboard",
+ "menu.dashboard.analysis": "分析页",
+ "menu.dashboard.monitor": "监控页",
+ "menu.dashboard.workplace": "工作台",
+ "menu.exception.403": "403",
+ "menu.exception.404": "404",
+ "menu.exception.500": "500",
+ "menu.form": "表单页",
+ "menu.form.basic-form": "基础表单",
+ "menu.form.step-form": "分步表单",
+ "menu.form.step-form.info": "分步表单(填写转账信息)",
+ "menu.form.step-form.confirm": "分步表单(确认转账信息)",
+ "menu.form.step-form.result": "分步表单(完成)",
+ "menu.form.advanced-form": "高级表单",
+ "menu.list": "列表页",
+ "menu.list.table-list": "查询表格",
+ "menu.list.basic-list": "标准列表",
+ "menu.list.card-list": "卡片列表",
+ "menu.list.search-list": "搜索列表",
+ "menu.list.search-list.articles": "搜索列表(文章)",
+ "menu.list.search-list.projects": "搜索列表(项目)",
+ "menu.list.search-list.applications": "搜索列表(应用)",
+ "menu.profile": "详情页",
+ "menu.profile.basic": "基础详情页",
+ "menu.profile.advanced": "高级详情页",
+ "menu.result": "结果页",
+ "menu.result.success": "成功页",
+ "menu.result.fail": "失败页",
+ "menu.exception": "异常页",
+ "menu.exception.not-permission": "403",
+ "menu.exception.not-find": "404",
+ "menu.exception.server-error": "500",
+ "menu.exception.trigger": "触发错误",
+ "menu.account": "个人页",
+ "menu.account.center": "个人中心",
+ "menu.account.settings": "个人设置",
+ "menu.account.trigger": "触发报错",
+ "menu.account.logout": "退出登录",
+ "menu.editor": "图形编辑器",
+ "menu.editor.flow": "流程编辑器",
+ "menu.editor.mind": "脑图编辑器",
+ "menu.editor.koni": "拓扑编辑器",
+ // 基础设施相关菜单
};
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 9734cc5..01d33f2 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,16 +1,17 @@
-import { history, useIntl } from '@umijs/max';
-import { Button, Card, Result } from 'antd';
-import React from 'react';
+import { PageContainer } from "@ant-design/pro-components";
+import { history, useIntl } from "@umijs/max";
+import { Button, Card, Result } from "antd";
+import React from "react";
const NoFoundPage: React.FC = () => (
history.push('/')}>
- {useIntl().formatMessage({ id: 'pages.404.buttonText' })}
+
}
/>
diff --git a/src/pages/system/tenant/list/index.tsx b/src/pages/system/tenant/list/index.tsx
new file mode 100644
index 0000000..66349a7
--- /dev/null
+++ b/src/pages/system/tenant/list/index.tsx
@@ -0,0 +1,5 @@
+const TenantList = () => {
+ return TenantList
;
+};
+
+export default TenantList;
diff --git a/src/pages/system/tenant/package/index.tsx b/src/pages/system/tenant/package/index.tsx
new file mode 100644
index 0000000..34286af
--- /dev/null
+++ b/src/pages/system/tenant/package/index.tsx
@@ -0,0 +1,5 @@
+const TenantPackage = () => {
+ return TenantPackage
;
+};
+
+export default TenantPackage;
diff --git a/src/pages/user/login/index.tsx b/src/pages/user/login/index.tsx
index e12f08b..7c61b2b 100644
--- a/src/pages/user/login/index.tsx
+++ b/src/pages/user/login/index.tsx
@@ -104,7 +104,7 @@ const Page = () => {
backdropFilter: "blur(4px)",
}}
onFinish={handleSubmit}
- subTitle="百业到家-管理平台"
+ subTitle="百业到家云控台"
// activityConfig={{
// style: {
// boxShadow: "0px 0px 8px rgba(0, 0, 0, 0.2)",
diff --git a/src/services/login/index.ts b/src/services/login/index.ts
index 864eb04..a5c4315 100644
--- a/src/services/login/index.ts
+++ b/src/services/login/index.ts
@@ -40,7 +40,6 @@ export async function login(
// };
export async function getTenantIdByName(
- // 叠加生成的Param类型 (非body参数swagger默认没有生成对象)
params: { name: string },
options?: { [key: string]: any }
) {
diff --git a/src/services/login/oauth2/index.ts b/src/services/login/oauth2/index.ts
index f4a67fb..5062615 100644
--- a/src/services/login/oauth2/index.ts
+++ b/src/services/login/oauth2/index.ts
@@ -1,41 +1,41 @@
-import request from '@/config/axios'
+import { request } from "@umijs/max";
// 获得授权信息
-export const getAuthorize = (clientId: string) => {
- return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })
-}
+// export const getAuthorize = (clientId: string) => {
+// return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })
+// }
-// 发起授权
-export const authorize = (
- responseType: string,
- clientId: string,
- redirectUri: string,
- state: string,
- autoApprove: boolean,
- checkedScopes: string[],
- uncheckedScopes: string[]
-) => {
- // 构建 scopes
- const scopes = {}
- for (const scope of checkedScopes) {
- scopes[scope] = true
- }
- for (const scope of uncheckedScopes) {
- scopes[scope] = false
- }
- // 发起请求
- return request.post({
- url: '/system/oauth2/authorize',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded'
- },
- params: {
- response_type: responseType,
- client_id: clientId,
- redirect_uri: redirectUri,
- state: state,
- auto_approve: autoApprove,
- scope: JSON.stringify(scopes)
- }
- })
-}
+// // 发起授权
+// export const authorize = (
+// responseType: string,
+// clientId: string,
+// redirectUri: string,
+// state: string,
+// autoApprove: boolean,
+// checkedScopes: string[],
+// uncheckedScopes: string[]
+// ) => {
+// // 构建 scopes
+// const scopes = {}
+// for (const scope of checkedScopes) {
+// scopes[scope] = true
+// }
+// for (const scope of uncheckedScopes) {
+// scopes[scope] = false
+// }
+// // 发起请求
+// return request.post({
+// url: '/system/oauth2/authorize',
+// headers: {
+// 'Content-Type': 'application/x-www-form-urlencoded'
+// },
+// params: {
+// response_type: responseType,
+// client_id: clientId,
+// redirect_uri: redirectUri,
+// state: state,
+// auto_approve: autoApprove,
+// scope: JSON.stringify(scopes)
+// }
+// })
+// }
diff --git a/src/services/system/menu/index.ts b/src/services/system/menu/index.ts
index e94500e..8f048ed 100644
--- a/src/services/system/menu/index.ts
+++ b/src/services/system/menu/index.ts
@@ -16,6 +16,7 @@ export interface MenuVO {
keepAlive: boolean;
alwaysShow?: boolean;
createTime: Date;
+ children?: MenuVO[];
}
// 查询菜单(精简)列表
diff --git a/src/utils/menuUtils.ts b/src/utils/menuUtils.ts
new file mode 100644
index 0000000..fd62235
--- /dev/null
+++ b/src/utils/menuUtils.ts
@@ -0,0 +1,97 @@
+// src/utils/menuUtils.ts
+import { MenuVO } from "@/services/system/menu";
+import type { MenuDataItem } from "@ant-design/pro-components";
+// src/utils/menuUtils.ts
+
+// src/utils/menuUtils.ts
+// src/utils/route.ts
+export function transformMenuToRoutes(menuData: MenuVO[]): any[] {
+ return menuData.map((item) => ({
+ path: item.path,
+ name: item.name,
+ icon: item.icon,
+ component: item.component,
+ routes: item.children ? transformMenuToRoutes(item.children) : undefined,
+ }));
+}
+
+// src/utils/route.ts
+export function transformBackendMenuToFlatRoutes(menuData: any[]) {
+ const flatRoutes: any[] = [];
+
+ function processMenu(items: any[], parentRouteId = "ant-design-pro-layout") {
+ items.forEach((item) => {
+ const currentRouteId = `route-${item.id}`;
+
+ // 处理路径 - 如果是子路由,需要组合完整路径
+ let fullPath = item.path;
+ if (item.parentId !== 0 && !item.path.startsWith("/")) {
+ // 子路由需要相对路径
+ fullPath = item.path;
+ }
+
+ const route: any = {
+ id: currentRouteId,
+ path: fullPath,
+ name: item.name,
+ parentId: parentRouteId,
+ };
+
+ // 添加图标(如果不是 # 的话)
+ if (item.icon && item.icon !== "#") {
+ route.icon = item.icon;
+ }
+
+ // 添加组件路径
+ if (item.component) {
+ // 转换组件路径为动态导入格式
+ route.component = item.component;
+ }
+
+ // 其他属性
+ if (!item.visible) {
+ route.hideInMenu = true;
+ }
+
+ flatRoutes.push(route);
+
+ // 递归处理子菜单
+ if (item.children && item.children.length > 0) {
+ processMenu(item.children, currentRouteId);
+ }
+ });
+ }
+
+ processMenu(menuData);
+ return flatRoutes;
+}
+
+export function transformMenuData(menuData: any[]) {
+ const transformItem = (item: any, parentPath = "") => {
+ const fullPath = item.path.startsWith("/")
+ ? item.path
+ : `${parentPath}/${item.path}`;
+ const result: any = {
+ path: fullPath,
+ name: item.name,
+ key: `${item.id}`,
+ };
+ if (item.icon && item.icon !== "#") {
+ result.icon = item.icon;
+ }
+
+ if (!item.visible) {
+ result.hideInMenu = true;
+ }
+
+ if (item.children && item.children.length > 0) {
+ result.children = item.children.map((child: any) =>
+ transformItem(child, fullPath)
+ );
+ }
+
+ return;
+ result;
+ };
+ return menuData.map((item) => transformItem(item));
+}
diff --git a/tsconfig.json b/tsconfig.json
index 2cd5f8c..71939ed 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -2,6 +2,7 @@
"compilerOptions": {
"baseUrl": "./",
"target": "esnext",
+ "module": "esnext",
"moduleResolution": "node",
"jsx": "react-jsx",
"esModuleInterop": true,