app.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /*
  2. * @Author: code4eat awesomedema@gmail.com
  3. * @Date: 2022-12-14 14:14:32
  4. * @LastEditors: code4eat awesomedema@gmail.com
  5. * @LastEditTime: 2023-08-10 14:01:11
  6. * @FilePath: /BudgetManaSystem/src/app.ts
  7. * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  8. */
  9. // 运行时配置
  10. // 全局初始化数据配置,用于 Layout 用户信息和权限初始化
  11. // 更多信息见文档:https://next.umijs.org/docs/api/runtime-config#getinitialstate
  12. import { AxiosResponse } from '@umijs/max';
  13. import { message, notification } from 'antd';
  14. import type { RequestConfig } from 'umi';
  15. import iconEnum from './menuIcons.js';
  16. import Report from './pages/reports/index';
  17. import Static from './pages/static/index';
  18. import DevicePixelRatio from './utils/devicePixelRatio.js';
  19. import Icon, { createFromIconfontCN, FolderOutlined } from '@ant-design/icons';
  20. import { getPlatformMenu } from '@/services/auth';
  21. import { useState } from 'react';
  22. import './utils/zhongtaiC'
  23. import { RuntimeAntdConfig } from '@umijs/max';
  24. const IconFont = createFromIconfontCN({
  25. scriptUrl: '',
  26. });
  27. // 错误处理方案: 错误类型
  28. enum ErrorShowType {
  29. SILENT = 0,
  30. WARN_MESSAGE = 1,
  31. ERROR_MESSAGE = 2,
  32. NOTIFICATION = 3,
  33. REDIRECT = 9,
  34. }
  35. // 与后端约定的响应数据格式
  36. interface ResponseStructure {
  37. success?: boolean;
  38. code?: string;
  39. data: any;
  40. errorCode?: number;
  41. errorMessage?: string;
  42. showType?: ErrorShowType;
  43. }
  44. interface UserData {
  45. name?: string,
  46. ruleVersion?: string,
  47. token?: string,
  48. userId?: number,
  49. youshuToken?: string
  50. }
  51. export async function getInitialState(): Promise<{
  52. isCollapsed: boolean, spacicalPageParamsType?: any[], userData: UserData,memuData:any[]
  53. }> {
  54. new DevicePixelRatio().init();
  55. return { isCollapsed: false, spacicalPageParamsType: [], userData: {},memuData:[] };
  56. }
  57. export const request: RequestConfig = {
  58. // 统一的请求设定
  59. timeout: 100000000,
  60. headers: { 'X-Requested-With': 'XMLHttpRequest' },
  61. // transformResponse:[function (data) {
  62. // try {
  63. // // 如果转换成功则返回转换的数据结果
  64. // return JSONbig.parse(data);
  65. // } catch (error) {
  66. // // 如果转换失败,则包装为统一数据格式并返回
  67. // return data
  68. // }
  69. // }],
  70. // 错误处理: umi@3 的错误处理方案。
  71. errorConfig: {
  72. // 错误抛出
  73. errorThrower: (res: ResponseStructure) => {
  74. const { success, data, errorCode, errorMessage, showType } = res;
  75. //console.log({success, data, errorCode, errorMessage, showType});
  76. if (!success) {
  77. const error: any = new Error(errorMessage);
  78. error.name = 'BizError';
  79. error.info = { errorCode, errorMessage, showType, data };
  80. throw error; // 抛出自制的错误
  81. }
  82. },
  83. // 错误接收及处理
  84. errorHandler: (error: any, opts: any) => {
  85. if (opts?.skipErrorHandler) throw error;
  86. // 我们的 errorThrower 抛出的错误。
  87. if (error.name === 'BizError') {
  88. const errorInfo: ResponseStructure | undefined = error.info;
  89. if (errorInfo) {
  90. const { errorMessage, errorCode } = errorInfo;
  91. switch (errorInfo.showType) {
  92. case ErrorShowType.SILENT:
  93. // do nothing
  94. break;
  95. case ErrorShowType.WARN_MESSAGE:
  96. message.warning(errorMessage);
  97. break;
  98. case ErrorShowType.ERROR_MESSAGE:
  99. message.error(errorMessage);
  100. break;
  101. case ErrorShowType.NOTIFICATION:
  102. notification.open({
  103. description: errorMessage,
  104. message: errorCode,
  105. });
  106. break;
  107. case ErrorShowType.REDIRECT:
  108. // TODO: redirect
  109. break;
  110. default:
  111. message.error(errorMessage);
  112. }
  113. }
  114. } else if (error.response) {
  115. // Axios 的错误
  116. // 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
  117. message.error(`Response status:${error.response.status}`);
  118. } else if (error.request) {
  119. // 请求已经成功发起,但没有收到响应
  120. // \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
  121. // 而在node.js中是 http.ClientRequest 的实例
  122. message.error('None response! Please retry.');
  123. } else {
  124. // 发送请求时出了点问题
  125. message.error('Request error, please retry.');
  126. }
  127. },
  128. },
  129. // 请求拦截器
  130. requestInterceptors: [
  131. (config: RequestConfig) => {
  132. // 拦截请求配置,进行个性化处理。
  133. const userData = localStorage.getItem('userData');
  134. const { token = '' } = JSON.parse(userData as any);
  135. return { ...config, url: `/gateway${config.url}`, headers: { token } }
  136. // return { ...config }
  137. }
  138. ],
  139. // 响应拦截器
  140. responseInterceptors: [
  141. (response: AxiosResponse) => {
  142. // 拦截响应数据,进行个性化处理
  143. const { status, data: { success, status: code, errorMessage, data: respData }, config: { method } } = response;
  144. try {
  145. if (status == 200) {
  146. // 网络请求成功
  147. if (method == 'post') {
  148. if (code == 200) {
  149. return response.data.data == null ? {
  150. success: true,
  151. data: true
  152. } : response.data
  153. } else {
  154. notification.error({
  155. message: '',
  156. description: errorMessage,
  157. placement: 'topRight',
  158. icon: <></>
  159. })
  160. return false
  161. }
  162. } else {
  163. if (status != 200) {
  164. message.error('请求失败!');
  165. return false
  166. } else {
  167. if (code == 200) {
  168. //console.log({code,'response.data':response.data});
  169. return response.data
  170. } else {
  171. notification.error({
  172. message: '',
  173. description: errorMessage,
  174. placement: 'topRight',
  175. icon: <></>
  176. })
  177. return false;
  178. }
  179. }
  180. }
  181. } else {
  182. return false
  183. }
  184. } catch (error) {
  185. console.log({ error });
  186. }
  187. }
  188. ]
  189. };
  190. export function patchClientRoutes({ routes }: { routes: any }) {
  191. const treeLoop = (treeData: any) => {
  192. // console.log({treeData});
  193. if(treeData.routes){
  194. const paths = [...new Array(20).keys()].map((a, index) => ({
  195. path: `${treeData.path == '/'?'':treeData.path}/reports/${index}`,
  196. exact: true,
  197. element: <Report />,
  198. }));
  199. const lanhuPagePaths = [...new Array(20).keys()].map((a, index) => ({
  200. path: `${treeData.path == '/'?'':treeData.path}/static/${index}`,
  201. exact: true,
  202. element: <Static />,
  203. }));
  204. //console.log({paths});
  205. paths.forEach((a: any) => {
  206. treeData.routes.push(a);
  207. });
  208. lanhuPagePaths.forEach((a: any) => {
  209. treeData.routes.push(a);
  210. });
  211. }
  212. if (treeData.routes && treeData.routes.length > 0) {
  213. treeData.routes.forEach((a: any) => {
  214. treeLoop(a);
  215. })
  216. }
  217. }
  218. treeLoop(routes[0]);
  219. }
  220. export type menuDataItemType = {
  221. path: string,
  222. name: string,
  223. icon: any,
  224. component?: string,
  225. softUrl?: string, // 帆软url
  226. children?: menuDataItemType[]
  227. }
  228. // 将服务端获取的菜单 icon 字符串映射为对应的 icon Dom
  229. const mappingIcon = (menuData: menuDataItemType[]) => {
  230. if (menuData.length == 0) {
  231. return [
  232. {
  233. path: '',
  234. name: '',
  235. icon: '',
  236. component: './404',
  237. }
  238. ]
  239. }
  240. const mappingMenu: menuDataItemType[] = menuData.map(item => ({
  241. ...item,
  242. icon: item.icon && iconEnum[item.icon],
  243. children: item.children ? mappingIcon(item.children) : [],
  244. }));
  245. return mappingMenu;
  246. };
  247. const imgNode = (props: any) => {
  248. return <IconFont type='icon-shouye' {...props} />
  249. };
  250. const fileIcon = (params: any) => {
  251. return <IconFont type='icon-jixiaoguanli' />
  252. };
  253. const setting = (params: any) => {
  254. return <IconFont type='icon-xitongshezhi' />
  255. };
  256. const secondaryDistribute = (params: any) => {
  257. return <IconFont type='icon-ercifenpei' />
  258. };
  259. const reportCheck = (params: any) => {
  260. return <IconFont type='icon-baobiaochaxun' />
  261. };
  262. const crosstabReport = (params: any) => {
  263. return <IconFont type='icon-baobiaochaxun' />
  264. };
  265. //布局配置
  266. export const layout = ({ initialState, setInitialState }: { initialState: any, setInitialState: any }) => {
  267. const { isCollapsed } = initialState;
  268. const [openKeys, set_openKeys] = useState<string[]>([]);
  269. const [selectedKeys, set_selectedKeys] = useState<string[]>([]);
  270. const onCollapse = (isCollapsed: boolean): void => {
  271. setInitialState({ ...initialState, isCollapsed }).then();
  272. };
  273. return {
  274. menuHeaderRender: false,
  275. token: {
  276. sider: {
  277. colorMenuBackground: '#fff',
  278. colorTextMenuActive: '#3376FE',
  279. colorTextMenuSelected: '#3376FE',
  280. colorTextMenuTitle: '#17181A',
  281. colorTextMenu: '#17181A',
  282. //colorBgMenuItemHover:'##f0f2f5',
  283. colorBgMenuItemSelected: '#F2F6FF',
  284. // colorBgMenuItemCollapsedHover:'#f0f2f5',
  285. // //colorBgMenuItemCollapsedSelected:'blue'
  286. }
  287. },
  288. siderWidth: 200,
  289. menu: {
  290. locale: false,
  291. request: async () => {
  292. const userData = localStorage.getItem('userData');
  293. const currentSelectedTab = localStorage.getItem('currentSelectedTab');
  294. if (currentSelectedTab) {
  295. const { menuId, path } = JSON.parse(currentSelectedTab);
  296. const systemId = menuId;
  297. const data: any[] = await getPlatformMenu(systemId);
  298. if (data) {
  299. const getVFromTree = (data: any[], key: string) => {
  300. let result: any[] = [];
  301. function looper(data: any[], key: string) {
  302. data.forEach((t) => {
  303. if (t[key] && t[key] != 0) {
  304. //非一般页面
  305. if (t[key] == 1 || t[key] == 2 || t[key] == 3) {
  306. //网易有数页面
  307. result.push({
  308. contentType: t[key],
  309. path: t['path'],
  310. reportId: t['reportId'],
  311. url: t['youshuUrl'],
  312. });
  313. }
  314. if (t[key] == 5) {
  315. result.push({
  316. contentType: t[key],
  317. path: t['path'],
  318. reportId: t['reportId'],
  319. url: t['softUrl'],
  320. });
  321. }
  322. }
  323. if (t.children && t.children.length > 0) {
  324. looper(t.children, key);
  325. }
  326. });
  327. }
  328. looper(data, key);
  329. return result;
  330. };
  331. const _menu = getVFromTree(data, 'contentType');
  332. setInitialState((t: any) => ({ ...t, spacicalPageParamsType: _menu,memuData:data, userData: JSON.parse(userData as string) }));
  333. //return mappingIcon(data);
  334. function addIconToPath(node: any, paths: string[]) {
  335. if (paths.includes(node.path)) {
  336. if (node.path == '/home') {
  337. node.icon = <Icon component={imgNode} />;
  338. }
  339. if (node.path == '/budgetMana') {
  340. node.icon = <Icon component={fileIcon} />;
  341. }
  342. if (node.path == '/setting') {
  343. node.icon = <Icon component={setting} />;
  344. }
  345. if (node.path == '/reportCheck') {
  346. node.icon = <Icon component={setting} />;
  347. }
  348. if (node.path == '/secondaryDistribute') {
  349. node.icon = <Icon component={secondaryDistribute} />;
  350. }
  351. if (node.path == '/reportCheck') {
  352. node.icon = <Icon component={reportCheck} />;
  353. }
  354. if (node.path == '/crosstabReport') {
  355. node.icon = <Icon component={reportCheck} />;
  356. }
  357. }
  358. if (node.children) {
  359. node.children.forEach((child: any) => addIconToPath(child, paths));
  360. }
  361. // node.icon = <FolderOutlined />;
  362. // if (node.children) {
  363. // node.children.forEach((child: any) => addIconToPath(child, paths));
  364. // }
  365. }
  366. data.forEach((item: any) => {
  367. addIconToPath(item, ['/home', '/budgetMana', '/setting', '/reportCheck', '/secondaryDistribute','/crosstabReport']);
  368. });
  369. return data
  370. }
  371. }
  372. },
  373. },
  374. onPageChange: (location: Location) => {
  375. // console.log({location});
  376. // const {pathname} = location;
  377. // localStorage.setItem('currentPath',pathname);
  378. },
  379. collapsedButtonRender: () => {
  380. return (
  381. <div style={{
  382. position: 'absolute', zIndex: 10, right: 17, bottom: 12,
  383. display: 'flex', justifyContent: 'center', alignItems: 'center',
  384. cursor: 'pointer', width: 24, height: 24
  385. }} onClick={
  386. () => {
  387. onCollapse(isCollapsed ? false : true);
  388. }
  389. }><IconFont className='menuCollapseIcon' type={isCollapsed ? 'icon-celanzhankai' : 'icon-celanshouqi'} /></div>
  390. )
  391. },
  392. collapsed: isCollapsed,
  393. contentStyle: {
  394. border: '16px solid #F7F9FC',
  395. //height: '94.5vh', //以去除顶部导航高度
  396. borderRadius: '22px',
  397. background: '#F7F9FC',
  398. height:'calc(100vh - 48px)'
  399. // overflow:'scroll',
  400. // margin:'20px 20px'
  401. },
  402. };
  403. };