mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +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 {
|
class App extends EventEmitter {
|
||||||
|
[x: string]: any;
|
||||||
public env: Env = new Env();
|
public env: Env = new Env();
|
||||||
public dsl?: MApp;
|
public dsl?: MApp;
|
||||||
public codeDsl?: CodeBlockDSL;
|
public codeDsl?: CodeBlockDSL;
|
||||||
@ -230,7 +231,7 @@ class App extends EventEmitter {
|
|||||||
* @param eventConfig 代码动作的配置
|
* @param eventConfig 代码动作的配置
|
||||||
* @returns void
|
* @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;
|
if (!codeId || isEmpty(this.codeDsl)) return;
|
||||||
const content = this.codeDsl?.[codeId]?.content;
|
const content = this.codeDsl?.[codeId]?.content;
|
||||||
if (typeof content === 'function') {
|
if (typeof content === 'function') {
|
||||||
@ -243,7 +244,7 @@ class App extends EventEmitter {
|
|||||||
methodName: string,
|
methodName: string,
|
||||||
params: Record<string, any>,
|
params: Record<string, any>,
|
||||||
args: any[],
|
args: any[],
|
||||||
flowState: FlowState,
|
flowState?: FlowState,
|
||||||
) {
|
) {
|
||||||
if (!dsId || !methodName) return;
|
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 {
|
class Env {
|
||||||
|
[x: string]: any;
|
||||||
isIos = false;
|
isIos = false;
|
||||||
isIphone = false;
|
isIphone = false;
|
||||||
isIpad = false;
|
isIpad = false;
|
||||||
|
@ -76,6 +76,51 @@ class Node extends EventEmitter {
|
|||||||
this.eventQueue.push(event);
|
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() {
|
public destroy() {
|
||||||
this.removeAllListeners();
|
this.removeAllListeners();
|
||||||
}
|
}
|
||||||
@ -107,51 +152,6 @@ class Node extends EventEmitter {
|
|||||||
await this.runHookCode('mounted');
|
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;
|
export default Node;
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
|
||||||
|
export { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
export * from '@tmagic/data-source';
|
export * from '@tmagic/data-source';
|
||||||
export * from '@tmagic/dep';
|
export * from '@tmagic/dep';
|
||||||
export * from '@tmagic/schema';
|
export * from '@tmagic/schema';
|
||||||
@ -32,5 +34,6 @@ export { default as Page } from './Page';
|
|||||||
export { default as Node } from './Node';
|
export { default as Node } from './Node';
|
||||||
export { default as IteratorContainer } from './IteratorContainer';
|
export { default as IteratorContainer } from './IteratorContainer';
|
||||||
export { default as FlowState } from './FlowState';
|
export { default as FlowState } from './FlowState';
|
||||||
|
export { default as DevtoolApi } from './DevtoolApi';
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -47,7 +47,7 @@ class DataSourceManager extends EventEmitter {
|
|||||||
|
|
||||||
public app: TMagicApp;
|
public app: TMagicApp;
|
||||||
|
|
||||||
public dataSourceMap = new Map<string, DataSource>();
|
public dataSourceMap = new Map<string, DataSource & Record<string, any>>();
|
||||||
|
|
||||||
public data: DataSourceManagerData = {};
|
public data: DataSourceManagerData = {};
|
||||||
public useMock?: boolean = false;
|
public useMock?: boolean = false;
|
||||||
|
@ -109,7 +109,7 @@ export const getNodePath = (id: Id, data: MNode[] = []): MNode[] => {
|
|||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getNodeInfo = (id: Id, root: MApp | null) => {
|
export const getNodeInfo = (id: Id, root: Pick<MApp, 'id' | 'items'> | null) => {
|
||||||
const info: EditorNodeInfo = {
|
const info: EditorNodeInfo = {
|
||||||
node: null,
|
node: null,
|
||||||
parent: null,
|
parent: null,
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 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';
|
import { useComponent, useDsl } from '@tmagic/vue-runtime-help';
|
||||||
|
|
||||||
const app = inject<TMagicApp>('app');
|
const app = inject<TMagicApp>('app');
|
||||||
@ -20,4 +20,28 @@ app?.on('page-change', (page?: Page) => {
|
|||||||
}
|
}
|
||||||
addParamToUrl({ page: page.data.id }, window);
|
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>
|
</script>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user