fastlion-amis
Version:
一种MIS页面生成工具
153 lines (145 loc) • 6.11 kB
text/typescript
import { evaluate } from "amis-formula";
import { isString } from "lodash";
import isObjectByLodash from 'lodash/isObject';
import { collectVariables } from "./grammar";
import { getVariable } from "./helper";
import { filter } from "./tpl";
import { getFilters, resolveVariableAndFilter } from "./tpl-builtin";
/**
* formulaExec 运算器:根据当前字符串类型执行对应运算,也可按指定执行模式执行运算
*
* 运算模式(execMode)支持以下取值:
* 1. tpl: 按模板字符串执行(JavaScript 模板引擎),比如:Hello ${amisUser.email}、<h1>Hello</h1>, <span>${amisUser.email}</span>;
* 备注: 在模板中可以自由访问变量,详细请见:https://www.lodashjs.com/docs/lodash.template;
* 2. formula: 按新版公式表达式执行,用于执行 ${ xxx } 格式的表达式;
* 支持从window、localStorage、sessionStorage获取数据,比如:${num1 + 2}、${ls:env}、${window:document}、${window:document.URL}、${amisUser.email};
* 详细请见:https://aisuda.bce.baidu.com/amis/zh-CN/docs/concepts/data-mapping#namespace
* 3. evalFormula: 按新版公式表达式执行,用于执行 ${ xxx } 和 非${ xxx } 格式的表达式(evalMode 为 true,不用 ${} 包裹也可以执行),功能同 formula 运算模式;
* 4. js: 按Javascript执行,表达式中可以通过data.xxx来获取指定数据,并且支持简单运算;
* 比如:data.num1 + 2、this.num1 + 2、num1 + 2;(备注:三个表达式是等价的,这里的 this 就是 data。)
* 5. var: 以此字符串作为key值从当前数据域data中获取数值;性能最高(运行期间不会生成ast和表达式运算);
* 6. true 或者 false: 当execMode设置为true时,不用 ${} 包裹也可以执行表达式;
* 7. collect: 用于从表达式中获取所有变量;
*
* 备注1: 用户也可以使用 registerFormulaExec 注册一个自定义运算器;
* 备注2: 模板字符串 和 Javascript 模板引擎 不可以交叉使用;
* 备注3: amis 现有的 evalFormula 方法,可执行 ${} 格式类表达式,但不支持 filter 过滤器,所以这里用 resolveValueByName 实现;
* 备注4: 后续可考虑将 amis现有的运算器都放这里管理,充当统一的运算器入口。
*/
// 缓存,用于提升性能
const FORMULA_EVAL_CACHE: { [key: string]: Function } = {};
/**
* 用于存储当前可用运算器,默认支持 tpl、formula、js、var 四种类型运算器
* 备注:在这里统一参数。
*/
export const FormulaExec: {
[key: string]: Function;
} = {
tpl: (expression: string, data?: object) => {
const curData = data || {};
return filter(expression, curData);
},
formula: (expression: string, data?: object) => {
// 邮箱格式直接返回,后续需要在 amis-formula 中处理
if (
/^\$\{([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+((.[a-zA-Z0-9_-]{2,3}){1,2})\}$/.test(
expression
)
) {
return expression.substring(2, expression.length - 1); // 剔除前后特殊字符
}
const curData = data || {};
let result = undefined;
try {
// 执行 ${} 格式类表达式,且支持 filter 过滤器。(备注: isPureVariable 可用于判断是否有 过滤器。)
result = resolveVariableAndFilter(expression, curData, '| raw');
} catch (e) {
console.warn(
'[formula]表达式执行异常,当前表达式: ',
expression,
',当前上下文数据: ',
data
);
return expression;
}
// 备注: 此处不用 result ?? expression 是为了避免 没有对应结果时直接显示 expression: ${xxx}
return result;
},
evalFormula: (expression: string, data?: object) => {
const curData = data || {};
let result = undefined;
try {
result = evaluate(expression, curData, {
evalMode: true, // evalMode 为 true 时,不用 ${} 包裹也可以执行,
allowFilter: false
});
} catch (e) {
console.warn(
'[evalFormula]表达式执行异常,当前表达式: ',
expression,
',当前上下文数据: ',
data
);
return expression;
}
return result ?? expression;
},
js: (expression: string, data?: object) => {
let debug = false;
const idx = expression.indexOf('debugger');
if (~idx) {
debug = true;
expression = expression.replace(/debugger;?/, '');
}
let fn;
if (expression in FORMULA_EVAL_CACHE) {
fn = FORMULA_EVAL_CACHE[expression];
} else {
fn = new Function(
'data',
'utils',
`with(data) {${debug ? 'debugger;' : ''}return (${expression});}`
);
FORMULA_EVAL_CACHE[expression] = fn;
}
data = data || {};
let curResult = undefined;
try {
curResult = fn.call(data, data, getFilters());
} catch (e) {
console.warn(
'[formula:js]表达式执行异常,当前表达式: ',
expression,
',当前上下文数据: ',
data
);
return expression;
}
return curResult;
},
var: (expression: string, data?: object) => {
const curData = data || {};
const result = getVariable(curData, expression); // 不支持过滤器
return result ?? expression;
},
collect: (expression: any) => {
let variables: Array<string> = [];
if (isObjectByLodash(expression) || isString(expression)) {
// 仅对 Object类型 和 String类型 进行变量提取
variables = collectVariables(expression);
} else {
variables = [];
}
return variables;
}
};
// 用于判断是否优先使用value。
export function isExpression(expression: any): boolean {
if (!expression || !isString(expression)) {
// 非字符串类型,比如:Object、Array类型、boolean、number类型
return false;
}
// 备注1: "\\${xxx}"不作为表达式,至少含一个${xxx}才算是表达式
// 备注2: safari 不支持 /(?<!\\)(\${).+(\})/.test(expression)
return /(^|[^\\])\$\{.+\}/.test(expression);
}