mirror of
				https://github.com/Tencent/tmagic-editor.git
				synced 2025-11-04 18:52:18 +08:00 
			
		
		
		
	feat(core): 新增调试api
This commit is contained in:
		
							parent
							
								
									0d6420215c
								
							
						
					
					
						commit
						a0f39d90d6
					
				@ -48,6 +48,7 @@ export interface AppOptionsConfig {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class App extends EventEmitter {
 | 
			
		||||
  [x: string]: any;
 | 
			
		||||
  public env: Env = new Env();
 | 
			
		||||
  public dsl?: MApp;
 | 
			
		||||
  public codeDsl?: CodeBlockDSL;
 | 
			
		||||
@ -230,7 +231,7 @@ class App extends EventEmitter {
 | 
			
		||||
   * @param eventConfig 代码动作的配置
 | 
			
		||||
   * @returns void
 | 
			
		||||
   */
 | 
			
		||||
  public async runCode(codeId: Id, params: Record<string, any>, args: any[], flowState: FlowState) {
 | 
			
		||||
  public async runCode(codeId: Id, params: Record<string, any>, args: any[], flowState?: FlowState) {
 | 
			
		||||
    if (!codeId || isEmpty(this.codeDsl)) return;
 | 
			
		||||
    const content = this.codeDsl?.[codeId]?.content;
 | 
			
		||||
    if (typeof content === 'function') {
 | 
			
		||||
@ -243,7 +244,7 @@ class App extends EventEmitter {
 | 
			
		||||
    methodName: string,
 | 
			
		||||
    params: Record<string, any>,
 | 
			
		||||
    args: any[],
 | 
			
		||||
    flowState: FlowState,
 | 
			
		||||
    flowState?: FlowState,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (!dsId || !methodName) return;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										118
									
								
								packages/core/src/DevtoolApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								packages/core/src/DevtoolApi.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
			
		||||
import { cloneDeep } from 'lodash-es';
 | 
			
		||||
 | 
			
		||||
import { compiledNodeField } from '@tmagic/data-source';
 | 
			
		||||
import type { DisplayCondItem, EventConfig, Id } from '@tmagic/schema';
 | 
			
		||||
import { NODE_CONDS_KEY } from '@tmagic/schema';
 | 
			
		||||
import { isValueIncludeDataSource, setValueByKeyPath } from '@tmagic/utils';
 | 
			
		||||
 | 
			
		||||
import TMagicApp from './App';
 | 
			
		||||
 | 
			
		||||
export default class DevToolApi {
 | 
			
		||||
  app: TMagicApp;
 | 
			
		||||
 | 
			
		||||
  constructor({ app }: { app: TMagicApp }) {
 | 
			
		||||
    this.app = app;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public openPop(popId: Id) {
 | 
			
		||||
    if (typeof this.app.openPop === 'function') {
 | 
			
		||||
      return this.app.openPop(popId);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setDataSourceData(dsId: string, data: any, path?: string) {
 | 
			
		||||
    const ds = this.app.dataSourceManager?.get(dsId);
 | 
			
		||||
 | 
			
		||||
    if (!ds) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ds.setData(data, path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public delDataSourceData() {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public requestDataSource(dsId: string) {
 | 
			
		||||
    const ds = this.app.dataSourceManager?.get(dsId);
 | 
			
		||||
 | 
			
		||||
    if (!ds) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeof ds.refresh === 'function') {
 | 
			
		||||
      return ds.refresh();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (typeof ds.request === 'function') {
 | 
			
		||||
      return ds.request();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ds.isInit = false;
 | 
			
		||||
    this.app.dataSourceManager?.init(ds);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getDisplayCondRealValue(_nodeId: Id, condItem: DisplayCondItem) {
 | 
			
		||||
    return this.app.dataSourceManager?.compliedConds({ [NODE_CONDS_KEY]: [{ cond: [condItem] }] });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async callHook(nodeId: Id, hookName: string, hookData: { params: Record<string, any> }[]) {
 | 
			
		||||
    const node = this.app.getNode(nodeId);
 | 
			
		||||
    if (!node) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (const item of hookData) {
 | 
			
		||||
      await node.runHookCode(hookName, item.params);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public trigger(nodeId: Id, events: EventConfig) {
 | 
			
		||||
    const node = this.app.getNode(nodeId);
 | 
			
		||||
    if (!node) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.app.emit(events.name, node);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public updateDsl(_nodeId: Id, _data: any, _path: string) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public isValueIncludeDataSource(value: any) {
 | 
			
		||||
    return isValueIncludeDataSource(value);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public compileDataSourceValue(value: any) {
 | 
			
		||||
    return compiledNodeField(value, this.app.dataSourceManager?.data || {});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public updateCode(codeId: string, value: any, path: string) {
 | 
			
		||||
    if (!this.app.dsl) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { codeBlocks } = this.app.dsl;
 | 
			
		||||
    if (!codeBlocks) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const code = codeBlocks[codeId];
 | 
			
		||||
 | 
			
		||||
    if (!code) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const newCode = cloneDeep(code);
 | 
			
		||||
    if (path === 'content' && typeof value === 'string' && (value.includes('function') || value.includes('=>'))) {
 | 
			
		||||
      // eslint-disable-next-line no-eval
 | 
			
		||||
      value = eval(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    setValueByKeyPath(path, value, newCode);
 | 
			
		||||
 | 
			
		||||
    codeBlocks[codeId] = newCode;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -17,6 +17,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
class Env {
 | 
			
		||||
  [x: string]: any;
 | 
			
		||||
  isIos = false;
 | 
			
		||||
  isIphone = false;
 | 
			
		||||
  isIpad = false;
 | 
			
		||||
 | 
			
		||||
@ -76,6 +76,51 @@ class Node extends EventEmitter {
 | 
			
		||||
    this.eventQueue.push(event);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async runHookCode(hook: string, params?: Record<string, any>) {
 | 
			
		||||
    if (typeof this.data[hook] === 'function') {
 | 
			
		||||
      // 兼容旧的数据格式
 | 
			
		||||
      await this.data[hook](this);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hookData = this.data[hook] as {
 | 
			
		||||
      /** 钩子类型 */
 | 
			
		||||
      hookType: HookType;
 | 
			
		||||
      hookData: {
 | 
			
		||||
        /** 函数类型 */
 | 
			
		||||
        codeType?: HookCodeType;
 | 
			
		||||
        /** 函数id, 代码块为string, 数据源为[数据源id, 方法名称] */
 | 
			
		||||
        codeId: string | [string, string];
 | 
			
		||||
        /** 参数配置 */
 | 
			
		||||
        params: Record<string, any>;
 | 
			
		||||
      }[];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (hookData?.hookType !== HookType.CODE) return;
 | 
			
		||||
 | 
			
		||||
    for (const item of hookData.hookData) {
 | 
			
		||||
      const { codeType = HookCodeType.CODE, codeId, params: itemParams = {} } = item;
 | 
			
		||||
 | 
			
		||||
      let functionContent: ((...args: any[]) => any) | string | undefined;
 | 
			
		||||
      const functionParams: { app: TMagicApp; params: Record<string, any>; dataSource?: DataSource } = {
 | 
			
		||||
        app: this.app,
 | 
			
		||||
        params: params || itemParams,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (codeType === HookCodeType.CODE && typeof codeId === 'string' && this.app.codeDsl?.[codeId]) {
 | 
			
		||||
        functionContent = this.app.codeDsl[codeId].content;
 | 
			
		||||
      } else if (codeType === HookCodeType.DATA_SOURCE_METHOD && Array.isArray(codeId) && codeId.length > 1) {
 | 
			
		||||
        const dataSource = this.app.dataSourceManager?.get(codeId[0]);
 | 
			
		||||
        functionContent = dataSource?.methods.find((method) => method.name === codeId[1])?.content;
 | 
			
		||||
        functionParams.dataSource = dataSource;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (functionContent && typeof functionContent === 'function') {
 | 
			
		||||
        await functionContent(functionParams);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public destroy() {
 | 
			
		||||
    this.removeAllListeners();
 | 
			
		||||
  }
 | 
			
		||||
@ -107,51 +152,6 @@ class Node extends EventEmitter {
 | 
			
		||||
      await this.runHookCode('mounted');
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async runHookCode(hook: string) {
 | 
			
		||||
    if (typeof this.data[hook] === 'function') {
 | 
			
		||||
      // 兼容旧的数据格式
 | 
			
		||||
      await this.data[hook](this);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const hookData = this.data[hook] as {
 | 
			
		||||
      /** 钩子类型 */
 | 
			
		||||
      hookType: HookType;
 | 
			
		||||
      hookData: {
 | 
			
		||||
        /** 函数类型 */
 | 
			
		||||
        codeType?: HookCodeType;
 | 
			
		||||
        /** 函数id, 代码块为string, 数据源为[数据源id, 方法名称] */
 | 
			
		||||
        codeId: string | [string, string];
 | 
			
		||||
        /** 参数配置 */
 | 
			
		||||
        params: Record<string, any>;
 | 
			
		||||
      }[];
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (hookData?.hookType !== HookType.CODE) return;
 | 
			
		||||
 | 
			
		||||
    for (const item of hookData.hookData) {
 | 
			
		||||
      const { codeType = HookCodeType.CODE, codeId, params = {} } = item;
 | 
			
		||||
 | 
			
		||||
      let functionContent: ((...args: any[]) => any) | string | undefined;
 | 
			
		||||
      const functionParams: { app: TMagicApp; params: Record<string, any>; dataSource?: DataSource } = {
 | 
			
		||||
        app: this.app,
 | 
			
		||||
        params,
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      if (codeType === HookCodeType.CODE && typeof codeId === 'string' && this.app.codeDsl?.[codeId]) {
 | 
			
		||||
        functionContent = this.app.codeDsl[codeId].content;
 | 
			
		||||
      } else if (codeType === HookCodeType.DATA_SOURCE_METHOD && Array.isArray(codeId) && codeId.length > 1) {
 | 
			
		||||
        const dataSource = this.app.dataSourceManager?.get(codeId[0]);
 | 
			
		||||
        functionContent = dataSource?.methods.find((method) => method.name === codeId[1])?.content;
 | 
			
		||||
        functionParams.dataSource = dataSource;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (functionContent && typeof functionContent === 'function') {
 | 
			
		||||
        await functionContent(functionParams);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Node;
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,8 @@
 | 
			
		||||
 | 
			
		||||
import App from './App';
 | 
			
		||||
 | 
			
		||||
export { cloneDeep } from 'lodash-es';
 | 
			
		||||
 | 
			
		||||
export * from '@tmagic/data-source';
 | 
			
		||||
export * from '@tmagic/dep';
 | 
			
		||||
export * from '@tmagic/schema';
 | 
			
		||||
@ -32,5 +34,6 @@ export { default as Page } from './Page';
 | 
			
		||||
export { default as Node } from './Node';
 | 
			
		||||
export { default as IteratorContainer } from './IteratorContainer';
 | 
			
		||||
export { default as FlowState } from './FlowState';
 | 
			
		||||
export { default as DevtoolApi } from './DevtoolApi';
 | 
			
		||||
 | 
			
		||||
export default App;
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ class DataSourceManager extends EventEmitter {
 | 
			
		||||
 | 
			
		||||
  public app: TMagicApp;
 | 
			
		||||
 | 
			
		||||
  public dataSourceMap = new Map<string, DataSource>();
 | 
			
		||||
  public dataSourceMap = new Map<string, DataSource & Record<string, any>>();
 | 
			
		||||
 | 
			
		||||
  public data: DataSourceManagerData = {};
 | 
			
		||||
  public useMock?: boolean = false;
 | 
			
		||||
 | 
			
		||||
@ -109,7 +109,7 @@ export const getNodePath = (id: Id, data: MNode[] = []): MNode[] => {
 | 
			
		||||
  return path;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getNodeInfo = (id: Id, root: MApp | null) => {
 | 
			
		||||
export const getNodeInfo = (id: Id, root: Pick<MApp, 'id' | 'items'> | null) => {
 | 
			
		||||
  const info: EditorNodeInfo = {
 | 
			
		||||
    node: null,
 | 
			
		||||
    parent: null,
 | 
			
		||||
 | 
			
		||||
@ -3,11 +3,11 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts" setup>
 | 
			
		||||
import { inject } from 'vue';
 | 
			
		||||
import { inject, reactive } from 'vue';
 | 
			
		||||
 | 
			
		||||
import type { MPage, Page } from '@tmagic/core';
 | 
			
		||||
import type { Id, MPage, Page } from '@tmagic/core';
 | 
			
		||||
import type TMagicApp from '@tmagic/core';
 | 
			
		||||
import { addParamToUrl } from '@tmagic/core';
 | 
			
		||||
import { addParamToUrl, cloneDeep, DevtoolApi, getNodeInfo, replaceChildNode, setValueByKeyPath } from '@tmagic/core';
 | 
			
		||||
import { useComponent, useDsl } from '@tmagic/vue-runtime-help';
 | 
			
		||||
 | 
			
		||||
const app = inject<TMagicApp>('app');
 | 
			
		||||
@ -20,4 +20,28 @@ app?.on('page-change', (page?: Page) => {
 | 
			
		||||
  }
 | 
			
		||||
  addParamToUrl({ page: page.data.id }, window);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
if (import.meta.env.DEV && app) {
 | 
			
		||||
  app.devtools = new (class extends DevtoolApi {
 | 
			
		||||
    public updateDsl(nodeId: Id, data: any, path: string) {
 | 
			
		||||
      if (!app.dsl) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const { node } = getNodeInfo(nodeId, app.dsl);
 | 
			
		||||
 | 
			
		||||
      if (!node) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const newNode = cloneDeep(node);
 | 
			
		||||
 | 
			
		||||
      setValueByKeyPath(path, data, newNode);
 | 
			
		||||
 | 
			
		||||
      replaceChildNode(reactive(newNode), [pageConfig.value as MPage]);
 | 
			
		||||
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  })({ app });
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user