|  |  | 
 |  |  | import React from 'react'; | 
 |  |  | import { Footer, Question, SelectLang, AvatarDropdown, AvatarName } from '@/components'; | 
 |  |  | import { LinkOutlined } from '@ant-design/icons'; | 
 |  |  | import { SettingDrawer } from '@ant-design/pro-components'; | 
 |  |  | import { history, Link } from '@umijs/max'; | 
 |  |  | import defaultSettings from '../config/defaultSettings'; | 
 |  |  | import { errorConfig } from './requestErrorConfig'; | 
 |  |  | import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; | 
 |  |  | import { getRemoteMenu, getRoutersInfo, getUserInfo, setRemoteMenu, patchRouteWithRemoteMenus } from './services/route'; | 
 |  |  | import { getToken } from '@/utils/token-util' | 
 |  |  | import { TOKEN_HEADER_NAME, TOKEN_STORE_NAME } from '@/config/setting'; | 
 |  |  | import { API_BASE_URL } from '@/config/setting' | 
 |  |  |  | 
 |  |  | const isDev = process.env.NODE_ENV === 'development'; | 
 |  |  | const loginPath = '/user/login'; | 
 |  |  | const defaultAvatar = 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png' | 
 |  |  |  | 
 |  |  | export function render(oldRender) { | 
 |  |  |   const token = getToken(); | 
 |  |  |   if (!token || token?.length === 0) { | 
 |  |  |     oldRender(); | 
 |  |  |     return; | 
 |  |  |   } | 
 |  |  |   getRoutersInfo().then(res => { | 
 |  |  |     setRemoteMenu(res); | 
 |  |  |     oldRender(); | 
 |  |  |   }); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | export async function patchClientRoutes({ routes }) { | 
 |  |  |   patchRouteWithRemoteMenus(routes); | 
 |  |  | } | 
 |  |  |  | 
 |  |  | export async function onRouteChange({ clientRoutes, location }) { | 
 |  |  |   const menus = getRemoteMenu(); | 
 |  |  |   if (menus === null && location.pathname !== loginPath) { | 
 |  |  |     history.go(0); | 
 |  |  |   } | 
 |  |  | } | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state | 
 |  |  |  * */ | 
 |  |  | export async function getInitialState() { | 
 |  |  |   const fetchUserInfo = async () => { | 
 |  |  |     try { | 
 |  |  |       const { data: userInfo } = await getUserInfo({ | 
 |  |  |         skipErrorHandler: true, | 
 |  |  |       }); | 
 |  |  |       // console.log(userInfo); | 
 |  |  |       if (userInfo?.avatar === '') { | 
 |  |  |         userInfo.avatar = defaultAvatar; | 
 |  |  |       } | 
 |  |  |       userInfo.name = userInfo.nickname; | 
 |  |  |       return { | 
 |  |  |         ...userInfo | 
 |  |  |       }; | 
 |  |  |  | 
 |  |  |       // const userInfo = await queryCurrentUser({ | 
 |  |  |       //   skipErrorHandler: true, | 
 |  |  |       // }); | 
 |  |  |       // console.log(userInfo); | 
 |  |  |       // return userInfo.data; | 
 |  |  |     } catch (error) { | 
 |  |  |       console.log(error); | 
 |  |  |       history.push(loginPath); | 
 |  |  |     } | 
 |  |  |     return undefined; | 
 |  |  |   }; | 
 |  |  |   // 如果不是登录页面,执行 | 
 |  |  |   const { location } = history; | 
 |  |  |   if (location.pathname !== loginPath) { | 
 |  |  |     const currentUser = await fetchUserInfo(); | 
 |  |  |     return { | 
 |  |  |       fetchUserInfo, | 
 |  |  |       currentUser, | 
 |  |  |       settings: defaultSettings, | 
 |  |  |     }; | 
 |  |  |   } | 
 |  |  |   return { | 
 |  |  |     memo: 'create by vincent', | 
 |  |  |     fetchUserInfo, | 
 |  |  |     settings: defaultSettings, | 
 |  |  |   }; | 
 |  |  | } | 
 |  |  |  | 
 |  |  | // ProLayout 支持的api https://procomponents.ant.design/components/layout | 
 |  |  | export const layout = ({ initialState, setInitialState }) => { | 
 |  |  |   return { | 
 |  |  |     actionsRender: () => [<Question key="doc" />, <SelectLang key="SelectLang" />], | 
 |  |  |     avatarProps: { | 
 |  |  |       src: initialState?.currentUser?.avatar, | 
 |  |  |       title: <AvatarName />, | 
 |  |  |       render: (_, avatarChildren) => { | 
 |  |  |         return <AvatarDropdown>{avatarChildren}</AvatarDropdown>; | 
 |  |  |       }, | 
 |  |  |     }, | 
 |  |  |     menu: { | 
 |  |  |       locale: false, | 
 |  |  |       // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request | 
 |  |  |       params: { | 
 |  |  |         userId: initialState?.currentUser?.id, | 
 |  |  |       }, | 
 |  |  |       request: async () => { | 
 |  |  |         if (!initialState?.currentUser?.id) { | 
 |  |  |           return []; | 
 |  |  |         } | 
 |  |  |         return getRemoteMenu(); | 
 |  |  |       }, | 
 |  |  |     }, | 
 |  |  |     waterMarkProps: { | 
 |  |  |       content: initialState?.currentUser?.nickname, | 
 |  |  |     }, | 
 |  |  |     footerRender: () => <Footer />, | 
 |  |  |     onPageChange: () => { | 
 |  |  |       const { location } = history; | 
 |  |  |       // 如果没有登录,重定向到 login | 
 |  |  |       if (!initialState?.currentUser && location.pathname !== loginPath) { | 
 |  |  |         history.push(loginPath); | 
 |  |  |       } | 
 |  |  |     }, | 
 |  |  |     bgLayoutImgList: [ | 
 |  |  |       { | 
 |  |  |         src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr', | 
 |  |  |         left: 85, | 
 |  |  |         bottom: 100, | 
 |  |  |         height: '303px', | 
 |  |  |       }, | 
 |  |  |       { | 
 |  |  |         src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr', | 
 |  |  |         bottom: -68, | 
 |  |  |         right: -45, | 
 |  |  |         height: '303px', | 
 |  |  |       }, | 
 |  |  |       { | 
 |  |  |         src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr', | 
 |  |  |         bottom: 0, | 
 |  |  |         left: 0, | 
 |  |  |         width: '331px', | 
 |  |  |       }, | 
 |  |  |     ], | 
 |  |  |     links: isDev | 
 |  |  |       ? [ | 
 |  |  |         <Link key="openapi" to="/umi/plugin/openapi" target="_blank"> | 
 |  |  |           <LinkOutlined /> | 
 |  |  |           <span>OpenAPI 文档</span> | 
 |  |  |         </Link>, | 
 |  |  |       ] | 
 |  |  |       : [], | 
 |  |  |     menuHeaderRender: undefined, | 
 |  |  |     // 自定义 403 页面 | 
 |  |  |     // unAccessible: <div>unAccessible</div>, | 
 |  |  |     // 增加一个 loading 的状态 | 
 |  |  |     childrenRender: (children) => { | 
 |  |  |       // if (initialState?.loading) return <PageLoading />; | 
 |  |  |       return ( | 
 |  |  |         <> | 
 |  |  |           {children} | 
 |  |  |           {isDev && ( | 
 |  |  |             <SettingDrawer | 
 |  |  |               disableUrlParams | 
 |  |  |               enableDarkTheme | 
 |  |  |               settings={initialState?.settings} | 
 |  |  |               onSettingChange={(settings) => { | 
 |  |  |                 setInitialState((preInitialState) => ({ | 
 |  |  |                   ...preInitialState, | 
 |  |  |                   settings, | 
 |  |  |                 })); | 
 |  |  |               }} | 
 |  |  |             /> | 
 |  |  |           )} | 
 |  |  |         </> | 
 |  |  |       ); | 
 |  |  |     }, | 
 |  |  |     ...initialState?.settings, | 
 |  |  |   }; | 
 |  |  | }; | 
 |  |  |  | 
 |  |  |  | 
 |  |  | /** | 
 |  |  |  * @name request 配置,可以配置错误处理 | 
 |  |  |  * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。 | 
 |  |  |  * @doc https://umijs.org/docs/max/request#配置 | 
 |  |  |  */ | 
 |  |  | export const request = { | 
 |  |  |   baseURL: API_BASE_URL, | 
 |  |  |   ...errorConfig, | 
 |  |  |   timeout: 60000, | 
 |  |  |   // 前置守卫 | 
 |  |  |   requestInterceptors: [ | 
 |  |  |     (url, options) => { | 
 |  |  |       console.log('request ====>:', url); | 
 |  |  |       const token = getToken(); | 
 |  |  |       if (token && options.headers) { | 
 |  |  |         options.headers[TOKEN_HEADER_NAME] = token; | 
 |  |  |       } | 
 |  |  |       return { url, options }; | 
 |  |  |     } | 
 |  |  |   ], | 
 |  |  |   // 后置守卫 | 
 |  |  |   responseInterceptors: [ | 
 |  |  |     (response) => { | 
 |  |  |  | 
 |  |  |       return response; | 
 |  |  |     } | 
 |  |  |   ] | 
 |  |  | }; | 
 |  |  | import React from 'react';
 | 
 |  |  | import { Footer, Question, SelectLang, AvatarDropdown, AvatarName, Brightness, LayoutSwitch, FullScreen } from '@/components';
 | 
 |  |  | import { LinkOutlined } from '@ant-design/icons';
 | 
 |  |  | import { SettingDrawer } from '@ant-design/pro-components';
 | 
 |  |  | import { history, Link } from '@umijs/max';
 | 
 |  |  | import defaultSettings from '../config/defaultSettings';
 | 
 |  |  | import { errorConfig } from './requestErrorConfig';
 | 
 |  |  | import { getRemoteMenu, getRoutersInfo, getUserInfo, setRemoteMenu, patchRouteWithRemoteMenus } from './services/route';
 | 
 |  |  | import { getToken, setToken } from '@/utils/token-util'
 | 
 |  |  | import { TOKEN_HEADER_NAME, TOKEN_STORE_NAME } from '@/config/setting';
 | 
 |  |  | import { API_BASE_URL } from '@/config/setting'
 | 
 |  |  | import { message } from 'antd';
 | 
 |  |  | 
 | 
 |  |  | import logo from '../public/img/logo.png'
 | 
 |  |  | import logoDark from '../public/img/logo-dark.png'
 | 
 |  |  | 
 | 
 |  |  | const isDev = process.env.NODE_ENV === 'development';
 | 
 |  |  | const loginPath = '/user/login';
 | 
 |  |  | const defaultAvatar = 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png'
 | 
 |  |  | 
 | 
 |  |  | // https://xflow.antv.vision/zh-CN/docs/tutorial/solutions/dag DAG 流程图
 | 
 |  |  | export function render(oldRender) {
 | 
 |  |  |   const token = getToken();
 | 
 |  |  |   if (!token || token?.length === 0) {
 | 
 |  |  |     oldRender();
 | 
 |  |  |     return;
 | 
 |  |  |   }
 | 
 |  |  |   getRoutersInfo().then(res => {
 | 
 |  |  |     setRemoteMenu(res);
 | 
 |  |  |     oldRender();
 | 
 |  |  |   });
 | 
 |  |  | }
 | 
 |  |  | 
 | 
 |  |  | export async function patchClientRoutes({ routes }) {
 | 
 |  |  |   patchRouteWithRemoteMenus(routes);
 | 
 |  |  | }
 | 
 |  |  | 
 | 
 |  |  | export async function onRouteChange({ clientRoutes, location }) {
 | 
 |  |  |   const menus = getRemoteMenu();
 | 
 |  |  |   if (menus === null && location.pathname !== loginPath) {
 | 
 |  |  |     history.go(0);
 | 
 |  |  |   }
 | 
 |  |  | }
 | 
 |  |  | 
 | 
 |  |  | /**
 | 
 |  |  |  * @see  https://umijs.org/zh-CN/plugins/plugin-initial-state
 | 
 |  |  |  * */
 | 
 |  |  | export async function getInitialState() {
 | 
 |  |  |   const fetchUserInfo = async () => {
 | 
 |  |  |     try {
 | 
 |  |  |       const { data: userInfo } = await getUserInfo({
 | 
 |  |  |         skipErrorHandler: true,
 | 
 |  |  |       });
 | 
 |  |  |       if (userInfo?.avatar === '') {
 | 
 |  |  |         userInfo.avatar = defaultAvatar;
 | 
 |  |  |       }
 | 
 |  |  |       userInfo.name = userInfo.nickname;
 | 
 |  |  |       return {
 | 
 |  |  |         ...userInfo
 | 
 |  |  |       };
 | 
 |  |  |     } catch (error) {
 | 
 |  |  |       console.log(error);
 | 
 |  |  |       history.push(loginPath);
 | 
 |  |  |     }
 | 
 |  |  |     return undefined;
 | 
 |  |  |   };
 | 
 |  |  |   // 如果不是登录页面,执行
 | 
 |  |  |   const { location } = history;
 | 
 |  |  |   if (location.pathname !== loginPath) {
 | 
 |  |  |     const currentUser = await fetchUserInfo();
 | 
 |  |  |     return {
 | 
 |  |  |       fetchUserInfo,
 | 
 |  |  |       currentUser,
 | 
 |  |  |       settings: defaultSettings,
 | 
 |  |  |     };
 | 
 |  |  |   }
 | 
 |  |  |   return {
 | 
 |  |  |     memo: 'create by vincent',
 | 
 |  |  |     fetchUserInfo,
 | 
 |  |  |     settings: defaultSettings,
 | 
 |  |  |   };
 | 
 |  |  | }
 | 
 |  |  | 
 | 
 |  |  | // ProLayout 支持的api https://procomponents.ant.design/components/layout
 | 
 |  |  | // 优先级 layout > config > defaultSetting
 | 
 |  |  | export const layout = ({ initialState, setInitialState }) => {
 | 
 |  |  |   const [darkMode, setDarkMode] = React.useState(() => {
 | 
 |  |  |     const storedValue = localStorage.getItem('darkMode');
 | 
 |  |  |     return storedValue !== null ? JSON.parse(storedValue) : true;
 | 
 |  |  |   });
 | 
 |  |  | 
 | 
 |  |  |   const [layoutMode, setLayoutMode] = React.useState(() => {
 | 
 |  |  |     const storedValue = localStorage.getItem('layoutMode');
 | 
 |  |  |     return storedValue !== null ? JSON.parse(storedValue) : true;
 | 
 |  |  |   });
 | 
 |  |  | 
 | 
 |  |  |   const [fullScreen, setFullScreen] = React.useState(false);
 | 
 |  |  | 
 | 
 |  |  |   React.useEffect(() => {
 | 
 |  |  |     localStorage.setItem('darkMode', JSON.stringify(darkMode));
 | 
 |  |  |     localStorage.setItem('layoutMode', JSON.stringify(layoutMode));
 | 
 |  |  |   }, [darkMode, layoutMode]);
 | 
 |  |  | 
 | 
 |  |  |   return {
 | 
 |  |  |     actionsRender: () => [
 | 
 |  |  |       // <Question key="doc" />,
 | 
 |  |  |       <SelectLang key="SelectLang" />,
 | 
 |  |  |       <FullScreen fullScreen={fullScreen} setFullScreen={setFullScreen} />,
 | 
 |  |  |       <LayoutSwitch layoutMode={layoutMode} setLayoutMode={setLayoutMode} />,
 | 
 |  |  |       <Brightness darkMode={darkMode} setDarkMode={setDarkMode} />,
 | 
 |  |  |     ],
 | 
 |  |  |     avatarProps: {
 | 
 |  |  |       src: initialState?.currentUser?.avatar,
 | 
 |  |  |       title: <AvatarName />,
 | 
 |  |  |       render: (_, avatarChildren) => {
 | 
 |  |  |         return <AvatarDropdown>{avatarChildren}</AvatarDropdown>;
 | 
 |  |  |       },
 | 
 |  |  |     },
 | 
 |  |  |     menu: {
 | 
 |  |  |       locale: false,
 | 
 |  |  |       // 每当 initialState?.currentUser?.userid 发生修改时重新执行 request
 | 
 |  |  |       params: {
 | 
 |  |  |         userId: initialState?.currentUser?.id,
 | 
 |  |  |       },
 | 
 |  |  |       request: async () => {
 | 
 |  |  |         if (!initialState?.currentUser?.id) {
 | 
 |  |  |           return [];
 | 
 |  |  |         }
 | 
 |  |  |         return getRemoteMenu();
 | 
 |  |  |       },
 | 
 |  |  |     },
 | 
 |  |  |     footerRender: () => <Footer />,
 | 
 |  |  |     onPageChange: () => {
 | 
 |  |  |       const { location } = history;
 | 
 |  |  |       // 如果没有登录,重定向到 login
 | 
 |  |  |       if (!initialState?.currentUser && location.pathname !== loginPath) {
 | 
 |  |  |         history.push(loginPath);
 | 
 |  |  |       }
 | 
 |  |  |     },
 | 
 |  |  |     // token: {
 | 
 |  |  |     //   bgLayout: '#fff',
 | 
 |  |  |     //   header: {
 | 
 |  |  |     //     colorBgHeader: '#fff',
 | 
 |  |  |     //   },
 | 
 |  |  |     //   sider: {
 | 
 |  |  |     //     colorMenuBackground: '#fff',
 | 
 |  |  |     //   },
 | 
 |  |  |     //   pageContainer: {
 | 
 |  |  |     //     colorBgPageContainer: '#fff',
 | 
 |  |  |     //   },
 | 
 |  |  |     // },
 | 
 |  |  |     bgLayoutImgList: [
 | 
 |  |  |       // {
 | 
 |  |  |       //   src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/D2LWSqNny4sAAAAAAAAAAAAAFl94AQBr',
 | 
 |  |  |       //   left: 85,
 | 
 |  |  |       //   bottom: 100,
 | 
 |  |  |       //   height: '303px',
 | 
 |  |  |       // },
 | 
 |  |  |       // {
 | 
 |  |  |       //   src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/C2TWRpJpiC0AAAAAAAAAAAAAFl94AQBr',
 | 
 |  |  |       //   bottom: -68,
 | 
 |  |  |       //   right: -45,
 | 
 |  |  |       //   height: '303px',
 | 
 |  |  |       // },
 | 
 |  |  |       // {
 | 
 |  |  |       //   src: 'https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/F6vSTbj8KpYAAAAAAAAAAAAAFl94AQBr',
 | 
 |  |  |       //   bottom: 0,
 | 
 |  |  |       //   left: 0,
 | 
 |  |  |       //   width: '331px',
 | 
 |  |  |       // },
 | 
 |  |  |     ],
 | 
 |  |  |     // 显示在菜单右下角的快捷操作
 | 
 |  |  |     links: [],
 | 
 |  |  |     menuHeaderRender: undefined,
 | 
 |  |  |     // 自定义 403 页面
 | 
 |  |  |     // unAccessible: <div>unAccessible</div>,
 | 
 |  |  |     // 增加一个 loading 的状态
 | 
 |  |  |     childrenRender: (children) => {
 | 
 |  |  |       // if (initialState?.loading) return <PageLoading />;
 | 
 |  |  |       return (
 | 
 |  |  |         <>
 | 
 |  |  |           {children}
 | 
 |  |  |           {/* {isDev && (
 | 
 |  |  |             <SettingDrawer
 | 
 |  |  |               disableUrlParams
 | 
 |  |  |               enableDarkTheme
 | 
 |  |  |               settings={initialState?.settings}
 | 
 |  |  |               onSettingChange={(settings) => {
 | 
 |  |  |                 console.log(settings);
 | 
 |  |  |                 setInitialState((preInitialState) => ({
 | 
 |  |  |                   ...preInitialState,
 | 
 |  |  |                   settings,
 | 
 |  |  |                 }));
 | 
 |  |  |               }}
 | 
 |  |  |             />
 | 
 |  |  |           )} */}
 | 
 |  |  |         </>
 | 
 |  |  |       );
 | 
 |  |  |     },
 | 
 |  |  |     ...initialState?.settings,
 | 
 |  |  |     layout: layoutMode ? 'top' : 'mix',  // layout 的菜单模式,side:右侧导航,top:顶部导航
 | 
 |  |  |     // contentStyle: () => {  //    layout 的内容区 style
 | 
 |  |  |     //   return  | 
 |  |  |     // },
 | 
 |  |  |     contentWidth: 'Fluid', // layout 的内容模式,Fluid:自适应(全屏),Fixed:定宽 (小) 1200px
 | 
 |  |  |     fixedHeader: true,  // 固定 header
 | 
 |  |  |     fixSiderbar: true,  // 固定导航
 | 
 |  |  |     // settings: defaultSettings, // layout 的设置
 | 
 |  |  |     // waterMarkProps: { content: initialState?.currentUser?.nickname }, //水印
 | 
 |  |  |     // navTheme: 'realDark', // 默认主题颜色  "realDark" | "light" | undef...
 | 
 |  |  |     navTheme: darkMode ? 'realDark' : 'light',
 | 
 |  |  |     footerRender: false,  // 页脚 启用请注释,不是设置为true
 | 
 |  |  |     logo: darkMode
 | 
 |  |  |       ? <img src={logo} className='header-logo' />
 | 
 |  |  |       : <img src={logoDark} className='header-logo' />
 | 
 |  |  |     ,
 | 
 |  |  |     title: (
 | 
 |  |  |       <div className='header-title'>
 | 
 |  |  |       </div>
 | 
 |  |  |     ),
 | 
 |  |  |     colorWeak: true,
 | 
 |  |  |   };
 | 
 |  |  | };
 | 
 |  |  | 
 | 
 |  |  | 
 | 
 |  |  | /**
 | 
 |  |  |  * @name request 配置,可以配置错误处理
 | 
 |  |  |  * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
 | 
 |  |  |  * @doc https://umijs.org/docs/max/request#配置
 | 
 |  |  |  */
 | 
 |  |  | export const request = {
 | 
 |  |  |   baseURL: API_BASE_URL,
 | 
 |  |  |   ...errorConfig,
 | 
 |  |  |   timeout: 60000,
 | 
 |  |  |   // 前置守卫
 | 
 |  |  |   requestInterceptors: [
 | 
 |  |  |     (url, options) => {
 | 
 |  |  |       const token = getToken();
 | 
 |  |  |       if (token && options.headers) {
 | 
 |  |  |         options.headers[TOKEN_HEADER_NAME] = token;
 | 
 |  |  |       }
 | 
 |  |  |       return { url, options };
 | 
 |  |  |     }
 | 
 |  |  |   ],
 | 
 |  |  |   // 后置守卫
 | 
 |  |  |   responseInterceptors: [
 | 
 |  |  |     (response) => {
 | 
 |  |  |       if (response?.data?.code === 401) {
 | 
 |  |  |         // message.error(intl.formatMessage({
 | 
 |  |  |         //   id: 'pages.login.failure',
 | 
 |  |  |         //   defaultMessage: '登录失败,请重试!',
 | 
 |  |  |         // }));
 | 
 |  |  |         history.push(loginPath)
 | 
 |  |  |       }
 | 
 |  |  |       const token = response.headers[TOKEN_HEADER_NAME];
 | 
 |  |  |       if (token) {
 | 
 |  |  |         setToken(token);
 | 
 |  |  |       }
 | 
 |  |  |       return response;
 | 
 |  |  |     }
 | 
 |  |  |   ]
 | 
 |  |  | };
 |