feat: 消息中心-站内信管理

This commit is contained in:
2025-09-18 17:47:35 +08:00
parent 73bc5aec6b
commit 8afdb0d09e
26 changed files with 1929 additions and 135 deletions

View File

@@ -1,38 +1,38 @@
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, Navigate } from '@umijs/max';
import { Modal, Spin } from 'antd';
import React, {
Children,
Component,
createContext,
JSX,
Suspense,
} from "react";
import { Modal, 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, Navigate } from "@umijs/max";
} from 'react';
import {
AvatarDropdown,
AvatarName,
Footer,
Question,
SelectLang,
} from "@/components";
import { getInfo } from "@/services/login";
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 { CACHE_KEY, useCache } from "./hooks/web/useCache";
import { MenuVO } from "./services/system/menu";
} from '@/components';
import { getInfo } from '@/services/login';
import type { TokenType, UserInfoVO, UserVO } 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 {
transformBackendMenuToFlatRoutes,
transformMenuToRoutes,
} from "@/utils/menuUtils";
const isDev = process.env.NODE_ENV === "development";
} from '@/utils/menuUtils';
import { CACHE_KEY, useCache } from './hooks/web/useCache';
import { MenuVO } from './services/system/menu';
const isDev = process.env.NODE_ENV === 'development';
const isDevOrTest = isDev || process.env.CI;
const loginPath = "/user/login";
const loginPath = '/user/login';
// 全局存储菜单数据和路由映射
@@ -51,7 +51,7 @@ export async function getInitialState(): Promise<{
try {
const token = getAccessToken();
if (!token) {
throw new Error("No token found");
throw new Error('No token found');
}
const data = await getInfo();
wsCache.set(CACHE_KEY.USER, data);
@@ -69,8 +69,8 @@ export async function getInitialState(): Promise<{
const { location } = history;
if (
![loginPath, "/user/register", "/user/register-result"].includes(
location.pathname
![loginPath, '/user/register', '/user/register-result'].includes(
location.pathname,
)
) {
const currentUser = wsCache.get(CACHE_KEY.USER);
@@ -125,22 +125,22 @@ export const layout: RunTimeLayoutConfig = ({
},
bgLayoutImgList: [
{
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr",
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
left: 85,
bottom: 100,
height: "303px",
height: '303px',
},
{
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr",
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
bottom: -68,
right: -45,
height: "303px",
height: '303px',
},
{
src: "https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr",
src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
bottom: 0,
left: 0,
width: "331px",
width: '331px',
},
],
menuHeaderRender: undefined,
@@ -178,14 +178,14 @@ export const layout: RunTimeLayoutConfig = ({
* @doc https://umijs.org/docs/max/request#配置
*/
export const request: RequestConfig = {
baseURL: isDev ? "" : "https://proapi.azurewebsites.net",
baseURL: isDev ? '' : 'https://proapi.azurewebsites.net',
...errorConfig,
// 添加请求拦截器
requestInterceptors: [
(url, options) => {
// 为所有请求添加 API 前缀
if (url && !url.startsWith(process.env.API_PREFIX || "/admin-api")) {
url = (process.env.API_PREFIX || "/admin-api") + url;
if (url && !url.startsWith(process.env.API_PREFIX || '/admin-api')) {
url = (process.env.API_PREFIX || '/admin-api') + url;
}
// 获取存储在本地的 token 和 tenantId
const token = getAccessToken();
@@ -193,12 +193,12 @@ export const request: RequestConfig = {
// 设置统一的请求头
const headers: Record<string, string> = {
...options.headers,
Accept: "*",
"tenant-id": tenantId,
Accept: '*',
'tenant-id': tenantId,
};
// 如果有token则添加Authorization头
if (token) {
headers["Authorization"] = `Bearer ${getAccessToken()}`;
headers['Authorization'] = `Bearer ${getAccessToken()}`;
}
return { url, options: { ...options, headers } };
},
@@ -210,7 +210,7 @@ export const request: RequestConfig = {
const appendParams = (key: string, value: any) => {
if (Array.isArray(value)) {
// 特殊处理 createTime 数组,转换为 createTime[0] 和 createTime[1] 格式
if (key === "createTime") {
if (key === 'createTime') {
value.forEach((val, index) => {
searchParams.append(`${key}[${index}]`, val);
});
@@ -237,23 +237,23 @@ export function patchClientRoutes({ routes }: { routes: any }) {
const { wsCache } = useCache();
console.log(2222);
const globalMenus = wsCache.get(CACHE_KEY.ROLE_ROUTERS);
const routerIndex = routes.findIndex((item: any) => item.path === "/");
const routerIndex = routes.findIndex((item: any) => item.path === '/');
const parentId = routes[routerIndex].id;
if (globalMenus) {
routes[routerIndex]["routes"].push(...loopMenuItem(globalMenus, parentId));
routes[routerIndex]['routes'].push(...loopMenuItem(globalMenus, parentId));
}
}
const loopMenuItem = (menus: any[], pId: number | string): any[] => {
return menus.flatMap((item) => {
let Component: React.ComponentType<any> | null = null;
console.log(findFirstLeafRoute(item), "item");
console.log(findFirstLeafRoute(item), 'item');
if (item.component && item.component.length > 0) {
// 防止配置了路由,但本地暂未添加对应的页面,产生的错误
Component = React.lazy(() => {
const importComponent = () => import(`@/pages/${item.component}`);
const import404 = () => import("@/pages/404");
const import404 = () => import('@/pages/404');
return importComponent().catch(import404);
});
}
@@ -262,7 +262,7 @@ const loopMenuItem = (menus: any[], pId: number | string): any[] => {
{
path: item.path,
name: item.name,
icon: item.icon,
// icon: item.icon,
id: item.id,
parentId: pId,
@@ -281,13 +281,13 @@ const loopMenuItem = (menus: any[], pId: number | string): any[] => {
{
path: item.path,
name: item.name,
icon: item.icon,
// icon: item.icon,
id: item.menuID,
parentId: pId,
redirect: "/system/tenant/list",
redirect: '/system/tenant/list',
element: (
<React.Suspense
fallback={<Spin style={{ width: "100%", height: "100%" }} />}
fallback={<Spin style={{ width: '100%', height: '100%' }} />}
>
{Component && <Component />}
</React.Suspense>
@@ -299,7 +299,7 @@ const loopMenuItem = (menus: any[], pId: number | string): any[] => {
});
};
const findFirstLeafRoute = (menuItem: any, parent = "/"): string | null => {
const findFirstLeafRoute = (menuItem: any, parent = '/'): string | null => {
// 如果没有子菜单,返回当前路径
if (!menuItem.children || menuItem.children.length === 0) {
@@ -308,7 +308,7 @@ const findFirstLeafRoute = (menuItem: any, parent = "/"): string | null => {
// 递归查找第一个叶子节点
for (const child of menuItem.children) {
const leafRoute = findFirstLeafRoute(child, menuItem.path + "/");
const leafRoute = findFirstLeafRoute(child, menuItem.path + '/');
if (leafRoute) {
return leafRoute;
}