diff --git a/packages/editor/package.json b/packages/editor/package.json index 68981b6b..f6bb2e34 100644 --- a/packages/editor/package.json +++ b/packages/editor/package.json @@ -57,6 +57,7 @@ "@tmagic/utils": "workspace:*", "buffer": "^6.0.3", "color": "^3.1.3", + "deep-object-diff": "^1.1.9", "emmet-monaco-es": "^5.3.0", "events": "^3.3.0", "gesto": "^1.19.1", diff --git a/packages/editor/src/hooks/index.ts b/packages/editor/src/hooks/index.ts index 2d7b3854..8b56bc7e 100644 --- a/packages/editor/src/hooks/index.ts +++ b/packages/editor/src/hooks/index.ts @@ -17,7 +17,6 @@ */ export * from './use-code-block-edit'; -export * from './use-data-source-method'; export * from './use-stage'; export * from './use-float-box'; export * from './use-window-rect'; diff --git a/packages/editor/src/hooks/use-data-source-edit.ts b/packages/editor/src/hooks/use-data-source-edit.ts index 54f3be43..8c9b0d19 100644 --- a/packages/editor/src/hooks/use-data-source-edit.ts +++ b/packages/editor/src/hooks/use-data-source-edit.ts @@ -1,6 +1,7 @@ import { computed, ref } from 'vue'; import type { DataSourceSchema } from '@tmagic/core'; +import type { ContainerChangeEventData } from '@tmagic/form'; import DataSourceConfigPanel from '@editor/layouts/sidebar/data-source/DataSourceConfigPanel.vue'; import type { DataSourceService } from '@editor/services/dataSource'; @@ -24,9 +25,9 @@ export const useDataSourceEdit = (dataSourceService?: DataSourceService) => { editDialog.value.show(); }; - const submitDataSourceHandler = (value: DataSourceSchema) => { + const submitDataSourceHandler = (value: DataSourceSchema, eventData: ContainerChangeEventData) => { if (value.id) { - dataSourceService?.update(value); + dataSourceService?.update(value, { changeRecords: eventData.changeRecords }); } else { dataSourceService?.add(value); } diff --git a/packages/editor/src/hooks/use-data-source-method.ts b/packages/editor/src/hooks/use-data-source-method.ts deleted file mode 100644 index 2a2eb830..00000000 --- a/packages/editor/src/hooks/use-data-source-method.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { nextTick, ref } from 'vue'; -import { cloneDeep } from 'lodash-es'; - -import type { CodeBlockContent, DataSourceSchema } from '@tmagic/core'; -import { tMagicMessage } from '@tmagic/design'; - -import CodeBlockEditor from '@editor/components/CodeBlockEditor.vue'; -import { getEditorConfig } from '@editor/utils/config'; - -export const useDataSourceMethod = () => { - const codeConfig = ref(); - const codeBlockEditor = ref>(); - - const dataSource = ref(); - const dataSourceMethod = ref(''); - - return { - codeConfig, - codeBlockEditor, - - createCode: async (model: DataSourceSchema) => { - codeConfig.value = { - name: '', - content: `({ params, dataSource, app, flowState }) => {\n // place your code here\n}`, - params: [], - }; - - await nextTick(); - - dataSource.value = model; - dataSourceMethod.value = ''; - - codeBlockEditor.value?.show(); - }, - - editCode: async (model: DataSourceSchema, methodName: string) => { - const method = model.methods?.find((method) => method.name === methodName); - - if (!method) { - tMagicMessage.error('获取数据源方法失败'); - return; - } - - let codeContent = method.content || `({ params, dataSource, app }) => {\n // place your code here\n}`; - - if (typeof codeContent !== 'string') { - codeContent = codeContent.toString(); - } - - codeConfig.value = { - ...cloneDeep(method), - content: codeContent, - }; - - await nextTick(); - - dataSource.value = model; - dataSourceMethod.value = methodName; - - codeBlockEditor.value?.show(); - }, - - deleteCode: async (model: DataSourceSchema, methodName: string) => { - if (!model.methods) return; - - const index = model.methods.findIndex((method) => method.name === methodName); - - if (index === -1) { - return; - } - - model.methods.splice(index, 1); - }, - - submitCode: (values: CodeBlockContent) => { - if (!dataSource.value) return; - - if (!dataSource.value.methods) { - dataSource.value.methods = []; - } - - if (values.content) { - // 在保存的时候转换代码内容 - const parseDSL = getEditorConfig('parseDSL'); - if (typeof values.content === 'string') { - values.content = parseDSL<(...args: any[]) => any>(values.content); - } - } - - if (dataSourceMethod.value) { - const index = dataSource.value.methods.findIndex((method) => method.name === dataSourceMethod.value); - dataSource.value.methods.splice(index, 1, values); - } else { - dataSource.value.methods.push(values); - } - - codeBlockEditor.value?.hide(); - }, - }; -}; diff --git a/packages/editor/src/initService.ts b/packages/editor/src/initService.ts index fafe4103..b0b2ed85 100644 --- a/packages/editor/src/initService.ts +++ b/packages/editor/src/initService.ts @@ -17,11 +17,14 @@ import { createDataSourceMethodTarget, createDataSourceTarget, DepTargetType, + NODE_CONDS_KEY, Target, } from '@tmagic/core'; +import { ChangeRecord } from '@tmagic/form'; import { getNodes, isPage, traverseNode } from '@tmagic/utils'; import PropsPanel from './layouts/PropsPanel.vue'; +import { isIncludeDataSource, isValueIncludeDataSource } from './utils/editor'; import { EditorProps } from './editorProps'; import { Services } from './type'; @@ -219,6 +222,7 @@ export const initServiceEvents = ( }); if (Array.isArray(value.items)) { + depService.clearIdleTasks(); collectIdle(value.items, true); } else { depService.clear(); @@ -302,10 +306,6 @@ export const initServiceEvents = ( 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), @@ -348,8 +348,18 @@ export const initServiceEvents = ( } }; + const depCollectedHandler = () => { + const root = editorService.get('root'); + if (!root) return; + const app = getApp(); + if (app?.dsl) { + app.dsl.dataSourceDeps = root.dataSourceDeps; + } + }; + depService.on('add-target', targetAddHandler); depService.on('remove-target', targetRemoveHandler); + depService.on('collected', depCollectedHandler); const initDataSourceDepTarget = (ds: DataSourceSchema) => { depService.addTarget(createDataSourceTarget(ds, reactive({}))); @@ -380,10 +390,39 @@ export const initServiceEvents = ( }; // 节点更新,收集依赖 - const nodeUpdateHandler = (nodes: MNode[]) => { - collectIdle(nodes, true).then(() => { - afterUpdateNodes(nodes); + // 仅当修改到数据源相关的才收集 + const nodeUpdateHandler = (data: { newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }[]) => { + const needRecollectNodes: MNode[] = []; + const normalNodes: MNode[] = []; + data.forEach(({ newNode, oldNode, changeRecords }) => { + if (changeRecords?.length) { + for (const record of changeRecords) { + // NODE_CONDS_KEY为显示条件key + if ( + !record.propPath || + new RegExp(`${NODE_CONDS_KEY}.(\\d)+.cond`).test(record.propPath) || + new RegExp(`${NODE_CONDS_KEY}.(\\d)+.cond.(\\d)+.value`).test(record.propPath) || + record.propPath === NODE_CONDS_KEY || + isValueIncludeDataSource(record.value) + ) { + needRecollectNodes.push(newNode); + break; + } + } + } else if (isIncludeDataSource(newNode, oldNode)) { + needRecollectNodes.push(newNode); + } else { + normalNodes.push(newNode); + } }); + + if (needRecollectNodes.length) { + collectIdle(needRecollectNodes, true).then(() => { + afterUpdateNodes(needRecollectNodes); + }); + } else if (normalNodes.length) { + afterUpdateNodes(normalNodes); + } }; // 节点删除,清除对齐的依赖收集 @@ -425,14 +464,41 @@ export const initServiceEvents = ( getApp()?.dataSourceManager?.addDataSource(config); }; - const dataSourceUpdateHandler = (config: DataSourceSchema) => { - const root = editorService.get('root'); - removeDataSourceTarget(config.id); - initDataSourceDepTarget(config); + const dataSourceUpdateHandler = (config: DataSourceSchema, { changeRecords }: { changeRecords: ChangeRecord[] }) => { + let needRecollectDep = false; + for (const changeRecord of changeRecords) { + if (!changeRecord.propPath) { + continue; + } - collectIdle(root?.items || [], true).then(() => { - updateDataSourceSchema(root?.items || [], true); - }); + needRecollectDep = + changeRecord.propPath === 'fields' || + changeRecord.propPath === 'methods' || + /fields.(\d)+.name/.test(changeRecord.propPath) || + /fields.(\d)+$/.test(changeRecord.propPath) || + /methods.(\d)+.name/.test(changeRecord.propPath) || + /methods.(\d)+$/.test(changeRecord.propPath); + + if (needRecollectDep) { + break; + } + } + + const root = editorService.get('root'); + if (needRecollectDep) { + if (Array.isArray(root?.items)) { + depService.clearIdleTasks(); + + removeDataSourceTarget(config.id); + initDataSourceDepTarget(config); + + collectIdle(root.items, true).then(() => { + updateDataSourceSchema(root?.items || [], true); + }); + } + } else if (root?.dataSources) { + getApp()?.dataSourceManager?.updateSchema(root.dataSources); + } }; const removeDataSourceTarget = (id: string) => { @@ -459,6 +525,7 @@ export const initServiceEvents = ( onBeforeUnmount(() => { depService.off('add-target', targetAddHandler); depService.off('remove-target', targetRemoveHandler); + depService.off('collected', depCollectedHandler); editorService.off('history-change', historyChangeHandler); editorService.off('root-change', rootChangeHandler); diff --git a/packages/editor/src/layouts/Framework.vue b/packages/editor/src/layouts/Framework.vue index f9cd8930..01dc62f1 100644 --- a/packages/editor/src/layouts/Framework.vue +++ b/packages/editor/src/layouts/Framework.vue @@ -11,8 +11,6 @@ editorService?.get('root')); const page = computed(() => editorService?.get('page')); const pageLength = computed(() => editorService?.get('pageLength') || 0); -const stageLoading = computed(() => editorService?.get('stageLoading') || false); const showSrc = computed(() => uiService?.get('showSrc')); const LEFT_COLUMN_WIDTH_STORAGE_KEY = '$MagicEditorLeftColumnWidthData'; diff --git a/packages/editor/src/layouts/PropsPanel.vue b/packages/editor/src/layouts/PropsPanel.vue index 060130a2..67438f5f 100644 --- a/packages/editor/src/layouts/PropsPanel.vue +++ b/packages/editor/src/layouts/PropsPanel.vue @@ -43,7 +43,7 @@ import { Document as DocumentIcon } from '@element-plus/icons-vue'; import type { MNode } from '@tmagic/core'; import { TMagicButton } from '@tmagic/design'; -import type { FormState, FormValue } from '@tmagic/form'; +import type { ContainerChangeEventData, FormState, FormValue } from '@tmagic/form'; import { MForm } from '@tmagic/form'; import MIcon from '@editor/components/Icon.vue'; @@ -110,10 +110,10 @@ watchEffect(() => { } }); -const submit = async () => { +const submit = async (v: FormValue, eventData: ContainerChangeEventData) => { try { const values = await configForm.value?.submitForm(); - services?.editorService.update(values); + services?.editorService.update(values, { changeRecords: eventData.changeRecords }); } catch (e: any) { emit('submit-error', e); } diff --git a/packages/editor/src/layouts/sidebar/Sidebar.vue b/packages/editor/src/layouts/sidebar/Sidebar.vue index fa343618..64e8bbc0 100644 --- a/packages/editor/src/layouts/sidebar/Sidebar.vue +++ b/packages/editor/src/layouts/sidebar/Sidebar.vue @@ -19,6 +19,7 @@
('services'); +const collecting = computed(() => services?.depService.get('collecting')); + const columnLeftWidth = computed(() => services?.uiService.get('columnWidth')[ColumnLayout.LEFT] || 0); const { height: editorContentHeight } = useEditorContentHeight(); const columnLeftHeight = ref(0); diff --git a/packages/editor/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue b/packages/editor/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue index 2f3dee41..47135dde 100644 --- a/packages/editor/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +++ b/packages/editor/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue @@ -24,9 +24,9 @@