瀏覽代碼

📦 修改动态核心脚本解析器

快乐的梦鱼 1 月之前
父節點
當前提交
47376222a2

File diff suppressed because it is too large
+ 1260 - 0
src/common/utils/minisc/MiniScript.ts


+ 0 - 2
src/components/display/parse/Parse.vue

@@ -128,8 +128,6 @@ const parseHtml = (html: string): ParseNode[] => {
       nodes.push(childNode);
     }
   }
-  console.log(doc);
-  
   return nodes;
 };
 

+ 5 - 9
src/pages/article/common/CommonContent.ts

@@ -2,7 +2,7 @@ import { GetContentListItem, GetContentListParams } from "@/api/CommonContent";
 import { navTo } from "@/components/utils/PageAction";
 import { type IHomeCommonCategoryListTabListDataSolve } from "../data/CommonCategoryDefine";
 import { DateUtils } from "@imengyu/imengyu-utils";
-import { doEvaluateDynamicDataExpression } from "../data/CommonCategoryDynamicEvax";
+import dynamicScript, { isDynamicScript } from "../data/CommonCategoryScript";
 
 /**
  * 通用内容首页小列表控制代码组合
@@ -112,14 +112,10 @@ const resolveCommonContentData = {
 
 export function resolveCommonContentSolveProps(res: any[], dataSolve: IHomeCommonCategoryListTabListDataSolve[]) {
   for (const solve of dataSolve) {
-    if (solve.startsWith('dynamic:')) {
-      const dynamicExpression = solve.substring(8);
-      res = doEvaluateDynamicDataExpression(dynamicExpression, {
-        sourceData: {
-          main: res,
-          customData: {},
-        },
-      });
+    if (isDynamicScript(solve)) {
+      res = dynamicScript.execute(solve, {
+        main: res
+      }) as any;
     }
     res = resolveCommonContentData[solve]?.(res) || res;
   }

+ 5 - 9
src/pages/article/common/CommonListPage.ts

@@ -1,4 +1,4 @@
-import { doCallDynamicFunction } from "../data/CommonCategoryDynamicEvax";
+import dynamicScript from "../data/CommonCategoryScript";
 
 export type CommonListPageItemType = 'image-large-2'|'image-large'|'article-common'
   |'article-common-2'|'article-character'|'article-character-2'
@@ -18,12 +18,8 @@ export const CommonListPageItemTypeOptions = [
 ];
 
 export function doDynamicNavDetails(dynamic: string, content: any, parentData: any) {
-  return doCallDynamicFunction(dynamic, {
-    sourceData: {
-      main: content,
-      customData: {
-        parentData,
-      },
-    },
-  });
+  return dynamicScript.execute(dynamic, {
+    main: content,
+    parentData,
+  }) as any;
 }

+ 3 - 2
src/pages/article/common/CommonListPage.vue

@@ -168,6 +168,7 @@ import { navigateToAutoContent, resolveCommonContentGetPageDetailUrlAuto } from
 import { doDynamicNavDetails, type CommonListPageItemType } from './CommonListPage';
 import Grid4Item from '@/pages/parts/Grid4Item.vue';
 import Box2LineRightShadow from '@/pages/parts/Box2LineRightShadow.vue';
+import { isDynamicScript } from '../data/CommonCategoryScript';
 
 function getImage(item: any) {
   return item.thumbnail || item.image
@@ -387,8 +388,8 @@ function goDetails(item: any, id: number) {
     handleByContent();
     return;
   }
-  if (typeof props.detailsPage === 'string' && props.detailsPage.startsWith('dynamic:')) {
-    doDynamicNavDetails(props.detailsPage.substring(8), item, {});
+  if (typeof props.detailsPage === 'string' && isDynamicScript(props.detailsPage)) {
+    doDynamicNavDetails(props.detailsPage, item, {});
     return;
   }
 

+ 11 - 14
src/pages/article/data/CommonCategoryBlocks.vue

@@ -211,16 +211,13 @@ import CalendarBlock from '@/pages/travel/calendar/block.vue';
 import Box2LineLargeImageUserShadow from '@/pages/parts/Box2LineLargeImageUserShadow.vue';
 import StatsBlock from '@/pages/blocks/StatsBlock.vue';
 import MapCategoryBlock from '@/pages/blocks/MapBlock.vue';
-import Image from '@/components/basic/Image.vue';
-import AppCofig from '@/common/config/AppCofig';
 import Grid4Item from '@/pages/parts/Grid4Item.vue';
 import type { CategoryDefine } from './CommonCategoryBlocks';
 import { CommonCategoryDynamicDataSerializedApi, doGetDynamicListDataParams, type IHomeCommonCategoryDynamicDataDetailContent } from './CommonCategoryDynamicData';
 import AudioBlock from '@/pages/blocks/AudioBlock.vue';
-import { toast } from '@/components/utils/DialogAction';
-import { doDynamicNavDetails } from '../common/CommonListPage';
-import { doCallDynamicFunction } from './CommonCategoryDynamicEvax';
 import RichBlock from '@/pages/blocks/RichBlock.vue';
+import { doDynamicNavDetails } from '../common/CommonListPage';
+import dynamicScript, { isDynamicScript } from './CommonCategoryScript';
 
 const props = defineProps({
   /**
@@ -245,8 +242,8 @@ const categoryDatas = computed(() => props.categoryDefine.map(item => {
       ...item,
       detailsPage: () => {},
       morePage: () => {
-        if (item.morePage?.startsWith('dynamic:'))
-          doDynamicNavDetails(item.morePage.substring(8), {}, props.parentData);
+        if (item.morePage && isDynamicScript(item.morePage))
+          doDynamicNavDetails(item.morePage, {}, props.parentData);
         else if (item.morePage)
           navTo(item.morePage, {});
       },
@@ -274,8 +271,8 @@ const categoryDatas = computed(() => props.categoryDefine.map(item => {
           return;
         else if (item.detailsPage === 'byContent')
           navigateToAutoContent(dataItem, params);
-        else if (typeof item.detailsPage === 'string' && item.detailsPage.startsWith('dynamic:')) {
-          doDynamicNavDetails(item.detailsPage.substring(8), dataItem, props.parentData);
+        else if (item.detailsPage && isDynamicScript(item.detailsPage)) {
+          doDynamicNavDetails(item.detailsPage, dataItem, props.parentData);
           return;
         }
         else
@@ -301,11 +298,11 @@ const categoryDatas = computed(() => props.categoryDefine.map(item => {
             break;
           }
         }  
-        if (item.morePage?.startsWith('dynamic:')) {
-          doCallDynamicFunction(item.morePage.substring(8), {
-            sourceData: {
-              main: {},
-              customData: {},
+        if (item.morePage && isDynamicScript(item.morePage)) {
+          dynamicScript.execute(item.morePage, {
+            main: {
+              modelId,
+              mainBodyColumnId,
             },
           });
           return;

+ 4 - 1
src/pages/article/data/CommonCategoryDefine.ts

@@ -1,6 +1,7 @@
 import type { IHomeCommonCategoryDetailDefine, IHomeCommonArticleDetailDefine } from "./defines/Details";
 import type { IHomeCommonCategoryHomeDefine } from "./defines/Home";
 import type { IHomeCommonCategoryListDefine } from "./defines/List";
+import type { IHomeTravelDetailDefine, IHomeTravelListDefine } from "./defines/Travel";
 
 export * from "./defines/Blocks";
 export * from "./defines/Details";
@@ -28,7 +29,9 @@ export interface IHomeCommonCategoryDefine {
     content: IHomeCommonCategoryListDefine
       |IHomeCommonCategoryHomeDefine
       |IHomeCommonCategoryDetailDefine
-      |IHomeCommonArticleDetailDefine,
+      |IHomeCommonArticleDetailDefine
+      |IHomeTravelListDefine
+      |IHomeTravelDetailDefine,
   }[],
 }
 

+ 20 - 21
src/pages/article/data/CommonCategoryDetail.vue

@@ -157,8 +157,8 @@ import CommonContent, { GetContentDetailItem, GetContentListParams } from '@/api
 import CommonCategoryDetailContentBlocks from './CommonCategoryDetailContentBlocks.vue';
 import ImageGrid from '@/pages/parts/ImageGrid.vue';
 import CommonCategoryListBlock from './CommonCategoryListBlock.vue';
-import { doEvaluateDynamicCompareExpression, doEvaluateDynamicDataExpression } from './CommonCategoryDynamicEvax';
 import { resolveCommonContentSolveProps } from '../common/CommonContent';
+import dynamicScript, { isDynamicScript } from './CommonCategoryScript';
 
 export interface CommonCategoryDetailProps extends DetailTabPageProps {
   /**
@@ -168,6 +168,7 @@ export interface CommonCategoryDetailProps extends DetailTabPageProps {
     label: string;
     key: string;
     map?: Record<string|number, string>;
+    visibleVia?: string;
   }[];
   /**
    * 简介下方块
@@ -333,14 +334,12 @@ async function loadPageConfig() {
             break;
           }
           case 'expression': {
-            result = doEvaluateDynamicDataExpression(keys[2], {
-              sourceData: {
-                main: content,
-                customData: {
-                  customTabNameIdMap: currentCommonCategoryContentDefine.value?.props.customTabNameIdMap || {},
-                },
+            result = dynamicScript.execute(keys[2], {
+              main: content,
+              customData: {
+                customTabNameIdMap: currentCommonCategoryContentDefine.value?.props.customTabNameIdMap || {},
               },
-            });
+            }) as any;
             break;
           }
           case 'idMap': {
@@ -377,12 +376,9 @@ const descItems = computed(() => (
     .map((item) => {
       let value = '';
       if (item.key.startsWith('expression:')) {
-        value = doEvaluateDynamicDataExpression(item.key.substring(11), {
-          sourceData: {
-            main: content.value,
-            customData: {},
-          },
-        });
+        value = dynamicScript.execute(item.key.substring(11), {
+          main: content.value,
+        }) as any;
       }
       else
         value = content.value?.[item.key] || '';
@@ -393,6 +389,10 @@ const descItems = computed(() => (
         value,
       }
     })
+    .filter((item) => Boolean(item.value))
+    .filter((item) => !item.visibleVia || item.visibleVia === 'auto' || dynamicScript.execute(item.visibleVia, {
+      main: item,
+    }) as any)
 );
 
 function onLoaded(d: any) {
@@ -411,7 +411,9 @@ async function loadTabVisible(tabs: IHomeCommonCategoryDetailTabItemDefine[], d:
     if (tab.visibleVia && tab.visibleVia.includes(':')) {
       visibleCheckKeys = tab.visibleVia.split(':');
       visibleCheckBy = visibleCheckKeys[0];
-      visibleCheckExpression = tab.visibleVia.substring(visibleCheckKeys[0].length + 1);
+    } else if (tab.visibleVia && isDynamicScript(tab.visibleVia)) {
+      visibleCheckExpression = tab.visibleVia;
+      visibleCheckBy = 'expression';
     }
     switch (visibleCheckBy) {
       default:
@@ -432,12 +434,9 @@ async function loadTabVisible(tabs: IHomeCommonCategoryDetailTabItemDefine[], d:
         break;
       }
       case 'expression': {
-        check = doEvaluateDynamicCompareExpression(visibleCheckExpression, {
-          sourceData: {
-            main: d,
-            customData: {},
-          },
-        });
+        check = dynamicScript.execute(visibleCheckExpression, {
+          main: d,
+        }) as any;
         break;
       }
       case 'idMap': {

+ 4 - 7
src/pages/article/data/CommonCategoryDynamicData.ts

@@ -30,7 +30,7 @@ import { CommonCategorDynamicDropDownValuesToParams } from "./data-defines/Dropd
 import NewsIndexContent from "@/api/news/NewsIndexContent";
 import VillageContent from "@/api/fusion/VillageContent";
 import NotConfigue from "@/api/NotConfigue";
-import { doEvaluateDynamicDataExpression } from "./CommonCategoryDynamicEvax";
+import dynamicScript from "./CommonCategoryScript";
 
 export * from './data-defines/Category';
 export * from './data-defines/Dropdown';
@@ -263,12 +263,9 @@ export async function doLoadDynamicListData(
         undefined,
       )).data;
       if (item.dataSolve) {
-        data = doEvaluateDynamicDataExpression(item.dataSolve, {
-          sourceData: {
-            main: data,
-            customData: {},
-          },
-        });
+        data = dynamicScript.execute(item.dataSolve, {
+          main: data,
+        }) as any;
       }
       return data;
     }

+ 0 - 515
src/pages/article/data/CommonCategoryDynamicEvax.ts

@@ -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;
-}

+ 1 - 1
src/pages/article/data/CommonCategoryGlobalLoader.ts

@@ -26,7 +26,7 @@ export function useCommonCategoryGlobalLoader() {
     uni.showLoading({ title: '加载中' });
     try {
       //本地开发时,使用默认配置
-      if (false && getIsDevtoolsPlatform()) {
+      if (true && getIsDevtoolsPlatform()) {
         commonCategoryData.value = DefaultCofig as IHomeCommonCategoryDefine;
         return;
       }

+ 42 - 0
src/pages/article/data/CommonCategoryScript.ts

@@ -0,0 +1,42 @@
+import { GetContentListParams } from "@/api/CommonContent";
+import { MiniScript } from "../../../common/utils/minisc/MiniScript";
+import { navCommonDetail, navCommonList, navigateToAutoContent } from "../common/CommonContent";
+import { back, backReturnData, navTo, redirectTo } from "@/components/utils/PageAction";
+
+const dynamicScript = new MiniScript();
+dynamicScript.registerGlobal('uni', uni);
+dynamicScript.registerGlobal('consts', {
+  TYPE_VIDEO: GetContentListParams.TYPE_VIDEO,
+  TYPE_ARTICLE: GetContentListParams.TYPE_ARTICLE,
+  TYPE_AUDIO: GetContentListParams.TYPE_AUDIO,
+  TYPE_IMAGE: GetContentListParams.TYPE_IMAGE,
+  TYPE_ARCHIVE: GetContentListParams.TYPE_ARCHIVE,
+});
+dynamicScript.registerFunction('navigateToAutoContent', navigateToAutoContent);
+dynamicScript.registerFunction('navCommonDetail', navCommonDetail);
+dynamicScript.registerFunction('navCommonList', navCommonList);
+dynamicScript.registerFunction('navTo', navTo);
+dynamicScript.registerFunction('back', back);
+dynamicScript.registerFunction('backReturnData', backReturnData);
+dynamicScript.registerFunction('redirectTo', redirectTo);
+
+export function isDynamicScript(script: string): boolean {
+  return script.length >= 10 && (script.startsWith('if(') 
+    || script.startsWith('return') 
+    || script.includes('{') 
+    || script.includes('}') 
+    || script.includes('(') 
+    || script.includes(')') 
+    || script.includes('&&') 
+    || script.includes('||') 
+    || script.includes('!') 
+    || script.includes('==') 
+    || script.includes('!=') 
+    || script.includes('===') 
+    || script.includes('!==') 
+    || script.includes('>')
+
+  );
+}
+
+export default dynamicScript;

+ 64 - 12
src/pages/article/data/DefaultCategory.json

@@ -99,7 +99,7 @@
               "morePage": "/pages/inhert/language/list"
             },
             {
-              "text": "文化地图",
+              "text": "厦门闽南文化地图",
               "blockProps": {
                 "mapConfigItems": [
                   {
@@ -148,6 +148,20 @@
               "type": "MapBlock"
             },
             {
+              "text": "闽南文化精品线路",
+              "type": "small-grid",
+              "data": {
+                "type": "request",
+                "method": "GET",
+                "url": "https://mn.wenlvti.net/app_static/minnan/data/indexTestData.json",
+                "otherParams": {}
+              },
+              "showTitle": true,
+              "detailsPage": "navTo('/pages/travel/route/list')",
+              "style": {},
+              "morePage": "/pages/travel/route/list"
+            },
+            {
               "text": "精彩瞬间",
               "data": {
                 "type": "serializedApi",
@@ -158,10 +172,13 @@
                     298,
                     299
                   ]
+                },
+                "otherParams": {
+                  "is_external_link": "0"
                 }
               },
               "showMore": false,
-              "detailsPage": "dynamic:if:A,K#main.externalLink:jumpMp:default/#jumpMp#openOfficialAccountArticle:A,K#main.externalLink/break/#default#navigateToAutoContent:K#main",
+              "detailsPage": "if(main.externalLink) { uni.openOfficialAccountArticle({ url: main.externalLink }) } else { navigateToAutoContent(main) }",
               "dataSolve": [
                 "common"
               ],
@@ -717,6 +734,9 @@
                         277
                       ],
                       "modelId": 17
+                    },
+                    "otherParams": {
+                      "pid": "0"
                     }
                   },
                   "detailsPage": "byContent",
@@ -866,7 +886,7 @@
             {
               "text": "非遗项目",
               "type": "list",
-              "width": 150,
+              "width": 180,
               "data": {
                 "type": "serializedApi",
                 "name": "ProjectsContent"
@@ -911,13 +931,13 @@
             {
               "text": "非遗传承人",
               "type": "jump",
-              "width": 150,
+              "width": 190,
               "url": "/pages/inhert/inheritor/list"
             },
             {
               "text": "非遗传习中心(所)",
               "type": "list",
-              "width": 250,
+              "width": 280,
               "data": {
                 "type": "serializedApi",
                 "name": "SeminarContent"
@@ -941,7 +961,7 @@
             },
             {
               "text": "保护单位",
-              "width": 150,
+              "width": 180,
               "type": "list",
               "data": {
                 "type": "serializedApi",
@@ -1320,7 +1340,7 @@
             }
           ],
           "itemType": "article-common",
-          "detailsPage": "dynamic:if:A,K#main.externalLink:jumpMp:default/#jumpMp#openOfficialAccountArticle:A,K#main.externalLink/break/#default#navigateToAutoContent:K#main"
+          "detailsPage": "if(main.externalLink) { uni.openOfficialAccountArticle({ url: main.externalLink }) } else { navigateToAutoContent(main) }"
         }
       }
     },
@@ -1357,7 +1377,8 @@
             },
             {
               "label": "其他级别保护单位",
-              "key": "otherLevelCount"
+              "key": "otherLevelCount",
+              "visibleVia": "return main.value != '0个'"
             },
             {
               "label": "字号名称",
@@ -1872,7 +1893,38 @@
                         "type": "parentKey",
                         "key": "associationMeList"
                       },
-                      "dataSolve": []
+                      "dataSolve": [
+                        "return main.filter(item => item.type === consts.TYPE_VIDEO)"
+                      ]
+                    }
+                  ]
+                }
+              },
+              "visibleVia": "return main.associationMeList.filter(item => item.type === consts.TYPE_VIDEO).length > 0"
+            },
+            {
+              "text": "相关文章",
+              "type": "list",
+              "width": 170,
+              "key": "associationMeList",
+              "visibleVia": "return main.associationMeList.filter(item => item.type === consts.TYPE_ARTICLE).length > 0",
+              "define": {
+                "props": {
+                  "showTab": false,
+                  "showSearch": false,
+                  "itemType": "article-common",
+                  "detailsPage": "/pages/article/details",
+                  "tabs": [
+                    {
+                      "text": "Root",
+                      "type": "list",
+                      "data": {
+                        "type": "parentKey",
+                        "key": "associationMeList"
+                      },
+                      "dataSolve": [
+                        "return main.filter(item => item.type === consts.TYPE_ARTICLE)"
+                      ]
                     }
                   ]
                 }
@@ -1922,7 +1974,7 @@
                   ]
                 }
               },
-              "visibleVia": "expression:A,K#main.associationMeList[0].type:!==:A,TYPE_VIDEO&&A,K#main.associationMeList.length:>:A,number#0"
+              "visibleVia": "return main.associationMeList[0].type !== consts.TYPE_VIDEO && main.associationMeList.length > 0"
             },
             {
               "text": "视频",
@@ -1947,7 +1999,7 @@
                   ]
                 }
               },
-              "visibleVia": "expression:A,K#main.associationMeList[0].type:==:A,TYPE_VIDEO"
+              "visibleVia": "return main.associationMeList[0].type !== consts.TYPE_VIDEO"
             }
           ]
         }
@@ -2041,7 +2093,7 @@
             "icon": "https://mn.wenlvti.net/app_static/minnan/images/inhert/IconBorrow.png",
             "text": "点击查询借阅"
           },
-          "recommendDetailHandler": "dynamic:if:A,K#main.externalLink:jumpMp:default/#jumpMp#openOfficialAccountArticle:A,K#main.externalLink/break/#default#navigateToAutoContent:K#main"
+          "recommendDetailHandler": "if(main.externalLink) { uni.openOfficialAccountArticle({ url: main.externalLink }) } else { navigateToAutoContent(main) }"
         }
       }
     },

+ 1 - 1
src/pages/article/data/defines/Details.ts

@@ -71,7 +71,7 @@ export interface IHomeCommonCategoryDetailTabItemBaseDefine {
   /**
    * 标签页动态可见性判断方式
      * * auto: 自动判断
-     * * expression: 表达式判断 格式参考 doEvaluateDynamicCompareExpression
+     * * expression: 表达式判断 格式参考 dynamicScript.execute
      * * idMap: 标签页ID映射判断 格式:id:categoy (category为visibleVia中定义的分类ID)
    */
   visibleVia?: string,

+ 29 - 0
src/pages/article/data/defines/Travel.ts

@@ -0,0 +1,29 @@
+import type { IHomeCommonCategoryDynamicData } from "../CommonCategoryDynamicData";
+import type { IHomeCommonCategoryListTabListDropdownDefine, IHomeCommonCategoryListTabListDataSolve } from "./List";
+
+export interface IHomeTravelListDefine {
+  type: 'TravelList',
+  props: {
+    routeListImage: string,
+    routeListImageStyle: Record<string, any>,
+    /**
+     * 列表数据
+     */
+    data: IHomeCommonCategoryDynamicData,
+    /**
+     * 列表选项卡下拉选择定义
+     */
+    dropdownDefines?: IHomeCommonCategoryListTabListDropdownDefine[],
+    /**
+     * 列表数据解决方法
+     */
+    dataSolve?: IHomeCommonCategoryListTabListDataSolve[],
+  },
+}
+
+export interface IHomeTravelDetailDefine {
+  type: 'TravelDetail',
+  props: {
+
+  },
+}

+ 1 - 1
src/pages/article/data/editor/components/LinkPathEditor.vue

@@ -71,7 +71,7 @@
             <a-textarea
               v-else-if="pathType === 'custom'"
               v-model:value="linkPath"
-              placeholder="请输入跳转路径, 可用表达式:dynamic:开头,请参考源码设置表达式"
+              placeholder="请输入跳转路径, 可用表达式,请参考源码设置表达式"
               style="flex: 1"
               :auto-size="{ minRows: 2, maxRows: 5 }"
             />

+ 3 - 0
src/pages/article/data/editor/editors/DetailPropsEditor.vue

@@ -42,6 +42,9 @@
             <a-form-item label="映射关系">
               <KeyValueEditor v-model:value="item.map" />
             </a-form-item>
+            <a-form-item label="动态可见判断">
+              <a-textarea v-model:value="item.visibleVia" placeholder="动态可见判断表达式,请参考源码设置,默认auto:自动判断" :auto-size="{ minRows: 2, maxRows: 10 }" />
+            </a-form-item>
             <a-popconfirm title="确定删除?" @confirm="removeIntroBlockDesc(i)">
               <a-button type="link" danger size="small">删除</a-button>
             </a-popconfirm>

+ 10 - 19
src/pages/article/details.vue

@@ -136,7 +136,6 @@ import { navTo, redirectTo } from "@/components/utils/PageAction";
 import { navCommonDetail, resolveCommonContentFormData } from "./common/CommonContent";
 import { injectCommonCategory } from "./data/CommonCategoryGlobalLoader";
 import { getIsDevtoolsPlatform } from "@/common/utils/MpVersions";
-import { doCallDynamicFunction, doEvaluateDynamicCompareExpression } from "./data/CommonCategoryDynamicEvax";
 import type { GetContentDetailItem, GetContentListItem } from "@/api/CommonContent";
 import type { IHomeCommonArticleDetailDefine } from "./data/CommonCategoryDefine";
 import CommonContent, { GetContentListParams } from "@/api/CommonContent";
@@ -159,6 +158,7 @@ import FlexCol from "@/components/layout/FlexCol.vue";
 import Footer from "@/components/display/Footer.vue";
 import NavBar from "@/components/nav/NavBar.vue";
 import StatusBarSpace from "@/components/layout/space/StatusBarSpace.vue";
+import dynamicScript from "./data/CommonCategoryScript";
 
 export interface CommonArticleDetailProps {
   /**
@@ -326,20 +326,14 @@ const recommendListLoader = useSimpleDataLoader(async () => {
 });
 
 function evaluateButtonVisible(expression: string) {
-  return doEvaluateDynamicCompareExpression(expression, {
-    sourceData: {
-      main: loader.content.value || {},
-      customData: {},
-    }
-  });
+  return dynamicScript.execute(expression, {
+    main: loader.content.value || {},
+  }) as boolean;
 }
 function executeButtonAction(expression: string) {
-  doCallDynamicFunction(expression, {
-    sourceData: {
-      main: loader.content.value || {},
-      customData: {},
-    }
-  });
+  dynamicScript.execute(expression, {
+    main: loader.content.value || {},
+  }) as any;
 }
 function goExternalLink(url: string) {
   redirectTo('/pages/article/web/ewebview', { url });
@@ -398,12 +392,9 @@ function goArchive(id: number) {
 function goDetails(item: GetContentListItem) {
   const script = currentCommonCategoryContentDefine.value?.props.recommendDetailHandler;
   if (script) {
-    doCallDynamicFunction(script, {
-      sourceData: {
-        main: item,
-        customData: {},
-      }
-    });
+    dynamicScript.execute(script, {
+      main: item,
+    }) as any;
     return;
   }
   navCommonDetail({

+ 7 - 13
src/pages/blocks/RichBlock.vue

@@ -15,7 +15,7 @@
 import Parse from '@/components/display/parse/Parse.vue';
 import { onMounted, ref } from 'vue';
 import { doLoadDynamicDetailData, type IHomeCommonCategoryDynamicData } from '../article/data/CommonCategoryDynamicData';
-import { doEvaluateDynamicDataExpression } from '../article/data/CommonCategoryDynamicEvax';
+import dynamicScript from '../article/data/CommonCategoryScript';
 
 const props = defineProps({
   content: {
@@ -56,22 +56,16 @@ async function loadContent() {
 
 function linkTap(href: string) {
   if (props.linkTapScript) {
-    doEvaluateDynamicDataExpression(props.linkTapScript, {
-      sourceData: {
-        main: { href },
-        customData: {},
-      },
-    });
+    dynamicScript.execute(props.linkTapScript, {
+      main: { href },
+    }) as any;
   }
 }
 function viewTap(id: string) {
   if (props.viewTapScript) {
-    doEvaluateDynamicDataExpression(props.viewTapScript, {
-      sourceData: {
-        main: { id },
-        customData: {},
-      },
-    });
+    dynamicScript.execute(props.viewTapScript, {
+      main: { id },
+    }) as any;
   }
 }
 

+ 6 - 1
src/pages/travel/route/list.vue

@@ -74,7 +74,7 @@
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref, watch } from 'vue';
+import { computed, onMounted, ref, watch } from 'vue';
 import { navTo } from '@/components/utils/PageAction';
 import { useSimplePageContentLoader } from '@/common/composeabe/SimplePageContentLoader';
 import { useSimpleDataLoader } from '@/common/composeabe/SimpleDataLoader';
@@ -97,8 +97,13 @@ import SimpleDropDownPicker from '@/common/components/SimpleDropDownPicker.vue';
 import CommonContent, { GetContentListParams } from '@/api/CommonContent';
 import Empty from '@/components/feedback/Empty.vue';
 import Touchable from '@/components/feedback/Touchable.vue';
+import { injectCommonCategory } from '@/pages/article/data/CommonCategoryGlobalLoader';
+import type { IHomeTravelListDefine } from '@/pages/article/data/defines/Travel';
 
 const appConfiguration = injectAppConfiguration();
+const commonCategory = injectCommonCategory();
+const pageDefine = computed(() => commonCategory.value.page.find((p) => p.name === 'TravelList'));
+const pageContentDefine = computed(() => pageDefine.value?.content as IHomeTravelListDefine);
 
 const listLoader = useSimplePageContentLoader(async () => {
   const res = (await TravalContent.getTravalRouteList(