umijs4集成Material UI

效果圖

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丑搔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厦瓢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子啤月,更是在濱河造成了極大的恐慌煮仇,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谎仲,死亡現(xiàn)場離奇詭異浙垫,居然都是意外死亡,警方通過查閱死者的電腦和手機郑诺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門夹姥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辙诞,你說我怎么就攤上這事佃声。” “怎么了倘要?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵圾亏,是天一觀的道長。 經(jīng)常有香客問我封拧,道長志鹃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任泽西,我火速辦了婚禮曹铃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捧杉。我一直安慰自己陕见,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布味抖。 她就那樣靜靜地躺著评甜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪仔涩。 梳的紋絲不亂的頭發(fā)上忍坷,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音,去河邊找鬼佩研。 笑死柑肴,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的旬薯。 我是一名探鬼主播晰骑,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼绊序!你這毒婦竟也來了硕舆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤政模,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蚂会,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體淋样,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年胁住,在試婚紗的時候發(fā)現(xiàn)自己被綠了趁猴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡彪见,死狀恐怖儡司,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情余指,我是刑警寧澤捕犬,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站酵镜,受9級特大地震影響碉碉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜淮韭,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一垢粮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧靠粪,春花似錦蜡吧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至畔乙,卻和暖如春耀鸦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工袖订, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留氮帐,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓洛姑,卻偏偏與公主長得像上沐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子楞艾,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容