feat: 完善迭代器嵌套使用问题,重构事件配置处理代码

* feat(editor,core,data-source,dep,schema,ui,utils,vue-runtime-help): 完善迭代器

* test: 完善测试用例

* chore: 构建

* feat: 迭代器嵌套事件传递数据

---------

Co-authored-by: roymondchen <roymondchen@tencent.com>
This commit is contained in:
roymondchen 2024-08-08 15:52:29 +08:00
parent 5e7ac69929
commit de47514f69
44 changed files with 1530 additions and 948 deletions

View File

@ -10,7 +10,7 @@ export const transformTsFileToCodeSync = (filename: string): string =>
loader: 'ts', loader: 'ts',
sourcefile: filename, sourcefile: filename,
sourcemap: 'inline', sourcemap: 'inline',
target: 'node14', target: 'node18',
}).code; }).code;
/** /**

View File

@ -0,0 +1,17 @@
import { describe, expect, test } from 'vitest';
import path from 'node:path';
import Core from '../src/Core';
describe('Core', () => {
test('instance', () => {
const core = new Core({
packages: [],
source: './a',
temp: './b',
});
expect(core).toBeInstanceOf(Core);
expect(core.dir.temp()).toBe(path.join(process.cwd(), './a/b'));
});
});

View File

@ -18,27 +18,13 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { has, isArray, isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { createDataSourceManager, DataSource, DataSourceManager, ObservedDataClass } from '@tmagic/data-source'; import { createDataSourceManager, DataSourceManager, ObservedDataClass } from '@tmagic/data-source';
import { import type { CodeBlockDSL, Id, JsEngine, MApp, RequestFunction } from '@tmagic/schema';
ActionType,
type AppCore,
type CodeBlockDSL,
type CodeItemConfig,
type CompItemConfig,
type DataSourceItemConfig,
type EventActionItem,
type EventConfig,
type Id,
type JsEngine,
type MApp,
type RequestFunction,
} from '@tmagic/schema';
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
import Env from './Env'; import Env from './Env';
import { bindCommonEventListener, isCommonMethod, triggerCommonMethod } from './events'; import EventHelper from './EventHelper';
import Flexible from './Flexible'; import Flexible from './Flexible';
import Node from './Node'; import Node from './Node';
import Page from './Page'; import Page from './Page';
@ -52,39 +38,30 @@ interface AppOptionsConfig {
designWidth?: number; designWidth?: number;
curPage?: Id; curPage?: Id;
useMock?: boolean; useMock?: boolean;
pageFragmentContainerType?: string | string[];
iteratorContainerType?: string | string[];
transformStyle?: (style: Record<string, any>) => Record<string, any>; transformStyle?: (style: Record<string, any>) => Record<string, any>;
request?: RequestFunction; request?: RequestFunction;
DataSourceObservedData?: ObservedDataClass; DataSourceObservedData?: ObservedDataClass;
} }
interface EventCache { class App extends EventEmitter {
eventConfig: CompItemConfig;
fromCpt: any;
args: any[];
}
class App extends EventEmitter implements AppCore {
public env: Env = new Env(); public env: Env = new Env();
public dsl?: MApp; public dsl?: MApp;
public codeDsl?: CodeBlockDSL; public codeDsl?: CodeBlockDSL;
public dataSourceManager?: DataSourceManager; public dataSourceManager?: DataSourceManager;
public page?: Page; public page?: Page;
public useMock = false; public useMock = false;
public platform = 'mobile'; public platform = 'mobile';
public jsEngine: JsEngine = 'browser'; public jsEngine: JsEngine = 'browser';
public request?: RequestFunction;
public components = new Map(); public components = new Map();
public pageFragmentContainerType = new Set(['page-fragment-container']);
public eventQueueMap: Record<string, EventCache[]> = {}; public iteratorContainerType = new Set(['iterator-container']);
public request?: RequestFunction;
public transformStyle: (style: Record<string, any>) => Record<string, any>; public transformStyle: (style: Record<string, any>) => Record<string, any>;
public eventHelper?: EventHelper;
public flexible?: Flexible; private flexible?: Flexible;
private eventList = new Map<(fromCpt: Node, ...args: any[]) => void, string>();
private dataSourceEventList = new Map<string, Map<string, (...args: any[]) => void>>();
constructor(options: AppOptionsConfig) { constructor(options: AppOptionsConfig) {
super(); super();
@ -95,6 +72,24 @@ class App extends EventEmitter implements AppCore {
options.platform && (this.platform = options.platform); options.platform && (this.platform = options.platform);
options.jsEngine && (this.jsEngine = options.jsEngine); options.jsEngine && (this.jsEngine = options.jsEngine);
if (options.pageFragmentContainerType) {
const pageFragmentContainerType = Array.isArray(options.pageFragmentContainerType)
? options.pageFragmentContainerType
: [options.pageFragmentContainerType];
pageFragmentContainerType.forEach((type) => {
this.pageFragmentContainerType.add(type);
});
}
if (options.iteratorContainerType) {
const iteratorContainerType = Array.isArray(options.iteratorContainerType)
? options.iteratorContainerType
: [options.iteratorContainerType];
iteratorContainerType.forEach((type) => {
this.iteratorContainerType.add(type);
});
}
if (typeof options.useMock === 'boolean') { if (typeof options.useMock === 'boolean') {
this.useMock = options.useMock; this.useMock = options.useMock;
} }
@ -103,6 +98,10 @@ class App extends EventEmitter implements AppCore {
this.flexible = new Flexible({ designWidth: options.designWidth }); this.flexible = new Flexible({ designWidth: options.designWidth });
} }
if (this.platform !== 'editor') {
this.eventHelper = new EventHelper({ app: this });
}
this.transformStyle = this.transformStyle =
options.transformStyle || ((style: Record<string, any>) => defaultTransformStyle(style, this.jsEngine)); options.transformStyle || ((style: Record<string, any>) => defaultTransformStyle(style, this.jsEngine));
@ -113,8 +112,6 @@ class App extends EventEmitter implements AppCore {
if (options.config) { if (options.config) {
this.setConfig(options.config, options.curPage); this.setConfig(options.config, options.curPage);
} }
bindCommonEventListener(this);
} }
public setEnv(ua?: string) { public setEnv(ua?: string) {
@ -145,24 +142,16 @@ class App extends EventEmitter implements AppCore {
this.codeDsl = config.codeBlocks; this.codeDsl = config.codeBlocks;
this.setPage(curPage || this.page?.data?.id); this.setPage(curPage || this.page?.data?.id);
}
/** const dataSourceList = Array.from(this.dataSourceManager!.dataSourceMap.values());
* this.eventHelper?.bindDataSourceEvents(dataSourceList);
* @deprecated
*/
public addPage() {
console.info('addPage 已经弃用');
} }
public setPage(id?: Id) { public setPage(id?: Id) {
const pageConfig = this.dsl?.items.find((page) => `${page.id}` === `${id}`); const pageConfig = this.dsl?.items.find((page) => `${page.id}` === `${id}`);
if (!pageConfig) { if (!pageConfig) {
if (this.page) { this.deletePage();
this.page.destroy();
this.page = undefined;
}
super.emit('page-change'); super.emit('page-change');
return; return;
@ -170,21 +159,24 @@ class App extends EventEmitter implements AppCore {
if (pageConfig === this.page?.data) return; if (pageConfig === this.page?.data) return;
if (this.page) { this.page?.destroy();
this.page.destroy();
}
this.page = new Page({ this.page = new Page({
config: pageConfig, config: pageConfig,
app: this, app: this,
}); });
super.emit('page-change', this.page); this.eventHelper?.removeNodeEvents();
this.page.nodes.forEach((node) => {
this.eventHelper?.bindNodeEvents(node);
});
this.bindEvents(); super.emit('page-change', this.page);
} }
public deletePage() { public deletePage() {
this.page?.destroy();
this.eventHelper?.removeNodeEvents();
this.page = undefined; this.page = undefined;
} }
@ -200,6 +192,10 @@ class App extends EventEmitter implements AppCore {
} }
} }
public getNode<T extends Node = Node>(id: Id, iteratorContainerId?: Id[], iteratorIndex?: number[]) {
return this.page?.getNode<T>(id, iteratorContainerId, iteratorIndex);
}
public registerComponent(type: string, Component: any) { public registerComponent(type: string, Component: any) {
this.components.set(type, Component); this.components.set(type, Component);
} }
@ -212,43 +208,15 @@ class App extends EventEmitter implements AppCore {
return this.components.get(type) as T; return this.components.get(type) as T;
} }
public bindEvents() {
Array.from(this.eventList.keys()).forEach((handler) => {
const name = this.eventList.get(handler);
name && this.off(name, handler);
});
this.eventList.clear();
if (!this.page) return;
for (const [, value] of this.page.nodes) {
value.events?.forEach((event, index) => {
let eventName = `${event.name}_${value.data.id}`;
let eventHandler = (fromCpt: Node, ...args: any[]) => {
this.eventHandler(index, fromCpt, args);
};
// 页面片容器可以配置页面片内组件的事件,形式为“${nodeId}.${eventName}”
const eventNames = event.name.split('.');
if (eventNames.length > 1) {
eventName = `${eventNames[1]}_${eventNames[0]}`;
eventHandler = (fromCpt: Node, ...args: any[]) => {
this.eventHandler(index, value, args);
};
}
this.eventList.set(eventHandler, eventName);
this.on(eventName, eventHandler);
});
}
this.bindDataSourceEvents();
}
public emit(name: string | symbol, ...args: any[]): boolean { public emit(name: string | symbol, ...args: any[]): boolean {
const [node, ...otherArgs] = args; const [node, ...otherArgs] = args;
if (node instanceof Node && node?.data?.id) { if (
return super.emit(`${String(name)}_${node.data.id}`, node, ...otherArgs); this.eventHelper &&
node instanceof Node &&
node.data?.id &&
node.eventKeys.has(`${String(name)}_${node.data.id}`)
) {
return this.eventHelper?.emit(node.eventKeys.get(`${String(name)}_${node.data.id}`)!, node, ...otherArgs);
} }
return super.emit(name, ...args); return super.emit(name, ...args);
} }
@ -258,8 +226,7 @@ class App extends EventEmitter implements AppCore {
* @param eventConfig * @param eventConfig
* @returns void * @returns void
*/ */
public async codeActionHandler(eventConfig: CodeItemConfig, args: any[]) { public async runCode(codeId: Id, params: Record<string, any>, args: any[]) {
const { codeId = '', params = {} } = eventConfig;
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') {
@ -267,48 +234,10 @@ class App extends EventEmitter implements AppCore {
} }
} }
/** public async runDataSourceMethod(dsId: string, methodName: string, params: Record<string, any>, args: any[]) {
* if (!dsId || !methodName) return;
* @param eventConfig
* @returns void
*/
public async compActionHandler(eventConfig: CompItemConfig, fromCpt: Node | DataSource, args: any[]) {
if (!this.page) throw new Error('当前没有页面');
let { method: methodName, to } = eventConfig; const dataSource = this.dataSourceManager?.get(dsId);
if (isArray(methodName)) {
[to, methodName] = methodName;
}
const toNode = this.page.getNode(to);
if (!toNode) throw `ID为${to}的组件不存在`;
if (isCommonMethod(methodName)) {
return triggerCommonMethod(methodName, toNode);
}
if (toNode.instance) {
if (typeof toNode.instance[methodName] === 'function') {
await toNode.instance[methodName](fromCpt, ...args);
}
} else {
this.addEventToMap({
eventConfig,
fromCpt,
args,
});
}
}
public async dataSourceActionHandler(eventConfig: DataSourceItemConfig, args: any[]) {
const { dataSourceMethod = [], params = {} } = eventConfig;
const [id, methodName] = dataSourceMethod;
if (!id || !methodName) return;
const dataSource = this.dataSourceManager?.get(id);
if (!dataSource) return; if (!dataSource) return;
@ -329,89 +258,8 @@ class App extends EventEmitter implements AppCore {
this.flexible?.destroy(); this.flexible?.destroy();
this.flexible = undefined; this.flexible = undefined;
}
private bindDataSourceEvents() { this.eventHelper?.destroy();
if (this.platform === 'editor') return;
// 先清掉之前注册的事件,重新注册
Array.from(this.dataSourceEventList.keys()).forEach((dataSourceId) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSourceId)!;
Array.from(dataSourceEvent.keys()).forEach((eventName) => {
const [prefix, ...path] = eventName.split('.');
if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) {
this.dataSourceManager?.offDataChange(dataSourceId, path.join('.'), dataSourceEvent.get(eventName)!);
} else {
this.dataSourceManager?.get(dataSourceId)?.off(prefix, dataSourceEvent.get(eventName)!);
}
});
});
(this.dsl?.dataSources || []).forEach((dataSource) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSource.id) ?? new Map<string, (args: any) => void>();
(dataSource.events || []).forEach((event) => {
const [prefix, ...path] = event.name?.split('.') || [];
if (!prefix) return;
const handler = (...args: any[]) => {
this.eventHandler(event, this.dataSourceManager?.get(dataSource.id), args);
};
dataSourceEvent.set(event.name, handler);
if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) {
// 数据源数据变化
this.dataSourceManager?.onDataChange(dataSource.id, path.join('.'), handler);
} else {
// 数据源自定义事件
this.dataSourceManager?.get(dataSource.id)?.on(prefix, handler);
}
});
this.dataSourceEventList.set(dataSource.id, dataSourceEvent);
});
}
private async actionHandler(actionItem: EventActionItem, fromCpt: Node | DataSource, args: any[]) {
if (actionItem.actionType === ActionType.COMP) {
// 组件动作
await this.compActionHandler(actionItem as CompItemConfig, fromCpt, args);
} else if (actionItem.actionType === ActionType.CODE) {
// 执行代码块
await this.codeActionHandler(actionItem as CodeItemConfig, args);
} else if (actionItem.actionType === ActionType.DATA_SOURCE) {
await this.dataSourceActionHandler(actionItem as DataSourceItemConfig, args);
}
}
/**
*
* @param eventsConfigIndex node.event中获取最新事件配置
* @param fromCpt
* @param args
*/
private async eventHandler(config: EventConfig | number, fromCpt: Node | DataSource | undefined, args: any[]) {
const eventConfig = typeof config === 'number' ? (fromCpt as Node).events[config] : config;
if (has(eventConfig, 'actions')) {
// EventConfig类型
const { actions } = eventConfig as EventConfig;
for (let i = 0; i < actions.length; i++) {
if (typeof config === 'number') {
// 事件响应中可能会有修改数据源数据的会更新dsl所以这里需要重新获取
const actionItem = ((fromCpt as Node).events[config] as EventConfig).actions[i];
this.actionHandler(actionItem, fromCpt as Node, args);
} else {
this.actionHandler(actions[i], fromCpt as DataSource, args);
}
}
} else {
// 兼容DeprecatedEventConfig类型 组件动作
await this.compActionHandler(eventConfig as unknown as CompItemConfig, fromCpt as Node, args);
}
}
private addEventToMap(event: EventCache) {
if (this.eventQueueMap[event.eventConfig.to]) {
this.eventQueueMap[event.eventConfig.to].push(event);
} else {
this.eventQueueMap[event.eventConfig.to] = [event];
}
} }
} }

View File

@ -0,0 +1,260 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
*/
import { EventEmitter } from 'events';
import { has } from 'lodash-es';
import type { DataSource } from '@tmagic/data-source';
import {
ActionType,
type CodeItemConfig,
type CompItemConfig,
type DataSourceItemConfig,
type EventActionItem,
type EventConfig,
} from '@tmagic/schema';
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
import type { default as TMagicApp } from './App';
import type { default as TMagicNode } from './Node';
import { COMMON_EVENT_PREFIX, isCommonMethod, triggerCommonMethod } from './utils';
const getCommonEventName = (commonEventName: string) => {
if (commonEventName.startsWith(COMMON_EVENT_PREFIX)) return commonEventName;
return `${COMMON_EVENT_PREFIX}${commonEventName}`;
};
// 点击在组件内的某个元素上,需要向上寻找到当前组件
const getDirectComponent = (element: HTMLElement | null, app: TMagicApp): TMagicNode | undefined => {
if (!element) {
return;
}
if (!element.id) {
return getDirectComponent(element.parentElement, app);
}
const node = app.getNode(
element.id,
element.dataset.iteratorContainerId?.split(','),
element.dataset.iteratorIndex?.split(',').map((i) => globalThis.parseInt(i, 10)),
);
return node;
};
export default class EventHelper extends EventEmitter {
public app: TMagicApp;
private nodeEventList = new Map<(fromCpt: TMagicNode, ...args: any[]) => void, symbol>();
private dataSourceEventList = new Map<string, Map<string, (...args: any[]) => void>>();
constructor({ app }: { app: TMagicApp }) {
super();
this.app = app;
globalThis.document.body.addEventListener('click', this.commonClickEventHandler);
}
public destroy() {
this.removeNodeEvents();
this.removeAllListeners();
globalThis.document.body.removeEventListener('click', this.commonClickEventHandler);
}
public bindNodeEvents(node: TMagicNode) {
node.events?.forEach((event, index) => {
let eventNameKey = `${event.name}_${node.data.id}`;
// 页面片容器可以配置页面片内组件的事件,形式为“${nodeId}.${eventName}”
const eventNames = event.name.split('.');
if (eventNames.length > 1) {
eventNameKey = `${eventNames[1]}_${eventNames[0]}`;
}
let eventName = Symbol(eventNameKey);
if (node.eventKeys.has(eventNameKey)) {
eventName = node.eventKeys.get(eventNameKey)!;
} else {
node.eventKeys.set(eventNameKey, eventName);
}
const eventHandler = (fromCpt: TMagicNode, ...args: any[]) => {
this.eventHandler(index, node, args);
};
this.nodeEventList.set(eventHandler, eventName);
this.on(eventName, eventHandler);
});
}
public removeNodeEvents() {
Array.from(this.nodeEventList.keys()).forEach((handler) => {
const name = this.nodeEventList.get(handler);
name && this.off(name, handler);
});
this.nodeEventList.clear();
}
public bindDataSourceEvents(dataSourceList: DataSource[]) {
this.removeDataSourceEvents(dataSourceList);
dataSourceList.forEach((dataSource) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSource.id) ?? new Map<string, (args: any) => void>();
(dataSource.schema.events || []).forEach((event) => {
const [prefix, ...path] = event.name?.split('.') || [];
if (!prefix) return;
const handler = (...args: any[]) => {
this.eventHandler(event, dataSource, args);
};
dataSourceEvent.set(event.name, handler);
if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) {
// 数据源数据变化
dataSource?.onDataChange(path.join('.'), handler);
} else {
// 数据源自定义事件
dataSource.on(prefix, handler);
}
});
this.dataSourceEventList.set(dataSource.id, dataSourceEvent);
});
}
public removeDataSourceEvents(dataSourceList: DataSource[]) {
if (!this.dataSourceEventList.size) {
return;
}
// 先清掉之前注册的事件,重新注册
dataSourceList.forEach((dataSource) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSource.id)!;
if (!dataSourceEvent) return;
Array.from(dataSourceEvent.keys()).forEach((eventName) => {
const [prefix, ...path] = eventName.split('.');
if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) {
dataSource.offDataChange(path.join('.'), dataSourceEvent.get(eventName)!);
} else {
dataSource.off(prefix, dataSourceEvent.get(eventName)!);
}
});
});
this.dataSourceEventList.clear();
}
/**
*
* @param eventsConfigIndex node.event中获取最新事件配置
* @param fromCpt
* @param args
*/
private async eventHandler(config: EventConfig | number, fromCpt: TMagicNode | DataSource | undefined, args: any[]) {
const eventConfig = typeof config === 'number' ? (fromCpt as TMagicNode).events[config] : config;
if (has(eventConfig, 'actions')) {
// EventConfig类型
const { actions } = eventConfig as EventConfig;
for (let i = 0; i < actions.length; i++) {
if (typeof config === 'number') {
// 事件响应中可能会有修改数据源数据的会更新dsl所以这里需要重新获取
const actionItem = ((fromCpt as TMagicNode).events[config] as EventConfig).actions[i];
this.actionHandler(actionItem, fromCpt as TMagicNode, args);
} else {
this.actionHandler(actions[i], fromCpt as DataSource, args);
}
}
} else {
// 兼容DeprecatedEventConfig类型 组件动作
await this.compActionHandler(eventConfig as unknown as CompItemConfig, fromCpt as TMagicNode, args);
}
}
private async actionHandler(actionItem: EventActionItem, fromCpt: TMagicNode | DataSource, args: any[]) {
if (actionItem.actionType === ActionType.COMP) {
const compActionItem = actionItem as CompItemConfig;
// 组件动作
await this.compActionHandler(compActionItem, fromCpt, args);
} else if (actionItem.actionType === ActionType.CODE) {
const codeActionItem = actionItem as CodeItemConfig;
// 执行代码块
await this.app.runCode(codeActionItem.codeId, codeActionItem.params || {}, args);
} else if (actionItem.actionType === ActionType.DATA_SOURCE) {
const dataSourceActionItem = actionItem as DataSourceItemConfig;
const [dsId, methodName] = dataSourceActionItem.dataSourceMethod;
await this.app.runDataSourceMethod(dsId, methodName, dataSourceActionItem.params || {}, args);
}
}
/**
*
* @param eventConfig
* @returns void
*/
private async compActionHandler(eventConfig: CompItemConfig, fromCpt: TMagicNode | DataSource, args: any[]) {
if (!this.app.page) throw new Error('当前没有页面');
let { method: methodName, to } = eventConfig;
if (Array.isArray(methodName)) {
[to, methodName] = methodName;
}
const toNode = this.app.getNode(to);
if (!toNode) throw `ID为${to}的组件不存在`;
if (isCommonMethod(methodName)) {
return triggerCommonMethod(methodName, toNode);
}
if (toNode.instance) {
if (typeof toNode.instance[methodName] === 'function') {
await toNode.instance[methodName](fromCpt, ...args);
}
} else {
toNode.addEventToQueue({
method: methodName,
fromCpt,
args,
});
}
}
private commonClickEventHandler = (e: MouseEvent) => {
if (!e.target) {
return;
}
const node = getDirectComponent(e.target as HTMLElement, this.app);
const eventName = `${getCommonEventName('click')}_${node?.data.id}`;
if (node?.eventKeys.has(eventName)) {
this.emit(node.eventKeys.get(eventName)!, node);
}
};
}

View File

@ -0,0 +1,104 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Id, MNode } from '@tmagic/schema';
import type { default as TMagicNode } from './Node';
import Node from './Node';
class IteratorContainer extends Node {
public nodes: Map<Id, TMagicNode>[] = [];
public setData(data: MNode) {
this.resetNodes();
super.setData(data);
}
public resetNodes() {
this.nodes?.forEach((nodeMap) => {
nodeMap.forEach((node) => {
node.destroy();
});
});
this.nodes = [];
}
public initNode(config: MNode, parent: TMagicNode, map: Map<Id, TMagicNode>) {
if (map.has(config.id)) {
map.get(config.id)?.destroy();
}
if (config.type && this.app.iteratorContainerType.has(config.type)) {
const iteratorContainer = new IteratorContainer({
config,
parent,
page: this.page,
app: this.app,
});
map.set(config.id, iteratorContainer);
this.app.eventHelper?.bindNodeEvents(iteratorContainer);
return;
}
const node = new Node({
config,
parent,
page: this.page,
app: this.app,
});
this.app.eventHelper?.bindNodeEvents(node);
map.set(config.id, node);
if (config.type && this.app.pageFragmentContainerType.has(config.type) && config.pageFragmentId) {
const pageFragment = this.app.dsl?.items?.find((page) => page.id === config.pageFragmentId);
if (pageFragment) {
config.items = [pageFragment];
}
}
config.items?.forEach((element: MNode) => {
this.initNode(element, node, map);
});
}
public setNodes(nodes: MNode[], index: number) {
const map = this.nodes[index] || new Map();
nodes.forEach((node) => {
this.initNode(node, this, map);
});
this.nodes[index] = map;
}
public getNode(id: Id, index: number) {
return this.nodes[index]?.get(id);
}
public destroy(): void {
super.destroy();
this.resetNodes();
}
}
export default IteratorContainer;

View File

@ -19,19 +19,26 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { DataSource } from '@tmagic/data-source'; import { DataSource } from '@tmagic/data-source';
import type { AppCore, EventConfig, MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema'; import type { EventConfig, MNode } from '@tmagic/schema';
import { HookCodeType, HookType } from '@tmagic/schema'; import { HookCodeType, HookType } from '@tmagic/schema';
import type App from './App'; import type { default as TMagicApp } from './App';
import type Page from './Page'; import type Page from './Page';
import Store from './Store'; import Store from './Store';
interface NodeOptions { interface EventCache {
method: string;
fromCpt: any;
args: any[];
}
export interface NodeOptions {
config: MNode; config: MNode;
page?: Page; page?: Page;
parent?: Node; parent?: Node;
app: App; app: TMagicApp;
} }
class Node extends EventEmitter { class Node extends EventEmitter {
public data!: MNode; public data!: MNode;
public style!: { public style!: {
@ -41,8 +48,11 @@ class Node extends EventEmitter {
public instance?: any; public instance?: any;
public page?: Page; public page?: Page;
public parent?: Node; public parent?: Node;
public app: App; public app: TMagicApp;
public store = new Store(); public store = new Store();
public eventKeys = new Map<string, symbol>();
private eventQueue: EventCache[] = [];
constructor(options: NodeOptions) { constructor(options: NodeOptions) {
super(); super();
@ -54,12 +64,16 @@ class Node extends EventEmitter {
this.listenLifeSafe(); this.listenLifeSafe();
} }
public setData(data: MComponent | MContainer | MPage | MPageFragment) { public setData(data: MNode) {
this.data = data; this.data = data;
const { events, style } = data; const { events, style } = data;
this.events = events || []; this.events = events || [];
this.style = style || {}; this.style = style || {};
this.emit('update-data'); this.emit('update-data', data);
}
public addEventToQueue(event: EventCache) {
this.eventQueue.push(event);
} }
public destroy() { public destroy() {
@ -84,10 +98,10 @@ class Node extends EventEmitter {
this.once('mounted', async (instance: any) => { this.once('mounted', async (instance: any) => {
this.instance = instance; this.instance = instance;
const eventConfigQueue = this.app.eventQueueMap[instance.config.id] || []; for (let eventConfig = this.eventQueue.shift(); eventConfig; eventConfig = this.eventQueue.shift()) {
if (typeof instance[eventConfig.method] === 'function') {
for (let eventConfig = eventConfigQueue.shift(); eventConfig; eventConfig = eventConfigQueue.shift()) { await instance[eventConfig.method](eventConfig.fromCpt, ...eventConfig.args);
this.app.compActionHandler(eventConfig.eventConfig, eventConfig.fromCpt, eventConfig.args); }
} }
await this.runHookCode('mounted'); await this.runHookCode('mounted');
@ -120,7 +134,7 @@ class Node extends EventEmitter {
const { codeType = HookCodeType.CODE, codeId, params = {} } = item; const { codeType = HookCodeType.CODE, codeId, params = {} } = item;
let functionContent: ((...args: any[]) => any) | string | undefined; let functionContent: ((...args: any[]) => any) | string | undefined;
const functionParams: { app: AppCore; params: Record<string, any>; dataSource?: DataSource } = { const functionParams: { app: TMagicApp; params: Record<string, any>; dataSource?: DataSource } = {
app: this.app, app: this.app,
params, params,
}; };

View File

@ -19,6 +19,8 @@
import type { Id, MComponent, MContainer, MPage, MPageFragment } from '@tmagic/schema'; import type { Id, MComponent, MContainer, MPage, MPageFragment } from '@tmagic/schema';
import type App from './App'; import type App from './App';
import IteratorContainer from './IteratorContainer';
import type { default as TMagicNode } from './Node';
import Node from './Node'; import Node from './Node';
interface ConfigOptions { interface ConfigOptions {
config: MPage | MPageFragment; config: MPage | MPageFragment;
@ -26,7 +28,7 @@ interface ConfigOptions {
} }
class Page extends Node { class Page extends Node {
public nodes = new Map<Id, Node>(); public nodes = new Map<Id, TMagicNode>();
constructor(options: ConfigOptions) { constructor(options: ConfigOptions) {
super(options); super(options);
@ -37,7 +39,20 @@ class Page extends Node {
}); });
} }
public initNode(config: MComponent | MContainer, parent: Node) { public initNode(config: MComponent | MContainer, parent: TMagicNode) {
if (config.type && this.app.iteratorContainerType.has(config.type)) {
this.setNode(
config.id,
new IteratorContainer({
config,
parent,
page: this,
app: this.app,
}),
);
return;
}
const node = new Node({ const node = new Node({
config, config,
parent, parent,
@ -47,7 +62,7 @@ class Page extends Node {
this.setNode(config.id, node); this.setNode(config.id, node);
if (config.type === 'page-fragment-container' && config.pageFragmentId) { if (config.type && this.app.pageFragmentContainerType.has(config.type) && config.pageFragmentId) {
const pageFragment = this.app.dsl?.items?.find((page) => page.id === config.pageFragmentId); const pageFragment = this.app.dsl?.items?.find((page) => page.id === config.pageFragmentId);
if (pageFragment) { if (pageFragment) {
config.items = [pageFragment]; config.items = [pageFragment];
@ -59,11 +74,30 @@ class Page extends Node {
}); });
} }
public getNode(id: Id) { public getNode<T extends TMagicNode = TMagicNode>(
return this.nodes.get(id); id: Id,
iteratorContainerId?: Id[],
iteratorIndex?: number[],
): T | undefined {
if (this.nodes.has(id)) {
return this.nodes.get(id) as T;
}
if (Array.isArray(iteratorContainerId) && iteratorContainerId.length && Array.isArray(iteratorIndex)) {
let iteratorContainer = this.nodes.get(iteratorContainerId[0]) as IteratorContainer;
for (let i = 1, l = iteratorContainerId.length; i < l; i++) {
iteratorContainer = iteratorContainer?.getNode(
iteratorContainerId[i],
iteratorIndex[i - 1],
) as IteratorContainer;
}
return iteratorContainer?.getNode(id, iteratorIndex[iteratorIndex.length - 1]) as T;
}
} }
public setNode(id: Id, node: Node) { public setNode(id: Id, node: TMagicNode) {
this.nodes.set(id, node); this.nodes.set(id, node);
} }

View File

@ -1,118 +0,0 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
*/
import App from './App';
import Node from './Node';
export interface EventOption {
label: string;
value: string;
}
const COMMON_EVENT_PREFIX = 'magic:common:events:';
const COMMON_METHOD_PREFIX = 'magic:common:actions:';
const CommonMethod = {
SHOW: 'show',
HIDE: 'hide',
SCROLL_TO_VIEW: 'scrollIntoView',
SCROLL_TO_TOP: 'scrollToTop',
};
export const DEFAULT_EVENTS: EventOption[] = [{ label: '点击', value: `${COMMON_EVENT_PREFIX}click` }];
export const DEFAULT_METHODS: EventOption[] = [];
export const getCommonEventName = (commonEventName: string) => {
if (commonEventName.startsWith(COMMON_EVENT_PREFIX)) return commonEventName;
return `${COMMON_EVENT_PREFIX}${commonEventName}`;
};
export const isCommonMethod = (methodName: string) => methodName.startsWith(COMMON_METHOD_PREFIX);
// 点击在组件内的某个元素上,需要向上寻找到当前组件
const getDirectComponent = (element: HTMLElement | null, app: App): Node | Boolean => {
if (!element) {
return false;
}
if (!element.id) {
return getDirectComponent(element.parentElement, app);
}
const node = app.page?.getNode(element.id);
if (!node) {
return false;
}
return node;
};
const commonClickEventHandler = (app: App, eventName: string, e: any) => {
const node = getDirectComponent(e.target, app);
if (node) {
app.emit(getCommonEventName(eventName), node);
}
};
export const bindCommonEventListener = (app: App) => {
if (app.jsEngine !== 'browser') return;
window.document.body.addEventListener('click', (e: any) => {
commonClickEventHandler(app, 'click', e);
});
window.document.body.addEventListener(
'click',
(e: any) => {
commonClickEventHandler(app, 'click:capture', e);
},
true,
);
};
export const triggerCommonMethod = (methodName: string, node: Node) => {
const { instance } = node;
if (!instance) return;
switch (methodName.replace(COMMON_METHOD_PREFIX, '')) {
case CommonMethod.SHOW:
instance.show();
break;
case CommonMethod.HIDE:
instance.hide();
break;
case CommonMethod.SCROLL_TO_VIEW:
instance.$el?.scrollIntoView({ behavior: 'smooth' });
break;
case CommonMethod.SCROLL_TO_TOP:
window.scrollTo({ top: 0, behavior: 'smooth' });
break;
default:
break;
}
};

View File

@ -18,10 +18,12 @@
import App from './App'; import App from './App';
export * from './events'; export { default as EventHelper } from './EventHelper';
export * from './utils';
export { default as Env } from './Env'; export { default as Env } from './Env';
export { default as Page } from './Page'; 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 App; export default App;

View File

@ -15,9 +15,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { JsEngine } from '@tmagic/schema'; import { JsEngine } from '@tmagic/schema';
import type { default as TMagicNode } from './Node';
export const style2Obj = (style: string) => { export const style2Obj = (style: string) => {
if (typeof style !== 'string') { if (typeof style !== 'string') {
return style; return style;
@ -115,3 +116,51 @@ export const transformStyle = (style: Record<string, any> | string, jsEngine: Js
return results; return results;
}; };
export const COMMON_EVENT_PREFIX = 'magic:common:events:';
export const COMMON_METHOD_PREFIX = 'magic:common:actions:';
export const CommonMethod = {
SHOW: 'show',
HIDE: 'hide',
SCROLL_TO_VIEW: 'scrollIntoView',
SCROLL_TO_TOP: 'scrollToTop',
};
export const isCommonMethod = (methodName: string) => methodName.startsWith(COMMON_METHOD_PREFIX);
export const triggerCommonMethod = (methodName: string, node: TMagicNode) => {
const { instance } = node;
if (!instance) return;
switch (methodName.replace(COMMON_METHOD_PREFIX, '')) {
case CommonMethod.SHOW:
instance.show();
break;
case CommonMethod.HIDE:
instance.hide();
break;
case CommonMethod.SCROLL_TO_VIEW:
instance.$el?.scrollIntoView({ behavior: 'smooth' });
break;
case CommonMethod.SCROLL_TO_TOP:
window.scrollTo({ top: 0, behavior: 'smooth' });
break;
default:
break;
}
};
export interface EventOption {
label: string;
value: string;
}
export const DEFAULT_EVENTS: EventOption[] = [{ label: '点击', value: `${COMMON_EVENT_PREFIX}click` }];
export const DEFAULT_METHODS: EventOption[] = [];

View File

@ -0,0 +1,239 @@
import { describe, expect, test } from 'vitest';
import { MApp, NodeType, TMagicIteratorContainer } from '@tmagic/schema';
import App from '../src/App';
const createAppDsl = (pageLength: number, nodeLength = 0) => {
const dsl: MApp = {
type: NodeType.ROOT,
id: 'app_1',
dataSources: [
{
id: 'ds_1',
fields: [
{
type: 'array',
name: 'array',
title: 'array',
enable: true,
fields: [
{
type: 'array',
name: 'arr',
title: 'arr',
defaultValue: [],
enable: true,
fields: [],
},
],
},
],
events: [],
methods: [],
type: 'base',
},
],
dataSourceDeps: {},
dataSourceCondDeps: {},
items: [
...new Array(pageLength)
.fill({
type: NodeType.PAGE,
items: new Array(nodeLength)
.fill({
type: 'text',
})
.map((node, index) => ({
...node,
id: `text_${index}`,
})),
})
.map((page, index) => ({
...page,
id: `page_${index}`,
})),
{
type: NodeType.PAGE_FRAGMENT,
id: 'page_fragment_1',
items: [
{
type: 'text',
id: 'text_page_fragment',
text: 'text_page_fragment',
},
],
},
],
};
return dsl;
};
describe('App', () => {
test('instance', () => {
const app = new App({});
expect(app).toBeInstanceOf(App);
});
test('page', () => {
const app = new App({
config: createAppDsl(2),
});
expect(app.getNode('page_0')?.data.id).toBe('page_0');
expect(app.page?.data.id).toBe('page_0');
app.setConfig(createAppDsl(3), 'page_1');
expect(app.page?.data.id).toBe('page_1');
app.setPage('page_2');
expect(app.page?.data.id).toBe('page_2');
});
test('node', () => {
const app = new App({
config: createAppDsl(1, 10),
});
expect(app.getNode('text_1')?.data.id).toBe('text_1');
});
test('iterator-container', () => {
const dsl = createAppDsl(1, 10);
dsl.items[0].items.push({
type: 'iterator-container',
id: 'iterator-container_1',
items: [
{
type: 'text',
id: 'text',
},
],
});
const app = new App({
config: dsl,
});
const ic = app.getNode('iterator-container_1') as unknown as TMagicIteratorContainer;
expect(ic?.data.id).toBe('iterator-container_1');
ic?.setNodes(
[
{
type: 'text',
id: 'text',
text: '1',
},
{
type: 'page-fragment-container',
id: 'page_fragment_container_1',
pageFragmentId: 'page_fragment_1',
},
{
type: 'iterator-container',
id: 'iterator-container_11',
items: [
{
type: 'text',
id: 'text',
},
],
},
],
0,
);
ic?.setNodes(
[
{
type: 'text',
id: 'text',
text: '2',
},
{
type: 'iterator-container',
id: 'iterator-container_11',
items: [
{
type: 'text',
id: 'text',
},
],
},
],
1,
);
expect(app.getNode('text', ['iterator-container_1'], [0])?.data.text).toBe('1');
expect(app.getNode('text', ['iterator-container_1'], [1])?.data.text).toBe('2');
expect(app.getNode('text_page_fragment', ['iterator-container_1'], [0])?.data.text).toBe('text_page_fragment');
const ic1 = app.getNode(
'iterator-container_11',
['iterator-container_1'],
[0],
) as unknown as TMagicIteratorContainer;
ic1?.setNodes(
[
{
type: 'text',
id: 'text',
text: '111',
},
],
0,
);
ic1?.setNodes(
[
{
type: 'text',
id: 'text',
text: '222',
},
],
1,
);
const ic2 = app.getNode(
'iterator-container_11',
['iterator-container_1'],
[1],
) as unknown as TMagicIteratorContainer;
ic2?.setNodes(
[
{
type: 'text',
id: 'text',
text: '11',
},
],
0,
);
ic2?.setNodes(
[
{
type: 'text',
id: 'text',
text: '22',
},
],
1,
);
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [0, 0])?.data.text).toBe('111');
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [0, 1])?.data.text).toBe('222');
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [1, 0])?.data.text).toBe('11');
expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [1, 1])?.data.text).toBe('22');
ic.resetNodes();
expect(ic2?.nodes.length).toBe(0);
});
});

View File

@ -20,13 +20,15 @@ import EventEmitter from 'events';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type { AppCore, DataSourceSchema, DisplayCond, Id, MNode } from '@tmagic/schema'; import type { default as TMagicApp, IteratorContainer as TMagicIteratorContainer } from '@tmagic/core';
import type { DataSourceSchema, DisplayCond, Id, MNode, NODE_CONDS_KEY } from '@tmagic/schema';
import { compiledNode } from '@tmagic/utils'; import { compiledNode } from '@tmagic/utils';
import { SimpleObservedData } from './observed-data/SimpleObservedData'; import { SimpleObservedData } from './observed-data/SimpleObservedData';
import { DataSource, HttpDataSource } from './data-sources'; import { DataSource, HttpDataSource } from './data-sources';
import { getDeps } from './depsCache';
import type { ChangeEvent, DataSourceManagerData, DataSourceManagerOptions, ObservedDataClass } from './types'; import type { ChangeEvent, DataSourceManagerData, DataSourceManagerOptions, ObservedDataClass } from './types';
import { compiledNodeField, compliedConditions, compliedIteratorItemConditions, compliedIteratorItems } from './utils'; import { compiledNodeField, compliedConditions, compliedIteratorItem, createIteratorContentData } from './utils';
class DataSourceManager extends EventEmitter { class DataSourceManager extends EventEmitter {
private static dataSourceClassMap = new Map<string, typeof DataSource>(); private static dataSourceClassMap = new Map<string, typeof DataSource>();
@ -37,13 +39,6 @@ class DataSourceManager extends EventEmitter {
DataSourceManager.dataSourceClassMap.set(type, dataSource); DataSourceManager.dataSourceClassMap.set(type, dataSource);
} }
/**
* @deprecated
*/
public static registe<T extends typeof DataSource = typeof DataSource>(type: string, dataSource: T) {
DataSourceManager.register(type, dataSource);
}
public static getDataSourceClass(type: string) { public static getDataSourceClass(type: string) {
return DataSourceManager.dataSourceClassMap.get(type); return DataSourceManager.dataSourceClassMap.get(type);
} }
@ -52,7 +47,7 @@ class DataSourceManager extends EventEmitter {
DataSourceManager.ObservedDataClass = ObservedDataClass; DataSourceManager.ObservedDataClass = ObservedDataClass;
} }
public app: AppCore; public app: TMagicApp;
public dataSourceMap = new Map<string, DataSource>(); public dataSourceMap = new Map<string, DataSource>();
@ -167,6 +162,10 @@ class DataSourceManager extends EventEmitter {
this.dataSourceMap.delete(id); this.dataSourceMap.delete(id);
} }
/**
* dsldsl
* @param {DataSourceSchema[]} schemas
*/
public updateSchema(schemas: DataSourceSchema[]) { public updateSchema(schemas: DataSourceSchema[]) {
schemas.forEach((schema) => { schemas.forEach((schema) => {
const ds = this.get(schema.id); const ds = this.get(schema.id);
@ -184,6 +183,13 @@ class DataSourceManager extends EventEmitter {
}); });
} }
/**
* dsl中所有key中数据源相关的配置编译成对应的值
* @param {MNode} node dsl
* @param {string | number} sourceId ID
* @param {boolean} deep items)false
* @returns {MNode} dsl
*/
public compiledNode({ items, ...node }: MNode, sourceId?: Id, deep = false) { public compiledNode({ items, ...node }: MNode, sourceId?: Id, deep = false) {
const newNode = cloneDeep(node); const newNode = cloneDeep(node);
@ -195,6 +201,7 @@ class DataSourceManager extends EventEmitter {
if (node.condResult === false) return newNode; if (node.condResult === false) return newNode;
if (node.visible === false) return newNode; if (node.visible === false) return newNode;
// 编译函数这里作为参数,方便后续支持自定义编译
return compiledNode( return compiledNode(
(value: any) => compiledNodeField(value, this.data), (value: any) => compiledNodeField(value, this.data),
newNode, newNode,
@ -203,19 +210,75 @@ class DataSourceManager extends EventEmitter {
); );
} }
public compliedConds(node: MNode) { /**
*
* @param {{ [NODE_CONDS_KEY]?: DisplayCond[] }} node
* @returns {boolean}
*/
public compliedConds(node: { [NODE_CONDS_KEY]?: DisplayCond[] }) {
return compliedConditions(node, this.data); return compliedConditions(node, this.data);
} }
public compliedIteratorItemConds(itemData: any, displayConds: DisplayCond[] = []) { /**
return compliedIteratorItemConditions(displayConds, itemData); *
} * @param {any[]} itemData
* @param {{ [NODE_CONDS_KEY]?: DisplayCond[] }} node
public compliedIteratorItems(itemData: any, items: MNode[], dataSourceField: string[] = []) { * @param {string[]} dataSourceField ['dsId', 'key1', 'key2']
* @returns {boolean}
*/
public compliedIteratorItemConds(
itemData: any[],
node: { [NODE_CONDS_KEY]?: DisplayCond[] },
dataSourceField: string[] = [],
) {
const [dsId, ...keys] = dataSourceField; const [dsId, ...keys] = dataSourceField;
const ds = this.get(dsId); const ds = this.get(dsId);
if (!ds) return items; if (!ds) return true;
return compliedIteratorItems(itemData, items, dsId, keys, this.data, this.app.platform === 'editor');
const ctxData = createIteratorContentData(itemData, ds.id, keys, this.data);
return compliedConditions(node, ctxData);
}
public compliedIteratorItems(
nodeId: Id,
itemData: any,
nodes: MNode[],
dataSourceField: string[] = [],
dataIteratorContainerId?: Id[],
dataIteratorIndex?: number[],
) {
const iteratorContainer = this.app.getNode<TMagicIteratorContainer>(
nodeId,
dataIteratorContainerId,
dataIteratorIndex,
);
const [dsId, ...keys] = dataSourceField;
const ds = this.get(dsId);
if (!ds || !iteratorContainer) return nodes;
const ctxData = createIteratorContentData(itemData, ds.id, keys, this.data);
const { deps = {}, condDeps = {} } = getDeps(ds.schema, nodes);
if (!Object.keys(deps).length && !Object.keys(condDeps).length) {
return nodes;
}
return nodes.map((item) => {
const node = compliedIteratorItem({
compile: (value: any) => compiledNodeField(value, ctxData),
dsId: ds.id,
item,
deps,
});
if (condDeps[node.id]?.keys.length && this.app.platform !== 'editor') {
node.condResult = compliedConditions(node, ctxData);
}
return node;
});
} }
public destroy() { public destroy() {

View File

@ -17,7 +17,7 @@
*/ */
import { union } from 'lodash-es'; import { union } from 'lodash-es';
import type { AppCore } from '@tmagic/schema'; import type { default as TMagicApp } from '@tmagic/core';
import { getDepNodeIds, getNodes, isPage } from '@tmagic/utils'; import { getDepNodeIds, getNodes, isPage } from '@tmagic/utils';
import DataSourceManager from './DataSourceManager'; import DataSourceManager from './DataSourceManager';
@ -26,12 +26,12 @@ import { updateNode } from './utils';
/** /**
* *
* @param app AppCore * @param {TMagicApp} app
* @param useMock 使mock数据 * @param {boolean} useMock 使mock数据
* @param initialData ssr数据可以由此传入 * @param {DataSourceManagerData} initialData ssr数据可以由此传入
* @returns DataSourceManager | undefined * @returns {DataSourceManager | undefined}
*/ */
export const createDataSourceManager = (app: AppCore, useMock?: boolean, initialData?: DataSourceManagerData) => { export const createDataSourceManager = (app: TMagicApp, useMock?: boolean, initialData?: DataSourceManagerData) => {
const { dsl, platform } = app; const { dsl, platform } = app;
if (!dsl?.dataSources) return; if (!dsl?.dataSources) return;

View File

@ -19,7 +19,8 @@ import EventEmitter from 'events';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type { AppCore, CodeBlockContent, DataSchema, DataSourceSchema } from '@tmagic/schema'; import type { default as TMagicApp } from '@tmagic/core';
import type { CodeBlockContent, DataSchema, DataSourceSchema } from '@tmagic/schema';
import { getDefaultValueFromFields } from '@tmagic/utils'; import { getDefaultValueFromFields } from '@tmagic/utils';
import { ObservedData } from '@data-source/observed-data/ObservedData'; import { ObservedData } from '@data-source/observed-data/ObservedData';
@ -33,7 +34,7 @@ export default class DataSource<T extends DataSourceSchema = DataSourceSchema> e
public isInit = false; public isInit = false;
/** @tmagic/core 实例 */ /** @tmagic/core 实例 */
public app: AppCore; public app: TMagicApp;
protected mockData?: Record<string | number, any>; protected mockData?: Record<string | number, any>;

View File

@ -0,0 +1,47 @@
import { isDataSourceCondTarget, isDataSourceTarget, Target, Watcher } from '@tmagic/dep';
import type { DataSourceSchema, MNode } from '@tmagic/schema';
import { DSL_NODE_KEY_COPY_PREFIX } from '@tmagic/utils';
const cache = new Map();
export const getDeps = (ds: DataSourceSchema, nodes: MNode[]) => {
const cacheKey = `${ds.id}:${nodes.map((node) => node.id).join(':')}`;
if (cache.has(cacheKey)) {
return cache.get(cacheKey);
}
const watcher = new Watcher();
watcher.addTarget(
new Target({
id: ds.id,
type: 'data-source',
isTarget: (key: string | number, value: any) => {
if (`${key}`.includes(DSL_NODE_KEY_COPY_PREFIX)) {
return false;
}
return isDataSourceTarget(ds, key, value, true);
},
}),
);
watcher.addTarget(
new Target({
id: ds.id,
type: 'cond',
isTarget: (key, value) => isDataSourceCondTarget(ds, key, value, true),
}),
);
watcher.collect(nodes, {}, true);
const { deps } = watcher.getTarget(ds.id, 'data-source');
const { deps: condDeps } = watcher.getTarget(ds.id, 'cond');
const result = { deps, condDeps };
cache.set(cacheKey, result);
return result;
};

View File

@ -1,14 +1,15 @@
import type { AppCore, DataSourceSchema, HttpOptions, RequestFunction } from '@tmagic/schema'; import type { default as TMagicApp } from '@tmagic/core';
import type { DataSourceSchema, HttpOptions, RequestFunction } from '@tmagic/schema';
import type DataSource from './data-sources/Base'; import type DataSource from './data-sources/Base';
import type HttpDataSource from './data-sources/Http'; import type HttpDataSource from './data-sources/Http';
import { ObservedData } from './observed-data/ObservedData'; import type { ObservedData } from './observed-data/ObservedData';
export type ObservedDataClass = new (...args: any[]) => ObservedData; export type ObservedDataClass = new (...args: any[]) => ObservedData;
export interface DataSourceOptions<T extends DataSourceSchema = DataSourceSchema> { export interface DataSourceOptions<T extends DataSourceSchema = DataSourceSchema> {
schema: T; schema: T;
app: AppCore; app: TMagicApp;
initialData?: Record<string, any>; initialData?: Record<string, any>;
useMock?: boolean; useMock?: boolean;
request?: RequestFunction; request?: RequestFunction;
@ -25,14 +26,14 @@ export interface HttpDataSourceSchema extends DataSourceSchema {
autoFetch?: boolean; autoFetch?: boolean;
beforeRequest: beforeRequest:
| string | string
| ((options: HttpOptions, content: { app: AppCore; dataSource: HttpDataSource }) => HttpOptions); | ((options: HttpOptions, content: { app: TMagicApp; dataSource: HttpDataSource }) => HttpOptions);
afterResponse: afterResponse:
| string | string
| ((response: any, content: { app: AppCore; dataSource: HttpDataSource; options: Partial<HttpOptions> }) => any); | ((response: any, content: { app: TMagicApp; dataSource: HttpDataSource; options: Partial<HttpOptions> }) => any);
} }
export interface DataSourceManagerOptions { export interface DataSourceManagerOptions {
app: AppCore; app: TMagicApp;
/** 初始化数据ssr数据可以由此传入 */ /** 初始化数据ssr数据可以由此传入 */
initialData?: DataSourceManagerData; initialData?: DataSourceManagerData;
/** 是否使用mock数据 */ /** 是否使用mock数据 */

View File

@ -1,12 +1,12 @@
import { cloneDeep, template } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { isDataSourceTemplate, isUseDataSourceField, Target, Watcher } from '@tmagic/dep';
import type { DepData, DisplayCond, DisplayCondItem, MApp, MNode, MPage, MPageFragment } from '@tmagic/schema'; import type { DepData, DisplayCond, DisplayCondItem, MApp, MNode, MPage, MPageFragment } from '@tmagic/schema';
import { NODE_CONDS_KEY } from '@tmagic/schema';
import { import {
compiledCond, compiledCond,
compiledNode, compiledNode,
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
DSL_NODE_KEY_COPY_PREFIX, dataSourceTemplateRegExp,
getValueByKeyPath, getValueByKeyPath,
isPage, isPage,
isPageFragment, isPageFragment,
@ -32,11 +32,15 @@ export const compiledCondition = (cond: DisplayCondItem[], data: DataSourceManag
break; break;
} }
const fieldValue = getValueByKeyPath(fields.join('.'), dsData); try {
const fieldValue = getValueByKeyPath(fields.join('.'), dsData);
if (!compiledCond(op, fieldValue, value, range)) { if (!compiledCond(op, fieldValue, value, range)) {
result = false; result = false;
break; break;
}
} catch (e) {
console.warn(e);
} }
} }
@ -49,10 +53,10 @@ export const compiledCondition = (cond: DisplayCondItem[], data: DataSourceManag
* @param data * @param data
* @returns boolean * @returns boolean
*/ */
export const compliedConditions = (node: { displayConds?: DisplayCond[] }, data: DataSourceManagerData) => { export const compliedConditions = (node: { [NODE_CONDS_KEY]?: DisplayCond[] }, data: DataSourceManagerData) => {
if (!node.displayConds || !Array.isArray(node.displayConds) || !node.displayConds.length) return true; if (!node[NODE_CONDS_KEY] || !Array.isArray(node[NODE_CONDS_KEY]) || !node[NODE_CONDS_KEY].length) return true;
for (const { cond } of node.displayConds) { for (const { cond } of node[NODE_CONDS_KEY]) {
if (!cond) continue; if (!cond) continue;
if (compiledCondition(cond, data)) { if (compiledCondition(cond, data)) {
@ -63,36 +67,6 @@ export const compliedConditions = (node: { displayConds?: DisplayCond[] }, data:
return false; return false;
}; };
/**
*
* @param displayConds
* @param data
* @returns boolean
*/
export const compliedIteratorItemConditions = (displayConds: DisplayCond[] = [], data: DataSourceManagerData) => {
if (!displayConds || !Array.isArray(displayConds) || !displayConds.length) return true;
for (const { cond } of displayConds) {
if (!cond) continue;
let result = true;
for (const { op, value, range, field } of cond) {
const fieldValue = getValueByKeyPath(field.join('.'), data);
if (!compiledCond(op, fieldValue, value, range)) {
result = false;
break;
}
}
if (result) {
return result;
}
}
return false;
};
export const updateNode = (node: MNode, dsl: MApp) => { export const updateNode = (node: MNode, dsl: MApp) => {
if (isPage(node) || isPageFragment(node)) { if (isPage(node) || isPageFragment(node)) {
const index = dsl.items?.findIndex((child: MNode) => child.id === node.id); const index = dsl.items?.findIndex((child: MNode) => child.id === node.id);
@ -115,16 +89,30 @@ export const createIteratorContentData = (
fields: string[] = [], fields: string[] = [],
dsData: DataSourceManagerData = {}, dsData: DataSourceManagerData = {},
) => { ) => {
const data = { const data: DataSourceManagerData = {
...dsData, ...dsData,
[dsId]: {}, [dsId]: {},
}; };
fields.reduce((obj: any, field, index) => { let rawData = cloneDeep(dsData[dsId]);
obj[field] = index === fields.length - 1 ? itemData : {}; let obj: Record<string, any> = data[dsId];
return obj[field]; fields.forEach((key, index) => {
}, data[dsId]); Object.assign(obj, rawData);
if (index === fields.length - 1) {
obj[key] = itemData;
return;
}
if (Array.isArray(rawData[key])) {
rawData[key] = {};
obj[key] = {};
}
rawData = rawData[key];
obj = obj[key];
});
return data; return data;
}; };
@ -149,12 +137,25 @@ export const compliedDataSourceField = (value: any, data: DataSourceManagerData)
if (!dsData) return value; if (!dsData) return value;
return getValueByKeyPath(fields.join('.'), dsData); try {
return getValueByKeyPath(fields.join('.'), dsData);
} catch (e) {
return value;
}
} }
return value; return value;
}; };
export const template = (value: string, data?: DataSourceManagerData) =>
value.replaceAll(dataSourceTemplateRegExp, (match, $1) => {
try {
return getValueByKeyPath($1, data);
} catch (e: any) {
return match;
}
});
/** /**
* tmagic-editor的数据源源选择器data-source-inputdata-source-selectdata-source-field-select * tmagic-editor的数据源源选择器data-source-inputdata-source-selectdata-source-field-select
* @param value dsl节点中的数据源配置 * @param value dsl节点中的数据源配置
@ -164,7 +165,7 @@ export const compliedDataSourceField = (value: any, data: DataSourceManagerData)
export const compiledNodeField = (value: any, data: DataSourceManagerData) => { export const compiledNodeField = (value: any, data: DataSourceManagerData) => {
// 使用data-source-input等表单控件配置的字符串模板`xxx${id.field}xxx` // 使用data-source-input等表单控件配置的字符串模板`xxx${id.field}xxx`
if (typeof value === 'string') { if (typeof value === 'string') {
return template(value)(data); return template(value, data);
} }
// 使用data-source-select等表单控件配置的数据源{ isBindDataSource: true, dataSourceId: 'xxx'} // 使用data-source-select等表单控件配置的数据源{ isBindDataSource: true, dataSourceId: 'xxx'}
@ -174,7 +175,7 @@ export const compiledNodeField = (value: any, data: DataSourceManagerData) => {
// 指定数据源的字符串模板,如:{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`} // 指定数据源的字符串模板,如:{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`}
if (value?.isBindDataSourceField && value.dataSourceId && typeof value.template === 'string') { if (value?.isBindDataSourceField && value.dataSourceId && typeof value.template === 'string') {
return template(value.template)(data[value.dataSourceId]); return template(value.template, data[value.dataSourceId]);
} }
// 使用data-source-field-select等表单控件的数据源字段[`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${id}`, 'field'] // 使用data-source-field-select等表单控件的数据源字段[`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}${id}`, 'field']
@ -185,105 +186,32 @@ export const compiledNodeField = (value: any, data: DataSourceManagerData) => {
return value; return value;
}; };
export const compliedIteratorItems = ( export const compliedIteratorItem = ({
itemData: any, compile,
items: MNode[],
dsId: string,
keys: string[] = [],
data: DataSourceManagerData,
inEditor = false,
) => {
const watcher = new Watcher();
watcher.addTarget(
new Target({
id: dsId,
type: 'data-source',
isTarget: (key: string | number, value: any) => {
if (`${key}`.startsWith(DSL_NODE_KEY_COPY_PREFIX)) {
return false;
}
return isDataSourceTemplate(value, dsId) || isUseDataSourceField(value, dsId);
},
}),
);
watcher.addTarget(
new Target({
id: dsId,
type: 'cond',
isTarget: (key, value) => {
// 使用data-source-field-select value: 'key' 可以配置出来
if (!Array.isArray(value) || value[0] !== dsId || !`${key}`.startsWith('displayConds')) return false;
return true;
},
}),
);
watcher.collect(items, {}, true);
const { deps } = watcher.getTarget(dsId, 'data-source');
const { deps: condDeps } = watcher.getTarget(dsId, 'cond');
if (!Object.keys(deps).length && !Object.keys(condDeps).length) {
return items;
}
return items.map((item) => compliedIteratorItem({ itemData, data, dsId, keys, inEditor, condDeps, item, deps }));
};
const compliedIteratorItem = ({
itemData,
data,
dsId, dsId,
keys,
inEditor,
condDeps,
item, item,
deps, deps,
}: { }: {
itemData: any; compile: (value: any) => any;
data: DataSourceManagerData;
dsId: string; dsId: string;
keys: string[];
inEditor: boolean;
condDeps: DepData;
item: MNode; item: MNode;
deps: DepData; deps: DepData;
}) => { }) => {
const { items, ...node } = item; const { items, ...node } = item;
const newNode = cloneDeep(node); const newNode = cloneDeep(node);
if (items && !item.iteratorData) {
newNode.items = Array.isArray(items)
? items.map((item) => compliedIteratorItem({ itemData, data, dsId, keys, inEditor, condDeps, item, deps }))
: items;
}
if (Array.isArray(items) && items.length) { if (Array.isArray(items) && items.length) {
if (item.iteratorData) { newNode.items = items.map((item) => compliedIteratorItem({ compile, dsId, item, deps }));
newNode.items = items; } else if (items) {
} else {
newNode.items = items.map((item) =>
compliedIteratorItem({ itemData, data, dsId, keys, inEditor, condDeps, item, deps }),
);
}
} else {
newNode.items = items; newNode.items = items;
} }
const ctxData = createIteratorContentData(itemData, dsId, keys, data);
if (condDeps[newNode.id]?.keys.length && !inEditor) {
newNode.condResult = compliedConditions(newNode, ctxData);
}
if (!deps[newNode.id]?.keys.length) { if (!deps[newNode.id]?.keys.length) {
return newNode; return newNode;
} }
return compiledNode( return compiledNode(
(value: any) => compiledNodeField(value, ctxData), compile,
newNode, newNode,
{ {
[dsId]: deps, [dsId]: deps,

View File

@ -1,5 +1,7 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import App from '@tmagic/core';
import { DataSource } from '@data-source/index'; import { DataSource } from '@data-source/index';
describe('DataSource', () => { describe('DataSource', () => {
@ -10,8 +12,9 @@ describe('DataSource', () => {
id: '1', id: '1',
fields: [{ name: 'name' }], fields: [{ name: 'name' }],
methods: [], methods: [],
events: [],
}, },
app: {}, app: new App({}),
}); });
expect(ds).toBeInstanceOf(DataSource); expect(ds).toBeInstanceOf(DataSource);
@ -25,8 +28,9 @@ describe('DataSource', () => {
id: '1', id: '1',
fields: [{ name: 'name' }], fields: [{ name: 'name' }],
methods: [], methods: [],
events: [],
}, },
app: {}, app: new App({}),
}); });
ds.init(); ds.init();
@ -43,8 +47,9 @@ describe('DataSource setData', () => {
id: '1', id: '1',
fields: [{ name: 'name', defaultValue: 'name' }], fields: [{ name: 'name', defaultValue: 'name' }],
methods: [], methods: [],
events: [],
}, },
app: {}, app: new App({}),
}); });
ds.init(); ds.init();
@ -74,8 +79,9 @@ describe('DataSource setData', () => {
}, },
], ],
methods: [], methods: [],
events: [],
}, },
app: {}, app: new App({}),
}); });
ds.init(); ds.init();

View File

@ -1,18 +1,11 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import { type MApp, NodeType } from '@tmagic/schema'; import App from '@tmagic/core';
import { NodeType } from '@tmagic/schema';
import { DataSource, DataSourceManager } from '@data-source/index'; import { DataSource, DataSourceManager } from '@data-source/index';
class Core { const app = new App({
public dsl?: MApp;
constructor(options: any) {
this.dsl = options.config;
}
}
const app = new Core({
config: { config: {
type: NodeType.ROOT, type: NodeType.ROOT,
id: '1', id: '1',
@ -23,12 +16,14 @@ const app = new Core({
id: '1', id: '1',
fields: [{ name: 'name' }], fields: [{ name: 'name' }],
methods: [], methods: [],
events: [],
}, },
{ {
type: 'http', type: 'http',
id: '2', id: '2',
fields: [{ name: 'name' }], fields: [{ name: 'name' }],
methods: [], methods: [],
events: [],
}, },
], ],
}, },
@ -45,10 +40,10 @@ describe('DataSourceManager', () => {
expect(dsm.dataSourceMap.get('2')?.type).toBe('http'); expect(dsm.dataSourceMap.get('2')?.type).toBe('http');
}); });
test('registe', () => { test('register', () => {
class TestDataSource extends DataSource {} class TestDataSource extends DataSource {}
DataSourceManager.registe('test', TestDataSource as any); DataSourceManager.register('test', TestDataSource as any);
expect(DataSourceManager.getDataSourceClass('test')).toBe(TestDataSource); expect(DataSourceManager.getDataSourceClass('test')).toBe(TestDataSource);
}); });
@ -72,6 +67,7 @@ describe('DataSourceManager', () => {
id: '1', id: '1',
fields: [{ name: 'name1' }], fields: [{ name: 'name1' }],
methods: [], methods: [],
events: [],
}, },
]); ]);
const ds = dsm.get('1'); const ds = dsm.get('1');
@ -89,6 +85,7 @@ describe('DataSourceManager', () => {
id: '1', id: '1',
fields: [{ name: 'name' }], fields: [{ name: 'name' }],
methods: [], methods: [],
events: [],
}); });
expect(dsm.get('1')).toBeInstanceOf(DataSource); expect(dsm.get('1')).toBeInstanceOf(DataSource);
}); });

View File

@ -0,0 +1,68 @@
import { describe, expect, test } from 'vitest';
import { compiledCondition, createIteratorContentData, template } from '@data-source/utils';
describe('compiledCondition', () => {
test('=,true', () => {
const result = compiledCondition(
[
{
field: ['a', 'b'],
op: '=',
value: 1,
},
],
{ a: { b: 1 } },
);
expect(result).toBeTruthy();
});
test('=,false', () => {
const result = compiledCondition(
[
{
field: ['a', 'b'],
op: '=',
value: 2,
},
],
{ a: { b: 1 } },
);
expect(result).toBeFalsy();
});
});
describe('template', () => {
test('template', () => {
const value = template('xxx${aa.bb}123${aa1.bb1}dsf', { aa: { bb: 1 }, aa1: { bb1: 2 } });
expect(value).toBe('xxx11232dsf');
});
});
describe('createIteratorContentData', () => {
test('createIteratorContentData', () => {
const ctxData: any = createIteratorContentData({ b: 1 }, 'ds', ['a'], { ds: { a: [{ b: 1 }] } });
expect(ctxData.ds.a.b).toBe(1);
});
test('混用', () => {
const ctxData: any = createIteratorContentData({ b: 1 }, 'ds', ['a'], { ds: { a: [{ b: 1 }], b: 2 } });
expect(ctxData.ds.b).toBe(2);
});
test('二维数组', () => {
const ctxData: any = createIteratorContentData({ a: 1 }, 'ds', ['a', 'c'], {
ds: {
a: [
{
b: 0,
c: [{ a: 1 }],
},
],
b: 2,
},
});
expect(ctxData.ds.a.c.a).toBe(1);
});
});

View File

@ -6,8 +6,14 @@ import {
type HookData, type HookData,
HookType, HookType,
type Id, type Id,
NODE_CONDS_KEY,
} from '@tmagic/schema'; } from '@tmagic/schema';
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils'; import {
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
dataSourceTemplateRegExp,
getKeysArray,
isObject,
} from '@tmagic/utils';
import Target from './Target'; import Target from './Target';
import { DepTargetType, type TargetList } from './types'; import { DepTargetType, type TargetList } from './types';
@ -40,33 +46,67 @@ export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent, initi
* @returns boolean * @returns boolean
*/ */
export const isIncludeArrayField = (keys: string[], fields: DataSchema[]) => { export const isIncludeArrayField = (keys: string[], fields: DataSchema[]) => {
let includeArray = false; let f = fields;
keys.reduce((accumulator: DataSchema[], currentValue: string, currentIndex: number) => { return keys.some((key, index) => {
const field = accumulator.find(({ name }) => name === currentValue); const field = f.find(({ name }) => name === key);
if (
f = field?.fields || [];
// 字段类型为数组并且后面没有数字索引
return (
field && field &&
field.type === 'array' && field.type === 'array' &&
// 不是整数 // 不是整数
/^(?!\d+$).*$/.test(`${keys[currentIndex + 1]}`) && /^(?!\d+$).*$/.test(`${keys[index + 1]}`) &&
currentIndex < keys.length - 1 index < keys.length - 1
) { );
includeArray = true; });
}
return field?.fields || [];
}, fields);
return includeArray;
}; };
/** /**
* (value)使Id(dsId)`xxx${dsId.field}xxx${dsId.field}` * (value)使Id(dsId)`xxx${dsId.field}xxx${dsId.field}`
* @param value any * @param value any
* @param dsId string | number * @param dsId string | number
* @param hasArray boolean true: ; false: ;
* @returns boolean * @returns boolean
*/ */
export const isDataSourceTemplate = (value: any, dsId: string | number) => export const isDataSourceTemplate = (value: any, ds: Pick<DataSourceSchema, 'id' | 'fields'>, hasArray = false) => {
typeof value === 'string' && value.includes(`${dsId}`) && /\$\{([\s\S]+?)\}/.test(value); // 模板中可能会存在多个表达式,将表达式从模板中提取出来
const templates: string[] = value.match(dataSourceTemplateRegExp) || [];
if (templates.length <= 0) {
return false;
}
const arrayFieldTemplates = [];
const fieldTemplates = [];
templates.forEach((tpl) => {
// 将${dsId.xxxx} 转成 dsId.xxxx
const expression = tpl.substring(2, tpl.length - 1);
const keys = getKeysArray(expression);
const dsId = keys.shift();
if (!dsId || dsId !== ds.id) {
return;
}
// ${dsId.array} ${dsId.array[0]} ${dsId.array[0].a} 这种是依赖
// ${dsId.array.a} 这种不是依赖,这种需要再迭代器容器中的组件才能使用,依赖由迭代器处理
if (isIncludeArrayField(keys, ds.fields)) {
arrayFieldTemplates.push(tpl);
} else {
fieldTemplates.push(tpl);
}
});
if (hasArray) {
return arrayFieldTemplates.length > 0;
}
return fieldTemplates.length > 0;
};
/** /**
* ,{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`} * ,{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`}
@ -83,11 +123,11 @@ export const isSpecificDataSourceTemplate = (value: any, dsId: string | number)
/** /**
* [+ID, ] * [+ID, ]
* 使data-source-field-select value: 'value' * 使data-source-field-select value: 'value'
* @param value any * @param value any[]
* @param id string | number * @param id string | number
* @returns boolean * @returns boolean
*/ */
export const isUseDataSourceField = (value: any, id: string | number) => { export const isUseDataSourceField = (value: any[], id: string | number) => {
if (!Array.isArray(value) || typeof value[0] !== 'string') { if (!Array.isArray(value) || typeof value[0] !== 'string') {
return false; return false;
} }
@ -107,20 +147,21 @@ export const isUseDataSourceField = (value: any, id: string | number) => {
/** /**
* ${dsId.array.a} * ${dsId.array.a}
* @param value any * @param value any
* @param ds DataSourceSchema * @param ds Pick<DataSourceSchema, 'id' | 'fields'>
* @returns boolean * @returns boolean
*/ */
export const isDataSourceTemplateNotIncludeArrayField = (value: string, ds: DataSourceSchema): boolean => { export const isDataSourceTemplateNotIncludeArrayField = (
value: string,
ds: Pick<DataSourceSchema, 'id' | 'fields'>,
): boolean => {
// 模板中可能会存在多个表达式,将表达式从模板中提取出来 // 模板中可能会存在多个表达式,将表达式从模板中提取出来
const templates = value.match(/\$\{([\s\S]+?)\}/g) || []; const templates = value.match(dataSourceTemplateRegExp) || [];
let result = false;
for (const tpl of templates) { for (const tpl of templates) {
const keys = tpl // 将${dsId.xxxx} 转成 dsId.xxxx
// 将${dsId.xxxx} 转成 dsId.xxxx const expression = tpl.substring(2, tpl.length - 1);
.substring(2, tpl.length - 1) const keys = getKeysArray(expression);
// 将 array[0] 转成 array.0
.replaceAll(/\[(\d+)\]/g, '.$1')
.split('.');
const dsId = keys.shift(); const dsId = keys.shift();
if (!dsId || dsId !== ds.id) { if (!dsId || dsId !== ds.id) {
@ -132,62 +173,108 @@ export const isDataSourceTemplateNotIncludeArrayField = (value: string, ds: Data
if (isIncludeArrayField(keys, ds.fields)) { if (isIncludeArrayField(keys, ds.fields)) {
return false; return false;
} }
result = true;
} }
return true; return result;
}; };
export const createDataSourceTarget = (ds: DataSourceSchema, initialDeps: DepData = {}) => export const isDataSourceTarget = (
ds: Pick<DataSourceSchema, 'id' | 'fields'>,
key: string | number,
value: any,
hasArray = false,
) => {
if (`${key}`.startsWith(NODE_CONDS_KEY)) {
return false;
}
// 或者在模板在使用数据源
if (typeof value === 'string') {
return isDataSourceTemplate(value, ds, hasArray);
}
// 关联数据源对象,如:{ isBindDataSource: true, dataSourceId: 'xxx'}
// 使用data-source-select value: 'value' 可以配置出来
if (isObject(value) && value?.isBindDataSource && value.dataSourceId && value.dataSourceId === ds.id) {
return true;
}
if (isSpecificDataSourceTemplate(value, ds.id)) {
return true;
}
if (isUseDataSourceField(value, ds.id)) {
const [, ...keys] = value;
const includeArray = isIncludeArrayField(keys, ds.fields);
if (hasArray) {
return includeArray;
}
return !includeArray;
}
return false;
};
export const isDataSourceCondTarget = (
ds: Pick<DataSourceSchema, 'id' | 'fields'>,
key: string | number,
value: any,
hasArray = false,
) => {
if (!Array.isArray(value) || !ds) {
return false;
}
const [dsId, ...keys] = value;
// 使用data-source-field-select value: 'key' 可以配置出来
if (dsId !== ds.id || !`${key}`.startsWith(NODE_CONDS_KEY)) {
return false;
}
if (ds.fields?.find((field) => field.name === keys[0])) {
const includeArray = isIncludeArrayField(keys, ds.fields);
if (hasArray) {
return includeArray;
}
return true;
}
return false;
};
export const createDataSourceTarget = (ds: Pick<DataSourceSchema, 'id' | 'fields'>, initialDeps: DepData = {}) =>
new Target({ new Target({
type: DepTargetType.DATA_SOURCE, type: DepTargetType.DATA_SOURCE,
id: ds.id, id: ds.id,
initialDeps, initialDeps,
isTarget: (key: string | number, value: any) => { isTarget: (key: string | number, value: any) => isDataSourceTarget(ds, key, value),
// 关联数据源对象,如:{ isBindDataSource: true, dataSourceId: 'xxx'}
// 使用data-source-select value: 'value' 可以配置出来
if (value?.isBindDataSource && value.dataSourceId && value.dataSourceId === ds.id) {
return true;
}
// 或者在模板在使用数据源
if (isDataSourceTemplate(value, ds.id)) {
return isDataSourceTemplateNotIncludeArrayField(value, ds);
}
if (isSpecificDataSourceTemplate(value, ds.id)) {
return true;
}
if (isUseDataSourceField(value, ds.id)) {
const [, ...keys] = value;
return !isIncludeArrayField(keys, ds.fields);
}
return false;
},
}); });
export const createDataSourceCondTarget = (ds: DataSourceSchema, initialDeps: DepData = {}) => export const createDataSourceCondTarget = (ds: Pick<DataSourceSchema, 'id' | 'fields'>, initialDeps: DepData = {}) =>
new Target({ new Target({
type: DepTargetType.DATA_SOURCE_COND, type: DepTargetType.DATA_SOURCE_COND,
id: ds.id, id: ds.id,
initialDeps, initialDeps,
isTarget: (key: string | number, value: any) => { isTarget: (key: string | number, value: any) => isDataSourceCondTarget(ds, key, value),
// 使用data-source-field-select value: 'key' 可以配置出来
if (!Array.isArray(value) || value[0] !== ds.id || !`${key}`.startsWith('displayConds')) return false;
return Boolean(ds?.fields?.find((field) => field.name === value[1]));
},
}); });
export const createDataSourceMethodTarget = (ds: DataSourceSchema, initialDeps: DepData = {}) => export const createDataSourceMethodTarget = (ds: Pick<DataSourceSchema, 'id' | 'fields'>, initialDeps: DepData = {}) =>
new Target({ new Target({
type: DepTargetType.DATA_SOURCE_METHOD, type: DepTargetType.DATA_SOURCE_METHOD,
id: ds.id, id: ds.id,
initialDeps, initialDeps,
isTarget: (key: string | number, value: any) => { isTarget: (key: string | number, value: any) => {
// 使用data-source-method-select 可以配置出来 // 使用data-source-method-select 可以配置出来
if (!Array.isArray(value) || value[0] !== ds.id) return false; if (!Array.isArray(value) || !ds) {
return false;
}
const [dsId, ...keys] = value;
if (dsId !== ds.id || ds.fields?.find((field) => field.name === keys[0])) {
return false;
}
return true; return true;
}, },

View File

@ -70,11 +70,11 @@ describe('utils', () => {
}); });
test('isDataSourceTemplate', () => { test('isDataSourceTemplate', () => {
expect(utils.isDataSourceTemplate('xxx${dsId.field}xxx${dsId.field}', 'dsId')).toBeTruthy(); expect(utils.isDataSourceTemplate('xxx${dsId.field}xxx${dsId.field}', { id: 'dsId', fields: [] })).toBeTruthy();
expect(utils.isDataSourceTemplate('${dsId.field}', 'dsId')).toBeTruthy(); expect(utils.isDataSourceTemplate('${dsId.field}', { id: 'dsId', fields: [] })).toBeTruthy();
expect(utils.isDataSourceTemplate('${dsId}', 'dsId')).toBeTruthy(); expect(utils.isDataSourceTemplate('${dsId}', { id: 'dsId', fields: [] })).toBeTruthy();
expect(utils.isDataSourceTemplate('${dsId.field}', 'dsId1')).toBeFalsy(); expect(utils.isDataSourceTemplate('${dsId.field}', { id: 'dsId1', fields: [] })).toBeFalsy();
expect(utils.isDataSourceTemplate('${dsId.field', 'dsId')).toBeFalsy(); expect(utils.isDataSourceTemplate('${dsId.field', { id: 'dsId', fields: [] })).toBeFalsy();
}); });
test('isSpecificDataSourceTemplate', () => { test('isSpecificDataSourceTemplate', () => {
@ -139,6 +139,7 @@ describe('utils', () => {
id: 'dsId', id: 'dsId',
methods: [], methods: [],
fields: arrayFields, fields: arrayFields,
events: [],
}), }),
).toBeTruthy(); ).toBeTruthy();
@ -148,6 +149,7 @@ describe('utils', () => {
id: 'dsId', id: 'dsId',
methods: [], methods: [],
fields: [...arrayFields, ...objectFields], fields: [...arrayFields, ...objectFields],
events: [],
}), }),
).toBeTruthy(); ).toBeTruthy();
@ -157,6 +159,7 @@ describe('utils', () => {
id: 'dsId', id: 'dsId',
methods: [], methods: [],
fields: [...arrayFields, ...objectFields], fields: [...arrayFields, ...objectFields],
events: [],
}), }),
).toBeFalsy(); ).toBeFalsy();
@ -166,6 +169,7 @@ describe('utils', () => {
id: 'dsId', id: 'dsId',
methods: [], methods: [],
fields: arrayFields, fields: arrayFields,
events: [],
}), }),
).toBeFalsy(); ).toBeFalsy();
@ -175,6 +179,7 @@ describe('utils', () => {
id: 'dsId', id: 'dsId',
methods: [], methods: [],
fields: arrayFields, fields: arrayFields,
events: [],
}), }),
).toBeFalsy(); ).toBeFalsy();
@ -184,6 +189,7 @@ describe('utils', () => {
id: 'dsId', id: 'dsId',
methods: [], methods: [],
fields: arrayFields, fields: arrayFields,
events: [],
}), }),
).toBeTruthy(); ).toBeTruthy();
}); });

View File

@ -54,7 +54,7 @@ import { Coin } from '@element-plus/icons-vue';
import { getConfig, TMagicAutocomplete, TMagicTag } from '@tmagic/design'; import { getConfig, TMagicAutocomplete, TMagicTag } from '@tmagic/design';
import type { FieldProps, FormItem } from '@tmagic/form'; import type { FieldProps, FormItem } from '@tmagic/form';
import type { DataSchema, DataSourceSchema } from '@tmagic/schema'; import type { DataSchema, DataSourceSchema } from '@tmagic/schema';
import { isNumber } from '@tmagic/utils'; import { getKeysArray, isNumber } from '@tmagic/utils';
import Icon from '@editor/components/Icon.vue'; import Icon from '@editor/components/Icon.vue';
import type { Services } from '@editor/type'; import type { Services } from '@editor/type';
@ -218,7 +218,7 @@ const fieldQuerySearch = (
const dsKey = queryString.substring(leftAngleIndex + 1, dotIndex); const dsKey = queryString.substring(leftAngleIndex + 1, dotIndex);
// xx.xx.xx // xx.xx.xx
const keys = dsKey.replaceAll(/\[(\d+)\]/g, '.$1').split('.'); const keys = getKeysArray(dsKey);
// id // id
const dsId = keys.shift(); const dsId = keys.shift();

View File

@ -192,6 +192,54 @@ export const initServiceEvents = (
((event: 'update:modelValue', value: MApp | null) => void), ((event: 'update:modelValue', value: MApp | null) => void),
{ editorService, codeBlockService, dataSourceService, depService }: Services, { editorService, codeBlockService, dataSourceService, depService }: Services,
) => { ) => {
const rootChangeHandler = async (value: MApp | null, preValue?: MApp | null) => {
if (!value) return;
value.codeBlocks = value.codeBlocks || {};
value.dataSources = value.dataSources || [];
codeBlockService.setCodeDsl(value.codeBlocks);
dataSourceService.set('dataSources', value.dataSources);
depService.removeTargets(DepTargetType.CODE_BLOCK);
Object.entries(value.codeBlocks).forEach(([id, code]) => {
depService.addTarget(createCodeBlockTarget(id, code));
});
dataSourceService.get('dataSources').forEach((ds) => {
initDataSourceDepTarget(ds);
});
if (Array.isArray(value.items)) {
collectIdle(value.items, true);
} else {
depService.clear();
delete value.dataSourceDeps;
delete value.dataSourceCondDeps;
}
const nodeId = editorService.get('node')?.id || props.defaultSelected;
let node;
if (nodeId) {
node = editorService.getNodeById(nodeId);
}
if (node && node !== value) {
await editorService.select(node.id);
} else if (value.items?.length) {
await editorService.select(value.items[0]);
} else if (value.id) {
editorService.set('nodes', [value]);
editorService.set('parent', null);
editorService.set('page', null);
}
if (toRaw(value) !== toRaw(preValue)) {
emit('update:modelValue', value);
}
};
const getApp = () => { const getApp = () => {
const stage = editorService.get('stage'); const stage = editorService.get('stage');
return stage?.renderer.runtime?.getApp?.(); return stage?.renderer.runtime?.getApp?.();
@ -292,55 +340,6 @@ export const initServiceEvents = (
depService.addTarget(createDataSourceCondTarget(ds, reactive({}))); depService.addTarget(createDataSourceCondTarget(ds, reactive({})));
}; };
const rootChangeHandler = async (value: MApp | null, preValue?: MApp | null) => {
if (!value) return;
value.codeBlocks = value.codeBlocks || {};
value.dataSources = value.dataSources || [];
codeBlockService.setCodeDsl(value.codeBlocks);
dataSourceService.set('dataSources', value.dataSources);
depService.removeTargets(DepTargetType.CODE_BLOCK);
Object.entries(value.codeBlocks).forEach(([id, code]) => {
depService.addTarget(createCodeBlockTarget(id, code));
});
dataSourceService.get('dataSources').forEach((ds) => {
initDataSourceDepTarget(ds);
});
if (Array.isArray(value.items)) {
value.items.forEach((page) => {
depService.collectIdle([page], { pageId: page.id }, true);
});
} else {
depService.clear();
delete value.dataSourceDeps;
}
const nodeId = editorService.get('node')?.id || props.defaultSelected;
let node;
if (nodeId) {
node = editorService.getNodeById(nodeId);
}
if (node && node !== value) {
await editorService.select(node.id);
} else if (value.items?.length) {
await editorService.select(value.items[0]);
} else if (value.id) {
editorService.set('nodes', [value]);
editorService.set('parent', null);
editorService.set('page', null);
}
if (toRaw(value) !== toRaw(preValue)) {
emit('update:modelValue', value);
}
};
const collectIdle = (nodes: MNode[], deep: boolean) => { const collectIdle = (nodes: MNode[], deep: boolean) => {
nodes.forEach((node) => { nodes.forEach((node) => {
let pageId: Id | undefined; let pageId: Id | undefined;
@ -372,7 +371,7 @@ export const initServiceEvents = (
// 由于历史记录变化是更新整个page所以历史记录变化时需要重新收集依赖 // 由于历史记录变化是更新整个page所以历史记录变化时需要重新收集依赖
const historyChangeHandler = (page: MPage | MPageFragment) => { const historyChangeHandler = (page: MPage | MPageFragment) => {
depService.collectIdle([page], { pageId: page.id }, true); collectIdle([page], true);
}; };
editorService.on('history-change', historyChangeHandler); editorService.on('history-change', historyChangeHandler);
@ -407,9 +406,7 @@ export const initServiceEvents = (
removeDataSourceTarget(config.id); removeDataSourceTarget(config.id);
initDataSourceDepTarget(config); initDataSourceDepTarget(config);
(root?.items || []).forEach((page) => { collectIdle(root?.items || [], true);
depService.collectIdle([page], { pageId: page.id }, true);
});
}; };
const removeDataSourceTarget = (id: string) => { const removeDataSourceTarget = (id: string) => {

View File

@ -1,6 +1,11 @@
import { CascaderOption, FormConfig, FormState } from '@tmagic/form'; import { CascaderOption, FormConfig, FormState } from '@tmagic/form';
import { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/schema'; import { DataSchema, DataSourceFieldType, DataSourceSchema } from '@tmagic/schema';
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, isNumber } from '@tmagic/utils'; import {
DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX,
dataSourceTemplateRegExp,
getKeysArray,
isNumber,
} from '@tmagic/utils';
import BaseFormConfig from './formConfigs/base'; import BaseFormConfig from './formConfigs/base';
import HttpFormConfig from './formConfigs/http'; import HttpFormConfig from './formConfigs/http';
@ -118,7 +123,7 @@ export const getFormValue = (type: string, values: Partial<DataSourceSchema>): P
* context * context
* *
* interface Content { * interface Content {
* app: AppCore; * app: TMagicApp;
* dataSource: HttpDataSource; * dataSource: HttpDataSource;
* } * }
* *
@ -135,7 +140,7 @@ export const getFormValue = (type: string, values: Partial<DataSourceSchema>): P
* context * context
* *
* interface Content { * interface Content {
* app: AppCore; * app: TMagicApp;
* dataSource: HttpDataSource; * dataSource: HttpDataSource;
* } * }
* *
@ -152,7 +157,7 @@ export const getDisplayField = (dataSources: DataSourceSchema[], key: string) =>
const displayState: { value: string; type: 'var' | 'text' }[] = []; const displayState: { value: string; type: 'var' | 'text' }[] = [];
// 匹配es6字符串模块 // 匹配es6字符串模块
const matches = key.matchAll(/\$\{([\s\S]+?)\}/g); const matches = key.matchAll(dataSourceTemplateRegExp);
let index = 0; let index = 0;
for (const match of matches) { for (const match of matches) {
if (typeof match.index === 'undefined') break; if (typeof match.index === 'undefined') break;
@ -167,25 +172,22 @@ export const getDisplayField = (dataSources: DataSourceSchema[], key: string) =>
let ds: DataSourceSchema | undefined; let ds: DataSourceSchema | undefined;
let fields: DataSchema[] | undefined; let fields: DataSchema[] | undefined;
// 将模块解析成数据源对应的值 // 将模块解析成数据源对应的值
match[1] getKeysArray(match[1]).forEach((item, index) => {
.replaceAll(/\[(\d+)\]/g, '.$1') if (index === 0) {
.split('.') ds = dataSources.find((ds) => ds.id === item);
.forEach((item, index) => { dsText += ds?.title || item;
if (index === 0) { fields = ds?.fields;
ds = dataSources.find((ds) => ds.id === item); return;
dsText += ds?.title || item; }
fields = ds?.fields;
return;
}
if (isNumber(item)) { if (isNumber(item)) {
dsText += `[${item}]`; dsText += `[${item}]`;
} else { } else {
const field = fields?.find((field) => field.name === item); const field = fields?.find((field) => field.name === item);
fields = field?.fields; fields = field?.fields;
dsText += `.${field?.title || item}`; dsText += `.${field?.title || item}`;
} }
}); });
displayState.push({ displayState.push({
type: 'var', type: 'var',

View File

@ -18,6 +18,7 @@
*/ */
import type { FormConfig, FormState, TabPaneConfig } from '@tmagic/form'; import type { FormConfig, FormState, TabPaneConfig } from '@tmagic/form';
import { NODE_CONDS_KEY } from '@tmagic/schema';
export const arrayOptions = [ export const arrayOptions = [
{ text: '包含', value: 'include' }, { text: '包含', value: 'include' },
@ -359,7 +360,7 @@ export const displayTabConfig: TabPaneConfig = {
items: [ items: [
{ {
type: 'display-conds', type: 'display-conds',
name: 'displayConds', name: NODE_CONDS_KEY,
titlePrefix: '条件组', titlePrefix: '条件组',
defaultValue: [], defaultValue: [],
}, },

View File

@ -38,6 +38,7 @@
"vite": "^5.3.5" "vite": "^5.3.5"
}, },
"peerDependencies": { "peerDependencies": {
"@types/events": "^3.0.0",
"typescript": "*" "typescript": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {

View File

@ -35,18 +35,6 @@ export type RequestFunction = <T = any>(options: HttpOptions) => Promise<T>;
export type JsEngine = 'browser' | 'hippy' | 'nodejs'; export type JsEngine = 'browser' | 'hippy' | 'nodejs';
export interface AppCore {
/** 页面配置描述 */
dsl?: MApp;
/** 允许平台editor: 编辑器中mobile: 手机端tv: 电视端, pc: 电脑端 */
platform?: 'editor' | 'mobile' | 'tv' | 'pc' | string;
/** 代码运行环境 */
jsEngine?: JsEngine | string;
/** 网络请求函数 */
request?: RequestFunction;
[key: string]: any;
}
export enum NodeType { export enum NodeType {
/** 容器 */ /** 容器 */
CONTAINER = 'container', CONTAINER = 'container',
@ -58,6 +46,8 @@ export enum NodeType {
PAGE_FRAGMENT = 'page-fragment', PAGE_FRAGMENT = 'page-fragment',
} }
export const NODE_CONDS_KEY = 'displayConds';
export type Id = string | number; export type Id = string | number;
// 事件联动的动作类型 // 事件联动的动作类型
@ -97,7 +87,7 @@ export interface CodeItemConfig {
/** 代码ID */ /** 代码ID */
codeId: Id; codeId: Id;
/** 代码参数 */ /** 代码参数 */
params?: object; params?: Record<string, any>;
} }
export interface CompItemConfig { export interface CompItemConfig {
@ -139,7 +129,7 @@ export interface MComponent {
style?: { style?: {
[key: string]: any; [key: string]: any;
}; };
displayConds?: DisplayCond[]; [NODE_CONDS_KEY]?: DisplayCond[];
[key: string]: any; [key: string]: any;
} }
@ -150,6 +140,17 @@ export interface MContainer extends MComponent {
items: (MComponent | MContainer)[]; items: (MComponent | MContainer)[];
} }
export interface MIteratorContainer extends MContainer {
type: 'iterator-container';
iteratorData: any[];
dsField: string[];
itemConfig: {
layout: string;
[NODE_CONDS_KEY]: DisplayCond[];
style: Record<string, string | number>;
};
}
export interface MPage extends MContainer { export interface MPage extends MContainer {
/** 页面类型 */ /** 页面类型 */
type: NodeType.PAGE; type: NodeType.PAGE;
@ -203,7 +204,7 @@ export interface PastePosition {
top?: number; top?: number;
} }
export type MNode = MComponent | MContainer | MPage | MApp | MPageFragment; export type MNode = MComponent | MContainer | MIteratorContainer | MPage | MApp | MPageFragment;
export enum HookType { export enum HookType {
/** 代码块钩子标识 */ /** 代码块钩子标识 */

View File

@ -38,7 +38,8 @@
"keycon": "^1.4.0", "keycon": "^1.4.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"moveable": "^0.53.0", "moveable": "^0.53.0",
"moveable-helper": "^0.4.0" "moveable-helper": "^0.4.0",
"scenejs": "^1.10.3"
}, },
"devDependencies": { "devDependencies": {
"@types/events": "^3.0.0", "@types/events": "^3.0.0",

View File

@ -33,7 +33,7 @@
"@tmagic/core": "workspace:*", "@tmagic/core": "workspace:*",
"@tmagic/schema": "workspace:*", "@tmagic/schema": "workspace:*",
"@tmagic/utils": "workspace:*", "@tmagic/utils": "workspace:*",
"@tmagic/vue-runtime-help": ">=0.0.7", "@tmagic/vue-runtime-help": "workspace:*",
"vue": ">=3.4.27", "vue": ">=3.4.27",
"typescript": "*" "typescript": "*"
}, },

View File

@ -1,12 +1,22 @@
<template> <template>
<div v-if="display(config)" :id="`${config.id}`" :class="className" :style="app?.transformStyle(config.style || {})"> <div
v-if="display(config)"
:id="`${config.id}`"
:data-iterator-index="iteratorIndex"
:data-iterator-container-id="iteratorContainerId"
:class="className"
:style="app?.transformStyle(config.style || {})"
>
<slot> <slot>
<template v-for="item in config.items"> <template v-for="(item, index) in config.items">
<component <component
v-if="display(item)" v-if="display(item)"
:key="item.id" :key="item.id"
:is="`magic-ui-${toLine(item.type)}`" :is="`magic-ui-${toLine(item.type)}`"
:id="item.id" :id="item.id"
:data-container-index="index"
:data-iterator-index="iteratorIndex"
:data-iterator-container-id="iteratorContainerId"
:class="`${item.className || ''}`" :class="`${item.className || ''}`"
:style="app?.transformStyle(item.style || {})" :style="app?.transformStyle(item.style || {})"
:config="{ ...item, [IS_DSL_NODE_KEY]: true }" :config="{ ...item, [IS_DSL_NODE_KEY]: true }"
@ -19,13 +29,21 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import type { MContainer, UiComponentProps } from '@tmagic/schema'; import type { Id, MContainer, UiComponentProps } from '@tmagic/schema';
import { IS_DSL_NODE_KEY, toLine } from '@tmagic/utils'; import { IS_DSL_NODE_KEY, toLine } from '@tmagic/utils';
import { useApp } from '@tmagic/vue-runtime-help'; import { useApp } from '@tmagic/vue-runtime-help';
const props = withDefaults(defineProps<UiComponentProps<MContainer>>(), { const props = withDefaults(
model: () => ({}), defineProps<
}); UiComponentProps<MContainer> & {
iteratorIndex?: number[];
iteratorContainerId?: Id[];
}
>(),
{
model: () => ({}),
},
);
const { display, app } = useApp({ const { display, app } = useApp({
config: props.config, config: props.config,

View File

@ -1,30 +1,34 @@
<template> <template>
<div class="magic-ui-iterator-container"> <div
<Container v-for="(item, index) in configs" :key="index" :config="item"></Container> class="magic-ui-iterator-container"
:data-iterator-index="dataIteratorIndex"
:data-iterator-container-id="dataIteratorContainerId"
>
<Container
v-for="(item, index) in configs"
:iterator-index="[...(dataIteratorIndex || []), index]"
:iterator-container-id="[...(dataIteratorContainerId || []), config.id]"
:key="index"
:config="item"
></Container>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed, watch } from 'vue';
import { type DisplayCond, type MContainer, NodeType } from '@tmagic/schema'; import type { IteratorContainer as TMagicIteratorContainer } from '@tmagic/core';
import { type Id, type MIteratorContainer, NodeType } from '@tmagic/schema';
import { useApp } from '@tmagic/vue-runtime-help'; import { useApp } from '@tmagic/vue-runtime-help';
import Container from '../../container'; import Container from '../../container';
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
config: MContainer & { config: MIteratorContainer;
type: 'iterator-container';
iteratorData: any[];
dsField: string[];
itemConfig: {
layout: string;
displayConds: DisplayCond[];
style: Record<string, string | number>;
};
};
model?: any; model?: any;
dataIteratorIndex?: number[];
dataIteratorContainerId?: Id[];
}>(), }>(),
{ {
model: () => ({}), model: () => ({}),
@ -33,11 +37,14 @@ const props = withDefaults(
const { app } = useApp({ const { app } = useApp({
config: props.config, config: props.config,
iteratorContainerId: props.dataIteratorContainerId,
iteratorIndex: props.dataIteratorIndex,
methods: {}, methods: {},
}); });
const configs = computed(() => { const configs = computed(() => {
let { iteratorData = [] } = props.config; let { iteratorData = [] } = props.config;
const { id, itemConfig, dsField, items } = props.config;
if (!Array.isArray(iteratorData)) { if (!Array.isArray(iteratorData)) {
iteratorData = []; iteratorData = [];
@ -50,18 +57,26 @@ const configs = computed(() => {
return iteratorData.map((itemData) => { return iteratorData.map((itemData) => {
const condResult = const condResult =
app?.platform !== 'editor' app?.platform !== 'editor'
? app?.dataSourceManager?.compliedIteratorItemConds(itemData, props.config.itemConfig.displayConds) ?? true ? app?.dataSourceManager?.compliedIteratorItemConds(itemData, itemConfig, dsField) ?? true
: true; : true;
const newItems =
app?.dataSourceManager?.compliedIteratorItems(
id,
itemData,
items,
dsField,
props.dataIteratorContainerId,
props.dataIteratorIndex,
) ?? items;
return { return {
items: items: newItems,
app?.dataSourceManager?.compliedIteratorItems(itemData, props.config.items, props.config.dsField) ??
props.config.items,
id: '', id: '',
type: NodeType.CONTAINER, type: NodeType.CONTAINER,
condResult, condResult,
style: { style: {
...props.config.itemConfig.style, ...itemConfig.style,
position: 'relative', position: 'relative',
left: 0, left: 0,
top: 0, top: 0,
@ -69,4 +84,28 @@ const configs = computed(() => {
}; };
}); });
}); });
watch(
configs,
(configs) => {
const iteratorContainerNode = app?.getNode<TMagicIteratorContainer>(
props.config.id,
props.dataIteratorContainerId,
props.dataIteratorIndex,
);
if (!iteratorContainerNode) {
return;
}
iteratorContainerNode.resetNodes();
configs.forEach((config, index) => {
iteratorContainerNode.setNodes(config.items, index);
});
},
{
immediate: true,
},
);
</script> </script>

View File

@ -15,6 +15,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { NODE_CONDS_KEY } from '@tmagic/schema';
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils'; import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
export default [ export default [
@ -45,9 +46,8 @@ export default [
items: [ items: [
{ {
type: 'display-conds', type: 'display-conds',
name: 'displayConds', name: NODE_CONDS_KEY,
titlePrefix: '条件组', titlePrefix: '条件组',
parentFields: (formState: any, { formValue }: any) => formValue.dsField,
defaultValue: [], defaultValue: [],
}, },
{ {

View File

@ -1,6 +0,0 @@
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}

View File

@ -163,18 +163,26 @@ export const guid = (digit = 8): string =>
return v.toString(16); return v.toString(16);
}); });
export const getKeysArray = (keys: string | number) =>
// 将 array[0] 转成 array.0
`${keys}`.replaceAll(/\[(\d+)\]/g, '.$1').split('.');
export const getValueByKeyPath = ( export const getValueByKeyPath = (
keys: number | string | string[] = '', keys: number | string | string[] = '',
data: Record<string | number, any> = {}, data: Record<string | number, any> = {},
): any => { ): any => {
// 将 array[0] 转成 array.0 // 将 array[0] 转成 array.0
const keyArray = Array.isArray(keys) ? keys : `${keys}`.replaceAll(/\[(\d+)\]/g, '.$1').split('.'); const keyArray = Array.isArray(keys) ? keys : getKeysArray(keys);
return keyArray.reduce((accumulator, currentValue: any) => { return keyArray.reduce((accumulator, currentValue: any) => {
if (isObject(accumulator) || Array.isArray(accumulator)) { if (isObject(accumulator)) {
return accumulator[currentValue]; return accumulator[currentValue];
} }
return void 0; if (Array.isArray(accumulator) && /^\d*$/.test(`${currentValue}`)) {
return accumulator[currentValue];
}
throw new Error(`${data}中不存在${keys}`);
}, data); }, data);
}; };
@ -267,7 +275,7 @@ export const compiledNode = (
} }
keys.forEach((key) => { keys.forEach((key) => {
const keys = `${key}`.replaceAll(/\[(\d+)\]/g, '.$1').split('.'); const keys = getKeysArray(key);
const cacheKey = keys.map((key, index) => { const cacheKey = keys.map((key, index) => {
if (index < keys.length - 1) { if (index < keys.length - 1) {
@ -276,12 +284,16 @@ export const compiledNode = (
return `${DSL_NODE_KEY_COPY_PREFIX}${key}`; return `${DSL_NODE_KEY_COPY_PREFIX}${key}`;
}); });
const value = getValueByKeyPath(key, node);
let templateValue = getValueByKeyPath(cacheKey, node); let templateValue = getValueByKeyPath(cacheKey, node);
if (typeof templateValue === 'undefined') { if (typeof templateValue === 'undefined') {
setValueByKeyPath(cacheKey.join('.'), value, node); try {
templateValue = value; const value = getValueByKeyPath(key, node);
setValueByKeyPath(cacheKey.join('.'), value, node);
templateValue = value;
} catch (e) {
console.warn(e);
return;
}
} }
let newValue; let newValue;
@ -435,3 +447,5 @@ export const addParamToUrl = (obj: Record<string, any>, global = globalThis, nee
global.history.pushState({}, '', url); global.history.pushState({}, '', url);
} }
}; };
export const dataSourceTemplateRegExp = /\$\{([\s\S]+?)\}/g;

View File

@ -16,7 +16,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { describe, expect, test } from 'vitest'; import { assert, describe, expect, test } from 'vitest';
import type { DataSchema } from '@tmagic/schema'; import type { DataSchema } from '@tmagic/schema';
@ -354,17 +354,23 @@ describe('getValueByKeyPath', () => {
}); });
test('error', () => { test('error', () => {
const value = util.getValueByKeyPath('a.b.c.d', { assert.throws(() => {
a: {}, util.getValueByKeyPath('a.b.c.d', {
a: {},
});
}); });
expect(value).toBeUndefined(); assert.throws(() => {
util.getValueByKeyPath('a.b.c', {
const value1 = util.getValueByKeyPath('a.b.c', { a: {},
a: {}, });
}); });
expect(value1).toBeUndefined(); assert.doesNotThrow(() => {
util.getValueByKeyPath('a', {
a: {},
});
});
}); });
}); });

199
pnpm-lock.yaml generated
View File

@ -481,6 +481,9 @@ importers:
packages/schema: packages/schema:
dependencies: dependencies:
'@types/events':
specifier: ^3.0.0
version: 3.0.3
typescript: typescript:
specifier: '*' specifier: '*'
version: 5.5.4 version: 5.5.4
@ -524,6 +527,9 @@ importers:
moveable-helper: moveable-helper:
specifier: ^0.4.0 specifier: ^0.4.0
version: 0.4.0(scenejs@1.10.3) version: 0.4.0(scenejs@1.10.3)
scenejs:
specifier: ^1.10.3
version: 1.10.3
typescript: typescript:
specifier: '*' specifier: '*'
version: 5.5.4 version: 5.5.4
@ -642,8 +648,8 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../utils version: link:../utils
'@tmagic/vue-runtime-help': '@tmagic/vue-runtime-help':
specifier: '>=0.0.7' specifier: workspace:*
version: 0.0.7(@tmagic/core@packages+core)(@tmagic/data-source@1.4.19(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(typescript@5.5.4))(@tmagic/schema@packages+schema)(@tmagic/stage@1.4.19(@tmagic/core@packages+core)(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(scenejs@1.10.3)(typescript@5.5.4))(@tmagic/utils@packages+utils)(@vue/composition-api@1.7.2(vue@3.4.35(typescript@5.5.4)))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)) version: link:../../runtime/vue-runtime-help
qrcode: qrcode:
specifier: ^1.5.0 specifier: ^1.5.0
version: 1.5.3 version: 1.5.3
@ -958,20 +964,20 @@ importers:
runtime/vue-runtime-help: runtime/vue-runtime-help:
dependencies: dependencies:
'@tmagic/core': '@tmagic/core':
specifier: '>=1.4.16' specifier: workspace:*
version: 1.4.16(@tmagic/data-source@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4) version: link:../../packages/core
'@tmagic/data-source': '@tmagic/data-source':
specifier: '>=1.4.16' specifier: workspace:*
version: 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4) version: link:../../packages/data-source
'@tmagic/schema': '@tmagic/schema':
specifier: '>=1.4.16' specifier: workspace:*
version: 1.4.16(typescript@5.5.4) version: link:../../packages/schema
'@tmagic/stage': '@tmagic/stage':
specifier: '>=1.4.16' specifier: workspace:*
version: 1.4.16(@tmagic/core@1.4.16(@tmagic/data-source@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(scenejs@1.10.3)(typescript@5.5.4) version: link:../../packages/stage
'@tmagic/utils': '@tmagic/utils':
specifier: '>=1.4.16' specifier: workspace:*
version: 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4) version: link:../../packages/utils
'@vue/composition-api': '@vue/composition-api':
specifier: '>=1.7.2' specifier: '>=1.7.2'
version: 1.7.2(vue@3.4.35(typescript@5.5.4)) version: 1.7.2(vue@3.4.35(typescript@5.5.4))
@ -2347,18 +2353,6 @@ packages:
typescript: typescript:
optional: true optional: true
'@tmagic/core@1.4.16':
resolution: {integrity: sha512-rXNMtENGI+1YUQxp7jqqrAfUZ87cuF16wwSccpz+9+E8KG6VLRks110IjF1jcTxfcVY7tLrdeWX8RLTTYTz1Pg==}
engines: {node: '>=18'}
peerDependencies:
'@tmagic/data-source': 1.4.16
'@tmagic/schema': 1.4.16
'@tmagic/utils': 1.4.16
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@tmagic/core@1.4.19': '@tmagic/core@1.4.19':
resolution: {integrity: sha512-+bnuHypmODdbp4Wmwpu0eBMBo4mqkt8c8ysd2P0686KiYgoiWJq6Sq6L9vM9HoRugk8wliksGgmKpQD1f3f9MA==} resolution: {integrity: sha512-+bnuHypmODdbp4Wmwpu0eBMBo4mqkt8c8ysd2P0686KiYgoiWJq6Sq6L9vM9HoRugk8wliksGgmKpQD1f3f9MA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -2371,17 +2365,6 @@ packages:
typescript: typescript:
optional: true optional: true
'@tmagic/data-source@1.4.16':
resolution: {integrity: sha512-gYUARAJ5IpJzh2R9nTsvpISTJk8uiEwGwiRfpErgD2OzX9a2uEKvDaFK3GWTUtg8LLlw+UUC/vIt3Qk/89aEGg==}
engines: {node: '>=18'}
peerDependencies:
'@tmagic/schema': 1.4.16
'@tmagic/utils': 1.4.16
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@tmagic/data-source@1.4.19': '@tmagic/data-source@1.4.19':
resolution: {integrity: sha512-siBAN+N6TJqAHZ7sW6rX6p31tqGDl/lMntkBzK5HPv1MmgGyOiN9EmmuBZTF69O8JbqFcH3ErHep4zcJfVmF3w==} resolution: {integrity: sha512-siBAN+N6TJqAHZ7sW6rX6p31tqGDl/lMntkBzK5HPv1MmgGyOiN9EmmuBZTF69O8JbqFcH3ErHep4zcJfVmF3w==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -2404,17 +2387,6 @@ packages:
typescript: typescript:
optional: true optional: true
'@tmagic/dep@1.4.16':
resolution: {integrity: sha512-wB7lE9wwODHPhOH/FvIyt5Gq/stuJ/0rw0H3uTDb3rkyI/R7tTxvGTjmTsUditV8xKiHmiql6IC/XjTvWOKiAw==}
engines: {node: '>=18'}
peerDependencies:
'@tmagic/schema': 1.4.16
'@tmagic/utils': 1.4.16
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@tmagic/dep@1.4.19': '@tmagic/dep@1.4.19':
resolution: {integrity: sha512-8nr+Bv4U9y/NjM1JpA4NyoVMbiKZhH05TCNTJpJs642g40Bn4yZQA6DY3UtMjXsjSIRB6VqC/jd89lfgv/wzqw==} resolution: {integrity: sha512-8nr+Bv4U9y/NjM1JpA4NyoVMbiKZhH05TCNTJpJs642g40Bn4yZQA6DY3UtMjXsjSIRB6VqC/jd89lfgv/wzqw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -2514,15 +2486,6 @@ packages:
typescript: typescript:
optional: true optional: true
'@tmagic/schema@1.4.16':
resolution: {integrity: sha512-1aPNwOZcLEQMnnp7n8Azrbq2kamBnUW0i6CizC7LOGXzIDV7kNvHKP/SLjZoKc86i8XX863+8mRwop5gkn5W+g==}
engines: {node: '>=18'}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@tmagic/schema@1.4.19': '@tmagic/schema@1.4.19':
resolution: {integrity: sha512-JvlqseW6WMGRQUPgiwZC8F1R4SMGwU2tgtGXijxwan6fNmEbXQlvTTfCqQbYnarzQlaVN/W0DIcdxp251lrXOw==} resolution: {integrity: sha512-JvlqseW6WMGRQUPgiwZC8F1R4SMGwU2tgtGXijxwan6fNmEbXQlvTTfCqQbYnarzQlaVN/W0DIcdxp251lrXOw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -2544,18 +2507,6 @@ packages:
typescript: typescript:
optional: true optional: true
'@tmagic/stage@1.4.16':
resolution: {integrity: sha512-/05Im0FkB6xH/PR/FOwf7TJc68fTWA7badJeBq2eL0ofctdBDMfJ/rlW6tcO/xCZ2KHFJH9v0XFWWW0UA+48ew==}
engines: {node: '>=18'}
peerDependencies:
'@tmagic/core': 1.4.16
'@tmagic/schema': 1.4.16
'@tmagic/utils': 1.4.16
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@tmagic/stage@1.4.19': '@tmagic/stage@1.4.19':
resolution: {integrity: sha512-d9w0Q1faJirv1iX8OCaXKpIPxNDODwpgdheUI2yGS44VGEY49F3lW+80KKHitldlpl9Jh4rlwMfb0bWww9Xehw==} resolution: {integrity: sha512-d9w0Q1faJirv1iX8OCaXKpIPxNDODwpgdheUI2yGS44VGEY49F3lW+80KKHitldlpl9Jh4rlwMfb0bWww9Xehw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -2658,16 +2609,6 @@ packages:
typescript: typescript:
optional: true optional: true
'@tmagic/utils@1.4.16':
resolution: {integrity: sha512-bljMgc6a9DvbvCc1yawgCOzMqml6K5qgw4OBYVcxr06d+fT2pWfwCNrwZ5o8I2w6i0YcDMmKBr4ADy2Zb+cr6Q==}
engines: {node: '>=18'}
peerDependencies:
'@tmagic/schema': 1.4.16
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@tmagic/utils@1.4.19': '@tmagic/utils@1.4.19':
resolution: {integrity: sha512-0jM+0UOiGmLWYj0cHTMqo1BawmA63oZDbanzNhEWhwhNJ+wUFbWadvCc8qOithXShVW9tw2BNCzbPyx5kr6eIQ==} resolution: {integrity: sha512-0jM+0UOiGmLWYj0cHTMqo1BawmA63oZDbanzNhEWhwhNJ+wUFbWadvCc8qOithXShVW9tw2BNCzbPyx5kr6eIQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -7575,16 +7516,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/core@1.4.16(@tmagic/data-source@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)':
dependencies:
'@tmagic/data-source': 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)
'@tmagic/schema': 1.4.16(typescript@5.5.4)
'@tmagic/utils': 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4)
events: 3.3.0
lodash-es: 4.17.21
optionalDependencies:
typescript: 5.5.4
'@tmagic/core@1.4.19(@tmagic/data-source@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)': '@tmagic/core@1.4.19(@tmagic/data-source@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)':
dependencies: dependencies:
'@tmagic/data-source': 1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4) '@tmagic/data-source': 1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)
@ -7595,17 +7526,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/data-source@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)':
dependencies:
'@tmagic/dep': 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)
'@tmagic/schema': 1.4.16(typescript@5.5.4)
'@tmagic/utils': 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4)
deep-state-observer: 5.5.13
events: 3.3.0
lodash-es: 4.17.21
optionalDependencies:
typescript: 5.5.4
'@tmagic/data-source@1.4.19(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)': '@tmagic/data-source@1.4.19(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)':
dependencies: dependencies:
'@tmagic/dep': 1.4.19(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4) '@tmagic/dep': 1.4.19(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)
@ -7628,17 +7548,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/data-source@1.4.19(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(typescript@5.5.4)':
dependencies:
'@tmagic/dep': 1.4.19(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(typescript@5.5.4)
'@tmagic/schema': link:packages/schema
'@tmagic/utils': link:packages/utils
deep-state-observer: 5.5.13
events: 3.3.0
lodash-es: 4.17.21
optionalDependencies:
typescript: 5.5.4
'@tmagic/dep@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)': '@tmagic/dep@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)':
dependencies: dependencies:
'@tmagic/schema': 1.4.15(typescript@5.5.4) '@tmagic/schema': 1.4.15(typescript@5.5.4)
@ -7646,13 +7555,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/dep@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)':
dependencies:
'@tmagic/schema': 1.4.16(typescript@5.5.4)
'@tmagic/utils': 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4)
optionalDependencies:
typescript: 5.5.4
'@tmagic/dep@1.4.19(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)': '@tmagic/dep@1.4.19(@tmagic/schema@1.4.15(typescript@5.5.4))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)':
dependencies: dependencies:
'@tmagic/schema': 1.4.15(typescript@5.5.4) '@tmagic/schema': 1.4.15(typescript@5.5.4)
@ -7667,13 +7569,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/dep@1.4.19(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(typescript@5.5.4)':
dependencies:
'@tmagic/schema': link:packages/schema
'@tmagic/utils': link:packages/utils
optionalDependencies:
typescript: 5.5.4
'@tmagic/design@1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))': '@tmagic/design@1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))':
dependencies: dependencies:
vue: 3.4.35(typescript@5.5.4) vue: 3.4.35(typescript@5.5.4)
@ -7766,10 +7661,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/schema@1.4.16(typescript@5.5.4)':
optionalDependencies:
typescript: 5.5.4
'@tmagic/schema@1.4.19(typescript@5.5.4)': '@tmagic/schema@1.4.19(typescript@5.5.4)':
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
@ -7790,22 +7681,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- scenejs - scenejs
'@tmagic/stage@1.4.16(@tmagic/core@1.4.16(@tmagic/data-source@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(scenejs@1.10.3)(typescript@5.5.4)':
dependencies:
'@scena/guides': 0.29.2
'@tmagic/core': 1.4.16(@tmagic/data-source@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.16(typescript@5.5.4))(@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)
'@tmagic/schema': 1.4.16(typescript@5.5.4)
'@tmagic/utils': 1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4)
events: 3.3.0
keycon: 1.4.0
lodash-es: 4.17.21
moveable: 0.53.0
moveable-helper: 0.4.0(scenejs@1.10.3)
optionalDependencies:
typescript: 5.5.4
transitivePeerDependencies:
- scenejs
'@tmagic/stage@1.4.19(@tmagic/core@1.4.19(@tmagic/data-source@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(scenejs@1.10.3)(typescript@5.5.4)': '@tmagic/stage@1.4.19(@tmagic/core@1.4.19(@tmagic/data-source@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(scenejs@1.10.3)(typescript@5.5.4)':
dependencies: dependencies:
'@scena/guides': 0.29.2 '@scena/guides': 0.29.2
@ -7822,23 +7697,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- scenejs - scenejs
'@tmagic/stage@1.4.19(@tmagic/core@packages+core)(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(scenejs@1.10.3)(typescript@5.5.4)':
dependencies:
'@scena/guides': 0.29.2
'@tmagic/core': link:packages/core
'@tmagic/schema': link:packages/schema
'@tmagic/utils': link:packages/utils
events: 3.3.0
keycon: 1.4.0
lodash-es: 4.17.21
moveable: 0.53.0
moveable-helper: 0.4.0(scenejs@1.10.3)
optionalDependencies:
typescript: 5.5.4
transitivePeerDependencies:
- scenejs
optional: true
'@tmagic/table@1.4.15(@tmagic/design@1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)))(@tmagic/form@1.4.15(@tmagic/design@1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))': '@tmagic/table@1.4.15(@tmagic/design@1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)))(@tmagic/form@1.4.15(@tmagic/design@1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)))(@tmagic/utils@1.4.15(@tmagic/schema@1.4.15(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))':
dependencies: dependencies:
'@tmagic/design': 1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4)) '@tmagic/design': 1.4.19(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))
@ -7910,14 +7768,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/utils@1.4.16(@tmagic/schema@1.4.16(typescript@5.5.4))(typescript@5.5.4)':
dependencies:
'@tmagic/schema': 1.4.16(typescript@5.5.4)
dayjs: 1.11.12
lodash-es: 4.17.21
optionalDependencies:
typescript: 5.5.4
'@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4)': '@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4)':
dependencies: dependencies:
'@tmagic/schema': 1.4.19(typescript@5.5.4) '@tmagic/schema': 1.4.19(typescript@5.5.4)
@ -7926,19 +7776,6 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.5.4 typescript: 5.5.4
'@tmagic/vue-runtime-help@0.0.7(@tmagic/core@packages+core)(@tmagic/data-source@1.4.19(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(typescript@5.5.4))(@tmagic/schema@packages+schema)(@tmagic/stage@1.4.19(@tmagic/core@packages+core)(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(scenejs@1.10.3)(typescript@5.5.4))(@tmagic/utils@packages+utils)(@vue/composition-api@1.7.2(vue@3.4.35(typescript@5.5.4)))(typescript@5.5.4)(vue@3.4.35(typescript@5.5.4))':
dependencies:
'@tmagic/core': link:packages/core
'@tmagic/data-source': 1.4.19(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(typescript@5.5.4)
'@tmagic/utils': link:packages/utils
vue: 3.4.35(typescript@5.5.4)
vue-demi: 0.14.10(@vue/composition-api@1.7.2(vue@3.4.35(typescript@5.5.4)))(vue@3.4.35(typescript@5.5.4))
optionalDependencies:
'@tmagic/schema': link:packages/schema
'@tmagic/stage': 1.4.19(@tmagic/core@packages+core)(@tmagic/schema@packages+schema)(@tmagic/utils@packages+utils)(scenejs@1.10.3)(typescript@5.5.4)
'@vue/composition-api': 1.7.2(vue@3.4.35(typescript@5.5.4))
typescript: 5.5.4
'@tmagic/vue-runtime-help@0.0.7(iw6w6ghqwusnx4qxoselwthjvi)': '@tmagic/vue-runtime-help@0.0.7(iw6w6ghqwusnx4qxoselwthjvi)':
dependencies: dependencies:
'@tmagic/core': 1.4.19(@tmagic/data-source@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4) '@tmagic/core': 1.4.19(@tmagic/data-source@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4))(@tmagic/schema@1.4.19(typescript@5.5.4))(@tmagic/utils@1.4.19(@tmagic/schema@1.4.19(typescript@5.5.4))(typescript@5.5.4))(typescript@5.5.4)

View File

@ -32,11 +32,11 @@
"vue-demi": "^0.14.7" "vue-demi": "^0.14.7"
}, },
"peerDependencies": { "peerDependencies": {
"@tmagic/core": ">=1.4.16", "@tmagic/core": "workspace:*",
"@tmagic/data-source": ">=1.4.16", "@tmagic/data-source": "workspace:*",
"@tmagic/schema": ">=1.4.16", "@tmagic/schema": "workspace:*",
"@tmagic/stage": ">=1.4.16", "@tmagic/stage": "workspace:*",
"@tmagic/utils": ">=1.4.16", "@tmagic/utils": "workspace:*",
"@vue/composition-api": ">=1.7.2", "@vue/composition-api": ">=1.7.2",
"typescript": "*", "typescript": "*",
"vue": ">=2.0.0 || >=3.0.0" "vue": ">=2.0.0 || >=3.0.0"

View File

@ -82,7 +82,7 @@ export const useEditorDsl = (app: Core | undefined, win = window) => {
const newNode = app.dataSourceManager?.compiledNode(config, undefined, true) || config; const newNode = app.dataSourceManager?.compiledNode(config, undefined, true) || config;
replaceChildNode(reactive(newNode), [root.value], parentId); replaceChildNode(reactive(newNode), [root.value], parentId);
const nodeInstance = app.page?.getNode(config.id); const nodeInstance = app.getNode(config.id);
if (nodeInstance) { if (nodeInstance) {
nodeInstance.setData(newNode); nodeInstance.setData(newNode);
} }

View File

@ -18,12 +18,14 @@
import { inject, onBeforeUnmount, onMounted } from 'vue-demi'; import { inject, onBeforeUnmount, onMounted } from 'vue-demi';
import type Core from '@tmagic/core'; import type TMagicApp from '@tmagic/core';
import type { MNode } from '@tmagic/schema'; import type { Id, MNode } from '@tmagic/schema';
import { IS_DSL_NODE_KEY } from '@tmagic/utils'; import { IS_DSL_NODE_KEY } from '@tmagic/utils';
interface UseAppOptions<T extends MNode = MNode> { interface UseAppOptions<T extends MNode = MNode> {
config: T; config: T;
iteratorContainerId?: Id[];
iteratorIndex?: number[];
methods?: { methods?: {
[key: string]: Function; [key: string]: Function;
}; };
@ -31,8 +33,8 @@ interface UseAppOptions<T extends MNode = MNode> {
const isDslNode = (config: MNode) => typeof config[IS_DSL_NODE_KEY] === 'undefined' || config[IS_DSL_NODE_KEY] === true; const isDslNode = (config: MNode) => typeof config[IS_DSL_NODE_KEY] === 'undefined' || config[IS_DSL_NODE_KEY] === true;
export default ({ methods, config }: UseAppOptions) => { export default ({ methods, config, iteratorContainerId, iteratorIndex }: UseAppOptions) => {
const app: Core | undefined = inject('app'); const app: TMagicApp | undefined = inject('app');
const emitData = { const emitData = {
config, config,
@ -52,7 +54,7 @@ export default ({ methods, config }: UseAppOptions) => {
return displayCfg !== false; return displayCfg !== false;
}; };
const node = isDslNode(config) ? app?.page?.getNode(config.id || '') : undefined; const node = isDslNode(config) ? app?.getNode(config.id || '', iteratorContainerId, iteratorIndex) : undefined;
if (node) { if (node) {
node.emit('created', emitData); node.emit('created', emitData);

View File

@ -19,7 +19,7 @@
import Vue from 'vue'; import Vue from 'vue';
import Core from '@tmagic/core'; import Core from '@tmagic/core';
import { DataSourceManager, DeepObservedData registerDataSourceOnDemand } from '@tmagic/data-source'; import { DataSourceManager, DeepObservedData, registerDataSourceOnDemand } from '@tmagic/data-source';
import { getUrlParam } from '@tmagic/utils'; import { getUrlParam } from '@tmagic/utils';
import asyncDataSources from '../.tmagic/async-datasource-entry'; import asyncDataSources from '../.tmagic/async-datasource-entry';

View File

@ -5,27 +5,27 @@ const { type } = minimist(process.argv.slice(2));
const run = (bin, args, opts = {}) => execa(bin, args, { stdio: 'inherit', ...opts }); const run = (bin, args, opts = {}) => execa(bin, args, { stdio: 'inherit', ...opts });
const build = (pkg) => run('pnpm', ['--filter', `@tmagic/${pkg}`, type ? 'build:type' : 'build']);
const main = async () => { const main = async () => {
// 按照依赖顺序构建 // 按照依赖顺序构建
const packages = [ build('cli');
'schema',
'utils',
'dep',
'data-source',
'core',
'design',
'element-plus-adapter',
'tdesign-vue-next-adapter',
'form',
'table',
'stage',
'editor',
'cli',
'ui',
];
for (const pkg of packages) { await Promise.all([
await run('pnpm', ['--filter', `@tmagic/${pkg}`, type ? 'build:type' : 'build']); (async () => {
for (const pkg of ['schema', 'utils', 'dep', 'data-source', 'core', 'stage']) {
await build(pkg);
}
})(),
(async () => {
for (const pkg of ['design', 'element-plus-adapter', 'tdesign-vue-next-adapter']) {
await build(pkg);
}
})(),
]);
for (const pkg of ['form', 'table', 'editor', 'vue-runtime-help', 'tmagic-form-runtime', 'ui']) {
await build(pkg);
} }
}; };

View File

@ -20,6 +20,7 @@
// src/index.ts, . // src/index.ts, .
"@tmagic/*": ["packages/*/src"], "@tmagic/*": ["packages/*/src"],
"@tmagic/tmagic-form-runtime": ["runtime/tmagic-form/src"], "@tmagic/tmagic-form-runtime": ["runtime/tmagic-form/src"],
"@tmagic/vue-runtime-help": ["runtime/vue-runtime-help/src"],
"@editor/*": ["packages/editor/src/*"], "@editor/*": ["packages/editor/src/*"],
"@form/*": ["packages/form/src/*"], "@form/*": ["packages/form/src/*"],
"@data-source/*": ["packages/data-source/src/*"], "@data-source/*": ["packages/data-source/src/*"],

View File

@ -11,7 +11,22 @@ export default defineConfig({
test: { test: {
include: ['./packages/*/tests/**'], include: ['./packages/*/tests/**'],
environment: 'jsdom', environment: 'jsdom',
environmentMatchGlobs: [['packages/cli/**', 'node']],
coverage: { coverage: {
exclude: [
'./runtime/**',
'./playground/**',
'./docs/**',
'./packages/*/types/**',
'./packages/*/tests/**',
'./packages/cli/lib/**',
'./packages/ui/**',
'./packages/ui-vue2/**',
'./packages/ui-react/**',
'./packages/design/**',
'./packages/element-plus-adapter/**',
'./packages/tdesign-vue-next-adapter/**',
],
extension: ['.ts', '.vue'], extension: ['.ts', '.vue'],
}, },
}, },