/* * @Author: your name * @Date: 2021-11-16 09:12:37 * @LastEditTime: 2025-06-13 15:31:59 * @LastEditors: code4eat awesomedema@gmail.com * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE * @FilePath: /KC-MiddlePlatform/src/pages/index/components/topBar/index.tsx */ import React, { useEffect, useRef, useState } from 'react'; import './style.less'; import { Badge, Input, Tooltip, Tabs, Spin, Empty, Modal, message } from 'antd'; import { LogoutOutlined, SettingOutlined } from '@ant-design/icons'; // import logo from '../../../public/images/kc-logo.png'; import tabCloseIcon from '../../../public/images/tabCloseIcon.png'; import { history, useLocation, useModel } from 'umi'; import { Divider } from 'antd'; import { updateTokenReq } from '@/service/user'; import { logoutHandle } from '@/global'; import { switchOrgReq, getMessageList, batchMessage } from '@/service'; import { KcimCenterSysId } from '@/constant'; interface TopBarType { onTabChange?: (data: TopBar.Tab[]) => void; //当tab切换时回调 openedTabs: TopBar.Tab[]; //已打开的列表 currentTab?: TopBar.Tab | undefined; //当前tab userPannelTabClick?: (tag: 'SETTING' | 'LOGOUT' | 'SETUSERINFO') => void; onCloseTab?: (data: TopBar.Tab) => void; onTabClick?: (data: TopBar.Tab) => void; userData?: { name: string;[key: string]: any }; navData: TopBar.PanelData[]; logo?: string; topBarTitle?: string; } let isClickInside = false; const TopBar: React.FC = (props) => { const { onTabChange, userPannelTabClick, onCloseTab, onTabClick, userData, navData, currentTab, logo = undefined, topBarTitle = '欢迎进入医管平台' } = props; const [systemTabs, setSystemTabs] = useState([]); //已打开的tab const [currentSelectedTab, setCurrentSelectedTab] = useState(); const [ifOpenPannel, setIfOpenPannel] = useState(false); const [arrowRotate, setArrowRotate] = useState(false); // const [pageTitle, set_pageTitle] = useState(''); const [currentActivedBlockIndex, set_currentActivedBlockIndex] = useState(0); const [panelData, set_panelData] = useState([]); const [onTabSystemTabs, set_onTabSystemTabs] = useState([]); //tab导航可以放下的数量,剩余通过下拉获取 const [onTabSystemTabs_hide, set_onTabSystemTabs_hide] = useState([]); //下拉掩藏的导航 const { initialState, setInitialState } = useModel('@@initialState'); const [tokenUpdateModalVisible, set_tokenUpdateModalVisible] = useState(false); const location = useLocation(); const [showMoreTabPannel, set_showMoreTabPannel] = useState(false); const [password, set_password] = useState(undefined); const [showGroupList, set_showGroupList] = useState(false); const [groupList, set_groupList] = useState([]); const [currentActivedGroup, set_currentActivedGroup] = useState(undefined); const [unreadCount, setUnreadCount] = useState(0); const [notificationModalVisible, setNotificationModalVisible] = useState(false); const [notificationTab, setNotificationTab] = useState<'unread' | 'read'>('unread'); const [notificationLoading, setNotificationLoading] = useState(false); const [notificationBatchLoading, setNotificationBatchLoading] = useState(false); const [notificationList, setNotificationList] = useState<{ unread: any[]; read: any[] }>({ unread: [], read: [], }); const localSavedTab = localStorage.getItem('currentSelectedTab'); const currentSelectedTabFromLocal = localSavedTab ? JSON.parse(localSavedTab) : {}; const PannelRef = useRef(null); const GroupListWrapperRef = useRef(null); const notificationWsRef = useRef(null); const notificationPanelRef = useRef(null); const _systemTabClickHandle = (item: TopBar.Tab) => { //导航栏tab点击 // console.log('_systemTabClickHandle',item); onTabClick && onTabClick(item); if (item.type != 1 && item.contentType != 7) { localStorage.setItem('currentSelectedTab', JSON.stringify(item)); setCurrentSelectedTab(item); } }; const _systemListClickHandle = (data: TopBar.Tab, currentActivedBlockIndex: number, index: number, i: number) => { //导航栏系统菜单列表点击回调 if (currentSelectedTab?.menuId == data.menuId) return; //临时保存衣打开过的菜单 const t = localStorage.getItem('visitedTabs'); if (t) { let visitedTabs = JSON.parse(t); // 确保 visitedTabs 是数组类型,防止 findIndex 报错 if (Array.isArray(visitedTabs)) { let index = visitedTabs.findIndex((t: TopBar.Tab) => t.menuId == data.menuId); if (index == -1) { visitedTabs.push(data); localStorage.setItem('visitedTabs', JSON.stringify(visitedTabs)); } } else { // 如果不是数组,重新初始化 const newVisitedTabs = [data]; localStorage.setItem('visitedTabs', JSON.stringify(newVisitedTabs)); } } else { localStorage.setItem('visitedTabs', JSON.stringify([data])); } _systemTabClickHandle(data); //触发一次tab点击 // set_pageTitle(panelData[currentActivedBlockIndex].child[index].name); setInitialState((s: any) => ({ ...s, pageTitle: panelData[currentActivedBlockIndex].child[index].name })); if (panelData[currentActivedBlockIndex].child[index].child) { //console.log([...panelData[currentActivedBlockIndex].child[index].child]) setSystemTabs([...panelData[currentActivedBlockIndex].child[index].child]); } setIfOpenPannel(false); }; const _userPannelTabClick = (tag: 'SETTING' | 'LOGOUT') => { //用户菜单tab点击回调 userPannelTabClick && userPannelTabClick(tag); }; const closeTabHandle = (item: TopBar.Tab, e: React.MouseEvent) => { e.stopPropagation(); let _systemTabs = [...systemTabs]; let delIndex = -1; const filtered: any[] = _systemTabs.filter((t, index) => { if (t.menuId == item.menuId) { delIndex = index; } return t.menuId != item.menuId; }); setSystemTabs([...filtered]); if (delIndex != 0) { _systemTabClickHandle(_systemTabs[delIndex - 1]); //自动切换为前一个tab } onTabChange && onTabChange(filtered); onCloseTab && onCloseTab(item); }; const UserPannel = () => { return (
_userPannelTabClick('SETTING')}> 设置
_userPannelTabClick('LOGOUT')}> 退出
); }; const goChannelIndex = (menuData: any) => { setIfOpenPannel(false); onTabClick && onTabClick(menuData); }; const goToHome = () => { const go = () => { history.replace('/index'); setSystemTabs([]); //清空tab导航 onTabChange && onTabChange([]); setCurrentSelectedTab(undefined); setInitialState((s: any) => ({ ...s, pageTitle: topBarTitle })); setIfOpenPannel(false); console.log('goHome'); localStorage.removeItem('currentSelectedTab'); localStorage.removeItem('selectedKeys'); // localStorage.removeItem('visitedTabs'); localStorage.removeItem('openKeys'); }; const currentSelectedSubHop_json = localStorage.getItem('currentSelectedSubHop'); if (currentSelectedSubHop_json) { const currentSelectedSubHop = JSON.parse(currentSelectedSubHop_json); if (currentSelectedSubHop.loadType) { return false; } else { go(); } } else { go(); } }; const goSystemIndex = (name: string) => { if (panelData[currentActivedBlockIndex]) { const result: TopBar.TypeBlock[] = panelData[currentActivedBlockIndex].child.filter((t) => t.name == name); if (result.length > 0) { _systemTabClickHandle(Object.assign(result[0])); // history.push(result[0].path) } } }; const getBaseParams = () => { const userId = initialState?.userData?.userId; let hospId: any; const currentSelectedSubHop_json = localStorage.getItem('currentSelectedSubHop'); if (currentSelectedSubHop_json) { try { const parsed = JSON.parse(currentSelectedSubHop_json); hospId = parsed?.id; } catch (e) { hospId = undefined; } } return { hospId, userId }; }; const formatMsgTime = (val: any) => { if (!val) return ''; const d = new Date(val); if (Number.isNaN(d.getTime())) return ''; return d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g, '-'); }; const normalizeMessageList = (payload: any): any[] => { if (Array.isArray(payload)) return payload; if (Array.isArray(payload?.data)) return payload.data; if (Array.isArray(payload?.data?.records)) return payload.data.records; if (Array.isArray(payload?.records)) return payload.records; return []; }; const defaultDateRange = () => { // 默认拉取近30天,避免固定日期导致查不到消息 const end = new Date(); const start = new Date(); start.setDate(end.getDate() - 30); const pad2 = (n: number) => `${n}`.padStart(2, '0'); const fmt = (d: Date) => `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`; return { receiveBeginTime: fmt(start), receiveEndTime: fmt(end) }; }; const pickMsgId = (item: any): string | number | undefined => { if (!item) return undefined; return item?.id ?? item?.messageId ?? item?.msgId; }; const renderNotificationItems = (list: any[], isUnread: boolean) => { if (!list || list.length === 0) { return (
); } return (
{list.map((item, index) => { const time = formatMsgTime(item?.receiveTime || item?.createDate || item?.createTime || item?.resolveTime); return (
{item?.title || item?.recordTitle || '消息通知'}
{item?.content || item?.description || '您有一条消息,请及时查看'}
{time}
); })}
); }; const fetchNotifications = async (tab: 'unread' | 'read') => { const { hospId } = getBaseParams(); if (!hospId) return; const status = tab === 'unread' ? 0 : 1; setNotificationLoading(true); try { const { receiveBeginTime, receiveEndTime } = defaultDateRange(); const resp = await getMessageList({ status, systemId: KcimCenterSysId, receiveBeginTime, receiveEndTime, resolveBeginTime: '', resolveEndTime: '', }); const list = normalizeMessageList(resp); setNotificationList((prev) => ({ ...prev, [tab === 'unread' ? 'unread' : 'read']: list })); if (tab === 'unread') { setUnreadCount(list.length); } } finally { setNotificationLoading(false); } }; const markAllUnreadAsRead = async () => { isClickInside = true; if (notificationBatchLoading) return; const msgIds = notificationList.unread .map(pickMsgId) .filter((id): id is string | number => id !== undefined && id !== null && `${id}`.length > 0); if (msgIds.length === 0) { message.info('暂无可标记的未读消息'); return; } setNotificationBatchLoading(true); try { await batchMessage({ msgIds, operation: 'read', systemId: KcimCenterSysId, }); setUnreadCount(0); message.success('已全部标记为已读'); await fetchNotifications('unread'); await fetchNotifications('read'); } catch (e) { message.error('标记已读失败,请稍后重试'); } finally { setNotificationBatchLoading(false); } }; useEffect(() => { if (process.env.NODE_ENV === 'development') { // 开发环境暂不建立 WebSocket 连接,避免本地代理异常导致 dev server 崩溃 return; } const { hospId, userId } = getBaseParams(); if (!hospId || !userId) return; const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const host = window.location.host; // 统一走 /gateway,与 HTTP 请求保持一致,便于本地代理转发 let token = ''; try { const storageUserData = localStorage.getItem('userData'); if (storageUserData) { const parsed = JSON.parse(storageUserData); token = parsed?.token || ''; } else if (initialState?.userData?.token) { token = initialState.userData.token; } } catch (_) { token = ''; } const tokenQuery = token ? `?token=${encodeURIComponent(token)}` : ''; const wsUrl = `${protocol}//${host}/ws-center/centerSys/websocket/unread-count/${hospId}/${userId}${tokenQuery}`; const ws = new WebSocket(wsUrl); notificationWsRef.current = ws; ws.onmessage = (evt) => { let count = 0; try { const payload = JSON.parse(evt.data); if (typeof payload === 'number') { count = payload; } else if (typeof payload?.unreadCount === 'number') { count = payload.unreadCount; } else if (typeof payload?.count === 'number') { count = payload.count; } else if (typeof payload?.data === 'number') { count = payload.data; } } catch (e) { const num = Number(evt.data); if (!Number.isNaN(num)) count = num; } setUnreadCount(count); }; ws.onerror = () => { setUnreadCount(0); }; ws.onclose = () => { setUnreadCount(0); }; return () => { ws.close(); notificationWsRef.current = null; }; }, [initialState?.userData?.userId]); const openNav = () => { isClickInside = true; setIfOpenPannel(!ifOpenPannel); }; const moreItemClickHandle = (systemData: TopBar.Tab) => { //点击更多应用时 _systemTabClickHandle(systemData); const temp = onTabSystemTabs[onTabSystemTabs.length - 1]; const _onTabSystemTabs = [...onTabSystemTabs]; const b = _onTabSystemTabs.filter((a) => (a.systemId ? a.systemId : a.menuId) != (temp.systemId ? temp.systemId : temp.menuId)); set_onTabSystemTabs([...b, systemData]); set_onTabSystemTabs_hide([...onTabSystemTabs_hide.filter((a) => (a.systemId ? a.systemId : a.menuId) != (systemData.systemId ? systemData.systemId : systemData.menuId)), temp]); }; const reSetNav = (_panelData: TopBar.PanelData[], cur: TopBar.Tab) => { if (!(_panelData.length > 0)) return; if (JSON.stringify(cur) != '{}') { let blockIndex = 0; let channelIndex = 0; const _currentSelectedTabFromLocal = cur; one: for (let index = 0; index < _panelData.length; index++) { blockIndex = index; if (_panelData[index] && _panelData[index].child) { two: for (let i = 0; i < _panelData[index].child.length; i++) { channelIndex = i; if (_panelData && _panelData.length > 0) { const _systems = _panelData[index].child[i].child; if (_systems && _systems.length > 0) { for (let k = 0; k < _systems.length; k++) { if (_systems[k].menuId == _currentSelectedTabFromLocal.menuId) { set_currentActivedBlockIndex(blockIndex); // set_pageTitle(_panelData[blockIndex].child[channelIndex].name); //设置体系标题 setInitialState((s: any) => ({ ...s, pageTitle: _panelData[blockIndex].child[channelIndex].name })); break one; } } } } } } } if (_panelData && _panelData.length > 0 && _panelData[blockIndex].child) { setSystemTabs(_panelData[blockIndex].child[channelIndex].child); //恢复体系列表 setCurrentSelectedTab(_currentSelectedTabFromLocal); localStorage.setItem('currentSelectedTab', JSON.stringify(_currentSelectedTabFromLocal)); setInitialState((s) => ({ ...s, currentSelectedSys: _currentSelectedTabFromLocal } as any)); } // console.log({_currentSelectedTabFromLocal,location}); const { pathname } = location; if (pathname.indexOf(_currentSelectedTabFromLocal.path) == -1) { history.push(_currentSelectedTabFromLocal.path); } //_systemTabClickHandle(_currentSelectedTabFromLocal); //恢复选中的tab } }; const [hideTimer, setHideTimer] = useState(null); const handleTabMoreMouseLeave = () => { // 设置一个延时器,在一段时间后隐藏morePannel const timer = setTimeout(() => { set_showMoreTabPannel(false); }, 300); // 例如延迟300毫秒 setHideTimer(timer); }; const handleMorePannelMouseEnter = () => { // 清除延时器,防止morePannel隐藏 if (hideTimer) { clearTimeout(hideTimer); setHideTimer(null); } }; const handleMorePannelMouseLeave = () => { set_showMoreTabPannel(false); }; const updateToken = async () => { const account = localStorage.getItem('account') as string; const hospSign = localStorage.getItem('hospSign'); const data = { account, password: password, hospSign, }; const resp = await updateTokenReq(data); if (resp) { set_password(undefined); set_tokenUpdateModalVisible(false); window.location.reload(); } }; const switchGroupHandle = async (group: any) => { const resp = await switchOrgReq(group.orgId, group.orgName); if (resp) { set_currentActivedGroup(group); set_showGroupList(false); const currentPath = window.location.pathname; window.history.pushState(null, '', '/'); setTimeout(() => { window.history.pushState(null, '', currentPath); }, 100); } }; useEffect(() => { if (currentSelectedTabFromLocal) { reSetNav(navData, currentSelectedTabFromLocal); } set_panelData(navData); }, [navData]); useEffect(() => { if (currentTab) reSetNav(panelData, currentTab); }, [currentTab]); useEffect(() => { setInitialState((s: any) => ({ ...s, pageTitle: topBarTitle })); }, [topBarTitle]); useEffect(() => { if (systemTabs.length > 5) { //set_onTabSystemTabs(tabs); let _onTabSystemTabs: any[] = []; let _set_onTabSystemTabs_hide: any[] = []; systemTabs.forEach((a, index) => { if (index <= 4) { _onTabSystemTabs.push(a); } else { if (a.systemId == currentSelectedTabFromLocal.systemId) { const _temp = _onTabSystemTabs[_onTabSystemTabs.length - 1]; _onTabSystemTabs.pop(); _onTabSystemTabs.push(a); _set_onTabSystemTabs_hide.push(_temp); } else { _set_onTabSystemTabs_hide.push(a); } } }); set_onTabSystemTabs(_onTabSystemTabs); set_onTabSystemTabs_hide(_set_onTabSystemTabs_hide); } else { set_onTabSystemTabs(systemTabs); } }, [systemTabs]); useEffect(() => { if (initialState?.userInfo) { const { orgInfo = [] } = initialState.userInfo; if (orgInfo && orgInfo.length > 0) { const defaultGroup = orgInfo.filter((a: any) => a.defaultFlag); set_groupList(orgInfo); set_currentActivedGroup(defaultGroup.length > 0 ? defaultGroup[0] : undefined); } } else { console.error('initialState or userInfo is null/undefined'); } }, [initialState]); useEffect(() => { // _systemTabClickHandle(currentSelectedTabFromLocal); //恢复选中的tab const handleClickOutside = (event: { target: any }) => { if (!isClickInside && PannelRef.current && !PannelRef.current.contains(event.target)) { setIfOpenPannel(false); } if (!isClickInside && GroupListWrapperRef.current && !GroupListWrapperRef.current.contains(event.target)) { set_showGroupList(false); } if (!isClickInside && notificationPanelRef.current && !notificationPanelRef.current.contains(event.target)) { setNotificationModalVisible(false); } isClickInside = false; }; document.addEventListener('click', handleClickOutside); // 事件监听器的函数定义 const handleStorageChange = (e: any) => { if (e.key === 'tokenExpired') { set_tokenUpdateModalVisible(true); } }; // 添加事件监听器 window.addEventListener('removeLocalItemEvent', handleStorageChange); // 返回的函数用于在组件卸载时移除事件监听器 return () => { window.removeEventListener('removeLocalItemEvent', handleStorageChange); document.removeEventListener('click', handleClickOutside); }; }, []); return (
e.stopPropagation()}>
登录超时锁定
{userData?.name}
set_password(e.target.value)} value={password} className="input" autoComplete="off" onKeyDown={(e) => e.key === 'Enter' && updateToken()} />
updateToken()}> 解锁
logoutHandle()}>退出登录
{logo && goToHome()} />}
openNav()}>
goSystemIndex(pageTitle)} > {initialState?.pageTitle}
<> {/** * 已打开的tab */}
{onTabSystemTabs && onTabSystemTabs.map((item, index) => (
_systemTabClickHandle(item)}>
{item.name}
closeTabHandle(item, e)} />
))}
{systemTabs.length > 5 && (
set_showMoreTabPannel(true)} onMouseLeave={handleTabMoreMouseLeave}> {showMoreTabPannel && (
{onTabSystemTabs_hide.map((item, index) => { return (
moreItemClickHandle(item)}> {/* */} {item.name} {/* */}
); })}
)}
)}
{ isClickInside = true; setNotificationModalVisible(!notificationModalVisible); if (!notificationModalVisible) { fetchNotifications(notificationTab); } }} > 0} color="#ff4d4f">
{groupList.length > 0 && (
{ isClickInside = true; set_showGroupList(true); }} >
)} {showGroupList && (
{groupList.map((a, index) => (
switchGroupHandle(a)}> {a.orgName}
))}
)}
} color="#fff" onOpenChange={(visible) => setArrowRotate(visible)}>
{localStorage.getItem('hospAbbreviation')} {userData?.name}
{/* */}
{ifOpenPannel && (
e.stopPropagation()}>
{panelData.map((item, index) => { return (
set_currentActivedBlockIndex(index)}> {/* */}
{item.name}
); })}
setIfOpenPannel(false)}>
{panelData.length > 0 && panelData[currentActivedBlockIndex] && panelData[currentActivedBlockIndex].child && panelData[currentActivedBlockIndex].child.map((item, index: number) => { return (
goChannelIndex(item)}> {item.name}
{item.child && item.child.length > 0 && item.child.map((val, i: number) => { return (
_systemListClickHandle(val, currentActivedBlockIndex, index, i)} > {val.name}
); })}
); })}
)} {notificationModalVisible && (
e.stopPropagation()}>
消息记录 { isClickInside = true; setNotificationModalVisible(false); }} />
{ const tab = key === 'read' ? 'read' : 'unread'; setNotificationTab(tab); fetchNotifications(tab); }} tabBarExtraContent={ 0 ? 'mark-all' : 'mark-all disabled'} onClick={(e) => { e.stopPropagation(); if (notificationTab !== 'unread') return; if (notificationList.unread.length === 0) return; markAllUnreadAsRead(); }} > {notificationBatchLoading ? '标记中...' : '全部已读'} } items={[ { key: 'unread', label: `未读(${notificationList.unread.length})`, children: ( {renderNotificationItems(notificationList.unread, true)} ), }, { key: 'read', label: `消息`, children: ( {renderNotificationItems(notificationList.read, false)} ), }, ]} />
)}
); }; export default TopBar;