mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-09-18 19:40:03 +08:00
feat(dep,editor,data-source,schema): 优化编辑器中依赖收集性能
This commit is contained in:
parent
cc8ec39dad
commit
ee269917f8
@ -74,7 +74,7 @@
|
||||
"semver": "^7.3.7",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"shx": "^0.3.4",
|
||||
"typescript": "^5.4.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
"vitepress": "1.2.2",
|
||||
"vitest": "^1.6.0",
|
||||
|
@ -154,7 +154,7 @@ export const compliedIteratorItems = (itemData: any, items: MNode[], dsId: strin
|
||||
}),
|
||||
);
|
||||
|
||||
watcher.collect(items, true);
|
||||
watcher.collect(items, {}, true);
|
||||
|
||||
const { deps } = watcher.getTarget(dsId);
|
||||
if (!Object.keys(deps).length) {
|
||||
|
@ -2,6 +2,13 @@ import type { DepData } from '@tmagic/schema';
|
||||
|
||||
import { DepTargetType, type IsTarget, type TargetOptions } from './types';
|
||||
|
||||
export interface DepUpdateOptions {
|
||||
id: string | number;
|
||||
name: string;
|
||||
key: string | number;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要收集依赖的目标
|
||||
* 例如:一个代码块可以为一个目标
|
||||
@ -49,20 +56,20 @@ export default class Target {
|
||||
|
||||
/**
|
||||
* 更新依赖
|
||||
* @param node 节点配置
|
||||
* @param option 节点配置
|
||||
* @param key 哪个key配置了这个目标的id
|
||||
*/
|
||||
public updateDep(node: Record<string | number, any>, key: string | number) {
|
||||
const dep = this.deps[node.id] || {
|
||||
name: node.name,
|
||||
public updateDep({ id, name, key, data }: DepUpdateOptions) {
|
||||
const dep = this.deps[id] || {
|
||||
name,
|
||||
keys: [],
|
||||
};
|
||||
|
||||
if (node.name) {
|
||||
dep.name = node.name;
|
||||
}
|
||||
dep.name = name;
|
||||
|
||||
this.deps[node.id] = dep;
|
||||
dep.data = data;
|
||||
|
||||
this.deps[id] = dep;
|
||||
|
||||
if (dep.keys.indexOf(key) === -1) {
|
||||
dep.keys.push(key);
|
||||
@ -75,15 +82,15 @@ export default class Target {
|
||||
* @param key 节点下哪个key需要移除,如果为空,则移除改节点下的所有依赖key
|
||||
* @returns void
|
||||
*/
|
||||
public removeDep(node?: Record<string | number, any>, key?: string | number) {
|
||||
if (!node) {
|
||||
public removeDep(id?: string | number, key?: string | number) {
|
||||
if (typeof id === 'undefined') {
|
||||
Object.keys(this.deps).forEach((depKey) => {
|
||||
delete this.deps[depKey];
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const dep = this.deps[node.id];
|
||||
const dep = this.deps[id];
|
||||
|
||||
if (!dep) return;
|
||||
|
||||
@ -92,10 +99,10 @@ export default class Target {
|
||||
dep.keys.splice(index, 1);
|
||||
|
||||
if (dep.keys.length === 0) {
|
||||
delete this.deps[node.id];
|
||||
delete this.deps[id];
|
||||
}
|
||||
} else {
|
||||
delete this.deps[node.id];
|
||||
delete this.deps[id];
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,8 +112,8 @@ export default class Target {
|
||||
* @param key 哪个key
|
||||
* @returns boolean
|
||||
*/
|
||||
public hasDep(node: Record<string | number, any>, key: string | number) {
|
||||
const dep = this.deps[node.id];
|
||||
public hasDep(id: string | number, key: string | number) {
|
||||
const dep = this.deps[id];
|
||||
|
||||
return Boolean(dep?.keys.find((d) => d === key));
|
||||
}
|
||||
|
@ -1,15 +1,23 @@
|
||||
import { isObject } from '@tmagic/utils';
|
||||
|
||||
import type Target from './Target';
|
||||
import { DepTargetType, type TargetList } from './types';
|
||||
import { type DepExtendedData, DepTargetType, type TargetList, TargetNode } from './types';
|
||||
import { traverseTarget } from './utils';
|
||||
|
||||
export default class Watcher {
|
||||
private targetsList: TargetList = {};
|
||||
private childrenProp = 'items';
|
||||
private idProp = 'id';
|
||||
private nameProp = 'name';
|
||||
|
||||
constructor(options?: { initialTargets?: TargetList }) {
|
||||
constructor(options?: { initialTargets?: TargetList; childrenProp?: string }) {
|
||||
if (options?.initialTargets) {
|
||||
this.targetsList = options.initialTargets;
|
||||
}
|
||||
|
||||
if (options?.childrenProp) {
|
||||
this.childrenProp = options.childrenProp;
|
||||
}
|
||||
}
|
||||
|
||||
public getTargetsList() {
|
||||
@ -106,58 +114,58 @@ export default class Watcher {
|
||||
* @param deep 是否需要收集子节点
|
||||
* @param type 强制收集指定类型的依赖
|
||||
*/
|
||||
public collect(nodes: Record<string | number, any>[], deep = false, type?: DepTargetType) {
|
||||
Object.values(this.targetsList).forEach((targets) => {
|
||||
Object.values(targets).forEach((target) => {
|
||||
if ((!type && !target.isCollectByDefault) || (type && target.type !== type)) return;
|
||||
nodes.forEach((node) => {
|
||||
// 先删除原有依赖,重新收集
|
||||
target.removeDep(node);
|
||||
this.collectItem(node, target, deep);
|
||||
});
|
||||
});
|
||||
public collect(nodes: TargetNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
|
||||
this.collectByCallback(nodes, type, ({ node, target }) => {
|
||||
this.removeTargetDep(target, node);
|
||||
this.collectItem(node, target, depExtendedData, deep);
|
||||
});
|
||||
}
|
||||
|
||||
public collectByCallback(
|
||||
nodes: TargetNode[],
|
||||
type: DepTargetType | undefined,
|
||||
cb: (data: { node: TargetNode; target: Target }) => void,
|
||||
) {
|
||||
traverseTarget(
|
||||
this.targetsList,
|
||||
(target) => {
|
||||
if (!type && !target.isCollectByDefault) {
|
||||
return;
|
||||
}
|
||||
nodes.forEach((node) => {
|
||||
cb({ node, target });
|
||||
});
|
||||
},
|
||||
type,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有目标的依赖
|
||||
* @param nodes 需要清除依赖的节点
|
||||
*/
|
||||
public clear(nodes?: Record<string | number, any>[]) {
|
||||
const clearedItemsNodeIds: (string | number)[] = [];
|
||||
Object.values(this.targetsList).forEach((targets) => {
|
||||
Object.values(targets).forEach((target) => {
|
||||
if (nodes) {
|
||||
nodes.forEach((node) => {
|
||||
target.removeDep(node);
|
||||
public clear(nodes?: TargetNode[], type?: DepTargetType) {
|
||||
let { targetsList } = this;
|
||||
|
||||
if (Array.isArray(node.items) && node.items.length && !clearedItemsNodeIds.includes(node.id)) {
|
||||
clearedItemsNodeIds.push(node.id);
|
||||
this.clear(node.items);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
target.removeDep();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
if (type) {
|
||||
targetsList = {
|
||||
[type]: this.getTargets(type),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定类型的依赖
|
||||
* @param type 类型
|
||||
* @param nodes 需要清除依赖的节点
|
||||
*/
|
||||
public clearByType(type: DepTargetType, nodes?: Record<string | number, any>[]) {
|
||||
const clearedItemsNodeIds: (string | number)[] = [];
|
||||
const targetList = this.getTargets(type);
|
||||
Object.values(targetList).forEach((target) => {
|
||||
traverseTarget(targetsList, (target) => {
|
||||
if (nodes) {
|
||||
nodes.forEach((node) => {
|
||||
target.removeDep(node);
|
||||
if (Array.isArray(node.items) && node.items.length && !clearedItemsNodeIds.includes(node.id)) {
|
||||
clearedItemsNodeIds.push(node.id);
|
||||
this.clear(node.items);
|
||||
target.removeDep(node[this.idProp]);
|
||||
|
||||
if (
|
||||
Array.isArray(node[this.childrenProp]) &&
|
||||
node[this.childrenProp].length &&
|
||||
!clearedItemsNodeIds.includes(node[this.idProp])
|
||||
) {
|
||||
clearedItemsNodeIds.push(node[this.idProp]);
|
||||
this.clear(node[this.childrenProp]);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@ -166,14 +174,28 @@ export default class Watcher {
|
||||
});
|
||||
}
|
||||
|
||||
private collectItem(node: Record<string | number, any>, target: Target, deep = false) {
|
||||
/**
|
||||
* 清除指定类型的依赖
|
||||
* @param type 类型
|
||||
* @param nodes 需要清除依赖的节点
|
||||
*/
|
||||
public clearByType(type: DepTargetType, nodes?: TargetNode[]) {
|
||||
this.clear(nodes, type);
|
||||
}
|
||||
|
||||
public collectItem(node: TargetNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
|
||||
const collectTarget = (config: Record<string | number, any>, prop = '') => {
|
||||
const doCollect = (key: string, value: any) => {
|
||||
const keyIsItems = key === 'items';
|
||||
const keyIsItems = key === this.childrenProp;
|
||||
const fullKey = prop ? `${prop}.${key}` : key;
|
||||
|
||||
if (target.isTarget(fullKey, value)) {
|
||||
target.updateDep(node, fullKey);
|
||||
target.updateDep({
|
||||
id: node[this.idProp],
|
||||
name: `${node[this.nameProp] || node[this.idProp]}`,
|
||||
data: depExtendedData,
|
||||
key: fullKey,
|
||||
});
|
||||
} else if (!keyIsItems && Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
if (isObject(item)) {
|
||||
@ -186,7 +208,7 @@ export default class Watcher {
|
||||
|
||||
if (keyIsItems && deep && Array.isArray(value)) {
|
||||
value.forEach((child) => {
|
||||
this.collectItem(child, target, deep);
|
||||
this.collectItem(child, target, depExtendedData, deep);
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -199,4 +221,13 @@ export default class Watcher {
|
||||
|
||||
collectTarget(node);
|
||||
}
|
||||
|
||||
public removeTargetDep(target: Target, node: TargetNode, key?: string | number) {
|
||||
target.removeDep(node[this.idProp], key);
|
||||
if (typeof key === 'undefined' && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) {
|
||||
node[this.childrenProp].forEach((item: TargetNode) => {
|
||||
this.removeTargetDep(target, item, key);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,6 @@ export enum DepTargetType {
|
||||
DATA_SOURCE_METHOD = 'data-source-method',
|
||||
/** 数据源条件 */
|
||||
DATA_SOURCE_COND = 'data-source-cond',
|
||||
/** 复制组件时关联的组件 */
|
||||
RELATED_COMP_WHEN_COPY = 'related-comp-when-copy',
|
||||
/** 复制组件时关联的代码块 */
|
||||
RELATED_CODE_WHEN_COPY = 'related-code-when-copy',
|
||||
/** 复制组件时关联的数据源 */
|
||||
RELATED_DS_WHEN_COPY = 'related-ds-when-copy',
|
||||
}
|
||||
|
||||
export type IsTarget = (key: string | number, value: any) => boolean;
|
||||
@ -47,3 +41,11 @@ export interface TargetList {
|
||||
[targetId: string | number]: Target;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TargetNode {
|
||||
readonly id: string | number;
|
||||
readonly name?: string;
|
||||
readonly [key: string | number]: any;
|
||||
}
|
||||
|
||||
export type DepExtendedData = Record<string, any>;
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
||||
|
||||
import Target from './Target';
|
||||
import { CustomTargetOptions, DepTargetType } from './types';
|
||||
import { DepTargetType, type TargetList } from './types';
|
||||
|
||||
export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent, initialDeps: DepData = {}) =>
|
||||
new Target({
|
||||
@ -32,13 +32,6 @@ export const createCodeBlockTarget = (id: Id, codeBlock: CodeBlockContent, initi
|
||||
},
|
||||
});
|
||||
|
||||
export const createRelatedTargetForCopy = (options: CustomTargetOptions, type: DepTargetType) =>
|
||||
new Target({
|
||||
id: type,
|
||||
type,
|
||||
...options,
|
||||
});
|
||||
|
||||
/**
|
||||
* ['array'] ['array', '0'] ['array', '0', 'a'] 这种返回false
|
||||
* ['array', 'a'] 这种返回true
|
||||
@ -199,3 +192,14 @@ export const createDataSourceMethodTarget = (ds: DataSourceSchema, initialDeps:
|
||||
return Boolean(ds?.methods?.find((method) => method.name === value[1]));
|
||||
},
|
||||
});
|
||||
|
||||
export const traverseTarget = (targetsList: TargetList, cb: (target: Target) => void, type?: DepTargetType) => {
|
||||
Object.values(targetsList).forEach((targets) => {
|
||||
Object.values(targets).forEach((target) => {
|
||||
if (type && target.type !== type) {
|
||||
return;
|
||||
}
|
||||
cb(target);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
@ -223,6 +223,7 @@ describe('Watcher', () => {
|
||||
],
|
||||
},
|
||||
],
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import type { EventOption } from '@tmagic/core';
|
||||
import type { CustomTargetOptions } from '@tmagic/dep';
|
||||
import type { FormConfig, FormState } from '@tmagic/form';
|
||||
import type { DataSourceSchema, Id, MApp, MNode } from '@tmagic/schema';
|
||||
import StageCore, {
|
||||
@ -70,12 +69,6 @@ export interface EditorProps {
|
||||
codeOptions?: { [key: string]: any };
|
||||
/** 禁用鼠标左键按下时就开始拖拽,需要先选中再可以拖拽 */
|
||||
disabledDragStart?: boolean;
|
||||
/** 自定义依赖收集器,复制组件时会将关联组件一并复制 */
|
||||
collectorOptions?: CustomTargetOptions;
|
||||
/** 自定义依赖收集器,复制组件时会将关联代码块一并复制 */
|
||||
collectorOptionsForCode?: CustomTargetOptions;
|
||||
/** 自定义依赖收集器,复制组件时会将关联数据源一并复制 */
|
||||
collectorOptionsForDataSource?: CustomTargetOptions;
|
||||
/** 标尺配置 */
|
||||
guidesOptions?: Partial<GuidesOptions>;
|
||||
/** 禁止多选 */
|
||||
|
@ -7,12 +7,11 @@ import {
|
||||
createDataSourceCondTarget,
|
||||
createDataSourceMethodTarget,
|
||||
createDataSourceTarget,
|
||||
createRelatedTargetForCopy,
|
||||
DepTargetType,
|
||||
Target,
|
||||
} from '@tmagic/dep';
|
||||
import type { CodeBlockContent, DataSourceSchema, Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/schema';
|
||||
import { getNodes } from '@tmagic/utils';
|
||||
import { getNodes, isPage } from '@tmagic/utils';
|
||||
|
||||
import PropsPanel from './layouts/PropsPanel.vue';
|
||||
import { EditorProps } from './editorProps';
|
||||
@ -268,17 +267,12 @@ export const initServiceEvents = (
|
||||
}
|
||||
};
|
||||
|
||||
const depUpdateHandler = (node: MNode) => {
|
||||
updateNodeWhenDataSourceChange([node]);
|
||||
};
|
||||
|
||||
const collectedHandler = (nodes: MNode[]) => {
|
||||
updateNodeWhenDataSourceChange(nodes);
|
||||
};
|
||||
|
||||
depService.on('add-target', targetAddHandler);
|
||||
depService.on('remove-target', targetRemoveHandler);
|
||||
depService.on('dep-update', depUpdateHandler);
|
||||
depService.on('collected', collectedHandler);
|
||||
|
||||
const initDataSourceDepTarget = (ds: DataSourceSchema) => {
|
||||
@ -307,7 +301,9 @@ export const initServiceEvents = (
|
||||
});
|
||||
|
||||
if (Array.isArray(value.items)) {
|
||||
depService.collect(value.items, true);
|
||||
value.items.forEach((page) => {
|
||||
depService.collectIdle([page], { pageId: page.id }, true);
|
||||
});
|
||||
} else {
|
||||
depService.clear();
|
||||
delete value.dataSourceDeps;
|
||||
@ -334,14 +330,28 @@ export const initServiceEvents = (
|
||||
}
|
||||
};
|
||||
|
||||
const collectIdle = (nodes: MNode[], deep: boolean) => {
|
||||
nodes.forEach((node) => {
|
||||
let pageId: Id | undefined;
|
||||
|
||||
if (isPage(node)) {
|
||||
pageId = node.id;
|
||||
} else {
|
||||
const info = editorService.getNodeInfo(node.id);
|
||||
pageId = info.page?.id;
|
||||
}
|
||||
depService.collectIdle([node], { pageId }, deep);
|
||||
});
|
||||
};
|
||||
|
||||
// 新增节点,收集依赖
|
||||
const nodeAddHandler = (nodes: MNode[]) => {
|
||||
depService.collect(nodes);
|
||||
collectIdle(nodes, true);
|
||||
};
|
||||
|
||||
// 节点更新,收集依赖
|
||||
const nodeUpdateHandler = (nodes: MNode[]) => {
|
||||
depService.collect(nodes);
|
||||
collectIdle(nodes, false);
|
||||
};
|
||||
|
||||
// 节点删除,清除对齐的依赖收集
|
||||
@ -351,7 +361,7 @@ export const initServiceEvents = (
|
||||
|
||||
// 由于历史记录变化是更新整个page,所以历史记录变化时,需要重新收集依赖
|
||||
const historyChangeHandler = (page: MPage | MPageFragment) => {
|
||||
depService.collect([page], true);
|
||||
depService.collectIdle([page], { pageId: page.id }, true);
|
||||
};
|
||||
|
||||
editorService.on('history-change', historyChangeHandler);
|
||||
@ -386,7 +396,9 @@ export const initServiceEvents = (
|
||||
removeDataSourceTarget(config.id);
|
||||
initDataSourceDepTarget(config);
|
||||
|
||||
depService.collect(root?.items || [], true);
|
||||
(root?.items || []).forEach((page) => {
|
||||
depService.collectIdle([page], { pageId: page.id }, true);
|
||||
});
|
||||
|
||||
const targets = depService.getTargets(DepTargetType.DATA_SOURCE);
|
||||
|
||||
@ -410,25 +422,9 @@ export const initServiceEvents = (
|
||||
dataSourceService.on('update', dataSourceUpdateHandler);
|
||||
dataSourceService.on('remove', dataSourceRemoveHandler);
|
||||
|
||||
// 初始化复制组件相关的依赖收集器
|
||||
if (props.collectorOptions && !depService.hasTarget(DepTargetType.RELATED_COMP_WHEN_COPY)) {
|
||||
depService.addTarget(createRelatedTargetForCopy(props.collectorOptions, DepTargetType.RELATED_COMP_WHEN_COPY));
|
||||
}
|
||||
if (props.collectorOptionsForCode && !depService.hasTarget(DepTargetType.RELATED_CODE_WHEN_COPY)) {
|
||||
depService.addTarget(
|
||||
createRelatedTargetForCopy(props.collectorOptionsForCode, DepTargetType.RELATED_CODE_WHEN_COPY),
|
||||
);
|
||||
}
|
||||
if (props.collectorOptionsForDataSource && !depService.hasTarget(DepTargetType.RELATED_DS_WHEN_COPY)) {
|
||||
depService.addTarget(
|
||||
createRelatedTargetForCopy(props.collectorOptionsForDataSource, DepTargetType.RELATED_DS_WHEN_COPY),
|
||||
);
|
||||
}
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
depService.off('add-target', targetAddHandler);
|
||||
depService.off('remove-target', targetRemoveHandler);
|
||||
depService.off('dep-update', depUpdateHandler);
|
||||
depService.off('collected', collectedHandler);
|
||||
|
||||
editorService.off('history-change', historyChangeHandler);
|
||||
|
@ -58,26 +58,44 @@ const { codeBlockService, depService, editorService } = services || {};
|
||||
|
||||
// 代码块列表
|
||||
const codeList = computed<TreeNodeData[]>(() =>
|
||||
Object.values(depService?.getTargets(DepTargetType.CODE_BLOCK) || {}).map((target) => {
|
||||
Object.entries(codeBlockService?.getCodeDsl() || {}).map(([codeId, code]) => {
|
||||
const target = depService?.getTarget(codeId, DepTargetType.CODE_BLOCK);
|
||||
|
||||
// 按页面分类显示
|
||||
const pageList: TreeNodeData[] =
|
||||
editorService?.get('root')?.items.map((page) => ({
|
||||
name: page.devconfig?.tabName || page.name,
|
||||
type: 'node',
|
||||
id: `${codeId}_${page.id}`,
|
||||
key: page.id,
|
||||
items: [],
|
||||
})) || [];
|
||||
|
||||
// 组件节点
|
||||
const compNodes: TreeNodeData[] = Object.entries(target.deps).map(([id, dep]) => ({
|
||||
name: dep.name,
|
||||
type: 'node',
|
||||
id: `${target.id}_${id}`,
|
||||
key: id,
|
||||
items: dep.keys.map((key) => {
|
||||
const data: TreeNodeData = { name: `${key}`, id: `${target.id}_${id}_${key}`, type: 'key' };
|
||||
return data;
|
||||
}),
|
||||
}));
|
||||
if (target) {
|
||||
Object.entries(target.deps).forEach(([id, dep]) => {
|
||||
const page = pageList.find((page) => page.key === dep.data?.pageId);
|
||||
page?.items?.push({
|
||||
name: dep.name,
|
||||
type: 'node',
|
||||
id: `${page.id}_${id}`,
|
||||
key: id,
|
||||
items: dep.keys.map((key) => {
|
||||
const data: TreeNodeData = { name: `${key}`, id: `${target.id}_${id}_${key}`, type: 'key' };
|
||||
return data;
|
||||
}),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const data: TreeNodeData = {
|
||||
id: target.id,
|
||||
key: target.id,
|
||||
name: target.name,
|
||||
id: codeId,
|
||||
key: codeId,
|
||||
name: code.name,
|
||||
type: 'code',
|
||||
codeBlockContent: codeBlockService?.getCodeContentById(target.id),
|
||||
items: compNodes,
|
||||
codeBlockContent: codeBlockService?.getCodeContentById(codeId),
|
||||
// 只有一个页面不显示页面分类
|
||||
items: pageList.length > 1 ? pageList.filter((page) => page.items?.length) : pageList[0]?.items || [],
|
||||
};
|
||||
|
||||
return data;
|
||||
|
@ -81,16 +81,19 @@ const getNodeTreeConfig = (id: string, dep: DepData[string], type?: string, pare
|
||||
* @param deps 依赖
|
||||
* @param type 依赖类型
|
||||
*/
|
||||
const mergeChildren = (dsId: Id, items: any[], deps: DepData, type?: string) => {
|
||||
const mergeChildren = (dsId: Id, pageItems: any[], deps: DepData, type?: string) => {
|
||||
Object.entries(deps).forEach(([id, dep]) => {
|
||||
// 按页面分类显示
|
||||
const page = pageItems.find((page) => page.key === dep.data?.pageId);
|
||||
|
||||
// 已经生成过的节点
|
||||
const nodeItem = items.find((item) => item.key === id);
|
||||
const nodeItem = page?.items.find((item: any) => item.key === id);
|
||||
// 节点存在,则追加依赖的key
|
||||
if (nodeItem) {
|
||||
nodeItem.items = nodeItem.items.concat(getKeyTreeConfig(dep, type, nodeItem.key));
|
||||
} else {
|
||||
// 节点不存在,则生成
|
||||
items.push(getNodeTreeConfig(id, dep, type, dsId));
|
||||
page?.items.push(getNodeTreeConfig(id, dep, type, page.id));
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -101,7 +104,15 @@ const list = computed(() =>
|
||||
const dsMethodDeps = dsMethodDep.value[ds.id]?.deps || {};
|
||||
const dsCondDeps = dsCondDep.value[ds.id]?.deps || {};
|
||||
|
||||
const items: any[] = [];
|
||||
const items =
|
||||
editorService?.get('root')?.items.map((page) => ({
|
||||
name: page.devconfig?.tabName || page.name,
|
||||
type: 'node',
|
||||
id: `${ds.id}_${page.id}`,
|
||||
key: page.id,
|
||||
items: [],
|
||||
})) || [];
|
||||
|
||||
// 数据源依赖分为三种类型:key/node、method、cond,是分开存储,这里将其合并展示
|
||||
mergeChildren(ds.id, items, dsDeps);
|
||||
mergeChildren(ds.id, items, dsMethodDeps, 'method');
|
||||
@ -112,7 +123,8 @@ const list = computed(() =>
|
||||
key: ds.id,
|
||||
name: ds.title,
|
||||
type: 'ds',
|
||||
items,
|
||||
// 只有一个页面不显示页面分类
|
||||
items: items.length > 1 ? items.filter((page) => page.items.length) : items[0]?.items || [],
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
@ -20,11 +20,10 @@ import { reactive } from 'vue';
|
||||
import { cloneDeep, get, keys, pick } from 'lodash-es';
|
||||
import type { Writable } from 'type-fest';
|
||||
|
||||
import { DepTargetType } from '@tmagic/dep';
|
||||
import { type CustomTargetOptions, Target, Watcher } from '@tmagic/dep';
|
||||
import type { ColumnConfig } from '@tmagic/form';
|
||||
import type { CodeBlockContent, CodeBlockDSL, Id, MNode } from '@tmagic/schema';
|
||||
|
||||
import depService from '@editor/services/dep';
|
||||
import editorService from '@editor/services/editor';
|
||||
import storageService, { Protocol } from '@editor/services/storage';
|
||||
import type { AsyncHookPlugin, CodeState } from '@editor/type';
|
||||
@ -258,17 +257,26 @@ class CodeBlock extends BaseService {
|
||||
* @param config 组件节点配置
|
||||
* @returns
|
||||
*/
|
||||
public copyWithRelated(config: MNode | MNode[]): void {
|
||||
public copyWithRelated(config: MNode | MNode[], collectorOptions?: CustomTargetOptions): void {
|
||||
const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
|
||||
// 关联的代码块也一并复制
|
||||
depService.clearByType(DepTargetType.RELATED_CODE_WHEN_COPY);
|
||||
depService.collect(copyNodes, true, DepTargetType.RELATED_CODE_WHEN_COPY);
|
||||
const customTarget = depService.getTarget(
|
||||
DepTargetType.RELATED_CODE_WHEN_COPY,
|
||||
DepTargetType.RELATED_CODE_WHEN_COPY,
|
||||
);
|
||||
const copyData: CodeBlockDSL = {};
|
||||
if (customTarget) {
|
||||
|
||||
if (collectorOptions && typeof collectorOptions.isTarget === 'function') {
|
||||
const customTarget = new Target({
|
||||
id: 'related-code-when-copy',
|
||||
...collectorOptions,
|
||||
});
|
||||
|
||||
const coperWatcher = new Watcher();
|
||||
|
||||
coperWatcher.addTarget(customTarget);
|
||||
|
||||
coperWatcher.collect(
|
||||
copyNodes.map((node) => ({ id: `${node.id}`, name: `${node.name || node.id}` })),
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
Object.keys(customTarget.deps).forEach((nodeId: Id) => {
|
||||
const node = editorService.getNodeById(nodeId);
|
||||
if (!node) return;
|
||||
|
@ -3,12 +3,11 @@ import { cloneDeep, get } from 'lodash-es';
|
||||
import { Writable } from 'type-fest';
|
||||
|
||||
import type { EventOption } from '@tmagic/core';
|
||||
import { DepTargetType } from '@tmagic/dep';
|
||||
import { type CustomTargetOptions, Target, Watcher } from '@tmagic/dep';
|
||||
import type { FormConfig } from '@tmagic/form';
|
||||
import type { DataSourceSchema, Id, MNode } from '@tmagic/schema';
|
||||
import { guid, toLine } from '@tmagic/utils';
|
||||
|
||||
import depService from '@editor/services/dep';
|
||||
import editorService from '@editor/services/editor';
|
||||
import storageService, { Protocol } from '@editor/services/storage';
|
||||
import type { DatasourceTypeOption, SyncHookPlugin } from '@editor/type';
|
||||
@ -163,13 +162,26 @@ class DataSource extends BaseService {
|
||||
* @param config 组件节点配置
|
||||
* @returns
|
||||
*/
|
||||
public copyWithRelated(config: MNode | MNode[]): void {
|
||||
public copyWithRelated(config: MNode | MNode[], collectorOptions?: CustomTargetOptions): void {
|
||||
const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
|
||||
depService.clearByType(DepTargetType.RELATED_DS_WHEN_COPY);
|
||||
depService.collect(copyNodes, true, DepTargetType.RELATED_DS_WHEN_COPY);
|
||||
const customTarget = depService.getTarget(DepTargetType.RELATED_DS_WHEN_COPY, DepTargetType.RELATED_DS_WHEN_COPY);
|
||||
const copyData: DataSourceSchema[] = [];
|
||||
if (customTarget) {
|
||||
|
||||
if (collectorOptions && typeof collectorOptions.isTarget === 'function') {
|
||||
const customTarget = new Target({
|
||||
id: 'related-ds-when-copy',
|
||||
...collectorOptions,
|
||||
});
|
||||
|
||||
const coperWatcher = new Watcher();
|
||||
|
||||
coperWatcher.addTarget(customTarget);
|
||||
|
||||
coperWatcher.collect(
|
||||
copyNodes.map((node) => ({ id: `${node.id}`, name: `${node.name || node.id}` })),
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
Object.keys(customTarget.deps).forEach((nodeId: Id) => {
|
||||
const node = editorService.getNodeById(nodeId);
|
||||
if (!node) return;
|
||||
|
@ -17,18 +17,35 @@
|
||||
*/
|
||||
import { reactive } from 'vue';
|
||||
|
||||
import { DepTargetType, type Target, Watcher } from '@tmagic/dep';
|
||||
import { type DepExtendedData, DepTargetType, type Target, type TargetNode, Watcher } from '@tmagic/dep';
|
||||
import type { Id, MNode } from '@tmagic/schema';
|
||||
import { isPage } from '@tmagic/utils';
|
||||
|
||||
import { IdleTask } from '@editor/utils/idle-task';
|
||||
|
||||
import BaseService from './BaseService';
|
||||
|
||||
export interface DepEvents {
|
||||
'add-target': [target: Target];
|
||||
'remove-target': [id: string | number];
|
||||
collected: [nodes: MNode[], deep: boolean];
|
||||
}
|
||||
|
||||
const idleTask = new IdleTask<{ node: TargetNode; deep: boolean; target: Target }>();
|
||||
|
||||
class Dep extends BaseService {
|
||||
private watcher = new Watcher({ initialTargets: reactive({}) });
|
||||
|
||||
public removeTargets(type: string = DepTargetType.DEFAULT) {
|
||||
this.watcher.removeTargets(type);
|
||||
|
||||
this.emit('remove-target');
|
||||
const targets = this.watcher.getTargets(type);
|
||||
|
||||
if (!targets) return;
|
||||
|
||||
for (const target of Object.values(targets)) {
|
||||
this.emit('remove-target', target.id);
|
||||
}
|
||||
}
|
||||
|
||||
public getTargets(type: string = DepTargetType.DEFAULT) {
|
||||
@ -46,18 +63,55 @@ class Dep extends BaseService {
|
||||
|
||||
public removeTarget(id: Id, type: string = DepTargetType.DEFAULT) {
|
||||
this.watcher.removeTarget(id, type);
|
||||
this.emit('remove-target');
|
||||
this.emit('remove-target', id);
|
||||
}
|
||||
|
||||
public clearTargets() {
|
||||
this.watcher.clearTargets();
|
||||
}
|
||||
|
||||
public collect(nodes: MNode[], deep = false, type?: DepTargetType) {
|
||||
this.watcher.collect(nodes, deep, type);
|
||||
public collect(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
|
||||
this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
|
||||
this.collectNode(node, target, depExtendedData, deep);
|
||||
});
|
||||
|
||||
this.emit('collected', nodes, deep);
|
||||
}
|
||||
|
||||
public collectIdle(nodes: MNode[], depExtendedData: DepExtendedData = {}, deep = false, type?: DepTargetType) {
|
||||
this.watcher.collectByCallback(nodes, type, ({ node, target }) => {
|
||||
idleTask.enqueueTask(
|
||||
({ node, deep, target }) => {
|
||||
this.collectNode(node, target, depExtendedData, deep);
|
||||
},
|
||||
{
|
||||
node,
|
||||
deep,
|
||||
target,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
idleTask.once('finish', () => {
|
||||
this.emit('collected', nodes, deep);
|
||||
});
|
||||
}
|
||||
|
||||
collectNode(node: MNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
|
||||
// 先删除原有依赖,重新收集
|
||||
if (isPage(node)) {
|
||||
Object.entries(target.deps).forEach(([depKey, dep]) => {
|
||||
if (dep.data?.pageId && dep.data.pageId === depExtendedData.pageId) {
|
||||
delete target.deps[depKey];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.watcher.removeTargetDep(target, node);
|
||||
}
|
||||
|
||||
this.watcher.collectItem(node, target, depExtendedData, deep);
|
||||
}
|
||||
|
||||
public clear(nodes?: MNode[]) {
|
||||
return this.watcher.clear(nodes);
|
||||
}
|
||||
@ -73,6 +127,24 @@ class Dep extends BaseService {
|
||||
public hasSpecifiedTypeTarget(type: string = DepTargetType.DEFAULT): boolean {
|
||||
return this.watcher.hasSpecifiedTypeTarget(type);
|
||||
}
|
||||
|
||||
public on<Name extends keyof DepEvents, Param extends DepEvents[Name]>(
|
||||
eventName: Name,
|
||||
listener: (...args: Param) => void | Promise<void>,
|
||||
) {
|
||||
return super.on(eventName, listener as any);
|
||||
}
|
||||
|
||||
public once<Name extends keyof DepEvents, Param extends DepEvents[Name]>(
|
||||
eventName: Name,
|
||||
listener: (...args: Param) => void | Promise<void>,
|
||||
) {
|
||||
return super.once(eventName, listener as any);
|
||||
}
|
||||
|
||||
public emit<Name extends keyof DepEvents, Param extends DepEvents[Name]>(eventName: Name, ...args: Param) {
|
||||
return super.emit(eventName, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export type DepService = Dep;
|
||||
|
@ -20,14 +20,13 @@ import { reactive, toRaw } from 'vue';
|
||||
import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es';
|
||||
import { Writable } from 'type-fest';
|
||||
|
||||
import { DepTargetType } from '@tmagic/dep';
|
||||
import { type CustomTargetOptions, Target, Watcher } from '@tmagic/dep';
|
||||
import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment } from '@tmagic/schema';
|
||||
import { NodeType } from '@tmagic/schema';
|
||||
import { calcValueByFontsize, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
|
||||
|
||||
import BaseService from '@editor/services//BaseService';
|
||||
import propsService from '@editor/services//props';
|
||||
import depService from '@editor/services/dep';
|
||||
import historyService from '@editor/services/history';
|
||||
import storageService, { Protocol } from '@editor/services/storage';
|
||||
import type {
|
||||
@ -661,16 +660,26 @@ class Editor extends BaseService {
|
||||
* @param config 组件节点配置
|
||||
* @returns
|
||||
*/
|
||||
public copyWithRelated(config: MNode | MNode[]): void {
|
||||
public copyWithRelated(config: MNode | MNode[], collectorOptions?: CustomTargetOptions): void {
|
||||
const copyNodes: MNode[] = Array.isArray(config) ? config : [config];
|
||||
// 关联的组件也一并复制
|
||||
depService.clearByType(DepTargetType.RELATED_COMP_WHEN_COPY);
|
||||
depService.collect(copyNodes, true, DepTargetType.RELATED_COMP_WHEN_COPY);
|
||||
const customTarget = depService.getTarget(
|
||||
DepTargetType.RELATED_COMP_WHEN_COPY,
|
||||
DepTargetType.RELATED_COMP_WHEN_COPY,
|
||||
);
|
||||
if (customTarget) {
|
||||
|
||||
// 初始化复制组件相关的依赖收集器
|
||||
if (collectorOptions && typeof collectorOptions.isTarget === 'function') {
|
||||
const customTarget = new Target({
|
||||
id: 'related-comp-when-copy',
|
||||
...collectorOptions,
|
||||
});
|
||||
|
||||
const coperWatcher = new Watcher();
|
||||
|
||||
coperWatcher.addTarget(customTarget);
|
||||
|
||||
coperWatcher.collect(
|
||||
copyNodes.map((node) => ({ id: `${node.id}`, name: `${node.name || node.id}` })),
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
Object.keys(customTarget.deps).forEach((nodeId: Id) => {
|
||||
const node = this.getNodeById(nodeId);
|
||||
if (!node) return;
|
||||
@ -686,6 +695,7 @@ class Editor extends BaseService {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
storageService.setItem(COPY_STORAGE_KEY, copyNodes, {
|
||||
protocol: Protocol.OBJECT,
|
||||
});
|
||||
@ -696,7 +706,10 @@ class Editor extends BaseService {
|
||||
* @param position 粘贴的坐标
|
||||
* @returns 添加后的组件节点配置
|
||||
*/
|
||||
public async paste(position: PastePosition = {}): Promise<MNode | MNode[] | void> {
|
||||
public async paste(
|
||||
position: PastePosition = {},
|
||||
collectorOptions?: CustomTargetOptions,
|
||||
): Promise<MNode | MNode[] | void> {
|
||||
const config: MNode[] = storageService.getItem(COPY_STORAGE_KEY);
|
||||
if (!Array.isArray(config)) return;
|
||||
|
||||
@ -711,7 +724,11 @@ class Editor extends BaseService {
|
||||
}
|
||||
}
|
||||
const pasteConfigs = await this.doPaste(config, position);
|
||||
propsService.replaceRelateId(config, pasteConfigs);
|
||||
|
||||
if (collectorOptions && typeof collectorOptions.isTarget === 'function') {
|
||||
propsService.replaceRelateId(config, pasteConfigs, collectorOptions);
|
||||
}
|
||||
|
||||
return this.add(pasteConfigs, parent);
|
||||
}
|
||||
|
||||
|
@ -20,12 +20,11 @@ import { reactive } from 'vue';
|
||||
import { cloneDeep, mergeWith } from 'lodash-es';
|
||||
import { Writable } from 'type-fest';
|
||||
|
||||
import { DepTargetType } from '@tmagic/dep';
|
||||
import { type CustomTargetOptions, Target, Watcher } from '@tmagic/dep';
|
||||
import type { FormConfig } from '@tmagic/form';
|
||||
import type { Id, MComponent, MNode } from '@tmagic/schema';
|
||||
import { getNodePath, getValueByKeyPath, guid, setValueByKeyPath, toLine } from '@tmagic/utils';
|
||||
|
||||
import depService from '@editor/services/dep';
|
||||
import editorService from '@editor/services/editor';
|
||||
import type { AsyncHookPlugin, PropsState, SyncHookPlugin } from '@editor/type';
|
||||
import { fillConfig } from '@editor/utils/props';
|
||||
@ -196,13 +195,21 @@ class Props extends BaseService {
|
||||
* @param originConfigs 原组件配置
|
||||
* @param targetConfigs 待替换的组件配置
|
||||
*/
|
||||
public replaceRelateId(originConfigs: MNode[], targetConfigs: MNode[]) {
|
||||
public replaceRelateId(originConfigs: MNode[], targetConfigs: MNode[], collectorOptions: CustomTargetOptions) {
|
||||
const relateIdMap = this.getRelateIdMap();
|
||||
|
||||
if (Object.keys(relateIdMap).length === 0) return;
|
||||
depService.clearByType(DepTargetType.RELATED_COMP_WHEN_COPY);
|
||||
depService.collect(originConfigs, true, DepTargetType.RELATED_COMP_WHEN_COPY);
|
||||
const target = depService.getTarget(DepTargetType.RELATED_COMP_WHEN_COPY, DepTargetType.RELATED_COMP_WHEN_COPY);
|
||||
if (!target) return;
|
||||
|
||||
const target = new Target({
|
||||
id: 'related-comp-when-copy',
|
||||
...collectorOptions,
|
||||
});
|
||||
|
||||
const coperWatcher = new Watcher();
|
||||
|
||||
coperWatcher.addTarget(target);
|
||||
coperWatcher.collect(originConfigs);
|
||||
|
||||
originConfigs.forEach((config: MNode) => {
|
||||
const newId = relateIdMap[config.id];
|
||||
const path = getNodePath(newId, targetConfigs);
|
||||
@ -219,7 +226,7 @@ class Props extends BaseService {
|
||||
|
||||
// 递归items
|
||||
if (config.items && Array.isArray(config.items)) {
|
||||
this.replaceRelateId(config.items, targetConfigs);
|
||||
this.replaceRelateId(config.items, targetConfigs, collectorOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
72
packages/editor/src/utils/idle-task.ts
Normal file
72
packages/editor/src/utils/idle-task.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
export interface IdleTaskEvents {
|
||||
finish: [];
|
||||
}
|
||||
|
||||
globalThis.requestIdleCallback =
|
||||
globalThis.requestIdleCallback ||
|
||||
function (cb) {
|
||||
const start = Date.now();
|
||||
return setTimeout(() => {
|
||||
cb({
|
||||
didTimeout: false,
|
||||
timeRemaining() {
|
||||
return Math.max(0, 50 - (Date.now() - start));
|
||||
},
|
||||
});
|
||||
}, 1);
|
||||
};
|
||||
|
||||
export class IdleTask<T = any> extends EventEmitter {
|
||||
private taskList: {
|
||||
handler: (data: T) => void;
|
||||
data: T;
|
||||
}[] = [];
|
||||
|
||||
private taskHandle: number | null = null;
|
||||
|
||||
public enqueueTask(taskHandler: (data: T) => void, taskData: T) {
|
||||
this.taskList.push({
|
||||
handler: taskHandler,
|
||||
data: taskData,
|
||||
});
|
||||
|
||||
if (!this.taskHandle) {
|
||||
this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 10000 });
|
||||
}
|
||||
}
|
||||
|
||||
public on<Name extends keyof IdleTaskEvents, Param extends IdleTaskEvents[Name]>(
|
||||
eventName: Name,
|
||||
listener: (...args: Param) => void | Promise<void>,
|
||||
) {
|
||||
return super.on(eventName, listener as any);
|
||||
}
|
||||
|
||||
public once<Name extends keyof IdleTaskEvents, Param extends IdleTaskEvents[Name]>(
|
||||
eventName: Name,
|
||||
listener: (...args: Param) => void | Promise<void>,
|
||||
) {
|
||||
return super.once(eventName, listener as any);
|
||||
}
|
||||
|
||||
public emit<Name extends keyof IdleTaskEvents, Param extends IdleTaskEvents[Name]>(eventName: Name, ...args: Param) {
|
||||
return super.emit(eventName, ...args);
|
||||
}
|
||||
|
||||
private runTaskQueue(deadline: IdleDeadline) {
|
||||
while ((deadline.timeRemaining() > 15 || deadline.didTimeout) && this.taskList.length) {
|
||||
const task = this.taskList.shift();
|
||||
task!.handler(task!.data);
|
||||
}
|
||||
|
||||
if (this.taskList.length) {
|
||||
this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 10000 });
|
||||
} else {
|
||||
this.taskHandle = 0;
|
||||
|
||||
this.emit('finish');
|
||||
}
|
||||
}
|
||||
}
|
@ -275,6 +275,7 @@ export interface DepData {
|
||||
/** 组件名称 */
|
||||
name: string;
|
||||
keys: (string | number)[];
|
||||
data?: Record<string, any>;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,6 @@
|
||||
"devDependencies": {
|
||||
"@types/react": "^17.0.37",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"typescript": "^5.4.2"
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@
|
||||
"@vue/compiler-sfc": "^3.4.27",
|
||||
"sass": "^1.77.0",
|
||||
"terser": "^5.31.0",
|
||||
"typescript": "^5.4.2",
|
||||
"typescript": "^5.4.5",
|
||||
"unplugin-auto-import": "^0.12.0",
|
||||
"unplugin-vue-components": "^0.22.11",
|
||||
"vite": "^5.2.11"
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { Files, FolderOpened, Grid, PictureFilled, SwitchButton, Ticket, Tickets } from '@element-plus/icons-vue';
|
||||
|
||||
import type { ComponentGroup } from '@tmagic/editor';
|
||||
|
||||
export default [
|
||||
{
|
||||
title: '示例容器',
|
||||
@ -90,4 +92,4 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
] as ComponentGroup[];
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="editor-app">
|
||||
<m-editor
|
||||
<TMagicEditor
|
||||
v-model="value"
|
||||
ref="editor"
|
||||
:menu="menu"
|
||||
@ -17,7 +17,6 @@
|
||||
:moveable-options="moveableOptions"
|
||||
:auto-scroll-into-view="true"
|
||||
:stage-rect="stageRect"
|
||||
:collector-options="collectorOptions"
|
||||
:layerContentMenu="contentMenuData"
|
||||
:stageContentMenu="contentMenuData"
|
||||
@props-submit-error="propsSubmitErrorHandler"
|
||||
@ -25,7 +24,7 @@
|
||||
<template #workspace-content>
|
||||
<DeviceGroup ref="deviceGroup" v-model="stageRect"></DeviceGroup>
|
||||
</template>
|
||||
</m-editor>
|
||||
</TMagicEditor>
|
||||
|
||||
<TMagicDialog v-model="previewVisible" destroy-on-close class="pre-viewer" title="预览" :width="stageRect?.width">
|
||||
<iframe
|
||||
@ -51,13 +50,12 @@ import { TMagicDialog, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||
import {
|
||||
ContentMenu,
|
||||
COPY_STORAGE_KEY,
|
||||
DatasourceTypeOption,
|
||||
DepTargetType,
|
||||
type DatasourceTypeOption,
|
||||
editorService,
|
||||
MenuBarData,
|
||||
MenuButton,
|
||||
MoveableOptions,
|
||||
Services,
|
||||
type MenuBarData,
|
||||
type MenuButton,
|
||||
type MoveableOptions,
|
||||
type Services,
|
||||
TMagicEditor,
|
||||
} from '@tmagic/editor';
|
||||
import type { MContainer, MNode } from '@tmagic/schema';
|
||||
@ -136,16 +134,14 @@ const usePasteMenu = (menu?: Ref<InstanceType<typeof ContentMenu> | undefined>):
|
||||
},
|
||||
});
|
||||
|
||||
const contentMenuData = computed(() => [
|
||||
const contentMenuData = computed<MenuButton[]>(() => [
|
||||
{
|
||||
type: 'button',
|
||||
text: '复制(带关联信息)',
|
||||
icon: markRaw(CopyDocument),
|
||||
display: (services: Services) =>
|
||||
services?.depService?.hasSpecifiedTypeTarget(DepTargetType.RELATED_COMP_WHEN_COPY) || false,
|
||||
handler: (services: Services) => {
|
||||
const nodes = services?.editorService?.get('nodes');
|
||||
nodes && services?.editorService?.copyWithRelated(cloneDeep(nodes));
|
||||
nodes && services?.editorService?.copyWithRelated(cloneDeep(nodes), collectorOptions);
|
||||
nodes && services?.codeBlockService?.copyWithRelated(cloneDeep(nodes));
|
||||
nodes && services?.dataSourceService?.copyWithRelated(cloneDeep(nodes));
|
||||
},
|
||||
|
798
pnpm-lock.yaml
generated
798
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -42,7 +42,7 @@
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@vitejs/plugin-legacy": "^5.4.0",
|
||||
"@vitejs/plugin-react-refresh": "^1.3.1",
|
||||
"typescript": "^5.4.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11"
|
||||
}
|
||||
}
|
||||
|
@ -46,7 +46,7 @@
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/compiler-sfc": "^3.4.27",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.4.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11",
|
||||
"vue-tsc": "^2.0.16"
|
||||
}
|
||||
|
@ -45,6 +45,6 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.19.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"typescript": "^5.4.2"
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@
|
||||
"rollup-plugin-external-globals": "^0.10.0",
|
||||
"sass": "^1.77.0",
|
||||
"terser": "^5.31.0",
|
||||
"typescript": "^5.4.2",
|
||||
"typescript": "^5.4.5",
|
||||
"vite": "^5.2.11"
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user