umi 一般的架子
routes.ts
export default [
{
path: '/',
// 推荐自己写 layouts 作为根路由
component: '@/layouts',
// 子路由
routes: [
{
path: '/level1',
name: '一级',
// 面包屑
breadcrumbKey: 'level1',
routes: [
{
path: '/level1/2',
name: '二级',
breadcrumbKey: '2',
component: '@/pages/...',
}
],
},
],
},
];
面包屑组件
// process.env.BASE 有些项目会有额外的url,或者项目是作为应用的微应用,也会增加一些url
export default (props) => {
const getPath = () => location.pathname.substr(process.env.BASE?.length || 0);
// route 数组
const pathSplits = getPath().substr(1).split('/');
const mapRoutes = (ary: any[]) => {
const map = new Map();
const traverse = (route: any) => {
// breadcrumbKey 在这使用
const { breadcrumbKey = '', routes = [] } = route;
if (!breadcrumbKey)
throw 'every route should have a breadcrumbKey to map view';
map.set(breadcrumbKey, route);
if (routes.length) routes.forEach((i: any) => traverse(i));
};
ary.forEach((i) => traverse(i));
return map;
};
const map = React.useMemo(() => mapRoutes(props.routes), []);
const breadcrumbClick = (path, flag) => {
if (flag || !path) return;
history.push(path);
};
return (
<Breadcrumb>
{pathSplits.map((i, index) => {
const r = map.get(i) || {};
// 第一个 和 最后一个样式做特殊处理,也不能点击;
const s = index === 0;
const e = index === pathSplits.length - 1;
return (
<Breadcrumb.Item
key={i}
onClick={() => breadcrumbClick(r.path, s || e)}
>
<span
className={
s
? styles.breadItemStart
: e
? styles.breadItemEnd
: styles.breadItem
}
>
{r.name || i}
</span>
</Breadcrumb.Item>
);
})}
</Breadcrumb>
);
};
layouts
<ProLayout
route={{ routes: renderRoutes }}
subMenuItemRender={(_: any, node: React.ReactNode) => node}
menuItemRender={(itemInfo, node) => <Link to={itemInfo.path}>{node}</Link>}
menuProps={{
selectedKeys: [path],
openKeys: subs,
onOpenChange: handleOpenChange,
}}
headerContentRender={headerContentRender}
title={title}
logo={logo}
fixSiderbar={fixSiderbar}
fixedHeader={fixedHeader}
>
{children}
</ProLayout>
项目配置
import routes from './routes';
export default defineConfig({
routes,
qiankun: {
slave: {},
},
define: {
'process.env.ENV': 'dev',
BASE_REQUEST: '/api',
'process.env.BASE': '',
},
publicPath: '/metadata-web/',
antd: {},
nodeModulesTransform: {
type: 'none',
},
fastRefresh: {},
links: [{ rel: 'icon', href }],
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': '',
},
},
},
})
request
const request = extend({});
// 请求拦截
request.interceptors.request.use((url, options) => {
const headers = options.headers;
return {
// 代理
url: `${BASE_REQUEST}${url}`,
options: {
...options,
headers: {
...headers,
// token校验
token:
localStorage.getItem('token') ||
'dc41d433-f01f-4cc4-af16-340c6d74700a',
},
},
};
});
request.interceptors.response.use(async (response, option) => {
// 响应头
const contentType = response.headers.get('Content-type');
// 特殊响应头处理
if (contentType === 'application/octet-stream') {
const blob = await response.blob();
return {
data: blob,
code: 200,
};
}
// 请求失败(接口错误)
if (response.status !== 200) {
message.error('error');
return {
code: -1,
};
}
const res = await response.clone().json();
// 响应码错误
if (res.code !== 200) message.error(res.message || '请求错误');
return res;
});