Compare commits

..

No commits in common. "master" and "v1.5.22" have entirely different histories.

346 changed files with 4260 additions and 4961 deletions

View File

@ -18,10 +18,10 @@ jobs:
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v4
- name: Set node version to 22 - name: Set node version to 18
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 22 node-version: 18
cache: 'pnpm' cache: 'pnpm'
- run: pnpm bootstrap - run: pnpm bootstrap

View File

@ -1,150 +1,3 @@
## [1.6.1](https://github.com/Tencent/tmagic-editor/compare/v1.6.0...v1.6.1) (2025-10-14)
### Bug Fixes
* **core:** 异步加载数据源时,数据源事件配置失效 ([1031595](https://github.com/Tencent/tmagic-editor/commit/1031595a976b33f8e5e3a71d8c00944d4777beb1))
* **form:** text与同行元素不对齐问题 ([cae11dc](https://github.com/Tencent/tmagic-editor/commit/cae11dce1290b9ea314c07daee30f1eb6f400681))
* 组件声明周期函数配置中配置数据源自有方法生效 ([e400175](https://github.com/Tencent/tmagic-editor/commit/e400175ffe89ab3105e50d2ffcd702d9d2a12970))
### Features
* **data-source, editor, schema, react-runtime-help, vue-components:** 新增条件成立时隐藏的配置功能 ([51f95ae](https://github.com/Tencent/tmagic-editor/commit/51f95aef6f649851222e09e7234648f4985d1ad8))
* **data-source:** 数据源数据变化事件监听响应支持立即执行 ([849b561](https://github.com/Tencent/tmagic-editor/commit/849b561933d74bcc68f75f15d67e0d252c5a8468))
* **editor:** 属性配置中的样式面板样式优化 ([81aa8f1](https://github.com/Tencent/tmagic-editor/commit/81aa8f151d765bf08d2f88c334ff7c58b38aab56))
# [1.6.0](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.6...v1.6.0) (2025-08-27)
### Bug Fixes
* **cli:** 在pnpm工作空间中的项目保持package.json不受配置中的packages影响 ([a10f9d2](https://github.com/Tencent/tmagic-editor/commit/a10f9d230d93064c28ff82f2c3aba2679f8fe495))
### Features
* **cli:** 支持tmagic.config.local配置文件会与tmagic.config配置合并可用于本地开发时的临时配置 ([9f35054](https://github.com/Tencent/tmagic-editor/commit/9f350541bf84a634499a9af53449015f34da26b5))
# [1.6.0-beta.6](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.5...v1.6.0-beta.6) (2025-08-15)
### Features
* **core:** 事件支持关联数据源自身方法 ([4c6118f](https://github.com/Tencent/tmagic-editor/commit/4c6118f50f8e78e8db157f9b32c49f5f068e3e9a))
# [1.6.0-beta.5](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.4...v1.6.0-beta.5) (2025-08-08)
### Bug Fixes
* **core:** 事件行为应该为串行执行 ([fc1c9fe](https://github.com/Tencent/tmagic-editor/commit/fc1c9feafd337ad8fa8a9b5b3501278779d32410))
# [1.6.0-beta.4](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.3...v1.6.0-beta.4) (2025-07-24)
### Bug Fixes
* **editor:** runtime url更新后恢复当前选中状态 ([c2830fc](https://github.com/Tencent/tmagic-editor/commit/c2830fca6b21648b51a486650caba3f9eb753c91))
* **editor:** 避免services plugin重复添加 ([cdb07df](https://github.com/Tencent/tmagic-editor/commit/cdb07dfaea13a1813caddd59ba7e8efbdb374b3c))
# [1.6.0-beta.3](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.2...v1.6.0-beta.3) (2025-07-24)
### Bug Fixes
* **editor:** runtimeUrl更新后需要重新设置runtime的dsl ([d31a544](https://github.com/Tencent/tmagic-editor/commit/d31a544a2312b4d78ae74eb600760df450946db8))
* **editor:** 修复代码块编辑器的更新内容后按下ctrl+s光标会偏移的问题 ([07b8f5f](https://github.com/Tencent/tmagic-editor/commit/07b8f5fe8303bad57490c7dd0eba00e435fbfad5))
# [1.6.0-beta.2](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.1...v1.6.0-beta.2) (2025-07-22)
### Features
* **core:** 异步加载dsl片段下事件触发时node可能不存在 ([01b88db](https://github.com/Tencent/tmagic-editor/commit/01b88dba251a1f7cc29947c2a80e15839e50fcca))
* **utile:** replaceChildNode找不多目标不报错 ([2605011](https://github.com/Tencent/tmagic-editor/commit/2605011df481d9edba5503d2ee3b3c9d4b76d0a3))
# [1.6.0-beta.1](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.0...v1.6.0-beta.1) (2025-07-17)
### Bug Fixes
* **data-source:** 页面未初始化好之前数据源数据变化后需要修改page.data ([305ea46](https://github.com/Tencent/tmagic-editor/commit/305ea4619fe15efbae069ef3f289c8742e8d51ee))
* **vue-components:** iterator-container 传递 page-fragment-contianer-id 参数 ([7799a5d](https://github.com/Tencent/tmagic-editor/commit/7799a5da618b0136f4b3c12315c69d21b27a5965))
* **vue-components:** page-fragment-container不标记为不是node节点 ([bf9fad1](https://github.com/Tencent/tmagic-editor/commit/bf9fad18b64521a9075a60f7e6e662b01d37f66f))
### Features
* **core:** Node中的instance初始为null用于判断是否与组件产生关联 ([291de20](https://github.com/Tencent/tmagic-editor/commit/291de2005de54b8eeba818de67ab3506885f5057))
* **editor:** 页面片容器内的组件不允许选中 ([ded24c8](https://github.com/Tencent/tmagic-editor/commit/ded24c8b4ff2c203260ec7633fc85325c98a565c))
* **vue-components:** page-fragment-container编辑器中不去除内部组件id不然会导致无法从app中获取dsl ([34bc223](https://github.com/Tencent/tmagic-editor/commit/34bc223f0241d9eef39450ebcef8a92b23c63f37))
* **vue-components:** 添加页面片容器id prop ([a43825c](https://github.com/Tencent/tmagic-editor/commit/a43825caa27695fcd85cc4b5351a00080fefcec0))
# [1.6.0-beta.0](https://github.com/Tencent/tmagic-editor/compare/v1.5.24...v1.6.0-beta.0) (2025-07-15)
### Bug Fixes
* **editor:** 依赖收集后没有同步到dsl中 ([aaf8046](https://github.com/Tencent/tmagic-editor/commit/aaf8046c63949ae733719409eb12109ef17b2cf1))
### Features
* **core,data-source,utils,react-runtime-help,vue-runtime-help:** 新增对页面片节点的管理 ([6a54720](https://github.com/Tencent/tmagic-editor/commit/6a547200681991ae8abefd0e3d72a603d21f12d4))
## [1.5.24](https://github.com/Tencent/tmagic-editor/compare/v1.5.23...v1.5.24) (2025-07-03)
### Bug Fixes
* **editor:** 初始化收集依赖后编译数据源不能使用源数据 ([84bf9ba](https://github.com/Tencent/tmagic-editor/commit/84bf9ba2f96829ba8e88b278bd49f2cb454605aa))
## [1.5.23](https://github.com/Tencent/tmagic-editor/compare/v1.5.22...v1.5.23) (2025-07-03)
### Bug Fixes
* **editor,react-runtime-help,vue-runtime-help:** 修复拖动页面顺序失效问题 ([b6a2604](https://github.com/Tencent/tmagic-editor/commit/b6a260471d9369e9b911f35d16ff0fca82a7d4fe))
* **editor:** 树组件搜索后不展开所有节点 ([c984c1a](https://github.com/Tencent/tmagic-editor/commit/c984c1a0cf0c934d41e5affb471d2ff2efa4bb97))
* **editor:** 防止快捷键重复注册 ([18e8df4](https://github.com/Tencent/tmagic-editor/commit/18e8df4a557cb845a8f1d2868ea4e8833a75941d))
* **editor:** 页面列表滚动条 ([727af10](https://github.com/Tencent/tmagic-editor/commit/727af1058d23e0843482536133954243c9f208d5))
### Features
* **core,data-source,dep,editor,schema:** 新增组件禁用代码块/数据源的配置开关 ([2f4a7a3](https://github.com/Tencent/tmagic-editor/commit/2f4a7a33b85015bfe5368bd10bf562514f07299c))
* **dep,schema:** 添加组件没用数据源或者代码块的配置key ([3f7d039](https://github.com/Tencent/tmagic-editor/commit/3f7d03959cdbafacc439df2bf3a61aa1e21cb8c2))
* **editor:** service新增removePlugin方法 ([8d6da37](https://github.com/Tencent/tmagic-editor/commit/8d6da3712efdc269cbaa47b003bf667bbce00ae7))
* **editor:** stage overlay 支持放大缩小 ([3268196](https://github.com/Tencent/tmagic-editor/commit/32681964b340fc8e68f8b6a6eb420043fd569b78))
* **editor:** 数据源属性/方法表格可以拖动列宽 ([222a96e](https://github.com/Tencent/tmagic-editor/commit/222a96e4652003b908c5c75378e5ddf2029fdd65))
* **editor:** 新增props-panel-unmounted事件 ([9e590c5](https://github.com/Tencent/tmagic-editor/commit/9e590c5cf73c9925f7aad562409f31c1ff273b18))
* **playground:** 新增按比例拖动组件大小快捷键 ([b78ef20](https://github.com/Tencent/tmagic-editor/commit/b78ef206022d69a8c3f81c55a301e5a4b5147641))
* **stage:** 新增禁用标尺配置 ([95d6263](https://github.com/Tencent/tmagic-editor/commit/95d6263f42ad13c2ac0560704695408634e55d91))
* **stage:** 添加updateMoveableOptions api用于更新选中框状态 ([bdd59cf](https://github.com/Tencent/tmagic-editor/commit/bdd59cff9b378461f905b04e250f8ad7f6ed21a0))
## [1.5.22](https://github.com/Tencent/tmagic-editor/compare/v1.5.21...v1.5.22) (2025-06-09) ## [1.5.22](https://github.com/Tencent/tmagic-editor/compare/v1.5.21...v1.5.22) (2025-06-09)

View File

@ -1,6 +1,6 @@
Tencent is pleased to support the open source community by making TMagicEditor available. Tencent is pleased to support the open source community by making TMagicEditor available.
Copyright (C) 2025 Tencent. All rights reserved. Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
TMagicEditor is licensed under the Apache License Version 2.0 except for the third-party components listed below. TMagicEditor is licensed under the Apache License Version 2.0 except for the third-party components listed below.

View File

@ -24,7 +24,7 @@ export default defineConfig({
footer: { footer: {
message: 'Powered by 腾讯视频会员平台技术中心', message: 'Powered by 腾讯视频会员平台技术中心',
copyright: 'Copyright (C) 2025 Tencent.' copyright: 'Copyright (C) 2023 THL A29 Limited, a Tencent company.'
}, },
nav: [ nav: [

View File

@ -375,7 +375,7 @@ const stageContentMenu = ref([
```html ```html
<template> <template>
<m-editor <m-editor
runtime-url="https://tencent.github.io/tmagic-editor/playground/runtime/vue/playground/index.html" runtime-url="https://tencent.github.io/tmagic-editor/playground/runtime/vue3/playground/index.html"
></m-editor> ></m-editor>
</template> </template>
``` ```
@ -675,7 +675,7 @@ const datasourceConfigs = {
- **默认值:** `{}` - **默认值:** `{}`
- **类型:** ((config: [CustomizeMoveableOptionsCallbackConfig](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/stage/src/types.ts#L97-L109)) => MoveableOptions) | [MoveableOptions](https://daybrush.com/moveable/release/latest/doc/) - **类型:** ((config?: [CustomizeMoveableOptionsCallbackConfig](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/stage/src/types.ts#L97-L109)) => MoveableOptions) | [MoveableOptions](https://daybrush.com/moveable/release/latest/doc/)
- **示例:** - **示例:**

View File

@ -60,5 +60,6 @@ export default defineComponent({
## 渲染器示例 ## 渲染器示例
在tmagic-editor的示例项目中我们提供了三个版本的 @tmagic/ui。可以参考对应前端框架的渲染器实现。 在tmagic-editor的示例项目中我们提供了三个版本的 @tmagic/ui。可以参考对应前端框架的渲染器实现。
- [vue 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/vue-components/container/src/Container.vue) - [vue3 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/packages/ui/src/container/src/Container.vue)
- [react 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/react-components/container/src/Container.tsx) - [vue2 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/packages/ui-vue2/src/container/Container.vue)
- [react 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/packages/ui-react/src/container/Container.tsx)

View File

@ -5,13 +5,15 @@ tmagic-editor的设计是希望发布的页面支持多个前端框架即各
所以tmagic-editor的设计中针对每个前端框架都需要有一个对应的 @tmagic/ui 来承担渲染器职责。同时,也需要一个使用和 @tmagic/ui 相同前端框架的 runtime 来 @tmagic/ui 和业务组件的,具体 runtime 概念,可以参考[页面发布](../publish)。 所以tmagic-editor的设计中针对每个前端框架都需要有一个对应的 @tmagic/ui 来承担渲染器职责。同时,也需要一个使用和 @tmagic/ui 相同前端框架的 runtime 来 @tmagic/ui 和业务组件的,具体 runtime 概念,可以参考[页面发布](../publish)。
我们以项目代码中提供的 vue 版本的 [vue-components](https://tencent.github.io/tmagic-editor/vue-components) 作为示例介绍其中包含的内容。 @tmagic/ui 在tmagic-editor设计中承担的是业务逻辑无关的基础组件渲染的功能。一切和业务相关的逻辑都应该在 [runtime](../runtime.html) 中实现。这样 @tmagic/ui 就能保持其通用性。
我们以项目代码中提供的 vue3 版本的 @tmagic/ui 作为示例介绍其中包含的内容。
## 渲染器 ## 渲染器
在 vue 中,实现渲染器的具体形式参考[页面渲染](../advanced/page)中描述的[容器渲染](../advanced/page.html#容器渲染)和[组件渲染](../advanced/page.html#容器渲染)。 在 vue3 中,实现渲染器的具体形式参考[页面渲染](../advanced/page)中描述的[容器渲染](../advanced/page.html#容器渲染)和[组件渲染](../advanced/page.html#容器渲染)。
## 基础组件 ## 基础组件
[vue-components](https://tencent.github.io/tmagic-editor/vue-components) 中,我们提供了几个基础组件,可以在项目源码中找到对应内容。 @tmagic/ui vue3 中,我们提供了几个基础组件,可以在项目源码中找到对应内容。
- page tmagic-editor的页面基础 - page tmagic-editor的页面基础
- container tmagic-editor的容器渲染器 - container tmagic-editor的容器渲染器
@ -21,3 +23,8 @@ tmagic-editor的设计是希望发布的页面支持多个前端框架即各
其中 page/container/Component 是 UI 的基础,是每个框架的 UI 都应该实现的。 其中 page/container/Component 是 UI 的基础,是每个框架的 UI 都应该实现的。
button/text 其实就是一个组件开发的示例,具体组件开发相关规范可以参考[组件开发](../component)。 button/text 其实就是一个组件开发的示例,具体组件开发相关规范可以参考[组件开发](../component)。
## @tmagic/ui 示例
- [vue3 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/packages/ui)
- [vue2 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/packages/ui-vue2)
- [react 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/packages/ui-react)

View File

@ -2,14 +2,14 @@
tmagic-editor支持业务方进行自定义组件开发。在tmagic-editor中组件是以 npm 包形式存在的组件和插件只要按照规范开发就可以在tmagic-editor的 runtime 中被加入并正确渲染组件。 tmagic-editor支持业务方进行自定义组件开发。在tmagic-editor中组件是以 npm 包形式存在的组件和插件只要按照规范开发就可以在tmagic-editor的 runtime 中被加入并正确渲染组件。
## 组件开发 ## 组件开发
以 vue 的组件开发为例。运行项目中的 playground 示例,会自动加载 vue 的 runtime。runtime会加载[@tmagic/ui](https://github.com/Tencent/tmagic-editor/tree/master/packages/ui) 以 vue3 的组件开发为例。运行项目中的 playground 示例,会自动加载 vue3 的 runtime。runtime会加载[@tmagic/ui](https://github.com/Tencent/tmagic-editor/tree/master/packages/ui)
## 组件注册 ## 组件注册
在 [playground](https://tencent.github.io/tmagic-editor/playground/index.html#/) 中,我们可以尝试点击添加一个组件,在模拟器区域里,就会出现这个组件。其中就涉及到组件注册。 在 [playground](https://tencent.github.io/tmagic-editor/playground/index.html#/) 中,我们可以尝试点击添加一个组件,在模拟器区域里,就会出现这个组件。其中就涉及到组件注册。
这一步需要开发者基于tmagic-editor搭建了平台后实现组件列表的注册、获取机制tmagic-editor组件注册其实就是保存好组件 `type` 的映射关系。`type` 可以参考[组件介绍](../guide/conception.html#组件)。 这一步需要开发者基于tmagic-editor搭建了平台后实现组件列表的注册、获取机制tmagic-editor组件注册其实就是保存好组件 `type` 的映射关系。`type` 可以参考[组件介绍](../guide/conception.html#组件)。
可以参考 vue 版本的 @tmagic/ui 中,[组件渲染](../guide/advanced/page.html#组件渲染)逻辑里type 会作为组件名进入渲染。所以在 vue 的组件开发中,我们也需要在为 vue 组件声明 name 字段时,和 type 值对应起来,才能正确渲染组件。 可以参考 vue3 版本的 @tmagic/ui 中,[组件渲染](../guide/advanced/page.html#组件渲染)逻辑里type 会作为组件名进入渲染。所以在 vue3 的组件开发中,我们也需要在为 vue 组件声明 name 字段时,和 type 值对应起来,才能正确渲染组件。
### 组件规范 ### 组件规范
组件的基础形式,需要有四个文件 组件的基础形式,需要有四个文件
@ -22,7 +22,7 @@ tmagic-editor支持业务方进行自定义组件开发。在tmagic-editor中
@tmagic/ui 中的 button/text 就是基础的组件示例。我们要求声明 index 入口,因为我们希望在后续的配套打包工具实现上,可以有一个统一规范入口。 @tmagic/ui 中的 button/text 就是基础的组件示例。我们要求声明 index 入口,因为我们希望在后续的配套打包工具实现上,可以有一个统一规范入口。
### 1. 创建组件 ### 1. 创建组件
在项目中,如 runtime 目录中,创建一个名为 test-component 的组件目录,其中包含上面四个规范文件。 在项目中,如 runtime vue3 目录中,创建一个名为 test-component 的组件目录,其中包含上面四个规范文件。
```javascript ```javascript
// index.js // index.js
// vue // vue
@ -69,7 +69,7 @@ export default {
}; };
``` ```
版本的组件代码示例 vue3 版本的组件代码示例
```vue ```vue
<!-- Test.vue --> <!-- Test.vue -->
<template> <template>
@ -119,7 +119,7 @@ export default Test;
``` ```
### 2. 使用tmagic-cli ### 2. 使用tmagic-cli
在 runtime vue 中,我们已经提供好一份示例。在 tmagic.config.ts 文件中。只需要在 packages 加入你创建的组件的路径(如果是个 npm 包,则将路径替换为包名即可),打包工具就会自动识别到你的组件。 在 runtime vue3 中,我们已经提供好一份示例。在 tmagic.config.ts 文件中。只需要在 packages 加入你创建的组件的路径(如果是个 npm 包,则将路径替换为包名即可),打包工具就会自动识别到你的组件。
### 3. 启动 playground ### 3. 启动 playground
在上面的步骤完成后,在 playground/src/configs/componentGroupList 中。找到组件栏的基础组件列表,在其中加入你的开发组件 在上面的步骤完成后,在 playground/src/configs/componentGroupList 中。找到组件栏的基础组件列表,在其中加入你的开发组件
@ -155,7 +155,7 @@ npm run playground
<img src="https://image.video.qpic.cn/oa_fd3c9c-3_548108267_1636719045199471"> <img src="https://image.video.qpic.cn/oa_fd3c9c-3_548108267_1636719045199471">
### 4. 启动 runtime ### 4. 启动 runtime
在完成开发中组件在编辑器中的实现后,我们将编辑器中的 DSL 源码📄 打开,复制 DSL。并在 runtime/vue/src/page 下。创建一个 page-config.js 文件。将 DSL 作为配置导出。 在完成开发中组件在编辑器中的实现后,我们将编辑器中的 DSL 源码📄 打开,复制 DSL。并在 runtime/vue3/src/page 下。创建一个 page-config.js 文件。将 DSL 作为配置导出。
```javascript ```javascript
window.magicDSL = [ window.magicDSL = [
@ -168,7 +168,7 @@ window.magicDSL = [
import './page-config.js'; import './page-config.js';
``` ```
然后执行在 runtime/ 目录下执行 然后执行在 runtime/vue3 目录下执行
``` ```
npm run build:libs npm run build:libs
npm run dev npm run dev

View File

@ -60,6 +60,11 @@ DSL 是编辑器搭建页面的最终产物(描述文件),其中包含了
### runtime ### runtime
我们把页面统一称为 runtime更具体的 runtime 概念可以查看[页面发布](./publish.html#runtime)。**runtime 是承载tmagic-editor项目页面的运行环境**。编辑器的工作区是 runtime 的一个具体实例,另一个就是我们发布上线后,用户访问的真实项目页面。 我们把页面统一称为 runtime更具体的 runtime 概念可以查看[页面发布](./publish.html#runtime)。**runtime 是承载tmagic-editor项目页面的运行环境**。编辑器的工作区是 runtime 的一个具体实例,另一个就是我们发布上线后,用户访问的真实项目页面。
### @tmagic/ui
@tmagic/ui 包含了tmagic-editor的基础组件库提供了容器、文本、按钮这样的基础组件。我们提供了不同语言框架的 @tmagic/ui,如 vue2 和 vue3。
@tmagic/ui 和 runtime 是配套出现的runtime 必须基于 @tmagic/ui 才可以实现渲染。因为 @tmagic/ui 需要提供 runtime 所需要的渲染器。
## 联动 ## 联动
页面搭建过程中,会涉及到两种联动形式 页面搭建过程中,会涉及到两种联动形式
- 在编辑器中,组件的表单配置项之间需要联动。 - 在编辑器中,组件的表单配置项之间需要联动。

View File

@ -125,7 +125,7 @@ app.mount('#app');
// 初始化页面数据 // 初始化页面数据
}), }),
runtimeUrl: "/runtime/vue/playground/index.html", runtimeUrl: "/runtime/vue3/playground/index.html",
propsConfigs: [ propsConfigs: [
// 组件属性列表 // 组件属性列表
@ -208,10 +208,10 @@ npm install sass -D
```javascript ```javascript
setup() { setup() {
asyncLoadJs(`/runtime/vue/assets/config.js`).then(() => { asyncLoadJs(`/runtime/vue3/assets/config.js`).then(() => {
propsConfigs.value = window.magicPresetConfigs; propsConfigs.value = window.magicPresetConfigs;
}); });
asyncLoadJs(`/runtime/vue/assets/value.js`).then(() => { asyncLoadJs(`/runtime/vue3/assets/value.js`).then(() => {
propsValues.value = window.magicPresetValues; propsValues.value = window.magicPresetValues;
}); });
} }

View File

@ -36,6 +36,9 @@ tmagic-editor可视化开源项目是从魔方平台演化而来的开源项目
- **@tmagic/core** 实现对组件进行跨框架管理与一些通用复杂逻辑的实现。 - **@tmagic/core** 实现对组件进行跨框架管理与一些通用复杂逻辑的实现。
- **@tmagic/data-source** 实现数据源的管理与编译。 - **@tmagic/data-source** 实现数据源的管理与编译。
- **@tmagic/stage** 实现在编辑器中对组件的位置拖动与大小拖拉。 - **@tmagic/stage** 实现在编辑器中对组件的位置拖动与大小拖拉。
- **@tmagic/ui** 提供一些vue3基础组件。
- **@tmagic/ui-vue2** 提供一些vue2基础组件。
- **@tmagic/ui-react** 提供一些react基础组件。
- **runtime** 实现在编辑器中对使用不同框架的组件的渲染。 - **runtime** 实现在编辑器中对使用不同框架的组件的渲染。
可以查阅 tmagic 的[源代码](https://github.com/Tencent/tmagic-editor),与文档描述内容可以逐一对应上,希望文档内容可以为开发者带来比较好的开发体验。 可以查阅 tmagic 的[源代码](https://github.com/Tencent/tmagic-editor),与文档描述内容可以逐一对应上,希望文档内容可以为开发者带来比较好的开发体验。

View File

@ -13,7 +13,7 @@ runtime 的概念是理解tmagic-editor项目页运行的重要概念runti
所以更深入描述runtime 是tmagic-editor页面的渲染环境提供不同场景下的能力封装。如果理解了tmagic-editor的设计阅读了tmagic-editor的源码可以发现runtime 只是对tmagic-editor的渲染器做了一层包装在不同 runtime 中tmagic-editor的渲染逻辑和组件代码都是相同的。 所以更深入描述runtime 是tmagic-editor页面的渲染环境提供不同场景下的能力封装。如果理解了tmagic-editor的设计阅读了tmagic-editor的源码可以发现runtime 只是对tmagic-editor的渲染器做了一层包装在不同 runtime 中tmagic-editor的渲染逻辑和组件代码都是相同的。
并且由于tmagic-editor在编辑器中的模拟器是通过 iframe 渲染的和tmagic-editor平台本身可以做到框架解耦所以 runtime 也可以用不同框架开发。目前tmagic-editor提供了 vue 和 react 的 runtime 示例。 并且由于tmagic-editor在编辑器中的模拟器是通过 iframe 渲染的和tmagic-editor平台本身可以做到框架解耦所以 runtime 也可以用不同框架开发。目前tmagic-editor提供了 vue2/vue3 和 react 的 runtime 示例。
各个 runtime 的作用除了作为不同场景下的渲染环境同时也是不同环境的打包构建载体。tmagic-editor示例代码中的打包就是基于 runtime 进行的。 各个 runtime 的作用除了作为不同场景下的渲染环境同时也是不同环境的打包构建载体。tmagic-editor示例代码中的打包就是基于 runtime 进行的。
@ -21,7 +21,8 @@ runtime 的概念是理解tmagic-editor项目页运行的重要概念runti
由于 runtime 是页面渲染的承载环境,其中会加载 @tmagic/ui 以及各个业务组件,业务发布项目页也是基于 runtime所以在 runtime 中实现业务方的自定义逻辑是最合适的。runtime 可以提供一些全局 API供业务组件调用。我们可以把下面的模拟器中的 runtime 视为一个业务方runtime。 由于 runtime 是页面渲染的承载环境,其中会加载 @tmagic/ui 以及各个业务组件,业务发布项目页也是基于 runtime所以在 runtime 中实现业务方的自定义逻辑是最合适的。runtime 可以提供一些全局 API供业务组件调用。我们可以把下面的模拟器中的 runtime 视为一个业务方runtime。
tmagic-editor提供了三个版本的 runtime 示例,可以参考: tmagic-editor提供了三个版本的 runtime 示例,可以参考:
- [vue runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue) - [vue3 runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue3)
- [vue2 runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue2)
- [react runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/react) - [react runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/react)
### 真实页面渲染Page ### 真实页面渲染Page

View File

@ -14,7 +14,8 @@ runtime 的 `page` 部分,就是真实项目页面的渲染环境。发布出
- 加载第三方全局组件/插件等 - 加载第三方全局组件/插件等
具体的 page 实现示例,可以参考 具体的 page 实现示例,可以参考
- [vue runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue/page) - [vue3 runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue3/page)
- [vue2 runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue2/page)
- [react runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/react/page) - [react runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/react/page)
### playground ### playground
@ -44,7 +45,8 @@ window.magic.onPageElUpdate(document.querySelector('.magic-ui-page'));
|sortNode| 组件在容器间排序 |{ `src` , `dist`, `root` }: `SortEventData` | |sortNode| 组件在容器间排序 |{ `src` , `dist`, `root` }: `SortEventData` |
runtime 的实现示例可以参考tmagic-editor提供的 runtime 的实现示例可以参考tmagic-editor提供的
- [vue runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue) - [vue3 runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue3)
- [vue2 runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue2)
- [react runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/react) - [react runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/react)
### 页面发布 ### 页面发布

View File

@ -1,6 +1,6 @@
# 3.[DSL](../conception.md#dsl) 解析渲染 # 3.[DSL](../conception.md#dsl) 解析渲染
tmagic 提供了 vue/react 两个个版本的解析渲染组件,可以直接使用 tmagic 提供了 vue3/vue2/react 三个版本的解析渲染组件,可以直接使用
[@tmagic/ui](https://www.npmjs.com/package/@tmagic/ui) [@tmagic/ui](https://www.npmjs.com/package/@tmagic/ui)

View File

@ -1,5 +1,6 @@
import js from '@eslint/js'; import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin'; import stylistic from '@stylistic/eslint-plugin';
import stylisticTs from '@stylistic/eslint-plugin-ts';
import parserTs from '@typescript-eslint/parser'; import parserTs from '@typescript-eslint/parser';
import { defineConfig } from 'eslint/config'; import { defineConfig } from 'eslint/config';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
@ -20,6 +21,7 @@ export default (tsconfigRootDir) =>
...tseslint.config(tencentEslintBaseConfig, tencentEslintImportexport, tseslint.configs.base, { ...tseslint.config(tencentEslintBaseConfig, tencentEslintImportexport, tseslint.configs.base, {
plugins: { plugins: {
'@stylistic': stylistic, '@stylistic': stylistic,
'@stylistic/ts': stylisticTs,
}, },
languageOptions: { languageOptions: {
parser: parserTs, parser: parserTs,

View File

@ -1,6 +1,6 @@
{ {
"name": "@tmagic/eslint-config", "name": "@tmagic/eslint-config",
"version": "0.0.3", "version": "0.0.2",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
"repository": { "repository": {
@ -9,17 +9,18 @@
"url": "https://github.com/Tencent/tmagic-editor.git" "url": "https://github.com/Tencent/tmagic-editor.git"
}, },
"dependencies": { "dependencies": {
"@eslint/js": "^9.34.0", "@eslint/js": "^9.24.0",
"@typescript-eslint/parser": "^8.41.0", "@typescript-eslint/parser": "^8.30.1",
"@typescript-eslint/eslint-plugin": "^8.41.0 ", "@typescript-eslint/eslint-plugin": "^8.30.1",
"@stylistic/eslint-plugin": "^5.2.3", "@stylistic/eslint-plugin": "^4.2.0",
"eslint-config-prettier": "^10.1.8", "@stylistic/eslint-plugin-ts": "^4.2.0",
"eslint-plugin-import": "^2.32.0", "eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-vue": "^10.4.0", "eslint-plugin-vue": "^10.0.0",
"eslint-plugin-prettier": "^5.5.4 ", "eslint-plugin-prettier": "^5.2.6",
"globals": "^16.3.0", "globals": "^16.0.0",
"typescript-eslint": "^8.41.0" "typescript-eslint": "^8.30.1"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": ">=9.24.0", "eslint": ">=9.24.0",

View File

@ -1,9 +1,9 @@
{ {
"version": "1.6.1", "version": "1.5.22",
"name": "tmagic", "name": "tmagic",
"private": true, "private": true,
"type": "module", "type": "module",
"packageManager": "pnpm@10.18.2", "packageManager": "pnpm@10.11.1",
"scripts": { "scripts": {
"bootstrap": "pnpm i && pnpm build", "bootstrap": "pnpm i && pnpm build",
"clean:top": "rimraf */**/dist */**/types */dist coverage dwt* temp packages/cli/lib", "clean:top": "rimraf */**/dist */**/types */dist coverage dwt* temp packages/cli/lib",
@ -11,14 +11,16 @@
"clean:all": "pnpm clean:top && pnpm clean:modules", "clean:all": "pnpm clean:top && pnpm clean:modules",
"lint": "eslint --cache .", "lint": "eslint --cache .",
"lint-fix": "eslint --fix --cache .", "lint-fix": "eslint --fix --cache .",
"playground": "pnpm --filter \"runtime-vue\" build:libs && pnpm --filter \"runtime-vue\" --filter \"tmagic-playground\" dev", "playground": "pnpm --filter \"runtime-vue3\" build:libs && pnpm --filter \"runtime-vue3\" --filter \"tmagic-playground\" dev",
"pg": "pnpm playground", "pg": "pnpm playground",
"playground:vue2": "pnpm --filter \"runtime-vue2\" build:libs && pnpm --filter \"runtime-vue2\" --filter \"tmagic-playground\" dev:vue2",
"pg:vue2": "pnpm playground:vue2",
"playground:react": "pnpm --filter \"runtime-react\" build:libs && pnpm --filter \"runtime-react\" --filter \"tmagic-playground\" dev:react", "playground:react": "pnpm --filter \"runtime-react\" build:libs && pnpm --filter \"runtime-react\" --filter \"tmagic-playground\" dev:react",
"pg:react": "pnpm playground:react", "pg:react": "pnpm playground:react",
"build": "pnpm build:dts && node scripts/build.mjs", "build": "pnpm build:dts && node scripts/build.mjs",
"build:dts": "pnpm --filter \"@tmagic/cli\" build && tsc -p tsconfig.build-browser.json && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.build-vue.json && rollup -c rollup.dts.config.js && rimraf temp", "build:dts": "pnpm --filter \"@tmagic/cli\" build && tsc -p tsconfig.build-browser.json && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.build-vue.json && rollup -c rollup.dts.config.js && rimraf temp",
"check:type": "tsc --incremental --noEmit -p tsconfig.check.json && vue-tsc --noEmit -p tsconfig.check-vue.json", "check:type": "tsc --incremental --noEmit -p tsconfig.check.json && vue-tsc --noEmit -p tsconfig.check-vue.json",
"build:playground": "pnpm --filter \"runtime-vue\" build && pnpm --filter \"tmagic-playground\" build", "build:playground": "pnpm --filter \"runtime-vue3\" build && pnpm --filter \"tmagic-playground\" build",
"docs:dev": "vitepress dev docs", "docs:dev": "vitepress dev docs",
"docs:serve": "vitepress serve docs", "docs:serve": "vitepress serve docs",
"docs:build": "vitepress build docs", "docs:build": "vitepress build docs",
@ -30,7 +32,7 @@
"release": "node scripts/release.mjs" "release": "node scripts/release.mjs"
}, },
"engines": { "engines": {
"node": "^20.19.0 || >=22.12.0" "node": ">=18"
}, },
"workspaces": [ "workspaces": [
"packages/*" "packages/*"
@ -44,18 +46,18 @@
"@commitlint/config-conventional": "^19.8.1", "@commitlint/config-conventional": "^19.8.1",
"@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-alias": "^5.1.1",
"@tmagic/eslint-config": "workspace:*", "@tmagic/eslint-config": "workspace:*",
"@types/node": "24.0.10", "@types/node": "18.19.61",
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^5.2.3",
"@vitest/coverage-v8": "^2.1.9", "@vitest/coverage-v8": "^2.1.9",
"@vue/compiler-sfc": "catalog:", "@vue/compiler-sfc": "^3.5.13",
"c8": "^7.14.0", "c8": "^7.14.0",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"conventional-changelog-cli": "^5.0.0", "conventional-changelog-cli": "^5.0.0",
"cosmiconfig": "^8.3.6", "cosmiconfig": "^8.3.6",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"element-plus": "^2.11.4", "element-plus": "^2.9.11",
"enquirer": "^2.4.1", "enquirer": "^2.4.1",
"eslint": "^9.37.0", "eslint": "^9.28.0",
"execa": "^4.1.0", "execa": "^4.1.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"husky": "^9.1.7", "husky": "^9.1.7",
@ -63,20 +65,20 @@
"lint-staged": "^16.1.0", "lint-staged": "^16.1.0",
"minimist": "^1.2.8", "minimist": "^1.2.8",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"prettier": "^3.6.2", "prettier": "^3.5.3",
"recast": "^0.23.11", "recast": "^0.23.11",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "4.44.1", "rollup": "^4.38.0",
"rollup-plugin-dts": "^6.2.1", "rollup-plugin-dts": "^6.2.1",
"semver": "^7.7.1", "semver": "^7.7.1",
"serialize-javascript": "^6.0.2", "serialize-javascript": "^6.0.2",
"shx": "^0.3.4", "shx": "^0.3.4",
"typescript": "catalog:", "typescript": "catalog:",
"vite": "catalog:", "vite": "catalog:",
"vitepress": "^1.6.4", "vitepress": "^1.6.3",
"vitest": "^3.2.4", "vitest": "^3.2.1",
"vue": "catalog:", "vue": "catalog:",
"vue-tsc": "^3.1.1" "vue-tsc": "^2.2.8"
}, },
"config": { "config": {
"commitizen": { "commitizen": {

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.1", "version": "1.5.22",
"name": "@tmagic/cli", "name": "@tmagic/cli",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -30,17 +30,16 @@
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"esbuild": "^0.21.5", "esbuild": "^0.21.5",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"merge-options": "^3.0.4",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"recast": "^0.23.11", "recast": "^0.23.11",
"tslib": "^2.8.0" "tslib": "^2.8.0"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/node": "^24.0.10" "@types/node": "^18.19.61"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "catalog:" "typescript": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {

View File

@ -1,8 +1,6 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'fs-extra'; import fs from 'fs-extra';
// @ts-ignore
import mergeOptions from 'merge-options';
import App from '../Core'; import App from '../Core';
import { UserConfig } from '../types'; import { UserConfig } from '../types';
@ -24,29 +22,18 @@ export const scripts = (defaultAppConfig: UserConfig) => {
path.resolve(defaultAppConfig.temp, 'config.cjs'), path.resolve(defaultAppConfig.temp, 'config.cjs'),
].find((item) => fs.pathExistsSync(item)); ].find((item) => fs.pathExistsSync(item));
const localUserConfigPath = [ const { npmConfig = {}, ...userConfig } = await loadUserConfig(userConfigPath);
path.resolve(defaultAppConfig.source, 'tmagic.config.local.ts'),
path.resolve(defaultAppConfig.source, 'tmagic.config.local.js'),
path.resolve(defaultAppConfig.source, 'tmagic.config.local.cjs'),
path.resolve(defaultAppConfig.temp, 'config.local.ts'),
path.resolve(defaultAppConfig.temp, 'config.local.js'),
path.resolve(defaultAppConfig.temp, 'config.local.cjs'),
].find((item) => fs.pathExistsSync(item));
let userConfig = await loadUserConfig(userConfigPath);
if (localUserConfigPath) {
const localUserConfig = await loadUserConfig(localUserConfigPath);
if (localUserConfig.packages?.length) {
localUserConfig.packages = [...(userConfig.packages || []), ...localUserConfig.packages];
}
userConfig = mergeOptions(userConfig, localUserConfig);
}
// resolve the final app config to use // resolve the final app config to use
const appConfig = mergeOptions(defaultAppConfig, userConfig); const appConfig = {
...defaultAppConfig,
...userConfig,
npmConfig: {
...(defaultAppConfig.npmConfig || {}),
...npmConfig,
},
};
const app = new App(appConfig); const app = new App(appConfig);
// clean temp and cache // clean temp and cache

View File

@ -1,35 +1,11 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import process from 'node:process';
const windowsPathRegex = /^(?:[a-zA-Z]:|[\\/]{2}[^\\/]+[\\/]+[^\\/]+)?[\\/]$/;
export const isRootPath = (path: string) => {
if (typeof path !== 'string') {
throw new TypeError('Expected a string');
}
path = path.trim();
// While it's unclear how long a root path can be on Windows, it definitely cannot be longer than 100 characters.
if (path === '' || path.length > 100) {
return false;
}
return process.platform === 'win32' ? windowsPathRegex.test(path) : path === '/';
};
export const backupFile = (runtimeSource: string, file: string) => { export const backupFile = (runtimeSource: string, file: string) => {
if (isRootPath(runtimeSource)) {
return;
}
const filePath = path.join(runtimeSource, file); const filePath = path.join(runtimeSource, file);
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
fs.copyFileSync(filePath, `${filePath}.bak`); fs.copyFileSync(filePath, `${filePath}.bak`);
} else {
backupFile(path.resolve(runtimeSource, '..'), file);
} }
}; };
@ -50,17 +26,11 @@ export const backupLock = (runtimeSource: string, npmType: string) => {
}; };
export const restoreFile = (runtimeSource: string, file: string) => { export const restoreFile = (runtimeSource: string, file: string) => {
if (isRootPath(runtimeSource)) {
return;
}
const filePath = path.join(runtimeSource, file); const filePath = path.join(runtimeSource, file);
if (fs.existsSync(filePath)) { if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
fs.renameSync(`${filePath}.bak`, filePath); fs.renameSync(`${filePath}.bak`, filePath);
} else {
restoreFile(path.resolve(runtimeSource, '..'), file);
} }
}; };

View File

@ -601,20 +601,10 @@ const flattenPackagesConfig = (packages: (string | Record<string, string>)[]) =>
const packagesConfig: ([string] | [string, string])[] = []; const packagesConfig: ([string] | [string, string])[] = [];
packages.forEach((item) => { packages.forEach((item) => {
if (typeof item === 'object') { if (typeof item === 'object') {
for (const [key, packagePath] of Object.entries(item)) { Object.entries(item).forEach(([key, packagePath]) => {
const index = packagesConfig.findIndex(([, k]) => { packagesConfig.push([packagePath, key]);
return k === key; });
});
if (index > -1) {
packagesConfig[index] = [packagePath, key];
} else {
packagesConfig.push([packagePath, key]);
}
}
} else if (typeof item === 'string') { } else if (typeof item === 'string') {
if (packagesConfig.find(([k]) => k === item)) {
return;
}
packagesConfig.push([item]); packagesConfig.push([item]);
} }
}); });

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.1", "version": "1.5.22",
"name": "@tmagic/core", "name": "@tmagic/core",
"type": "module", "type": "module",
"main": "dist/tmagic-core.umd.cjs", "main": "dist/tmagic-core.umd.cjs",
@ -41,11 +41,11 @@
"lodash-es": "^4.17.21" "lodash-es": "^4.17.21"
}, },
"devDependencies": { "devDependencies": {
"@types/events": "^3.0.3", "@types/events": "^3.0.0",
"@types/lodash-es": "^4.17.4" "@types/lodash-es": "^4.17.4"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "catalog:" "typescript": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,7 +29,7 @@ import Flexible from './Flexible';
import FlowState from './FlowState'; import FlowState from './FlowState';
import Node from './Node'; import Node from './Node';
import Page from './Page'; import Page from './Page';
import { AppOptionsConfig, ErrorHandler, GetNodeOptions } from './type'; import { AppOptionsConfig, ErrorHandler } from './type';
import { transformStyle as defaultTransformStyle } from './utils'; import { transformStyle as defaultTransformStyle } from './utils';
class App extends EventEmitter { class App extends EventEmitter {
@ -45,7 +45,6 @@ class App extends EventEmitter {
public codeDsl?: CodeBlockDSL; public codeDsl?: CodeBlockDSL;
public dataSourceManager?: DataSourceManager; public dataSourceManager?: DataSourceManager;
public page?: Page; public page?: Page;
public pageFragments: Map<Id, Page> = new Map();
public useMock = false; public useMock = false;
public platform = 'mobile'; public platform = 'mobile';
public jsEngine: JsEngine = 'browser'; public jsEngine: JsEngine = 'browser';
@ -160,20 +159,11 @@ class App extends EventEmitter {
super.emit('dsl-change', { dsl: config, curPage: pageId }); super.emit('dsl-change', { dsl: config, curPage: pageId });
this.pageFragments.forEach((page) => {
page.destroy();
});
this.pageFragments.clear();
this.setPage(pageId); this.setPage(pageId);
if (this.dataSourceManager) { if (this.dataSourceManager) {
if (this.dataSourceManager.isAllDataSourceRegistered()) { const dataSourceList = Array.from(this.dataSourceManager.dataSourceMap.values());
this.eventHelper?.bindDataSourceEvents(); this.eventHelper?.bindDataSourceEvents(dataSourceList);
} else {
this.dataSourceManager.once('registered-all', () => {
this.eventHelper?.bindDataSourceEvents();
});
}
} }
} }
@ -202,11 +192,6 @@ class App extends EventEmitter {
for (const [, node] of this.page.nodes) { for (const [, node] of this.page.nodes) {
this.eventHelper.bindNodeEvents(node); this.eventHelper.bindNodeEvents(node);
} }
for (const [, page] of this.pageFragments) {
for (const [, node] of page.nodes) {
this.eventHelper.bindNodeEvents(node);
}
}
} }
super.emit('page-change', this.page); super.emit('page-change', this.page);
@ -230,8 +215,8 @@ class App extends EventEmitter {
} }
} }
public getNode<T extends Node = Node>(id: Id, options?: GetNodeOptions) { public getNode<T extends Node = Node>(id: Id, iteratorContainerId?: Id[], iteratorIndex?: number[]) {
return this.page?.getNode<T>(id, options); return this.page?.getNode<T>(id, iteratorContainerId, iteratorIndex);
} }
public registerComponent(type: string, Component: any) { public registerComponent(type: string, Component: any) {
@ -264,15 +249,15 @@ class App extends EventEmitter {
* @param eventConfig * @param eventConfig
* @returns void * @returns void
*/ */
public async runCode(codeId: Id, params: Record<string, any>, args: any[], flowState?: FlowState, node?: Node) { public async runCode(codeId: Id, params: Record<string, any>, args: any[], flowState?: FlowState) {
if (!codeId || isEmpty(this.codeDsl)) return; if (!codeId || isEmpty(this.codeDsl)) return;
const content = this.codeDsl?.[codeId]?.content; const content = this.codeDsl?.[codeId]?.content;
if (typeof content === 'function') { if (typeof content === 'function') {
try { try {
await content({ app: this, params, eventParams: args, flowState, node }); await content({ app: this, params, eventParams: args, flowState });
} catch (e: any) { } catch (e: any) {
if (this.errorHandler) { if (this.errorHandler) {
this.errorHandler(e, undefined, { type: 'run-code', codeId, params, eventParams: args, flowState, node }); this.errorHandler(e, undefined, { type: 'run-code', codeId, params, eventParams: args, flowState });
} else { } else {
throw e; throw e;
} }
@ -286,7 +271,6 @@ class App extends EventEmitter {
params: Record<string, any>, params: Record<string, any>,
args: any[], args: any[],
flowState?: FlowState, flowState?: FlowState,
node?: Node,
) { ) {
if (!dsId || !methodName) return; if (!dsId || !methodName) return;
@ -294,32 +278,28 @@ class App extends EventEmitter {
if (!dataSource) return; if (!dataSource) return;
try { const methods = dataSource.methods || [];
const methods = dataSource.methods || [];
const method = methods.find((item) => item.name === methodName); const method = methods.find((item) => item.name === methodName);
if (method && typeof method.content === 'function') {
await method.content({ app: this, params, dataSource, eventParams: args, flowState, node }); if (!method) return;
} else if (typeof dataSource[methodName] === 'function') {
await dataSource[methodName](); if (typeof method.content === 'function') {
} try {
} catch (e: any) { await method.content({ app: this, params, dataSource, eventParams: args, flowState });
if (this.errorHandler) { } catch (e: any) {
this.errorHandler(e, dataSource, { type: 'data-source-method', params, eventParams: args, flowState, node }); if (this.errorHandler) {
} else { this.errorHandler(e, dataSource, { type: 'data-source-method', params, eventParams: args, flowState });
throw e; } else {
throw e;
}
} }
} }
} }
public destroy() { public destroy() {
this.removeAllListeners(); this.removeAllListeners();
this.page?.destroy();
this.page = undefined; this.page = undefined;
this.pageFragments.forEach((page) => {
page.destroy();
});
this.pageFragments.clear();
this.flexible?.destroy(); this.flexible?.destroy();
this.flexible = undefined; this.flexible = undefined;

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -31,9 +31,6 @@ import {
type DataSourceItemConfig, type DataSourceItemConfig,
type EventActionItem, type EventActionItem,
type EventConfig, type EventConfig,
Id,
NODE_DISABLE_CODE_BLOCK_KEY,
NODE_DISABLE_DATA_SOURCE_KEY,
} from '@tmagic/schema'; } from '@tmagic/schema';
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils'; import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
@ -42,17 +39,8 @@ import FlowState from './FlowState';
import type { default as TMagicNode } from './Node'; import type { default as TMagicNode } from './Node';
import { AfterEventHandler, BeforeEventHandler } from './type'; import { AfterEventHandler, BeforeEventHandler } from './type';
interface EventCache {
toId: Id;
method: string;
fromCpt: any;
args: any[];
handled?: boolean;
}
export default class EventHelper extends EventEmitter { export default class EventHelper extends EventEmitter {
public app: TMagicApp; public app: TMagicApp;
public eventQueue: EventCache[] = [];
private nodeEventList = new Map<(fromCpt: TMagicNode, ...args: any[]) => void, symbol>(); private nodeEventList = new Map<(fromCpt: TMagicNode, ...args: any[]) => void, symbol>();
private dataSourceEventList = new Map<string, Map<string, (...args: any[]) => void>>(); private dataSourceEventList = new Map<string, Map<string, (...args: any[]) => void>>();
@ -113,23 +101,21 @@ export default class EventHelper extends EventEmitter {
} }
public removeNodeEvents() { public removeNodeEvents() {
for (const handler of Array.from(this.nodeEventList.keys())) { Array.from(this.nodeEventList.keys()).forEach((handler) => {
const name = this.nodeEventList.get(handler); const name = this.nodeEventList.get(handler);
name && this.off(name, handler); name && this.off(name, handler);
} });
this.nodeEventList.clear(); this.nodeEventList.clear();
} }
public bindDataSourceEvents() { public bindDataSourceEvents(dataSourceList: DataSource[]) {
const dataSourceList = Array.from(this.app.dataSourceManager?.dataSourceMap.values() || []);
this.removeDataSourceEvents(dataSourceList); this.removeDataSourceEvents(dataSourceList);
for (const dataSource of dataSourceList) { dataSourceList.forEach((dataSource) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSource.id) ?? new Map<string, (args: any) => void>(); const dataSourceEvent = this.dataSourceEventList.get(dataSource.id) ?? new Map<string, (args: any) => void>();
for (const event of dataSource.schema.events || []) { (dataSource.schema.events || []).forEach((event) => {
const [prefix, ...path] = event.name?.split('.') || []; const [prefix, ...path] = event.name?.split('.') || [];
if (!prefix) return; if (!prefix) return;
const handler = (...args: any[]) => { const handler = (...args: any[]) => {
@ -143,9 +129,9 @@ export default class EventHelper extends EventEmitter {
// 数据源自定义事件 // 数据源自定义事件
dataSource.on(prefix, handler); dataSource.on(prefix, handler);
} }
} });
this.dataSourceEventList.set(dataSource.id, dataSourceEvent); this.dataSourceEventList.set(dataSource.id, dataSourceEvent);
} });
} }
public removeDataSourceEvents(dataSourceList: DataSource[]) { public removeDataSourceEvents(dataSourceList: DataSource[]) {
@ -154,32 +140,24 @@ export default class EventHelper extends EventEmitter {
} }
// 先清掉之前注册的事件,重新注册 // 先清掉之前注册的事件,重新注册
for (const dataSource of dataSourceList) { dataSourceList.forEach((dataSource) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSource.id)!; const dataSourceEvent = this.dataSourceEventList.get(dataSource.id)!;
if (!dataSourceEvent) return; if (!dataSourceEvent) return;
for (const eventName of Array.from(dataSourceEvent.keys())) { Array.from(dataSourceEvent.keys()).forEach((eventName) => {
const [prefix, ...path] = eventName.split('.'); const [prefix, ...path] = eventName.split('.');
if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) { if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) {
dataSource.offDataChange(path.join('.'), dataSourceEvent.get(eventName)!); dataSource.offDataChange(path.join('.'), dataSourceEvent.get(eventName)!);
} else { } else {
dataSource.off(prefix, dataSourceEvent.get(eventName)!); dataSource.off(prefix, dataSourceEvent.get(eventName)!);
} }
} });
} });
this.dataSourceEventList.clear(); this.dataSourceEventList.clear();
} }
public getEventQueue() {
return this.eventQueue;
}
public addEventToQueue(event: EventCache) {
this.eventQueue.push(event);
}
/** /**
* *
* @param eventsConfigIndex node.event中获取最新事件配置 * @param eventsConfigIndex node.event中获取最新事件配置
@ -202,9 +180,9 @@ export default class EventHelper extends EventEmitter {
if (typeof config === 'number') { if (typeof config === 'number') {
// 事件响应中可能会有修改数据源数据的会更新dsl所以这里需要重新获取 // 事件响应中可能会有修改数据源数据的会更新dsl所以这里需要重新获取
const actionItem = ((fromCpt as TMagicNode).events[config] as EventConfig).actions[i]; const actionItem = ((fromCpt as TMagicNode).events[config] as EventConfig).actions[i];
await this.actionHandler(actionItem, fromCpt as TMagicNode, args, flowState); this.actionHandler(actionItem, fromCpt as TMagicNode, args, flowState);
} else { } else {
await this.actionHandler(actions[i], fromCpt as DataSource, args, flowState); this.actionHandler(actions[i], fromCpt as DataSource, args, flowState);
} }
} }
flowState.reset(); flowState.reset();
@ -238,17 +216,10 @@ export default class EventHelper extends EventEmitter {
// 组件动作 // 组件动作
await this.compActionHandler(compActionItem, fromCpt, args); await this.compActionHandler(compActionItem, fromCpt, args);
} else if (actionItem.actionType === ActionType.CODE) { } else if (actionItem.actionType === ActionType.CODE) {
if (fromCpt.data[NODE_DISABLE_CODE_BLOCK_KEY]) {
return;
}
const codeActionItem = actionItem as CodeItemConfig; const codeActionItem = actionItem as CodeItemConfig;
// 执行代码块 // 执行代码块
await this.app.runCode(codeActionItem.codeId, codeActionItem.params || {}, args, flowState); await this.app.runCode(codeActionItem.codeId, codeActionItem.params || {}, args, flowState);
} else if (actionItem.actionType === ActionType.DATA_SOURCE) { } else if (actionItem.actionType === ActionType.DATA_SOURCE) {
if (fromCpt.data[NODE_DISABLE_DATA_SOURCE_KEY]) {
return;
}
const dataSourceActionItem = actionItem as DataSourceItemConfig; const dataSourceActionItem = actionItem as DataSourceItemConfig;
const [dsId, methodName] = dataSourceActionItem.dataSourceMethod; const [dsId, methodName] = dataSourceActionItem.dataSourceMethod;
@ -278,44 +249,19 @@ export default class EventHelper extends EventEmitter {
[to, methodName] = methodName; [to, methodName] = methodName;
} }
const toNodes = [];
const toNode = this.app.getNode(to); const toNode = this.app.getNode(to);
if (toNode) { if (!toNode) throw new Error(`ID为${to}的组件不存在`);
toNodes.push(toNode);
}
for (const [, page] of this.app.pageFragments) { if (toNode.instance) {
const node = page.getNode(to); if (typeof toNode.instance[methodName] === 'function') {
if (node) { await toNode.instance[methodName](fromCpt, ...args);
toNodes.push(node);
} }
} } else {
toNode.addEventToQueue({
if (toNodes.length === 0) {
this.addEventToQueue({
toId: to,
method: methodName, method: methodName,
fromCpt, fromCpt,
args, args,
}); });
return;
} }
const instanceMethodPropmise = [];
for (const node of toNodes) {
if (node.instance) {
if (typeof node.instance[methodName] === 'function') {
instanceMethodPropmise.push(node.instance[methodName](fromCpt, ...args));
}
} else {
node.addEventToQueue({
method: methodName,
fromCpt,
args,
});
}
}
await Promise.all(instanceMethodPropmise);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,8 +18,9 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { DataSource } from '@tmagic/data-source';
import type { EventConfig, MNode } from '@tmagic/schema'; import type { EventConfig, MNode } from '@tmagic/schema';
import { HookCodeType, HookType, NODE_DISABLE_CODE_BLOCK_KEY } from '@tmagic/schema'; import { HookCodeType, HookType } from '@tmagic/schema';
import type { default as TMagicApp } from './App'; import type { default as TMagicApp } from './App';
import type Page from './Page'; import type Page from './Page';
@ -48,7 +49,7 @@ class Node extends EventEmitter {
[key: string]: any; [key: string]: any;
}; };
public events: EventConfig[] = []; public events: EventConfig[] = [];
public instance?: any = null; public instance?: any = {};
public page?: Page; public page?: Page;
public parent?: Node; public parent?: Node;
public app: TMagicApp; public app: TMagicApp;
@ -74,14 +75,7 @@ class Node extends EventEmitter {
this.events = events || []; this.events = events || [];
this.style = style || {}; this.style = style || {};
try { try {
if ( this.instance.config = data;
this.instance &&
!Object.isFrozen(this.instance) &&
Object.getOwnPropertyDescriptor(this.instance, 'config')?.writable !== false &&
!this.instance.__isVue
) {
this.instance.config = data;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e: any) {} } catch (e: any) {}
@ -140,10 +134,23 @@ class Node extends EventEmitter {
for (const item of hookData.hookData) { for (const item of hookData.hookData) {
const { codeType = HookCodeType.CODE, codeId, params: itemParams = {} } = item; const { codeType = HookCodeType.CODE, codeId, params: itemParams = {} } = item;
if (codeType === HookCodeType.CODE && typeof codeId === 'string') { let functionContent: ((...args: any[]) => any) | string | undefined;
await this.app.runCode(codeId, params || itemParams, [], undefined, this); const functionParams: { app: TMagicApp; node: Node; params: Record<string, any>; dataSource?: DataSource } = {
app: this.app,
node: this,
params: params || itemParams,
};
if (codeType === HookCodeType.CODE && typeof codeId === 'string' && this.app.codeDsl?.[codeId]) {
functionContent = this.app.codeDsl[codeId].content;
} else if (codeType === HookCodeType.DATA_SOURCE_METHOD && Array.isArray(codeId) && codeId.length > 1) { } else if (codeType === HookCodeType.DATA_SOURCE_METHOD && Array.isArray(codeId) && codeId.length > 1) {
await this.app.runDataSourceMethod(codeId[0], codeId[1], params || itemParams, [], undefined, this); const dataSource = this.app.dataSourceManager?.get(codeId[0]);
functionContent = dataSource?.methods.find((method) => method.name === codeId[1])?.content;
functionParams.dataSource = dataSource;
}
if (functionContent && typeof functionContent === 'function') {
await functionContent(functionParams);
} }
} }
} }
@ -160,8 +167,8 @@ class Node extends EventEmitter {
this.once('created', (instance: any) => { this.once('created', (instance: any) => {
this.once('destroy', () => { this.once('destroy', () => {
this.instance = null; this.instance = null;
if (this.data[NODE_DISABLE_CODE_BLOCK_KEY] !== true) { if (typeof this.data.destroy === 'function') {
this.runHookCode('destroy'); this.data.destroy(this);
} }
this.listenLifeSafe(); this.listenLifeSafe();
@ -171,9 +178,7 @@ class Node extends EventEmitter {
this.setInstance(instance); this.setInstance(instance);
} }
if (this.data[NODE_DISABLE_CODE_BLOCK_KEY] !== true) { this.runHookCode('created');
this.runHookCode('created');
}
}); });
this.once('mounted', (instance: any) => { this.once('mounted', (instance: any) => {
@ -188,28 +193,7 @@ class Node extends EventEmitter {
} }
} }
if (this.app.eventHelper) { this.runHookCode('mounted');
for (const eventConfig of this.app.eventHelper.getEventQueue()) {
for (const [, page] of this.app.pageFragments) {
const node = page.getNode(eventConfig.toId);
if (node && node === this) {
if (typeof instance[eventConfig.method] === 'function') {
await instance[eventConfig.method](eventConfig.fromCpt, ...eventConfig.args);
}
eventConfig.handled = true;
}
}
}
this.app.eventHelper.eventQueue = this.app.eventHelper
.getEventQueue()
.filter((eventConfig) => !eventConfig.handled);
}
if (this.data[NODE_DISABLE_CODE_BLOCK_KEY] !== true) {
this.runHookCode('mounted');
}
}; };
handler(); handler();
}); });

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,7 +22,6 @@ import App from './App';
import IteratorContainer from './IteratorContainer'; import IteratorContainer from './IteratorContainer';
import type { default as TMagicNode } from './Node'; import type { default as TMagicNode } from './Node';
import Node from './Node'; import Node from './Node';
import { GetNodeOptions } from './type';
interface ConfigOptions { interface ConfigOptions {
config: MPage | MPageFragment; config: MPage | MPageFragment;
app: App; app: App;
@ -65,15 +64,8 @@ class Page extends Node {
if (config.type && this.app.pageFragmentContainerType.has(config.type) && config.pageFragmentId) { if (config.type && this.app.pageFragmentContainerType.has(config.type) && config.pageFragmentId) {
const pageFragment = this.app.dsl?.items?.find((page) => page.id === config.pageFragmentId); const pageFragment = this.app.dsl?.items?.find((page) => page.id === config.pageFragmentId);
if (pageFragment) { if (pageFragment) {
this.app.pageFragments.set( config.items = [pageFragment];
config.id,
new Page({
config: pageFragment,
app: this.app,
}),
);
} }
} }
@ -84,16 +76,13 @@ class Page extends Node {
public getNode<T extends TMagicNode = TMagicNode>( public getNode<T extends TMagicNode = TMagicNode>(
id: Id, id: Id,
{ iteratorContainerId, iteratorIndex, pageFragmentContainerId }: GetNodeOptions = {}, iteratorContainerId?: Id[],
iteratorIndex?: number[],
): T | undefined { ): T | undefined {
if (this.nodes.has(id)) { if (this.nodes.has(id)) {
return this.nodes.get(id) as T; return this.nodes.get(id) as T;
} }
if (pageFragmentContainerId) {
return this.app.pageFragments.get(pageFragmentContainerId)?.getNode(id, { iteratorContainerId, iteratorIndex });
}
if (Array.isArray(iteratorContainerId) && iteratorContainerId.length && Array.isArray(iteratorIndex)) { if (Array.isArray(iteratorContainerId) && iteratorContainerId.length && Array.isArray(iteratorIndex)) {
let iteratorContainer = this.nodes.get(iteratorContainerId[0]) as IteratorContainer; let iteratorContainer = this.nodes.get(iteratorContainerId[0]) as IteratorContainer;

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -43,9 +43,3 @@ export type AfterEventHandler = (args: {
source: TMagicNode | DataSource | undefined; source: TMagicNode | DataSource | undefined;
args: any[]; args: any[];
}) => void; }) => void;
export interface GetNodeOptions {
iteratorContainerId?: Id[];
iteratorIndex?: number[];
pageFragmentContainerId?: Id;
}

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -168,21 +168,15 @@ describe('App', () => {
1, 1,
); );
expect(app.getNode('text', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [0] })?.data.text).toBe( expect(app.getNode('text', ['iterator-container_1'], [0])?.data.text).toBe('1');
'1', expect(app.getNode('text', ['iterator-container_1'], [1])?.data.text).toBe('2');
); expect(app.getNode('text_page_fragment', ['iterator-container_1'], [0])?.data.text).toBe('text_page_fragment');
expect(app.getNode('text', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [1] })?.data.text).toBe(
'2',
);
expect(
app.getNode('text_page_fragment', { iteratorContainerId: ['iterator-container_1'], iteratorIndex: [0] })?.data
.text,
).toBe('text_page_fragment');
const ic1 = app.getNode('iterator-container_11', { const ic1 = app.getNode(
iteratorContainerId: ['iterator-container_1'], 'iterator-container_11',
iteratorIndex: [0], ['iterator-container_1'],
}) as unknown as TMagicIteratorContainer; [0],
) as unknown as TMagicIteratorContainer;
ic1?.setNodes( ic1?.setNodes(
[ [
@ -206,10 +200,11 @@ describe('App', () => {
1, 1,
); );
const ic2 = app.getNode('iterator-container_11', { const ic2 = app.getNode(
iteratorContainerId: ['iterator-container_1'], 'iterator-container_11',
iteratorIndex: [1], ['iterator-container_1'],
}) as unknown as TMagicIteratorContainer; [1],
) as unknown as TMagicIteratorContainer;
ic2?.setNodes( ic2?.setNodes(
[ [
@ -233,30 +228,10 @@ describe('App', () => {
1, 1,
); );
expect( expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [0, 0])?.data.text).toBe('111');
app.getNode('text', { expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [0, 1])?.data.text).toBe('222');
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'], expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [1, 0])?.data.text).toBe('11');
iteratorIndex: [0, 0], expect(app.getNode('text', ['iterator-container_1', 'iterator-container_11'], [1, 1])?.data.text).toBe('22');
})?.data.text,
).toBe('111');
expect(
app.getNode('text', {
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
iteratorIndex: [0, 1],
})?.data.text,
).toBe('222');
expect(
app.getNode('text', {
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
iteratorIndex: [1, 0],
})?.data.text,
).toBe('11');
expect(
app.getNode('text', {
iteratorContainerId: ['iterator-container_1', 'iterator-container_11'],
iteratorIndex: [1, 1],
})?.data.text,
).toBe('22');
ic.resetNodes(); ic.resetNodes();

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.1", "version": "1.5.22",
"name": "@tmagic/data-source", "name": "@tmagic/data-source",
"type": "module", "type": "module",
"main": "dist/tmagic-data-source.umd.cjs", "main": "dist/tmagic-data-source.umd.cjs",
@ -37,7 +37,7 @@
}, },
"peerDependencies": { "peerDependencies": {
"@tmagic/core": "workspace:*", "@tmagic/core": "workspace:*",
"typescript": "catalog:" "typescript": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {
@ -45,7 +45,7 @@
} }
}, },
"devDependencies": { "devDependencies": {
"@types/events": "^3.0.3", "@types/events": "^3.0.0",
"@types/lodash-es": "^4.17.4" "@types/lodash-es": "^4.17.4"
} }
} }

View File

@ -2,7 +2,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,13 +22,7 @@ import EventEmitter from 'events';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type { DataSourceSchema, default as TMagicApp, DisplayCond, Id, MNode } from '@tmagic/core'; import type { DataSourceSchema, default as TMagicApp, DisplayCond, Id, MNode } from '@tmagic/core';
import { import { compiledNode, getDefaultValueFromFields, NODE_CONDS_KEY } from '@tmagic/core';
compiledNode,
getDefaultValueFromFields,
NODE_CONDS_KEY,
NODE_CONDS_RESULT_KEY,
NODE_DISABLE_DATA_SOURCE_KEY,
} from '@tmagic/core';
import { SimpleObservedData } from './observed-data/SimpleObservedData'; import { SimpleObservedData } from './observed-data/SimpleObservedData';
import { DataSource, HttpDataSource } from './data-sources'; import { DataSource, HttpDataSource } from './data-sources';
@ -231,12 +225,7 @@ class DataSourceManager extends EventEmitter {
* @param {boolean} deep items)false * @param {boolean} deep items)false
* @returns {MNode} dsl * @returns {MNode} dsl
*/ */
public compiledNode(n: MNode, sourceId?: Id, deep = false) { public compiledNode({ items, ...node }: MNode, sourceId?: Id, deep = false) {
if (n[NODE_DISABLE_DATA_SOURCE_KEY]) {
return n;
}
const { items, ...node } = n;
const newNode = cloneDeep(node); const newNode = cloneDeep(node);
if (items) { if (items) {
@ -244,9 +233,8 @@ class DataSourceManager extends EventEmitter {
Array.isArray(items) && deep ? items.map((item) => this.compiledNode(item, sourceId, deep)) : items; Array.isArray(items) && deep ? items.map((item) => this.compiledNode(item, sourceId, deep)) : items;
} }
if (node.condResult === false || (typeof node.condResult === 'undefined' && node[NODE_CONDS_RESULT_KEY])) { if (node.condResult === false) return newNode;
return newNode; if (node.visible === false) return newNode;
}
// 编译函数这里作为参数,方便后续支持自定义编译 // 编译函数这里作为参数,方便后续支持自定义编译
return compiledNode( return compiledNode(
@ -262,24 +250,8 @@ class DataSourceManager extends EventEmitter {
* @param {{ [NODE_CONDS_KEY]?: DisplayCond[] }} node * @param {{ [NODE_CONDS_KEY]?: DisplayCond[] }} node
* @returns {boolean} * @returns {boolean}
*/ */
public compliedConds( public compliedConds(node: { [NODE_CONDS_KEY]?: DisplayCond[] }) {
node: { return compliedConditions(node, this.data);
[NODE_CONDS_KEY]?: DisplayCond[];
[NODE_CONDS_RESULT_KEY]?: boolean;
[NODE_DISABLE_DATA_SOURCE_KEY]?: boolean;
},
data = this.data,
) {
if (node[NODE_DISABLE_DATA_SOURCE_KEY]) {
return true;
}
const result = compliedConditions(node, data);
if (!node[NODE_CONDS_RESULT_KEY]) {
return result;
}
return !result;
} }
/** /**
@ -291,11 +263,7 @@ class DataSourceManager extends EventEmitter {
*/ */
public compliedIteratorItemConds( public compliedIteratorItemConds(
itemData: any, itemData: any,
node: { node: { [NODE_CONDS_KEY]?: DisplayCond[] },
[NODE_CONDS_KEY]?: DisplayCond[];
[NODE_CONDS_RESULT_KEY]?: boolean;
[NODE_DISABLE_DATA_SOURCE_KEY]?: boolean;
},
dataSourceField: string[] = [], dataSourceField: string[] = [],
) { ) {
const [dsId, ...keys] = dataSourceField; const [dsId, ...keys] = dataSourceField;
@ -303,7 +271,7 @@ class DataSourceManager extends EventEmitter {
if (!ds) return true; if (!ds) return true;
const ctxData = createIteratorContentData(itemData, ds.id, keys, this.data); const ctxData = createIteratorContentData(itemData, ds.id, keys, this.data);
return this.compliedConds(node, ctxData); return compliedConditions(node, ctxData);
} }
public compliedIteratorItems(itemData: any, nodes: MNode[], dataSourceField: string[] = []): MNode[] { public compliedIteratorItems(itemData: any, nodes: MNode[], dataSourceField: string[] = []): MNode[] {

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,7 +18,7 @@
import { union } from 'lodash-es'; import { union } from 'lodash-es';
import type { default as TMagicApp } from '@tmagic/core'; import type { default as TMagicApp } from '@tmagic/core';
import { getDepNodeIds, getNodes, isPage, isPageFragment, replaceChildNode } from '@tmagic/core'; import { getDepNodeIds, getNodes, isPage } from '@tmagic/core';
import DataSourceManager from './DataSourceManager'; import DataSourceManager from './DataSourceManager';
import type { ChangeEvent, DataSourceManagerData } from './types'; import type { ChangeEvent, DataSourceManagerData } from './types';
@ -52,19 +52,18 @@ export const createDataSourceManager = (app: TMagicApp, useMock?: boolean, initi
// ssr环境下数据应该是提前准备好的放到initialData中不应该发生变化无需监听 // ssr环境下数据应该是提前准备好的放到initialData中不应该发生变化无需监听
// 有initialData不一定是在ssr环境下 // 有initialData不一定是在ssr环境下
if (app.jsEngine === 'nodejs') { if (app.jsEngine !== 'nodejs') {
return dataSourceManager; dataSourceManager.on('change', (sourceId: string, changeEvent: ChangeEvent) => {
} const dep = dsl.dataSourceDeps?.[sourceId] || {};
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
dataSourceManager.on('change', (sourceId: string, changeEvent: ChangeEvent) => { const nodeIds = union([...Object.keys(condDep), ...Object.keys(dep)]);
const dep = dsl.dataSourceDeps?.[sourceId] || {};
const condDep = dsl.dataSourceCondDeps?.[sourceId] || {};
const nodeIds = union([...Object.keys(condDep), ...Object.keys(dep)]); const pages = app.page?.data && app.platform !== 'editor' ? [app.page.data] : dsl.items;
for (const page of dsl.items) { dataSourceManager.emit(
if (app.platform === 'editor' || (isPage(page) && page.id === app.page?.data.id) || isPageFragment(page)) { 'update-data',
const newNodes = getNodes(nodeIds, [page]).map((node) => { getNodes(nodeIds, pages).map((node) => {
if (app.platform !== 'editor') { if (app.platform !== 'editor') {
node.condResult = dataSourceManager.compliedConds(node); node.condResult = dataSourceManager.compliedConds(node);
} }
@ -74,33 +73,19 @@ export const createDataSourceManager = (app: TMagicApp, useMock?: boolean, initi
if (typeof app.page?.setData === 'function') { if (typeof app.page?.setData === 'function') {
if (isPage(newNode)) { if (isPage(newNode)) {
app.page.setData(newNode); app.page.setData(newNode);
} else if (page.id === app.page.data.id && !app.page.instance) { } else {
replaceChildNode(newNode, [app.page.data]); const n = app.page.getNode(node.id);
} n?.setData(newNode);
app.getNode(node.id)?.setData(newNode);
for (const [, pageFragment] of app.pageFragments) {
if (pageFragment.data.id === newNode.id) {
pageFragment.setData(newNode);
} else if (pageFragment.data.id === page.id) {
pageFragment.getNode(newNode.id)?.setData(newNode);
if (!pageFragment.instance) {
replaceChildNode(newNode, [pageFragment.data]);
}
}
} }
} }
return newNode; return newNode;
}); }),
sourceId,
if (newNodes.length) { changeEvent,
dataSourceManager.emit('update-data', newNodes, sourceId, changeEvent, page.id); );
} });
} }
}
});
return dataSourceManager; return dataSourceManager;
}; };

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -23,9 +23,9 @@ export class DeepObservedData extends ObservedData {
update = (data: any, path?: string) => { update = (data: any, path?: string) => {
this.state?.update(path ?? '', data); this.state?.update(path ?? '', data);
}; };
on = (path: string, callback: (newVal: any) => void, options?: { immediate?: boolean }) => { on = (path: string, callback: (newVal: any) => void) => {
// subscribe 会立即执行一次ignoreFirstCall 会忽略第一次执行 // subscribe 会立即执行一次ignoreFirstCall 会忽略第一次执行
const unsubscribe = this.state!.subscribe(path, options?.immediate ? callback : ignoreFirstCall(callback)); const unsubscribe = this.state!.subscribe(path, ignoreFirstCall(callback));
// 把取消监听的函数保存下来,供 off 时调用 // 把取消监听的函数保存下来,供 off 时调用
const pathSubscribers = this.subscribers.get(path) ?? new Map<Function, () => void>(); const pathSubscribers = this.subscribers.get(path) ?? new Map<Function, () => void>();

View File

@ -1,7 +1,7 @@
export abstract class ObservedData { export abstract class ObservedData {
abstract update(data: any, path?: string): void; abstract update(data: any, path?: string): void;
abstract on(path: string, callback: (newVal: any) => void, options?: { immediate?: boolean }): void; abstract on(path: string, callback: (newVal: any) => void): void;
abstract off(path: string, callback: (newVal: any) => void): void; abstract off(path: string, callback: (newVal: any) => void): void;

View File

@ -32,10 +32,7 @@ export class SimpleObservedData extends ObservedData {
this.event.emit('', changeEvent); this.event.emit('', changeEvent);
} }
on(path: string, callback: (newVal: any) => void, options?: { immediate?: boolean }): void { on(path: string, callback: (newVal: any) => void): void {
if (options?.immediate) {
callback(this.getData(path));
}
this.event.on(path, callback); this.event.on(path, callback);
} }

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.1", "version": "1.5.22",
"name": "@tmagic/dep", "name": "@tmagic/dep",
"type": "module", "type": "module",
"main": "dist/tmagic-dep.umd.cjs", "main": "dist/tmagic-dep.umd.cjs",
@ -30,7 +30,7 @@
"peerDependencies": { "peerDependencies": {
"@tmagic/schema": "workspace:*", "@tmagic/schema": "workspace:*",
"@tmagic/utils": "workspace:*", "@tmagic/utils": "workspace:*",
"typescript": "catalog:" "typescript": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {

View File

@ -1,4 +1,3 @@
import { NODE_DISABLE_CODE_BLOCK_KEY, NODE_DISABLE_DATA_SOURCE_KEY } from '@tmagic/schema';
import { isObject } from '@tmagic/utils'; import { isObject } from '@tmagic/utils';
import type Target from './Target'; import type Target from './Target';
@ -190,19 +189,6 @@ export default class Watcher {
} }
public collectItem(node: TargetNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) { public collectItem(node: TargetNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
const dataSourceTargetTypes: string[] = [
DepTargetType.DATA_SOURCE,
DepTargetType.DATA_SOURCE_COND,
DepTargetType.DATA_SOURCE_METHOD,
];
if (node[NODE_DISABLE_DATA_SOURCE_KEY] && dataSourceTargetTypes.includes(target.type)) {
return;
}
if (node[NODE_DISABLE_CODE_BLOCK_KEY] && target.type === DepTargetType.CODE_BLOCK) {
return;
}
const collectTarget = (config: Record<string | number, any>, prop = '') => { const collectTarget = (config: Record<string | number, any>, prop = '') => {
const doCollect = (key: string, value: any) => { const doCollect = (key: string, value: any) => {
const keyIsItems = key === this.childrenProp; const keyIsItems = key === this.childrenProp;

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.1", "version": "1.5.22",
"name": "@tmagic/design", "name": "@tmagic/design",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [
@ -42,8 +42,8 @@
"@popperjs/core": "^2.11.8" "@popperjs/core": "^2.11.8"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "catalog:", "vue": ">=3.5.0",
"typescript": "catalog:" "typescript": "*"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,5 +1,5 @@
{ {
"version": "1.6.1", "version": "1.5.22",
"name": "@tmagic/editor", "name": "@tmagic/editor",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [
@ -65,7 +65,7 @@
"sortablejs": "^1.15.6" "sortablejs": "^1.15.6"
}, },
"devDependencies": { "devDependencies": {
"@types/events": "^3.0.3", "@types/events": "^3.0.0",
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/serialize-javascript": "^5.0.4", "@types/serialize-javascript": "^5.0.4",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
@ -75,8 +75,8 @@
"peerDependencies": { "peerDependencies": {
"@tmagic/core": "workspace:*", "@tmagic/core": "workspace:*",
"monaco-editor": "^0.48.0", "monaco-editor": "^0.48.0",
"typescript": "catalog:", "typescript": "*",
"vue": "catalog:" "vue": ">=3.5.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"typescript": { "typescript": {

View File

@ -97,7 +97,6 @@
:extend-state="extendFormState" :extend-state="extendFormState"
:disabled-show-src="disabledShowSrc" :disabled-show-src="disabledShowSrc"
@mounted="propsPanelMountedHandler" @mounted="propsPanelMountedHandler"
@unmounted="propsPanelUnmountedHandler"
@form-error="propsPanelFormErrorHandler" @form-error="propsPanelFormErrorHandler"
@submit-error="propsPanelSubmitErrorHandler" @submit-error="propsPanelSubmitErrorHandler"
> >
@ -129,7 +128,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { provide } from 'vue'; import { provide, watch } from 'vue';
import type { MApp } from '@tmagic/core'; import type { MApp } from '@tmagic/core';
@ -164,7 +163,6 @@ defineOptions({
const emit = defineEmits<{ const emit = defineEmits<{
'props-panel-mounted': [instance: InstanceType<typeof FormPanel>]; 'props-panel-mounted': [instance: InstanceType<typeof FormPanel>];
'props-panel-unmounted': [];
'update:modelValue': [value: MApp | null]; 'update:modelValue': [value: MApp | null];
'props-form-error': [e: any]; 'props-form-error': [e: any];
'props-submit-error': [e: any]; 'props-submit-error': [e: any];
@ -211,6 +209,22 @@ const stageOptions: StageOptions = {
stageOverlayService.set('stageOptions', stageOptions); stageOverlayService.set('stageOptions', stageOptions);
watch(
() => props.runtimeUrl,
(url) => {
if (!url) {
return;
}
const stage = editorService.get('stage');
if (!stage) {
return;
}
stage.reloadIframe(url);
},
);
provide('services', services); provide('services', services);
provide('codeOptions', props.codeOptions); provide('codeOptions', props.codeOptions);
@ -221,9 +235,6 @@ provide<EventBus>('eventBus', new EventEmitter());
const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => { const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
emit('props-panel-mounted', e); emit('props-panel-mounted', e);
}; };
const propsPanelUnmountedHandler = () => {
emit('props-panel-unmounted');
};
const propsPanelSubmitErrorHandler = (e: any) => { const propsPanelSubmitErrorHandler = (e: any) => {
emit('props-submit-error', e); emit('props-submit-error', e);

View File

@ -3,8 +3,9 @@ import type { FormConfig, FormState } from '@tmagic/form';
import StageCore, { import StageCore, {
CONTAINER_HIGHLIGHT_CLASS_NAME, CONTAINER_HIGHLIGHT_CLASS_NAME,
ContainerHighlightType, ContainerHighlightType,
type CustomizeMoveableOptions, type CustomizeMoveableOptionsCallbackConfig,
type GuidesOptions, type GuidesOptions,
type MoveableOptions,
RenderType, RenderType,
type UpdateDragEl, type UpdateDragEl,
} from '@tmagic/stage'; } from '@tmagic/stage';
@ -55,7 +56,7 @@ export interface EditorProps {
datasourceConfigs?: Record<string, FormConfig>; datasourceConfigs?: Record<string, FormConfig>;
datasourceEventMethodList?: Record<string, { events: EventOption[]; methods: EventOption[] }>; datasourceEventMethodList?: Record<string, { events: EventOption[]; methods: EventOption[] }>;
/** 画布中组件选中框的移动范围 */ /** 画布中组件选中框的移动范围 */
moveableOptions?: CustomizeMoveableOptions; moveableOptions?: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
/** 编辑器初始化时默认选中的组件ID */ /** 编辑器初始化时默认选中的组件ID */
defaultSelected?: Id; defaultSelected?: Id;
/** 拖入画布中容器时识别到容器后给容器根dom加上的class */ /** 拖入画布中容器时识别到容器后给容器根dom加上的class */
@ -126,7 +127,7 @@ export const defaultEditorProps = {
eventMethodList: () => ({}), eventMethodList: () => ({}),
datasourceValues: () => ({}), datasourceValues: () => ({}),
datasourceConfigs: () => ({}), datasourceConfigs: () => ({}),
canSelect: (el: HTMLElement) => Boolean(getIdFromEl()(el) && !el.dataset.tmagicPageFragmentContainerId), canSelect: (el: HTMLElement) => Boolean(getIdFromEl()(el)),
isContainer: (el: HTMLElement) => el.classList.contains('magic-ui-container'), isContainer: (el: HTMLElement) => el.classList.contains('magic-ui-container'),
codeOptions: () => ({}), codeOptions: () => ({}),
customContentMenu: (menus: (MenuButton | MenuComponent)[]) => menus, customContentMenu: (menus: (MenuButton | MenuComponent)[]) => menus,

View File

@ -32,6 +32,7 @@
content="选择数据源" content="选择数据源"
> >
<TMagicButton <TMagicButton
style="margin-left: 5px"
:type="showDataSourceFieldSelect ? 'primary' : 'default'" :type="showDataSourceFieldSelect ? 'primary' : 'default'"
:size="size" :size="size"
@click="showDataSourceFieldSelect = !showDataSourceFieldSelect" @click="showDataSourceFieldSelect = !showDataSourceFieldSelect"

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="m-editor-data-source-fields"> <div class="m-editor-data-source-fields">
<MagicTable :data="model[name]" :columns="fieldColumns" :border="true"></MagicTable> <MagicTable :data="model[name]" :columns="fieldColumns"></MagicTable>
<div class="m-editor-data-source-fields-footer"> <div class="m-editor-data-source-fields-footer">
<TMagicButton size="small" :disabled="disabled" plain @click="newFromJsonHandler()">快速添加</TMagicButton> <TMagicButton size="small" :disabled="disabled" plain @click="newFromJsonHandler()">快速添加</TMagicButton>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="m-editor-data-source-methods"> <div class="m-editor-data-source-methods">
<MagicTable :data="model[name]" :columns="methodColumns" :border="true"></MagicTable> <MagicTable :data="model[name]" :columns="methodColumns"></MagicTable>
<div class="m-editor-data-source-methods-footer"> <div class="m-editor-data-source-methods-footer">
<TMagicButton size="small" type="primary" :disabled="disabled" plain @click="createCodeHandler" <TMagicButton size="small" type="primary" :disabled="disabled" plain @click="createCodeHandler"

View File

@ -1,19 +1,14 @@
<template> <template>
<TMagicCollapse class="m-fields-style-setter" :model-value="collapseValue"> <div class="m-fields-style-setter">
<template v-for="(item, index) in list" :key="index"> <TMagicCollapse :model-value="collapseValue">
<TMagicCollapseItem :name="`${index}`"> <template v-for="(item, index) in list" :key="index">
<template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template> <TMagicCollapseItem :name="`${index}`">
<component <template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template>
v-if="item.component" <component v-if="item.component" :is="item.component" :values="model[name]" @change="change"></component>
:is="item.component" </TMagicCollapseItem>
:values="model[name]" </template>
:size="size" </TMagicCollapse>
:disabled="disabled" </div>
@change="change"
></component>
</TMagicCollapseItem>
</template>
</TMagicCollapse>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

View File

@ -6,21 +6,13 @@
:key="index" :key="index"
link link
:class="model[name] === item.value && 'btn-active'" :class="model[name] === item.value && 'btn-active'"
:disabled="disabled"
@click="changeHandler(item.value)" @click="changeHandler(item.value)"
> >
<div :class="['position-icon', item.class, model[name] === item.value && 'active']"></div> <div :class="['position-icon', item.class, model[name] === item.value && 'active']"></div>
</TMagicButton> </TMagicButton>
</div> </div>
<div class="custom-value"> <div class="custom-value">
<TMagicInput <TMagicInput v-model="model[name]" size="small" placeholder="自定义背景位置" clearable @change="changeHandler">
v-model="model[name]"
placeholder="自定义背景位置"
clearable
:size="size"
:disabled="disabled"
@change="changeHandler"
>
</TMagicInput> </TMagicInput>
</div> </div>
</div> </div>

View File

@ -30,7 +30,7 @@
</div> </div>
</div> </div>
<div class="border-value-container"> <div class="border-value-container">
<MContainer :config="config" :model="model" :size="size" :disabled="disabled" @change="change"></MContainer> <MContainer :config="config" :model="model" @change="change"></MContainer>
</div> </div>
</div> </div>
</template> </template>
@ -67,6 +67,7 @@ const config = computed(() => ({
{ {
name: `border${direction.value}Style`, name: `border${direction.value}Style`,
text: '边框样式', text: '边框样式',
labelWidth: '68px', labelWidth: '68px',
type: 'data-source-field-select', type: 'data-source-field-select',
fieldConfig: { fieldConfig: {
@ -89,8 +90,6 @@ const emit = defineEmits<{
withDefaults( withDefaults(
defineProps<{ defineProps<{
model: FormValue; model: FormValue;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
}>(), }>(),
{}, {},
); );

View File

@ -5,10 +5,9 @@
<span class="next-input"> <span class="next-input">
<input <input
v-model="model[item.name]" v-model="model[item.name]"
placeholder="0"
:title="model[item.name]" :title="model[item.name]"
:disabled="disabled"
@change="change($event, item.name)" @change="change($event, item.name)"
placeholder="0"
/> />
</span> </span>
</div> </div>
@ -61,8 +60,6 @@ const emit = defineEmits<{
withDefaults( withDefaults(
defineProps<{ defineProps<{
disabled?: boolean;
size?: 'large' | 'default' | 'small';
model: FormValue; model: FormValue;
}>(), }>(),
{}, {},

View File

@ -4,10 +4,9 @@
<span class="next-input"> <span class="next-input">
<input <input
v-model="model[item.name]" v-model="model[item.name]"
placeholder="0"
:title="model[item.name]" :title="model[item.name]"
:disabled="disabled"
@change="change($event, item.name)" @change="change($event, item.name)"
placeholder="0"
/> />
</span> </span>
</div> </div>
@ -43,8 +42,6 @@ const emit = defineEmits<{
withDefaults( withDefaults(
defineProps<{ defineProps<{
model: FormValue; model: FormValue;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
}>(), }>(),
{}, {},
); );

View File

@ -1,5 +1,5 @@
<template> <template>
<MContainer :config="config" :model="values" :size="size" :disabled="disabled" @change="change"></MContainer> <MContainer :config="config" :model="values" @change="change"></MContainer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -11,11 +11,7 @@ import type { StyleSchema } from '@tmagic/schema';
import BackgroundPosition from '../components/BackgroundPosition.vue'; import BackgroundPosition from '../components/BackgroundPosition.vue';
import { BackgroundNoRepeat, BackgroundRepeat, BackgroundRepeatX, BackgroundRepeatY } from '../icons/background-repeat'; import { BackgroundNoRepeat, BackgroundRepeat, BackgroundRepeatX, BackgroundRepeatY } from '../icons/background-repeat';
defineProps<{ defineProps<{ values: Partial<StyleSchema> }>();
values: Partial<StyleSchema>;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
}>();
const emit = defineEmits<{ const emit = defineEmits<{
change: [v: StyleSchema, eventData: ContainerChangeEventData]; change: [v: StyleSchema, eventData: ContainerChangeEventData];

View File

@ -1,6 +1,6 @@
<template> <template>
<MContainer :config="config" :model="values" :size="size" :disabled="disabled" @change="change"></MContainer> <MContainer :config="config" :model="values" @change="change"></MContainer>
<Border :model="values" :size="size" :disabled="disabled" @change="change"></Border> <Border :model="values" @change="change"></Border>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -9,11 +9,7 @@ import type { StyleSchema } from '@tmagic/schema';
import Border from '../components/Border.vue'; import Border from '../components/Border.vue';
defineProps<{ defineProps<{ values: Partial<StyleSchema> }>();
values: Partial<StyleSchema>;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
}>();
const emit = defineEmits<{ const emit = defineEmits<{
change: [v: StyleSchema, eventData: ContainerChangeEventData]; change: [v: StyleSchema, eventData: ContainerChangeEventData];

View File

@ -1,5 +1,5 @@
<template> <template>
<MContainer :config="config" :model="values" :size="size" :disabled="disabled" @change="change"></MContainer> <MContainer :config="config" :model="values" @change="change"></MContainer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -10,11 +10,7 @@ import type { StyleSchema } from '@tmagic/schema';
import { AlignCenter, AlignLeft, AlignRight } from '../icons/text-align'; import { AlignCenter, AlignLeft, AlignRight } from '../icons/text-align';
defineProps<{ defineProps<{ values: Partial<StyleSchema> }>();
values: Partial<StyleSchema>;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
}>();
const emit = defineEmits<{ const emit = defineEmits<{
change: [v: StyleSchema, eventData: ContainerChangeEventData]; change: [v: StyleSchema, eventData: ContainerChangeEventData];

View File

@ -1,12 +1,6 @@
<template> <template>
<MContainer :config="config" :model="values" :size="size" :disabled="disabled" @change="change"></MContainer> <MContainer :config="config" :model="values" @change="change"></MContainer>
<Box <Box v-show="!['fixed', 'absolute'].includes(values.position)" :model="values" @change="change"></Box>
v-show="!['fixed', 'absolute'].includes(values.position)"
:model="values"
:size="size"
:disabled="disabled"
@change="change"
></Box>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -34,8 +28,6 @@ import {
defineProps<{ defineProps<{
values: Partial<StyleSchema>; values: Partial<StyleSchema>;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{

View File

@ -1,16 +1,12 @@
<template> <template>
<MContainer :config="config" :model="values" :size="size" :disabled="disabled" @change="change"></MContainer> <MContainer :config="config" :model="values" @change="change"></MContainer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ContainerChangeEventData, MContainer } from '@tmagic/form'; import { ContainerChangeEventData, MContainer } from '@tmagic/form';
import type { StyleSchema } from '@tmagic/schema'; import type { StyleSchema } from '@tmagic/schema';
const props = defineProps<{ const props = defineProps<{ values: Partial<StyleSchema> }>();
values: Partial<StyleSchema>;
disabled?: boolean;
size?: 'large' | 'default' | 'small';
}>();
const emit = defineEmits<{ const emit = defineEmits<{
change: [v: string | StyleSchema, eventData: ContainerChangeEventData]; change: [v: string | StyleSchema, eventData: ContainerChangeEventData];

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -32,12 +32,10 @@ export const useFilter = (
const visible = filterIsMatch(text, node); const visible = filterIsMatch(text, node);
if (visible && parents.length) { if (visible && parents.length) {
parents.forEach((parent) => { parents.forEach((parent) => {
if (text || text.length) { updateStatus(nodeStatusMap.value!, parent.id, {
updateStatus(nodeStatusMap.value!, parent.id, { visible,
visible, expand: true,
expand: true, });
});
}
}); });
} }

View File

@ -45,7 +45,6 @@ export const useStage = (stageOptions: StageOptions) => {
updateDragEl: stageOptions.updateDragEl, updateDragEl: stageOptions.updateDragEl,
guidesOptions: stageOptions.guidesOptions, guidesOptions: stageOptions.guidesOptions,
disabledMultiSelect: stageOptions.disabledMultiSelect, disabledMultiSelect: stageOptions.disabledMultiSelect,
disabledRule: stageOptions.disabledRule,
}); });
watch( watch(

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,4 +1,4 @@
import { nextTick, onBeforeUnmount, reactive, toRaw, watch } from 'vue'; import { onBeforeUnmount, reactive, toRaw, watch } from 'vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import type TMagicCore from '@tmagic/core'; import type TMagicCore from '@tmagic/core';
@ -21,11 +21,9 @@ import {
NODE_CONDS_KEY, NODE_CONDS_KEY,
NodeType, NodeType,
Target, Target,
updateNode,
} from '@tmagic/core'; } from '@tmagic/core';
import { ChangeRecord } from '@tmagic/form'; import { ChangeRecord } from '@tmagic/form';
import StageCore from '@tmagic/stage'; import { getNodes, isPage, isValueIncludeDataSource } from '@tmagic/utils';
import { getDepNodeIds, getNodes, isPage, isValueIncludeDataSource } from '@tmagic/utils';
import PropsPanel from './layouts/PropsPanel.vue'; import PropsPanel from './layouts/PropsPanel.vue';
import { isIncludeDataSource } from './utils/editor'; import { isIncludeDataSource } from './utils/editor';
@ -235,15 +233,14 @@ export const initServiceEvents = (
) => { ) => {
let getTMagicAppPrimise: Promise<TMagicCore | undefined> | null = null; let getTMagicAppPrimise: Promise<TMagicCore | undefined> | null = null;
const getTMagicApp = async (): Promise<TMagicCore | undefined> => { const getTMagicApp = (): Promise<TMagicCore | undefined> => {
const stage = await getStage(); const renderer = editorService.get('stage')?.renderer;
const { renderer } = stage;
if (!renderer) { if (!renderer) {
return void 0; return Promise.resolve(void 0);
} }
if (renderer.runtime) { if (renderer.runtime) {
return renderer.runtime.getApp?.(); return Promise.resolve(renderer.runtime.getApp?.());
} }
if (getTMagicAppPrimise) { if (getTMagicAppPrimise) {
@ -347,86 +344,6 @@ export const initServiceEvents = (
}, },
); );
watch(
() => props.runtimeUrl,
(url) => {
if (!url) {
return;
}
const stage = editorService.get('stage');
if (!stage) {
return;
}
stage.reloadIframe(url);
stage.renderer?.once('runtime-ready', (runtime) => {
runtime.updateRootConfig?.(cloneDeep(toRaw(editorService.get('root')))!);
const page = editorService.get('page');
const node = editorService.get('node');
page?.id && runtime?.updatePageId?.(page.id);
setTimeout(() => {
node && stage?.select(toRaw(node.id));
});
});
},
);
const getStage = (): Promise<StageCore> => {
const stage = editorService.get('stage');
if (stage) {
return Promise.resolve(stage);
}
return new Promise<StageCore>((resolve) => {
const unWatch = watch(
() => editorService.get('stage'),
(stage) => {
if (stage) {
resolve(stage);
nextTick(() => {
unWatch();
});
}
},
);
});
};
const updateStageDsl = async (value: MApp | null) => {
const stage = await getStage();
const runtime = await stage.renderer?.getRuntime();
const app = await getTMagicApp();
if (!app?.dataSourceManager) {
runtime?.updateRootConfig?.(cloneDeep(toRaw(value))!);
}
const page = editorService.get('page');
const node = editorService.get('node');
page?.id && runtime?.updatePageId?.(page.id);
setTimeout(() => {
node && stage?.select(toRaw(node.id));
});
if (value) {
depService.clearIdleTasks();
await (typeof Worker === 'undefined' ? collectIdle(value.items, true) : depService.collectByWorker(value));
const dsl = cloneDeep(toRaw(value));
if (dsl.dataSources && dsl.dataSourceDeps && app?.dataSourceManager) {
for (const node of getNodes(getDepNodeIds(dsl.dataSourceDeps), dsl.items)) {
updateNode(app.dataSourceManager.compiledNode(node), dsl);
}
}
runtime?.updateRootConfig?.(dsl);
}
};
const initDataSourceDepTarget = (ds: DataSourceSchema) => { const initDataSourceDepTarget = (ds: DataSourceSchema) => {
depService.addTarget(createDataSourceTarget(ds, reactive({}))); depService.addTarget(createDataSourceTarget(ds, reactive({})));
depService.addTarget(createDataSourceMethodTarget(ds, reactive({}))); depService.addTarget(createDataSourceMethodTarget(ds, reactive({})));
@ -453,14 +370,18 @@ export const initServiceEvents = (
} }
if (Array.isArray(value.items)) { if (Array.isArray(value.items)) {
updateStageDsl(value); depService.clearIdleTasks();
(typeof Worker === 'undefined' ? collectIdle(value.items, true) : depService.collectByWorker(value)).then(() => {
updateStageNodes(value.items);
});
} else { } else {
depService.clear(); depService.clear();
delete value.dataSourceDeps; delete value.dataSourceDeps;
delete value.dataSourceCondDeps; delete value.dataSourceCondDeps;
} }
(async () => { const handler = async () => {
const nodeId = editorService.get('node')?.id || props.defaultSelected; const nodeId = editorService.get('node')?.id || props.defaultSelected;
let node; let node;
if (nodeId) { if (nodeId) {
@ -479,7 +400,9 @@ export const initServiceEvents = (
if (toRaw(value) !== toRaw(preValue)) { if (toRaw(value) !== toRaw(preValue)) {
emit('update:modelValue', value); emit('update:modelValue', value);
} }
})(); };
handler();
}; };
// 新增节点,收集依赖 // 新增节点,收集依赖

View File

@ -20,7 +20,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'; import { nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
import { FullScreen } from '@element-plus/icons-vue'; import { FullScreen } from '@element-plus/icons-vue';
import { throttle } from 'lodash-es'; import { throttle } from 'lodash-es';
import serialize from 'serialize-javascript'; import serialize from 'serialize-javascript';
@ -62,7 +62,7 @@ const props = withDefaults(
const emit = defineEmits(['initd', 'save']); const emit = defineEmits(['initd', 'save']);
const toString = (v: string | any, language: string): string => { const toString = (v: string | any, language: string): string => {
let value: string; let value = '';
if (typeof v !== 'string') { if (typeof v !== 'string') {
if (language === 'json') { if (language === 'json') {
value = JSON.stringify(v, null, 2); value = JSON.stringify(v, null, 2);
@ -113,40 +113,19 @@ const setEditorValue = (v: string | any, m: string | any) => {
if (props.type === 'diff') { if (props.type === 'diff') {
const originalModel = monaco.editor.createModel(values.value, 'text/javascript'); const originalModel = monaco.editor.createModel(values.value, 'text/javascript');
const modifiedModel = monaco.editor.createModel(toString(m, props.language), 'text/javascript'); const modifiedModel = monaco.editor.createModel(toString(m, props.language), 'text/javascript');
const position = vsDiffEditor?.getPosition();
const result = vsDiffEditor?.setModel({ return vsDiffEditor?.setModel({
original: originalModel, original: originalModel,
modified: modifiedModel, modified: modifiedModel,
}); });
if (position) {
vsDiffEditor?.setPosition(position);
vsDiffEditor?.focus();
}
return result;
} }
//
const position = vsEditor?.getPosition(); return vsEditor?.setValue(values.value);
const result = vsEditor?.setValue(values.value);
//
if (position) {
vsEditor?.setPosition(position);
vsEditor?.focus();
}
return result;
}; };
const getEditorValue = () => const getEditorValue = () =>
(props.type === 'diff' ? vsDiffEditor?.getModifiedEditor().getValue() : vsEditor?.getValue()) || ''; (props.type === 'diff' ? vsDiffEditor?.getModifiedEditor().getValue() : vsEditor?.getValue()) || '';
const handleKeyDown = (e: KeyboardEvent) => {
if (e.keyCode === 83 && (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey)) {
e.preventDefault();
e.stopPropagation();
const newValue = getEditorValue();
values.value = newValue;
emit('save', props.parse ? parseCode(newValue, props.language) : newValue);
}
};
const init = async () => { const init = async () => {
if (!codeEditorEl.value) return; if (!codeEditorEl.value) return;
@ -170,7 +149,16 @@ const init = async () => {
setEditorValue(props.initValues, props.modifiedValues); setEditorValue(props.initValues, props.modifiedValues);
emit('initd', vsEditor); emit('initd', vsEditor);
codeEditorEl.value.addEventListener('keydown', handleKeyDown);
codeEditorEl.value.addEventListener('keydown', (e) => {
if (e.keyCode === 83 && (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey)) {
e.preventDefault();
e.stopPropagation();
const newValue = getEditorValue();
values.value = newValue;
emit('save', props.parse ? parseCode(newValue, props.language) : newValue);
}
});
if (props.type !== 'diff' && props.autoSave) { if (props.type !== 'diff' && props.autoSave) {
vsEditor?.onDidBlurEditorWidget(() => { vsEditor?.onDidBlurEditorWidget(() => {
@ -226,9 +214,7 @@ onBeforeUnmount(() => {
vsEditor = null; vsEditor = null;
vsDiffEditor = null; vsDiffEditor = null;
}); });
onUnmounted(() => {
codeEditorEl.value?.removeEventListener('keydown', handleKeyDown);
});
const fullScreen = ref(false); const fullScreen = ref(false);
const fullScreenHandler = () => { const fullScreenHandler = () => {
fullScreen.value = !fullScreen.value; fullScreen.value = !fullScreen.value;

View File

@ -78,7 +78,6 @@ const resizeObserver = new ResizeObserver(() => {
onMounted(() => { onMounted(() => {
pageBarEl.value && resizeObserver.observe(pageBarEl.value); pageBarEl.value && resizeObserver.observe(pageBarEl.value);
itemsContainerEl.value && resizeObserver.observe(itemsContainerEl.value);
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@ -137,7 +136,7 @@ watch(
let beforeDragList: Id[] = []; let beforeDragList: Id[] = [];
const options = { const options = {
...{ ...{
dataIdAttr: 'data-page-id', // dataIdAttr: 'page-id', //
onStart: async (event: SortableEvent) => { onStart: async (event: SortableEvent) => {
if (typeof props.pageBarSortOptions?.beforeStart === 'function') { if (typeof props.pageBarSortOptions?.beforeStart === 'function') {
await props.pageBarSortOptions.beforeStart(event, sortable); await props.pageBarSortOptions.beforeStart(event, sortable);

View File

@ -11,21 +11,19 @@
:width="160" :width="160"
:destroy-on-close="true" :destroy-on-close="true"
> >
<div class="page-bar-popover-wrapper"> <div>
<div class="page-bar-popover-inner"> <slot name="page-list-popover" :list="list">
<slot name="page-list-popover" :list="list"> <ToolButton
<ToolButton v-for="(item, index) in list"
v-for="(item, index) in list" :data="{
:data="{ type: 'button',
type: 'button', text: item.devconfig?.tabName || item.name || item.id,
text: item.devconfig?.tabName || item.name || item.id, className: item.id === page?.id ? 'active' : '',
className: item.id === page?.id ? 'active' : '', handler: () => switchPage(item.id),
handler: () => switchPage(item.id), }"
}" :key="index"
:key="index" ></ToolButton>
></ToolButton> </slot>
</slot>
</div>
</div> </div>
<template #reference> <template #reference>
<TMagicIcon class="m-editor-page-list-menu-icon"> <TMagicIcon class="m-editor-page-list-menu-icon">

View File

@ -42,7 +42,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, getCurrentInstance, inject, onMounted, onUnmounted, ref, useTemplateRef, watchEffect } from 'vue'; import { computed, getCurrentInstance, inject, onMounted, ref, useTemplateRef, watchEffect } from 'vue';
import { Document as DocumentIcon } from '@element-plus/icons-vue'; import { Document as DocumentIcon } from '@element-plus/icons-vue';
import { TMagicButton, TMagicScrollbar } from '@tmagic/design'; import { TMagicButton, TMagicScrollbar } from '@tmagic/design';
@ -78,7 +78,6 @@ const emit = defineEmits<{
'submit-error': [e: any]; 'submit-error': [e: any];
'form-error': [e: any]; 'form-error': [e: any];
mounted: [internalInstance: any]; mounted: [internalInstance: any];
unmounted: [];
}>(); }>();
const services = useServices(); const services = useServices();
@ -105,10 +104,6 @@ onMounted(() => {
emit('mounted', internalInstance?.proxy); emit('mounted', internalInstance?.proxy);
}); });
onUnmounted(() => {
emit('unmounted');
});
const submit = async (v: FormValue, eventData: ContainerChangeEventData) => { const submit = async (v: FormValue, eventData: ContainerChangeEventData) => {
try { try {
const values = await configFormRef.value?.submitForm(); const values = await configFormRef.value?.submitForm();

View File

@ -13,7 +13,6 @@
@submit-error="errorHandler" @submit-error="errorHandler"
@form-error="errorHandler" @form-error="errorHandler"
@mounted="mountedHandler" @mounted="mountedHandler"
@unmounted="unmountedHandler"
></FormPanel> ></FormPanel>
<Resizer v-if="showStylePanel" @change="widthChange"></Resizer> <Resizer v-if="showStylePanel" @change="widthChange"></Resizer>
@ -90,7 +89,6 @@ const emit = defineEmits<{
'submit-error': [e: any]; 'submit-error': [e: any];
'form-error': [e: any]; 'form-error': [e: any];
mounted: [internalInstance: InstanceType<typeof FormPanel>]; mounted: [internalInstance: InstanceType<typeof FormPanel>];
unmounted: [];
}>(); }>();
const { editorService, uiService, propsService, storageService } = useServices(); const { editorService, uiService, propsService, storageService } = useServices();
@ -167,10 +165,6 @@ const mountedHandler = () => {
} }
}; };
const unmountedHandler = () => {
emit('unmounted');
};
const propsPanelEl = useTemplateRef('propsPanel'); const propsPanelEl = useTemplateRef('propsPanel');
const propsPanelWidth = ref( const propsPanelWidth = ref(
storageService.getItem(PROPS_PANEL_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || 300, storageService.getItem(PROPS_PANEL_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || 300,

View File

@ -43,9 +43,20 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, markRaw, nextTick, onBeforeUnmount, onMounted, useTemplateRef, watch, watchEffect } from 'vue'; import {
computed,
markRaw,
nextTick,
onBeforeUnmount,
onMounted,
toRaw,
useTemplateRef,
watch,
watchEffect,
} from 'vue';
import { cloneDeep } from 'lodash-es';
import type { MContainer } from '@tmagic/core'; import type { MApp, MContainer } from '@tmagic/core';
import StageCore, { getOffset, Runtime } from '@tmagic/stage'; import StageCore, { getOffset, Runtime } from '@tmagic/stage';
import { calcValueByFontsize, getIdFromEl } from '@tmagic/utils'; import { calcValueByFontsize, getIdFromEl } from '@tmagic/utils';
@ -119,6 +130,12 @@ watchEffect(() => {
stage.on('runtime-ready', (rt) => { stage.on('runtime-ready', (rt) => {
runtime = rt; runtime = rt;
// toRawcloneDeep
root.value && runtime?.updateRootConfig?.(cloneDeep(toRaw(root.value)));
page.value?.id && runtime?.updatePageId?.(page.value.id);
setTimeout(() => {
node.value && stage?.select(toRaw(node.value.id));
});
}); });
}); });
@ -167,6 +184,14 @@ watch(page, (page) => {
} }
}); });
const rootChangeHandler = (root: MApp) => {
if (runtime && root) {
runtime.updateRootConfig?.(cloneDeep(toRaw(root)));
}
};
editorService.on('root-change', rootChangeHandler);
const resizeObserver = new ResizeObserver((entries) => { const resizeObserver = new ResizeObserver((entries) => {
for (const { contentRect } of entries) { for (const { contentRect } of entries) {
uiService.set('stageContainerRect', { uiService.set('stageContainerRect', {
@ -185,10 +210,10 @@ onMounted(() => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
stage?.destroy(); stage?.destroy();
stage = null;
resizeObserver.disconnect(); resizeObserver.disconnect();
editorService.set('stage', null); editorService.set('stage', null);
keybindingService.unregisterEl('stage'); keybindingService.unregisterEl('stage');
editorService.off('root-change', rootChangeHandler);
}); });
const parseDSL = getEditorConfig('parseDSL'); const parseDSL = getEditorConfig('parseDSL');

View File

@ -1,19 +1,9 @@
<template> <template>
<div v-if="stageOverlayVisible" class="m-editor-stage-overlay"> <div v-if="stageOverlayVisible" class="m-editor-stage-overlay" @click="closeOverlayHandler">
<TMagicIcon class="m-editor-stage-overlay-close" :size="'30'" @click="closeOverlayHandler" <TMagicIcon class="m-editor-stage-overlay-close" :size="'20'" @click="closeOverlayHandler"
><CloseBold ><CloseBold
/></TMagicIcon> /></TMagicIcon>
<div ref="stageOverlay" class="m-editor-stage-overlay-container" :style="style" @click.stop></div>
<ScrollViewer
class="m-editor-stage"
:width="wrapWidth"
:height="wrapHeight"
:wrap-width="columnWidth.center"
:wrap-height="frameworkRect.height"
:zoom="zoom"
>
<div ref="stageOverlay" class="m-editor-stage-container" :style="style"></div>
</ScrollViewer>
</div> </div>
</template> </template>
@ -23,11 +13,10 @@ import { CloseBold } from '@element-plus/icons-vue';
import { TMagicIcon } from '@tmagic/design'; import { TMagicIcon } from '@tmagic/design';
import ScrollViewer from '@editor/components/ScrollViewer.vue';
import { useServices } from '@editor/hooks/use-services'; import { useServices } from '@editor/hooks/use-services';
import type { StageOptions } from '@editor/type'; import type { StageOptions } from '@editor/type';
const { stageOverlayService, editorService, uiService } = useServices(); const { stageOverlayService, editorService } = useServices();
const stageOptions = inject<StageOptions>('stageOptions'); const stageOptions = inject<StageOptions>('stageOptions');
@ -37,12 +26,10 @@ const stageOverlayVisible = computed(() => stageOverlayService.get('stageOverlay
const wrapWidth = computed(() => stageOverlayService.get('wrapWidth')); const wrapWidth = computed(() => stageOverlayService.get('wrapWidth'));
const wrapHeight = computed(() => stageOverlayService.get('wrapHeight')); const wrapHeight = computed(() => stageOverlayService.get('wrapHeight'));
const stage = computed(() => editorService.get('stage')); const stage = computed(() => editorService.get('stage'));
const zoom = computed(() => uiService.get('zoom'));
const columnWidth = computed(() => uiService.get('columnWidth'));
const frameworkRect = computed(() => uiService.get('frameworkRect'));
const style = computed(() => ({ const style = computed(() => ({
transform: `scale(${zoom.value})`, width: `${wrapWidth.value}px`,
height: `${wrapHeight.value}px`,
})); }));
watch(stage, (stage) => { watch(stage, (stage) => {
@ -56,12 +43,6 @@ watch(stage, (stage) => {
} }
}); });
watch(zoom, (zoom) => {
const stage = stageOverlayService.get('stage');
if (!stage || !zoom) return;
stage.setZoom(zoom);
});
watch(stageOverlayEl, (stageOverlay) => { watch(stageOverlayEl, (stageOverlay) => {
const subStage = stageOverlayService.createStage(stageOptions); const subStage = stageOverlayService.createStage(stageOptions);
stageOverlayService.set('stage', subStage); stageOverlayService.set('stage', subStage);

View File

@ -2,7 +2,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -199,35 +199,24 @@ export default class extends EventEmitter {
* @deprecated 使usePlugin代替 * @deprecated 使usePlugin代替
*/ */
public use(options: Record<string, Function>) { public use(options: Record<string, Function>) {
for (const [methodName, method] of Object.entries(options)) { Object.entries(options).forEach(([methodName, method]: [string, Function]) => {
if (typeof method === 'function') this.middleware[methodName].push(method); if (typeof method === 'function') this.middleware[methodName].push(method);
} });
} }
public usePlugin(options: Record<string, Function>) { public usePlugin(options: Record<string, Function>) {
for (const [methodName, method] of Object.entries(options)) { Object.entries(options).forEach(([methodName, method]: [string, Function]) => {
if (typeof method === 'function' && !this.pluginOptionsList[methodName].includes(method)) { if (typeof method === 'function') this.pluginOptionsList[methodName].push(method);
this.pluginOptionsList[methodName].push(method); });
}
}
}
public removePlugin(options: Record<string, Function>) {
for (const [methodName, method] of Object.entries(options)) {
if (Array.isArray(this.pluginOptionsList[methodName])) {
this.pluginOptionsList[methodName] = this.pluginOptionsList[methodName].filter((item) => item !== method);
}
}
} }
public removeAllPlugins() { public removeAllPlugins() {
for (const key of Object.keys(this.pluginOptionsList)) { Object.keys(this.pluginOptionsList).forEach((key) => {
this.pluginOptionsList[key] = []; this.pluginOptionsList[key] = [];
} });
Object.keys(this.middleware).forEach((key) => {
for (const key of Object.keys(this.middleware)) {
this.middleware[key] = []; this.middleware[key] = [];
} });
} }
private async doTask() { private async doTask() {

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -145,18 +145,6 @@ class Keybinding extends BaseService {
for (const [type = '', eventType = 'keydown'] of when) { for (const [type = '', eventType = 'keydown'] of when) {
const cacheItem: KeyBindingCacheItem = { type, command, keybinding, eventType, bound: false }; const cacheItem: KeyBindingCacheItem = { type, command, keybinding, eventType, bound: false };
if (
this.bindingList.find(
(item) =>
item.command === command &&
item.eventType === eventType &&
item.type === type &&
item.keybinding === keybinding,
)
) {
continue;
}
this.bindingList.push(cacheItem); this.bindingList.push(cacheItem);
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -97,9 +97,9 @@ class StageOverlay extends BaseService {
public createStage(stageOptions: StageOptions = {}) { public createStage(stageOptions: StageOptions = {}) {
return useStage({ return useStage({
...stageOptions, ...stageOptions,
zoom: 1,
runtimeUrl: '', runtimeUrl: '',
autoScrollIntoView: false, autoScrollIntoView: false,
disabledRule: true,
render: async (stage: StageCore) => { render: async (stage: StageCore) => {
this.copyDocumentElement(); this.copyDocumentElement();

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -11,17 +11,6 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
margin-bottom: 10px; margin-bottom: 10px;
} }
> .el-collapse-item__header {
padding: 0 10px;
box-sizing: border-box;
}
}
.el-collapse-item__title {
display: flex;
align-items: center;
gap: 3px;
} }
.el-collapse-item__header, .el-collapse-item__header,

View File

@ -13,8 +13,4 @@
flex: 2; flex: 2;
} }
} }
.tmagic-design-button {
margin-left: 5px;
padding: 5px 8px;
}
} }

View File

@ -92,11 +92,6 @@
padding: 4px 0; padding: 4px 0;
} }
.page-bar-popover-wrapper {
max-height: calc(100vh - $page-bar-height - 20px);
overflow: auto;
}
.menu-item { .menu-item {
cursor: pointer; cursor: pointer;
transition: all 0.2s ease 0s; transition: all 0.2s ease 0s;

View File

@ -72,6 +72,11 @@
align-items: center; align-items: center;
border-bottom: 2px solid $border-color; border-bottom: 2px solid $border-color;
} }
.tmagic-design-form {
padding-right: 10px;
padding-left: 10px;
}
} }
.m-editor-props-panel-src-icon { .m-editor-props-panel-src-icon {

View File

@ -35,15 +35,22 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
background-color: #fff; background-color: #fff;
display: flex;
z-index: 20; z-index: 20;
overflow: auto;
}
.m-editor-stage-overlay-container {
position: relative;
flex-shrink: 0;
margin: auto;
box-shadow: rgba(0, 0, 0, 0.04) 0px 3px 5px;
} }
.m-editor-stage-overlay-close.tmagic-design-icon { .m-editor-stage-overlay-close.tmagic-design-icon {
position: fixed; position: fixed;
right: 20px; right: 20px;
top: 10px; top: 10px;
cursor: pointer;
z-index: 1;
} }
.m-editor-stage-float-button { .m-editor-stage-float-button {
@ -59,10 +66,8 @@
background-color: #ffffff; background-color: #ffffff;
transition: background-color 0.2s; transition: background-color 0.2s;
color: rgba(0, 0, 0, 0.88); color: rgba(0, 0, 0, 0.88);
box-shadow: box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
} }
.m-editor-node-list-menu { .m-editor-node-list-menu {

View File

@ -8,25 +8,12 @@
.tmagic-design-collapse-item { .tmagic-design-collapse-item {
> .el-collapse-item__header { > .el-collapse-item__header {
background-color: #f2f3f7; background-color: #f2f3f7;
height: 26px; height: 36px;
min-height: 26px; padding: 10px;
line-height: 26px;
padding: 0 20px;
box-sizing: border-box;
} }
.el-collapse-item__wrap { .el-collapse-item__wrap {
padding: 10px 20px; padding: 0 10px;
}
.el-collapse-item__title {
display: flex;
align-items: center;
gap: 3px;
}
.el-collapse-item__content {
padding: 0;
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,8 +26,9 @@ import type { FormConfig, TableColumnConfig } from '@tmagic/form';
import type StageCore from '@tmagic/stage'; import type StageCore from '@tmagic/stage';
import type { import type {
ContainerHighlightType, ContainerHighlightType,
CustomizeMoveableOptions, CustomizeMoveableOptionsCallbackConfig,
GuidesOptions, GuidesOptions,
MoveableOptions,
RenderType, RenderType,
UpdateDragEl, UpdateDragEl,
} from '@tmagic/stage'; } from '@tmagic/stage';
@ -156,14 +157,13 @@ export interface StageOptions {
containerHighlightType?: ContainerHighlightType; containerHighlightType?: ContainerHighlightType;
disabledDragStart?: boolean; disabledDragStart?: boolean;
render?: (stage: StageCore) => HTMLDivElement | void | Promise<HTMLDivElement | void>; render?: (stage: StageCore) => HTMLDivElement | void | Promise<HTMLDivElement | void>;
moveableOptions?: CustomizeMoveableOptions; moveableOptions?: MoveableOptions | ((config?: CustomizeMoveableOptionsCallbackConfig) => MoveableOptions);
canSelect?: (el: HTMLElement) => boolean | Promise<boolean>; canSelect?: (el: HTMLElement) => boolean | Promise<boolean>;
isContainer?: (el: HTMLElement) => boolean | Promise<boolean>; isContainer?: (el: HTMLElement) => boolean | Promise<boolean>;
updateDragEl?: UpdateDragEl; updateDragEl?: UpdateDragEl;
renderType?: RenderType; renderType?: RenderType;
guidesOptions?: Partial<GuidesOptions>; guidesOptions?: Partial<GuidesOptions>;
disabledMultiSelect?: boolean; disabledMultiSelect?: boolean;
disabledRule?: boolean;
zoom?: number; zoom?: number;
} }

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -16,12 +17,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { import { NODE_CONDS_KEY } from '@tmagic/core';
NODE_CONDS_KEY,
NODE_CONDS_RESULT_KEY,
NODE_DISABLE_CODE_BLOCK_KEY,
NODE_DISABLE_DATA_SOURCE_KEY,
} from '@tmagic/core';
import { tMagicMessage } from '@tmagic/design'; import { tMagicMessage } from '@tmagic/design';
import type { FormConfig, FormState, TabConfig, TabPaneConfig } from '@tmagic/form'; import type { FormConfig, FormState, TabConfig, TabPaneConfig } from '@tmagic/form';
@ -127,20 +123,6 @@ export const advancedTabConfig: TabPaneConfig = {
title: '高级', title: '高级',
lazy: true, lazy: true,
items: [ items: [
{
name: NODE_DISABLE_CODE_BLOCK_KEY,
text: '禁用代码块',
type: 'switch',
defaultValue: false,
extra: '开启后,配置的代码块将不会被执行',
},
{
name: NODE_DISABLE_DATA_SOURCE_KEY,
text: '禁用数据源',
type: 'switch',
defaultValue: false,
extra: '开启后,组件内配置的数据源相关配置将不会被编译,显隐条件将失效',
},
{ {
name: 'created', name: 'created',
text: 'created', text: 'created',
@ -167,20 +149,8 @@ export const advancedTabConfig: TabPaneConfig = {
export const displayTabConfig: TabPaneConfig = { export const displayTabConfig: TabPaneConfig = {
title: '显示条件', title: '显示条件',
display: (_state: FormState, { model }: any) => model.type !== 'page', display: (_vm: FormState, { model }: any) => model.type !== 'page',
items: [ items: [
{
name: NODE_CONDS_RESULT_KEY,
type: 'select',
text: '条件成立时',
defaultValue: false,
options: [
{ text: '显示', value: false },
{ text: '隐藏', value: true },
],
extra: (_state, { model }) =>
`条件成立时${model[NODE_CONDS_RESULT_KEY] ? '隐藏' : '显示'},不成立时${model[NODE_CONDS_RESULT_KEY] ? '显示' : '隐藏'}<br />同一条件组内的所有条件配置同时成立时表示该条件组成立,任意一个条件组成立时表示条件成立(条件组内为且的关系,条件组间为或的关系)<br />条件为空时表示成立;`,
},
{ {
type: 'display-conds', type: 'display-conds',
name: NODE_CONDS_KEY, name: NODE_CONDS_KEY,

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

View File

@ -1,7 +1,7 @@
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
* Copyright (C) 2025 Tencent. All rights reserved. * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.

Some files were not shown because too many files have changed in this diff Show More