mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-05 19:41:40 +08:00
fix(editor): 修复修改页面配置后可能改造页面卡死问题
This commit is contained in:
parent
5dad9f0ec9
commit
fc38fc3957
@ -1,5 +1,5 @@
|
||||
import { onBeforeUnmount, reactive, toRaw, watch } from 'vue';
|
||||
import { cloneDeep, debounce } from 'lodash-es';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import type {
|
||||
CodeBlockContent,
|
||||
@ -19,7 +19,7 @@ import {
|
||||
DepTargetType,
|
||||
Target,
|
||||
} from '@tmagic/core';
|
||||
import { isPage, traverseNode } from '@tmagic/utils';
|
||||
import { getNodes, isPage, traverseNode } from '@tmagic/utils';
|
||||
|
||||
import PropsPanel from './layouts/PropsPanel.vue';
|
||||
import { EditorProps } from './editorProps';
|
||||
@ -252,62 +252,23 @@ export const initServiceEvents = (
|
||||
return stage?.renderer?.runtime?.getApp?.();
|
||||
};
|
||||
|
||||
const updateDataSourceSchema = () => {
|
||||
const updateDataSourceSchema = (nodes: MNode[], deep: boolean) => {
|
||||
const root = editorService.get('root');
|
||||
|
||||
if (root?.dataSources) {
|
||||
getApp()?.dataSourceManager?.updateSchema(root.dataSources);
|
||||
}
|
||||
};
|
||||
|
||||
const targetAddHandler = (target: Target) => {
|
||||
const root = editorService.get('root');
|
||||
if (!root) return;
|
||||
|
||||
if (target.type === DepTargetType.DATA_SOURCE) {
|
||||
if (!root.dataSourceDeps) {
|
||||
root.dataSourceDeps = {};
|
||||
}
|
||||
root.dataSourceDeps[target.id] = target.deps;
|
||||
}
|
||||
|
||||
if (target.type === DepTargetType.DATA_SOURCE_COND) {
|
||||
if (!root.dataSourceCondDeps) {
|
||||
root.dataSourceCondDeps = {};
|
||||
}
|
||||
root.dataSourceCondDeps[target.id] = target.deps;
|
||||
}
|
||||
};
|
||||
|
||||
const targetRemoveHandler = (id: string | number) => {
|
||||
const root = editorService.get('root');
|
||||
|
||||
if (root?.dataSourceDeps) {
|
||||
delete root.dataSourceDeps[id];
|
||||
}
|
||||
|
||||
if (root?.dataSourceCondDeps) {
|
||||
delete root.dataSourceCondDeps[id];
|
||||
}
|
||||
};
|
||||
|
||||
const collectedHandler = debounce((nodes: MNode[], deep: boolean) => {
|
||||
const root = editorService.get('root');
|
||||
const stage = editorService.get('stage');
|
||||
|
||||
if (!root || !stage) return;
|
||||
|
||||
const app = getApp();
|
||||
|
||||
if (!app) return;
|
||||
|
||||
if (app.dsl) {
|
||||
if (root && app?.dsl) {
|
||||
app.dsl.dataSourceDeps = root.dataSourceDeps;
|
||||
app.dsl.dataSourceCondDeps = root.dataSourceCondDeps;
|
||||
app.dsl.dataSources = root.dataSources;
|
||||
}
|
||||
|
||||
updateDataSourceSchema();
|
||||
if (root?.dataSources) {
|
||||
getApp()?.dataSourceManager?.updateSchema(root.dataSources);
|
||||
}
|
||||
|
||||
const stage = editorService.get('stage');
|
||||
|
||||
if (!root || !stage) return;
|
||||
|
||||
const allNodes: MNode[] = [];
|
||||
|
||||
@ -335,11 +296,60 @@ export const initServiceEvents = (
|
||||
});
|
||||
});
|
||||
});
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const afterUpdateNodes = (nodes: MNode[]) => {
|
||||
const root = editorService.get('root');
|
||||
if (!root) return;
|
||||
const stage = editorService.get('stage');
|
||||
const app = getApp();
|
||||
if (app?.dsl) {
|
||||
app.dsl.dataSourceDeps = root.dataSourceDeps;
|
||||
}
|
||||
for (const node of nodes) {
|
||||
stage?.update({
|
||||
config: cloneDeep(node),
|
||||
parentId: editorService.getParentById(node.id)?.id,
|
||||
root: cloneDeep(root),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const targetAddHandler = (target: Target) => {
|
||||
const root = editorService.get('root');
|
||||
if (!root) return;
|
||||
|
||||
if (target.type === DepTargetType.DATA_SOURCE) {
|
||||
if (!root.dataSourceDeps) {
|
||||
root.dataSourceDeps = {};
|
||||
}
|
||||
root.dataSourceDeps[target.id] = target.deps;
|
||||
}
|
||||
|
||||
if (target.type === DepTargetType.DATA_SOURCE_COND) {
|
||||
if (!root.dataSourceCondDeps) {
|
||||
root.dataSourceCondDeps = {};
|
||||
}
|
||||
root.dataSourceCondDeps[target.id] = target.deps;
|
||||
}
|
||||
};
|
||||
|
||||
const targetRemoveHandler = (id: string | number) => {
|
||||
const root = editorService.get('root');
|
||||
|
||||
if (!root) return;
|
||||
|
||||
if (root.dataSourceDeps) {
|
||||
delete root.dataSourceDeps[id];
|
||||
}
|
||||
|
||||
if (root.dataSourceCondDeps) {
|
||||
delete root.dataSourceCondDeps[id];
|
||||
}
|
||||
};
|
||||
|
||||
depService.on('add-target', targetAddHandler);
|
||||
depService.on('remove-target', targetRemoveHandler);
|
||||
depService.on('collected', collectedHandler);
|
||||
|
||||
const initDataSourceDepTarget = (ds: DataSourceSchema) => {
|
||||
depService.addTarget(createDataSourceTarget(ds, reactive({})));
|
||||
@ -347,28 +357,33 @@ export const initServiceEvents = (
|
||||
depService.addTarget(createDataSourceCondTarget(ds, reactive({})));
|
||||
};
|
||||
|
||||
const collectIdle = (nodes: MNode[], deep: boolean) => {
|
||||
nodes.forEach((node) => {
|
||||
let pageId: Id | undefined;
|
||||
const collectIdle = (nodes: MNode[], deep: boolean) =>
|
||||
Promise.all(
|
||||
nodes.map((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);
|
||||
});
|
||||
};
|
||||
if (isPage(node)) {
|
||||
pageId = node.id;
|
||||
} else {
|
||||
const info = editorService.getNodeInfo(node.id);
|
||||
pageId = info.page?.id;
|
||||
}
|
||||
return depService.collectIdle([node], { pageId }, deep);
|
||||
}),
|
||||
);
|
||||
|
||||
// 新增节点,收集依赖
|
||||
const nodeAddHandler = (nodes: MNode[]) => {
|
||||
collectIdle(nodes, true);
|
||||
collectIdle(nodes, true).then(() => {
|
||||
afterUpdateNodes(nodes);
|
||||
});
|
||||
};
|
||||
|
||||
// 节点更新,收集依赖
|
||||
const nodeUpdateHandler = (nodes: MNode[]) => {
|
||||
collectIdle(nodes, true);
|
||||
collectIdle(nodes, true).then(() => {
|
||||
afterUpdateNodes(nodes);
|
||||
});
|
||||
};
|
||||
|
||||
// 节点删除,清除对齐的依赖收集
|
||||
@ -378,7 +393,9 @@ export const initServiceEvents = (
|
||||
|
||||
// 由于历史记录变化是更新整个page,所以历史记录变化时,需要重新收集依赖
|
||||
const historyChangeHandler = (page: MPage | MPageFragment) => {
|
||||
collectIdle([page], true);
|
||||
collectIdle([page], true).then(() => {
|
||||
updateDataSourceSchema([page], true);
|
||||
});
|
||||
};
|
||||
|
||||
editorService.on('history-change', historyChangeHandler);
|
||||
@ -413,7 +430,9 @@ export const initServiceEvents = (
|
||||
removeDataSourceTarget(config.id);
|
||||
initDataSourceDepTarget(config);
|
||||
|
||||
collectIdle(root?.items || [], true);
|
||||
collectIdle(root?.items || [], true).then(() => {
|
||||
updateDataSourceSchema(root?.items || [], true);
|
||||
});
|
||||
};
|
||||
|
||||
const removeDataSourceTarget = (id: string) => {
|
||||
@ -423,8 +442,14 @@ export const initServiceEvents = (
|
||||
};
|
||||
|
||||
const dataSourceRemoveHandler = (id: string) => {
|
||||
const root = editorService.get('root');
|
||||
const nodeIds = Object.keys(root?.dataSourceDeps?.[id] || {});
|
||||
const nodes = getNodes(nodeIds, root?.items);
|
||||
collectIdle(nodes, false).then(() => {
|
||||
updateDataSourceSchema(nodes, false);
|
||||
});
|
||||
|
||||
removeDataSourceTarget(id);
|
||||
getApp()?.dataSourceManager?.removeDataSource(id);
|
||||
};
|
||||
|
||||
dataSourceService.on('add', dataSourceAddHandler);
|
||||
@ -434,7 +459,6 @@ export const initServiceEvents = (
|
||||
onBeforeUnmount(() => {
|
||||
depService.off('add-target', targetAddHandler);
|
||||
depService.off('remove-target', targetRemoveHandler);
|
||||
depService.off('collected', collectedHandler);
|
||||
|
||||
editorService.off('history-change', historyChangeHandler);
|
||||
editorService.off('root-change', rootChangeHandler);
|
||||
|
@ -132,9 +132,20 @@ watch(zoom, (zoom) => {
|
||||
stage.setZoom(zoom);
|
||||
});
|
||||
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
watch(page, (page) => {
|
||||
if (runtime && page) {
|
||||
services?.editorService.set('stageLoading', true);
|
||||
|
||||
if (timeoutId) {
|
||||
globalThis.clearTimeout(timeoutId);
|
||||
}
|
||||
|
||||
timeoutId = globalThis.setTimeout(() => {
|
||||
services?.editorService.set('stageLoading', false);
|
||||
timeoutId = null;
|
||||
}, 3000);
|
||||
|
||||
runtime.updatePageId?.(page.id);
|
||||
nextTick(() => {
|
||||
stage?.select(page.id);
|
||||
|
@ -92,8 +92,11 @@ class Dep extends BaseService {
|
||||
);
|
||||
});
|
||||
|
||||
idleTask.once('finish', () => {
|
||||
this.emit('collected', nodes, deep);
|
||||
return new Promise<void>((resolve) => {
|
||||
idleTask.once('finish', () => {
|
||||
this.emit('collected', nodes, deep);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -20,10 +20,19 @@ import { reactive, toRaw } from 'vue';
|
||||
import { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es';
|
||||
import type { Writable } from 'type-fest';
|
||||
|
||||
import type { Id, MApp, MComponent, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
|
||||
import type { Id, MApp, MContainer, MNode, MPage, MPageFragment, TargetOptions } from '@tmagic/core';
|
||||
import { NodeType, Target, Watcher } from '@tmagic/core';
|
||||
import { isFixed } from '@tmagic/stage';
|
||||
import { calcValueByFontsize, getElById, getNodePath, isNumber, isPage, isPageFragment, isPop } from '@tmagic/utils';
|
||||
import {
|
||||
calcValueByFontsize,
|
||||
getElById,
|
||||
getNodeInfo,
|
||||
getNodePath,
|
||||
isNumber,
|
||||
isPage,
|
||||
isPageFragment,
|
||||
isPop,
|
||||
} from '@tmagic/utils';
|
||||
|
||||
import BaseService from '@editor/services//BaseService';
|
||||
import propsService from '@editor/services//props';
|
||||
@ -175,36 +184,7 @@ class Editor extends BaseService {
|
||||
root = toRaw(root);
|
||||
}
|
||||
|
||||
const info: EditorNodeInfo = {
|
||||
node: null,
|
||||
parent: null,
|
||||
page: null,
|
||||
};
|
||||
|
||||
if (!root) return info;
|
||||
|
||||
if (id === root.id) {
|
||||
info.node = root;
|
||||
return info;
|
||||
}
|
||||
|
||||
const path = getNodePath(id, root.items);
|
||||
|
||||
if (!path.length) return info;
|
||||
|
||||
path.unshift(root);
|
||||
|
||||
info.node = path[path.length - 1] as MComponent;
|
||||
info.parent = path[path.length - 2] as MContainer;
|
||||
|
||||
path.forEach((item) => {
|
||||
if (isPage(item) || isPageFragment(item)) {
|
||||
info.page = item as MPage | MPageFragment;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return info;
|
||||
return getNodeInfo(id, root);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -586,11 +566,7 @@ class Editor extends BaseService {
|
||||
nodes.splice(targetIndex, 1, newConfig);
|
||||
this.set('nodes', [...nodes]);
|
||||
|
||||
this.get('stage')?.update({
|
||||
config: cloneDeep(newConfig),
|
||||
parentId: parent.id,
|
||||
root: cloneDeep(root),
|
||||
});
|
||||
// update后会触发依赖收集,收集完后会掉stage.update方法
|
||||
|
||||
if (isPage(newConfig) || isPageFragment(newConfig)) {
|
||||
this.set('page', newConfig as MPage | MPageFragment);
|
||||
@ -875,7 +851,11 @@ class Editor extends BaseService {
|
||||
await stage.select(targetId);
|
||||
|
||||
const targetParent = this.getParentById(target.id);
|
||||
await stage.update({ config: cloneDeep(target), parentId: targetParent?.id, root: cloneDeep(root) });
|
||||
await stage.update({
|
||||
config: cloneDeep(target),
|
||||
parentId: targetParent?.id,
|
||||
root: cloneDeep(root),
|
||||
});
|
||||
|
||||
await this.select(newConfig);
|
||||
stage.select(newConfig.id);
|
||||
|
@ -56,13 +56,14 @@ export class IdleTask<T = any> extends EventEmitter {
|
||||
}
|
||||
|
||||
private runTaskQueue(deadline: IdleDeadline) {
|
||||
while ((deadline.timeRemaining() > 15 || deadline.didTimeout) && this.taskList.length) {
|
||||
// 动画会占用空闲时间,当任务一直无法执行时,看看是否有动画正在播放
|
||||
while ((deadline.timeRemaining() > 10 || 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 });
|
||||
this.taskHandle = globalThis.requestIdleCallback(this.runTaskQueue.bind(this), { timeout: 300 });
|
||||
} else {
|
||||
this.taskHandle = 0;
|
||||
|
||||
|
@ -18,9 +18,22 @@
|
||||
|
||||
import { cloneDeep, set as objectSet } from 'lodash-es';
|
||||
|
||||
import type { DataSchema, DataSourceDeps, Id, MComponent, MNode, MNodeInstance } from '@tmagic/schema';
|
||||
import type {
|
||||
DataSchema,
|
||||
DataSourceDeps,
|
||||
Id,
|
||||
MApp,
|
||||
MComponent,
|
||||
MContainer,
|
||||
MNode,
|
||||
MNodeInstance,
|
||||
MPage,
|
||||
MPageFragment,
|
||||
} from '@tmagic/schema';
|
||||
import { NodeType } from '@tmagic/schema';
|
||||
|
||||
import type { EditorNodeInfo } from '@editor/type';
|
||||
|
||||
export * from './dom';
|
||||
|
||||
export const sleep = (ms: number): Promise<void> =>
|
||||
@ -78,6 +91,39 @@ export const getNodePath = (id: Id, data: MNode[] = []): MNode[] => {
|
||||
return path;
|
||||
};
|
||||
|
||||
export const getNodeInfo = (id: Id, root: MApp | null) => {
|
||||
const info: EditorNodeInfo = {
|
||||
node: null,
|
||||
parent: null,
|
||||
page: null,
|
||||
};
|
||||
|
||||
if (!root) return info;
|
||||
|
||||
if (id === root.id) {
|
||||
info.node = root;
|
||||
return info;
|
||||
}
|
||||
|
||||
const path = getNodePath(id, root.items);
|
||||
|
||||
if (!path.length) return info;
|
||||
|
||||
path.unshift(root);
|
||||
|
||||
info.node = path[path.length - 1] as MComponent;
|
||||
info.parent = path[path.length - 2] as MContainer;
|
||||
|
||||
path.forEach((item) => {
|
||||
if (isPage(item) || isPageFragment(item)) {
|
||||
info.page = item as MPage | MPageFragment;
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
return info;
|
||||
};
|
||||
|
||||
export const filterXSS = (str: string) =>
|
||||
str.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user