UNPKG

amis

Version:

一种MIS页面生成工具

264 lines (220 loc) 7.61 kB
import {Api, ApiObject, fetcherResult, Payload} from '../types'; import {fetcherConfig} from '../factory'; import {tokenize, dataMapping} from './tpl-builtin'; import qs from 'qs'; import {evalExpression} from './tpl'; import {isObject, isObjectShallowModified, hasFile, object2formData, qsstringify} from './helper'; const rSchema = /(?:^|raw\:)(get|post|put|delete|patch|options|head):/i; interface ApiCacheConfig extends ApiObject { cachedPromise: Promise<any>; requestTime: number; } const apiCaches: Array<ApiCacheConfig> = []; export function normalizeApi(api: Api): ApiObject { if (typeof api === 'string') { let method = rSchema.test(api) ? RegExp.$1 : ''; method && (api = api.replace(method + ':', '')); api = { method: method as any, url: api }; } else { api = { ...api }; } return api; } export function buildApi( api: Api, data?: object, options: { autoAppend?: boolean; ignoreData?: boolean; [propName: string]: any; } = {} ): ApiObject { api = normalizeApi(api); const {autoAppend, ignoreData, ...rest} = options; api.config = { ...rest }; api.method = api.method || (options as any).method || 'get'; if (!data) { return api; } else if (data instanceof FormData || data instanceof Blob || data instanceof ArrayBuffer) { api.data = data; return api; } const raw = (api.url = api.url || ''); api.url = tokenize(api.url, data, '| url_encode'); if (ignoreData) { return api; } if (api.data) { api.data = dataMapping(api.data, data); } else if (api.method === 'post' || api.method === 'put') { api.data = data; } // get 类请求,把 data 附带到 url 上。 if (api.method === 'get') { if (!~raw.indexOf('$') && !api.data && autoAppend) { api.data = data; } if (api.data) { const idx = api.url.indexOf('?'); if (~idx) { let params = { ...qs.parse(api.url.substring(idx + 1)), ...api.data }; api.url = api.url.substring(0, idx) + '?' + qsstringify(params); } else { api.url += '?' + qsstringify(api.data); } delete api.data; } } if (api.headers) { api.headers = dataMapping(api.headers, data); } if (api.requestAdaptor && typeof api.requestAdaptor === 'string') { api.requestAdaptor = str2function(api.requestAdaptor, 'api') as any; } if (api.adaptor && typeof api.adaptor === 'string') { api.adaptor = str2function(api.adaptor, 'payload', 'response', 'api') as any; } return api; } function str2function(contents: string, ...args: Array<string>): Function | null { try { let fn = new Function(...args, contents); return fn; } catch (e) { console.warn(e); return null; } } function responseAdaptor(ret: fetcherResult) { const data = ret.data; if (!data) { throw new Error('Response is empty!'); } else if (!data.hasOwnProperty('status')) { throw new Error('接口返回格式不符合,请参考 http://amis.baidu.com/v2/docs/api'); } const payload: Payload = { ok: data.status == 0, status: data.status, msg: data.msg, data: data.data }; if (payload.status == 422) { payload.errors = data.errors; } return payload; } export function wrapFetcher( fn: (config: fetcherConfig) => Promise<fetcherResult> ): (api: Api, data: object, options?: object) => Promise<Payload | void> { return function(api, data, options) { api = buildApi(api, data, options) as ApiObject; if (api.data && (hasFile(api.data) || api.dataType === 'form-data')) { api.data = object2formData(api.data, api.qsOptions); } else if (api.data && api.dataType === 'form') { api.data = qsstringify(api.data, api.qsOptions) as any; api.headers = api.headers || (api.headers = {}); api.headers['Content-Type'] = 'application/x-www-form-urlencoded'; } api.requestAdaptor && (api = api.requestAdaptor(api) || api); if (typeof api.cache === 'number' && api.cache > 0) { const apiCache = getApiCache(api); return wrapAdaptor(apiCache ? (apiCache as ApiCacheConfig).cachedPromise : setApiCache(api, fn(api)), api); } return wrapAdaptor(fn(api), api); }; } export function wrapAdaptor(promise: Promise<fetcherResult>, api: ApiObject) { const adaptor = api.adaptor; return adaptor ? promise .then(response => ({...response, data: adaptor((response as any).data, response, api)})) .then(responseAdaptor) : promise.then(responseAdaptor); } export function isApiOutdated( prevApi: Api | undefined, nextApi: Api | undefined, prevData: any, nextData: any ): boolean { const url: string = (nextApi && (nextApi as ApiObject).url) || (nextApi as string); if (url && typeof url === 'string' && ~url.indexOf('$')) { prevApi = buildApi(prevApi as Api, prevData as object, {ignoreData: true}); nextApi = buildApi(nextApi as Api, nextData as object, {ignoreData: true}); return !!( prevApi.url !== nextApi.url && isValidApi(nextApi.url) && (!nextApi.sendOn || evalExpression(nextApi.sendOn, nextData)) ); } return false; } export function isValidApi(api: string) { return api && /^(?:https?:\/\/[^\/]+)?(\/[^\s\/\?]*){1,}(\?.*)?$/.test(api); } export function isEffectiveApi(api?: Api, data?: any, initFetch?: boolean, initFetchOn?: string): api is Api { if (!api) { return false; } if (initFetch === false) { return false; } if (initFetchOn && data && !evalExpression(initFetchOn, data)) { return false; } if (typeof api === 'string' && api.length) { return true; } else if (isObject(api) && (api as ApiObject).url) { if ((api as ApiObject).sendOn && data && !evalExpression((api as ApiObject).sendOn as string, data)) { return false; } return true; } return false; } export function isSameApi(apiA: ApiObject | ApiCacheConfig, apiB: ApiObject | ApiCacheConfig): boolean { return ( apiA.method === apiB.method && apiA.url === apiB.url && !isObjectShallowModified(apiA.data, apiB.data, false) ); } export function getApiCache(api: ApiObject): ApiCacheConfig | undefined { // 清理过期cache const now = Date.now(); let result: ApiCacheConfig | undefined; for (let idx = 0, len = apiCaches.length; idx < len; idx++) { const apiCache = apiCaches[idx]; if (now - apiCache.requestTime > (apiCache.cache as number)) { apiCaches.splice(idx, 1); len--; idx--; continue; } if (isSameApi(api, apiCache)) { result = apiCache; break; } } return result; } export function setApiCache(api: ApiObject, promise: Promise<any>): Promise<any> { apiCaches.push({ ...api, cachedPromise: promise, requestTime: Date.now() }); return promise; } export function clearApiCache() { apiCaches.splice(0, apiCaches.length); } // window.apiCaches = apiCaches;