mirror of
https://github.com/WeBankFinTech/fes.js.git
synced 2025-04-06 03:59:53 +08:00
feat: 添加request plugin
This commit is contained in:
parent
28498a30bc
commit
67903dab8b
@ -3,7 +3,7 @@ import { join } from 'path';
|
||||
|
||||
// utils must build before core
|
||||
// runtime must build before renderer-react
|
||||
const headPkgs = ['fes-runtime', 'fes-core', 'fes', 'fes-plugin-built-in'];
|
||||
const headPkgs = ['fes-runtime', 'fes-core', 'fes', 'fes-plugin-built-in', 'fes-plugin-request'];
|
||||
const tailPkgs = [];
|
||||
// const otherPkgs = readdirSync(join(__dirname, 'packages')).filter(
|
||||
// (pkg) =>
|
||||
|
@ -4,6 +4,7 @@ import {
|
||||
join
|
||||
} from 'path';
|
||||
import { routesToJSON } from '@webank/fes-core';
|
||||
import { runtimePath } from '../constants';
|
||||
|
||||
export default function (api) {
|
||||
const {
|
||||
@ -16,9 +17,15 @@ export default function (api) {
|
||||
api.writeTmpFile({
|
||||
path: 'core/routes.js',
|
||||
content: Mustache.render(routesTpl, {
|
||||
runtimePath,
|
||||
routes: routesToJSON({ routes, config: api.config }),
|
||||
config: api.config
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
api.addFesExports(() => ({
|
||||
specifiers: ['router'],
|
||||
source: './routes.js'
|
||||
}));
|
||||
}
|
||||
|
@ -1,7 +1,22 @@
|
||||
|
||||
import { createRouter, createWebHashHistory } from '{{{ runtimePath }}}';
|
||||
|
||||
export function getRoutes() {
|
||||
const routes = {{{ routes }}};
|
||||
// TODO 支持动态变更路由
|
||||
return routes;
|
||||
}
|
||||
}
|
||||
|
||||
let router = null;
|
||||
export const createHistory = () => {
|
||||
if (router) {
|
||||
return router;
|
||||
}
|
||||
router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: getRoutes()
|
||||
});
|
||||
|
||||
return router;
|
||||
};
|
||||
|
||||
export { router };
|
||||
|
@ -6,8 +6,8 @@ import {
|
||||
} from 'vue';
|
||||
import { plugin } from './core/plugin';
|
||||
import './core/pluginRegister';
|
||||
import { ApplyPluginsType, createRouter, createWebHashHistory } from '{{{ runtimePath }}}';
|
||||
import { getRoutes } from './core/routes';
|
||||
import { ApplyPluginsType } from '{{{ runtimePath }}}';
|
||||
import { createRouter } from './core/routes';
|
||||
{{{ imports }}}
|
||||
|
||||
{{{ entryCodeAhead }}}
|
||||
@ -23,10 +23,7 @@ const renderClient = (opts = {}) => {
|
||||
}
|
||||
});
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: getRoutes()
|
||||
});
|
||||
const router = createRouter();
|
||||
const app = createApp(rootContainer);
|
||||
app.use(router);
|
||||
|
||||
|
6
packages/fes-plugin-request/.fatherrc.js
Normal file
6
packages/fes-plugin-request/.fatherrc.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default {
|
||||
target: 'browser',
|
||||
cjs: { type: 'rollup', lazy: false },
|
||||
esm: { type: 'rollup' },
|
||||
disableTypeCheck: false,
|
||||
};
|
@ -6,10 +6,14 @@
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"module": "dist/index.esm.js",
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@webank/fes": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.20.0",
|
||||
"throttle-debounce": "^2.3.0"
|
||||
|
@ -1,29 +1,64 @@
|
||||
import { debounce } from 'throttle-debounce';
|
||||
import initAxiosInstance from './request';
|
||||
import { readFileSync, copyFileSync, statSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
|
||||
let request;
|
||||
|
||||
function _advanceRequest({ url, debounce: waitTime, options = {} }) {
|
||||
return debounce((data, specialCaseOptions) => {
|
||||
request(url, data, Object.assign(options, specialCaseOptions));
|
||||
}, true, waitTime || 0);
|
||||
}
|
||||
|
||||
export const requestWrap = (interfaces) => {
|
||||
const obj = {};
|
||||
Object.entries(interfaces).forEach(([key, value]) => {
|
||||
if (value.url) {
|
||||
obj[key] = _advanceRequest(value);
|
||||
} else {
|
||||
obj[key] = requestWrap(value);
|
||||
export default (api) => {
|
||||
api.addRuntimePluginKey(() => 'request');
|
||||
// 配置
|
||||
api.describe({
|
||||
config: {
|
||||
schema(joi) {
|
||||
return joi.object({
|
||||
dataField: joi
|
||||
.string()
|
||||
.pattern(/^[a-zA-Z]*$/)
|
||||
.allow('')
|
||||
});
|
||||
},
|
||||
default: {
|
||||
dataField: 'result',
|
||||
messageUI: 'ant-design-vue'
|
||||
}
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
};
|
||||
|
||||
export const createRequest = () => ({
|
||||
install(app, options, ctx) {
|
||||
request = initAxiosInstance(options, { router: ctx.router });
|
||||
ctx.request = request;
|
||||
}
|
||||
});
|
||||
const namespace = 'plugin-request';
|
||||
const requestTemplate = readFileSync(join(__dirname, 'template', 'request.ts'), 'utf-8');
|
||||
api.onGenerateFiles(() => {
|
||||
// 文件写出
|
||||
const { dataField = '', messageUI } = api.config.request;
|
||||
api.writeTmpFile({
|
||||
path: `${namespace}/request.js`,
|
||||
content: requestTemplate
|
||||
.replace('REPLACE_MESSAGE_UI', messageUI || 'ant-design-vue')
|
||||
.replace('REPLACE_DATA_FIELD', dataField)
|
||||
});
|
||||
});
|
||||
|
||||
let generatedOnce = false;
|
||||
api.onGenerateFiles(() => {
|
||||
if (generatedOnce) return;
|
||||
generatedOnce = true;
|
||||
const cwd = join(__dirname, './template');
|
||||
const files = api.utils.glob.sync('**/*', {
|
||||
cwd
|
||||
});
|
||||
const base = join(api.paths.absTmpPath, namespace);
|
||||
files.forEach((file) => {
|
||||
if (['request.js'].includes(file)) return;
|
||||
const source = join(cwd, file);
|
||||
const target = join(base, file);
|
||||
if (statSync(source).isDirectory()) {
|
||||
api.utils.mkdirp.sync(target);
|
||||
} else {
|
||||
copyFileSync(source, target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
api.addFesExports(() => [
|
||||
{
|
||||
exportAll: true,
|
||||
source: `../${namespace}/request.js`
|
||||
}
|
||||
]);
|
||||
};
|
||||
|
@ -1,19 +0,0 @@
|
||||
import { checkHttpRequestHasBody, trimObj } from './helpers';
|
||||
|
||||
export default function reqInterceptors(instance) {
|
||||
// 将 http method 转换为大写
|
||||
instance.interceptors.request.use((config) => {
|
||||
config.method = config.method.toUpperCase();
|
||||
return config;
|
||||
});
|
||||
|
||||
// 清理请求值中的空格
|
||||
instance.interceptors.request.use((config) => {
|
||||
if (checkHttpRequestHasBody(config.method)) {
|
||||
config.data = trimObj(config.data);
|
||||
} else {
|
||||
config.params = trimObj(config.params);
|
||||
}
|
||||
return config;
|
||||
});
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import reqInterceptors from './reqInterceptors';
|
||||
import resInterceptors from './resInterceptors';
|
||||
import { checkHttpRequestHasBody, isObject } from './helpers';
|
||||
|
||||
// TODO
|
||||
// 响应体控制
|
||||
// formData 控制
|
||||
// 段时间内不能重复发送的请求
|
||||
// 错误控制
|
||||
// 跳错误页面 || 或者重新登录
|
||||
|
||||
let instance;
|
||||
|
||||
export function requestUse(before, error) {
|
||||
return this.instance.interceptors.request.use(before, error);
|
||||
}
|
||||
|
||||
export function requestEject(interceptor) {
|
||||
this.instance.interceptors.request.eject(interceptor);
|
||||
}
|
||||
|
||||
export function responseUse(after, error) {
|
||||
return instance.interceptors.response.use(after, error);
|
||||
}
|
||||
|
||||
export function responseEject(interceptor) {
|
||||
instance.interceptors.response.eject(interceptor);
|
||||
}
|
||||
|
||||
export function getRequestInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
function _failedHandler(error, customerErrorHandler) {
|
||||
if (error.response) {
|
||||
const status = error.response.status;
|
||||
if (typeof customerErrorHandler[status] === 'function') {
|
||||
customerErrorHandler(error);
|
||||
}
|
||||
} else if (error.request) {
|
||||
// TODO 请求异常
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
function _successedHandler(response, responseDataStruct) {
|
||||
const responseData = response.data;
|
||||
if (responseDataStruct && isObject(responseData)) {
|
||||
// TODO 响应体解构解析
|
||||
return responseData;
|
||||
}
|
||||
return responseData;
|
||||
}
|
||||
|
||||
function initAxiosInstance({ options: internalOptions, responseDataStruct, errorHandler }) {
|
||||
const customerErrorHandler = errorHandler || {};
|
||||
|
||||
instance = axios.create(Object.assign({
|
||||
timeout: 10000,
|
||||
withCredentials: true
|
||||
}, internalOptions));
|
||||
|
||||
// 设置请求拦截器
|
||||
reqInterceptors(instance);
|
||||
|
||||
// 设置响应拦截器
|
||||
resInterceptors(instance);
|
||||
|
||||
return (url, data, options = {}) => {
|
||||
options.url = url;
|
||||
options.method = options.method || 'post';
|
||||
if (checkHttpRequestHasBody(options.method)) {
|
||||
options.data = data;
|
||||
} else {
|
||||
options.params = data;
|
||||
}
|
||||
// 请求内容可能是一个json
|
||||
// 一个 query
|
||||
// formdata
|
||||
// 响应内容可能是一个文件流
|
||||
// 一个文本
|
||||
// 一个 json
|
||||
// eslint-disable-next-line
|
||||
return this.instance.request(options).then(response => _successedHandler(response, responseDataStruct)).catch(error => _failedHandler(error, customerErrorHandler));
|
||||
};
|
||||
}
|
||||
|
||||
export default initAxiosInstance;
|
@ -1,4 +0,0 @@
|
||||
|
||||
export default function resInterceptors() {
|
||||
|
||||
}
|
@ -76,13 +76,15 @@ export function checkHttpRequestHasBody(method) {
|
||||
}
|
||||
|
||||
export function trimObj(obj) {
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (isString(value)) {
|
||||
obj[key] = value.trim();
|
||||
} else if (isObject(value)) {
|
||||
trimObj(value);
|
||||
} else if (Array.isArray(value)) {
|
||||
trimObj(value);
|
||||
}
|
||||
});
|
||||
if (isObject(obj)) {
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
if (isString(value)) {
|
||||
obj[key] = value.trim();
|
||||
} else if (isObject(value)) {
|
||||
trimObj(value);
|
||||
} else if (Array.isArray(value)) {
|
||||
trimObj(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
174
packages/fes-plugin-request/src/template/request.js
Normal file
174
packages/fes-plugin-request/src/template/request.js
Normal file
@ -0,0 +1,174 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// import { debounce } from 'throttle-debounce';
|
||||
import { message } from 'REPLACE_MESSAGE_UI';
|
||||
import { ApplyPluginsType, plugin, router } from '@webank/fes';
|
||||
import {
|
||||
checkHttpRequestHasBody,
|
||||
trimObj,
|
||||
isString,
|
||||
isFunction,
|
||||
isObject
|
||||
} from './helpers';
|
||||
|
||||
/**
|
||||
* 统一错误处理
|
||||
* @param {object | string | function} errorStruct
|
||||
* {
|
||||
* errorMessage: '', // 错误地址
|
||||
* errorPage: '', // 错误页面地址
|
||||
* showType: 1 // 0 不提示 | 1 警告 | 2 错误 | 9 页面跳转
|
||||
* }
|
||||
*/
|
||||
function errrorHandler(error) {
|
||||
if (isFunction(error)) {
|
||||
error();
|
||||
} else if (isString(error)) {
|
||||
message.error(error);
|
||||
} else if (isObject(error)) {
|
||||
switch (error.showType) {
|
||||
case 1:
|
||||
message.warning(error.errorMessage);
|
||||
break;
|
||||
case 2:
|
||||
message.error(error.errorMessage);
|
||||
break;
|
||||
case 9:
|
||||
router.replace(error.errorPage);
|
||||
break;
|
||||
default:
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addInterceptors(instance, interceptors, type = 'request') {
|
||||
interceptors.forEach((fn) => {
|
||||
if (Array.isArray(fn)) {
|
||||
instance.interceptors[type].use(...fn);
|
||||
} else if (isFunction(fn)) {
|
||||
instance.interceptors[type].use(fn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addRequestInterceptors(instance, interceptors) {
|
||||
addInterceptors(instance, interceptors, 'request');
|
||||
}
|
||||
|
||||
function addResponseInterceptors(instance, interceptors) {
|
||||
addInterceptors(instance, interceptors, 'response');
|
||||
}
|
||||
|
||||
function getRequestInstance() {
|
||||
const {
|
||||
responseDataAdaptor,
|
||||
errorConfig,
|
||||
requestInterceptors,
|
||||
responseInterceptors,
|
||||
...otherConfigs
|
||||
} = plugin.applyPlugins({
|
||||
key: 'request',
|
||||
type: ApplyPluginsType.modify,
|
||||
initialValue: {}
|
||||
});
|
||||
|
||||
const _errorConfig = Object.assign({
|
||||
401: {
|
||||
showType: 9,
|
||||
errorPage: '/login'
|
||||
},
|
||||
403: '用户得到授权,但访问是禁止的'
|
||||
}, errorConfig);
|
||||
|
||||
const _requestInterceptors = [].concat([
|
||||
(config) => {
|
||||
config.method = config.method.toUpperCase();
|
||||
return config;
|
||||
},
|
||||
(config) => {
|
||||
if (checkHttpRequestHasBody(config.method)) {
|
||||
config.data = trimObj(config.data);
|
||||
} else {
|
||||
config.params = trimObj(config.params);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
], requestInterceptors);
|
||||
|
||||
const _responseInterceptors = [].concat([
|
||||
[
|
||||
function (response) {
|
||||
if (isObject(response.data) && response.data.code !== '0') {
|
||||
errrorHandler(_errorConfig[response.data.code] || response.data.msg || response.data.errorMessage || response.data.errorMsg || '服务异常');
|
||||
return Promise.reject(response);
|
||||
}
|
||||
|
||||
return response;
|
||||
}, function (error) {
|
||||
if (error.response) {
|
||||
// The request was made and the server responded with a status code
|
||||
// that falls out of the range of 2xx
|
||||
if (_errorConfig[error.response.status]) {
|
||||
errrorHandler(_errorConfig[error.response.status]);
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
]
|
||||
], responseInterceptors);
|
||||
if (responseDataAdaptor && isFunction(responseDataAdaptor)) {
|
||||
_responseInterceptors.unshift((response) => {
|
||||
// 响应内容可能是个文件流 or 普通文本
|
||||
if (isObject(response.data)) {
|
||||
response.data = responseDataAdaptor(response.data);
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}
|
||||
// 只把响应数据暴露出去
|
||||
_responseInterceptors.push((response) => {
|
||||
// eslint-disable-next-line
|
||||
const dataField = REPLACE_DATA_FIELD;
|
||||
if (isObject(response.data) && dataField) {
|
||||
return response.data[dataField];
|
||||
}
|
||||
return response;
|
||||
});
|
||||
|
||||
const instance = axios.create(Object.assign({
|
||||
timeout: 10000,
|
||||
withCredentials: true
|
||||
}, otherConfigs));
|
||||
|
||||
addRequestInterceptors(instance, _requestInterceptors);
|
||||
addResponseInterceptors(instance, _responseInterceptors);
|
||||
|
||||
return {
|
||||
instance
|
||||
};
|
||||
}
|
||||
|
||||
// TODO 待实现能力
|
||||
// formData 控制
|
||||
// 轮询
|
||||
// 并行请求 >> 通过定义 key 区分
|
||||
// 防抖 & 节流
|
||||
// 缓存 & SWR & 预加载
|
||||
// loadingDelay
|
||||
|
||||
let currentRequestInstance = null;
|
||||
export const request = (url, data, options = {}) => {
|
||||
if (!currentRequestInstance) {
|
||||
const { instance } = getRequestInstance();
|
||||
currentRequestInstance = instance;
|
||||
}
|
||||
options.url = url;
|
||||
options.method = options.method || 'post';
|
||||
if (checkHttpRequestHasBody(options.method)) {
|
||||
options.data = data;
|
||||
} else {
|
||||
options.params = data;
|
||||
}
|
||||
return currentRequestInstance.request(options);
|
||||
};
|
@ -1,4 +1,4 @@
|
||||
import { requestWrap } from '@webank/fes-plugin-request';
|
||||
import { requestWrap } from '@webank/fes';
|
||||
|
||||
// TODO
|
||||
// 响应体控制
|
||||
@ -7,10 +7,6 @@ import { requestWrap } from '@webank/fes-plugin-request';
|
||||
// 跳错误页面 || 或者重新登录
|
||||
// 段时间内不能重复发送的请求
|
||||
|
||||
// request(url, data, option).then(() => {
|
||||
|
||||
// });
|
||||
|
||||
// or
|
||||
export default requestWrap({
|
||||
login: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user