UNPKG

fastlion-amis

Version:

一种MIS页面生成工具

206 lines (177 loc) 5.81 kB
import { isEqual } from 'lodash'; import { number } from 'yargs'; import { createObject } from './helper'; import { register as registerBulitin, getFilters } from './tpl-builtin'; import { register as registerLodash } from './tpl-lodash'; import { parse, evaluate } from 'amis-formula'; import { BigNumber } from 'bignumber.js'; import { add } from 'lodash'; export interface Enginer { test: (tpl: string) => boolean; removeEscapeToken?: (tpl: string) => string; compile: (tpl: string, data: object, ...rest: Array<any>) => string; } const enginers: { [propName: string]: Enginer; } = {}; export function registerTplEnginer(name: string, enginer: Enginer) { enginers[name] = enginer; } export const tplReg = /\$\{.+?\}|<%.+?%>/ // 模板字符的匹配表达式 匹配 "ddddd ${aa} ddd"的字符 const tplItemsReg = /\$\{items\|.+?\}/ // 模板字符的匹配表达式 匹配 "ddddd ${items|aa} ddd"的字符 // 暂存的数据缓存 缓存计算过的数据结果 防止重复计算 let curDataCahe = {} let curDataCaheTimer: any // 定时器标识 export function filter( tpl?: any, data: any = {}, ...rest: Array<any> ): string { // 记录代码块开始时间 if (!tpl || typeof tpl !== 'string') { return ''; } // 如果不是模板字符无需进行模板替换 if (!tplReg.test(tpl)) return tpl const keys = Object.keys(enginers); const sum = {} let sumData // 需要用到items的并且有才需要跌迭代压缩一下items 否则没必要 if (tplItemsReg.test(tpl) && data.items) { if (data.dataSetId && curDataCahe[data.dataSetId]) { sumData = { items: [curDataCahe[data.dataSetId]] } } else { for (const item of data?.items) { for (const key in item) { if (typeof item[key] === 'object') continue if (typeof item[key] === 'number' || !Number.isNaN(+item[key])) { sum[key] = +sum[key] || 0 sum[key] = new BigNumber(sum[key]).plus(+item[key] || 0) } else if (typeof item[key] === 'string') { sum[key] += item[key] } } } curDataCahe[data.dataSetId] = sum sumData = { items: [sum] } // 同一时间段重复的数据只计算一次 if (curDataCaheTimer) clearTimeout(curDataCaheTimer) curDataCaheTimer = setTimeout(() => curDataCahe = {}, 500) } } else { sumData = data } for (let i = 0, len = keys.length; i < len; i++) { let enginer = enginers[keys[i]]; if (enginer.test(tpl)) { return enginer.compile(tpl, sumData, ...rest); } else if (enginer.removeEscapeToken) { tpl = enginer.removeEscapeToken(tpl); } } return tpl; } // 缓存一下提升性能 const EVAL_CACHE: { [key: string]: Function } = {}; let customEvalExpressionFn: (expression: string, data?: any) => boolean; export function setCustomEvalExpression( fn: (expression: string, data?: any) => boolean ) { customEvalExpressionFn = fn; } // 几乎所有的 visibleOn requiredOn 都是通过这个方法判断出来结果,很粗暴也存在风险,建议自己实现。 // 如果想自己实现,请通过 setCustomEvalExpression 来替换。 export function evalExpression(expression: string, data?: object): boolean { if (typeof customEvalExpressionFn === 'function') { return customEvalExpressionFn(expression, data); } if (!expression || typeof expression !== 'string') { return false; } /* jshint evil:true */ try { if ( typeof expression === 'string' && expression.substring(0, 2) === '${' && expression[expression.length - 1] === '}' ) { // 启用新版本的公式表达式 return evalFormula(expression, data); } let debug = false; const idx = expression.indexOf('debugger'); if (~idx) { debug = true; expression = expression.replace(/debugger;?/, ''); } let fn; if (expression in EVAL_CACHE) { fn = EVAL_CACHE[expression]; } else { fn = new Function( 'data', 'utils', `with(data) {${debug ? 'debugger;' : ''}return !!(${expression});}` ); EVAL_CACHE[expression] = fn; } data = data || {}; return fn.call(data, data, getFilters()); } catch (e) { console.warn(expression, e); return false; } } const AST_CACHE: { [key: string]: any } = {}; function evalFormula(expression: string, data: any) { const ast = AST_CACHE[expression] || parse(expression, { evalMode: false }); AST_CACHE[expression] = ast; return evaluate(ast, data, { defaultFilter: 'raw' }); } let customEvalJsFn: (expression: string, data?: any) => any; export function setCustomEvalJs(fn: (expression: string, data?: any) => any) { customEvalJsFn = fn; } // 这个主要用在 formula 里面,用来动态的改变某个值。也很粗暴,建议自己实现。 // 如果想自己实现,请通过 setCustomEvalJs 来替换。 export function evalJS(js: string, data: object): any { if (typeof customEvalJsFn === 'function') { return customEvalJsFn(js, data); } /* jshint evil:true */ try { if ( typeof js === 'string' && js.substring(0, 2) === '${' && js[js.length - 1] === '}' ) { // 启用新版本的公式表达式 return evalFormula(js, data); } const fn = new Function( 'data', 'utils', `with(data) {${/^\s*return\b/.test(js) ? '' : 'return '}${js};}` ); data = data || {}; return fn.call(data, data, getFilters()); } catch (e) { console.warn(js, e); return null; } } [registerBulitin, registerLodash].forEach(fn => { const info = fn(); registerTplEnginer(info.name, { test: info.test, compile: info.compile, removeEscapeToken: info.removeEscapeToken }); });