antd-custom-tpl
Version:
antd-custom项目模板
339 lines (262 loc) • 13.7 kB
Markdown
# antd + react + redux + webpack4 + react-router4基础框架
---
## 说明
该基础框架采用第三方依赖包都是目前最新版本,结合antd的UI框架,实现主题定制,webpack自主配置,动态菜单路由设计,redux数据管理,国际化多语言,错误统一处理,本地mock服务等功能。
设计原则:
1)开放性:秉承自由开放精神,绝对不会给大家造任何的黑盒子(啥叫黑盒子,就是它规定你怎么写,你就得怎么写,它不更新,你也休想正常使用,当然除了react,antd等第三方框架),项目中数据管理(伪dva),webpack配置,动态路由设计等,都是采用自由开放思想,大家可根据自己实际项目需要自行修改。
2)多选择性:该框架有目前存在多个版本供大家选择
> 1)test分支(min版本,里面只有静态菜单路由,数据管理,按需加载几个简单功能,适合小型项目)
>
> 2)master分支(默认,稳定版本)
>
> 3)develop分支(开发版本,最新的版本)
>
> 4)pre分支(健全版本,由min版本升级后的第一个版本,功能相对健全)
功能说明:
+ [1.菜单配置](#function.menus)
+ [2.动态路由](#function.route)
+ [3.权限控制](#function.perm)
+ [4.数据管理](#function.data)
+ [5.按需加载](#function.lazy)
+ [6.国际化语言](#function.intl)
+ [7.本地mock服务](#function.mock)
+ [8.路径alias别名](#function.alias)
+ [9.错误统一处理](#function.error)
+ [10.安全CSRF防范](#function.csrf)
+ [11.导出当前页](#function.export)
## 初始化项目
```bash
$ npm i generator-antd-custom -g
$ cfe init react
```
## 切换至指定版本分支(需要用git初始化项目)
```bash
$ git init
$ git clone https://github.com/ctq123/antd-custom.git
$ git checkout N
N = test || develop || pre
```
## 运行
```bash
$ npm start
```
or
```bash
$ npm run local
```
## 打包
```bash
$ npm run build:test
```
or
```bash
$ npm run build:prod
```
## 结构说明

## 语法规范说明
1.针对tabs业务模块,不再使用文件名称代表某个业务模块,建议使用目录名称替代。比如主页模块,目录名称home就代表home业务模块,home目录下采用index.js作为模块入口
2.由于react已经采用react16.9版本,切勿继续使用componentWillMount和componentWillReceiveProps等即将要废弃的生命周期,免得要在react17版本中增加维护成本
...
## 功能说明
+ [1.菜单配置](#function.menus)
+ [2.动态路由](#function.route)
+ [3.权限控制](#function.perm)
+ [4.数据管理](#function.data)
+ [5.按需加载](#function.lazy)
+ [6.国际化语言](#function.intl)
+ [7.本地mock服务](#function.mock)
+ [8.路径alias别名](#function.alias)
+ [9.错误统一处理](#function.error)
+ [10.安全CSRF防范](#function.csrf)
+ [11.导出当前页](#function.export)
### <span id="function.menus">1.菜单配置</span>
新增一个菜单需要两个步骤:
1)在menus中的menu.data.js文件中添加新菜单,其中key和path都是唯一值(其中permKey是权限字段,[权限控制](#function.perm)中会介绍)
2)在对应的tabs模块下添加index.menux.js文件配置,其中key和path要与步骤1中保持一致(其中routeProps是路由属性,[动态路由](#function.route)中会介绍)
涉及范围:
> menus/menu.data.js
>
> tabs/../index.route.js
### <span id="function.route">2.动态路由</span>
项目中采用[react-router4](https://reacttraining.com/react-router/)管理路由,路由又分为静态路由和动态路由
1)静态路由:src/App.js中转跳app页面和login页面采用的是静态路由设计,它属于页面层级,并不会因为用户权限的差异而不同
2)动态路由:pages/app/index.js中采用的是动态生成路由,它会因为用户权限的不同而存在差异
动态生成路由逻辑:
> 1)先去pages/tabs业务目录下查找出所有业务模块index.route.js文件
>
> 2)根据index.route.js查找routeProps属性生成Route
>
> 3)根据传入用户菜单列表的menuList查找父级菜单下的第一个子菜单生成Redirect(业务需求:点击父级菜单路由会转跳至第一个有效子菜单)
>
> 详情:menus/menu.route.js
涉及范围:
> pages/app/index.js
>
> menus/menu.route.js
>
> tabs/../index.route.js
app内菜单处理技术方案:
> 1)路由转跳方案:点击菜单加载tab页面文件,转跳到对应的路由并渲染页面,本项目采用的是路由转跳处理方案
>
> 2)新增tab方案:点击菜单加载tab页面文件,新增tab渲染页面,若采用该技术实现方案,可将上述涉及范围删除
### <span id="function.perm">3.权限控制</span>
用户登陆成功后,会向后端获取用户权限列表,是一个权限key值的列表,分为菜单权限和功能权限
1)菜单权限:
在menus/menu.data.js路由中配置permKey值,获取到用户权限列表后,重新生成新的菜单列表
2)路由权限:
在index.route.js路由中配置permKey值,获取到用户权限列表后,重新生成新的路由列表
3)功能权限:
若页面中存在某个按钮只有某些角色(权限)才能看到,根据用户权限列表判断是否需要显示该按钮,如tabs/example模块
涉及范围:
> pages/app/index.js
>
> menus/menu.data.js
>
> menus/menu.permission.js
>
> utils/permission.js
>
> tabs/../index.route.js
更多权限技术方案:
> 1)前后端结合方案:用户登陆后,获取用户权限列表,前端根据用户权限列表生成相应的菜单,但需要前端配置菜单权限key值;
>> 优势:路由权限和功能权限可以一次性获取
>
> 2)后端判断方案:用户登陆后,后端判断该用户权限,直接返回菜单列表,前端直接显示
>> 优势:前端不需要重新生成菜单,也不需要配置权限key值
>> 劣势:若有菜单内颗粒度更小的按钮功能等权限时,还需要再次向后端获取权限列表值,判断是否需要显示
### <span id="function.data">4.数据管理</span>
数据管理采用的是[redux](https://www.redux.org.cn/)+[redux-saga](https://redux-saga-in-chinese.js.org/)方案,这里参考dva的部分设计思想,自己重新生成一套数据管理方案,使用方法与dva非常相似
如果你还不熟悉[redux](https://www.redux.org.cn/)和[redux-saga](https://redux-saga-in-chinese.js.org/),请自行学习和了解
如果你不想了解它们,那也没关系,按照以下两个步骤使用即可,不过我还是建议你抽空学习并掌握它们
新增一个菜单数据管理需要两个步骤:
以tabs/home模块为例
1)在home模块下创建一个index.model.js文件,其包含四个属性name, state, reducers, effects。
> name: model名称,model中的name需要保持唯一,它是挂在store下state对应的key值
>
> state: 初始state状态
>
> reducers: reducer纯函数对象
>
> effects: [redux-saga](https://redux-saga-in-chinese.js.org/)处理异步请求的副作用对象
2)在home/index.js文件中使用connect()引入该模块的state属性,即步骤1中的name,它对全局有效
> 说明:reducers和effects函数的key,最好以步骤1中的name开头,调用时业务清晰明了,同时方便后期全局的搜索和维护
涉及范围:
> pages/../index.model.js
>
> pages/../index.js
>
> redux/*
核心设计思想:
> 1)遍历pages/*目录下index.model.js找出{name, state, reducers},并将其重新整合生成redux的reducer
>
> 2)遍历pages/*目录下index.model.js找出effects,将其重新整合生成redux-saga
>
> 更多详情请看[redux最佳实践的前世今生2](https://github.com/ctq123/blogs/issues/2)
**redux建议使用场景**
> 1.兄弟组件之间通信,不建议在直接父子组件中滥用redux
>
> 2.跨多层父子组件之间通信,比如爷爷和孙子,曾祖父和曾孙等。redux原理之一就是基于这个来做的。
>
>>理论上,在index.js中直接使用axios处理异步已满足百分之九十业务场景了,比如user模块;
>>
>>只有一些跨兄弟组件通信或者跨多层父子组件才需要使用redux,比如login模块;
>>
>>home模块中的示例只是为了方便介绍redux的使用方法,故意而为之
### <span id="function.lazy">5.按需加载</span>
按需加载处理方案:
1)采用require.ensure()处理
2)采用react.lazy()处理,react16.6引入,利用import()原理处理懒加载,暂时还不支持服务器渲染
3)采用[loadable-components](https://github.com/smooth-code/loadable-components)处理,支持服务器渲染,也是react官方推荐处理服务器渲染方案。
后面两种方案都是采用[import()](https://zh-hans.reactjs.org/docs/code-splitting.html#import)原理进行代码切割,并使用promise进行异步加载。
由于本项目不涉及服务器渲染,采用第二种技术方案,更重要的是它是react原生官方的,是亲生儿子,比任何第三方插件都可靠。
### <span id="function.intl">6.国际化语言</span>
目前处理国际化语言最流行的两种解决方案是[react-intl](https://github.com/formatjs/react-intl/blob/master/docs/Components.md)和[i18n](https://lingui.js.org/ref/react.html#component-I18nProvider)
本项目采用的是antd官方使用的一套国际化插件,[react-intl3](https://github.com/formatjs/react-intl/blob/master/docs/Components.md),也是非常流行的一套解决方案,但目前网上的文章多是react-intl2的,react-intl3配置和使用方法均出现了较大的变化,具体可看官网。
react-intl有两种使用方法:
>1)dom场景:直接生成HTML对应的翻译文本dom,如home模块(home/index.js)
>
>2)字符串场景:直接生成string字符串翻译文本字符串,如user模块(user/index.js)
实际使用场景中需要在locales/目录下配置对应的key-value值,通过引用key来显示对应的文字
涉及范围:
> src/components/react-intl/*
>
> locales/*
>
> pages/../index.js
>
> src/components/*
### <span id="function.mock">7.本地mock服务</span>
这里采用的是一个外部插件cf-mock-server作为本地mock服务,具体配置可在webpack中配置
webpack.config.js
```js
module.exports = {
//...
devServer{
//...
after: (app, server) => {
app.use(mock({
config: path.join(__dirname, './mock-server/config.js')
}))
},
}
}
```
然后创建mock-server文件夹及其配置文件config.js,最后在config.js配置对应的接口API以及对应json文件数据即可,具体可看home模块的例子
涉及范围:
> mock-server/*
### <span id="function.alias">8.路径alias别名</span>
通过创建import或require的别名,来确保模块的引入变得更简单。
webpack.config.js
```js
module.exports = {
//...
resolve: {
alias: {
'@assets': path.join(__dirname, 'assets'),
'@src': path.join(__dirname, 'src'),
'@components': path.join(__dirname, 'src/components'),
'@utils': path.join(__dirname, 'src/utils'),
'@menus': path.join(__dirname, 'src/menus'),
'@locales': path.join(__dirname, 'src/locales'),
}
}
}
```
通常我们使用的编辑器是vscode,上述只对webpack有效,vscode编辑器它并不知道,command+点击并不会发生转跳,因此需要添加jsconfig.json配置
jsconfig.json
```js
{
"compilerOptions": {
"checkJs": false,
"allowSyntheticDefaultImports": true,
"baseUrl": ".",
"paths": {
"@assets/*": ["assets/*"],
"@src/*": ["src/*"],
"@components/*": ["src/components/*"],
"@utils/*": ["src/utils/*"],
"@menus/*": ["src/menus/*"],
"@locales/*": ["src/locales/*"],
}
},
}
```
### <span id="function.error">9.错误统一处理</span>
我们采用axios作为通信处理方式,在应用页面初始化时,设置axios,并对url返回进行拦截处理,若出现错误异常,会出现两种场景,一种是网络错误,一种是业务错误
1)网络错误:网络请求状态为401,404,503等错误,并提示对应的信息。
2)业务错误:这种没有网络异常(即返回状态为200),通常后端返回的数据都会经过一层包装,若数据中存在success的状态,若为false,即发生了业务异常,需要对其进行特殊处理。本项目中业务异常返回到各模块中进行处理,在拦截层做统一处理,若成功直接返回真正可用的data数据;若失败,先提取后端返回的错误信息,并与原始数据一起直接返回给view层,至于如何处理错误,由view层自己决定
涉及范围:
> utils/handleAxios.js
### <span id="function.csrf">10.安全CSRF防范</span>
安全方面,我们采用token验证的方式解决
我们通过设置withCredentials携带cookie,前端登陆成功后,我们会从cookie中获取token值,并在axios请求的header中统一设置Authorization字段为token值,以后所有的请求的头部都会带上该Authorization字段,后端根据该字段与后端保存的token进行验证判断该请求是否合法
涉及范围:
> utils/handleAxios.js
### <span id="function.export">11.导出当前页</span>
采用xlsx依赖包,它比file-saver更出色,提供多种导出文件格式并且提供样式导出,比如合并单元格等
对antd的columns进行解析和封装,使用时直接调用方法即可,优雅简单,不需要其他配置
涉及范围:
> utils/exportTableData.js
>
> utils/download-file.js