diff --git a/packages/core/src/Node.ts b/packages/core/src/Node.ts index ad61527c..9814a868 100644 --- a/packages/core/src/Node.ts +++ b/packages/core/src/Node.ts @@ -21,6 +21,7 @@ import { EventEmitter } from 'events'; import { isEmpty } from 'lodash-es'; import type { EventItemConfig, MComponent, MContainer, MPage } from '@tmagic/schema'; +import { HookType } from '@tmagic/schema'; import type App from './App'; import type Page from './Page'; @@ -85,10 +86,11 @@ class Node extends EventEmitter { } private async runCodeBlock(hook: string) { - if (!Array.isArray(this.data[hook]) || !this.app.codeDsl || isEmpty(this.app?.codeDsl)) return; - for (const codeId of this.data[hook]) { + if (this.data[hook]?.hookType !== HookType.CODE || !this.app.codeDsl || isEmpty(this.app?.codeDsl)) return; + for (const item of this.data[hook].data) { + const { codeId, params = {} } = item; if (this.app.codeDsl[codeId] && typeof this.app?.codeDsl[codeId]?.content === 'function') { - await this.app.codeDsl[codeId].content(this); + await this.app.codeDsl[codeId].content(this, params); } } } diff --git a/packages/editor/src/components/CodeDraftEditor.vue b/packages/editor/src/components/CodeDraftEditor.vue index 14d67e38..5b4c22aa 100644 --- a/packages/editor/src/components/CodeDraftEditor.vue +++ b/packages/editor/src/components/CodeDraftEditor.vue @@ -22,6 +22,7 @@ import { computed, inject, ref, watchEffect } from 'vue'; import type * as monaco from 'monaco-editor'; import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design'; +import { Id } from '@tmagic/schema'; import { datetimeFormatter } from '@tmagic/utils'; import MagicCodeEditor from '../layouts/CodeEditor.vue'; @@ -30,7 +31,7 @@ import type { Services } from '../type'; const props = withDefaults( defineProps<{ /** 代码id */ - id: string; + id: Id; /** 代码内容 */ content: string; /** 是否可编辑 */ diff --git a/packages/editor/src/components/FunctionEditor.vue b/packages/editor/src/components/FunctionEditor.vue index 9856c3d5..e32c83ff 100644 --- a/packages/editor/src/components/FunctionEditor.vue +++ b/packages/editor/src/components/FunctionEditor.vue @@ -23,6 +23,7 @@ import { inject, ref, watchEffect } from 'vue'; import { TMagicCard, TMagicInput, tMagicMessage } from '@tmagic/design'; +import { Id } from '@tmagic/schema'; import type { Services } from '../type'; @@ -30,7 +31,7 @@ import CodeDraftEditor from './CodeDraftEditor.vue'; const props = withDefaults( defineProps<{ - id: string; + id: Id; name: string; content: string; editable?: boolean; diff --git a/packages/editor/src/fields/CodeSelect.vue b/packages/editor/src/fields/CodeSelect.vue index 87bd197e..50765498 100644 --- a/packages/editor/src/fields/CodeSelect.vue +++ b/packages/editor/src/fields/CodeSelect.vue @@ -1,96 +1,131 @@ diff --git a/packages/editor/src/layouts/sidebar/code-block/CodeBlockEditor.vue b/packages/editor/src/layouts/sidebar/code-block/CodeBlockEditor.vue index 7705f718..de905ccc 100644 --- a/packages/editor/src/layouts/sidebar/code-block/CodeBlockEditor.vue +++ b/packages/editor/src/layouts/sidebar/code-block/CodeBlockEditor.vue @@ -52,7 +52,7 @@ > - + @@ -61,10 +61,11 @@ import { computed, inject, reactive, ref, watchEffect } from 'vue'; import { cloneDeep, forIn, isEmpty } from 'lodash-es'; import { TMagicDialog, TMagicTree } from '@tmagic/design'; +import { CodeBlockContent } from '@tmagic/schema'; import FunctionEditor from '../../../components/FunctionEditor.vue'; import Layout from '../../../components/Layout.vue'; -import type { CodeBlockContent, CodeDslList, ListState, Services } from '../../../type'; +import type { CodeDslList, ListState, Services } from '../../../type'; import { CodeEditorMode } from '../../../type'; import { serializeConfig } from '../../../utils/editor'; @@ -86,6 +87,8 @@ const editable = computed(() => services?.codeBlockService.getEditStatus()); // 当前选中组件绑定的代码块id数组 const selectedIds = computed(() => services?.codeBlockService.getCombineIds() || []); +services?.codeBlockService.getCombineInfo(); + watchEffect(async () => { codeConfig.value = cloneDeep(await services?.codeBlockService.getCodeContentById(id.value)) || null; if (!codeConfig.value) return; diff --git a/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue b/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue index 9a49fbe1..d6887d42 100644 --- a/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue +++ b/packages/editor/src/layouts/sidebar/code-block/CodeBlockList.vue @@ -99,11 +99,11 @@ import { Close, Edit, Link, View } from '@element-plus/icons-vue'; import { forIn, isEmpty } from 'lodash-es'; import { TMagicButton, TMagicInput, tMagicMessage, TMagicTooltip, TMagicTree } from '@tmagic/design'; -import { Id } from '@tmagic/schema'; +import { CodeBlockContent, Id } from '@tmagic/schema'; import StageCore from '@tmagic/stage'; import Icon from '../../../components/Icon.vue'; -import type { CodeBlockContent, CodeRelation, Services } from '../../../type'; +import type { Services } from '../../../type'; import { CodeDeleteErrorType, CodeDslList, CodeEditorMode, ListRelationState } from '../../../type'; import codeBlockEditor from './CodeBlockEditor.vue'; @@ -126,15 +126,11 @@ const editable = computed(() => services?.codeBlockService.getEditStatus()); const isShowCodeBlockEditor = computed(() => services?.codeBlockService.getCodeEditorShowStatus() || false); // 根据代码块ID获取其绑定的组件信息 -const getBindCompsByCodeId = (codeId: string, codeBlockContent: CodeBlockContent) => { - if (isEmpty(codeBlockContent) || isEmpty(codeBlockContent.comps)) { - state.bindComps[codeId] = []; - return; - } - const compsField = codeBlockContent.comps as CodeRelation; - const bindCompIds = Object.keys(compsField); - const bindCompsFiltered = bindCompIds.filter((compId) => !isEmpty(compsField[compId])); - const compsInfo = bindCompsFiltered.map((compId) => ({ +const getBindCompsByCodeId = (codeId: string) => { + const codeCombineInfo = services?.codeBlockService.getCombineInfo(); + if (!codeCombineInfo) return null; + const bindCompsId = Object.keys(codeCombineInfo[codeId]); + const compsInfo = bindCompsId.map((compId) => ({ id: compId, name: getCompName(compId), })); @@ -147,7 +143,7 @@ const initList = async () => { if (!codeDsl) return; state.codeList = []; forIn(codeDsl, (value: CodeBlockContent, codeId: string) => { - getBindCompsByCodeId(codeId, value); + getBindCompsByCodeId(codeId); state.codeList.push({ id: codeId, name: value.name, diff --git a/packages/editor/src/services/codeBlock.ts b/packages/editor/src/services/codeBlock.ts index 764bb202..af842e90 100644 --- a/packages/editor/src/services/codeBlock.ts +++ b/packages/editor/src/services/codeBlock.ts @@ -19,10 +19,10 @@ import { reactive } from 'vue'; import { cloneDeep, forIn, isEmpty, keys, omit, pick } from 'lodash-es'; -import { Id, MNode } from '@tmagic/schema'; +import { CodeBlockContent, CodeBlockDSL, HookType, Id, MApp, MNode } from '@tmagic/schema'; import editorService from '../services/editor'; -import type { CodeBlockContent, CodeBlockDSL, CodeState } from '../type'; +import type { CodeRelation, CodeState, HookData } from '../type'; import { CODE_DRAFT_STORAGE_KEY, CodeEditorMode, CodeSelectOp } from '../type'; import { error, info } from '../utils/logger'; @@ -37,6 +37,7 @@ class CodeBlock extends BaseService { mode: CodeEditorMode.EDITOR, combineIds: [], undeletableList: [], + relations: {}, }); constructor() { @@ -82,10 +83,10 @@ class CodeBlock extends BaseService { /** * 根据代码块id获取代码块内容 - * @param {string} id 代码块id + * @param {Id} id 代码块id * @returns {CodeBlockContent | null} */ - public async getCodeContentById(id: string): Promise { + public async getCodeContentById(id: Id): Promise { if (!id) return null; const totalCodeDsl = await this.getCodeDsl(); if (!totalCodeDsl) return null; @@ -94,11 +95,11 @@ class CodeBlock extends BaseService { /** * 设置代码块ID和代码内容到源dsl - * @param {string} id 代码块id + * @param {Id} id 代码块id * @param {CodeBlockContent} codeConfig 代码块内容配置信息 * @returns {void} */ - public async setCodeDslById(id: string, codeConfig: CodeBlockContent): Promise { + public async setCodeDslById(id: Id, codeConfig: CodeBlockContent): Promise { let codeDsl = await this.getCodeDsl(); if (!codeDsl) { // dsl中无代码块字段 @@ -154,10 +155,10 @@ class CodeBlock extends BaseService { /** * 设置代码编辑面板展示状态及展示内容 * @param {boolean} status 是否展示代码编辑面板 - * @param {string} id 代码块id + * @param {Id} id 代码块id * @returns {void} */ - public setCodeEditorContent(status: boolean, id: string): void { + public setCodeEditorContent(status: boolean, id: Id): void { if (!id) return; this.setId(id); this.state.isShowCodeEditor = status; @@ -190,19 +191,19 @@ class CodeBlock extends BaseService { /** * 设置当前选中的代码块ID - * @param {string} id 代码块id + * @param {Id} id 代码块id * @returns {void} */ - public setId(id: string) { + public setId(id: Id) { if (!id) return; this.state.id = id; } /** * 获取当前选中的代码块ID - * @returns {string} id 代码块id + * @returns {Id} id 代码块id */ - public getId(): string { + public getId(): Id { return this.state.id; } @@ -249,12 +250,12 @@ class CodeBlock extends BaseService { * @returns {void} */ public async setCombineRelation(compId: Id, diffCodeIds: string[], opFlag: CodeSelectOp, hook: string) { - const codeDsl = cloneDeep(await this.getCodeDsl()); - if (!codeDsl) return; + const combineInfo = this.getCombineInfo(); + if (!combineInfo) return; if (opFlag === CodeSelectOp.DELETE) { try { diffCodeIds.forEach((codeId) => { - const compsContent = codeDsl[codeId].comps; + const compsContent = combineInfo[codeId]; const index = compsContent?.[compId].findIndex((item) => item === hook); if (typeof index !== 'undefined' && index !== -1) { compsContent?.[compId].splice(index, 1); @@ -267,12 +268,12 @@ class CodeBlock extends BaseService { } else if (opFlag === CodeSelectOp.ADD) { try { diffCodeIds.forEach((codeId) => { - const compsContent = codeDsl[codeId].comps; + const compsContent = combineInfo[codeId]; const existHooks = compsContent?.[compId]; if (isEmpty(existHooks)) { // comps属性不存在,或者comps为空:新增 - codeDsl[codeId].comps = { - ...(codeDsl[codeId].comps || {}), + combineInfo[codeId] = { + ...(combineInfo[codeId] || {}), [compId]: [hook], }; } else { @@ -286,16 +287,16 @@ class CodeBlock extends BaseService { } } else if (opFlag === CodeSelectOp.CHANGE) { // 单选修改 - forIn(codeDsl, (codeBlockContent, codeId) => { + forIn(combineInfo, (combineItem, codeId) => { if (codeId === diffCodeIds[0]) { // 增加 - codeBlockContent.comps = { - ...(codeBlockContent?.comps || {}), + combineItem = { + ...(combineItem || {}), [compId]: [hook], }; } else if (isEmpty(diffCodeIds) || codeId !== diffCodeIds[0]) { // 清空或者移除之前的选项 - const compHooks = codeBlockContent?.comps?.[compId]; + const compHooks = combineItem?.[compId]; // continue if (!compHooks) return true; const index = compHooks.findIndex((hookName) => hookName === hook); @@ -307,53 +308,65 @@ class CodeBlock extends BaseService { } }); } - this.setCodeDsl(codeDsl); + console.log('---combineInfo--', combineInfo); + console.log('---this.state.relations--', this.state.relations); + } + + /** + * 获取绑定关系 + * @returns {CodeRelation | null} + */ + public getCombineInfo(): CodeRelation | null { + const root = editorService.get('root'); + if (!root) return null; + this.recurseMNode(root); + return this.state.relations; } /** * 获取不可删除列表 - * @returns {string[]} + * @returns {Id[]} */ - public getUndeletableList(): string[] { + public getUndeletableList(): Id[] { return this.state.undeletableList; } /** * 设置不可删除列表:为业务逻辑预留的不可删除的代码块列表,由业务逻辑维护(如代码块上线后不可删除) - * @param {string[]} codeIds 代码块id数组 + * @param {Id[]} codeIds 代码块id数组 * @returns {void} */ - public async setUndeleteableList(codeIds: string[]): Promise { + public async setUndeleteableList(codeIds: Id[]): Promise { this.state.undeletableList = codeIds; } /** * 设置代码草稿 */ - public setCodeDraft(codeId: string, content: string): void { + public setCodeDraft(codeId: Id, content: string): void { globalThis.localStorage.setItem(`${CODE_DRAFT_STORAGE_KEY}_${codeId}`, content); } /** * 获取代码草稿 */ - public getCodeDraft(codeId: string): string | null { + public getCodeDraft(codeId: Id): string | null { return globalThis.localStorage.getItem(`${CODE_DRAFT_STORAGE_KEY}_${codeId}`); } /** * 删除代码草稿 */ - public removeCodeDraft(codeId: string): void { + public removeCodeDraft(codeId: Id): void { globalThis.localStorage.removeItem(`${CODE_DRAFT_STORAGE_KEY}_${codeId}`); } /** * 在dsl数据源中删除指定id的代码块 - * @param {string[]} codeIds 需要删除的代码块id数组 + * @param {Id[]} codeIds 需要删除的代码块id数组 * @returns {CodeBlockDSL} 删除后的code dsl */ - public async deleteCodeDslByIds(codeIds: string[]): Promise { + public async deleteCodeDslByIds(codeIds: Id[]): Promise { const currentDsl = await this.getCodeDsl(); const newDsl = omit(currentDsl, codeIds); await this.setCodeDsl(newDsl); @@ -362,9 +375,9 @@ class CodeBlock extends BaseService { /** * 生成代码块唯一id - * @returns {string} 代码块唯一id + * @returns {Id} 代码块唯一id */ - public async getUniqueId(): Promise { + public async getUniqueId(): Promise { const newId = `code_${Math.random().toString(10).substring(2).substring(0, 4)}`; // 判断是否重复 const dsl = await this.getCodeDsl(); @@ -381,7 +394,7 @@ class CodeBlock extends BaseService { public async deleteCompsInRelation(node: MNode) { const codeDsl = cloneDeep(await this.getCodeDsl()); if (!codeDsl) return; - this.recurseNodes(node, codeDsl); + this.refreshRelationDeep(node, codeDsl); this.setCodeDsl(codeDsl); } @@ -395,8 +408,13 @@ class CodeBlock extends BaseService { this.state.undeletableList = []; } - // 删除组件时 如果是容器 需要遍历删除其包含节点的绑定信息 - private recurseNodes(node: MNode, codeDsl: CodeBlockDSL) { + /** + * 删除组件时 如果是容器 需要遍历删除其包含节点的绑定信息 + * @param {MNode} node 节点信息 + * @param {CodeBlockDSL} codeDsl 代码块 + * @returns void + */ + private refreshRelationDeep(node: MNode, codeDsl: CodeBlockDSL) { if (!node.id) return; forIn(codeDsl, (codeBlockContent) => { const compsContent = codeBlockContent.comps || {}; @@ -404,7 +422,34 @@ class CodeBlock extends BaseService { }); if (!isEmpty(node.items)) { node.items.forEach((item: MNode) => { - this.recurseNodes(item, codeDsl); + this.refreshRelationDeep(item, codeDsl); + }); + } + } + + /** + * 递归遍历dsl中挂载了代码块的节点,并更新绑定关系数据 + * @param {MNode} node 节点信息 + * @returns void + */ + private recurseMNode(node: MNode) { + forIn(node, (value, key) => { + if (value?.hookType === HookType.CODE && !isEmpty(value?.data)) { + value.data.forEach((relationItem: HookData) => { + if (!this.state.relations[relationItem.codeId]) { + this.state.relations[relationItem.codeId] = {}; + } + const codeItem = this.state.relations[relationItem.codeId]; + if (isEmpty(codeItem[node.id])) { + codeItem[node.id] = []; + } + codeItem[node.id].push(key); + }); + } + }); + if (!isEmpty(node.items)) { + node.items.forEach((item: MNode) => { + this.recurseMNode(item); }); } } diff --git a/packages/editor/src/theme/code-block.scss b/packages/editor/src/theme/code-block.scss index e6c1dd25..07a3057d 100644 --- a/packages/editor/src/theme/code-block.scss +++ b/packages/editor/src/theme/code-block.scss @@ -77,19 +77,6 @@ .m-fields-code-select { width: 100%; - .el-card__body { - padding: 5px; - } - .tool-bar { - display: flex; - align-items: center; - justify-content: end; - height: 20px; - .tool-item { - display: flex; - align-items: center; - } - } } .code-editor-dialog { .el-dialog__body { diff --git a/packages/editor/src/type.ts b/packages/editor/src/type.ts index 8f361260..8e830ab1 100644 --- a/packages/editor/src/type.ts +++ b/packages/editor/src/type.ts @@ -19,7 +19,7 @@ import type { Component } from 'vue'; import type { FormConfig } from '@tmagic/form'; -import type { Id, MApp, MContainer, MNode, MPage } from '@tmagic/schema'; +import type { CodeBlockContent, CodeBlockDSL, Id, MApp, MContainer, MNode, MPage } from '@tmagic/schema'; import type StageCore from '@tmagic/stage'; import type { ContainerHighlightType, MoveableOptions } from '@tmagic/stage'; @@ -309,28 +309,13 @@ export interface ScrollViewerEvent { scrollWidth: number; } -export interface CodeBlockDSL { - [id: string]: CodeBlockContent; -} - -export interface CodeBlockContent { - /** 代码块名称 */ - name: string; - /** 代码块内容 */ - content: string; - /** 代码块与组件的绑定关系 */ - comps?: CodeRelation; - /** 扩展字段 */ - [propName: string]: any; -} - export type CodeState = { /** 是否展示代码块编辑区 */ isShowCodeEditor: boolean; /** 代码块DSL数据源 */ codeDsl: CodeBlockDSL | null; /** 当前选中的代码块id */ - id: string; + id: Id; /** 代码块是否可编辑 */ editable: boolean; /** 代码编辑面板的展示模式 */ @@ -338,12 +323,23 @@ export type CodeState = { /** list模式下左侧展示的代码列表 */ combineIds: string[]; /** 为业务逻辑预留的不可删除的代码块列表,由业务逻辑维护(如代码块上线后不可删除) */ - undeletableList: string[]; + undeletableList: Id[]; + /** 代码块和组件的绑定关系 */ + relations: CodeRelation; +}; + +export type HookData = { + /** 代码块id */ + codeId: Id; + /** 参数 */ + params?: object; }; export type CodeRelation = { - /** 组件id:['created'] */ - [compId: string | number]: string[]; + [codeId: Id]: { + /** 组件id:['created'] */ + [compId: Id]: string[]; + }; }; export enum CodeEditorMode { @@ -355,7 +351,7 @@ export enum CodeEditorMode { export interface CodeDslList { /** 代码块id */ - id: string; + id: Id; /** 代码块名称 */ name: string; /** 代码块函数内容 */ @@ -370,10 +366,10 @@ export interface ListState { } export interface ListRelationState extends ListState { - /** 与代码块绑定的组件id信息 */ + /** 与代码块绑定的组件信息 */ bindComps: { /** 代码块id : 组件信息 */ - [id: string]: MNode[]; + [id: Id]: MNode[]; }; } diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 3e7d59f3..0ec68bda 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -73,7 +73,7 @@ export interface MApp extends MComponent { } export interface CodeBlockDSL { - [id: string]: CodeBlockContent; + [id: Id]: CodeBlockContent; } export interface CodeBlockContent { @@ -81,10 +81,18 @@ export interface CodeBlockContent { name: string; /** 代码块内容 */ content: any; + /** 扩展字段 */ + [propName: string]: any; } + export interface PastePosition { left?: number; top?: number; } export type MNode = MComponent | MContainer | MPage | MApp; + +export enum HookType { + /** 代码块钩子标识 */ + CODE = 'code', +} diff --git a/playground/src/configs/dsl.ts b/playground/src/configs/dsl.ts index 6c33694b..91dad7a1 100644 --- a/playground/src/configs/dsl.ts +++ b/playground/src/configs/dsl.ts @@ -24,19 +24,13 @@ export default { code_5336: { name: 'getData', // eslint-disable-next-line no-eval - content: eval(`(vm) => {\n console.log("this is getData function")\n}`), - comps: { - page_299: ['mounted', 'created'], - }, + content: eval(`(vm, params) => {\n console.log("this is getData function",vm,params)\n}`), + params: ['name', 'age'], }, code_5316: { name: 'getList', // eslint-disable-next-line no-eval content: eval(`(vm) => {\n console.log("this is getList function")\n}`), - comps: { - text_9027: ['created'], - page_299: ['created'], - }, }, }, items: [ @@ -63,8 +57,29 @@ export default { fontWeight: '', }, events: [], - created: ['code_5316', 'code_5336'], - mounted: ['code_5336'], + created: { + hookType: 'code', + data: [ + { + codeId: 'code_5336', + params: { + name: 'lisa', + age: 12, + }, + }, + { + codeId: 'code_5316', + }, + ], + }, + mounted: { + hookType: 'code', + data: [ + { + codeId: 'code_5316', + }, + ], + }, items: [ { type: 'text', @@ -89,7 +104,14 @@ export default { text: 'Tmagic editor 营销活动编辑器', multiple: true, events: [], - created: ['code_5316'], + created: { + hookType: 'code', + data: [ + { + codeId: 'code_5316', + }, + ], + }, }, { type: 'qrcode',