Commit 36fbc930 by 安博

first

parent 49875a3f
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
下载8.11.1LTS版本,安装成功之后,cmd中path中有没有nodejs的安装路径 下载8.11.1LTS版本,安装成功之后,cmd中path中有没有nodejs的安装路径
node –version `node –version`
查看版本 查看版本
...@@ -30,21 +30,21 @@ node ersion ...@@ -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 install(发现很慢,卡死)
Npm换淘宝源: 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 1.4          下载webstorm
------------------------- -------------------------
...@@ -59,17 +59,17 @@ npm install ...@@ -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使前后台链接起来
![图片](media/73de7ae8d602858b05ca312d9c267f70.png) ![图片](media/73de7ae8d602858b05ca312d9c267f70.png)
...@@ -78,64 +78,45 @@ npm install ...@@ -78,64 +78,45 @@ npm install
路由的配置文件统一由src/common/router.js文件进行管理。const对象routerConfig中定义了最顶层的路由配置。以根目录的路由配置为例: 路由的配置文件统一由src/common/router.js文件进行管理。const对象routerConfig中定义了最顶层的路由配置。以根目录的路由配置为例:
/\*\* ```
/**
* src/common/router.js
*/
\* src/common/router.js '/':{compoent:dynamicWrapper(app,[],()=>import('../layouts/BasicLayout')}
```
\*/
'/':{compoent:dynamicWrapper(app,[],()=\>import('../layouts/BasicLayout')}
每个配置项是一组键值对,键是要路由到的路径,值是一个component组件,由函数dynamicWrappe生成,其传入的参数包括:工程上下文对象app(可省略);一个数组,包含要用到的model(可为空);要渲染的component(可以加载layout或者routes目录下的.js) 每个配置项是一组键值对,键是要路由到的路径,值是一个component组件,由函数dynamicWrappe生成,其传入的参数包括:工程上下文对象app(可省略);一个数组,包含要用到的model(可为空);要渲染的component(可以加载layout或者routes目录下的.js)
src/common/menu.js文件是导航栏记录。menuData对象是一个纯json,描述了菜单的层级结构和描述属性,包括名称、图标、路径、权限等。如下是一个带有子目录的配置。在menu.js代码末尾对该json文件进行读取和解析,封装成routeData。 src/common/menu.js文件是导航栏记录。menuData对象是一个纯json,描述了菜单的层级结构和描述属性,包括名称、图标、路径、权限等。如下是一个带有子目录的配置。在menu.js代码末尾对该json文件进行读取和解析,封装成routeData。
```
/**
/\*\* * src/common/menu.js
\* src/common/menu.js */
\*/
{ {
name: '账户',
name: '账户', icon: 'user',
path: 'user',
icon: 'user', authority: 'guest',
children: [
path: 'user', {
name: '登录',
authority: 'guest', path: 'login',
},
children: [ {
name: '注册',
{ path: 'register',
},
name: '登录', {
name: '注册结果',
path: 'login', path: 'register-result',
},
}, ],
}
{ ```
name: '注册',
path: 'register',
},
{
name: '注册结果',
path: 'register-result',
},
],
}
关于路径,router.js采用的是至多二级目录的完整路径,而menu.js每个节点只记录自己这一层的路径名,无需写出完整路径。 关于路径,router.js采用的是至多二级目录的完整路径,而menu.js每个节点只记录自己这一层的路径名,无需写出完整路径。
1.2          新建一个组件 1.2          新建一个组件
...@@ -144,656 +125,381 @@ path: 'register-result', ...@@ -144,656 +125,381 @@ path: 'register-result',
在使用组件时,默认会在 index.js 中寻找 export 在使用组件时,默认会在 index.js 中寻找 export
的对象,如果你的组件比较复杂,可以分为多个文件,最后在 index.js 中统一 export 的对象,如果你的组件比较复杂,可以分为多个文件,最后在 index.js 中统一 export
### 1.2.1          继承现有组件
### 1.2.2          创建全新组件
1.3          Model的实现原理 1.3          Model的实现原理
---------------------------- ----------------------------
Model我理解为,一个基于特定数据的状态和对状态进行改变的操作的整体,大致相当于对象(Object)或者数据结构的概念。每个model维护了一个特定状态,这个状态通常是基于某些数据的,当数据发生改变时,相应的状态也随之改变,从而触发react对组件的更新。 Model我理解为,一个基于特定数据的状态和对状态进行改变的操作的整体,大致相当于对象(Object)或者数据结构的概念。每个model维护了一个特定状态,这个状态通常是基于某些数据的,当数据发生改变时,相应的状态也随之改变,从而触发react对组件的更新。
React的思想是,数据单向流动,如果顶层数据改变则认为状态发生了变化,从而刷新受影响的组件。为此,我们需要在数据改变时去更改model的状态。一个常用的方式是,状态即数据。以下为示例代码。 React的思想是,数据单向流动,如果顶层数据改变则认为状态发生了变化,从而刷新受影响的组件。为此,我们需要在数据改变时去更改model的状态。一个常用的方式是,状态即数据。以下为示例代码。
```
/\*\* /**
* model/play.js
\* model/play.js */
\*/
import {play} from '../services/api' //引入API import {play} from '../services/api' //引入API
export default { export default {
namespace: 'play', //名字空间,用于组织函数
namespace: 'play', //名字空间,用于组织函数 state: {data: 0}, //状态对象,可以是字典、数组等,没有特别限制
effects: {
state: {data: 0}, //状态对象,可以是字典、数组等,没有特别限制 * getTime(_, {call, put})
{
effects: { const response = yield call(play);
yield put({
\* getTime(_, {call, put}) type: 'time',
data: response
{ })
}
const response = yield call(play); },
reducers: {
yield put({ time(state, {data})
{
type: 'time', return {
...state,
data: response data
}
}) }
}
} }
```
},
reducers: {
time(state, {data})
{
return {
...state,
data
}
}
}
}
### 1.3.1          effect(异步请求) ### 1.3.1          effect(异步请求)
effect是Model的一组操作,用于请求和改变数据,得到的数据需要指向一个reduce中定义的操作,在那里进行归约。 effect是Model的一组操作,用于请求和改变数据,得到的数据需要指向一个reduce中定义的操作,在那里进行归约。
```
//this is a sample //this is a sample
effects: {
effects: { * getTime(_, {call, put}) //*标志这是一个generator函数,_处是可能的输入数据(如果在调用effect时给了的话)
{
\* getTime(_, {call, put}) const response = yield call(play); //call是一个上下文提供的用于请求API的方法,可带参
//\*标志这是一个generator函数,_处是可能的输入数据(如果在调用effect时给了的话) yield put({
type: 'time',
{ data: response
}) //put方法用于提交数据到reduce,type字段是reduce函数的名称,其他字段可以自己随意命名,在reduce函数中用同样的名称接收
const response = yield call(play); }
//call是一个上下文提供的用于请求API的方法,可带参 }
```
yield put({
type: 'time',
data: response
})
//put方法用于提交数据到reduce,type字段是reduce函数的名称,其他字段可以自己随意命名,在reduce函数中用同样的名称接收
}
}
### 1.3.2          reduce(更新状态) ### 1.3.2          reduce(更新状态)
和effect类似,包含了一组操作,用于将effect传过来的数据进行归约。操作的输入是model已有的状态和归约的数据,输出是新的状态。如果状态没有改变则不会触发组件更新。 和effect类似,包含了一组操作,用于将effect传过来的数据进行归约。操作的输入是model已有的状态和归约的数据,输出是新的状态。如果状态没有改变则不会触发组件更新。
```
//this is a sample //this is a sample
reducers: {
reducers: { time(state, {data}) //state就是model中的state,data对应effect中传入的字段
{
time(state, {data}) //state就是model中的state,data对应effect中传入的字段 return {
...state,
{ data
} //返回新的状态。这里使用对象解构语法将state中的字段与data合并构造出新的state对象
return { }
}
...state, ```
data
}
//返回新的状态。这里使用对象解构语法将state中的字段与data合并构造出新的state对象
}
}
1.4          如何在组件渲染中使用Model 1.4          如何在组件渲染中使用Model
-------------------------------------- --------------------------------------
先上示例代码。play.js即1.2节中路由指向的脚本,在这里完成对应路由的页面渲染。 先上示例代码。play.js即1.2节中路由指向的脚本,在这里完成对应路由的页面渲染。
```
/\*\* /**
* routes/play/play.js
\* routes/play/play.js */
\*/
import React, {Component, Fragment} from 'react'; //引入react import React, {Component, Fragment} from 'react'; //引入react
import {connect} from 'dva' //引入dva框架中的connect组件 import {connect} from 'dva' //引入dva框架中的connect组件
/**
/\*\* *通过注解形式链接model,每个链接的model都会被保存到组件上下文中
*实质上是model的state
\*通过注解形式链接model,每个链接的model都会被保存到组件上下文中 */
@connect(({play, loading}) => ({
\*实质上是model的state play, //链接的model名称
getTime: loading.effects['play/getTime'] //loading是一个内建对象,可以从中提取model的effect函数或者state里的字段保存到上下文
\*/
\@connect(({play, loading}) =\> ({
play, //链接的model名称
getTime: loading.effects['play/getTime']
//loading是一个内建对象,可以从中提取model的effect函数或者state里的字段保存到上下文
})) }))
/\*\* /**
*继承component,或者引入的其他现有组件
\*继承component,或者引入的其他现有组件 */
\*/
export default class play extends Component { export default class play extends Component {
/**
/\*\* *这是组件加载时要执行的方法(实现interface)
*/
\*这是组件加载时要执行的方法(实现interface) componentDidMount()
{
\*/ const {dispatch} = this.props; //上下文里包含一个dispatch方法
dispatch({type: 'play/getTime'}) //向type指向的effect发起状态改变请求,可以带参
componentDidMount() }
/**
{ *在第一次加载页面或者model的状态发生改变时会触发render
*/
const {dispatch} = this.props; //上下文里包含一个dispatch方法 render()
{
dispatch({type: 'play/getTime'}) //向type指向的effect发起状态改变请求,可以带参 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>
\*在第一次加载页面或者model的状态发生改变时会触发render </div>); //JSX语法
}
\*/
refresh = e => {
render() e.preventDefault();
const {dispatch} = this.props;
{ dispatch({type: 'play/getTime'});
}
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 ### 1.4.1          连接Model
通过\@connect注解(本质上是装饰器)链接model,链接的model和对应的字段、方法可以保存到组件里的上下文中。 通过@connect注解(本质上是装饰器)链接model,链接的model和对应的字段、方法可以保存到组件里的上下文中。
```
/\*\* /**
*通过注解形式链接model,每个链接的model都会被保存到组件上下文中
\*通过注解形式链接model,每个链接的model都会被保存到组件上下文中 *实质上是model的state
*/
\*实质上是model的state @connect(({play, loading}) => ({
play, //链接的model名称
\*/ getTime: loading.effects['play/getTime'] //loading是一个内建对象,可以从中提取model的effect函数或者state里的字段保存到上下文
\@connect(({play, loading}) =\> ({
play, //链接的model名称
getTime: loading.effects['play/getTime']
//loading是一个内建对象,可以从中提取model的effect函数或者state里的字段保存到上下文
})) }))
```
### 1.4.2          jsx ### 1.4.2          jsx
JSX语法类似于HTML,是一种模板语言,可以使用大括号标识要模板化的数据。 JSX语法类似于HTML,是一种模板语言,可以使用大括号标识要模板化的数据。
```
return (\<div\>{data.time} return (<div>{data.time}
<button style={{marginLeft: 10}} onClick={this.refresh}>刷新时间</button>
\<button style={{marginLeft: 10}} onClick={this.refresh}\>刷新时间\</button\> </div>); //JSX语法
```
\</div\>); //JSX语法
1.5          调用API 1.5          调用API
-------------------- --------------------
### 1.5.1        service ### 1.5.1        service
注意到我们在model/play.js里第一行引入了services/API中的内容。这个文件的部分内容如下。 注意到我们在model/play.js里第一行引入了services/API中的内容。这个文件的部分内容如下。
```
/\*\* /**
*services/API.js
\*services/API.js */
\*/
import { stringify } from 'qs'; import { stringify } from 'qs';
import request from '../utils/request'; //框架提供的异步请求函数 import request from '../utils/request'; //框架提供的异步请求函数
export async function queryProjectNotice() { export async function queryProjectNotice() {
return request('/api/project/notice');
return request('/api/project/notice');
} }
export async function queryRule(params) { export async function queryRule(params) {
return request(`/api/rule?${stringify(params)}`);
return request(\`/api/rule?\${stringify(params)}\`);
} }
export async function removeRule(params) { export async function removeRule(params) {
return request('/api/rule', {
return request('/api/rule', { method: 'POST',
body: {
method: 'POST', ...params,
method: 'delete',
body: { },
});
...params,
method: 'delete',
},
});
} }
export async function addRule(params) { export async function addRule(params) {
return request('/api/rule', {
return request('/api/rule', { method: 'POST',
body: {
method: 'POST', ...params,
method: 'post',
body: { },
});
...params,
method: 'post',
},
});
} }
export async function fakeSubmitForm(params) { export async function fakeSubmitForm(params) {
return request('/api/forms', {
return request('/api/forms', { method: 'POST',
body: params,
method: 'POST', });
body: params,
});
} }
export async function queryFakeList(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) { export async function fakeAccountLogin(params) {
return request('/api/login/account', {
return request('/api/login/account', { method: 'POST',
body: params,
method: 'POST', });
body: params,
});
} }
export async function play() export async function play()
{ {
return request('/api/play');
return request('/api/play');
} }
```
可以看到,我们引入的play就是最后的这个方法,它返回了一个对/api/play的请求。这个文件用于组织我们的API,几种请求的写法在上述代码中有示例。 可以看到,我们引入的play就是最后的这个方法,它返回了一个对/api/play的请求。这个文件用于组织我们的API,几种请求的写法在上述代码中有示例。
可以把API拆分成不同的文件,放在services目录下。 可以把API拆分成不同的文件,放在services目录下。
### 1.5.2         mock ### 1.5.2         mock
```
/\*\* /**
* .roadhogrc.mock.js
\* .roadhogrc.mock.js */
import mockjs from 'mockjs';
\*/ import {getRule, postRule} from './mock/rule';
import {getActivities, getNotice, getFakeList} from './mock/api';
import mockjs from 'mockjs'; import {getFakeChartData} from './mock/chart';
import {getProfileBasicData} from './mock/profile';
import {getRule, postRule} from './mock/rule'; import {getProfileAdvancedData} from './mock/profile';
import {getNotices} from './mock/notices';
import {getActivities, getNotice, getFakeList} from './mock/api'; import {format, delay} from 'roadhog-api-doc';
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;
// 是否禁用代理
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时还会进行是否为表单数据的判断,对非表单数据的补全头数据。 从request的方法头可以看出,我们在使用request请求url的时候,可以通过第二个参数指定使用post方法,具体只需要传入一个带有method:'post'的对象。此外在post时还会进行是否为表单数据的判断,对非表单数据的补全头数据。
...@@ -806,161 +512,127 @@ dispatch(routerRedux.push('/exception/404')); ...@@ -806,161 +512,127 @@ dispatch(routerRedux.push('/exception/404'));
----------------------------------- -----------------------------------
lambda函数的形式:前者是完整形式,后者是仅需要执行一行代码并返回一个结果情况下的简写。 lambda函数的形式:前者是完整形式,后者是仅需要执行一行代码并返回一个结果情况下的简写。
```
(pram1,pram2)=\>{console.log(pram1);} (pram1,pram2)=>{console.log(pram1);}
(p1,p2)=>p1**2;
(p1,p2)=\>p1\*\*2; p1=>p1**2;
```
p1=\>p1\*\*2;
一个简单的理解是,它定义了函数的输入和函数体但并不显式声明,可用于创建匿名函数。相比于普通形式的函数声明,它更能突出输入输出的信息,便于传递函数作为参数。 一个简单的理解是,它定义了函数的输入和函数体但并不显式声明,可用于创建匿名函数。相比于普通形式的函数声明,它更能突出输入输出的信息,便于传递函数作为参数。
请注意,lambda函数中可以使用创建它时的上下文中的可访问变量,换言之,函数体内的作用域继承了声明它的上下文的作用域,从而在函数被调用时即便上下文发生了改变,它也依旧可以使用创建时的作用域的可用变量,这个特性称为闭包。 请注意,lambda函数中可以使用创建它时的上下文中的可访问变量,换言之,函数体内的作用域继承了声明它的上下文的作用域,从而在函数被调用时即便上下文发生了改变,它也依旧可以使用创建时的作用域的可用变量,这个特性称为闭包。
Java8中的lambda函数不能修改上下文中的其他变量(显式或可隐式推断为final),而C\#和JavaScript中的lambda函数没有相关限制。 Java8中的lambda函数不能修改上下文中的其他变量(显式或可隐式推断为`final`),而C#和JavaScript中的lambda函数没有相关限制。
3.2          generator和yield 3.2          generator和yield
----------------------------- -----------------------------
generator即生成器函数,使用 \* 声明。至于为什么叫生成器可以从下面的例子来理解。 generator即生成器函数,使用 * 声明。至于为什么叫生成器可以从下面的例子来理解。
```
\*function example() *function example()
{ {
for(int i=0;i<10;i++)
for(int i=0;i\<10;i++) yield i;
yield i;
} }
```
这个函数第一次执行的时候会生成一个生成器函数,它提供了一个next()方法。当我们调用next方法时,函数会执行到yield的位置处停下,并返回yield后面的表达式的结果。当再次调用next方法的时候,可以传入一个参数,此时传入的参数会覆盖刚刚返回的表达式的结果,当然也可以不带参数正常执行。 这个函数第一次执行的时候会生成一个生成器函数,它提供了一个`next()`方法。当我们调用next方法时,函数会执行到yield的位置处停下,并返回yield后面的表达式的结果。当再次调用next方法的时候,可以传入一个参数,此时传入的参数会覆盖刚刚返回的表达式的结果,当然也可以不带参数正常执行。
可以看出,yield的存在让函数可以分步执行,还能在执行过程中进行干预,获取中间值。这个特性使得我们可以获取一个异步回调函数中的结果,而无须进入函数内部,从而避免了多个异步函数嵌套,造成“回调黑洞”现象。 可以看出,yield的存在让函数可以分步执行,还能在执行过程中进行干预,获取中间值。这个特性使得我们可以获取一个异步回调函数中的结果,而无须进入函数内部,从而避免了多个异步函数嵌套,造成“回调黑洞”现象。
\*可以写在function的前面或者函数名前面,不能写在函数名末尾。 *可以写在function的前面或者函数名前面,不能写在函数名末尾。
3.3          export 3.3          export
------------------- -------------------
export是ES6标准中用于组织模块的关键字,被export的对象可以在import相应js文件的时候通过解构取出来。可以指定default export是ES6标准中用于组织模块的关键字,被`export`的对象可以在`import`相应js文件的时候通过解构取出来。可以指定`default
export以作为默认导出对象。 export`以作为默认导出对象。
3.4          扩展运算符和解构 3.4          扩展运算符和解构
----------------------------- -----------------------------
扩展运算符用于将一个对象或数据的成员列举出来,使用 ... 作为运算符。 扩展运算符用于将一个对象或数据的成员列举出来,使用 `...` 作为运算符。
```
let object={a:1,b:2}; let object={a:1,b:2};
let newObject={...object,b:4} let newObject={...object,b:4}
console.log(newObject); //{a:1,b:4} console.log(newObject); //{a:1,b:4}
```
通过扩展运算符,可以方便的展开对象和数组,对其中的元素进行修改、提取。 通过扩展运算符,可以方便的展开对象和数组,对其中的元素进行修改、提取。
解构用于根据键名或者顺序提取对象和数组中的部分元素。 解构用于根据键名或者顺序提取对象和数组中的部分元素。
```
let {a}=object; let {a}=object;
console.log(a); //a:1 console.log(a); //a:1
let array=[1,2,3,4,5]; let array=[1,2,3,4,5];
let [p1,p2,...subArray]=array; let [p1,p2,...subArray]=array;
console.log(p2); //p2=2 console.log(p2); //p2=2
console.log(subArray); //subArray=[3,4,5] console.log(subArray); //subArray=[3,4,5]
```
解构操作支持嵌套解构,对于数组解构可以使用占位符跳过不需要的元素。 解构操作支持嵌套解构,对于数组解构可以使用占位符跳过不需要的元素。
3.5          promise和async/await 3.5          promise和async/await
--------------------------------- ---------------------------------
promise是一个特殊的对象,它封装了一个函数,当这个函数成功执行时会触发resolve方法返回一个结果,未成功执行时触发reject方法。多个promise可以用then方法链式调用,并将之前的计算结果传递下去。 promise是一个特殊的对象,它封装了一个函数,当这个函数成功执行时会触发`resolve`方法返回一个结果,未成功执行时触发`reject`方法。多个promise可以用`then`方法链式调用,并将之前的计算结果传递下去。
```
let p1=data=\>new Promise((resolve,reject)=\>{resolve(data)}); let p1=data=>new Promise((resolve,reject)=>{resolve(data)});
p1(1).then(p1).then(data=>console.log(data)).catch(e=>console.log(e));
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`捕捉整个链上的异常。
resolve方法中传入的参数即为计算结果,可以大概理解为用它代替传统的return。每个promise在构造后都会立即执行,执行过程中会改变promise的状态(未完成,成功,失败)。使用then传入一个函数(data=\>{})以提取返回的结果。如果then中传入的函数返回promise,则可以继续调用then获取这个promise的返回结果。使用catch捕捉整个链上的异常。
对于一个涉及多个异步操作的流程,传统的写法是: 对于一个涉及多个异步操作的流程,传统的写法是:
```
request("input",(resulta,error) request("input",(resulta,error)
{
request(resulta,(resultb,error)
{
request(resultb,(resultc,error))
{ {
request(resulta,(resultb,error)
console.log(resultc); {
request(resultb,(resultc,error))
} {
console.log(resultc);
}
})
}) })
```
})
做promise化处理后的写法: 做promise化处理后的写法:
```
let request_promise=data=\>new let request_promise=data=>new
Promise((resolve,reject)=\>{request(data,(result,error){resolve(result)})}); Promise((resolve,reject)=>{request(data,(result,error){resolve(result)})});
//对request做promise化 //对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));
//链式调用 //链式调用
```
尽管解决了多重回调的问题,promise调用链的写法依旧不够直观,为此引入了async和await关键字。 尽管解决了多重回调的问题,promise调用链的写法依旧不够直观,为此引入了`async``await`关键字。
```
async function fun(input) async function fun(input)
{ {
let resulta=await request_promise(input);
let resulta=await request_promise(input); let resultb=await request_promise(resulta);
let resultc=await request_promise(resultb);
let resultb=await request_promise(resulta); console.log(resultc);
return resultc;
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修饰的函数可以无限制调用。 await可以从一个promise中提取resolve的结果,如果当前promise的状态是未完成,则会在此等待返回。**使用了await的函数必须用async修饰**,但async修饰的函数可以无限制调用。
事实上async方法执行时也会返回一个promise,promise的resolve结果是方法中return的内容,这意味着可以用await获取async的返回结果。 事实上async方法执行时也会返回一个promise,promise的resolve结果是方法中return的内容,这意味着**可以用await获取async的返回结果**
```
async function main() async function main()
{ {
let result=await fun("input");
let result=await fun("input"); console.log(result);
console.log(result);
} }
main(); main();
```
所以,**在顶层作用域使用`async`方法的时候,将其作为一个`promise`看待(当然如果你不关心其返回结果就可以单纯的执行它);在函数中使用`async`方法的时候,使用`await`关键字获取其返回结果,并把这个函数本身也用`async`修饰。**
所以,**在顶层作用域使用async方法的时候,将其作为一个promise看待(当然如果你不关心其返回结果就可以单纯的执行它);在函数中使用async方法的时候,使用await关键字获取其返回结果,并把这个函数本身也用async修饰。** 目前`async``await`关键字是解决异步回调嵌套问题的最优解。
目前async和await关键字是解决异步回调嵌套问题的最优解。
第4章          页面组件 第4章          页面组件
======================= =======================
本章针对页面的各种步骤条、选择框、表单、图表等组件进行说明。首先要在router.js以及menu.js中把路由和菜单写好,确保单词拼写无误,尽可能复制,页面报错是检查不出拼写出错的,所以这个一定要反复确认没问题,不然会浪费很长时间。 本章针对页面的各种步骤条、选择框、表单、图表等组件进行说明。首先要在`router.js`以及`menu.js`中把路由和菜单写好,确保单词拼写无误,尽可能复制,页面报错是检查不出拼写出错的,所以这个一定要反复确认没问题,不然会浪费很长时间。
4.1          步骤条 4.1          步骤条
------------------- -------------------
...@@ -972,254 +644,139 @@ design銝剜*http://ant.design/components/steps-cn/*嚗 ...@@ -972,254 +644,139 @@ design銝剜*http://ant.design/components/steps-cn/*嚗
### 4.1.1         菜单栏添加 ### 4.1.1         菜单栏添加
首先在menu.js中,建立好菜单的关系,我们把资源注册命名为registration。 首先在`menu.js`中,建立好菜单的关系,我们把资源注册命名为registration。
```
{
name: '计算资源管理',
icon: 'table',
path: 'resource-management',
children: [
{
name: '计算资源注册',
path: 'registration',
},
{ {
name: '计算资源管理',
name: '计算资源查看', icon: 'table',
path: 'resource-management',
path: 'views', children: [
{
}, name: '计算资源注册',
path: 'registration',
], },
{
}, name: '计算资源查看',
path: 'views',
},
],
}
```
### 4.1.2         页面创建路由添加 ### 4.1.2         页面创建路由添加
然后router.js中添加路由关系,第一个为注册的路由,中间三个为资源注册的三个步骤的路由,也好命名好,同时根据其在routes文件夹下建立Registration文件,该文件下有四个js文件:index.js为Registration的基础,Step1.js是第一步的页面,Step2.js是第二步的页面, 然后router.js中添加路由关系,第一个为注册的路由,中间三个为资源注册的三个步骤的路由,也好命名好,同时根据其在routes文件夹下建立Registration文件,该文件下有四个js文件:index.js为Registration的基础,Step1.js是第一步的页面,Step2.js是第二步的页面,
Step3.js是第三步页面。 Step3.js是第三步页面。
```
'/resource-management/registration':{ '/resource-management/registration':{
component: dynamicWrapper(app, ['form'], () => import('../routes/ResourceManagement/Registration')),
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration')),
}, },
'/resource-management/registration/resource-info':{ '/resource-management/registration/resource-info':{
name: '计算资源注册',
name: '计算资源注册', component: dynamicWrapper(app, ['form'], () => import('../routes/ResourceManagement/Registration/Step1')),
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration/Step1')),
}, },
'/resource-management/registration/interview-method':{ '/resource-management/registration/interview-method':{
name: '计算资源注册',
name: '计算资源注册', component: dynamicWrapper(app, ['form'], () => import('../routes/ResourceManagement/Registration/Step2')),
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration/Step2')),
}, },
'/resource-management/registration/result':{ '/resource-management/registration/result':{
name: '计算资源注册',
name: '计算资源注册', component: dynamicWrapper(app, ['form'], () => import('../routes/ResourceManagement/Registration/Step3')),
component: dynamicWrapper(app, ['form'], () =\>
import('../routes/ResourceManagement/Registration/Step3')),
}, },
'/resource-management/views':{ '/resource-management/views':{
component: dynamicWrapper(app, [], () => import('../routes/ResourceManagement/Views')),
component: dynamicWrapper(app, [], () =\> }
import('../routes/ResourceManagement/Views')), ```
},
### 4.1.3         开始写代码 ### 4.1.3         开始写代码
在indes.js中写跳转关系以及路径选择,其class名称为该文件夹名称。 在indes.js中写跳转关系以及路径选择,其class名称为该文件夹名称。
switch为路径选择, `switch`为路径选择,
\<steps\>下为即为步骤条, `<steps>`下为即为步骤条,
Redirect是从当前页面跳转到下一个路径的页面部分。
`Redirect`是从当前页面跳转到下一个路径的页面部分。
```
export default class Registration extends PureComponent { export default class Registration extends PureComponent {
getCurrentStep() {
getCurrentStep() { const { location } = this.props;
const { pathname } = location;
const { location } = this.props; const pathList = pathname.split('/');
switch (pathList[pathList.length - 1]) {
const { pathname } = location; case 'resource-info':
return 0;
const pathList = pathname.split('/'); case 'interview-method':
return 1;
switch (pathList[pathList.length - 1]) { case 'result':
return 2;
case 'resource-info': default:
return 0;
return 0; }
}
case 'interview-method': render() {
const { match, routerData } = this.props;
return 1; return (
<PageHeaderLayout>
case 'result': <Card bordered={false}>
<Fragment>
return 2; <Steps current={this.getCurrentStep()} className={styles.steps}>
<Step title="填写计算资源信息" />
default: <Step title="填写访问方式" />
<Step title="完成" />
return 0; </Steps>
<Switch>
} {getRoutes(match.path, routerData).map(item => (
<Route
} key={item.key}
path={item.path}
render() { component={item.component}
exact={item.exact}
const { match, routerData } = this.props; />
))}
return ( <Redirect exact from="/resource-management/registration" to="/resource-management/registration/resource-info" />
<Route render={NotFound} />
\<PageHeaderLayout\> </Switch>
</Fragment>
\<Card bordered={false}\> </Card>
</PageHeaderLayout>
\<Fragment\> );
}
\<Steps current={this.getCurrentStep()} className={styles.steps}\> }
```
\<Step title="填写计算资源信息" /\> Step1.js中,在下一步的按钮提交函数中一定要写路径的跳转,同时,表单内部的内容可以自己根据情况写好,最后`export
default connect`一定不要忘记加上,没有他们,路由找不到页面,这块语法不是很懂。
\<Step title="填写访问方式" /\> ```
@Form.create()
\<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 { class Step1 extends React.PureComponent {
render() {
render() { const { form, dispatch, data } = this.props;
const { getFieldDecorator, validateFields } = form;
const { form, dispatch, data } = this.props; const onValidateForm = () => {
validateFields((err, values) => {
const { getFieldDecorator, validateFields } = form; if (!err) {
dispatch(routerRedux.push('/resource-management/registration/interview-method'));
const onValidateForm = () =\> { }
});
validateFields((err, values) =\> { };
return (
if (!err) { <Fragment>
<Form layout="horizontal" className={styles.stepForm} hideRequiredMark>
dispatch(routerRedux.push('/resource-management/registration/interview-method')); //此处可自己写
<Form.Item>
} <Button type="primary" onClick={onValidateForm}>
下一步
}); </Button>
</Form.Item>
}; </Form>
return ( </Fragment>
);
\<Fragment\> }
}
\<Form layout="horizontal" className={styles.stepForm} hideRequiredMark\>
export default connect(({ form }) => ({
//此处可自己写 data: form.step,
\<Form.Item\>
\<Button type="primary" onClick={onValidateForm}\>
下一步
\</Button\>
\</Form.Item\>
\</Form\>
\</Fragment\>
);
}
}
export default connect(({ form }) =\> ({
data: form.step,
}))(Step1); }))(Step1);
```
Step2.js和Step3.js的原理和Step1是一样的。只不过要加上一个onPrev函数是在点击上一步按钮时的回到上一步操作。
const onPrev = () =\> {
dispatch(routerRedux.push('/data-management/registration/interview-method'));
};
至此我们的步骤条就写完啦 至此我们的步骤条就写完啦
4.2          表单 4.2          表单
...@@ -1233,52 +790,36 @@ dispatch(routerRedux.push('/data-management/registration/interview-method')); ...@@ -1233,52 +790,36 @@ dispatch(routerRedux.push('/data-management/registration/interview-method'));
对于一个前端页面而言,其背后的脚本对用户是透明的,如果我们直接将网站上线势必造成代码暴露,为此我们需要考虑如何发布项目。 对于一个前端页面而言,其背后的脚本对用户是透明的,如果我们直接将网站上线势必造成代码暴露,为此我们需要考虑如何发布项目。
在项目的package.json中定义了若干命令: 在项目的`package.json`中定义了若干命令:
```
"scripts": { "scripts": {
"precommit": "npm run lint-staged",
"precommit": "npm run lint-staged", "start": "cross-env ESLINT=none roadhog dev",
"start:no-proxy": "cross-env NO_PROXY=true ESLINT=none roadhog dev",
"start": "cross-env ESLINT=none roadhog dev", "build": "cross-env ESLINT=none roadhog build",
"site": "roadhog-api-doc static && gh-pages -d dist",
"start:no-proxy": "cross-env NO_PROXY=true ESLINT=none roadhog dev", "analyze": "cross-env ANALYZE=true roadhog build",
"lint:style": "stylelint \"src/**/*.less\" --syntax less",
"build": "cross-env ESLINT=none roadhog build", "lint": "eslint --ext .js src mock tests && npm run lint:style",
"lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style",
"site": "roadhog-api-doc static && gh-pages -d dist", "lint-staged": "lint-staged",
"lint-staged:js": "eslint --ext .js",
"analyze": "cross-env ANALYZE=true roadhog build", "test": "roadhog test",
"test:component": "roadhog test ./src/components",
"lint:style": "stylelint \\"src/\*\*/\*.less\\" --syntax less", "test:all": "node ./tests/run-tests.js",
"prettier": "prettier --write ./src/**/**/**/*"
"lint": "eslint --ext .js src mock tests && npm run lint:style", }
```
"lint:fix": "eslint --fix --ext .js src mock tests && npm run lint:style", 其中,`start``start:no-proxy`命令是使用代理和不使用代理启动项目,而build命令则是用于编译项目的。该命令会将项目编译到`dist`目录,生成四个文件:
"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) ![图片](media/0fb462a4205c257717e410986e5b8163.png)
中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失效 5.2          mock失效
--------------------- ---------------------
请注意,之前在.roadhogrc.mock.js中编写的mock代码不会被打包,对API的访问控制截至request.js,所以之前的跨域请求会失效。此时有三种方式解决: 请注意,之前在`.roadhogrc.mock.js`中编写的mock代码不会被打包,对API的访问控制截至`request.js`,所以之前的跨域请求会失效。此时有三种方式解决:
1. 沿用开发环境的服务器,显然这种情况是无法做代码打包和混淆的; 1. 沿用开发环境的服务器,显然这种情况是无法做代码打包和混淆的;
...@@ -1292,48 +833,37 @@ dispatch(routerRedux.push('/data-management/registration/interview-method')); ...@@ -1292,48 +833,37 @@ dispatch(routerRedux.push('/data-management/registration/interview-method'));
--------------------- ---------------------
关于开启跨域许可,只需要在服务器返回的http响应头中添加以下字段,以Express框架为例: 关于开启跨域许可,只需要在服务器返回的http响应头中添加以下字段,以Express框架为例:
```
app.all('\*', function(req, res, next) { app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", req.get('Origin'));
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("Access-Control-Allow-Headers", "X-Requested-With"); res.header("Content-Type", "application/json;charset=utf-8");
res.header("Access-Control-Allow-Credentials","true");
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); next();
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          url重定向
---------------------- ----------------------
### 5.4.1          前端重定向 ### 5.4.1          前端重定向
修改request.js使其重定向,只需修改这一行: 修改`request.js`使其重定向,只需修改这一行:
```
//return fetch(url, newOptions) line67 //return fetch(url, newOptions) line67
return fetch('http://localhost:3000' + url, newOptions) return fetch('http://localhost:3000' + url, newOptions)
```
这种方式会暴露API服务器地址,并且api请求实际是从用户浏览器到后端服务器,外网环境下速度可能会有影响。 这种方式会暴露API服务器地址,并且api请求实际是从用户浏览器到后端服务器,外网环境下速度可能会有影响。
### 5.4.2          前端服务器代理 ### 5.4.2          前端服务器代理
如果不希望暴露API服务器地址,则可以在前端服务器中进行反向代理。以Express框架为例,只需要引入代理中间件: 如果不希望暴露API服务器地址,则可以在前端服务器中进行反向代理。以Express框架为例,只需要引入代理中间件:
```
let proxyMiddleWare = require("http-proxy-middleware"); 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}; let proxyOption ={target:proxyPath,changeOrigoin:true};
app.use("/api",proxyMiddleWare(proxyOption)) //对api请求使用代理 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