Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
A
ant-design-pro
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
安博
ant-design-pro
Commits
36fbc930
Commit
36fbc930
authored
May 28, 2018
by
安博
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
first
parent
49875a3f
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
558 additions
and
1028 deletions
+558
-1028
README.md
README.md
+558
-1028
No files found.
README.md
View file @
36fbc930
...
...
@@ -12,7 +12,7 @@
下载8.11.1LTS版本,安装成功之后,cmd中path中有没有nodejs的安装路径
node –versi
on
`node –versio
n`
查看版本
...
...
@@ -30,21 +30,21 @@ node ersion
下载文件:
git clone --depth=1 https://github.com/ant-design/ant-design-pro.git my-project
`git clone --depth=1 https://github.com/ant-design/ant-design-pro.git my-project`
打开文件:
cd my-project
`cd my-project`
安装依赖:npm install(发现很慢,卡死)
Npm换淘宝源:
npm config set registry https://registry.npm.taobao.org/
`npm config set registry https://registry.npm.taobao.org/`
再安装依赖:
npm install
`npm install`
1.
4 下载webstorm
-------------------------
...
...
@@ -59,17 +59,17 @@ npm install
在该框架中,执行的基本逻辑如下:
1.
先搭建路由,src/common下有menu和router两个文件,先在menu里写入预备在slider里边展示的路径,然后在router里进行配置,将路由和文件的路径相匹配,并连接该页面的model(models文件夹下的文件,页面需要的)
1.
先搭建路由,src/common下有menu和router两个文件,先在menu里写入预备在slider里边展示的路径,然后在router里进行配置,将路由和文件的路径相匹配,并连接该页面的model(models文件夹下的文件,页面需要的)
2.
在src/routes对应路径里的页面
2.
在src/routes对应路径里的页面
3.
在src/components下写各页面公共组件。
3.
在src/components下写各页面公共组件。
4.
在src/models中建立页面所需model,最好名称对应,该文件用来连接页面和services,主要内容是action(该页面调用了service的func)
4.
在src/models中建立页面所需model,最好名称对应,该文件用来连接页面和services,主要内容是action(该页面调用了service的func)
5.
在src/services/api中配置页面进行前后台数据请求的路径并调用src/utils/request文件中fetch方法向后台发送请求
5.
在src/services/api中配置页面进行前后台数据请求的路径并调用src/utils/request文件中fetch方法向后台发送请求
6.
roadhogrc.mock.js用于配置请求的url使前后台链接起来
6.
roadhogrc.mock.js用于配置请求的url使前后台链接起来

...
...
@@ -78,64 +78,45 @@ npm install
路由的配置文件统一由src/common/router.js文件进行管理。const对象routerConfig中定义了最顶层的路由配置。以根目录的路由配置为例:
/
\*\*
\*
src/common/router.js
\*
/
'/':{compoent:dynamicWrapper(app,
[
],()=
\>
import('../layouts/BasicLayo
ut')}
```
/**
*
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
* src/common/menu.js
\
*
/
*/
{
name: '账户',
icon: 'user',
path: 'user',
authority: 'guest',
children:
[
{
name: '登录',
path: 'login',
},
{
name: '注册',
path: 'register',
},
{
name: '注册结果',
path: 'register-result',
},
],
}
name: '账户',
icon: 'user',
path: 'user',
authority: 'guest',
children: [
{
name: '登录',
path: 'login',
},
{
name: '注册',
path: 'register',
},
{
name: '注册结果',
path: 'register-result',
},
],
}
```
关于路径,router.js采用的是至多二级目录的完整路径,而menu.js每个节点只记录自己这一层的路径名,无需写出完整路径。
1.
2 新建一个组件
...
...
@@ -144,656 +125,381 @@ path: 'register-result',
在使用组件时,默认会在 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
```
/**
* 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
}
}
}
}
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函数中用同样的名称接收
}
}
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对象
}
}
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里的字段保存到上下文
```
/**
* 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,或者引入的其他现有组件
\*
/
/**
*继承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'});
}
}
/**
*这是组件加载时要执行的方法(实现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里的字段保存到上下文
通过@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语法
```
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
\*
/
```
/**
*services/API.js
*/
import { stringify } from 'qs';
import request from '../utils/request'; //框架提供的异步请求函数
import request from '../utils/request'; //框架提供的异步请求函数
export async function queryProjectNotice() {
return request('/api/project/notice');
return request('/api/project/notice');
}
export async function queryRule(params) {
return request(
\`
/api/rule?
\$
{stringify(params)}
\`
);
return request(`/api/rule?${stringify(params)}`);
}
export async function removeRule(params) {
return request('/api/rule', {
method: 'POST',
body: {
...params,
method: 'delete',
},
});
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',
},
});
return request('/api/rule', {
method: 'POST',
body: {
...params,
method: 'post',
},
});
}
export async function fakeSubmitForm(params) {
return request('/api/forms', {
method: 'POST',
body: params,
});
return request('/api/forms', {
method: 'POST',
body: params,
});
}
export async function queryFakeList(params) {
return request(
\`
/api/fake_list?
\$
{stringify(params)}
\`
);
return request(`/api/fake_list?${stringify(params)}`);
}
export async function fakeAccountLogin(params) {
return request('/api/login/account', {
method: 'POST',
body: params,
});
return request('/api/login/account', {
method: 'POST',
body: params,
});
}
export async function play()
{
return request('/api/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'));
可以把API拆分成不同的文件,放在services目录下。
return;
### 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',
});}
}
if (status
\>
= 404 && status
\<
422) {
export default (noProxy ? {} : delay(proxy, 0));
```
`.roadhogrc.mock.js`
位于项目根目录下,负责代理全局请求。它的proxy对象保存了API和代理内容的键值对。其中,API可以使用通配符,代理内容格式可以是数组、对象、字符串、方法这几种。
dispatch(routerRedux.push('/exception/404'));
| 格式 | 意义 |
|--------|---------------------------------------------------------------------------------------------------------|
| 数组 | 即返回的具体数据,可用于构造假数据 |
| 对象 | 同数组 |
| 字符串 | 如果是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';
其中,codeMessage描述了主要的http状态码的含义,checkStatus方法对响应结果的状态进行了判定,如果属于错误状态则抛出一个异常,否则将响应内容返回。在request方法里,使用了promise链对响应结果进行处理,先判断状态,然后返回json格式的内容,最后统一处理异常。
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时还会进行是否为表单数据的判断,对非表单数据的补全头数据。
...
...
@@ -806,161 +512,127 @@ dispatch(routerRedux.push('/exception/404'));
-----------------------------------
lambda函数的形式:前者是完整形式,后者是仅需要执行一行代码并返回一个结果情况下的简写。
(pram1,pram2)=
\>
{console.log(pram1);}
(p1,p2)=
\>
p1
\*\*
2;
p1=
\>
p1
\*\*
2;
```
(pram1,pram2)=>{console.log(pram1);}
(p1,p2)=>p1**2;
p1=>p1**2;
```
一个简单的理解是,它定义了函数的输入和函数体但并不显式声明,可用于创建匿名函数。相比于普通形式的函数声明,它更能突出输入输出的信息,便于传递函数作为参数。
请注意,lambda函数中可以使用创建它时的上下文中的可访问变量,换言之,函数体内的作用域继承了声明它的上下文的作用域,从而在函数被调用时即便上下文发生了改变,它也依旧可以使用创建时的作用域的可用变量,这个特性称为闭包。
Java8中的lambda函数不能修改上
下文中的其他变
量(显式或可隐式推断为final),而C
\#
和JavaScript中的lambda函数没有相关限制。
Java8中的lambda函数不能修改上
下文中的其他变量
(显式或可隐式推断为
`final`
),而C#和JavaScript中的lambda函数没有相关限制。
3.
2 generator和yield
-----------------------------
generator即生成器函数,使用
\*
声明。至于为什么叫生成器可以从下面的例子来理解。
\*
function example()
generator即生成器函数,使用
*
声明。至于为什么叫生成器可以从下面的例子来理解。
```
*function example()
{
for(int i=0;i
\<
10;i++)
yield i;
for(int i=0;i<10;i++)
yield i;
}
这个函数第一次执行的时
候会生成一个
生成器函数,它提供了一个next()方法。当我们调用next方法时,函数会执行到yield的位置处停下,并返回yield后面的表达式的结果。当再次调用next方法的时候,可以传入一个参数,此时传入的参数会覆盖刚刚返回的表达式的结果,当然也可以不带参数正常执行。
```
这个函数第一次执行的时
候会生成一个生成
器函数,它提供了一个
`next()`
方法。当我们调用next方法时,函数会执行到yield的位置处停下,并返回yield后面的表达式的结果。当再次调用next方法的时候,可以传入一个参数,此时传入的参数会覆盖刚刚返回的表达式的结果,当然也可以不带参数正常执行。
可以看出,yield的存在让函数可以分步执行,还能在执行过程中进行干预,获取中间值。这个特性使得我们可以获取一个异步回调函数中的结果,而无须进入函数内部,从而避免了多个异步函数嵌套,造成“回调黑洞”现象。
\
*
可以写在function的前面或者函数名前面,不能写在函数名末尾。
*
可以写在function的前面或者函数名前面,不能写在函数名末尾。
3.
3 export
-------------------
export是ES6标准中用
于组织模块的关键字,被export的对象可以在
import相应js文件的时候通过解构取出来。可以指定default
export以作为默认导出对象。
export是ES6标准中用
于组织模块的关键字,被
`export`
的对象可以在
`im
port`
相应js文件的时候通过解构取出来。可以指定
`default
export
`
以作为默认导出对象。
3.
4 扩展运算符和解构
-----------------------------
扩展运算符用于将一个对象
或数据
的成员列举出来,使用 ... 作为运算符。
扩展运算符用于将一个对象
或数据的成
员列举出来,使用
`...`
作为运算符。
```
let object={a:1,b:2};
let newObject={...object,b:4}
console.log(newObject); //{a:1,b:4}
console.log(newObject); //{a:1,b:4}
```
通过扩展运算符,可以方便的展开对象和数组,对其中的元素进行修改、提取。
解构用于根据键名或者顺序提取对象和数组中的部分元素。
```
let {a}=object;
console.log(a); //a:1
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
]
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捕捉整个链上的异常。
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);
}
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)})});
```
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));
request_promise("input").then(request_promise).then(request_promise).then(result=>console.log(result));
//链式调用
尽管解决了多重回调的问题,promis
e调用链的写法依
旧不够直观,为此引入了async和await关键字。
```
尽管解决了多重回调的问题,promis
e调用链的写法依旧不够直
观,为此引入了
`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;
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"
fun("input").then(data=
>console.log(data);)
//data="input"
```
await可以从一个promise中提取resolve的结果,如果当前promise的状态是未完成,则会在此等待返回。
**使用了await的函数必须用async修饰**
,但async修饰的函数可以无限制调用。
事实上async方法执行时也会返回一个promise,promise的resol
ve结果是方法中ret
urn的内容,这意味着可以用await获取async的返回结果。
事实上async方法执行时也会返回一个promise,promise的resol
ve结果是方法中return的
内容,这意味着
**可以用await获取async的返回结果**
。
```
async function main()
{
let result=await fun("input");
console.log(result);
let result=await fun("input");
console.log(result);
}
main();
```
所以,
**在顶层作用域使用`async`方法的时候,将其作为一个`promise`看待(当然如果你不关心其返回结果就可以单纯的执行它);在函数中使用`async`方法的时候,使用`await`关键字获取其返回结果,并把这个函数本身也用`async`修饰。**
所以,
**在顶层作用域使用async方法的时候,将其作为一个promise看待(当然如果你不关心其返回结果就可以单纯的执行它);在函数中使用async方法的时候,使用await关键字获取其返回结果,并把这个函数本身也用async修饰。**
目前async和await关键字是解决异步回调嵌套问题的最优解。
目前
`async`
和
`await`
关键字是解决异步回调嵌套问题的最优解。
第4章 页面组件
=======================
本章针对页面的各种步骤条、选
择框、表单、图表等组件进行说明。首
先要在router.js以及menu.js中把路由和菜单写好,确保单词拼写无误,尽可能复制,页面报错是检查不出拼写出错的,所以这个一定要反复确认没问题,不然会浪费很长时间。
本章针对页面的各种步骤条、选
择框、表单、图表等组件进行说明。首先要在
`
router.js`
以及
`menu.js`
中把路由和菜单写好,确保单词拼写无误,尽可能复制,页面报错是检查不出拼写出错的,所以这个一定要反复确认没问题,不然会浪费很长时间。
4.
1 步骤条
-------------------
...
...
@@ -972,254 +644,139 @@ design銝剜*http://ant.design/components/steps-cn/*嚗
### 4.1.1 菜单栏添加
首先在menu.js中,建立好菜单的关系,我们把资源注册命名为registration。
{
name: '计算资源管理',
icon: 'table',
path: 'resource-management',
children:
[
{
name: '计算资源注册',
path: 'registration',
},
首先在
`menu.js`
中,建立好菜单的关系,我们把资源注册命名为registration。
```
{
name: '计算资源查看',
path: 'views',
},
],
},
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')),
component: dynamicWrapper(app, ['form'], () => import('../routes/ResourceManagement/Registration')),
},
'/resource-management/registration/resource-info':{
name: '计算资源注册',
component: dynamicWrapper(app,
[
'form'
]
, () =
\>
import('../routes/ResourceManagement/Registration/Step1')),
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')),
name: '计算资源注册',
component: dynamicWrapper(app, ['form'], () => import('../routes/ResourceManagement/Registration/Step2')),
},
'/resource-management/registration/result':{
name: '计算资源注册',
component: dynamicWrapper(app,
[
'form'
]
, () =
\>
import('../routes/ResourceManagement/Registration/Step3')),
name: '计算资源注册',
component: dynamicWrapper(app, ['form'], () => import('../routes/ResourceManagement/Registration/Step3')),
},
'/resource-management/views':{
component: dynamicWrapper(app,
[
], () =
\>
import('../routes/ResourceManagement/Views')),
},
component: dynamicWrapper(app, [], () => import('../routes/ResourceManagement/Views')),
}
```
### 4.1.3 开始写代码
在indes.js中写跳转关系以及路径选择,其class名称为该文件夹名称。
switch
为路径选择,
`switch`
为路径选择,
\<
steps
\>
下为即为步骤条,
Redirect是从当前页面跳转到下一个路径的页面部分。
`<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()
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,
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 表单
...
...
@@ -1233,52 +790,36 @@ dispatch(routerRedux.push('/data-management/registration/interview-method'));
对于一个前端页面而言,其背后的脚本对用户是透明的,如果我们直接将网站上线势必造成代码暴露,为此我们需要考虑如何发布项目。
在项
目的package.j
son中定义了若干命令:
在项
目的
`package.js
on`
中定义了若干命令:
```
"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目录,生成四个文件:
"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`
目录,生成四个文件:

其
中index.html只是单纯引用了js和css。index.f5c867a2.js是编译后的脚本,该
脚本将项目中的全部逻辑都打包了进去,并做了target=ES5的版本降级和代码混淆。css文件同样经过了打包。文件名中的字符串是根据项目文件的hash值生成的,可以用于标识版本。此外还可以通过其他参数实现脚本分割,从而利用并行加载加快网页打开速度。
其
中
`index.html`
只是单纯引用了js和css。
`index.f5c867a2.js`
是编译后的脚本,该脚本
将项目中的全部逻辑都打包了进去,并做了
`target=ES5`
的版本降级和代码混淆。css文件同样经过了打包。文件名中的字符串是根据项目文件的hash值生成的,可以用于标识版本。此外还可以通过其他参数实现脚本分割,从而利用并行加载加快网页打开速度。
5.
2 mock失效
---------------------
请注意
,之前在.roadhogrc.mock.js中编写的mock代码不会被打包,对A
PI的访问控制截至request.js,所以之前的跨域请求会失效。此时有三种方式解决:
请注意
,之前在
`.roadhogrc.mock.js`
中编写的mock代码不会被打包,对API
的访问控制截至
`request.js`
,所以之前的跨域请求会失效。此时有三种方式解决:
1.
沿用开发环境的服务器,显然这种情况是无法做代码打包和混淆的;
...
...
@@ -1292,48 +833,37 @@ dispatch(routerRedux.push('/data-management/registration/interview-method'));
---------------------
关于开启跨域许可,只需要在服务器返回的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();
```
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字段获得。对于自己的项目而言,完全可以写死在代码中或者放到配置文件里。
```
注意
`Access-Control-Allow-Origin`
字段的值不能为通配符
*
,必须为具体的host,可以通过查询请求
头中的
`Origin`
字段获得。对于自己的项目而言,完全可以写死在代码中或者放到配置文件里。
5.
4 url重定向
----------------------
### 5.4.1 前端重定向
修改request.js使其重定向,只需修改这一行:
//return fetch(url, newOptions) line67
修改
`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 proxyPath = "http://192.168.3.10:3000"; //后端服务器地址
let proxyOption ={target:proxyPath,changeOrigoin:true};
app.use("/api",proxyMiddleWare(proxyOption)) //对api请求使用代理
app.use("/api",proxyMiddleWare(proxyOption)) //对api请求使用代理
```
前端服务器代理的好处是对于用户而言只能看到前端服务器的地址,后端服务器的安全性得到提高;同时代理发生在内网环境,速度快。
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment