|
|
@@ -1,515 +0,0 @@
|
|
|
-import { DataObjectUtils } from "@imengyu/js-request-transform";
|
|
|
-import { doSerializeInternalVar } from "./CommonCategoryDynamicData";
|
|
|
-import { DateUtils } from "@imengyu/imengyu-utils";
|
|
|
-import { back, navTo, redirectTo } from "@/components/utils/PageAction";
|
|
|
-import { navigateToAutoContent } from "../common/CommonContent";
|
|
|
-
|
|
|
-export interface IDynamicCompareExpressionContext {
|
|
|
- sourceData: {
|
|
|
- main: Record<string, any>,
|
|
|
- customData: Record<string, Record<string, any>>,
|
|
|
- },
|
|
|
-}
|
|
|
-
|
|
|
-function parseMapExpression(mapExpression: string) {
|
|
|
- const arr = mapExpression.split('#');
|
|
|
- const map = {} as Record<string, any>;
|
|
|
- for (let i = 0; i < arr.length; i += 2) {
|
|
|
- map[arr[i]] = arr[i + 1];
|
|
|
- }
|
|
|
- return map;
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 评估动态取值表达式
|
|
|
- *
|
|
|
- * 取值格式: 下方表达式中以 [R/RW/W] 代表槽位:读取、读写、写入
|
|
|
- * [K#key] : 从sourceData中取值,键值为key
|
|
|
- * [M#MAP] : 处理映射,格式为:key#value#key#value#...
|
|
|
- * [R] : 从上一步表达式中结果
|
|
|
- * [value] : 静态数据,可选 type#value 格式,type为类型,value为值,默认 string
|
|
|
- *
|
|
|
- * 格式:
|
|
|
- * (最后一位表示是否存储结果至上一步表达式结果)
|
|
|
- * A,[R] [Y]: 从sourceData中取值,键值为key
|
|
|
- * M,[R],[M#MAP] [Y]: 从customData中取值,并使用MAP中的数据映射,MAP为自定义数据映射
|
|
|
- * R [Y]: 从上一步表达式中结果
|
|
|
- * S,[W],[R] [N]: 执行设置操作,R为从上一步表达式中结果,value为设置值
|
|
|
- * ST,[key],[R] [N]: 暂存变量:设置
|
|
|
- * RT,[key],[W] [Y]: 暂存变量:取值
|
|
|
- * CT,[key] [N]: 暂存变量:删除
|
|
|
- * OP,[R],[OP],[R] [Y]: 执行操作,A为操作数,OP为操作符,B为操作数
|
|
|
- *
|
|
|
- * 多个表达式用换行符或者‼分隔,会按顺序依次评估,并返回最后一个表达式的结果
|
|
|
- * @param expression
|
|
|
- * @returns
|
|
|
- */
|
|
|
-export function doEvaluateDynamicDataExpression(expressions: string, context: IDynamicCompareExpressionContext) {
|
|
|
- if (!expressions)
|
|
|
- return undefined;
|
|
|
- const expressionArr = expressions.replace(/\n/g, '‼').split('‼');
|
|
|
- const tempData = new Map<string, any>();
|
|
|
-
|
|
|
- function evaluateExpression(expression: string, prevResult: any) : any {
|
|
|
- const arr = expression.split(',');
|
|
|
- const type = arr[0];
|
|
|
- const key = arr[1] || '';
|
|
|
-
|
|
|
- function accessDynamicKeyOrPrevResult(key: string, write: boolean, setValue?: any) {
|
|
|
- //基础取值
|
|
|
- if (key ==='R') {
|
|
|
- if (write) {
|
|
|
- prevResult = setValue;
|
|
|
- return undefined;
|
|
|
- }
|
|
|
- return prevResult;
|
|
|
- }
|
|
|
- else if (key.startsWith('M#')) {
|
|
|
- if (write) {
|
|
|
- console.error('doEvaluateDynamicDataExpression: M#MAP is not supported in write mode');
|
|
|
- return undefined;
|
|
|
- }
|
|
|
- return parseMapExpression(key.substring(2));
|
|
|
- }
|
|
|
- else if (key.startsWith('K#')) {
|
|
|
- key = key.substring(2);
|
|
|
- const data = context.sourceData as Record<string, any>;
|
|
|
-
|
|
|
- //对象访问
|
|
|
- if (write) {
|
|
|
- return DataObjectUtils.accessObjectByString(data, key, true);
|
|
|
- } else {
|
|
|
- const preSerialize = doSerializeInternalVar(key);
|
|
|
- if (preSerialize.prased)
|
|
|
- return preSerialize.value;
|
|
|
- else if (typeof data !== 'object') {
|
|
|
- if (key)
|
|
|
- throw new Error('doEvaluateDynamicDataExpression: data is not an object: ' + key);
|
|
|
- return data;
|
|
|
- }
|
|
|
- return DataObjectUtils.accessObjectByString(data, key, false);
|
|
|
- }
|
|
|
- }
|
|
|
- else {
|
|
|
- //原始值
|
|
|
- if (write)
|
|
|
- throw new Error('doEvaluateDynamicDataExpression: is not writable: ' + key);
|
|
|
-
|
|
|
- const preSerialize = doSerializeInternalVar(key);
|
|
|
- if (preSerialize.prased)
|
|
|
- return preSerialize.value;
|
|
|
- let type = 'string';
|
|
|
- let dat = key;
|
|
|
- const sp = key.split('#');
|
|
|
- if (sp.length > 1) {
|
|
|
- type = sp[0];
|
|
|
- dat = sp[1];
|
|
|
- }
|
|
|
- switch (type) {
|
|
|
- case 'string': return dat as string;
|
|
|
- case 'number': return Number(dat);
|
|
|
- case 'boolean': return Boolean(dat);
|
|
|
- case 'date': return new Date(dat);
|
|
|
- case 'array': return dat.split(',');
|
|
|
- case 'object': return JSON.parse(dat);
|
|
|
- default: throw new Error('doEvaluateDynamicDataExpression: unknown type: ' + type);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- switch (type) {
|
|
|
- case 'A':
|
|
|
- return accessDynamicKeyOrPrevResult(key, false);
|
|
|
- default:
|
|
|
- return accessDynamicKeyOrPrevResult(type, false);
|
|
|
- case 'M': {
|
|
|
- if (arr.length < 3)
|
|
|
- return undefined;
|
|
|
- const map = accessDynamicKeyOrPrevResult(arr[2], false);
|
|
|
- return map[accessDynamicKeyOrPrevResult(key, false)];
|
|
|
- }
|
|
|
- case 'R': return prevResult;
|
|
|
- case 'S': {
|
|
|
- if (arr.length < 3)
|
|
|
- return undefined;
|
|
|
- accessDynamicKeyOrPrevResult(
|
|
|
- accessDynamicKeyOrPrevResult(arr[1], false),
|
|
|
- true,
|
|
|
- accessDynamicKeyOrPrevResult(arr[2], false)
|
|
|
- );
|
|
|
- }
|
|
|
- case 'ST': {
|
|
|
- if (arr.length < 3)
|
|
|
- return undefined;
|
|
|
- tempData.set(arr[1], accessDynamicKeyOrPrevResult(arr[2], false));
|
|
|
- return undefined;
|
|
|
- }
|
|
|
- case 'RT': {
|
|
|
- if (arr.length < 3)
|
|
|
- return undefined;
|
|
|
- return tempData.get(arr[1]);
|
|
|
- }
|
|
|
- case 'CT': {
|
|
|
- if (arr.length < 2)
|
|
|
- return undefined;
|
|
|
- tempData.delete(arr[1]);
|
|
|
- return undefined;
|
|
|
- }
|
|
|
- case 'OP': {
|
|
|
- if (arr.length < 4)
|
|
|
- return undefined;
|
|
|
- const a = accessDynamicKeyOrPrevResult(arr[1], false);
|
|
|
- const op = arr[2];
|
|
|
- const b = accessDynamicKeyOrPrevResult(arr[3], false);
|
|
|
- switch (op) {
|
|
|
- case '+': return a + b;
|
|
|
- case '-': return a - b;
|
|
|
- case '*': return a * b;
|
|
|
- case '/': return a / b;
|
|
|
- case '%': return a % b;
|
|
|
- case '**': return a ** b;
|
|
|
- case '==': return a == b;
|
|
|
- case '!=': return a != b;
|
|
|
- case '>': return a > b;
|
|
|
- case '>=': return a >= b;
|
|
|
- case '<': return a < b;
|
|
|
- case '<=': return a <= b;
|
|
|
- case '&&': return a && b;
|
|
|
- case '||': return a || b;
|
|
|
- case '!': return !a;
|
|
|
- default: {
|
|
|
- if (op.startsWith('arr#')) {
|
|
|
- if (Array.isArray(a)) {
|
|
|
- switch (op) {
|
|
|
- case 'pop': return a.pop();
|
|
|
- case 'shift': return a.shift();
|
|
|
- case 'unshift': return a.unshift(b);
|
|
|
- case 'join': return a.join(b);
|
|
|
- case 'push': return a.push(b);
|
|
|
- }
|
|
|
- }
|
|
|
- else
|
|
|
- throw new Error('doEvaluateDynamicDataExpression: push is not an array: ' + a);
|
|
|
- } else if (op.startsWith('obj#')) {
|
|
|
- if (typeof a === 'object' && a !== null) {
|
|
|
- switch (op) {
|
|
|
- case 'keys': return Object.keys(a);
|
|
|
- case 'values': return Object.values(a);
|
|
|
- case 'entries': return Object.entries(a);
|
|
|
- }
|
|
|
- }
|
|
|
- } else if (op.startsWith('str#')) {
|
|
|
- if (typeof a === 'string') {
|
|
|
- switch (op) {
|
|
|
- case 'split': return a.split(b);
|
|
|
- case 'replace': return a.replace(b, arr[3]);
|
|
|
- }
|
|
|
- }
|
|
|
- } else if (op.startsWith('num#')) {
|
|
|
- if (typeof a === 'number') {
|
|
|
- switch (op) {
|
|
|
- case 'round': return Math.round(a);
|
|
|
- case 'ceil': return Math.ceil(a);
|
|
|
- case 'floor': return Math.floor(a);
|
|
|
- }
|
|
|
- }
|
|
|
- } else if (op.startsWith('date#')) {
|
|
|
- if (a instanceof Date) {
|
|
|
- switch (op) {
|
|
|
- case 'format': return DateUtils.formatDate(a, b as string);
|
|
|
- case 'getYear': return a.getFullYear();
|
|
|
- case 'getMonth': return a.getMonth();
|
|
|
- case 'getDate': return a.getDate();
|
|
|
- case 'getDay': return a.getDay();
|
|
|
- case 'getHours': return a.getHours();
|
|
|
- case 'getMinutes': return a.getMinutes();
|
|
|
- case 'getSeconds': return a.getSeconds();
|
|
|
- case 'getMilliseconds': return a.getMilliseconds();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- throw new Error('doEvaluateDynamicDataExpression: unknown operator ' + op);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return undefined;
|
|
|
- }
|
|
|
-
|
|
|
- let result = null;
|
|
|
- for (const expression of expressionArr)
|
|
|
- result = evaluateExpression(expression, result);
|
|
|
-
|
|
|
- console.log('doEvaluateDynamicDataExpression: ', expressions, 'result: ', result);
|
|
|
- return result;
|
|
|
-}
|
|
|
-/**
|
|
|
- * 评估单条动态比较表达式(格式: 取值:符号:值)
|
|
|
- */
|
|
|
-function evaluateSingleCompareExpression(expression: string, context: IDynamicCompareExpressionContext): boolean {
|
|
|
- if (!expression)
|
|
|
- return false;
|
|
|
- const arr = expression.split(':');
|
|
|
- if (arr.length !== 3)
|
|
|
- return false;
|
|
|
- const key = arr[0];
|
|
|
- const symbol = arr[1];
|
|
|
- const value = doEvaluateDynamicDataExpression(arr[2], context);
|
|
|
- const data = doEvaluateDynamicDataExpression(key, context);
|
|
|
- const negate = symbol.charAt(0) === '!';
|
|
|
- switch (symbol) {
|
|
|
- case '!empty':
|
|
|
- case 'empty': {
|
|
|
- let compareValue = data === undefined || data === null || data === '';
|
|
|
- if (Array.isArray(data))
|
|
|
- compareValue = data.length === 0;
|
|
|
- return negate ? !compareValue : compareValue;
|
|
|
- }
|
|
|
- case '==':
|
|
|
- case '!==': {
|
|
|
- const compareValue = data == value;
|
|
|
- return negate ? !compareValue : compareValue;
|
|
|
- }
|
|
|
- case '>':
|
|
|
- case '>=':
|
|
|
- case '<':
|
|
|
- case '<=': {
|
|
|
- let compareValue = false;
|
|
|
- if (symbol.startsWith('>'))
|
|
|
- compareValue = symbol === '>=' ? data >= value : data > value;
|
|
|
- if (symbol.startsWith('<'))
|
|
|
- compareValue = symbol === '<=' ? data <= value : data < value;
|
|
|
- return negate ? !compareValue : compareValue;
|
|
|
- }
|
|
|
- default: {
|
|
|
- throw new Error('doEvaluateDynamicCompareExpression: unknown symbol: ' + symbol);
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-/**
|
|
|
- * 评估动态比较表达式
|
|
|
- *
|
|
|
- * 格式:
|
|
|
- * 取值:符号:值
|
|
|
- * 多个表达式用逻辑 && || 分隔:&& 优先于 ||,按顺序求值
|
|
|
- * 例如: key1:empty: && key2:eq:1 || key3:neq:0
|
|
|
- *
|
|
|
- * @param expression
|
|
|
- * @returns
|
|
|
- */
|
|
|
-export function doEvaluateDynamicCompareExpression(expression: string, context: IDynamicCompareExpressionContext): boolean {
|
|
|
- const trimmed = expression.trim();
|
|
|
- if (!trimmed)
|
|
|
- return false;
|
|
|
- const orParts = trimmed.split(/\s*\|\|\s*/).map((s) => s.trim()).filter(Boolean);
|
|
|
- return orParts.some((orPart) => {
|
|
|
- const andParts = orPart.split(/\s*&&\s*/).map((s) => s.trim()).filter(Boolean);
|
|
|
- return andParts.every((andPart) => evaluateSingleCompareExpression(andPart, context));
|
|
|
- });
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * 调用动态函数
|
|
|
- *
|
|
|
- * 格式:
|
|
|
- * 函数名:参数1:参数2:参数3
|
|
|
- *
|
|
|
- * 前缀# 表示行号,用于跳转
|
|
|
- * 例如:
|
|
|
- * if:A,K#main.externalLink:jumpMp:default/
|
|
|
- * #jumpMp#openOfficialAccountArticle:A,K#main.externalLink/
|
|
|
- * #default#navigateToAutoContent:K#main
|
|
|
- *
|
|
|
- * 多个函数用换行符分隔,会按顺序依次调用,并返回最后一个函数的返回值
|
|
|
- *
|
|
|
- * @param functions
|
|
|
- * @param context
|
|
|
- */
|
|
|
-export async function doCallDynamicFunction(functions: string, context: IDynamicCompareExpressionContext) {
|
|
|
- if (functions.startsWith('dynamic:'))
|
|
|
- functions = functions.substring(8);
|
|
|
- const functionArr = functions.replace(/\n/g, '‼').split('‼');
|
|
|
- let result = null;
|
|
|
- let currentPointer = 0;
|
|
|
- let breakFlag = false;
|
|
|
-
|
|
|
- //记录行号映射
|
|
|
- //格式#name#expression
|
|
|
- let lineMap = new Map<string, number>();
|
|
|
- for (let i = 0; i < functionArr.length; i++) {
|
|
|
- if (functionArr[i].startsWith('#')) {
|
|
|
- const endIndex = functionArr[i].substring(1).indexOf('#');
|
|
|
- lineMap.set(functionArr[i].substring(1, endIndex + 1), i);
|
|
|
- functionArr[i] = functionArr[i].substring(endIndex + 2);
|
|
|
- }
|
|
|
- }
|
|
|
- //执行函数
|
|
|
- for (; currentPointer < functionArr.length; currentPointer++) {
|
|
|
- result = await evaluateExpression(functionArr[currentPointer], result);
|
|
|
- if (breakFlag)
|
|
|
- break;
|
|
|
- }
|
|
|
- function functionJump(line: string) {
|
|
|
- const lineNumber = !Number.isNaN(Number(line)) ? Number(line) : lineMap.get(line);
|
|
|
- if (!lineNumber || lineNumber < currentPointer)
|
|
|
- throw new Error('doCallDynamicFunction: bad line number: ' + line);
|
|
|
- currentPointer = lineNumber - 1;
|
|
|
- }
|
|
|
- async function evaluateExpression(expression: string, prevResult: any) : Promise<any> {
|
|
|
- const arr = expression.split(':');
|
|
|
- const functionName = arr[0];
|
|
|
- const params = arr.slice(1);
|
|
|
- console.log('22222', expression, functionName, params);
|
|
|
-
|
|
|
- function assertParamCount(count: number) {
|
|
|
- if (params.length !== count)
|
|
|
- throw new Error('doCallDynamicFunction: invalid param count: ' + functionName);
|
|
|
- }
|
|
|
- switch (functionName) {
|
|
|
- /**
|
|
|
- * 条件判断
|
|
|
- * 格式: if:判断:true的跳转行号:false的跳转行号
|
|
|
- */
|
|
|
- case 'if': {
|
|
|
- assertParamCount(3);
|
|
|
- let returnNewLineName = '';
|
|
|
- const result = params[0].includes(':') ?
|
|
|
- doEvaluateDynamicCompareExpression(params[0], context) :
|
|
|
- doEvaluateDynamicDataExpression(params[0], context);
|
|
|
- if (result)
|
|
|
- returnNewLineName = doEvaluateDynamicDataExpression(params[1], context);
|
|
|
- else
|
|
|
- returnNewLineName = doEvaluateDynamicDataExpression(params[2], context);
|
|
|
- functionJump(returnNewLineName);
|
|
|
- break;
|
|
|
- }
|
|
|
- /**
|
|
|
- * 分支
|
|
|
- * 格式: switch:判断:值:跳转:值:跳转...:默认跳转
|
|
|
- */
|
|
|
- case 'switch': {
|
|
|
- assertParamCount(2);
|
|
|
- const values = [] as any[];
|
|
|
- for (let i = 1; i < params.length; i += 2)
|
|
|
- values.push(doEvaluateDynamicDataExpression(params[i], context));
|
|
|
- const valueMatch = doEvaluateDynamicDataExpression(params[1], context);
|
|
|
- const index = values.indexOf(valueMatch);
|
|
|
- if (index !== -1)
|
|
|
- functionJump(params[index * 2 + 2]);
|
|
|
- else if (params.length > 2 && params.length % 2 === 0)
|
|
|
- functionJump(params[params.length - 1]);
|
|
|
- break;
|
|
|
- }
|
|
|
- /**
|
|
|
- * 跳转
|
|
|
- * 格式: jump:跳转行号
|
|
|
- */
|
|
|
- case 'jump': {
|
|
|
- assertParamCount(1);
|
|
|
- functionJump(doEvaluateDynamicDataExpression(params[0], context));
|
|
|
- break;
|
|
|
- }
|
|
|
- /**
|
|
|
- * 终止当前脚本
|
|
|
- * 格式: break
|
|
|
- */
|
|
|
- case 'break':
|
|
|
- breakFlag = true;
|
|
|
- break;
|
|
|
- /**
|
|
|
- * 返回值
|
|
|
- * 格式: return:值
|
|
|
- */
|
|
|
- case 'return':
|
|
|
- return doEvaluateDynamicDataExpression(params[0], context);
|
|
|
- case 'get':
|
|
|
- assertParamCount(1);
|
|
|
- return context.sourceData.main[doEvaluateDynamicDataExpression(params[0], context)];
|
|
|
- case 'set':
|
|
|
- assertParamCount(2);
|
|
|
- context.sourceData.main[doEvaluateDynamicDataExpression(params[0], context)] =
|
|
|
- doEvaluateDynamicDataExpression(params[1], context);
|
|
|
- break;
|
|
|
- case 'navTo':
|
|
|
- assertParamCount(1);
|
|
|
- console.log('11111', params[0]);
|
|
|
-
|
|
|
- navTo(doEvaluateDynamicDataExpression(params[0], context), params[1] ? doEvaluateDynamicDataExpression(params[1], context) : undefined);
|
|
|
- break;
|
|
|
- case 'redirectTo':
|
|
|
- assertParamCount(1);
|
|
|
- redirectTo(doEvaluateDynamicDataExpression(params[0], context), params[1] ? doEvaluateDynamicDataExpression(params[1], context) : undefined);
|
|
|
- break;
|
|
|
- case 'reLaunch':
|
|
|
- assertParamCount(1);
|
|
|
- uni.reLaunch({
|
|
|
- url: doEvaluateDynamicDataExpression(params[0], context),
|
|
|
- ...(params[1] ? doEvaluateDynamicDataExpression(params[1], context) : {})
|
|
|
- })
|
|
|
- break;
|
|
|
- case 'back':
|
|
|
- back();
|
|
|
- break;
|
|
|
- case 'openOfficialAccountChat':
|
|
|
- assertParamCount(1);
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- (uni as any).openOfficialAccountChat({
|
|
|
- username: doEvaluateDynamicDataExpression(params[0], context),
|
|
|
- success: (res: any) => {
|
|
|
- console.log('doCallDynamicFunction: openOfficialAccountChat success: ' + JSON.stringify(res));
|
|
|
- resolve(res);
|
|
|
- },
|
|
|
- fail: (err: any) => {
|
|
|
- console.error('doCallDynamicFunction: openOfficialAccountArticle failed: ' + err);
|
|
|
- reject(err);
|
|
|
- },
|
|
|
- });
|
|
|
- });
|
|
|
- case 'openOfficialAccountArticle':
|
|
|
- assertParamCount(1);
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- (uni as any).openOfficialAccountArticle({
|
|
|
- url: doEvaluateDynamicDataExpression(params[0], context),
|
|
|
- success: (res: any) => {
|
|
|
- console.log('doCallDynamicFunction: openOfficialAccountArticle success: ' + JSON.stringify(res));
|
|
|
- resolve(res);
|
|
|
- },
|
|
|
- fail: (err: any) => {
|
|
|
- console.error('doCallDynamicFunction: openOfficialAccountArticle failed: ' + err);
|
|
|
- uni.showToast({
|
|
|
- title: '打开公众号文章失败',
|
|
|
- icon: 'none',
|
|
|
- });
|
|
|
- reject(err);
|
|
|
- },
|
|
|
- });
|
|
|
- });
|
|
|
- case 'navigateToMiniProgram':
|
|
|
- assertParamCount(2);
|
|
|
- return new Promise((resolve, reject) => {
|
|
|
- uni.navigateToMiniProgram({
|
|
|
- appId: doEvaluateDynamicDataExpression(params[0], context),
|
|
|
- path: doEvaluateDynamicDataExpression(params[1], context),
|
|
|
- envVersion: doEvaluateDynamicDataExpression(params[2], context),
|
|
|
- success: (res: any) => resolve(res),
|
|
|
- fail: (err: any) => {
|
|
|
- console.error('doCallDynamicFunction: navigateToMiniProgram failed: ' + err);
|
|
|
- reject(err);
|
|
|
- },
|
|
|
- });
|
|
|
- });
|
|
|
- case 'navigateToAutoContent':
|
|
|
- assertParamCount(1);
|
|
|
- navigateToAutoContent(
|
|
|
- doEvaluateDynamicDataExpression(params[0], context),
|
|
|
- doEvaluateDynamicDataExpression(params[1], context)
|
|
|
- );
|
|
|
- break;
|
|
|
- default:
|
|
|
- throw new Error('doCallDynamicFunction: unknown function: ' + functionName);
|
|
|
- }
|
|
|
- return prevResult;
|
|
|
- }
|
|
|
-
|
|
|
- return result;
|
|
|
-}
|