效果圖
IMG_2230.GIF
創(chuàng)建umi項目
一直在用ant design pro做項目说铃,著實有點兒審美疲勞了,正好最近有個小項目嘗試下Material UI宦芦,發(fā)現(xiàn)Material UI也挺好用的宙址,在此做個總結(jié)。
創(chuàng)建umi項目
# 官方推薦使用pnpm構(gòu)建項目
pnpm dlx create-umi@latest
umi 目錄結(jié)構(gòu)
.
├── 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實現(xiàn)light/dark的切換
根據(jù)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": 菜單。
服務(wù)端返回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: 該函數(shù)直接修改routes注益,實現(xiàn)組件動態(tài)注冊碴巾。
Loadable: 進度條,類似于nprogress.js丑搔。