index.tsx 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. /*
  2. * @Author: your name
  3. * @Date: 2021-11-16 09:12:37
  4. * @LastEditTime: 2025-06-13 15:31:59
  5. * @LastEditors: code4eat awesomedema@gmail.com
  6. * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  7. * @FilePath: /KC-MiddlePlatform/src/pages/index/components/topBar/index.tsx
  8. */
  9. import React, { useEffect, useRef, useState } from 'react';
  10. import './style.less';
  11. import { Badge, Input, Tooltip, Tabs, Spin, Empty, Modal, message } from 'antd';
  12. import { LogoutOutlined, SettingOutlined } from '@ant-design/icons';
  13. // import logo from '../../../public/images/kc-logo.png';
  14. import tabCloseIcon from '../../../public/images/tabCloseIcon.png';
  15. import { history, useLocation, useModel } from 'umi';
  16. import { Divider } from 'antd';
  17. import { updateTokenReq } from '@/service/user';
  18. import { logoutHandle } from '@/global';
  19. import { switchOrgReq, getMessageList, batchMessage } from '@/service';
  20. import { KcimCenterSysId } from '@/constant';
  21. interface TopBarType {
  22. onTabChange?: (data: TopBar.Tab[]) => void; //当tab切换时回调
  23. openedTabs: TopBar.Tab[]; //已打开的列表
  24. currentTab?: TopBar.Tab | undefined; //当前tab
  25. userPannelTabClick?: (tag: 'SETTING' | 'LOGOUT' | 'SETUSERINFO') => void;
  26. onCloseTab?: (data: TopBar.Tab) => void;
  27. onTabClick?: (data: TopBar.Tab) => void;
  28. userData?: { name: string;[key: string]: any };
  29. navData: TopBar.PanelData[];
  30. logo?: string;
  31. topBarTitle?: string;
  32. }
  33. let isClickInside = false;
  34. const TopBar: React.FC<TopBarType> = (props) => {
  35. const { onTabChange, userPannelTabClick, onCloseTab, onTabClick, userData, navData, currentTab, logo = undefined, topBarTitle = '欢迎进入医管平台' } = props;
  36. const [systemTabs, setSystemTabs] = useState<TopBar.Tab[]>([]); //已打开的tab
  37. const [currentSelectedTab, setCurrentSelectedTab] = useState<TopBar.Tab>();
  38. const [ifOpenPannel, setIfOpenPannel] = useState(false);
  39. const [arrowRotate, setArrowRotate] = useState(false);
  40. // const [pageTitle, set_pageTitle] = useState('');
  41. const [currentActivedBlockIndex, set_currentActivedBlockIndex] = useState(0);
  42. const [panelData, set_panelData] = useState<TopBar.PanelData[]>([]);
  43. const [onTabSystemTabs, set_onTabSystemTabs] = useState<TopBar.Tab[]>([]); //tab导航可以放下的数量,剩余通过下拉获取
  44. const [onTabSystemTabs_hide, set_onTabSystemTabs_hide] = useState<TopBar.Tab[]>([]); //下拉掩藏的导航
  45. const { initialState, setInitialState } = useModel('@@initialState');
  46. const [tokenUpdateModalVisible, set_tokenUpdateModalVisible] = useState(false);
  47. const location = useLocation();
  48. const [showMoreTabPannel, set_showMoreTabPannel] = useState(false);
  49. const [password, set_password] = useState<string | undefined>(undefined);
  50. const [showGroupList, set_showGroupList] = useState(false);
  51. const [groupList, set_groupList] = useState<any[]>([]);
  52. const [currentActivedGroup, set_currentActivedGroup] = useState<any>(undefined);
  53. const [unreadCount, setUnreadCount] = useState<number>(0);
  54. const [notificationModalVisible, setNotificationModalVisible] = useState(false);
  55. const [notificationTab, setNotificationTab] = useState<'unread' | 'read'>('unread');
  56. const [notificationLoading, setNotificationLoading] = useState(false);
  57. const [notificationBatchLoading, setNotificationBatchLoading] = useState(false);
  58. const [notificationList, setNotificationList] = useState<{ unread: any[]; read: any[] }>({
  59. unread: [],
  60. read: [],
  61. });
  62. const localSavedTab = localStorage.getItem('currentSelectedTab');
  63. const currentSelectedTabFromLocal = localSavedTab ? JSON.parse(localSavedTab) : {};
  64. const PannelRef = useRef<any>(null);
  65. const GroupListWrapperRef = useRef<any>(null);
  66. const notificationWsRef = useRef<WebSocket | null>(null);
  67. const notificationPanelRef = useRef<HTMLDivElement | null>(null);
  68. const _systemTabClickHandle = (item: TopBar.Tab) => {
  69. //导航栏tab点击
  70. // console.log('_systemTabClickHandle',item);
  71. onTabClick && onTabClick(item);
  72. if (item.type != 1 && item.contentType != 7) {
  73. localStorage.setItem('currentSelectedTab', JSON.stringify(item));
  74. setCurrentSelectedTab(item);
  75. }
  76. };
  77. const _systemListClickHandle = (data: TopBar.Tab, currentActivedBlockIndex: number, index: number, i: number) => {
  78. //导航栏系统菜单列表点击回调
  79. if (currentSelectedTab?.menuId == data.menuId) return;
  80. //临时保存衣打开过的菜单
  81. const t = localStorage.getItem('visitedTabs');
  82. if (t) {
  83. let visitedTabs = JSON.parse(t);
  84. // 确保 visitedTabs 是数组类型,防止 findIndex 报错
  85. if (Array.isArray(visitedTabs)) {
  86. let index = visitedTabs.findIndex((t: TopBar.Tab) => t.menuId == data.menuId);
  87. if (index == -1) {
  88. visitedTabs.push(data);
  89. localStorage.setItem('visitedTabs', JSON.stringify(visitedTabs));
  90. }
  91. } else {
  92. // 如果不是数组,重新初始化
  93. const newVisitedTabs = [data];
  94. localStorage.setItem('visitedTabs', JSON.stringify(newVisitedTabs));
  95. }
  96. } else {
  97. localStorage.setItem('visitedTabs', JSON.stringify([data]));
  98. }
  99. _systemTabClickHandle(data); //触发一次tab点击
  100. // set_pageTitle(panelData[currentActivedBlockIndex].child[index].name);
  101. setInitialState((s: any) => ({ ...s, pageTitle: panelData[currentActivedBlockIndex].child[index].name }));
  102. if (panelData[currentActivedBlockIndex].child[index].child) {
  103. //console.log([...panelData[currentActivedBlockIndex].child[index].child])
  104. setSystemTabs([...panelData[currentActivedBlockIndex].child[index].child]);
  105. }
  106. setIfOpenPannel(false);
  107. };
  108. const _userPannelTabClick = (tag: 'SETTING' | 'LOGOUT') => {
  109. //用户菜单tab点击回调
  110. userPannelTabClick && userPannelTabClick(tag);
  111. };
  112. const closeTabHandle = (item: TopBar.Tab, e: React.MouseEvent) => {
  113. e.stopPropagation();
  114. let _systemTabs = [...systemTabs];
  115. let delIndex = -1;
  116. const filtered: any[] = _systemTabs.filter((t, index) => {
  117. if (t.menuId == item.menuId) {
  118. delIndex = index;
  119. }
  120. return t.menuId != item.menuId;
  121. });
  122. setSystemTabs([...filtered]);
  123. if (delIndex != 0) {
  124. _systemTabClickHandle(_systemTabs[delIndex - 1]); //自动切换为前一个tab
  125. }
  126. onTabChange && onTabChange(filtered);
  127. onCloseTab && onCloseTab(item);
  128. };
  129. const UserPannel = () => {
  130. return (
  131. <div className="userPannel">
  132. <div className="userPannelTab" onClick={() => _userPannelTabClick('SETTING')}>
  133. <SettingOutlined />
  134. <span>设置</span>
  135. </div>
  136. <div className="userPannelTab" onClick={() => _userPannelTabClick('LOGOUT')}>
  137. <LogoutOutlined />
  138. <span>退出</span>
  139. </div>
  140. </div>
  141. );
  142. };
  143. const goChannelIndex = (menuData: any) => {
  144. setIfOpenPannel(false);
  145. onTabClick && onTabClick(menuData);
  146. };
  147. const goToHome = () => {
  148. const go = () => {
  149. history.replace('/index');
  150. setSystemTabs([]); //清空tab导航
  151. onTabChange && onTabChange([]);
  152. setCurrentSelectedTab(undefined);
  153. setInitialState((s: any) => ({ ...s, pageTitle: topBarTitle }));
  154. setIfOpenPannel(false);
  155. console.log('goHome');
  156. localStorage.removeItem('currentSelectedTab');
  157. localStorage.removeItem('selectedKeys');
  158. // localStorage.removeItem('visitedTabs');
  159. localStorage.removeItem('openKeys');
  160. };
  161. const currentSelectedSubHop_json = localStorage.getItem('currentSelectedSubHop');
  162. if (currentSelectedSubHop_json) {
  163. const currentSelectedSubHop = JSON.parse(currentSelectedSubHop_json);
  164. if (currentSelectedSubHop.loadType) {
  165. return false;
  166. } else {
  167. go();
  168. }
  169. } else {
  170. go();
  171. }
  172. };
  173. const goSystemIndex = (name: string) => {
  174. if (panelData[currentActivedBlockIndex]) {
  175. const result: TopBar.TypeBlock[] = panelData[currentActivedBlockIndex].child.filter((t) => t.name == name);
  176. if (result.length > 0) {
  177. _systemTabClickHandle(Object.assign(result[0]));
  178. // history.push(result[0].path)
  179. }
  180. }
  181. };
  182. const getBaseParams = () => {
  183. const userId = initialState?.userData?.userId;
  184. let hospId: any;
  185. const currentSelectedSubHop_json = localStorage.getItem('currentSelectedSubHop');
  186. if (currentSelectedSubHop_json) {
  187. try {
  188. const parsed = JSON.parse(currentSelectedSubHop_json);
  189. hospId = parsed?.id;
  190. } catch (e) {
  191. hospId = undefined;
  192. }
  193. }
  194. return { hospId, userId };
  195. };
  196. const formatMsgTime = (val: any) => {
  197. if (!val) return '';
  198. const d = new Date(val);
  199. if (Number.isNaN(d.getTime())) return '';
  200. return d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }).replace(/\//g, '-');
  201. };
  202. const normalizeMessageList = (payload: any): any[] => {
  203. if (Array.isArray(payload)) return payload;
  204. if (Array.isArray(payload?.data)) return payload.data;
  205. if (Array.isArray(payload?.data?.records)) return payload.data.records;
  206. if (Array.isArray(payload?.records)) return payload.records;
  207. return [];
  208. };
  209. const defaultDateRange = () => {
  210. // 默认拉取近30天,避免固定日期导致查不到消息
  211. const end = new Date();
  212. const start = new Date();
  213. start.setDate(end.getDate() - 30);
  214. const pad2 = (n: number) => `${n}`.padStart(2, '0');
  215. const fmt = (d: Date) =>
  216. `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())} ${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
  217. return { receiveBeginTime: fmt(start), receiveEndTime: fmt(end) };
  218. };
  219. const pickMsgId = (item: any): string | number | undefined => {
  220. if (!item) return undefined;
  221. return item?.id ?? item?.messageId ?? item?.msgId;
  222. };
  223. const renderNotificationItems = (list: any[], isUnread: boolean) => {
  224. if (!list || list.length === 0) {
  225. return (
  226. <div className="notification-empty">
  227. <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无消息" />
  228. </div>
  229. );
  230. }
  231. return (
  232. <div className="notification-list">
  233. {list.map((item, index) => {
  234. const time = formatMsgTime(item?.receiveTime || item?.createDate || item?.createTime || item?.resolveTime);
  235. return (
  236. <div className="notification-item" key={pickMsgId(item) || index}>
  237. <div className={isUnread ? 'notification-item-icon unread' : 'notification-item-icon read'} />
  238. <div className="notification-item-content">
  239. <div className="notification-item-title">{item?.title || item?.recordTitle || '消息通知'}</div>
  240. <div className="notification-item-desc">{item?.content || item?.description || '您有一条消息,请及时查看'}</div>
  241. </div>
  242. <div className="notification-item-time">{time}</div>
  243. </div>
  244. );
  245. })}
  246. </div>
  247. );
  248. };
  249. const fetchNotifications = async (tab: 'unread' | 'read') => {
  250. const { hospId } = getBaseParams();
  251. if (!hospId) return;
  252. const status = tab === 'unread' ? 0 : 1;
  253. setNotificationLoading(true);
  254. try {
  255. const { receiveBeginTime, receiveEndTime } = defaultDateRange();
  256. const resp = await getMessageList({
  257. status,
  258. systemId: KcimCenterSysId,
  259. receiveBeginTime,
  260. receiveEndTime,
  261. resolveBeginTime: '',
  262. resolveEndTime: '',
  263. });
  264. const list = normalizeMessageList(resp);
  265. setNotificationList((prev) => ({ ...prev, [tab === 'unread' ? 'unread' : 'read']: list }));
  266. if (tab === 'unread') {
  267. setUnreadCount(list.length);
  268. }
  269. } finally {
  270. setNotificationLoading(false);
  271. }
  272. };
  273. const markAllUnreadAsRead = async () => {
  274. isClickInside = true;
  275. if (notificationBatchLoading) return;
  276. const msgIds = notificationList.unread
  277. .map(pickMsgId)
  278. .filter((id): id is string | number => id !== undefined && id !== null && `${id}`.length > 0);
  279. if (msgIds.length === 0) {
  280. message.info('暂无可标记的未读消息');
  281. return;
  282. }
  283. setNotificationBatchLoading(true);
  284. try {
  285. await batchMessage({
  286. msgIds,
  287. operation: 'read',
  288. systemId: KcimCenterSysId,
  289. });
  290. setUnreadCount(0);
  291. message.success('已全部标记为已读');
  292. await fetchNotifications('unread');
  293. await fetchNotifications('read');
  294. } catch (e) {
  295. message.error('标记已读失败,请稍后重试');
  296. } finally {
  297. setNotificationBatchLoading(false);
  298. }
  299. };
  300. useEffect(() => {
  301. if (process.env.NODE_ENV === 'development') {
  302. // 开发环境暂不建立 WebSocket 连接,避免本地代理异常导致 dev server 崩溃
  303. return;
  304. }
  305. const { hospId, userId } = getBaseParams();
  306. if (!hospId || !userId) return;
  307. const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  308. const host = window.location.host;
  309. // 统一走 /gateway,与 HTTP 请求保持一致,便于本地代理转发
  310. let token = '';
  311. try {
  312. const storageUserData = localStorage.getItem('userData');
  313. if (storageUserData) {
  314. const parsed = JSON.parse(storageUserData);
  315. token = parsed?.token || '';
  316. } else if (initialState?.userData?.token) {
  317. token = initialState.userData.token;
  318. }
  319. } catch (_) {
  320. token = '';
  321. }
  322. const tokenQuery = token ? `?token=${encodeURIComponent(token)}` : '';
  323. const wsUrl = `${protocol}//${host}/ws-center/centerSys/websocket/unread-count/${hospId}/${userId}${tokenQuery}`;
  324. const ws = new WebSocket(wsUrl);
  325. notificationWsRef.current = ws;
  326. ws.onmessage = (evt) => {
  327. let count = 0;
  328. try {
  329. const payload = JSON.parse(evt.data);
  330. if (typeof payload === 'number') {
  331. count = payload;
  332. } else if (typeof payload?.unreadCount === 'number') {
  333. count = payload.unreadCount;
  334. } else if (typeof payload?.count === 'number') {
  335. count = payload.count;
  336. } else if (typeof payload?.data === 'number') {
  337. count = payload.data;
  338. }
  339. } catch (e) {
  340. const num = Number(evt.data);
  341. if (!Number.isNaN(num)) count = num;
  342. }
  343. setUnreadCount(count);
  344. };
  345. ws.onerror = () => {
  346. setUnreadCount(0);
  347. };
  348. ws.onclose = () => {
  349. setUnreadCount(0);
  350. };
  351. return () => {
  352. ws.close();
  353. notificationWsRef.current = null;
  354. };
  355. }, [initialState?.userData?.userId]);
  356. const openNav = () => {
  357. isClickInside = true;
  358. setIfOpenPannel(!ifOpenPannel);
  359. };
  360. const moreItemClickHandle = (systemData: TopBar.Tab) => {
  361. //点击更多应用时
  362. _systemTabClickHandle(systemData);
  363. const temp = onTabSystemTabs[onTabSystemTabs.length - 1];
  364. const _onTabSystemTabs = [...onTabSystemTabs];
  365. const b = _onTabSystemTabs.filter((a) => (a.systemId ? a.systemId : a.menuId) != (temp.systemId ? temp.systemId : temp.menuId));
  366. set_onTabSystemTabs([...b, systemData]);
  367. set_onTabSystemTabs_hide([...onTabSystemTabs_hide.filter((a) => (a.systemId ? a.systemId : a.menuId) != (systemData.systemId ? systemData.systemId : systemData.menuId)), temp]);
  368. };
  369. const reSetNav = (_panelData: TopBar.PanelData[], cur: TopBar.Tab) => {
  370. if (!(_panelData.length > 0)) return;
  371. if (JSON.stringify(cur) != '{}') {
  372. let blockIndex = 0;
  373. let channelIndex = 0;
  374. const _currentSelectedTabFromLocal = cur;
  375. one: for (let index = 0; index < _panelData.length; index++) {
  376. blockIndex = index;
  377. if (_panelData[index] && _panelData[index].child) {
  378. two: for (let i = 0; i < _panelData[index].child.length; i++) {
  379. channelIndex = i;
  380. if (_panelData && _panelData.length > 0) {
  381. const _systems = _panelData[index].child[i].child;
  382. if (_systems && _systems.length > 0) {
  383. for (let k = 0; k < _systems.length; k++) {
  384. if (_systems[k].menuId == _currentSelectedTabFromLocal.menuId) {
  385. set_currentActivedBlockIndex(blockIndex);
  386. // set_pageTitle(_panelData[blockIndex].child[channelIndex].name); //设置体系标题
  387. setInitialState((s: any) => ({ ...s, pageTitle: _panelData[blockIndex].child[channelIndex].name }));
  388. break one;
  389. }
  390. }
  391. }
  392. }
  393. }
  394. }
  395. }
  396. if (_panelData && _panelData.length > 0 && _panelData[blockIndex].child) {
  397. setSystemTabs(_panelData[blockIndex].child[channelIndex].child); //恢复体系列表
  398. setCurrentSelectedTab(_currentSelectedTabFromLocal);
  399. localStorage.setItem('currentSelectedTab', JSON.stringify(_currentSelectedTabFromLocal));
  400. setInitialState((s) => ({ ...s, currentSelectedSys: _currentSelectedTabFromLocal } as any));
  401. }
  402. // console.log({_currentSelectedTabFromLocal,location});
  403. const { pathname } = location;
  404. if (pathname.indexOf(_currentSelectedTabFromLocal.path) == -1) {
  405. history.push(_currentSelectedTabFromLocal.path);
  406. }
  407. //_systemTabClickHandle(_currentSelectedTabFromLocal); //恢复选中的tab
  408. }
  409. };
  410. const [hideTimer, setHideTimer] = useState<null | any>(null);
  411. const handleTabMoreMouseLeave = () => {
  412. // 设置一个延时器,在一段时间后隐藏morePannel
  413. const timer = setTimeout(() => {
  414. set_showMoreTabPannel(false);
  415. }, 300); // 例如延迟300毫秒
  416. setHideTimer(timer);
  417. };
  418. const handleMorePannelMouseEnter = () => {
  419. // 清除延时器,防止morePannel隐藏
  420. if (hideTimer) {
  421. clearTimeout(hideTimer);
  422. setHideTimer(null);
  423. }
  424. };
  425. const handleMorePannelMouseLeave = () => {
  426. set_showMoreTabPannel(false);
  427. };
  428. const updateToken = async () => {
  429. const account = localStorage.getItem('account') as string;
  430. const hospSign = localStorage.getItem('hospSign');
  431. const data = {
  432. account,
  433. password: password,
  434. hospSign,
  435. };
  436. const resp = await updateTokenReq(data);
  437. if (resp) {
  438. set_password(undefined);
  439. set_tokenUpdateModalVisible(false);
  440. window.location.reload();
  441. }
  442. };
  443. const switchGroupHandle = async (group: any) => {
  444. const resp = await switchOrgReq(group.orgId, group.orgName);
  445. if (resp) {
  446. set_currentActivedGroup(group);
  447. set_showGroupList(false);
  448. const currentPath = window.location.pathname;
  449. window.history.pushState(null, '', '/');
  450. setTimeout(() => {
  451. window.history.pushState(null, '', currentPath);
  452. }, 100);
  453. }
  454. };
  455. useEffect(() => {
  456. if (currentSelectedTabFromLocal) {
  457. reSetNav(navData, currentSelectedTabFromLocal);
  458. }
  459. set_panelData(navData);
  460. }, [navData]);
  461. useEffect(() => {
  462. if (currentTab) reSetNav(panelData, currentTab);
  463. }, [currentTab]);
  464. useEffect(() => {
  465. setInitialState((s: any) => ({ ...s, pageTitle: topBarTitle }));
  466. }, [topBarTitle]);
  467. useEffect(() => {
  468. if (systemTabs.length > 5) {
  469. //set_onTabSystemTabs(tabs);
  470. let _onTabSystemTabs: any[] = [];
  471. let _set_onTabSystemTabs_hide: any[] = [];
  472. systemTabs.forEach((a, index) => {
  473. if (index <= 4) {
  474. _onTabSystemTabs.push(a);
  475. } else {
  476. if (a.systemId == currentSelectedTabFromLocal.systemId) {
  477. const _temp = _onTabSystemTabs[_onTabSystemTabs.length - 1];
  478. _onTabSystemTabs.pop();
  479. _onTabSystemTabs.push(a);
  480. _set_onTabSystemTabs_hide.push(_temp);
  481. } else {
  482. _set_onTabSystemTabs_hide.push(a);
  483. }
  484. }
  485. });
  486. set_onTabSystemTabs(_onTabSystemTabs);
  487. set_onTabSystemTabs_hide(_set_onTabSystemTabs_hide);
  488. } else {
  489. set_onTabSystemTabs(systemTabs);
  490. }
  491. }, [systemTabs]);
  492. useEffect(() => {
  493. if (initialState?.userInfo) {
  494. const { orgInfo = [] } = initialState.userInfo;
  495. if (orgInfo && orgInfo.length > 0) {
  496. const defaultGroup = orgInfo.filter((a: any) => a.defaultFlag);
  497. set_groupList(orgInfo);
  498. set_currentActivedGroup(defaultGroup.length > 0 ? defaultGroup[0] : undefined);
  499. }
  500. } else {
  501. console.error('initialState or userInfo is null/undefined');
  502. }
  503. }, [initialState]);
  504. useEffect(() => {
  505. // _systemTabClickHandle(currentSelectedTabFromLocal); //恢复选中的tab
  506. const handleClickOutside = (event: { target: any }) => {
  507. if (!isClickInside && PannelRef.current && !PannelRef.current.contains(event.target)) {
  508. setIfOpenPannel(false);
  509. }
  510. if (!isClickInside && GroupListWrapperRef.current && !GroupListWrapperRef.current.contains(event.target)) {
  511. set_showGroupList(false);
  512. }
  513. if (!isClickInside && notificationPanelRef.current && !notificationPanelRef.current.contains(event.target)) {
  514. setNotificationModalVisible(false);
  515. }
  516. isClickInside = false;
  517. };
  518. document.addEventListener('click', handleClickOutside);
  519. // 事件监听器的函数定义
  520. const handleStorageChange = (e: any) => {
  521. if (e.key === 'tokenExpired') {
  522. set_tokenUpdateModalVisible(true);
  523. }
  524. };
  525. // 添加事件监听器
  526. window.addEventListener('removeLocalItemEvent', handleStorageChange);
  527. // 返回的函数用于在组件卸载时移除事件监听器
  528. return () => {
  529. window.removeEventListener('removeLocalItemEvent', handleStorageChange);
  530. document.removeEventListener('click', handleClickOutside);
  531. };
  532. }, []);
  533. return (
  534. <div className="topBar" onClick={(e) => e.stopPropagation()}>
  535. <Modal className="TokenUpdateModal" open={tokenUpdateModalVisible} width={400} title={false} footer={false} closable={false}>
  536. <div className="content">
  537. <div className="title">登录超时锁定</div>
  538. <div className="form">
  539. <div className="avatar">
  540. <img className="avatarImg" src={require('../../../public/images/initAvatar.png')} alt="" />
  541. <img className="suoding" src={require('../../../public/images/suoding.png')} alt="" />
  542. </div>
  543. <div className="name">{userData?.name}</div>
  544. <Input onChange={(e) => set_password(e.target.value)} value={password} className="input" autoComplete="off" onKeyDown={(e) => e.key === 'Enter' && updateToken()} />
  545. <div className="updateBtn" onClick={() => updateToken()}>
  546. 解锁
  547. </div>
  548. <a onClick={() => logoutHandle()}>退出登录</a>
  549. </div>
  550. </div>
  551. </Modal>
  552. <div className="logoWrap">
  553. {logo && <img className="logo" src={logo} onClick={() => goToHome()} />}
  554. <Divider type="vertical" style={{ background: 'white', height: 16, opacity: 0.29, position: 'relative', top: 1, marginLeft: 16, marginRight: 8 }} />
  555. <div className={ifOpenPannel ? 'menu active' : 'menu'} onClick={() => openNav()}>
  556. <img src={require('../../../public/images/menu.png')} alt="" />
  557. </div>
  558. <span
  559. className="systemTitle"
  560. // onClick={() => goSystemIndex(pageTitle)}
  561. >
  562. {initialState?.pageTitle}
  563. </span>
  564. </div>
  565. <div className="userRelaInfoWrap">
  566. <>
  567. {/**
  568. * 已打开的tab
  569. */}
  570. <div className="tabWrap">
  571. {onTabSystemTabs &&
  572. onTabSystemTabs.map((item, index) => (
  573. <div key={index} className={currentSelectedTab?.menuId == item.menuId ? `tab on` : `tab`} onClick={() => _systemTabClickHandle(item)}>
  574. <div className="tabText">{item.name} </div>
  575. <div className="closeIconWrap">
  576. <img src={tabCloseIcon} onClick={(e) => closeTabHandle(item, e)} />
  577. </div>
  578. </div>
  579. ))}
  580. </div>
  581. {systemTabs.length > 5 && (
  582. <div className={showMoreTabPannel ? 'tabMore active' : 'tabMore'} onMouseEnter={() => set_showMoreTabPannel(true)} onMouseLeave={handleTabMoreMouseLeave}>
  583. {showMoreTabPannel && (
  584. <div className="morePannel" onMouseEnter={handleMorePannelMouseEnter} onMouseLeave={handleMorePannelMouseLeave}>
  585. {onTabSystemTabs_hide.map((item, index) => {
  586. return (
  587. <div key={index} className="moreItem" onClick={() => moreItemClickHandle(item)}>
  588. {/* <Tooltip placement="right" title={item.name}> */}
  589. <span>{item.name}</span>
  590. {/* </Tooltip> */}
  591. </div>
  592. );
  593. })}
  594. </div>
  595. )}
  596. </div>
  597. )}
  598. </>
  599. <div
  600. className="notification"
  601. onClick={() => {
  602. isClickInside = true;
  603. setNotificationModalVisible(!notificationModalVisible);
  604. if (!notificationModalVisible) {
  605. fetchNotifications(notificationTab);
  606. }
  607. }}
  608. >
  609. <Badge dot={unreadCount > 0} color="#ff4d4f">
  610. <img className="notificationIcon" src={require('../../../public/images/notificationIcon.png')} />
  611. </Badge>
  612. </div>
  613. <div className="group">
  614. {groupList.length > 0 && (
  615. <Tooltip title={currentActivedGroup?.orgName ?? ''} placement="right">
  616. <div
  617. className="group-switcher"
  618. onClick={() => {
  619. isClickInside = true;
  620. set_showGroupList(true);
  621. }}
  622. >
  623. <img src={require('./images/groupIcon.png')} alt="" />
  624. </div>
  625. </Tooltip>
  626. )}
  627. {showGroupList && (
  628. <div className="group-list-wrapper" ref={GroupListWrapperRef}>
  629. {groupList.map((a, index) => (
  630. <div key={index} className={currentActivedGroup?.orgId === a.orgId ? `group-list actived` : 'group-list'} onClick={() => switchGroupHandle(a)}>
  631. {a.orgName}
  632. </div>
  633. ))}
  634. </div>
  635. )}
  636. </div>
  637. <Tooltip className="topBarTooltip" placement="bottomRight" title={<UserPannel />} color="#fff" onOpenChange={(visible) => setArrowRotate(visible)}>
  638. <div className="user">
  639. <div className="avator">
  640. <img src={userData?.avatarUrl ? userData.avatarUrl : require('../../../public/images/avatar.png')} />
  641. </div>
  642. <div className="info">
  643. <span className="hospName">{localStorage.getItem('hospAbbreviation')}</span>
  644. <span className="name">{userData?.name}</span>
  645. </div>
  646. {/* <img className={arrowRotate ? `arrow on` : `arrow`} src={require('../../../public/images/arrow_white.png')} /> */}
  647. </div>
  648. </Tooltip>
  649. </div>
  650. {ifOpenPannel && (
  651. <div className="panel" ref={PannelRef} onClick={(e) => e.stopPropagation()}>
  652. <div className="left">
  653. {panelData.map((item, index) => {
  654. return (
  655. <div className={currentActivedBlockIndex == index ? `typeBlock active` : `typeBlock`} key={index} onClick={() => set_currentActivedBlockIndex(index)}>
  656. {/* <img className='typeBlockIcon' src={item?.icon} alt="" /> */}
  657. <div className={` typeBlockIcon typeBlockIcon${item.icon}`}></div>
  658. <span className="typeBlockName">{item.name}</span>
  659. </div>
  660. );
  661. })}
  662. </div>
  663. <div className="right">
  664. <div className="panelCloseBtn" onClick={() => setIfOpenPannel(false)}></div>
  665. {panelData.length > 0 &&
  666. panelData[currentActivedBlockIndex] &&
  667. panelData[currentActivedBlockIndex].child &&
  668. panelData[currentActivedBlockIndex].child.map((item, index: number) => {
  669. return (
  670. <div className="row" key={index}>
  671. <img className="channelIcon" src={item.icon ? item.icon : require(`../../../public/images/tongyong_tixi.png`)} alt="" />
  672. <div className="rowDetail">
  673. <div className="channelName" onClick={() => goChannelIndex(item)}>
  674. {item.name}
  675. </div>
  676. <div className="channelList">
  677. {item.child &&
  678. item.child.length > 0 &&
  679. item.child.map((val, i: number) => {
  680. return (
  681. <div
  682. className={currentSelectedTab?.menuId == val.menuId ? 'systemTab on' : 'systemTab'}
  683. key={i}
  684. onClick={() => _systemListClickHandle(val, currentActivedBlockIndex, index, i)}
  685. >
  686. {val.name}
  687. </div>
  688. );
  689. })}
  690. </div>
  691. </div>
  692. </div>
  693. );
  694. })}
  695. </div>
  696. </div>
  697. )}
  698. {notificationModalVisible && (
  699. <div className="notification-panel" ref={notificationPanelRef} onClick={(e) => e.stopPropagation()}>
  700. <div className="notification-header">
  701. <span className="title">消息记录</span>
  702. <img
  703. className="close"
  704. src={require('../../../public/images/cancel_black.png')}
  705. alt=""
  706. onClick={() => {
  707. isClickInside = true;
  708. setNotificationModalVisible(false);
  709. }}
  710. />
  711. </div>
  712. <Tabs
  713. className="notification-tabs"
  714. activeKey={notificationTab}
  715. onChange={(key) => {
  716. const tab = key === 'read' ? 'read' : 'unread';
  717. setNotificationTab(tab);
  718. fetchNotifications(tab);
  719. }}
  720. tabBarExtraContent={
  721. <span
  722. className={notificationTab === 'unread' && notificationList.unread.length > 0 ? 'mark-all' : 'mark-all disabled'}
  723. onClick={(e) => {
  724. e.stopPropagation();
  725. if (notificationTab !== 'unread') return;
  726. if (notificationList.unread.length === 0) return;
  727. markAllUnreadAsRead();
  728. }}
  729. >
  730. {notificationBatchLoading ? '标记中...' : '全部已读'}
  731. </span>
  732. }
  733. items={[
  734. {
  735. key: 'unread',
  736. label: `未读(${notificationList.unread.length})`,
  737. children: (
  738. <Spin spinning={notificationLoading}>
  739. {renderNotificationItems(notificationList.unread, true)}
  740. </Spin>
  741. ),
  742. },
  743. {
  744. key: 'read',
  745. label: `消息`,
  746. children: (
  747. <Spin spinning={notificationLoading}>
  748. {renderNotificationItems(notificationList.read, false)}
  749. </Spin>
  750. ),
  751. },
  752. ]}
  753. />
  754. </div>
  755. )}
  756. </div>
  757. );
  758. };
  759. export default TopBar;