目录
- 一. 简述
- 二. 目录规划
- 三. Vite 配置
- 3.1. 配置路径别名
- 3.2. 配置 less
- 四. 页面
- 4.1. 入口文件
- 4.2. 骨架文件
- 4.3. 普通页面
- 五. 路由配置
- 六. 预览启动
一. 简述
上一篇文章我们介绍了项目初始化,此篇文章我们会先介绍下当前项目的目录规划,接着对vite
配置以便我们后续的开发,最后会根据 xxl-job
的页面创建我们项目的页面并配置路由信息。
二. 目录规划
一般来说前端项目可以分为下面几个部分:页面、路由、状态管理、静态资源、工具方法。结合我们的项目我调整了下项目的目录结构如下:
api
:存放api
定义assets
:存放静态文件components
:公共组件hooks
:公共的React Hooks
pages
:存放页面router
:路由信息store
:存放状态管理文件types
:定义的接口交互的接口utils
:常用的一些工具类
如果有其他的目录结构设计,可以评论区交流!
三. Vite 配置
上面我们规划了项目的目录结构,接着我们配置下 vite
。
3.1. 配置路径别名
配置路径别名之后可以省去写冗长的相对路径。
我们只需要在vite.config.ts
中添加如下配置:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"@assets": path.resolve(__dirname, "src/assets"),
"@pages": path.resolve(__dirname, "src/pages"),
"@store ": path.resolve(__dirname, "src/store"),
"@images ": path.resolve(__dirname, "src/assets/images"),
},
}
})
另外还需要修改 tsconfig.json
,否者引入路径会飘红。
{
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
...
}
3.2. 配置 less
我们在项目中使用less
做样式管理,需要在vite.config.ts
中配置如下:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from "path";
// https://vitejs.dev/config/
export default defineConfig({
... 忽略
css: {
preprocessorOptions: {
less: {
// 全局变量
modifyVars: {},
javascriptEnabled: true,
},
},
},
})
后续我们在实现切换主题的功能的时候还会在配置这个地方,现在先放一放。
四. 页面
在 xxl-job
的任务调度中心有登录页面和管理页面组成,而管理页面也是我们常规管理系统的页面结构(结构如下)。
所以菜单栏、头部、尾部相当于管理页面的骨架,内容部分是需要切换路由动态展示的。做过管理系统的同学们应该很快就可以大体规划出页面模块。下面看一下我在pages
目录下定义的页面模块如下:
course
:使用教程dispatch
:调度日志exception
:异常页面executor
:执行器管理layout
:页面骨架login
:登录页面task
:任务管理user
:用户管理index.tsx
:入口文件
日常开发建议:在定义模块目录的时候,最好见名知意,路由表也最好和模块名称对应(遇到 BUG可以最快的定位到问题页面)。主打一个不防御编程。
4.1. 入口文件
在 index.tsx 中,我们会配置引入定义的路由和 antd
的配置组件,内容如下:
import {ConfigProvider} from "antd";
import { RouterProvider, createBrowserRouter } from "react-router-dom";
import RouterSpace from "@/router";
import zhCN from "antd/lib/locale/zh_CN";
import routers = RouterSpace.routers;
const Application = () => {
return <ConfigProvider locale={zhCN}>
<RouterProvider router={createBrowserRouter(routers)} />
</ConfigProvider>
}
export default Application;
这里我们使用
ConfigProvider
全局化配置:https://ant-design.antgroup.com/components/config-provider-cn,方便我们使用国际化和配置组件样式等,具体可以看文档。
其中<RouterProvider router={createBrowserRouter(routers)} />
,是通过createBrowserRouter
创建一个路由表,然后通过RouterProvider
向下传递。
这里需要注意下BrowerserRouter
和HashRouter
的区别:
BrowerserRouter
:利用H5 history API
实现url
地址改变,并通过pushState
和replaceState
改变URL
,但不会触发浏览器的刷新。这使得单页应用程序可以像多页应用程序一样具有多个路由;虽然此路由更加自然、美观,但是刷新页面的时候请求服务器,可能会导致 404错误;HashRouter
:使用window.location.hash
属性和window.onhashchange
事件。可以监听浏览器hash
值得变化,去执行相应的Js
切换网页。使用URL
中的hash
(#
)部分来进行路由管理;正是因为路由信息是存储在 URL 的哈希部分,不会出发服务器请求,但是哈希符号,影响美观;
所以在正常项目中我们都是需要和服务端配置使用的,建议使用BrowerserRouter
就可以,在一些涉及到登录认证、菜单权限的权限的时候BrowerserRouter
会它的妙用。
4.2. 骨架文件
骨架文件是为了定义公共组件部分,并提取出动态组件部分,代码如下:
import {Link, Outlet} from "react-router-dom"
const LayoutPage = () => {
return <div>
{/* 菜单 */}
<div>
<Link to={'/xxl-job/report'}>运行报表</Link>
<br/>
<Link to={'/xxl-job/task'}>任务管理</Link>
<br/>
<Link to={'/xxl-job/dispatch'}>调度日志</Link>
<br/>
<Link to={'/xxl-job/executor'}>执行器分管理</Link>
<br/>
<Link to={'/xxl-job/user'}>用户管理</Link>
<br/>
<Link to={'/xxl-job/course'}>使用教程</Link>
<br/>
<Link to={'/login'}>退出登录</Link>
</div>
{/* 内容 */}
<div>
{/* 头 */}
<div></div>
{/* 内容 */}
<div>
<Outlet />
</div>
</div>
</div>
}
export default LayoutPage
Outlet
是一个用于渲染子路由的组件。它通常与 Route
配合使用,用于在父路由中指定子路由的渲染位置。Outlet
充当了子路由渲染的占位符,告诉 React Router
在当前组件中的哪里渲染子路由。
Link
是 React Router
提供的组件之一,用于在应用中创建导航链接。它通常用于代替传统的 <a>
标签,提供了一种在单页面应用(SPA
)中进行客户端路由导航的方式,而无需进行页面的完整刷新。
下一篇文章会介绍
css
组件如何使用。
4.3. 普通页面
除了骨架和入口文件,其他模块暂时都是简单的普通页面,如下图:
这些模块都如 task
目录下的 index.tsx
文件内容都是类似,内容如下:
const TaskPage = () => {
return <div>任务管理</div>
}
export default TaskPage;
这里需要注意函数名称的命名规则,在定义导出函数名称的时候,最好是模块 + Page,这样可以很好的区别其他组件和页面组件。
五. 路由配置
我们在入口文件中通过createBrowserRouter(routers)
创建路由表,这里面的 routers
就是我们需要定义的路由表结构信息。
import {Navigate, RouteObject} from "react-router-dom";
import LoginPage from "@/pages/login";
import TaskPage from "@/pages/task";
import ReportPage from "@/pages/report";
import DispatchPage from "@/pages/dispatch";
import ExecutorPage from "@/pages/executor";
import UserPage from "@/pages/user";
import CoursePage from "@/pages/course";
import LayoutPage from "@/pages/layout";
import NotFoundPage from "@/pages/exception/404.tsx";
namespace RouterSpace {
export const routers: RouteObject[] = [
{ path: '/login', element: <LoginPage /> },
{
path: '/xxl-job',
element: <LayoutPage />,
children: [
{ path: 'report', element: <ReportPage /> },
{ path: 'task', element: <TaskPage /> },
{ path: 'dispatch', element: <DispatchPage /> },
{ path: 'executor', element: <ExecutorPage /> },
{ path: 'user', element: <UserPage /> },
{ path: 'course', element: <CoursePage /> },
{ path: '404', element: <NotFoundPage /> },
{ path: "*", element: <Navigate to={'/xxl-job/404'} /> }
]
},
]
}
export default RouterSpace;
后面我们会优化这块路由表,改为 lazy 加载。
这里我们需要注意下RouteObject
,现阶段我们只使用了 path
、element
和 children
三个属性,这里面还有很多重要的属性可以参看文档:https://reactrouter.com/en/main/route/route#type-declaration。
interface RouteObject {
path?: string; // 指定路由的路径
index?: boolean; // 默认子路由ßß
children?: React.ReactNode; // 定义子路由,是一个包含其他RouteObject的数组。子路由的路径会相对于父路由的路径。
caseSensitive?: boolean; // 表示路由是否区分大小写,默认为false
id?: string; // 为路由指定唯一的标识符
loader?: LoaderFunction; // 一个异步加载函数,用于动态加载路由组件。鉴权的时候使用
action?: ActionFunction; // 定义路由的生命周期函数,用于在路由渲染前或渲染后执行一些操作
element?: React.ReactNode | null; // 指定路由匹配时要渲染的 React 元素
hydrateFallbackElement?: React.ReactNode | null;
errorElement?: React.ReactNode | null; // 在发生错误时渲染的元素
Component?: React.ComponentType | null;
HydrateFallback?: React.ComponentType | null;
ErrorBoundary?: React.ComponentType | null;
handle?: RouteObject["handle"];
shouldRevalidate?: ShouldRevalidateFunction; // 一个函数,用于定义路由是否应该重新验证
lazy?: LazyRouteFunction<RouteObject>; // 用于懒加载路由的函数
}
其他属性的使用可以参考我的另一个开源项目:https://gitee.com/molonglove/go-react-admin.git,里面有关于动态路由、权限等与路由相关的使用。
这里还有注意点:{ path: "*", element: <Navigate to={'/xxl-job/404'} /> }
,这个路由定义需要放在最后
,意味着如果路由匹配失败会跳转的路由地址。
六. 预览启动
执行 yarn dev
查看执行效果,就可以看到如下的效果!
下一篇文章我们将介绍借助 antd
实现登录页面和管理页面的Layout
骨架。