ERPTurbo_Admin/packages/app-operation/src/app.tsx
shenyifei b4ce6e45dc refactor(order): 重构发货单功能模块
- 将 ShipOrderList 组件从 Delivery 模块迁移至 Order 模块
- 移除 Delivery 模块的导出并在 Order 模块中新增 OrderShipList 和相关组件
- 更新发货单列表的列配置,添加订单、经销商、公司等关联字段
- 修改发货单列表的字段映射,将经销商名称和车次号格式从短横线改为竖线分隔
- 在 BizPage 组件中添加 convertValue 属性支持数据转换
- 更新左侧菜单组件的依赖数组以包含 menuData
- 调整发货单列表的国际化配置,更新字段标题和状态枚举值
- 新增 OrderShip 页面路由文件
- 移除项目根目录的 AGENTS.md 和 project.md 文件
- 从组件索引中移除 Delivery 模块并添加 SearchMenu 组件
2026-01-08 16:15:03 +08:00

529 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 运行时配置
// 全局初始化数据配置,用于 Layout 用户信息和权限初始化
// 更多信息见文档https://umijs.org/docs/api/runtime-config#getinitialstate
import { LeftMenu, SearchMenu, 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,
NotificationFilled,
QuestionCircleFilled,
} from '@ant-design/icons';
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 ? (
<SearchMenu key={'SearchMenu'} menuData={props.menuData} />
) : 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;