fastlion-amis
Version:
一种MIS页面生成工具
206 lines (177 loc) • 5.81 kB
text/typescript
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
});
});