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
|
// utils must build before core
|
||||||
// runtime must build before renderer-react
|
// 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 tailPkgs = [];
|
||||||
// const otherPkgs = readdirSync(join(__dirname, 'packages')).filter(
|
// const otherPkgs = readdirSync(join(__dirname, 'packages')).filter(
|
||||||
// (pkg) =>
|
// (pkg) =>
|
||||||
|
@ -4,6 +4,7 @@ import {
|
|||||||
join
|
join
|
||||||
} from 'path';
|
} from 'path';
|
||||||
import { routesToJSON } from '@webank/fes-core';
|
import { routesToJSON } from '@webank/fes-core';
|
||||||
|
import { runtimePath } from '../constants';
|
||||||
|
|
||||||
export default function (api) {
|
export default function (api) {
|
||||||
const {
|
const {
|
||||||
@ -16,9 +17,15 @@ export default function (api) {
|
|||||||
api.writeTmpFile({
|
api.writeTmpFile({
|
||||||
path: 'core/routes.js',
|
path: 'core/routes.js',
|
||||||
content: Mustache.render(routesTpl, {
|
content: Mustache.render(routesTpl, {
|
||||||
|
runtimePath,
|
||||||
routes: routesToJSON({ routes, config: api.config }),
|
routes: routesToJSON({ routes, config: api.config }),
|
||||||
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() {
|
export function getRoutes() {
|
||||||
const routes = {{{ routes }}};
|
const routes = {{{ routes }}};
|
||||||
// TODO 支持动态变更路由
|
// TODO 支持动态变更路由
|
||||||
return routes;
|
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';
|
} from 'vue';
|
||||||
import { plugin } from './core/plugin';
|
import { plugin } from './core/plugin';
|
||||||
import './core/pluginRegister';
|
import './core/pluginRegister';
|
||||||
import { ApplyPluginsType, createRouter, createWebHashHistory } from '{{{ runtimePath }}}';
|
import { ApplyPluginsType } from '{{{ runtimePath }}}';
|
||||||
import { getRoutes } from './core/routes';
|
import { createRouter } from './core/routes';
|
||||||
{{{ imports }}}
|
{{{ imports }}}
|
||||||
|
|
||||||
{{{ entryCodeAhead }}}
|
{{{ entryCodeAhead }}}
|
||||||
@ -23,10 +23,7 @@ const renderClient = (opts = {}) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter();
|
||||||
history: createWebHashHistory(),
|
|
||||||
routes: getRoutes()
|
|
||||||
});
|
|
||||||
const app = createApp(rootContainer);
|
const app = createApp(rootContainer);
|
||||||
app.use(router);
|
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": {
|
"scripts": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
|
"module": "dist/index.esm.js",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@webank/fes": "^2.0.0"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.20.0",
|
"axios": "^0.20.0",
|
||||||
"throttle-debounce": "^2.3.0"
|
"throttle-debounce": "^2.3.0"
|
||||||
|
@ -1,29 +1,64 @@
|
|||||||
import { debounce } from 'throttle-debounce';
|
import { readFileSync, copyFileSync, statSync } from 'fs';
|
||||||
import initAxiosInstance from './request';
|
import { join } from 'path';
|
||||||
|
|
||||||
let request;
|
export default (api) => {
|
||||||
|
api.addRuntimePluginKey(() => 'request');
|
||||||
function _advanceRequest({ url, debounce: waitTime, options = {} }) {
|
// 配置
|
||||||
return debounce((data, specialCaseOptions) => {
|
api.describe({
|
||||||
request(url, data, Object.assign(options, specialCaseOptions));
|
config: {
|
||||||
}, true, waitTime || 0);
|
schema(joi) {
|
||||||
}
|
return joi.object({
|
||||||
|
dataField: joi
|
||||||
export const requestWrap = (interfaces) => {
|
.string()
|
||||||
const obj = {};
|
.pattern(/^[a-zA-Z]*$/)
|
||||||
Object.entries(interfaces).forEach(([key, value]) => {
|
.allow('')
|
||||||
if (value.url) {
|
});
|
||||||
obj[key] = _advanceRequest(value);
|
},
|
||||||
} else {
|
default: {
|
||||||
obj[key] = requestWrap(value);
|
dataField: 'result',
|
||||||
|
messageUI: 'ant-design-vue'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createRequest = () => ({
|
const namespace = 'plugin-request';
|
||||||
install(app, options, ctx) {
|
const requestTemplate = readFileSync(join(__dirname, 'template', 'request.ts'), 'utf-8');
|
||||||
request = initAxiosInstance(options, { router: ctx.router });
|
api.onGenerateFiles(() => {
|
||||||
ctx.request = request;
|
// 文件写出
|
||||||
}
|
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) {
|
export function trimObj(obj) {
|
||||||
Object.entries(obj).forEach(([key, value]) => {
|
if (isObject(obj)) {
|
||||||
if (isString(value)) {
|
Object.entries(obj).forEach(([key, value]) => {
|
||||||
obj[key] = value.trim();
|
if (isString(value)) {
|
||||||
} else if (isObject(value)) {
|
obj[key] = value.trim();
|
||||||
trimObj(value);
|
} else if (isObject(value)) {
|
||||||
} else if (Array.isArray(value)) {
|
trimObj(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
|
// TODO
|
||||||
// 响应体控制
|
// 响应体控制
|
||||||
@ -7,10 +7,6 @@ import { requestWrap } from '@webank/fes-plugin-request';
|
|||||||
// 跳错误页面 || 或者重新登录
|
// 跳错误页面 || 或者重新登录
|
||||||
// 段时间内不能重复发送的请求
|
// 段时间内不能重复发送的请求
|
||||||
|
|
||||||
// request(url, data, option).then(() => {
|
|
||||||
|
|
||||||
// });
|
|
||||||
|
|
||||||
// or
|
// or
|
||||||
export default requestWrap({
|
export default requestWrap({
|
||||||
login: {
|
login: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user