_layout.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641
  1. /*
  2. * @Author: your name
  3. * @Date: 2022-01-06 15:25:39
  4. * @LastEditTime: 2025-05-22 14:30:28
  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/platform/_layout.tsx
  8. */
  9. import { IRouteComponentProps, useModel, Helmet } from 'umi';
  10. // import { CrownOutlined, UserOutlined, SmileOutlined } from '@ant-design/icons';
  11. import ProLayout, { PageContainer } from '@ant-design/pro-layout';
  12. import { getPlatformMenu, getSpecifyMenuDetail } from '@/service/menu';
  13. import './index.less';
  14. import { TreeItemType } from '@/utils';
  15. import { SpacicalPageParamsType } from '@/typings';
  16. import { Key, useEffect, useRef, useState } from 'react';
  17. import Icon, { FileOutlined, FolderOutlined, createFromIconfontCN } from '@ant-design/icons';
  18. import { getAllParams } from '@/service';
  19. import '../../../public/zhongtaiA';
  20. import ResizableContainer from '@/components/ResizableContainer';
  21. import { Menu } from 'antd';
  22. import EmptyPage from '@/components/emptyPage';
  23. const IconFont = createFromIconfontCN({
  24. scriptUrl: '',
  25. });
  26. interface TransformResult {
  27. newTree: any[];
  28. firstLeafNode: any | null;
  29. firstLeafNodePath: any[];
  30. }
  31. function transformTree(tree: any[]): TransformResult {
  32. let firstLeafNode: any | null = null;
  33. let firstLeafNodePath: any[] = [];
  34. function traverse(node: any, path: any[]): any {
  35. const newNode: any = {
  36. ...node,
  37. label: node.name,
  38. };
  39. const newPath = [...path, newNode];
  40. if (node.children && node.children.length > 0) {
  41. newNode.children = node.children.map((child: any) => traverse(child, newPath));
  42. } else {
  43. if (!firstLeafNode) {
  44. firstLeafNode = newNode;
  45. firstLeafNodePath = newPath;
  46. }
  47. }
  48. // Remove children property if it is an empty array
  49. if (node.children && node.children.length === 0) {
  50. newNode.children = null;
  51. }
  52. return newNode;
  53. }
  54. const newTree = tree.map((node) => traverse(node, []));
  55. return { newTree, firstLeafNode, firstLeafNodePath };
  56. }
  57. const findItemByKey: any = (tree: any[], key: string, keyName: string) => {
  58. for (const node of tree) {
  59. if (node[`${keyName}`] === key) {
  60. return node;
  61. }
  62. if (node.children) {
  63. const result = findItemByKey(node.children, key, keyName);
  64. if (result) {
  65. return result;
  66. }
  67. }
  68. }
  69. return null;
  70. };
  71. // 权限检查函数
  72. function checkAccess(menu: any[], pathname: string) {
  73. const matchPath = (path: any, target: string) => {
  74. // 如果路径以 /platform 开头,则需要完整匹配
  75. if (target.startsWith('/platform')) {
  76. return path === target; // 完整匹配
  77. }
  78. // 否则,执行前缀匹配逻辑
  79. return target.startsWith(path);
  80. };
  81. for (const item of menu) {
  82. // 如果匹配成功,则立即返回 true
  83. if (matchPath(item.path, pathname)) {
  84. return true;
  85. }
  86. // 递归检查 item.child 的情况
  87. if (item.child) {
  88. const hasAccess = checkAccess(item.child, pathname);
  89. if (hasAccess) {
  90. return true;
  91. }
  92. }
  93. // 递归检查 item.children 的情况
  94. if (item.children) {
  95. const hasAccess = checkAccess(item.children, pathname);
  96. if (hasAccess) {
  97. return true;
  98. }
  99. }
  100. }
  101. // 如果所有匹配都失败,返回 false
  102. return false;
  103. }
  104. export default function Layout({ children, location, route, history, match, ...rest }: IRouteComponentProps) {
  105. const { initialState, setInitialState } = useModel('@@initialState');
  106. const [openKeys, set_openKeys] = useState<string[]>([]);
  107. const [selectedKeys, set_selectedKeys] = useState<string[]>([]);
  108. const [emptyPageContent, set_emptyPageContent] = useState('');
  109. const [isShowPageMenu, set_isShowPageMenu] = useState(true);
  110. const [isEmpty, set_isEmpty] = useState(false);
  111. const [isThirdPartySystem, setIsThirdPartySystem] = useState(false);
  112. const [collapsed, set_collapsed] = useState(false);
  113. const [pageUrl, set_pageUrl] = useState<string | undefined>(undefined);
  114. const queryParams = new URLSearchParams(location.search);
  115. const noMenu = queryParams.get('noMenu');
  116. const noTopBar = queryParams.get('noTopbar');
  117. const moreConfig: { menuRender?: any } = {};
  118. const { pathname } = location;
  119. const isMenuClickRef = useRef(false);
  120. const [loading, setLoading] = useState(true);
  121. const [dataLoaded, setDataLoaded] = useState(false);
  122. // 用于记录上一次 isThirdPartySystem 状态
  123. const prevIsThirdPartySystemRef = useRef(isThirdPartySystem);
  124. const setEmptyPageContent = async (menuId: Key) => {
  125. const menuItem = await getSpecifyMenuDetail(menuId);
  126. if (menuItem.isSetupMenu) {
  127. set_isShowPageMenu(false);
  128. }
  129. set_emptyPageContent(menuItem.description);
  130. };
  131. const currentHospName = localStorage.getItem('currentHospName');
  132. const checkPermission = (path: any) => {
  133. const { navData = [], menuData = [] } = initialState || {};
  134. // 检查访问权限
  135. const hasAccess = checkAccess([...navData, ...menuData], path);
  136. return hasAccess;
  137. };
  138. useEffect(() => {
  139. const isShowMenu = localStorage.getItem('isChildShowMenu');
  140. if (isShowMenu) {
  141. set_isShowPageMenu(isShowMenu == 'true');
  142. } else {
  143. set_isShowPageMenu(true);
  144. }
  145. });
  146. useEffect(() => {
  147. if (location.query.menuId) {
  148. setEmptyPageContent(location.query.menuId as string);
  149. }
  150. set_isEmpty(location.query.isEmpty == 'true');
  151. }, [location]);
  152. useEffect(() => {
  153. const handleResize = () => {
  154. if (window.innerWidth <= 1280) {
  155. // 自定义触发宽度为 800px
  156. set_collapsed(true);
  157. } else {
  158. set_collapsed(false);
  159. }
  160. };
  161. // 初始化
  162. handleResize();
  163. // 监听窗口大小变化
  164. window.addEventListener('resize', handleResize);
  165. // 清除监听器
  166. return () => {
  167. window.removeEventListener('resize', handleResize);
  168. };
  169. }, []);
  170. const adjustIframe = () => {
  171. // var ifm:any = document.getElementById("bi_iframe");
  172. // if(ifm){
  173. // ifm.height=document.documentElement.clientHeight;
  174. // ifm.width=document.documentElement.clientWidth;
  175. // }
  176. };
  177. const addTokenToUrl = (url: string, token = '') => {
  178. // 检查 URL 中是否有查询字符串
  179. url = url.trim();
  180. if (url.includes('?')) {
  181. // 检查是否已经有 token 参数
  182. if (!url.includes('token=')) {
  183. // 添加 &token=,因为 URL 中已经有其他查询参数
  184. url += `&token=${token}`;
  185. }
  186. } else {
  187. // URL 中没有查询参数,所以添加 ?token=
  188. url += `?token=${token}`;
  189. }
  190. return url;
  191. };
  192. const { type, url, contentType } = initialState?.currentSelectedSys ?? {};
  193. const userData = localStorage.getItem('userData');
  194. const { token } = JSON.parse(userData as string);
  195. // if (initialState?.currentSelectedSys) {
  196. // const { type, url, contentType } = initialState.currentSelectedSys;
  197. // if (type == 1 && contentType == 6) {
  198. // const userData = localStorage.getItem('userData');
  199. // const { token } = JSON.parse(userData as string);
  200. // return <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={addTokenToUrl(url as string, token)}
  201. // onLoad={() => adjustIframe()}
  202. // ></iframe>;
  203. // }
  204. // }
  205. //临时演示处理
  206. if (location.pathname == '/platform/costMana') {
  207. //临时解决未嵌入成本核算,而实现访问的效果
  208. const getToken = async () => {
  209. const resp = await getAllParams();
  210. if (resp) {
  211. const needItem = resp.filter((a: any) => a.code == '1647777324889935872');
  212. if (needItem.length > 0) {
  213. set_pageUrl(`http://47.96.149.190:8000/platformMana/roleManage?hospSign=dOBHdoPmJgPGnMSH&token=${needItem[0].value}`);
  214. }
  215. }
  216. };
  217. getToken();
  218. return <>{pageUrl && <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={pageUrl} onLoad={() => adjustIframe()}></iframe>};</>;
  219. }
  220. if (location.pathname == '/platform/costMana2') {
  221. //临时解决未嵌入成本核算,而实现访问的效果
  222. const getToken = async () => {
  223. const resp = await getAllParams();
  224. if (resp) {
  225. const needItem = resp.filter((a: any) => a.code == '1733034722981974016');
  226. if (needItem.length > 0) {
  227. set_pageUrl(`http://47.96.149.190:8000/platformMana/roleManage?hospSign=QgqzJxUR5reLh5ER&token=${needItem[0].value}`);
  228. }
  229. }
  230. };
  231. getToken();
  232. return <>{pageUrl && <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={pageUrl} onLoad={() => adjustIframe()}></iframe>};</>;
  233. }
  234. if (location.pathname == '/platform/Budget') {
  235. const pageUrl = `https://test.baokangyiguan.com/index/apiLogin?username=admin&url=budget/index`;
  236. return <>{pageUrl && <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={pageUrl} onLoad={() => adjustIframe()}></iframe>};</>;
  237. }
  238. if (location.pathname == '/platform/DRG') {
  239. const pageUrl = `https://test.baokangyiguan.com/index/apiLogin?username=admin&url=drgs/index`;
  240. return <>{pageUrl && <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={pageUrl} onLoad={() => adjustIframe()}></iframe>};</>;
  241. }
  242. if (location.pathname == '/platform/DIP') {
  243. const pageUrl = `https://test.baokangyiguan.com/index/apiLogin?username=admin&url=dips/index`;
  244. return <>{pageUrl && <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={pageUrl} onLoad={() => adjustIframe()}></iframe>};</>;
  245. }
  246. if (location.pathname == '/platform/baokangCostApp') {
  247. const pageUrl = `https://test.baokangyiguan.com/index/apiLogin?username=admin&url=cost/index `;
  248. return <>{pageUrl && <iframe id={'bi_iframe'} style={{ width: '100%', height: '100%', border: 'none' }} src={pageUrl} onLoad={() => adjustIframe()}></iframe>};</>;
  249. }
  250. // useEffect(() => {
  251. // if (!dataLoaded && initialState) {
  252. // const navData = initialState.navData || [];
  253. // const menuData = initialState.menuData || [];
  254. // if (navData.length > 0 || menuData.length > 0) {
  255. // setDataLoaded(true);
  256. // const { pathname } = history.location;
  257. // const hasAccess = checkPermission(pathname);
  258. // if (!hasAccess) {
  259. // if (!pathname.includes('/platform')) {
  260. // history.push('/noAccess');
  261. // }
  262. // }
  263. // setLoading(false);
  264. // }
  265. // }
  266. // }, [initialState, dataLoaded]);
  267. useEffect(() => {
  268. return history.listen((location) => {
  269. if (!isMenuClickRef.current && dataLoaded) {
  270. const hasPermission = checkPermission(location.pathname);
  271. if (!hasPermission) {
  272. history.push('/noAccess');
  273. }
  274. }
  275. isMenuClickRef.current = false; // 重置状态
  276. });
  277. }, [dataLoaded, initialState]);
  278. useEffect(() => {
  279. if (initialState?.currentSelectedSys) {
  280. const { type, contentType } = initialState.currentSelectedSys;
  281. setIsThirdPartySystem(type === 1 && contentType === 6);
  282. }
  283. }, [initialState?.currentSelectedSys]);
  284. useEffect(() => {
  285. // 只有从第三方系统切回中台时才处理
  286. if (prevIsThirdPartySystemRef.current && !isThirdPartySystem) {
  287. // 取当前选中的菜单 key
  288. if (selectedKeys && selectedKeys.length > 0 && initialState?.menuData) {
  289. // 递归查找菜单项
  290. const findItemByKey = (tree: any[], key: string, keyName: string): any => {
  291. for (const node of tree) {
  292. if (node[`${keyName}`] === key) {
  293. return node;
  294. }
  295. if (node.children) {
  296. const result = findItemByKey(node.children, key, keyName);
  297. if (result) {
  298. return result;
  299. }
  300. }
  301. }
  302. return null;
  303. };
  304. const selectedMenu = findItemByKey(initialState.menuData, selectedKeys[0], 'key');
  305. if (selectedMenu && selectedMenu.path && history.location.pathname !== selectedMenu.path) {
  306. history.push(selectedMenu.path);
  307. }
  308. }
  309. }
  310. prevIsThirdPartySystemRef.current = isThirdPartySystem;
  311. }, [isThirdPartySystem]);
  312. return (
  313. <ProLayout
  314. style={{
  315. height:noTopBar?'100%': 'calc(100vh - 50px)',
  316. }}
  317. //iconfontUrl="//at.alicdn.com/t/font_8d5l8fzk5b87iudi.js"
  318. logoStyle={{
  319. display: 'none',
  320. }}
  321. className="platFormLayout"
  322. contentStyle={{
  323. margin: 0,
  324. }}
  325. location={{}}
  326. headerContentRender={false}
  327. headerRender={false}
  328. siderWidth={isShowPageMenu && noMenu != 'true' ? 200 : 0}
  329. breakpoint={false}
  330. pageTitleRender={false}
  331. disableContentMargin
  332. collapsed={collapsed}
  333. onCollapse={(collapsed) => set_collapsed(collapsed)}
  334. collapsedButtonRender={false}
  335. // collapsedButtonRender={() => {
  336. // return (
  337. // <div
  338. // className="collapsedBtn"
  339. // onClick={() => {
  340. // set_collapsed(collapsed ? false : true);
  341. // }}
  342. // >
  343. // <IconFont style={{ display: 'inline-block' }} className="menuCollapseIcon" type={collapsed ? 'icon-celanzhankai' : 'icon-celanshouqi'} />
  344. // </div>
  345. // );
  346. // }}
  347. menuItemRender={(item, dom) => {
  348. return (
  349. <a
  350. onClick={() => {
  351. isMenuClickRef.current = true; // 标记为菜单点击
  352. history.push(`${item.path}${item.contentType == '4' ? `?isEmpty=true&menuId=${item.key}` : ''}` || '/');
  353. }}
  354. >
  355. {dom}
  356. </a>
  357. );
  358. }}
  359. menuProps={{
  360. openKeys: [...openKeys],
  361. selectedKeys: [...selectedKeys],
  362. onSelect: ({ key, keyPath, selectedKeys, domEvent }) => {
  363. set_selectedKeys(selectedKeys);
  364. localStorage.setItem('selectedKeys', JSON.stringify(selectedKeys));
  365. },
  366. onOpenChange: (keys: string[]) => {
  367. set_openKeys([...keys]);
  368. localStorage.setItem('openKeys', JSON.stringify(keys));
  369. },
  370. }}
  371. menu={isShowPageMenu && !noMenu ? {
  372. autoClose: false,
  373. params:initialState?.currentSelectedSys,
  374. request: async () => {
  375. if (initialState && initialState.currentSelectedSys) {
  376. const { systemId, menuId, path, type, contentType } = initialState.currentSelectedSys;
  377. if (systemId || (menuId && type != 1 && contentType != 6)) {
  378. //只有当存在systemId
  379. const menuData = await getPlatformMenu(systemId || menuId);
  380. let homePage: TreeItemType | undefined;
  381. const getVFromTree = (data: TreeItemType[], key: string) => {
  382. let result: SpacicalPageParamsType[] = [];
  383. function looper(data: TreeItemType[], key: string) {
  384. data.forEach((t) => {
  385. if (t.isHomepage) {
  386. homePage = t;
  387. }
  388. if (t[key] == 1 || t[key] == 2 || t[key] == 3) {
  389. //网易有数页面
  390. result.push({
  391. contentType: t[key],
  392. path: t['path'],
  393. reportId: t['reportId'],
  394. url: t['youshuUrl'],
  395. });
  396. }
  397. if (t[key] == 5) {
  398. //蓝湖静态展示页面
  399. result.push({
  400. contentType: t[key],
  401. path: t['path'],
  402. reportId: t['reportId'],
  403. url: t['softUrl'],
  404. });
  405. }
  406. if (t.children && t.children.length > 0) {
  407. looper(t.children, key);
  408. }
  409. });
  410. }
  411. looper(data, key);
  412. return result;
  413. };
  414. const _menu = getVFromTree(menuData, 'contentType');
  415. setInitialState((t) => ({ ...t, spacicalPageParamsType: _menu, menuData } as any));
  416. if (homePage) {
  417. // console.log({homePage});
  418. set_openKeys([homePage.key]);
  419. set_selectedKeys([homePage.key]);
  420. history.push(homePage.path);
  421. } else {
  422. if (path == '/platform') {
  423. const selectedKeys = localStorage.getItem('selectedKeys');
  424. const openKeys = localStorage.getItem('openKeys');
  425. if (selectedKeys && openKeys) {
  426. const _selectedKeys = JSON.parse(selectedKeys);
  427. const _openKeys = JSON.parse(openKeys);
  428. set_openKeys(_openKeys);
  429. set_selectedKeys(_selectedKeys);
  430. } else {
  431. if (menuData[0].children && menuData[0].children.length > 0) {
  432. const childs = menuData[0].children;
  433. set_openKeys([menuData[0].key]);
  434. set_selectedKeys([childs[0].key]);
  435. localStorage.setItem('openKeys', JSON.stringify([menuData[0].key]));
  436. localStorage.setItem('selectedKeys', JSON.stringify([childs[0].key]));
  437. history.push(`${childs[0].path}`);
  438. } else {
  439. if (menuData[0]) {
  440. set_openKeys([menuData[0].key]);
  441. set_selectedKeys([menuData[0].key]);
  442. localStorage.setItem('openKeys', JSON.stringify([menuData[0].key]));
  443. localStorage.setItem('selectedKeys', JSON.stringify([menuData[0].key]));
  444. history.push(`${menuData[0].path}`);
  445. }
  446. }
  447. }
  448. }
  449. }
  450. const addIcon = (arr: any) =>
  451. arr.map((item: any) =>
  452. item.children
  453. ? {
  454. ...item,
  455. icon: <FolderOutlined />,
  456. children: addIcon(item.children),
  457. }
  458. : {
  459. ...item,
  460. icon: <FileOutlined />,
  461. },
  462. );
  463. const imgNode = (props: any) => {
  464. return <IconFont type="icon-pingtaiguanli" />;
  465. };
  466. const systemSet = (props: any) => {
  467. return <IconFont type="icon-xitongshezhi" />;
  468. };
  469. function addIconToPath(node: any) {
  470. if (node.name == '超管设置') {
  471. node.icon = <Icon component={imgNode} />;
  472. }
  473. if (node.name == '系统设置') {
  474. node.icon = <Icon component={systemSet} />;
  475. }
  476. if (node.children) {
  477. node.children.forEach((child: any) => addIconToPath(child));
  478. }
  479. }
  480. const result = addIcon(menuData);
  481. result.forEach((item: any) => {
  482. addIconToPath(item);
  483. });
  484. return [...result];
  485. }
  486. return [];
  487. }
  488. return [];
  489. },
  490. } : undefined}
  491. menuRender={(props: any, defaultDom) => {
  492. return (
  493. <div style={isShowPageMenu && noMenu != 'true' ? {} : { display: 'inline-block', width: 0, overflow: 'hidden' }}>
  494. <ResizableContainer width={collapsed ? 64 : 200} minWidth={0} maxWidth={600} height={'calc(100vh - 48px)'}>
  495. <div style={{ background: '#fff', height: '100%' }}>
  496. <div className="menuWrapper" style={{ height: 'calc(100% - 40px)', overflowY: 'scroll', overflowX: 'hidden', background: '#fff' }}>
  497. {defaultDom}
  498. </div>
  499. <div
  500. style={{
  501. position: 'absolute',
  502. zIndex: 10,
  503. right: 17,
  504. bottom: 9,
  505. display: 'flex',
  506. justifyContent: 'center',
  507. alignItems: 'center',
  508. cursor: 'pointer',
  509. width: 24,
  510. height: 24,
  511. background: '#fff',
  512. }}
  513. onClick={() => {
  514. set_collapsed(collapsed ? false : true);
  515. }}
  516. >
  517. <IconFont style={{ display: 'inline-block', fontSize: 24 }} className="menuCollapseIcon" type={collapsed ? 'icon-celanzhankai' : 'icon-celanshouqi'} />
  518. </div>
  519. </div>
  520. </ResizableContainer>
  521. </div>
  522. );
  523. }}
  524. onPageChange={(location) => {}}
  525. layout="side"
  526. navTheme="light"
  527. {...moreConfig}
  528. >
  529. {isEmpty && <EmptyPage textContent={emptyPageContent} />}
  530. {!isEmpty && !isThirdPartySystem && (
  531. <PageContainer
  532. className="kcmpPageContainer"
  533. header={{
  534. title: false,
  535. }}
  536. style={{
  537. margin: 16,
  538. marginRight: 8,
  539. }}
  540. >
  541. <Helmet>
  542. <title>{initialState?.customerType == '2' ? currentHospName : '精益管理中台'}</title>
  543. </Helmet>
  544. <div className="page" style={noTopBar?{height:'100%',overflowY: 'scroll'}:{ height: 'calc(100vh - 80px)', overflowY: 'scroll' }}>
  545. {children}
  546. </div>
  547. </PageContainer>
  548. )}
  549. {isThirdPartySystem && (
  550. <iframe
  551. id={'bi_iframe'}
  552. style={{ width: '100%', height: 'calc(100vh - 48px)', border: 'none' }}
  553. src={addTokenToUrl(url as string, token)}
  554. onLoad={() => adjustIframe()}
  555. />
  556. )}
  557. </ProLayout>
  558. );
  559. }