完整项目地址:ice-fusion-admin
前言
为什么我要做这个项目呢,简单来说 2 个目的。一是为了沉淀。目前自己还是做了很多 React 的中后台管理项目,也发现了这些项目的一些共通性,但缺少一个沉淀的项目和机会。因此最近打算从零搭建一个 React 中后台管理项目启动模板,总结目前自己在已有项目中学到的优秀方法。另外可能会有盆友发现项目名称是曾相识,没错,是借鉴了vue-element-admin这个项目,可以说这个项目是 Vue 技术栈里最好的中后台管理模板了,当初这个项目极大的提高了我 Vue 的开发水平,这也就是我做这个项目的第二个原因,帮助新学习 React 技术栈的同学们,希望在这个项目中大家可以相互交流学习。
初始化一个 Ice 基础项目
首先使用 ice cli 初始化一个简单的 Ts 项目
mkdir ice-fusion-admin && cd ice-fusion-admin npm init ice
选择简单模板
执行
npm install && npm start ,随后便可在浏览器中看到我们初始化的项目了
随后在 Vscode 中打开项目文件,可以看到如下的文件结构,如果对项目的工程配置需要有其他的定制,可以在 build.json 文件 中编写。所有配置项文档:ice.work/docs/config… 。
├── build/ # 构建产物
├── mock/ # 本地模拟数据
│ ├── index.[j,t]s
├── public/
│ ├── index.html # 应用入口 HTML
│ └── favicon.png # Favicon
├── src/ # 源码路径
│ ├── components/ # 项目公共组件,局部页面组件请放在对应 page 目录下的 components
│ │ └── Guide/
│ │ ├── index.[j,t]sx
│ │ └── index.module.scss
│ ├── pages/ # 页面
│ │ └── index.tsx/
│ ├── global.scss # 全局样式
│ └── app.[j,t]s[x] # 应用入口脚本
├── README.md
├── package.json
├── .editorconfig
├── .eslintignore
├── .eslintrc.[j,t]s
├── .prettierignore # prettier 忽略文件
├── .prettierrc.js # prettier 插件的配置文件,prettier 是一个美化代码的插件
├── .gitignore
├── .stylelintignore
├── .stylelintrc.[j,t]s
├── .gitignore
└── [j,t]sconfig.json # js/ts 配置文件,配置 js 版本、编译选项等
推荐安装 prettier 插件,并和团队协商共同的 prettier 插件的配置文件,可以让编码风格统一
如果项目中没有生成.prettierrc.js 配置文件的话,可以安装完插件后手动进行配置
// .prettierrc.js
const { getPrettierConfig } = require('@iceworks/spec');
// 直接引入ice提供的react对应prettier配置,我们在项目中也是这样使用
module.exports = getPrettierConfig('react');
// .prettierignore
build/
tests/
demo/
.ice/
coverage/
**/*-min.js
**/*.min.js
package-lock.json
yarn.lock
引入 Fusion Next 组件库
接下来就是在项目中引入 fusion 的 next 组件库了
首先安装 next 组件库。
npm install @alifd/next --save
随后安装 ice 提供的 fusion 插件
npm install build-plugin-fusion --save-dev
我们可以基于定制 fusion 主题等操作,所有支持的配置项可以查看
ice.work/docs/plugin…
安装完成后,在 build.json 文件中对插件进行配置
{
"plugins": [
[
"build-plugin-fusion",
{
"themePackage": "@alifd/theme-design-pro"
}
]
]
}
随后在项目中引入 next 组件库的 button 检查是否配置成功
//src/components/Guide
import * as React from 'react';
import { Button, Box } from '@alifd/next';
import styles from './index.module.scss';
const Guide = () => {
return (
<div className={styles.container}>
<h2 className={styles.title}>Welcome to icejs!</h2>
<Box direction="row" spacing={20} justify="center">
<Button type="normal">Normal</Button>
<Button type="primary">Prirmary</Button>
<Button type="secondary">Secondary</Button>
</Box>
<p className={styles.description}>This is a awesome project, enjoy it!</p>
</div>
);
};
export default Guide;
可以看到已经成功引入了 next 组件库
为应用添加 Layout
layout 也就是布局,页面整体布局是一个产品最外层的框架结构,往往会包含导航、侧边栏、面包屑以及内容等。想要了解一个后台项目,先要了解它的基础布局。从一般定义上来说,非业务的“应用内容”部分,也就是导航菜单、用户信息、页面头部、面包屑导航等部分就是属于 layout。
这里我们采用@alifd/fusion-design-pro-js 这款模板中提供的 Layout 组件 在 src 目录下新建 layouts 目录存放布局文件
这里我们使用了 2 个布局,一个是 BasicLayout,也就是包含了导航、侧边栏、面包屑等组件等应用内容布局,另一个是 UserLayout,用于用户登陆、注册等页面。
路由管理
在 ice.js 中可以使用约定式路由、配置式路由 2 种方式,官方推荐使用配置式路由,应用的路由信息统一在 src/routes.ts 中配置我个人也觉得这种配置方式比较好,尤其是在复杂的项目中,而且也方便进行个性化配置。 配置式路由:ice.work/docs/guide/…
约定式路由:ice.work/docs/guide/…
我们新建登陆、项目主页(dashboard),并配合上面导入的 layout 组件,配置完成的路由配置文件如下:
import React from 'react';
import { IRouterConfig } from 'ice';
import UserLayout from '@/layouts/UserLayout';
import BasicLayout from '@/layouts/BasicLayout';
import Login from '@/pages/Login';
import Workplace from '@/pages/Workplace';
import FeedbackNotFound from '@/pages/FeedbackNotFound';
// 懒加载路由
const Register = React.lazy(() => import('@/pages/Register'));
const routerConfig: IRouterConfig[] = [
{
path: '/user',
component: UserLayout,
children: [
{
path: '/login/:username',
component: Login,
},
{
path: '/register',
component: Register,
},
{
path: '/',
redirect: '/user/login',
},
],
},
{
path: '/',
component: BasicLayout,
children: [
{
path: '/dashboard/workplace',
component: Workplace,
},
{
// 404 没有匹配到的路由
component: FeedbackNotFound,
},
],
},
];
export default routerConfig;
但其实在实际项目中,我们还会对路由从不同子应用/子业务层面进行拆分,统一整合后再向外导出:
最后在 src/app.ts 中,我们可以配置路由的类型和基础路径等信息
import React from 'react';
import { runApp, IAppConfig } from 'ice';
const appConfig: IAppConfig = {
router: {
type: 'browser', // 路由类型browser是BrowserHistory模式,hash是HashHistory模式
basename: '/seller', //统一路径前置路径
fallback: <div>loading...</div>, //配合路由懒加载的加载组件
modifyRoutes: (routes) => {
// 动态路由配置
return routes;
},
},
app: {
rootId: 'ice-container',
},
};
runApp(appConfig);
在应用中跳转不同路由和获取参数的方法和使用 react-router 基本一样,其实 Ice 的路由功能也是基于 react-router 的封装
HashHistory 与 BrowserHistory
前端路由通常有两种实现方式:HashHistory 和 BrowserHistory,路由都带着 # 说明使用的是 HashHistory。这两种方式优缺点:
| 特点\方案 | HashHistory | BrowserHistory |
|---|---|---|
| 美观度 | 不好,有 # 号 | 好 |
| 易用性 | 简单 | 中等,需要 server 配合 |
| 依赖 server 端配置 | 不依赖 | 依赖 |
| 跟锚点功能冲突 | 冲突 | 不冲突 |
| 兼容性 | IE8 | IE10 |
| state 传递参数 | 不支持 | 支持 |
多环境
ice 默认默认情况下支持 start/build 两个环境,对应的即 icejs start/build 两个命令,也就是所谓的本地开发/打包发布代码模式,但是实际开发中会需要多套模式的支持,比如我部门目前一个项目有就通常有日常、预发、线上等 3 套环境,在不同编译环境下会需要启动不同等插件,在 ice 项目中,我们可以通过 --mode 参数来设置不同环境,比如我这里新增加 pre 、daily 模式,在并且配置在这 2 个模式下关闭 mock 服务
封装一个请求方法
之前有写过一篇详细的文章介绍封装过程,有兴趣的朋友可以看看 👀,基于 Axios 封装一个带缓存功能的请求方法 在 src 目录下建立 utils 文件夹,存放项目中需要的公共方法
管理所有的接口模块
一般中后台项目都会涉及到数量众多的接口,合理的管理这些接口有助于更好的维护项目,这是我目前项目中 Api 模块等一部分
如图可见模块有很多,而且随着业务的迭代,模块还会会越来越多。 所以这里建议根据业务模块来划分 pages,在 src 目录下建立 service 文件夹放置 api 模块,并且将 pages 和 service 两个模块一一对应,从而方便维护。如下图:
在此项目中我们先建立一个简单的登陆接口模块
跨域问题
前后端交互最长遇到的就是跨域问题,我们团队都是通过访问后端地址代理前端资源的方式来解决,也就是直接访问后端服务提供的页面 url 地址,然后将页面中加载的资源代理成本地调试的资源
这种方案 ice 也提供了对应的支持
- 通过 icejs 插件 build-plguin-smart-debug
- chrome 插件 xswitch
其他方案还有通过cros、以及本地代理的方式,但 cros 方案需要后端的支持,所以还是看团队协商怎样方便,在这个项目中,我们就使用本地代理的方式来解决跨域问题。其他跨域解决方案可以参考:ice.work/docs/guide/…
我们可以在 build.json 文件中配置 proxy 字段控制跨域,这种方式比较适合简单配置,但并不能完全满足需求,ice 的跨域解决方案本质还是基于http-proxy-middleware实现的,所以支持的配置项和http-proxy-middleware一致。但 json 文件中并不能支持函数配置项(当然也可以开启 JS 类型的配置文件,同时需要在 npm scripts 中指定配置文件),这里我们可以用插件的形式实现功能,使用单独的配置文件来控制跨域配置。 在根目录新建 setupProxy.ts 配置文件,写入配置内容:
const merge = require('lodash/merge');
module.exports = ({ context, onGetWebpackConfig }) => {
onGetWebpackConfig((config) => {
// 这里配置需要开启代理的mode,何为mode?see:https://ice.work/docs/guide/basic/config
// 根据配置的不同启动mode,自动使用不同的代理
const proxyModes = {
// 所有支持的参数,详细配置教程 https://github.com/chimurai/http-proxy-middleware
pre: {
'/api': {
target: 'http://www.preServer.org',
},
'/preOnly': {
target: 'http://www.preOnlyServer.org',
},
},
daily: {
'/api': {
target: 'http://www.dailyServer.org',
},
},
};
const { mode } = context.commandArgs;
if (!proxyModes[mode]) {
return;
}
const proxyRules = Object.entries(proxyModes[mode]);
const originalDevServeProxy = config.devServer.get('proxy') || [];
if (proxyRules.length) {
const proxy = proxyRules
.map(([match, opts]) => {
const { target, ...proxyRule } = opts;
return merge(
{
target,
changeOrigin: true,
logLevel: 'warn',
onProxyRes: function onProxyReq(proxyRes, req) {
proxyRes.headers['x-proxy-by'] = 'ice-proxy';
proxyRes.headers['x-proxy-match'] = match;
proxyRes.headers['x-proxy-target'] = target;
let distTarget = target;
if (target && target.endsWith('/')) {
distTarget = target.replace(/\/$/, '');
}
proxyRes.headers['x-proxy-target-path'] = distTarget + req.url;
},
onError: function onError(err, req, res) {
// proxy server error can't trigger onProxyRes
res.writeHead(500, {
'x-proxy-by': 'ice-proxy',
'x-proxy-match': match,
'x-proxy-target': target,
});
res.end(`proxy server error: ${err.message}`);
},
},
proxyRule,
{ context: match }
);
})
.filter((v) => v);
config.devServer.proxy([...originalDevServeProxy, ...proxy]);
}
// http://localhost:3000/api/foo/bar -> http://www.example.org/api/foo/bar
});
};
最后在 build.json 中将 setupProxy.ts 配置为一个插件进行引入
这样就能在项目编译之前自动执行代理脚本了。这个配置文件本质上就是一个 ice.js 的插件,如果有其他的工程配置需求也可以像这样做个定制插件,并直接在 build.json 中使用,详情 🔎 查看 ice 插件开发指南
前后端的交互和 Mock 数据
从我自己的开发经历来看,平时的开发中交流成本占据了我们很大一部分时间,但前后端如果有一个好的协作方式的话能解决很多时间。我们开发流程都是前后端和产品一起开会讨论项目,之后后端根据需求,首先定义数据格式和 api,有些复杂的接口会和我们前端一起讨论。定好接口规范的后,一般前端会会先对一些页面 UI 进行开发,后端 Swagger 文档写好后我们通过Pont(一个数据调试平台) 生成接口都 mock 数据,进行数据交互开发。有一说一,Pont 是真的好用,强烈推荐,尤其是使用 ts 开发项目。节省了非常多的前后端沟通成本。但也有少数时候后端没写 swwager 的,这时候一般就直接使用本地 Mock 服务了,ice 也提供了开箱即用的 Mock 服务。参见文档:ice.work/docs/guide/… 我们首先在项目根目录下建立 mock 文件夹,mock/ 文件夹下面的所有 js/ts 都会被框架自动加载,因此你可以把接口合理的拆分到多个 mock 文件中,推荐是和在 service 目录下的业务接口文件一一对应,然后在 mock/ 文件夹下面建立 index.ts 存放所有公共的 mock 接口配置。
占坑
从零开始,构建复杂 React 中后台项目,完整项目地址:ice-fusion-admin
后续内容持续更新中,欢迎关注💕~