- 将格式化函数提取到公共工具文件,避免重复定义 - 优化成本数据处理逻辑,完善数据映射和去重操作 - 统一拖拽组件分页配置,增强分页功能一致性 - 修复类型导入问题,提升代码健壮性 - 新增经销商列表公司返点字段展示与表单控制 - 重构发货单组件为订单发货组件,优化状态管理和搜索功能 - 创建新的采购订单和供应商订单组件,完善订单状态tab - 调整组件导出路径,统一归类到Order目录下 - 扩展用户头像功能,支持角色切换能力
529 lines
13 KiB
TypeScript
529 lines
13 KiB
TypeScript
// 运行时配置
|
||
|
||
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
|
||
// 更多信息见文档:https://umijs.org/docs/api/runtime-config#getinitialstate
|
||
import { VersionChecker } from '@/components';
|
||
import Avatar from '@/layout/guide/Avatar';
|
||
import { auth } from '@/services';
|
||
import { Navigate } from '@@/exports';
|
||
import { RunTimeLayoutConfig } from '@@/plugin-layout/types';
|
||
import { RequestConfig } from '@@/plugin-request/request';
|
||
import { InfoCircleFilled } from '@ant-design/icons';
|
||
import { LeftMenu } from '@chageable/components';
|
||
import '@nutui/nutui-react/dist/style.scss';
|
||
import { history } from '@umijs/max';
|
||
import { Alert, Button, message, notification, Spin } from 'antd';
|
||
import React from 'react';
|
||
import './global.less';
|
||
|
||
interface InitialStateProps {
|
||
currentUser?: AuthAPI.UserVO;
|
||
currentAdmin?: AuthAPI.AdminVO;
|
||
platformId: string;
|
||
channel?: AuthAPI.ChannelVO;
|
||
tip: any;
|
||
}
|
||
|
||
// 动态导入组件
|
||
function dynamicImport(componentPath: string) {
|
||
const Component = React.lazy(() => {
|
||
// 处理不同的组件路径格式
|
||
const path = componentPath.startsWith('@/')
|
||
? componentPath.replace('@/', '')
|
||
: componentPath;
|
||
|
||
return new Promise((resolve) => {
|
||
import(`@/pages/${path}`)
|
||
.then((module) => resolve(module))
|
||
.catch((reason) => {
|
||
console.log('组件加载异常', reason);
|
||
resolve(import(`@/pages/404` as string));
|
||
}); // 处理查找兜底,需要对应路径下有404兜底组件
|
||
});
|
||
});
|
||
|
||
return (
|
||
<React.Suspense fallback={<Spin />}>
|
||
<Component />
|
||
</React.Suspense>
|
||
);
|
||
}
|
||
|
||
// 路由转换函数
|
||
function transformTreeRoutes(serverRoutes: any[], parentId = ''): any[] {
|
||
return serverRoutes.map((route) => {
|
||
const umiRoute: any = {
|
||
id: route.menuId,
|
||
path: route.path,
|
||
parentId,
|
||
name: route.name,
|
||
icon: route.icon,
|
||
access: route.access,
|
||
hideInMenu: route.hideInMenu,
|
||
};
|
||
|
||
// 处理组件
|
||
if (route.component) {
|
||
try {
|
||
umiRoute.element = dynamicImport(route.component);
|
||
} catch (e) {
|
||
console.log('组件加载异常', e);
|
||
umiRoute.element = dynamicImport('@page/404');
|
||
}
|
||
}
|
||
|
||
// 处理子路由
|
||
if (route.routes?.length) {
|
||
umiRoute.children = transformTreeRoutes(
|
||
route.routes,
|
||
route.menuId,
|
||
).filter((r) => !(r?.path === ' ') && r.hideInMenu === false);
|
||
if (umiRoute.children[0]?.path) {
|
||
umiRoute.children.unshift({
|
||
path: route.path,
|
||
element: <Navigate to={umiRoute.children[0]?.path} replace />,
|
||
});
|
||
}
|
||
}
|
||
|
||
return umiRoute;
|
||
});
|
||
}
|
||
|
||
// 动态路由缓存(改为函数内部变量)
|
||
let cachedRoutes: any[] = [];
|
||
let cachedMenus: any[] = [];
|
||
|
||
export async function patchClientRoutes({ routes }: { routes: any[] }) {
|
||
try {
|
||
// 转换路由结构
|
||
const root = routes.find((r) => r.path === '/');
|
||
|
||
root.children.push(...cachedRoutes);
|
||
root.children.unshift({
|
||
path: '/',
|
||
element: <Navigate to={root.children[1].path} replace />,
|
||
});
|
||
} catch (error) {
|
||
console.error('Failed to load dynamic routes:', error);
|
||
history.push('/404');
|
||
}
|
||
}
|
||
|
||
// 拦截路由渲染,比如做一些权限校验
|
||
export const render = async (oldRender: () => void) => {
|
||
// 如果已有缓存,直接使用
|
||
if (cachedRoutes.length > 0) {
|
||
oldRender();
|
||
return;
|
||
}
|
||
|
||
const { data: employeeVO } = await auth.user.userEmployee({
|
||
employeeShowQry: {},
|
||
});
|
||
|
||
let platformId = '1769220738064285698';
|
||
let roleIdList: string[] = [];
|
||
|
||
if (employeeVO) {
|
||
window.localStorage.setItem('employee', JSON.stringify(employeeVO));
|
||
|
||
const userRoleVOList = employeeVO.userRoleList?.filter(
|
||
(item) => item.platformId === platformId,
|
||
);
|
||
|
||
if (!userRoleVOList?.length) {
|
||
message.error('请先绑定角色');
|
||
window.localStorage.removeItem('employee');
|
||
window.localStorage.removeItem('admin');
|
||
window.localStorage.removeItem('roleSlug');
|
||
// 未登录,跳转到登录页面
|
||
window.location.href =
|
||
process.env.UMI_APP_SSO_LOGIN_URL +
|
||
encodeURIComponent(process.env.UMI_APP_OPERATION_URL || '');
|
||
return;
|
||
}
|
||
|
||
window.localStorage.setItem(
|
||
'userRoleVOList',
|
||
JSON.stringify(userRoleVOList),
|
||
);
|
||
|
||
// 根据 localStorage 判断有没有选择角色,如果没有选择则默认选择第一个
|
||
if (!window.localStorage.getItem('roleSlug')) {
|
||
window.localStorage.setItem('roleSlug', userRoleVOList?.[0]?.slug);
|
||
roleIdList = [userRoleVOList?.[0]?.roleId];
|
||
} else {
|
||
const roleSlug = window.localStorage.getItem('roleSlug');
|
||
const role = userRoleVOList?.find((item) => item.slug === roleSlug);
|
||
|
||
if (!role) {
|
||
message.error('请先绑定角色');
|
||
window.localStorage.removeItem('employee');
|
||
window.localStorage.removeItem('admin');
|
||
window.localStorage.removeItem('roleSlug');
|
||
// 未登录,跳转到登录页面
|
||
window.location.href =
|
||
process.env.UMI_APP_SSO_LOGIN_URL +
|
||
encodeURIComponent(process.env.UMI_APP_OPERATION_URL || '');
|
||
return;
|
||
}
|
||
|
||
roleIdList = [role?.roleId];
|
||
}
|
||
} else {
|
||
const { data: adminVO } = await auth.user.userAdmin({
|
||
adminShowQry: {},
|
||
});
|
||
|
||
window.localStorage.setItem('admin', JSON.stringify(adminVO));
|
||
window.localStorage.setItem(
|
||
'userRoleVOList',
|
||
JSON.stringify([]),
|
||
);
|
||
window.localStorage.setItem('roleSlug', 'operation');
|
||
}
|
||
|
||
// 获取服务端路由配置
|
||
await auth.user
|
||
.userMenu({
|
||
roleMenuTreeQry: {
|
||
platformId: platformId,
|
||
roleId: roleIdList,
|
||
},
|
||
})
|
||
.then(({ data: serverRoutes }) => {
|
||
cachedMenus = serverRoutes as any[];
|
||
cachedRoutes = transformTreeRoutes(
|
||
serverRoutes || [],
|
||
'ant-design-pro-layout',
|
||
);
|
||
oldRender();
|
||
});
|
||
};
|
||
|
||
export async function getInitialState(): Promise<InitialStateProps> {
|
||
let current: any;
|
||
|
||
const { data: channel } = await auth.channel.selectChannelByDomain({
|
||
domain: 'operation.erp.qilincloud168.com',
|
||
});
|
||
|
||
// 根据 localStorage 获取当前用户信息 admin 和 employee
|
||
const adminVO = JSON.parse(window.localStorage.getItem('admin') || '{}');
|
||
const employeeVO = JSON.parse(
|
||
window.localStorage.getItem('employee') || '{}',
|
||
);
|
||
|
||
current = {
|
||
channel,
|
||
platformId: '1769220738064285698',
|
||
currentUser: {
|
||
...employeeVO,
|
||
...adminVO,
|
||
},
|
||
};
|
||
|
||
return current;
|
||
}
|
||
|
||
export const layout: RunTimeLayoutConfig = ({ initialState }) => {
|
||
const link =
|
||
document.querySelector("link[rel*='icon']") ||
|
||
document.createElement('link');
|
||
//@ts-ignore
|
||
link.type = 'image/x-icon';
|
||
//@ts-ignore
|
||
link.rel = 'shortcut icon';
|
||
//@ts-ignore
|
||
link.href = initialState?.channel?.logo;
|
||
document.getElementsByTagName('head')[0].appendChild(link);
|
||
|
||
return {
|
||
title: initialState?.channel?.title,
|
||
subTitle: initialState?.channel?.subTitle,
|
||
logo: initialState?.channel?.logo,
|
||
menuRender: (props, defaultDom) => {
|
||
if (props.isMobile) {
|
||
return defaultDom;
|
||
}
|
||
|
||
const menuData = props.menuData;
|
||
|
||
if (!menuData) {
|
||
return defaultDom;
|
||
}
|
||
|
||
return (
|
||
<LeftMenu
|
||
menuData={menuData}
|
||
pathname={history.location.pathname}
|
||
onClick={(item) => {
|
||
history.push(item.key as string);
|
||
}}
|
||
/>
|
||
);
|
||
},
|
||
menu: {
|
||
type: 'group',
|
||
locale: false,
|
||
request: async () => cachedMenus,
|
||
},
|
||
fixedHeader: true,
|
||
// links: false,
|
||
layout: 'mix',
|
||
navTheme: 'light',
|
||
// splitMenus: true,
|
||
contentWidth: 'Fluid',
|
||
siderWidth: 134,
|
||
// siderMenuType: 'inline',
|
||
hasPageContainer: 1,
|
||
token: {
|
||
header: {
|
||
heightLayoutHeader: initialState?.tip?.show ? 108 : 56,
|
||
},
|
||
},
|
||
avatarProps: {
|
||
src: initialState?.currentUser?.avatar,
|
||
size: 'small',
|
||
title: initialState?.currentUser?.name,
|
||
render: (props, dom) => {
|
||
return <Avatar>{dom}</Avatar>;
|
||
},
|
||
},
|
||
onPageChange: () => {
|
||
// if (!initialState?.currentUser) {
|
||
// // 未登录,跳转到登录页面
|
||
// window.location.href =
|
||
// process.env.UMI_APP_SSO_LOGIN_URL +
|
||
// encodeURIComponent(process.env.UMI_APP_OPERATION_URL || '');
|
||
// }
|
||
},
|
||
// actionsRender: (props) => {
|
||
// if (props.isMobile) return [];
|
||
// if (typeof window === 'undefined') return [];
|
||
// return [
|
||
// props.layout !== 'side' && document.body.clientWidth > 1400 ? (
|
||
// <SearchInput key={'SearchInput'} />
|
||
// ) : undefined,
|
||
// <InfoCircleFilled key="InfoCircleFilled" />,
|
||
// <QuestionCircleFilled key="QuestionCircleFilled" />,
|
||
// <NotificationFilled key="NotificationFilled" />,
|
||
// ];
|
||
// },
|
||
headerRender: (props, defaultDom) => {
|
||
return (
|
||
<>
|
||
<VersionChecker />
|
||
{initialState?.tip?.show && (
|
||
<Alert
|
||
message={
|
||
<div
|
||
style={{
|
||
color: 'white',
|
||
}}
|
||
>
|
||
本网站提供的部分服务在你当前浏览器中无法使用,建议你更换为
|
||
Chrome 浏览器查看本网站。
|
||
</div>
|
||
}
|
||
icon={
|
||
<InfoCircleFilled
|
||
style={{
|
||
color: 'white',
|
||
}}
|
||
/>
|
||
}
|
||
banner
|
||
style={{
|
||
backgroundColor: 'black',
|
||
}}
|
||
action={
|
||
<Button
|
||
type="text"
|
||
style={{
|
||
color: 'white',
|
||
}}
|
||
>
|
||
查看详情
|
||
</Button>
|
||
}
|
||
/>
|
||
)}
|
||
|
||
{React.cloneElement(defaultDom as any, {
|
||
style: {
|
||
height: '56px',
|
||
lineHeight: '56px',
|
||
},
|
||
})}
|
||
</>
|
||
);
|
||
},
|
||
footerRender: () => {
|
||
return <></>;
|
||
},
|
||
// menuExtraRender: (props) => {
|
||
// if (props.isMobile) {
|
||
// return <StoreSelect />;
|
||
// }
|
||
// },
|
||
menuFooterRender: (props) => {
|
||
if (props?.collapsed) return undefined;
|
||
return (
|
||
<p
|
||
style={{
|
||
textAlign: 'center',
|
||
color: 'rgba(0,0,0,0.6)',
|
||
paddingBlockStart: 12,
|
||
}}
|
||
>
|
||
{initialState?.channel?.title} 提供技术支持
|
||
</p>
|
||
);
|
||
},
|
||
};
|
||
};
|
||
|
||
// 错误处理方案: 错误类型
|
||
enum ErrorShowType {
|
||
SILENT = 0,
|
||
WARN_MESSAGE = 1,
|
||
ERROR_MESSAGE = 2,
|
||
NOTIFICATION = 3,
|
||
REDIRECT = 9,
|
||
}
|
||
|
||
// 与后端约定的响应数据格式
|
||
interface ResponseStructure {
|
||
success: boolean;
|
||
data: any;
|
||
errCode?: string;
|
||
errMessage?: string;
|
||
showType?: ErrorShowType;
|
||
}
|
||
|
||
// 运行时配置
|
||
export const request: RequestConfig = {
|
||
// 统一的请求设定
|
||
timeout: 60000,
|
||
headers: {
|
||
'X-Requested-With': 'XMLHttpRequest',
|
||
'Xh-Platform-Id': '1769220738064285698',
|
||
},
|
||
// 错误处理: umi@3 的错误处理方案。
|
||
errorConfig: {
|
||
// 错误抛出
|
||
errorThrower: (res: ResponseStructure) => {
|
||
const { success, data, errCode, errMessage, showType } = res;
|
||
if (!success) {
|
||
if (errCode === '401' || errCode === '403') {
|
||
window.localStorage.removeItem('employee');
|
||
window.localStorage.removeItem('admin');
|
||
window.localStorage.removeItem('roleSlug');
|
||
// 未登录,跳转到登录页面
|
||
window.location.href =
|
||
process.env.UMI_APP_SSO_LOGIN_URL +
|
||
encodeURIComponent(process.env.UMI_APP_OPERATION_URL || '');
|
||
return;
|
||
}
|
||
|
||
if (errCode === '400') {
|
||
message.error(errMessage);
|
||
return;
|
||
}
|
||
|
||
if (errCode === 'UNKNOWN_ERROR') {
|
||
message.error('未知的错误');
|
||
return;
|
||
}
|
||
|
||
const error: any = new Error(errMessage || errCode);
|
||
error.name = 'BizError';
|
||
error.info = { errCode, errMessage, showType, data };
|
||
throw error; // 抛出自制的错误
|
||
}
|
||
},
|
||
// 错误接收及处理
|
||
errorHandler: (error: any, opts: any) => {
|
||
if (opts?.skipErrorHandler) throw error;
|
||
// 我们的 errorThrower 抛出的错误。
|
||
if (error.name === 'BizError') {
|
||
const errorInfo: ResponseStructure | undefined = error.info;
|
||
if (errorInfo) {
|
||
const { errMessage, errCode } = errorInfo;
|
||
switch (errorInfo.showType) {
|
||
case ErrorShowType.SILENT:
|
||
// do nothing
|
||
break;
|
||
case ErrorShowType.WARN_MESSAGE:
|
||
message.warning(errMessage);
|
||
break;
|
||
case ErrorShowType.ERROR_MESSAGE:
|
||
message.error(errMessage);
|
||
break;
|
||
case ErrorShowType.NOTIFICATION:
|
||
notification.open({
|
||
description: errMessage,
|
||
message: errCode,
|
||
});
|
||
break;
|
||
case ErrorShowType.REDIRECT:
|
||
// TODO: redirect
|
||
break;
|
||
default:
|
||
message.error(errMessage);
|
||
}
|
||
}
|
||
} else if (error.response) {
|
||
// Axios 的错误
|
||
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
|
||
message.error(`Response status:${error.response.status}`);
|
||
} else if (error.request) {
|
||
// 请求已经成功发起,但没有收到响应
|
||
// \`error.request\` 在浏览器中是 XMLHttpRequest 的实例,
|
||
// 而在node.js中是 http.ClientRequest 的实例
|
||
message.error('None response! Please retry.1');
|
||
} else {
|
||
// 发送请求时出了点问题
|
||
message.error('Request error, please retry.2');
|
||
}
|
||
},
|
||
},
|
||
|
||
// 请求拦截器
|
||
requestInterceptors: [
|
||
(
|
||
url: any,
|
||
options: {
|
||
headers: any;
|
||
},
|
||
) => {
|
||
const roleSlug = window.localStorage.getItem('roleSlug');
|
||
|
||
if (roleSlug) {
|
||
options.headers = {
|
||
...options.headers,
|
||
'Xh-Role-Slug': roleSlug,
|
||
};
|
||
}
|
||
|
||
console.debug('请求拦截器', url, options);
|
||
const apiUrl = '/api' + url;
|
||
|
||
return {
|
||
url: apiUrl,
|
||
options,
|
||
};
|
||
},
|
||
],
|
||
|
||
// 响应拦截器
|
||
responseInterceptors: [
|
||
(response: ResponseStructure) => {
|
||
// 拦截响应数据,进行个性化处理
|
||
return response;
|
||
},
|
||
],
|
||
} as any;
|