文档服务地址:http://47.92.0.57:3000/ 周报索引地址:http://47.92.0.57:3000/s/NruNXRYmV

Commit d41c40be by 安博

Update README.md

parent fad2946c
......@@ -2,90 +2,1338 @@
# Ant Design Pro
[![](https://img.shields.io/travis/ant-design/ant-design-pro.svg?style=flat-square)](https://travis-ci.org/ant-design/ant-design-pro) [![Build status](https://ci.appveyor.com/api/projects/status/67fxu2by3ibvqtat/branch/master?svg=true)](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master) [![Gitter](https://badges.gitter.im/ant-design/ant-design-pro.svg)](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
开箱即用的中台前端/设计解决方案。
![](https://gw.alipayobjects.com/zos/rmsportal/xEdBqwSzvoSapmnSnYjU.png)
- 预览:http://preview.pro.ant.design
- 首页:http://pro.ant.design/index-cn
- 使用文档:http://pro.ant.design/docs/getting-started-cn
- 更新日志: http://pro.ant.design/docs/changelog-cn
- 常见问题:http://pro.ant.design/docs/faq-cn
- 国内镜像:http://ant-design-pro.gitee.io
## 特性
- :gem: **优雅美观**:基于 Ant Design 体系精心设计
- :triangular_ruler: **常见设计模式**:提炼自中后台应用的典型页面和场景
- :rocket: **最新技术栈**:使用 React/dva/antd 等前端前沿技术开发
- :iphone: **响应式**:针对不同屏幕大小设计
- :art: **主题**:可配置的主题满足多样化的品牌诉求
- :globe_with_meridians: **国际化**:内建业界通用的国际化方案
- :gear: **最佳实践**:良好的工程实践助您持续产出高质量代码
- :1234: **Mock 数据**:实用的本地数据调试方案
- :white_check_mark: **UI 测试**:自动化测试保障前端产品质量
## 模板
```
- Dashboard
- 分析页
- 监控页
- 工作台
- 表单页
- 基础表单页
- 分步表单页
- 高级表单页
- 列表页
- 查询表格
- 标准列表
- 卡片列表
- 搜索列表(项目/应用/文章)
- 详情页
- 基础详情页
- 高级详情页
- 结果
- 成功页
- 失败页
- 异常
- 403 无权限
- 404 找不到
- 500 服务器出错
- 帐户
- 登录
- 注册
- 注册成功
```
## 使用
```bash
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1
$ cd ant-design-pro
$ npm install
$ npm start # 访问 http://localhost:8000
```
也可以使用集成化的 [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli) 工具。
```bash
$ npm install ant-design-pro-cli -g
$ mkdir pro-demo && cd pro-demo
$ pro new
```
更多信息请参考 [使用文档](http://pro.ant.design/docs/getting-started)
## 兼容性
现代浏览器及 IE11。
## 参与贡献
我们非常欢迎你的贡献,你可以通过以下方式和我们一起共建 :smiley::
- 在你的公司或个人项目中使用 Ant Design Pro。
- 通过 [Issue](http://github.com/ant-design/ant-design-pro/issues) 报告 bug 或进行咨询。
- 提交 [Pull Request](http://github.com/ant-design/ant-design-pro/pulls) 改进 Pro 的代码。
第1章          环境配置
=======================
1.1          Node.js
--------------------
[https://nodejs.org/zh-cn/](https://nodejs.org/zh-cn/%E4%B8%8B%E8%BD%BD8.11.1)
下载8.11.1LTS版本,安装成功之后,cmd中path中有没有nodejs的安装路径
node –version
查看版本
1.2          Git
----------------
<https://git-scm.com/downloads>
下载windows版本,安装成功之后,cmd中git查看是否安装成功
1.3          下载ant pro项目
----------------------------
首先在cmd中进入自己想下载到的目录文件中
下载文件:
git clone --depth=1 https://github.com/ant-design/ant-design-pro.git my-project
打开文件:
cd my-project
安装依赖:npm install(发现很慢,卡死)
Npm换淘宝源:
npm config set registry https://registry.npm.taobao.org/
再安装依赖:
npm install
1.4          下载webstorm
-------------------------
自行下载。
第2章          访问流程和数据流动
=================================
1.1          目录结构
---------------------
在该框架中,执行的基本逻辑如下:
1.先搭建路由,src/common下有menu和router两个文件,先在menu里写入预备在slider里边展示的路径,然后在router里进行配置,将路由和文件的路径相匹配,并连接该页面的model(models文件夹下的文件,页面需要的)
2.在src/routes对应路径里的页面
3.在src/components下写各页面公共组件。
4.在src/models中建立页面所需model,最好名称对应,该文件用来连接页面和services,主要内容是action(该页面调用了service的func)
5.在src/services/api中配置页面进行前后台数据请求的路径并调用src/utils/request文件中fetch方法向后台发送请求
6.roadhogrc.mock.js用于配置请求的url使前后台链接起来
![图片](media/73de7ae8d602858b05ca312d9c267f70.png)
1.2          路由和菜单
-----------------------
路由的配置文件统一由src/common/router.js文件进行管理。const对象routerConfig中定义了最顶层的路由配置。以根目录的路由配置为例:
/\*\*
\* src/common/router.js
\*/
'/':{compoent:dynamicWrapper(app,[],()=\>import('../layouts/BasicLayout')}
每个配置项是一组键值对,键是要路由到的路径,值是一个component组件,由函数dynamicWrappe生成,其传入的参数包括:工程上下文对象app(可省略);一个数组,包含要用到的model(可为空);要渲染的component(可以加载layout或者routes目录下的.js)
src/common/menu.js文件是导航栏记录。menuData对象是一个纯json,描述了菜单的层级结构和描述属性,包括名称、图标、路径、权限等。如下是一个带有子目录的配置。在menu.js代码末尾对该json文件进行读取和解析,封装成routeData。
/\*\*
\* src/common/menu.js
\*/
{
name: '账户',
icon: 'user',
path: 'user',
authority: 'guest',
children: [
{
name: '登录',
path: 'login',
},
{
name: '注册',
path: 'register',
},
{
name: '注册结果',
path: 'register-result',
},
],
}
关于路径,router.js采用的是至多二级目录的完整路径,而menu.js每个节点只记录自己这一层的路径名,无需写出完整路径。
1.2          新建一个组件
-------------------------
在使用组件时,默认会在 index.js 中寻找 export
的对象,如果你的组件比较复杂,可以分为多个文件,最后在 index.js 中统一 export
### 1.2.1          继承现有组件
### 1.2.2          创建全新组件
1.3          Model的实现原理
----------------------------
Model我理解为,一个基于特定数据的状态和对状态进行改变的操作的整体,大致相当于对象(Object)或者数据结构的概念。每个model维护了一个特定状态,这个状态通常是基于某些数据的,当数据发生改变时,相应的状态也随之改变,从而触发react对组件的更新。
React的思想是,数据单向流动,如果顶层数据改变则认为状态发生了变化,从而刷新受影响的组件。为此,我们需要在数据改变时去更改model的状态。一个常用的方式是,状态即数据。以下为示例代码。
/\*\*
\* model/play.js
\*/
import {play} from '../services/api' //引入API
export default {
namespace: 'play', //名字空间,用于组织函数
state: {data: 0}, //状态对象,可以是字典、数组等,没有特别限制
effects: {
\* getTime(_, {call, put})
{
const response = yield call(play);
yield put({
type: 'time',
data: response
})
}
},
reducers: {
time(state, {data})
{
return {
...state,
data
}
}
}
}
### 1.3.1          effect(异步请求)
effect是Model的一组操作,用于请求和改变数据,得到的数据需要指向一个reduce中定义的操作,在那里进行归约。
//this is a sample
effects: {
\* getTime(_, {call, put})
//\*标志这是一个generator函数,_处是可能的输入数据(如果在调用effect时给了的话)
{
const response = yield call(play);
//call是一个上下文提供的用于请求API的方法,可带参
yield put({
type: 'time',
data: response
})
//put方法用于提交数据到reduce,type字段是reduce函数的名称,其他字段可以自己随意命名,在reduce函数中用同样的名称接收
}
}
### 1.3.2          reduce(更新状态)
和effect类似,包含了一组操作,用于将effect传过来的数据进行归约。操作的输入是model已有的状态和归约的数据,输出是新的状态。如果状态没有改变则不会触发组件更新。
//this is a sample
reducers: {
time(state, {data}) //state就是model中的state,data对应effect中传入的字段
{
return {
...state,
data
}
//返回新的状态。这里使用对象解构语法将state中的字段与data合并构造出新的state对象
}
}
1.4          如何在组件渲染中使用Model
--------------------------------------
先上示例代码。play.js即1.2节中路由指向的脚本,在这里完成对应路由的页面渲染。
/\*\*
\* routes/play/play.js
\*/
import React, {Component, Fragment} from 'react'; //引入react
import {connect} from 'dva' //引入dva框架中的connect组件
/\*\*
\*通过注解形式链接model,每个链接的model都会被保存到组件上下文中
\*实质上是model的state
\*/
\@connect(({play, loading}) =\> ({
play, //链接的model名称
getTime: loading.effects['play/getTime']
//loading是一个内建对象,可以从中提取model的effect函数或者state里的字段保存到上下文
}))
/\*\*
\*继承component,或者引入的其他现有组件
\*/
export default class play extends Component {
/\*\*
\*这是组件加载时要执行的方法(实现interface)
\*/
componentDidMount()
{
const {dispatch} = this.props; //上下文里包含一个dispatch方法
dispatch({type: 'play/getTime'}) //向type指向的effect发起状态改变请求,可以带参
}
/\*\*
\*在第一次加载页面或者model的状态发生改变时会触发render
\*/
render()
{
const {play} = this.props; //从上下文里解构出链接的model
console.log(play);
const {data} = play \|\| {time: "Error"}; //从model里解构出自己定义的数据
console.log(data);
return (\<div\>{data.time}
\<button style={{marginLeft: 10}} onClick={this.refresh}\>刷新时间\</button\>
\</div\>); //JSX语法
}
refresh = e =\> {
e.preventDefault();
const {dispatch} = this.props;
dispatch({type: 'play/getTime'});
}
}
### 1.4.1          连接Model
通过\@connect注解(本质上是装饰器)链接model,链接的model和对应的字段、方法可以保存到组件里的上下文中。
/\*\*
\*通过注解形式链接model,每个链接的model都会被保存到组件上下文中
\*实质上是model的state
\*/
\@connect(({play, loading}) =\> ({
play, //链接的model名称
getTime: loading.effects['play/getTime']
//loading是一个内建对象,可以从中提取model的effect函数或者state里的字段保存到上下文
}))
### 1.4.2          jsx
JSX语法类似于HTML,是一种模板语言,可以使用大括号标识要模板化的数据。
return (\<div\>{data.time}
\<button style={{marginLeft: 10}} onClick={this.refresh}\>刷新时间\</button\>
\</div\>); //JSX语法
1.5          调用API
--------------------
### 1.5.1        service
注意到我们在model/play.js里第一行引入了services/API中的内容。这个文件的部分内容如下。
/\*\*
\*services/API.js
\*/
import { stringify } from 'qs';
import request from '../utils/request'; //框架提供的异步请求函数
export async function queryProjectNotice() {
return request('/api/project/notice');
}
export async function queryRule(params) {
return request(\`/api/rule?\${stringify(params)}\`);
}
export async function removeRule(params) {
return request('/api/rule', {
method: 'POST',
body: {
...params,
method: 'delete',
},
});
}
export async function addRule(params) {
return request('/api/rule', {
method: 'POST',
body: {
...params,
method: 'post',
},
});
}
export async function fakeSubmitForm(params) {
return request('/api/forms', {
method: 'POST',
body: params,
});
}
export async function queryFakeList(params) {
return request(\`/api/fake_list?\${stringify(params)}\`);
}
export async function fakeAccountLogin(params) {
return request('/api/login/account', {
method: 'POST',
body: params,
});
}
export async function play()
{
return request('/api/play');
}
可以看到,我们引入的play就是最后的这个方法,它返回了一个对/api/play的请求。这个文件用于组织我们的API,几种请求的写法在上述代码中有示例。
可以把API拆分成不同的文件,放在services目录下。
### 1.5.2         mock
/\*\*
\* .roadhogrc.mock.js
\*/
import mockjs from 'mockjs';
import {getRule, postRule} from './mock/rule';
import {getActivities, getNotice, getFakeList} from './mock/api';
import {getFakeChartData} from './mock/chart';
import {getProfileBasicData} from './mock/profile';
import {getProfileAdvancedData} from './mock/profile';
import {getNotices} from './mock/notices';
import {format, delay} from 'roadhog-api-doc';
// 是否禁用代理
const noProxy = process.env.NO_PROXY === 'true';
const proxy = {
// 支持值为 Object 和 Array
'GET /api/play': 'http://localhost:3000',
'GET /api/users': [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
},
],
'GET /api/404': (req, res) =\> {
res.status(404).send({
timestamp: 1513932643431,
status: 404,
error: 'Not Found',
message: 'No message available',
path: '/base/category/list/2121212',
});}
}
export default (noProxy ? {} : delay(proxy, 0));
.roadhogrc.mock.js位于项目根目录下,负责代理全局请求。它的proxy对象保存了API和代理内容的键值对。其中,API可以使用通配符,代理内容格式可以是数组、对象、字符串、方法这几种。
| 格式 | 意义 |
|--------|---------------------------------------------------------------------------------------------------------|
| 数组 | 即返回的具体数据,可用于构造假数据 |
| 对象 | 同数组 |
| 字符串 | 如果是http或者https开头的有效URL,则重定向至对应的域名(用于真实后台接入),否则和数组/对象处理方式一样 |
| 方法 | 默认传入req和res对象,可自定义返回内容 |
最后的(noProxy ? {} : delay(proxy,
0))表示如果工程启动命令指定了no-proxy,则不执行代理,否则执行一个可选延迟时间的代理。此处的0可替换成想要的延迟,单位毫秒。
**事实证明,跨域请求只能在代理中完成,如果直接在API中请求会出现跨域错误。**
### 1.5.2         request.js
上文已经提到,src/utils/request.js提供了http请求的功能,其代码不长。
import fetch from 'dva/fetch';
import { notification } from 'antd';
import { routerRedux } from 'dva/router';
import store from '../index';
const codeMessage = {
200: '服务器成功返回请求的数据。',
201: '新建或修改数据成功。',
202: '一个请求已经进入后台排队(异步任务)。',
204: '删除数据成功。',
400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
401: '用户没有权限(令牌、用户名、密码错误)。',
403: '用户得到授权,但是访问是被禁止的。',
404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
406: '请求的格式不可得。',
410: '请求的资源被永久删除,且不会再得到的。',
422: '当创建一个对象时,发生一个验证错误。',
500: '服务器发生错误,请检查服务器。',
502: '网关错误。',
503: '服务不可用,服务器暂时过载或维护。',
504: '网关超时。',
};
function checkStatus(response) {
if (response.status \>= 200 && response.status \< 300) {
return response;
}
const errortext = codeMessage[response.status] \|\| response.statusText;
notification.error({
message: \`请求错误 \${response.status}: \${response.url}\`,
description: errortext,
});
const error = new Error(errortext);
error.name = response.status;
error.response = response;
throw error;
}
/\*\*
\* Requests a URL, returning a promise.
\*
\* \@param {string} url The URL we want to request
\* \@param {object} [options] The options we want to pass to "fetch"
\* \@return {object} An object containing either "data" or "err"
\*/
export default function request(url, options) {
const defaultOptions = {
credentials: 'include',
};
const newOptions = { ...defaultOptions, ...options };
if (newOptions.method === 'POST' \|\| newOptions.method === 'PUT') {
if (!(newOptions.body instanceof FormData)) {
newOptions.headers = {
Accept: 'application/json',
'Content-Type': 'application/json; charset=utf-8',
...newOptions.headers,
};
newOptions.body = JSON.stringify(newOptions.body);
} else {
// newOptions.body is FormData
newOptions.headers = {
Accept: 'application/json',
...newOptions.headers,
};
}
}
return fetch(url, newOptions)
.then(checkStatus)
.then(response =\> {
if (newOptions.method === 'DELETE' \|\| response.status === 204) {
return response.text();
}
return response.json();
})
.catch(e =\> {
const { dispatch } = store;
const status = e.name;
if (status === 401) {
dispatch({
type: 'login/logout',
});
return;
}
if (status === 403) {
dispatch(routerRedux.push('/exception/403'));
return;
}
if (status \<= 504 && status \>= 500) {
dispatch(routerRedux.push('/exception/500'));
return;
}
if (status \>= 404 && status \< 422) {
dispatch(routerRedux.push('/exception/404'));
}
});
}
其中,codeMessage描述了主要的http状态码的含义,checkStatus方法对响应结果的状态进行了判定,如果属于错误状态则抛出一个异常,否则将响应内容返回。在request方法里,使用了promise链对响应结果进行处理,先判断状态,然后返回json格式的内容,最后统一处理异常。
从request的方法头可以看出,我们在使用request请求url的时候,可以通过第二个参数指定使用post方法,具体只需要传入一个带有method:'post'的对象。此外在post时还会进行是否为表单数据的判断,对非表单数据的补全头数据。
第3章          语法
===================
假定你已经具有基本的HTML和JavaScript知识,并对ES6标准中的let和const关键字有了解。
3.1          lambda函数(箭头函数)
-----------------------------------
lambda函数的形式:前者是完整形式,后者是仅需要执行一行代码并返回一个结果情况下的简写。
(pram1,pram2)=\>{console.log(pram1);}
(p1,p2)=\>p1\*\*2;
p1=\>p1\*\*2;
一个简单的理解是,它定义了函数的输入和函数体但并不显式声明,可用于创建匿名函数。相比于普通形式的函数声明,它更能突出输入输出的信息,便于传递函数作为参数。
请注意,lambda函数中可以使用创建它时的上下文中的可访问变量,换言之,函数体内的作用域继承了声明它的上下文的作用域,从而在函数被调用时即便上下文发生了改变,它也依旧可以使用创建时的作用域的可用变量,这个特性称为闭包。
Java8中的lambda函数不能修改上下文中的其他变量(显式或可隐式推断为final),而C\#和JavaScript中的lambda函数没有相关限制。
3.2          generator和yield
-----------------------------
generator即生成器函数,使用 \* 声明。至于为什么叫生成器可以从下面的例子来理解。
\*function example()
{
for(int i=0;i\<10;i++)
yield i;
}
这个函数第一次执行的时候会生成一个生成器函数,它提供了一个next()方法。当我们调用next方法时,函数会执行到yield的位置处停下,并返回yield后面的表达式的结果。当再次调用next方法的时候,可以传入一个参数,此时传入的参数会覆盖刚刚返回的表达式的结果,当然也可以不带参数正常执行。
可以看出,yield的存在让函数可以分步执行,还能在执行过程中进行干预,获取中间值。这个特性使得我们可以获取一个异步回调函数中的结果,而无须进入函数内部,从而避免了多个异步函数嵌套,造成“回调黑洞”现象。
\*可以写在function的前面或者函数名前面,不能写在函数名末尾。
3.3          export
-------------------
export是ES6标准中用于组织模块的关键字,被export的对象可以在import相应js文件的时候通过解构取出来。可以指定default
export以作为默认导出对象。
3.4          扩展运算符和解构
-----------------------------
扩展运算符用于将一个对象或数据的成员列举出来,使用 ... 作为运算符。
let object={a:1,b:2};
let newObject={...object,b:4}
console.log(newObject); //{a:1,b:4}
通过扩展运算符,可以方便的展开对象和数组,对其中的元素进行修改、提取。
解构用于根据键名或者顺序提取对象和数组中的部分元素。
let {a}=object;
console.log(a); //a:1
let array=[1,2,3,4,5];
let [p1,p2,...subArray]=array;
console.log(p2); //p2=2
console.log(subArray); //subArray=[3,4,5]
解构操作支持嵌套解构,对于数组解构可以使用占位符跳过不需要的元素。
3.5          promise和async/await
---------------------------------
promise是一个特殊的对象,它封装了一个函数,当这个函数成功执行时会触发resolve方法返回一个结果,未成功执行时触发reject方法。多个promise可以用then方法链式调用,并将之前的计算结果传递下去。
let p1=data=\>new Promise((resolve,reject)=\>{resolve(data)});
p1(1).then(p1).then(data=\>console.log(data)).catch(e=\>console.log(e));
resolve方法中传入的参数即为计算结果,可以大概理解为用它代替传统的return。每个promise在构造后都会立即执行,执行过程中会改变promise的状态(未完成,成功,失败)。使用then传入一个函数(data=\>{})以提取返回的结果。如果then中传入的函数返回promise,则可以继续调用then获取这个promise的返回结果。使用catch捕捉整个链上的异常。
对于一个涉及多个异步操作的流程,传统的写法是:
request("input",(resulta,error)
{
request(resulta,(resultb,error)
{
request(resultb,(resultc,error))
{
console.log(resultc);
}
})
})
做promise化处理后的写法:
let request_promise=data=\>new
Promise((resolve,reject)=\>{request(data,(result,error){resolve(result)})});
//对request做promise化
request_promise("input").then(request_promise).then(request_promise).then(result=\>console.log(result));
//链式调用
尽管解决了多重回调的问题,promise调用链的写法依旧不够直观,为此引入了async和await关键字。
async function fun(input)
{
let resulta=await request_promise(input);
let resultb=await request_promise(resulta);
let resultc=await request_promise(resultb);
console.log(resultc);
return resultc;
}
fun("input").then(data=\>console.log(data);) //data="input"
await可以从一个promise中提取resolve的结果,如果当前promise的状态是未完成,则会在此等待返回。**使用了await的函数必须用async修饰**,但async修饰的函数可以无限制调用。
事实上async方法执行时也会返回一个promise,promise的resolve结果是方法中return的内容,这意味着可以用await获取async的返回结果。
async function main()
{
let result=await fun("input");
console.log(result);
}
main();
所以,**在顶层作用域使用async方法的时候,将其作为一个promise看待(当然如果你不关心其返回结果就可以单纯的执行它);在函数中使用async方法的时候,使用await关键字获取其返回结果,并把这个函数本身也用async修饰。**
目前async和await关键字是解决异步回调嵌套问题的最优解。
第4章          页面组件
=======================
本章针对页面的各种步骤条、选择框、表单、图表等组件进行说明。首先要在router.js以及menu.js中把路由和菜单写好,确保单词拼写无误,尽可能复制,页面报错是检查不出拼写出错的,所以这个一定要反复确认没问题,不然会浪费很长时间。
4.1          步骤条
-------------------
以我们项目的边缘节点管理者中的资源注册页面为例,可见其分为三个部分,这就是我们说的步骤条,当然在ant
design中有相关的教程*http://ant.design/components/steps-cn/*,不过它仅是一个进度条的实现,此处不仅包含进度条实现还有几个页面之间的实现。
![图片](media/194403febb742dba898a81ccdac22589.png)
### 4.1.1         菜单栏添加
首先在menu.js中,建立好菜单的关系,我们把资源注册命名为registration。
{
name: '计算资源管理',
icon: 'table',
path: 'resource-management',
children: [
{
name: '计算资源注册',
path: 'registration',
},
{
name: '计算资源查看',
path: 'views',
},
],
},
### 4.1.2         页面创建路由添加
然后router.js中添加路由关系,第一个为注册的路由,中间三个为资源注册的三个步骤的路由,也好命名好,同时根据其在routes文件夹下建立Registration文件,该文件下有四个js文件:index.js为Registration的基础,Step1.js是第一步的页面,Step2.js是第二步的页面,
Step3.js是第三步页面。
'/resource-management/registration':{
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration')),
},
'/resource-management/registration/resource-info':{
name: '计算资源注册',
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration/Step1')),
},
'/resource-management/registration/interview-method':{
name: '计算资源注册',
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration/Step2')),
},
'/resource-management/registration/result':{
name: '计算资源注册',
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration/Step3')),
},
'/resource-management/views':{
component: dynamicWrapper(app, [], () =\>
import('../routes/ResourceManagement/Views')),
},
### 4.1.3         开始写代码
在indes.js中写跳转关系以及路径选择,其class名称为该文件夹名称。
switch为路径选择,
\<steps\>下为即为步骤条,
Redirect是从当前页面跳转到下一个路径的页面部分。
export default class Registration extends PureComponent {
getCurrentStep() {
const { location } = this.props;
const { pathname } = location;
const pathList = pathname.split('/');
switch (pathList[pathList.length - 1]) {
case 'resource-info':
return 0;
case 'interview-method':
return 1;
case 'result':
return 2;
default:
return 0;
}
}
render() {
const { match, routerData } = this.props;
return (
\<PageHeaderLayout\>
\<Card bordered={false}\>
\<Fragment\>
\<Steps current={this.getCurrentStep()} className={styles.steps}\>
\<Step title="填写计算资源信息" /\>
\<Step title="填写访问方式" /\>
\<Step title="完成" /\>
\</Steps\>
\<Switch\>
{getRoutes(match.path, routerData).map(item =\> (
\<Route
key={item.key}
path={item.path}
component={item.component}
exact={item.exact}
/\>
))}
\<Redirect exact from="/resource-management/registration"
to="/resource-management/registration/resource-info" /\>
\<Route render={NotFound} /\>
\</Switch\>
\</Fragment\>
\</Card\>
\</PageHeaderLayout\>
);
}
}
Step1.js中,在下一步的按钮提交函数中一定要写路径的跳转,同时,表单内部的内容可以自己根据情况写好,最后export
default connect一定不要忘记加上,没有他们,路由找不到页面,这块语法不是很懂。
\@Form.create()
class Step1 extends React.PureComponent {
render() {
const { form, dispatch, data } = this.props;
const { getFieldDecorator, validateFields } = form;
const onValidateForm = () =\> {
validateFields((err, values) =\> {
if (!err) {
dispatch(routerRedux.push('/resource-management/registration/interview-method'));
}
});
};
return (
\<Fragment\>
\<Form layout="horizontal" className={styles.stepForm} hideRequiredMark\>
//此处可自己写
\<Form.Item\>
\<Button type="primary" onClick={onValidateForm}\>
下一步
\</Button\>
\</Form.Item\>
\</Form\>
\</Fragment\>
);
}
}
export default connect(({ form }) =\> ({
data: form.step,
}))(Step1);
Step2.js和Step3.js的原理和Step1是一样的。只不过要加上一个onPrev函数是在点击上一步按钮时的回到上一步操作。
const onPrev = () =\> {
dispatch(routerRedux.push('/data-management/registration/interview-method'));
};
至此我们的步骤条就写完啦
4.2          表单
-----------------
第5章          项目发布
=======================
5.1          build命令
----------------------
对于一个前端页面而言,其背后的脚本对用户是透明的,如果我们直接将网站上线势必造成代码暴露,为此我们需要考虑如何发布项目。
在项目的package.json中定义了若干命令:
"scripts": {
"precommit": "npm run lint-staged",
"start": "cross-env ESLINT=none roadhog dev",
"start:no-proxy": "cross-env NO_PROXY=true ESLINT=none roadhog dev",
"build": "cross-env ESLINT=none roadhog build",
"site": "roadhog-api-doc static && gh-pages -d dist",
"analyze": "cross-env ANALYZE=true roadhog build",
"lint:style": "stylelint \\"src/\*\*/\*.less\\" --syntax less",
"lint": "eslint --ext .js src mock tests && npm run lint:style",
"lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
"lint-staged": "lint-staged",
"lint-staged:js": "eslint --ext .js",
"test": "roadhog test",
"test:component": "roadhog test ./src/components",
"test:all": "node ./tests/run-tests.js",
"prettier": "prettier --write ./src/\*\*/\*\*/\*\*/\*"
}
其中,start和start:no-proxy命令是使用代理和不使用代理启动项目,而build命令则是用于编译项目的。该命令会将项目编译到dist目录,生成四个文件:
![图片](media/0fb462a4205c257717e410986e5b8163.png)
其中index.html只是单纯引用了js和css。index.f5c867a2.js是编译后的脚本,该脚本将项目中的全部逻辑都打包了进去,并做了target=ES5的版本降级和代码混淆。css文件同样经过了打包。文件名中的字符串是根据项目文件的hash值生成的,可以用于标识版本。此外还可以通过其他参数实现脚本分割,从而利用并行加载加快网页打开速度。
5.2          mock失效
---------------------
请注意,之前在.roadhogrc.mock.js中编写的mock代码不会被打包,对API的访问控制截至request.js,所以之前的跨域请求会失效。此时有三种方式解决:
1. 沿用开发环境的服务器,显然这种情况是无法做代码打包和混淆的;
2. 将页面和脚本放到单独的前端服务器,后端服务器开启跨域许可;
3. 将页面和脚本放到后端服务器,最传统的方式。
第一种方式是不推荐的,编译打包的目的已经说过;第二种方式实现了前后端在服务器层面的分离,便于多个平台共享后端,但需要将所有请求定向到后端url;第三种方式实现起来最简单。
5.3          跨域处理
---------------------
关于开启跨域许可,只需要在服务器返回的http响应头中添加以下字段,以Express框架为例:
app.all('\*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.get('Origin'));
res.header("Access-Control-Allow-Headers", "X-Requested-With");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
res.header("Content-Type", "application/json;charset=utf-8");
res.header("Access-Control-Allow-Credentials","true");
next();
});
注意Access-Control-Allow-Origin字段的值不能为通配符\*,必须为具体的host,可以通过查询请求头中的Origin字段获得。对于自己的项目而言,完全可以写死在代码中或者放到配置文件里。
5.4          url重定向
----------------------
### 5.4.1          前端重定向
修改request.js使其重定向,只需修改这一行:
//return fetch(url, newOptions) line67
return fetch('http://localhost:3000' + url, newOptions)
这种方式会暴露API服务器地址,并且api请求实际是从用户浏览器到后端服务器,外网环境下速度可能会有影响。
### 5.4.2          前端服务器代理
如果不希望暴露API服务器地址,则可以在前端服务器中进行反向代理。以Express框架为例,只需要引入代理中间件:
let proxyMiddleWare = require("http-proxy-middleware");
let proxyPath = "http://192.168.3.10:3000"; //后端服务器地址
let proxyOption ={target:proxyPath,changeOrigoin:true};
app.use("/api",proxyMiddleWare(proxyOption)) //对api请求使用代理
前端服务器代理的好处是对于用户而言只能看到前端服务器的地址,后端服务器的安全性得到提高;同时代理发生在内网环境,速度快。
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment