mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-04-06 03:57:56 +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 { onBeforeUnmount, reactive, toRaw, watch } from 'vue';
|
||||||
import { cloneDeep, debounce } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
CodeBlockContent,
|
CodeBlockContent,
|
||||||
@ -19,7 +19,7 @@ import {
|
|||||||
DepTargetType,
|
DepTargetType,
|
||||||
Target,
|
Target,
|
||||||
} from '@tmagic/core';
|
} from '@tmagic/core';
|
||||||
import { isPage, traverseNode } from '@tmagic/utils';
|
import { getNodes, isPage, traverseNode } from '@tmagic/utils';
|
||||||
|
|
||||||
import PropsPanel from './layouts/PropsPanel.vue';
|
import PropsPanel from './layouts/PropsPanel.vue';
|
||||||
import { EditorProps } from './editorProps';
|
import { EditorProps } from './editorProps';
|
||||||
@ -252,62 +252,23 @@ export const initServiceEvents = (
|
|||||||
return stage?.renderer?.runtime?.getApp?.();
|
return stage?.renderer?.runtime?.getApp?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDataSourceSchema = () => {
|
const updateDataSourceSchema = (nodes: MNode[], deep: boolean) => {
|
||||||
const root = editorService.get('root');
|
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();
|
const app = getApp();
|
||||||
|
|
||||||
if (!app) return;
|
if (root && app?.dsl) {
|
||||||
|
|
||||||
if (app.dsl) {
|
|
||||||
app.dsl.dataSourceDeps = root.dataSourceDeps;
|
app.dsl.dataSourceDeps = root.dataSourceDeps;
|
||||||
app.dsl.dataSourceCondDeps = root.dataSourceCondDeps;
|
app.dsl.dataSourceCondDeps = root.dataSourceCondDeps;
|
||||||
app.dsl.dataSources = root.dataSources;
|
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[] = [];
|
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('add-target', targetAddHandler);
|
||||||
depService.on('remove-target', targetRemoveHandler);
|
depService.on('remove-target', targetRemoveHandler);
|
||||||
depService.on('collected', collectedHandler);
|
|
||||||
|
|
||||||
const initDataSourceDepTarget = (ds: DataSourceSchema) => {
|
const initDataSourceDepTarget = (ds: DataSourceSchema) => {
|
||||||
depService.addTarget(createDataSourceTarget(ds, reactive({})));
|
depService.addTarget(createDataSourceTarget(ds, reactive({})));
|
||||||
@ -347,28 +357,33 @@ export const initServiceEvents = (
|
|||||||
depService.addTarget(createDataSourceCondTarget(ds, reactive({})));
|
depService.addTarget(createDataSourceCondTarget(ds, reactive({})));
|
||||||
};
|
};
|
||||||
|
|
||||||
const collectIdle = (nodes: MNode[], deep: boolean) => {
|
const collectIdle = (nodes: MNode[], deep: boolean) =>
|
||||||
nodes.forEach((node) => {
|
Promise.all(
|
||||||
let pageId: Id | undefined;
|
nodes.map((node) => {
|
||||||
|
let pageId: Id | undefined;
|
||||||
|
|
||||||
if (isPage(node)) {
|
if (isPage(node)) {
|
||||||
pageId = node.id;
|
pageId = node.id;
|
||||||
} else {
|
} else {
|
||||||
const info = editorService.getNodeInfo(node.id);
|
const info = editorService.getNodeInfo(node.id);
|
||||||
pageId = info.page?.id;
|
pageId = info.page?.id;
|
||||||
}
|
}
|
||||||
depService.collectIdle([node], { pageId }, deep);
|
return depService.collectIdle([node], { pageId }, deep);
|
||||||
});
|
}),
|
||||||
};
|
);
|
||||||
|
|
||||||
// 新增节点,收集依赖
|
// 新增节点,收集依赖
|
||||||
const nodeAddHandler = (nodes: MNode[]) => {
|
const nodeAddHandler = (nodes: MNode[]) => {
|
||||||
collectIdle(nodes, true);
|
collectIdle(nodes, true).then(() => {
|
||||||
|
afterUpdateNodes(nodes);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 节点更新,收集依赖
|
// 节点更新,收集依赖
|
||||||
const nodeUpdateHandler = (nodes: MNode[]) => {
|
const nodeUpdateHandler = (nodes: MNode[]) => {
|
||||||
collectIdle(nodes, true);
|
collectIdle(nodes, true).then(() => {
|
||||||
|
afterUpdateNodes(nodes);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 节点删除,清除对齐的依赖收集
|
// 节点删除,清除对齐的依赖收集
|
||||||
@ -378,7 +393,9 @@ export const initServiceEvents = (
|
|||||||
|
|
||||||
// 由于历史记录变化是更新整个page,所以历史记录变化时,需要重新收集依赖
|
// 由于历史记录变化是更新整个page,所以历史记录变化时,需要重新收集依赖
|
||||||
const historyChangeHandler = (page: MPage | MPageFragment) => {
|
const historyChangeHandler = (page: MPage | MPageFragment) => {
|
||||||
collectIdle([page], true);
|
collectIdle([page], true).then(() => {
|
||||||
|
updateDataSourceSchema([page], true);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
editorService.on('history-change', historyChangeHandler);
|
editorService.on('history-change', historyChangeHandler);
|
||||||
@ -413,7 +430,9 @@ export const initServiceEvents = (
|
|||||||
removeDataSourceTarget(config.id);
|
removeDataSourceTarget(config.id);
|
||||||
initDataSourceDepTarget(config);
|
initDataSourceDepTarget(config);
|
||||||
|
|
||||||
collectIdle(root?.items || [], true);
|
collectIdle(root?.items || [], true).then(() => {
|
||||||
|
updateDataSourceSchema(root?.items || [], true);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeDataSourceTarget = (id: string) => {
|
const removeDataSourceTarget = (id: string) => {
|
||||||
@ -423,8 +442,14 @@ export const initServiceEvents = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const dataSourceRemoveHandler = (id: string) => {
|
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);
|
removeDataSourceTarget(id);
|
||||||
getApp()?.dataSourceManager?.removeDataSource(id);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dataSourceService.on('add', dataSourceAddHandler);
|
dataSourceService.on('add', dataSourceAddHandler);
|
||||||
@ -434,7 +459,6 @@ export const initServiceEvents = (
|
|||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
depService.off('add-target', targetAddHandler);
|
depService.off('add-target', targetAddHandler);
|
||||||
depService.off('remove-target', targetRemoveHandler);
|
depService.off('remove-target', targetRemoveHandler);
|
||||||
depService.off('collected', collectedHandler);
|
|
||||||
|
|
||||||
editorService.off('history-change', historyChangeHandler);
|
editorService.off('history-change', historyChangeHandler);
|
||||||
editorService.off('root-change', rootChangeHandler);
|
editorService.off('root-change', rootChangeHandler);
|
||||||
|
@ -132,9 +132,20 @@ watch(zoom, (zoom) => {
|
|||||||
stage.setZoom(zoom);
|
stage.setZoom(zoom);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let timeoutId: NodeJS.Timeout | null = null;
|
||||||
watch(page, (page) => {
|
watch(page, (page) => {
|
||||||
if (runtime && page) {
|
if (runtime && page) {
|
||||||
services?.editorService.set('stageLoading', true);
|
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);
|
runtime.updatePageId?.(page.id);
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
stage?.select(page.id);
|
stage?.select(page.id);
|
||||||
|
@ -92,8 +92,11 @@ class Dep extends BaseService {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
idleTask.once('finish', () => {
|
return new Promise<void>((resolve) => {
|
||||||
this.emit('collected', nodes, deep);
|
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 { cloneDeep, get, isObject, mergeWith, uniq } from 'lodash-es';
|
||||||
import type { Writable } from 'type-fest';
|
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 { NodeType, Target, Watcher } from '@tmagic/core';
|
||||||
import { isFixed } from '@tmagic/stage';
|
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 BaseService from '@editor/services//BaseService';
|
||||||
import propsService from '@editor/services//props';
|
import propsService from '@editor/services//props';
|
||||||
@ -175,36 +184,7 @@ class Editor extends BaseService {
|
|||||||
root = toRaw(root);
|
root = toRaw(root);
|
||||||
}
|
}
|
||||||
|
|
||||||
const info: EditorNodeInfo = {
|
return getNodeInfo(id, root);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -586,11 +566,7 @@ class Editor extends BaseService {
|
|||||||
nodes.splice(targetIndex, 1, newConfig);
|
nodes.splice(targetIndex, 1, newConfig);
|
||||||
this.set('nodes', [...nodes]);
|
this.set('nodes', [...nodes]);
|
||||||
|
|
||||||
this.get('stage')?.update({
|
// update后会触发依赖收集,收集完后会掉stage.update方法
|
||||||
config: cloneDeep(newConfig),
|
|
||||||
parentId: parent.id,
|
|
||||||
root: cloneDeep(root),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isPage(newConfig) || isPageFragment(newConfig)) {
|
if (isPage(newConfig) || isPageFragment(newConfig)) {
|
||||||
this.set('page', newConfig as MPage | MPageFragment);
|
this.set('page', newConfig as MPage | MPageFragment);
|
||||||
@ -875,7 +851,11 @@ class Editor extends BaseService {
|
|||||||
await stage.select(targetId);
|
await stage.select(targetId);
|
||||||
|
|
||||||
const targetParent = this.getParentById(target.id);
|
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);
|
await this.select(newConfig);
|
||||||
stage.select(newConfig.id);
|
stage.select(newConfig.id);
|
||||||
|
@ -56,13 +56,14 @@ export class IdleTask<T = any> extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private runTaskQueue(deadline: IdleDeadline) {
|
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();
|
const task = this.taskList.shift();
|
||||||
task!.handler(task!.data);
|
task!.handler(task!.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.taskList.length) {
|
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 {
|
} else {
|
||||||
this.taskHandle = 0;
|
this.taskHandle = 0;
|
||||||
|
|
||||||
|
@ -18,9 +18,22 @@
|
|||||||
|
|
||||||
import { cloneDeep, set as objectSet } from 'lodash-es';
|
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 { NodeType } from '@tmagic/schema';
|
||||||
|
|
||||||
|
import type { EditorNodeInfo } from '@editor/type';
|
||||||
|
|
||||||
export * from './dom';
|
export * from './dom';
|
||||||
|
|
||||||
export const sleep = (ms: number): Promise<void> =>
|
export const sleep = (ms: number): Promise<void> =>
|
||||||
@ -78,6 +91,39 @@ export const getNodePath = (id: Id, data: MNode[] = []): MNode[] => {
|
|||||||
return path;
|
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) =>
|
export const filterXSS = (str: string) =>
|
||||||
str.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
str.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user