前端的代码是运行在nodejs环境下,前端代码大概分为service层,model层,和view层(page),service层封装请求接口的函数,model层调用service层定义的函数获取参数并存储起来,然后把存储的数据再page中展示出来。
下面一步步讲解:
由于涉及的前端知识比较多,很多细节并没有讲解得得很充分,只是大概讲一个过程,想学更多得前端知识请看:es6,react,dva,antd等一系列知识。
在service层中需要用到两个封装好的工具函数,在前端项目中可以添加到utils目录中
urlencodeed.js 作用:拼接请求参数
//@ts-check
/**
* @param {{[x: string]: any}} fields
*/
export function urlencoded(fields) {
if (!fields)
return '';
const result = [];
Object.keys(fields).forEach(key => {
const value = fields[key];
if (typeof value === 'undefined' || (typeof value === 'object' && !value)) // undefined or null
return;
if (Array.isArray(value))
return value.forEach(v => result.push(`${key}=${encodeURIComponent(v)}`));
return result.push(`${key}=${encodeURIComponent(value)}`);
});
return result.join('&');
}
requestWithToken.js 作用:用于发起HTTP请求
//@ts-check
///
import _ from 'lodash';
import fetch from 'dva/fetch';
import { notification } from 'antd';
import { saveTokenInfo, getTokenInfo, clearTokenInfo } from './tokenStorage';
import { isDevelopment, unused, devEcho, devWarning } from './debug';
import { urlencoded } from './urlencoded';
import { expandObjectToJSON } from './expandObjectToJSON';
const defaultOptions = { credentials: 'include' };
const X_CSRF = 'X-CSRF-TOKEN';
/**
* @param {string} methodUri for example: "GET /api/services/xxx"
* @param {{headers?: {[name: string]: string}; body?: any; asJSON?: boolean|"expand";}} [options]
*/
function createHttpFetch(methodUri, options = {}) {
const [method, uri] = methodUri.split(/\s+/);
const opts = { ...defaultOptions, ...options, method };
if (method !== 'GET') {
if (!(opts.body instanceof FormData)) {
opts.headers = {
Accept: 'application/json',
// 'Content-Type': 'application/json; charset=utf-8',
'Content-Type': opts.asJSON
? 'application/json; charset=utf-8'
: 'application/x-www-form-urlencoded; charset=utf-8',
...opts.headers,
};
// opts.body = JSON.stringify(opts.body);
opts.body = opts.asJSON
? JSON.stringify(opts.asJSON === 'expand' ? expandObjectToJSON(opts.body) : opts.body)
: urlencoded(opts.body);
} else {
// newOptions.body is FormData
opts.headers = {
Accept: 'application/json',
// 修复参考: https://github.com/ant-design/ant-design-pro/issues/1217
// 'Content-Type': 'multipart/form-data',
...opts.headers,
};
}
}
//@ts-ignore
return fetch(uri, opts);
}
let lastLogoutNotify = 0;
function passiveLogout() {
clearTokenInfo(); // 清除 localStorage 中的 token 信息
const now = Date.now();
if (now > lastLogoutNotify + 1500) {
lastLogoutNotify = now;
notification.warning({
message: '请重新登录',
description: '由于你的会话已经过期, 请重新登录!',
placement: 'bottomRight',
});
}
waitingForDispatch().then(dispatch => {
if(dispatch)
dispatch({ type: 'login/passiveLogout' });
});
}
function notifyFatalError(status, reason) {
notification.warning({
message: `错误 ${status}`,
description: `错误原因: ${reason}\n系统内部故障, 请联系开发人员维护.`,
placement: 'bottomRight',
duration: 8,
});
}
export const WITH_CSRF_TOKEN = true;
export const NO_CSRF_TOKEN = false;
export const REQUEST_NORMAL = 0;
export const REQUEST_NO_INTERCEPT = 4;
export const REQUEST_KEEP_RAW = 8;
export async function requestTokenInfo() {
//eslint-disable-next-line
const log = isDevelopment() ? (msg => console.log(`${new Date().toJSON()} ${msg}`)) : (() => { });
const response = await request('GET /api/rest/user/me', {}, NO_CSRF_TOKEN);
if (!response || response.code !== 200) {
log(`请求user/me信息出错! response 异常 或 HTTP 响应码不为 200 (实际响应码: ${response && response.code}))`);
return Promise.resolve(false);
}
saveTokenInfo(response.data);
return Promise.resolve(true);
}
/**
* @param {string} actionUri for example: "GET /api/services/xxx"
* @param {{headers?: {[name: string]: string}; body?: any; asJSON?: boolean|"expand"}} [options]
* @param {boolean} [withCSRF] 默认携带Token Header信息
* @param {number} [requestType] 允许拦截特定的响应内容, 比如: 401 跳转到登入页面而不返回
* @returns {Promise<{code: number; data: any}>} 返回响应信息(该Promise一定不会reject), null 则表示该请求有错误, 而且非业务逻辑错误, 可能需要联系 管理员 或 重新登录
*/
export async function request(actionUri, options = {}, withCSRF = true, requestType = REQUEST_NORMAL) {
const promiseNull = Promise.resolve(null);
if (withCSRF) {
const csrf = _.get(getTokenInfo(), 'csrf', '');
if (!options) options = { headers: { [X_CSRF]: csrf } };
else if (!options.headers) options.headers = { [X_CSRF]: csrf };
else options.headers[X_CSRF] = csrf;
}
let status = 0;
return createHttpFetch(actionUri, options)
.then(response => {
status = response.status;
if (!(requestType & REQUEST_NO_INTERCEPT)) {
if (status === 401) {
//@ts-ignore
passiveLogout();
return promiseNull;
}
}
return new Promise(resolve => {
((requestType & REQUEST_KEEP_RAW) ? response.text() : response.json())
.then(object => resolve(object))
.catch(ex => {
//eslint-disable-next-line
console.error(ex);
notifyFatalError('JSON异常', `${actionUri} 返回的不是有效JSON数据.`);
resolve({});
})
});
})
.then(data => {
if (!data)
if (!(requestType & REQUEST_KEEP_RAW))
return promiseNull;
return { code: status, data };
});
}
function waitingForDispatch() {
return new Promise(resolve => {
let timeout = 50;
const asker = setInterval(() => {
//@ts-ignore
const app = window._i_am_an_app;
if (app && app._store && app._store.dispatch) {
clearInterval(asker);
return resolve(app._store.dispatch);
}
timeout--;
if (timeout < 0) {
devWarning('!!! 超时后仍未获得: window._i_am_an_app._store.dispatch 函数');
clearInterval(asker);
return resolve(null);
}
}, 50);
});
}
例如需要对下面这个接口发起请求:
根据接口文档提供的接口,可以在services层这样写,使用urlencoded和request工具函数拼接请求参数:
/**
*
* @param {any} filter
* @param {number} page
* @param {number} size
*/
export async function queryDeviceFaultRepair(filter = {}, page = 0, size = 30) {
const qs = urlencoded({ ...filter, page, size, trace: true });
return request(`GET /api/rest/device/fault/repair/?${qs}`);
}
在modal层调用这个函数:代码并不完成,只提供参考
export default {
namespace: 'deviceFaultRepair',
state: {
faultRepairList: {},
},
effects: {
*fetchDeviceFaultRepair({ payload }, { put }) {
//这个是调试函数
devMustIncludeFields(payload, 'filter', 'page', 'size', 'onError');
const response = yield services.queryDeviceFaultRepair(payload.filter, payload.page, payload.size);
/获取到response数据
if (!response) return;
if (response.code !== 200) return payload.onError(response);
//把数据推到reduces的gotfaultRepairList, payload是携带数据的一个对象
yield put({ type: 'gotfaultRepairList', payload: { data: response.data } });
},
}
reducers: {
gotfaultRepairList(state, { payload }) {
//获取到数据,并把数据存储到state的faultRepairList中
return { ...state, faultRepairList: payload.data };
},
}
链接modal层和page层:
在某个页面中写下面代码
deviceFaultRepair是model的命名空间的名字。
@connect(({ deviceFaultRepair, loading }) => ({
deviceFaultRepair,
loadingList: loading.effects['deviceFaultRepair/fetchDeviceFaultRepair'],
}))
组件的生命周期函数中调用model层effects 中的函数,来dispatch一个action
//表示组件在挂载得时候发起这个action
componentDidMount() {
this.query();
}
query(filter = {}, page = 0, size = defaultPageSize) {
向modal层发起一个Action,easyDispatch是封装好的一个dispatch函数
easyDispatch(this, 'deviceFaultRepair/fetchDeviceFaultRepair', {
filter, page, size, onError: this.onError.bind(this),
});
}
在浏览器的控制台network中可以看见已经获取到服务器的数据:
以及请求头的内容: 对了这里需要用到proxy代理,挂载自己服务器。
?以的就是urlencoded.js工具函数拼接而成
到此已经是向接口发起请求并获取到数据的全过程,在modal层获取到的数据并保存在deviceFaultRepair中,在modal和怕个connect以后可以通过:
const { deviceFaultRepair } = this.props;
//获取到deviceFaultRepair
然后可以在组件中使用deviceFaultRepair里面的数据了,比如做个列表,刚好deviceFaultRepair里面的json数据格式和Colum对应,就可以这样传入:
代码不全,仅供参考
//CRUDTableCard是自己封装的列表组件
最终在浏览器上的效果是: