mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-16 23:02:50 +00:00
310 lines
8.6 KiB
TypeScript
310 lines
8.6 KiB
TypeScript
/* eslint-disable no-console */
|
||
/* eslint-disable max-len */
|
||
/* eslint-disable object-curly-newline */
|
||
import { isJSFunction } from '@alilc/lowcode-types';
|
||
import { transformArrayToMap, transformStringToFunction } from './common';
|
||
import { jsonp, request, get, post } from './request';
|
||
import logger from './logger';
|
||
import { DataSource, DataSourceItem, IRendererAppHelper } from '../types';
|
||
|
||
const DS_STATUS = {
|
||
INIT: 'init',
|
||
LOADING: 'loading',
|
||
LOADED: 'loaded',
|
||
ERROR: 'error',
|
||
};
|
||
|
||
type DataSourceType = 'fetch' | 'jsonp';
|
||
|
||
/**
|
||
* do request for standard DataSourceType
|
||
* @param {DataSourceType} type type of DataSourceItem
|
||
* @param {any} options
|
||
*/
|
||
export function doRequest(type: DataSourceType, options: any) {
|
||
// eslint-disable-next-line prefer-const
|
||
let { uri, url, method = 'GET', headers, params, ...otherProps } = options;
|
||
otherProps = otherProps || {};
|
||
if (type === 'jsonp') {
|
||
return jsonp(uri, params, otherProps);
|
||
}
|
||
|
||
if (type === 'fetch') {
|
||
switch (method.toUpperCase()) {
|
||
case 'GET':
|
||
return get(uri, params, headers, otherProps);
|
||
case 'POST':
|
||
return post(uri, params, headers, otherProps);
|
||
default:
|
||
return request(uri, method, params, headers, otherProps);
|
||
}
|
||
}
|
||
|
||
logger.log(`Engine default dataSource does not support type:[${type}] dataSource request!`, options);
|
||
}
|
||
|
||
// TODO: according to protocol, we should implement errorHandler/shouldFetch/willFetch/requestHandler and isSync controll.
|
||
export class DataHelper {
|
||
/**
|
||
* host object that will be "this" object when excuting dataHandler
|
||
*
|
||
* @type {*}
|
||
* @memberof DataHelper
|
||
*/
|
||
host: any;
|
||
|
||
/**
|
||
* data source config
|
||
*
|
||
* @type {DataSource}
|
||
* @memberof DataHelper
|
||
*/
|
||
config: DataSource;
|
||
|
||
/**
|
||
* a parser function which will be called to process config data
|
||
* which eventually will call common/utils.processData() to process data
|
||
* (originalConfig) => parsedConfig
|
||
* @type {*}
|
||
* @memberof DataHelper
|
||
*/
|
||
parser: any;
|
||
|
||
/**
|
||
* config.list
|
||
*
|
||
* @type {any[]}
|
||
* @memberof DataHelper
|
||
*/
|
||
ajaxList: any[];
|
||
|
||
ajaxMap: any;
|
||
|
||
dataSourceMap: any;
|
||
|
||
appHelper: IRendererAppHelper;
|
||
|
||
constructor(comp: any, config: DataSource, appHelper: IRendererAppHelper, parser: any) {
|
||
this.host = comp;
|
||
this.config = config || {};
|
||
this.parser = parser;
|
||
this.ajaxList = config?.list || [];
|
||
this.ajaxMap = transformArrayToMap(this.ajaxList, 'id');
|
||
this.dataSourceMap = this.generateDataSourceMap();
|
||
this.appHelper = appHelper;
|
||
}
|
||
|
||
// 更新config,只会更新配置,状态保存;
|
||
updateConfig(config = {}) {
|
||
this.config = config as DataSource;
|
||
this.ajaxList = (config as DataSource)?.list || [];
|
||
const ajaxMap: any = transformArrayToMap(this.ajaxList, 'id');
|
||
// 删除已经移除的接口
|
||
Object.keys(this.ajaxMap).forEach((key) => {
|
||
if (!ajaxMap[key]) {
|
||
delete this.dataSourceMap[key];
|
||
}
|
||
});
|
||
this.ajaxMap = ajaxMap;
|
||
// 添加未加入到dataSourceMap中的接口
|
||
this.ajaxList.forEach((item) => {
|
||
if (!this.dataSourceMap[item.id]) {
|
||
this.dataSourceMap[item.id] = {
|
||
status: DS_STATUS.INIT,
|
||
load: (...args: any) => {
|
||
// @ts-ignore
|
||
return this.getDataSource(item.id, ...args);
|
||
},
|
||
};
|
||
}
|
||
});
|
||
return this.dataSourceMap;
|
||
}
|
||
|
||
generateDataSourceMap() {
|
||
const res: any = {};
|
||
this.ajaxList.forEach((item) => {
|
||
res[item.id] = {
|
||
status: DS_STATUS.INIT,
|
||
load: (...args: any) => {
|
||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||
// @ts-ignore
|
||
return this.getDataSource(item.id, ...args);
|
||
},
|
||
};
|
||
});
|
||
return res;
|
||
}
|
||
|
||
updateDataSourceMap(id: string, data: any, error: any) {
|
||
this.dataSourceMap[id].error = error || undefined;
|
||
this.dataSourceMap[id].data = data;
|
||
this.dataSourceMap[id].status = error ? DS_STATUS.ERROR : DS_STATUS.LOADED;
|
||
}
|
||
|
||
/**
|
||
* get all dataSourceItems which marked as isInit === true
|
||
* @private
|
||
* @returns
|
||
* @memberof DataHelper
|
||
*/
|
||
getInitDataSourseConfigs() {
|
||
const initConfigs = this.parser(this.ajaxList).filter((item: DataSourceItem) => {
|
||
// according to [spec](https://lowcode-engine.cn/lowcode), isInit should be boolean true to be working
|
||
if (item.isInit === true) {
|
||
this.dataSourceMap[item.id].status = DS_STATUS.LOADING;
|
||
return true;
|
||
}
|
||
return false;
|
||
});
|
||
return initConfigs;
|
||
}
|
||
|
||
/**
|
||
* process all dataSourceItems which marked as isInit === true, and get dataSource request results.
|
||
* @public
|
||
* @returns
|
||
* @memberof DataHelper
|
||
*/
|
||
getInitData() {
|
||
const initSyncData = this.getInitDataSourseConfigs();
|
||
// 所有 datasource 的 datahandler
|
||
return this.asyncDataHandler(initSyncData).then((res) => {
|
||
const { dataHandler } = this.config;
|
||
return this.handleData(null, dataHandler, res, null);
|
||
});
|
||
}
|
||
|
||
getDataSource(id: string, params: any, otherOptions: any, callback: any) {
|
||
const req = this.parser(this.ajaxMap[id]);
|
||
const options = req.options || {};
|
||
let callbackFn = callback;
|
||
let otherOptionsObj = otherOptions;
|
||
if (typeof otherOptions === 'function') {
|
||
callbackFn = otherOptions;
|
||
otherOptionsObj = {};
|
||
}
|
||
const { headers, ...otherProps } = otherOptionsObj || {};
|
||
if (!req) {
|
||
console.warn(`getDataSource API named ${id} not exist`);
|
||
return;
|
||
}
|
||
|
||
return this.asyncDataHandler([
|
||
{
|
||
...req,
|
||
options: {
|
||
...options,
|
||
// 支持参数为array的情况,当参数为array时,不做参数合并
|
||
params:
|
||
Array.isArray(options.params) || Array.isArray(params)
|
||
? params || options.params
|
||
: {
|
||
...options.params,
|
||
...params,
|
||
},
|
||
headers: {
|
||
...options.headers,
|
||
...headers,
|
||
},
|
||
...otherProps,
|
||
},
|
||
},
|
||
])
|
||
.then((res: any) => {
|
||
try {
|
||
callbackFn && callbackFn(res && res[id]);
|
||
} catch (e) {
|
||
console.error('load请求回调函数报错', e);
|
||
}
|
||
return res && res[id];
|
||
})
|
||
.catch((err) => {
|
||
try {
|
||
callbackFn && callbackFn(null, err);
|
||
} catch (e) {
|
||
console.error('load请求回调函数报错', e);
|
||
}
|
||
return err;
|
||
});
|
||
}
|
||
|
||
asyncDataHandler(asyncDataList: any[]) {
|
||
return new Promise((resolve, reject) => {
|
||
const allReq: any[] = [];
|
||
asyncDataList.forEach((req) => {
|
||
const { id, type } = req;
|
||
// TODO: need refactoring to remove 'legao' related logic
|
||
if (!id || !type || type === 'legao') {
|
||
return;
|
||
}
|
||
allReq.push(req);
|
||
});
|
||
|
||
if (allReq.length === 0) {
|
||
resolve({});
|
||
}
|
||
const res: any = {};
|
||
Promise.all(
|
||
allReq.map((item: any) => {
|
||
return new Promise((innerResolve) => {
|
||
const { type, id, dataHandler, options } = item;
|
||
|
||
const fetchHandler = (data: any, error: any) => {
|
||
res[id] = this.handleData(id, dataHandler, data, error);
|
||
this.updateDataSourceMap(id, res[id], error);
|
||
innerResolve({});
|
||
};
|
||
|
||
const doFetch = (innerType: string, innerOptions: any) => {
|
||
doRequest(innerType as any, innerOptions)
|
||
?.then((data: any) => {
|
||
fetchHandler(data, undefined);
|
||
})
|
||
.catch((err: Error) => {
|
||
fetchHandler(undefined, err);
|
||
});
|
||
};
|
||
|
||
this.dataSourceMap[id].status = DS_STATUS.LOADING;
|
||
doFetch(type, options);
|
||
});
|
||
}),
|
||
).then(() => {
|
||
resolve(res);
|
||
}).catch((e) => {
|
||
reject(e);
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* process data using dataHandler
|
||
*
|
||
* @param {(string | null)} id request id, will be used in error message, can be null
|
||
* @param {*} dataHandler
|
||
* @param {*} data
|
||
* @param {*} error
|
||
* @returns
|
||
* @memberof DataHelper
|
||
*/
|
||
handleData(id: string | null, dataHandler: any, data: any, error: any) {
|
||
let dataHandlerFun = dataHandler;
|
||
if (isJSFunction(dataHandler)) {
|
||
dataHandlerFun = transformStringToFunction(dataHandler.value);
|
||
}
|
||
if (!dataHandlerFun || typeof dataHandlerFun !== 'function') {
|
||
return data;
|
||
}
|
||
try {
|
||
return dataHandlerFun.call(this.host, data, error);
|
||
} catch (e) {
|
||
if (id) {
|
||
console.error(`[${id}]单个请求数据处理函数运行出错`, e);
|
||
} else {
|
||
console.error('请求数据处理函数运行出错', e);
|
||
}
|
||
}
|
||
}
|
||
}
|