消息角标开发
Some checks failed
Node CI / build (14.x, macOS-latest) (push) Has been cancelled
Node CI / build (14.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (14.x, windows-latest) (push) Has been cancelled
Node CI / build (16.x, macOS-latest) (push) Has been cancelled
Node CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node CI / build (16.x, windows-latest) (push) Has been cancelled
coverage CI / build (push) Has been cancelled
Node pnpm CI / build (16.x, macOS-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, ubuntu-latest) (push) Has been cancelled
Node pnpm CI / build (16.x, windows-latest) (push) Has been cancelled

This commit is contained in:
francis-fh
2026-07-20 20:50:35 +08:00
parent dfe22c6307
commit 86e70a38d0
5 changed files with 99 additions and 2 deletions

2
.gitignore vendored
View File

@@ -21,4 +21,4 @@ src/.umi-production/
EMBED_INTEGRATION.md
embed-debug.html
sdk_dist
package-lock.json
package-lock.json

View File

@@ -20,15 +20,23 @@ import {
patchRouteWithRemoteMenus,
setRemoteMenu,
} from './services/session';
import {
fetchAndUpdateBadge,
patchMenuBadge,
clearBadgeCount,
} from './services/Management/interviewBadge';
import { PageEnum } from './enums/pagesEnums';
import { stringify } from 'querystring';
import { message } from 'antd';
import React from 'react';
import { getProductionApiBaseUrl } from './utils/publicUrl';
const isDev = process.env.NODE_ENV === 'development';
const loginOut = async () => {
clearSessionToken();
setRemoteMenu(null);
clearBadgeCount();
const { search, pathname } = window.location;
const urlParams = new URL(window.location.href).searchParams;
/** 此方法会跳转到 redirect 参数所在的位置 */
@@ -112,6 +120,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
// content: initialState?.currentUser?.nickName,
},
// actionRef: layoutActionRef,
postMenuData: (menuData: any[]) => patchMenuBadge(menuData),
menu: {
locale: false,
// // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
@@ -122,7 +131,7 @@ export const layout: RunTimeLayoutConfig = ({ initialState, setInitialState }) =
if (!initialState?.currentUser?.userId) {
return [];
}
return getRemoteMenu();
return getRemoteMenu() || [];
},
},
footerRender: () => <Footer />,
@@ -254,6 +263,7 @@ export async function render(oldRender: () => void) {
try {
const routers = await getRoutersInfo();
setRemoteMenu(routers);
await fetchAndUpdateBadge();
} catch (error) {
console.error('获取路由失败:', error);
}

View File

@@ -2,6 +2,7 @@ import React, { useRef } from 'react';
import { Tag } from 'antd';
import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components';
import { getCmsInterviewList } from '@/services/Management/list';
import { fetchAndUpdateBadge } from '@/services/Management/interviewBadge';
const interviewStatusMap: Record<string, { text: string; color: string }> = {
pending: { text: '待确认', color: 'blue' },
@@ -134,6 +135,7 @@ const InterviewList: React.FC = () => {
pageNum: current,
pageSize,
});
fetchAndUpdateBadge();
return {
data: response.rows || [],
total: response.total || 0,

View File

@@ -0,0 +1,72 @@
import React from 'react';
import { getInterviewBadgeCount } from './list';
let badgeCount = 0;
let subscribers: Array<(count: number) => void> = [];
/** 获取当前角标数量 */
export function getBadgeCount(): number {
return badgeCount;
}
/** 订阅角标数量变化,返回取消订阅函数 */
export function subscribeBadgeCount(fn: (count: number) => void): () => void {
subscribers.push(fn);
return () => {
subscribers = subscribers.filter((s) => s !== fn);
};
}
function notifySubscribers() {
subscribers.forEach((fn) => fn(badgeCount));
}
/** 从接口获取并更新角标数量 */
export async function fetchAndUpdateBadge() {
try {
const res = await getInterviewBadgeCount();
if (res.code === 200 && res.data) {
badgeCount = (res.data.jss ?? 0) + (res.data.jjs ?? 0);
notifySubscribers();
}
} catch (e) {
console.error('获取面试角标失败', e);
}
}
/** 清除角标数量(退出登录时) */
export function clearBadgeCount() {
badgeCount = 0;
notifySubscribers();
}
/** 递归遍历菜单树,给面试列表菜单项的 name 注入红色角标 JSX */
export function patchMenuBadge(menuData: any[]): any[] {
if (!menuData) return menuData;
return menuData.map((item: any) => {
const patched: any = { ...item };
if (item.path === 'interview-list' || (item.path && item.path.endsWith('/interview-list'))) {
const originalName = item._originalName || item.name;
patched._originalName = originalName;
if (badgeCount > 0) {
patched.name = (
<span style={{ whiteSpace: 'nowrap' }}>
{originalName}
<span style={{ color: '#ff4d4f', fontWeight: 500, marginLeft: 4 }}>
({badgeCount})
</span>
</span>
);
} else {
patched.name = originalName;
}
}
if (item.routes) {
patched.routes = patchMenuBadge(item.routes);
}
if (item.children) {
patched.children = patchMenuBadge(item.children);
}
return patched;
});
}

View File

@@ -155,3 +155,16 @@ export async function getAppSkillList(params?: { userId?: number }) {
params,
});
}
// ========== 面试消息角标 APIs ==========
/** 获取面试消息角标数量(后台管理端菜单角标) */
export async function getInterviewBadgeCount() {
return request<{
msg: string;
code: number;
data: { total: number; jss: number; jjs: number };
}>('/api/cms/interview/msyyhbs', {
method: 'GET',
});
}