效果图
创建umi项目
一直在用ant design pro做项目,着实有点儿审美疲劳了,正好最近有个小项目尝试下Material UI,发现Material UI也挺好用的,在此做个总结。
创建umi项目
# 官方推荐使用pnpm构建项目
pnpm dlx create-umi@latest
umi 目录结构
.
├── config
│ └── config.ts
├── dist
├── mock
│ └── app.ts|tsx
├── src
│ ├── .umi
│ ├── .umi-production
│ ├── layouts
│ │ ├── BasicLayout.tsx
│ │ ├── index.less
│ ├── models
│ │ ├── global.ts
│ │ └── index.ts
│ ├── pages
│ │ ├── index.less
│ │ └── index.tsx
│ ├── utils // 推荐目录
│ │ └── index.ts
│ ├── services // 推荐目录
│ │ └── api.ts
│ ├── app.(ts|tsx)
│ ├── global.ts
│ ├── global.(css|less|sass|scss)
│ ├── overrides.(css|less|sass|scss)
│ ├── favicon.(ico|gif|png|jpg|jpeg|svg|avif|webp)
│ └── loading.(tsx|jsx)
├── node_modules
│ └── .cache
│ ├── bundler-webpack
│ ├── mfsu
│ └── mfsu-deps
├── .env
├── plugin.ts
├── .umirc.ts // 与 config/config 文件 2 选一
├── package.json
├── tsconfig.json
└── typings.d.ts
启用插件
# 安装插件
pnpm add -D @umijs/plugins
// config/config.ts
export default defineConfig({
//...
plugins: [
"@umijs/plugins/dist/tailwindcss.js",
"@umijs/plugins/dist/dva",
'@umijs/plugins/dist/initial-state',
'@umijs/plugins/dist/model',
],
// mock: false,
tailwindcss: {},
dva: {},
initialState: {},
model: {},
});
dva
// src/model/layout.ts
export interface LayoutModelState {
opened: boolean;
isOpen: Array<string>;
}
export default {
namespace: 'layout',
state: {
opened: true,
isOpen: [],
},
reducers: {
openLeftDrawer(state: LayoutModelState) {
return {
...state,
opened: state.opened,
};
},
openMenu(state: { isOpen: Array<string> }, { payload }: { payload: any }) {
const { menuId } = payload;
return {
...state,
isOpen: [ menuId ]
};
},
},
};
页面中使用dva
const mapStateToProps = (state: any) => {
return {
layout: state.layout,
loading: state.loading,
};
};
const Layout: React.FC = (props: any) => {
// 省略代码
return (
<>
<Main theme={themes} open={props.layout.opened}>
</>
)
};
export default connect(mapStateToProps)(Layout);
使用useDispatch, useSelector
const Layout: React.FC = (props: any) => {
const dispatch = useDispatch();
const layoutModel: LayoutModelState = useSelector((state: any) => {
return state.layout;
});
// 省略代码
return (
<>
<Main theme={themes} open={layoutModel.opened}>
</>
)
};
export default Layout;
编辑layout
...
<ColorModeContext.Provider value={colorMode}>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={themes}>
<CssBaseline />
<Box sx={{ display: 'flex' }}>
{/* header */}
<AppBar
enableColorOnDark
position="fixed"
color="inherit"
elevation={0}
>
<Toolbar>
<Header />
</Toolbar>
</AppBar>
{/* drawer */}
<Sidebar />
{/* main content */}
<Main theme={themes} open={props.layout.opened}>
{/* breadcrumb */}
<Outlet />
</Main>
<Customization />
</Box>
</ThemeProvider>
</StyledEngineProvider>
</ColorModeContext.Provider>
ColorModeContext: 用于切换light/dark模式。
createTheme
const themes = React.useMemo(
() =>
createTheme({
palette: {
mode,
...(mode === 'light'
? {
primary: {
light: colors.primaryLight,
main: colors.primaryMain,
200: colors.primary200,
},
secondary: {
light: colors.secondaryLight,
main: colors.secondaryMain,
},
}
: {
primary: {
light: colors.darkPrimaryLight,
main: colors.darkPrimaryMain,
200: colors.darkPrimary200,
},
secondary: {
light: colors.darkSecondaryLight,
main: colors.darkPrimaryMain,
},
}),
},
}),
[mode],
);
改变mode实现light/dark的切换
根据routes展示菜单
// routes
[
{
id: '1',
icon: 'dashboard',
title: 'Dashboard',
type: 'group',
caption: 'Dashboard Caption',
routes: [
{
id: '2',
path: '/dashboard',
icon: 'dashboard',
type: 'collapse',
title: 'Dashboard',
routes: [
{
id: '3',
path: '/dashboard/one',
type: 'item',
title: '测试菜单',
component: 'index'
},
{
id: '4',
path: '/dashboard/two',
type: 'item',
title: '测试菜单2',
component: 'docs'
}
]
},
{
id: '5',
path: '/dashboard/two',
type: 'item',
title: '测试菜单2',
component: 'docs'
}
],
},
]
type="group": 菜单分组。
type=“collapse”: 菜单目录。
type="item": 菜单。
服务端返回routes
// app.tsx
let extraRoutes: any[];
export function patchClientRoutes({ routes }: {routes: any}) {
routes[0].children = [];
forEachRouter(routes[0], extraRoutes);
}
function forEachRouter(currentRoute: any, routes: any) {
routes.forEach((route: any) => {
if(!!route.routes && route.routes.length > 0) {
forEachRouter(currentRoute, route.routes);
}
if(route.type === 'item') {
const tempComponent = Loadable(lazy(() => import(`@/pages/${route.component}`)));
currentRoute.children.unshift({
path: route.path,
element: createElement(tempComponent)
});
}
});
}
export async function render(oldRender: () => void) {
const res = await fetch("/api/sys/get/routes");
const data = await res.json()
extraRoutes = data.data;
oldRender();
}
patchClientRoutes: 该函数直接修改routes,实现组件动态注册。
Loadable: 进度条,类似于nprogress.js。