umijs4集成Material UI

效果图

IMG_2230.GIF

创建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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容