这个模板比原来的基于MUI系列的要容易不少,因为MUI那个系列是强组件,Tailwind这些东西感觉还是有点像原来写Bootstrap。
从顶层一下下看下去
main.tsx
import { Suspense } from 'react'
import { createRoot } from 'react-dom/client'
import '../src/css/globals.css'
import App from './App.tsx'
import Spinner from './views/spinner/Spinner.tsx'
createRoot(document.getElementById('root')!).render(
<Suspense fallback={<Spinner />}>
<App />
</Suspense>
)
这个比较简单。
其中的Suspense来自于React,要搭配lazy使用,所以能想到,APP中的所有路由,肯定都使用了lazy加载。
Spinner如下:
import { FC } from "react";
import "./spinner.css";
const Spinner: FC = () => (
<div className="fallback-spinner">
<div className="loading component-loader">
<div className="effect-1 effects" />
<div className="effect-2 effects" />
<div className="effect-3 effects" />
</div>
</div>
);
export default Spinner;这里的很多ccs类都是这个模板自带的,就不去改了。
接着看App.tsx
App.tsx
import { RouterProvider } from 'react-router';
import router from './routes/Router';
import './css/globals.css';
import { ThemeProvider } from './components/provider/theme-provider';
function App() {
return (
<>
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
<RouterProvider router={router} />
</ThemeProvider>
</>
);
}
export default App;这里也很简单,一个是主题提供,就提供了一个dark,以及LocalStorage的键值。
然后就是路由了,接下来就是页面了,还是使用的传统的路由方式匹配,确实比之前的框架好理解。
globals.css就是这个框架自己在tailwind上做的一些设置,我把global里改了一下设置,改成了Inter字体,而且下载之后本地加载,这样就保证不会有问题了。
router.tsx
这个文件很简单,用的旧式路由,倒也好,直接可以匹配我原来的路径设计。
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
import { lazy } from 'react';
import { Navigate, createBrowserRouter } from 'react-router';
import Loadable from '../layouts/full/shared/loadable/Loadable';
/* ***Layouts**** */
const FullLayout = Loadable(lazy(() => import('../layouts/full/FullLayout')));
const BlankLayout = Loadable(lazy(() => import('../layouts/blank/BlankLayout')));
// authentication
const Login2 = Loadable(lazy(() => import('../views/authentication/auth2/Login')));
const Register2 = Loadable(lazy(() => import('../views/authentication/auth2/Register')));
const Maintainance = Loadable(lazy(() => import('../views/authentication/Maintainance')));
// Dashboards
const Modern = Loadable(lazy(() => import('../views/dashboards/Modern')));
//pages
const UserProfile = Loadable(lazy(() => import('../views/pages/user-profile/UserProfile')));
/* ****Apps***** */
const Notes = Loadable(lazy(() => import('../views/apps/notes/Notes')));
const Form = Loadable(lazy(() => import('../views/utilities/form/Form')));
const Table = Loadable(lazy(() => import('../views/utilities/table/Table')));
const Tickets = Loadable(lazy(() => import('../views/apps/tickets/Tickets')));
const CreateTickets = Loadable(lazy(() => import('../views/apps/tickets/CreateTickets')));
const Blog = Loadable(lazy(() => import('../views/apps/blog/Blog')));
const BlogDetail = Loadable(lazy(() => import('../views/apps/blog/BlogDetail')));
const Error = Loadable(lazy(() => import('../views/authentication/Error')));
// // icons
const SolarIcon = Loadable(lazy(() => import('../views/icons/SolarIcon')));
// const SamplePage = lazy(() => import('../views/sample-page/SamplePage'));
const Router = [
{
path: '/',
element: <FullLayout />,
children: [
{ path: '/', exact: true, element: <Modern /> },
// { path: '/', exact: true, element: <SamplePage /> },
{ path: '*', element: <Navigate to="/auth/404" /> },
{ path: '/apps/notes', element: <Notes /> },
{ path: '/utilities/form', element: <Form /> },
{ path: '/utilities/table', element: <Table /> },
{ path: '/apps/tickets', element: <Tickets /> },
{ path: '/apps/tickets/create', element: <CreateTickets /> },
{ path: '/apps/blog/post', element: <Blog /> },
{ path: '/apps/blog/detail/:id', element: <BlogDetail /> },
{ path: '/user-profile', element: <UserProfile /> },
{ path: '/icons/iconify', element: <SolarIcon /> },
],
},
{
path: '/',
element: <BlankLayout />,
children: [
{ path: '/auth/auth2/login', element: <Login2 /> },
{ path: '/auth/auth2/register', element: <Register2 /> },
{ path: '/auth/maintenance', element: <Maintainance /> },
{ path: '404', element: <Error /> },
{ path: '/auth/404', element: <Error /> },
{ path: '*', element: <Navigate to="/auth/404" /> },
],
},
];
const router = createBrowserRouter(Router);
export default router;创建Router的路由描述对象,然后使用const router = createBrowserRouter(Router) 创建路由对象,最后交给Provider来消费。这也是React Router的老版本的标准使用方式。
这里还有一个Loadable 高阶组件,看一下:
import { Suspense, ComponentType, FC } from 'react';
import Spinner from '../../../../views/spinner/Spinner';
const Loadable = <P extends object>(
Component: ComponentType<P>
): FC<P> => {
return function LoadableComponent(props: P) {
return (
<Suspense fallback={<Spinner />}>
<Component {...props} />
</Suspense>
);
};
};
export default Loadable;这个组件不复杂,主要是提供了一个高阶组件的通用定义类型的方法。
TS里一个函数的参数类型,返回类型的写法,是这样的:
// 1. 先定义函数的“形状”:接收两个 number,返回一个 number
type CalcFunction = <泛型变量定义>(a: number, b: number) => number;
// 2. 声明函数时,直接把类型套在变量名上,后面就不需要重复写类型了
const sub: CalcFunction = (a, b) => { return a - b; };主要是要熟悉这种方式,还有就是React提供的类型。高阶组件基本上固定是这样的。接下来要研究页面了。
前边的那些都使用了FullLayout,这个将来肯定也是作为路由守卫存在的。
FullLayout
import { FC } from 'react';
import { Outlet } from 'react-router';
import Sidebar from './vertical/sidebar/Sidebar';
import Header from './vertical/header/Header';
const FullLayout: FC = () => {
return (
<>
<div className="flex w-full min-h-screen">
<div className="page-wrapper flex w-full ">
{/* Header/sidebar */}
<div className="xl:block hidden">
<Sidebar />
</div>
<div className="body-wrapper w-full bg-white dark:bg-dark">
{/* Top Header */}
<Header />
{/* Body Content */}
<div className={'container mx-auto px-6 py-30'}>
<main className="grow">
<Outlet />
</main>
</div>
</div>
</div>
</div>
</>
);
};
export default FullLayout;最外层<div className="flex w-full min-h-screen"> 是tailwind css的类,分别是设置 display:flex, width:100%和min-height: 100vh;来填满整个视口,并且是一个弹性盒子。
内层的<div className="page-wrapper flex w-full "> 也是弹性盒子,因为其内部包含了侧边栏和主页面。page-wrapper不是tailwind css的类。
再往里边xl:block hidden指的是在xl以下需要隐藏,xl以上显示,这是侧边栏的控制。
接下来是包裹了顶部导航条和正文内容的大div,这里使用了根据类的样式来控制黑白主题,这个设置一定要学一下,是tailwind css的特色。
<div className={'container mx-auto px-6 py-30'}>
这里的container也是tailwind css 的类了,类似于以前Bootstrap的container,自动根据屏幕大小限制宽度,不会无限拉伸。实验了一下,在2K屏幕上,中间的部分就缩小了一些,并没有填满全部的宽度。
接下来的grow也是tailwind css的类,相当于:flex-grow: 1 ,即占用全部的大小。这样即使Outlet内容不够,也会撑满页面。
接下来就看侧边栏Sidebar了。