mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-06-09 17:02:01 +00:00
Compare commits
78 Commits
v1.7.14-be
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
614f12adf3 | ||
|
|
bddc6f343c | ||
|
|
be3a900e6a | ||
|
|
bc555ebdc0 | ||
|
|
b7d1cea7c1 | ||
|
|
3bd0eecb42 | ||
|
|
cd19dec790 | ||
|
|
10b70c36bb | ||
|
|
27b2c2c685 | ||
|
|
a8a9cf372d | ||
|
|
6253d7ed23 | ||
|
|
444d4223a9 | ||
|
|
a9e9e65f9c | ||
|
|
42162f2e4a | ||
|
|
7a161cab00 | ||
|
|
1cd69b33fe | ||
|
|
12069e0937 | ||
|
|
1b66ab1b88 | ||
|
|
64d35d5363 | ||
|
|
35fc394199 | ||
|
|
8612311db1 | ||
|
|
818b41f07f | ||
|
|
9b34124805 | ||
|
|
7a61a35664 | ||
|
|
025cca365c | ||
|
|
a3333e2b4e | ||
|
|
cbc4b25072 | ||
|
|
b02aa75ddc | ||
|
|
f0c66427b8 | ||
|
|
c854dfa8bf | ||
|
|
59f4e0edac | ||
|
|
0f8abf7298 | ||
|
|
62a2ee6693 | ||
|
|
0446202ba6 | ||
|
|
285434ef3e | ||
|
|
8dae67769c | ||
|
|
09558fa027 | ||
|
|
4c855ba50b | ||
|
|
e2c065f90d | ||
|
|
a341c7d73e | ||
|
|
de94a75803 | ||
|
|
d01a28ce76 | ||
|
|
6c40425d8c | ||
|
|
b8b0490260 | ||
|
|
2846f9eb2a | ||
|
|
62fc818ae1 | ||
|
|
ff810d09e4 | ||
|
|
b1193b909e | ||
|
|
540a2716d8 | ||
|
|
a1fcb191d2 | ||
|
|
b9a6dd5b84 | ||
|
|
08011efd6d | ||
|
|
fbbd05e291 | ||
|
|
9b65917371 | ||
|
|
3d038513e3 | ||
|
|
eb1c5a3ec1 | ||
|
|
7ff590b1b6 | ||
|
|
7eeb9b544e | ||
|
|
638c3e9f3c | ||
|
|
2d31b3812f | ||
|
|
05e512b1fe | ||
|
|
1e69bc221d | ||
|
|
12ce19fb02 | ||
|
|
aa2ee9fd4b | ||
|
|
f00e84793d | ||
|
|
297e5cebb0 | ||
|
|
5ba2019d0b | ||
|
|
c45df6f6ec | ||
|
|
f1aedc4ce7 | ||
|
|
873a51fc87 | ||
|
|
d16ab9a805 | ||
|
|
df8790042f | ||
|
|
e64d86660d | ||
|
|
0efd23d6ab | ||
|
|
f13f94ca2d | ||
|
|
54a5570419 | ||
|
|
2ad5101471 | ||
|
|
ab6918f43d |
@ -1 +1 @@
|
|||||||
npm test
|
npm run test
|
||||||
|
|||||||
148
CHANGELOG.md
148
CHANGELOG.md
@ -1,3 +1,151 @@
|
|||||||
|
# [1.8.0-beta.4](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.3...v1.8.0-beta.4) (2026-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **editor:** 修复历史对比样式配置显示 ([cd19dec](https://github.com/Tencent/tmagic-editor/commit/cd19dec7907cac5cff775f1cbde24cb3f384e87b))
|
||||||
|
* **editor:** 修复合并历史记录信息展示 ([3bd0eec](https://github.com/Tencent/tmagic-editor/commit/3bd0eecb42d06f06f50cc4736ecc31cc07cc1886))
|
||||||
|
* **editor:** 禁止缺少变更记录的历史回滚 ([10b70c3](https://github.com/Tencent/tmagic-editor/commit/10b70c36bbace6af48bf6fa63f2df0704c6861af))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** 历史记录支持操作来源 ([27b2c2c](https://github.com/Tencent/tmagic-editor/commit/27b2c2c68598264e97a1e1ecc34121829851c85e))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.8.0-beta.3](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.2...v1.8.0-beta.3) (2026-06-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **form:** 对比模式下无 name 字段时不展示差异 ([64d35d5](https://github.com/Tencent/tmagic-editor/commit/64d35d53631698e8d94362765a1621654bd3d1f6))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** 历史记录列表展示时间并优化回滚差异弹窗 ([a9e9e65](https://github.com/Tencent/tmagic-editor/commit/a9e9e65f9c50e47b22de8eab7184cebd87632bc6))
|
||||||
|
* **editor:** 历史记录差异对比弹窗关闭时派发 close 事件 ([42162f2](https://github.com/Tencent/tmagic-editor/commit/42162f2e4ac651ad78ff2f5291e00639a658a1ae))
|
||||||
|
* **editor:** 历史记录面板支持自定义扩展 tab 并开放 Bucket/goto 配置 ([8612311](https://github.com/Tencent/tmagic-editor/commit/8612311db12a22adcc30188ae1ead03729fa6a7a))
|
||||||
|
* **editor:** 对比表单支持自定义 loadConfig 加载逻辑 ([1cd69b3](https://github.com/Tencent/tmagic-editor/commit/1cd69b33fecd75fe8522d9a261e1c03e806ecf69))
|
||||||
|
* **form:** fieldset legend 支持函数动态生成标题 ([35fc394](https://github.com/Tencent/tmagic-editor/commit/35fc39419902e14e2d5bdf98f99802f05a4b5934))
|
||||||
|
* **form:** submitForm 支持返回 changeRecords ([12069e0](https://github.com/Tencent/tmagic-editor/commit/12069e0937589cf9b7684e4bd5ed927e15462513))
|
||||||
|
* **stage:** 非点击画布选中组件时高亮闪烁选中区域 ([444d422](https://github.com/Tencent/tmagic-editor/commit/444d4223a943d763a33b752ffbbfa704591820ca))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.8.0-beta.2](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.1...v1.8.0-beta.2) (2026-05-29)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **editor:** 修复移动到菜单导致节点引用异常的问题 ([d01a28c](https://github.com/Tencent/tmagic-editor/commit/d01a28ce76203765f333548b30b4ec2954e68d4c))
|
||||||
|
* **editor:** 多选时对多个节点的操作合并入同一条历史记录 ([a341c7d](https://github.com/Tencent/tmagic-editor/commit/a341c7d73e78f0727c1adffce767b6806d356beb))
|
||||||
|
* **editor:** 显式标注 CompareForm 的 defineExpose 类型以修复 DTS 构建报错 ([7a61a35](https://github.com/Tencent/tmagic-editor/commit/7a61a356649838531f4f51c45e2e76ab84474107))
|
||||||
|
* 对比模式下关闭 tab-pane 的 lazy,确保差异数能正确统计 ([0f8abf7](https://github.com/Tencent/tmagic-editor/commit/0f8abf729854f5bfc3fbad98153a77e947ead246))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** vs-code 字段对比模式改用 monaco diff 编辑器 ([c854dfa](https://github.com/Tencent/tmagic-editor/commit/c854dfa8bf80bd501534b98c72fa1b2802076cac))
|
||||||
|
* **editor:** 代码块与数据源支持按 id 独立的历史记录 ([e2c065f](https://github.com/Tencent/tmagic-editor/commit/e2c065f90d12d1234edd3620430262857a014ee9))
|
||||||
|
* **editor:** 写操作支持 doNotPushHistory 选项以跳过历史记录 ([4c855ba](https://github.com/Tencent/tmagic-editor/commit/4c855ba50b69a2e0ab73f944171c4d5561d5a06a))
|
||||||
|
* **editor:** 历史记录接入 changeRecords,undo/redo 按 propPath 局部更新 ([09558fa](https://github.com/Tencent/tmagic-editor/commit/09558fa0273af0b7d25b4338a8ea56810b09bb1c))
|
||||||
|
* **editor:** 历史记录面板支持单步回滚(类 git revert) ([b02aa75](https://github.com/Tencent/tmagic-editor/commit/b02aa75ddc2b37a024a8966ddad96cf8d85317bb))
|
||||||
|
* **editor:** 历史记录面板支持差异对比 ([59f4e0e](https://github.com/Tencent/tmagic-editor/commit/59f4e0edac47e986a83a3f9b7862cf92650b7fee))
|
||||||
|
* **editor:** 历史记录面板支持点击跳转与回到初始状态 ([62a2ee6](https://github.com/Tencent/tmagic-editor/commit/62a2ee66931caed51f86bf170c3bce96c7e40dea))
|
||||||
|
* **editor:** 字段对比模式逐项展示差异并补充历史记录面板文档 ([cbc4b25](https://github.com/Tencent/tmagic-editor/commit/cbc4b25072542d98f19707a11b87be0295157216))
|
||||||
|
* **editor:** 数据源与代码块 service 支持 undo/redo ([8dae677](https://github.com/Tencent/tmagic-editor/commit/8dae67769c32dbf65413d47ac56ca46e65eaeecf))
|
||||||
|
* **editor:** 新增 hideSidebar 配置支持隐藏左侧面板 ([a3333e2](https://github.com/Tencent/tmagic-editor/commit/a3333e2b4e0f05b2f83c9dc539466ebd31c04250))
|
||||||
|
* **editor:** 新增历史记录列表面板 ([0446202](https://github.com/Tencent/tmagic-editor/commit/0446202ba6aaf0c99265b367343c7a4d1a8201e9))
|
||||||
|
* form 新增 showDiff prop 支持自定义对比判断 ([f0c6642](https://github.com/Tencent/tmagic-editor/commit/f0c66427b8e011252110a11c90a109f5f58d3101))
|
||||||
|
* **form:** 支持自定义 label slot ([285434e](https://github.com/Tencent/tmagic-editor/commit/285434ef3effd94c51d3ed10198842f6e689046a))
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **dep:** 依赖收集改为单次遍历批量处理多 target ([025cca3](https://github.com/Tencent/tmagic-editor/commit/025cca365c87d755abfc047786ac9a75758019f5))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.8.0-beta.1](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.0...v1.8.0-beta.1) (2026-05-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **core:** app.emit 在节点配置事件时不应短路 super.emit ([2846f9e](https://github.com/Tencent/tmagic-editor/commit/2846f9eb2a8655175a024b16eaba22b522e88603))
|
||||||
|
* **editor:** serializeConfig 只去掉对象 key 的引号,避免破坏字符串 value 内的引号 ([540a271](https://github.com/Tencent/tmagic-editor/commit/540a2716d8e8e7b947ec5aa6352736dff6ee225c))
|
||||||
|
* **editor:** 修复 root 整体替换时图层面板节点状态残留与组件树闪烁问题 ([b9a6dd5](https://github.com/Tencent/tmagic-editor/commit/b9a6dd5b84d6f043eda94dbc1a07b75aea87e6f2))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** 数据源字段选择按钮在对比模式与禁用态下禁止切换 ([ff810d0](https://github.com/Tencent/tmagic-editor/commit/ff810d09e41163834f0ac9fd2057bd9fb9d53c55))
|
||||||
|
* **editor:** 样式设置器 StyleSetter 支持表单对比模式 ([b1193b9](https://github.com/Tencent/tmagic-editor/commit/b1193b909e5e15f78783f72eb21959a52128e973))
|
||||||
|
* **eslint-config:** 禁止匿名 default class/function 导出 ([a1fcb19](https://github.com/Tencent/tmagic-editor/commit/a1fcb191d243b3c7034f31f753757ca4bbd83f5f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [1.8.0-beta.0](https://github.com/Tencent/tmagic-editor/compare/v1.7.14-beta.3...v1.8.0-beta.0) (2026-05-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **editor:** 属性面板 padding 仅作用于最外层表单 ([eb1c5a3](https://github.com/Tencent/tmagic-editor/commit/eb1c5a3ec1c5987b50c700dfb9019aad695e042a))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** 新增 DSL 修改方法的 doNotSwitchPage 选项 ([3d03851](https://github.com/Tencent/tmagic-editor/commit/3d038513e3f0d1c303332fd902c1ef83d7dfe860))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.14-beta.3](https://github.com/Tencent/tmagic-editor/compare/v1.7.14-beta.2...v1.7.14-beta.3) (2026-05-21)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **form:** select 在 model 值变化时补拉 init 选项 ([aa2ee9f](https://github.com/Tencent/tmagic-editor/commit/aa2ee9fd4b08a4a2896eead33dfd1d4ba029c501))
|
||||||
|
* **form:** 修复table-group-list中model属性可能为undefined导致的报错 ([12ce19f](https://github.com/Tencent/tmagic-editor/commit/12ce19fb02af7ac621d220b7e6d0a98859e631de))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** 新增 DSL 修改方法的 doNotSelect 选项 ([05e512b](https://github.com/Tencent/tmagic-editor/commit/05e512b1fe978e26aa3064e7deae9a1aeadcae25))
|
||||||
|
* **form:** 容器组件新增 extendState 属性 ([2d31b38](https://github.com/Tencent/tmagic-editor/commit/2d31b3812f2195f4afc5f16774e155f00cb0ec20))
|
||||||
|
* **form:** 新增 submitForm 命令式提交函数 ([638c3e9](https://github.com/Tencent/tmagic-editor/commit/638c3e9f3cb550da2749fd4814c3bec9d518d081))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.14-beta.2](https://github.com/Tencent/tmagic-editor/compare/v1.7.14-beta.1...v1.7.14-beta.2) (2026-05-18)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **editor:** 修复 CodeEditor setValue 时滚动位置与折叠等视图状态丢失 ([f1aedc4](https://github.com/Tencent/tmagic-editor/commit/f1aedc4ce7f93dd07cb4b7b3c1d39e459b504176))
|
||||||
|
* **form:** 修复 Select 在 value 为空时仍发起 initUrl 请求的问题 ([e64d866](https://github.com/Tencent/tmagic-editor/commit/e64d86660d83769b498de05d221b900a8c9c5b3c))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **editor:** 导航菜单支持菜单项溢出收纳,新增 NavMenuColumn 组件 ([df87900](https://github.com/Tencent/tmagic-editor/commit/df8790042fd1309a6599c2db45bae7c61e1a2600))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [1.7.14-beta.1](https://github.com/Tencent/tmagic-editor/compare/v1.7.14-beta.0...v1.7.14-beta.1) (2026-05-14)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **editor:** 修复 StyleSetter 嵌套场景下 propPath 丢失上下文路径的问题 ([2ad5101](https://github.com/Tencent/tmagic-editor/commit/2ad51014719632b9b6b141f025ed54c3ad20921d))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **form:** 支持 TextConfig handler 返回 Promise,buttonClickHandler 改为 async await ([54a5570](https://github.com/Tencent/tmagic-editor/commit/54a5570419690de6a42704187120e40c622edbf1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [1.7.14-beta.0](https://github.com/Tencent/tmagic-editor/compare/v1.7.13-beta.0...v1.7.14-beta.0) (2026-05-11)
|
## [1.7.14-beta.0](https://github.com/Tencent/tmagic-editor/compare/v1.7.13-beta.0...v1.7.14-beta.0) (2026-05-11)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -103,9 +103,10 @@ export default defineConfig({
|
|||||||
link: '/guide/advanced/data-source.md'
|
link: '/guide/advanced/data-source.md'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: '@tmagic/ui',
|
text: '历史记录面板',
|
||||||
link: '/guide/advanced/tmagic-ui.md',
|
link: '/guide/advanced/history-list.md',
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
text: '@tmagic/form',
|
text: '@tmagic/form',
|
||||||
link: '/guide/advanced/tmagic-form.md',
|
link: '/guide/advanced/tmagic-form.md',
|
||||||
@ -253,6 +254,15 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: '工具函数',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
text: 'submitForm',
|
||||||
|
link: '/api/form/submit-form'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -551,14 +561,8 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
optimizeDeps: {
|
define: {
|
||||||
rolldownOptions: {
|
global: 'globalThis',
|
||||||
transform: {
|
|
||||||
define: {
|
|
||||||
global: 'globalThis',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias:[
|
alias:[
|
||||||
|
|||||||
@ -1,9 +1,20 @@
|
|||||||
# codeBlockService方法
|
# codeBlockService方法
|
||||||
|
|
||||||
|
写入历史栈的方法([setCodeDslById](#setcodedslbyid)、[setCodeDslByIdSync](#setcodedslbyidsync)、[deleteCodeDslByIds](#deletecodedslbyids) 等)的 `options` 支持
|
||||||
|
[historyDescription / historySource](./editorServiceMethods.md#历史记录相关-options),会透传到 `historyService.pushCodeBlock` 的 `historyDescription` / `source` 字段。
|
||||||
|
|
||||||
## setCodeDsl
|
## setCodeDsl
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[CodeBlockDSL](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts#L75)} codeDsl 代码块DSL
|
- {`CodeBlockDSL`} codeDsl 代码块DSL
|
||||||
|
|
||||||
|
::: details 查看 CodeBlockDSL 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockDSL{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockContent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeParam{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -15,7 +26,7 @@
|
|||||||
## getCodeDsl
|
## getCodeDsl
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[CodeBlockDSL](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts#L75) | null}
|
- {`CodeBlockDSL` | null}
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -27,7 +38,7 @@
|
|||||||
- `{string | number}` id 代码块id
|
- `{string | number}` id 代码块id
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[CodeBlockContent](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts#L79) | null}
|
- {`CodeBlockContent` | null}
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -39,7 +50,16 @@
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string | number}` id 代码块id
|
- `{string | number}` id 代码块id
|
||||||
- {Partial<[CodeBlockContent](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts#L79)>} codeConfig 代码块内容配置信息
|
- {Partial<`CodeBlockContent`>} codeConfig 代码块内容配置信息
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- {`ChangeRecord`[]} changeRecords form 端变更记录,用于历史记录的精细化撤销/重做
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
|
||||||
|
::: details 查看 ChangeRecord 类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -52,8 +72,13 @@
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string | number}` id 代码块id
|
- `{string | number}` id 代码块id
|
||||||
- {Partial<[CodeBlockContent](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts#L79)>} codeConfig 代码块内容配置信息
|
- {Partial<`CodeBlockContent`>} codeConfig 代码块内容配置信息
|
||||||
- `{boolean}` force 是否强制写入,默认 `true`;为 `false` 时若同 id 已存在则跳过
|
- `{boolean}` force 是否强制写入,默认 `true`;为 `false` 时若同 id 已存在则跳过
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- {`ChangeRecord`[]} changeRecords form 端变更记录,用于历史记录的精细化撤销/重做
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -62,13 +87,20 @@
|
|||||||
|
|
||||||
同步版本的 [setCodeDslById](#setcodedslbyid),并会触发 `addOrUpdate` 事件
|
同步版本的 [setCodeDslById](#setcodedslbyid),并会触发 `addOrUpdate` 事件
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
写入成功时(`force=false` 且同 id 已存在的跳过场景除外)会自动调用 `historyService.pushCodeBlock`
|
||||||
|
把本次变更入历史栈,参见 [historyService.pushCodeBlock](./historyServiceMethods.md#pushcodeblock)。
|
||||||
|
传入的 `changeRecords` 会一同写进 step,撤销/重做时调用方可据此按 `propPath` 局部回放。
|
||||||
|
传入 `doNotPushHistory: true` 可跳过写入历史栈,常用于批量导入、外部同步等非用户操作场景。
|
||||||
|
:::
|
||||||
|
|
||||||
## getCodeDslByIds
|
## getCodeDslByIds
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string[]}` ids 代码块id数组
|
- `{string[]}` ids 代码块id数组
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[CodeBlockDSL](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts#L75)} 命中的代码块dsl
|
- {`CodeBlockDSL`} 命中的代码块dsl
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -186,6 +218,10 @@
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{(string | number)[]}` codeIds 需要删除的代码块id数组
|
- `{(string | number)[]}` codeIds 需要删除的代码块id数组
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -194,6 +230,157 @@
|
|||||||
|
|
||||||
在dsl数据源中删除指定id的代码块,每删除一个会触发一次 `remove` 事件
|
在dsl数据源中删除指定id的代码块,每删除一个会触发一次 `remove` 事件
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
对每个实际存在并被删除的代码块,会自动调用 `historyService.pushCodeBlock` 入栈一条
|
||||||
|
`newContent=null` 的删除记录;不存在的 id 不会入历史。传入 `doNotPushHistory: true` 也可显式跳过写入历史栈。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## setCodeDslByIdAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [setCodeDslById](#setcodedslbyid)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`Promise<string | null>`} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true` 等)时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [setCodeDslById](#setcodedslbyid) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,可用于精确引用 / 定位该条历史记录。
|
||||||
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { codeBlockService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const historyId = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", {
|
||||||
|
name: "代码块1",
|
||||||
|
content: "() => {}",
|
||||||
|
});
|
||||||
|
console.log(historyId); // 本次变更对应的历史记录 uuid,或 null
|
||||||
|
```
|
||||||
|
|
||||||
|
## setCodeDslByIdSyncAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [setCodeDslByIdSync](#setcodedslbyidsync)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`string | null`} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true`、或 `force=false` 跳过等)时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [setCodeDslByIdSync](#setcodedslbyidsync) 行为完全一致(同步),仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## deleteCodeDslByIdsAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [deleteCodeDslByIds](#deletecodedslbyids)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`Promise<string[]>`} 本次写入的全部历史记录 uuid(按删除顺序);未写入任何历史时返回空数组 `[]`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [deleteCodeDslByIds](#deletecodedslbyids) 行为完全一致。由于一次可删除多个代码块、会产生多条历史记录,因此返回的是 uuid 数组(每条删除记录一个 uuid);不存在的 id 不会入历史,也不会出现在返回数组中。
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { codeBlockService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const historyIds = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(["code_1", "code_2"]);
|
||||||
|
console.log(historyIds); // ['xxxx', 'yyyy'],或 []
|
||||||
|
```
|
||||||
|
|
||||||
|
## revertById
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{string}` uuid 目标历史记录的 uuid(通常由 [setCodeDslByIdAndGetHistoryId](#setcodedslbyidandgethistoryid) 等方法返回)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`Promise<CodeBlockStepValue | null>`} 反向应用后产生的新 step;找不到对应 uuid / 该步未应用时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
通过历史记录 uuid「回滚」某条代码块历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
|
||||||
|
仅无需调用方再传 `codeBlockId` 与 `index`:内部会按 uuid 在全部代码块栈中定位对应步骤后再回滚。
|
||||||
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { codeBlockService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const historyId = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", { name: "代码块1" });
|
||||||
|
if (historyId) {
|
||||||
|
await codeBlockService.revertById(historyId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## undo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 代码块id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{Promise<CodeBlockStepValue | null>}` 撤销的 step;栈不存在或已无可撤销时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
撤销指定代码块的最近一次变更。内部根据 [historyService](./historyServiceMethods.md) 取出 step 后,
|
||||||
|
复用 [setCodeDslByIdSync](#setcodedslbyidsync) / [deleteCodeDslByIds](#deletecodedslbyids) 写回,
|
||||||
|
并自动带上 `doNotPushHistory: true`,确保不会再次入栈。
|
||||||
|
|
||||||
|
写回会触发对应的 `addOrUpdate` / `remove` 事件,编辑器内部据此重新维护
|
||||||
|
`DepTargetType.CODE_BLOCK` 的 dep target,无需调用方额外处理。
|
||||||
|
|
||||||
|
对于带有 `changeRecords` 的更新 step,会按 `propPath` 局部 patch 当前代码块内容;缺省才退化为整内容替换,
|
||||||
|
避免冲掉同代码块上的其它无关变更。
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { codeBlockService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
if (codeBlockService.canUndo("code_1234")) {
|
||||||
|
await codeBlockService.undo("code_1234");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## redo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 代码块id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{Promise<CodeBlockStepValue | null>}` 重做的 step;栈不存在或已无可重做时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
重做指定代码块的下一次变更。其它行为同 [undo](#undo)。
|
||||||
|
|
||||||
|
## canUndo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 代码块id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
当前指定代码块是否可撤销,等价于 `historyService.canUndoCodeBlock(id)`。
|
||||||
|
|
||||||
|
## canRedo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 代码块id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
当前指定代码块是否可重做,等价于 `historyService.canRedoCodeBlock(id)`。
|
||||||
|
|
||||||
## setParamsColConfig
|
## setParamsColConfig
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
@ -227,9 +414,25 @@
|
|||||||
## copyWithRelated
|
## copyWithRelated
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} config 组件节点配置
|
- {`MNode` | `MNode`[]} config 组件节点配置
|
||||||
- `{TargetOptions}` collectorOptions 可选的依赖收集器配置
|
- `{TargetOptions}` collectorOptions 可选的依赖收集器配置
|
||||||
|
|
||||||
|
::: details 查看 MNode 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MIteratorContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MApp{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
|
|
||||||
@ -264,15 +467,11 @@
|
|||||||
|
|
||||||
销毁 codeBlockService,重置状态并移除所有事件监听和插件
|
销毁 codeBlockService,重置状态并移除所有事件监听和插件
|
||||||
|
|
||||||
## use
|
|
||||||
|
|
||||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
|
||||||
|
|
||||||
## usePlugin
|
## usePlugin
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||||
|
|
||||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||||
|
|
||||||
@ -281,3 +480,4 @@
|
|||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
删掉当前设置的所有扩展
|
删掉当前设置的所有扩展
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,13 @@
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
|
|
||||||
- {[ComponentGroup](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/editor/src/type.ts#L355)[]} componentGroupList 组件列表配置
|
- {`ComponentGroup`[]} componentGroupList 组件列表配置
|
||||||
|
|
||||||
|
::: details 查看 ComponentGroup 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#ComponentGroup{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#ComponentItem{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
@ -48,7 +54,7 @@ componentListService.setList([
|
|||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
- {[ComponentGroup](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/editor/src/type.ts#L355)[]} 组件列表配置
|
- {`ComponentGroup`[]} 组件列表配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -102,3 +108,4 @@ import { componentListService } from '@tmagic/editor';
|
|||||||
|
|
||||||
componentListService.destroy();
|
componentListService.destroy();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,19 @@ dataSourceService.set("editable", false);
|
|||||||
- `{string}` type 数据源类型,默认为 'base'
|
- `{string}` type 数据源类型,默认为 'base'
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)} 表单配置
|
- {`FormConfig`} 表单配置
|
||||||
|
|
||||||
|
::: details 查看 FormConfig 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItemConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChildConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DynamicTypeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -80,7 +92,7 @@ console.log(config);
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` type 数据源类型
|
- `{string}` type 数据源类型
|
||||||
- {[FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)} config 表单配置
|
- {`FormConfig`} config 表单配置
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -120,7 +132,23 @@ dataSourceService.setFormConfig("http", [
|
|||||||
- `{string}` type 数据源类型,默认为 'base'
|
- `{string}` type 数据源类型,默认为 'base'
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Partial<[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)>} 数据源默认值
|
- {Partial<`DataSourceSchema`>} 数据源默认值
|
||||||
|
|
||||||
|
::: details 查看 DataSourceSchema 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MockSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockContent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeParam{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#EventConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#JsEngine{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -141,7 +169,7 @@ console.log(defaultValue);
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` type 数据源类型
|
- `{string}` type 数据源类型
|
||||||
- {Partial<[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)>} value 数据源默认值
|
- {Partial<`DataSourceSchema`>} value 数据源默认值
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -170,7 +198,11 @@ dataSourceService.setFormValue("http", {
|
|||||||
- `{string}` type 数据源类型,默认为 'base'
|
- `{string}` type 数据源类型,默认为 'base'
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 事件列表
|
- {`EventOption`[]} 事件列表
|
||||||
|
|
||||||
|
::: details 查看 EventOption 类型定义
|
||||||
|
<<< @/../packages/core/src/utils.ts#EventOption{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -191,7 +223,7 @@ console.log(events);
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` type 数据源类型
|
- `{string}` type 数据源类型
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} value 事件列表
|
- {`EventOption`[]} value 事件列表
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -219,7 +251,7 @@ dataSourceService.setFormEvent("http", [
|
|||||||
- `{string}` type 数据源类型,默认为 'base'
|
- `{string}` type 数据源类型,默认为 'base'
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 方法列表
|
- {`EventOption`[]} 方法列表
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -240,7 +272,7 @@ console.log(methods);
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` type 数据源类型
|
- `{string}` type 数据源类型
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} value 方法列表
|
- {`EventOption`[]} value 方法列表
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -265,15 +297,25 @@ dataSourceService.setFormMethod("http", [
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} config 数据源配置
|
- {`DataSourceSchema`} config 数据源配置
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} 添加后的数据源配置
|
- {`DataSourceSchema`} 添加后的数据源配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
添加一个数据源,如果配置中没有id或id已存在,会自动生成新的id
|
添加一个数据源,如果配置中没有id或id已存在,会自动生成新的id
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
添加成功会自动调用 `historyService.pushDataSource` 入栈一条 `oldSchema=null` 的新增记录,
|
||||||
|
参见 [historyService.pushDataSource](./historyServiceMethods.md#pushdatasource)。
|
||||||
|
传入 `doNotPushHistory: true` 可跳过写入历史栈,常用于批量导入、外部同步等非用户操作场景。
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -294,17 +336,30 @@ console.log(newDs.id); // 自动生成的id
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} config 数据源配置
|
- {`DataSourceSchema`} config 数据源配置
|
||||||
- `{Object}` options 可选配置
|
- `{Object}` options 可选配置
|
||||||
- {[ChangeRecord](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/form/src/schema.ts#L27-L39)[]} changeRecords 变更记录
|
- {`ChangeRecord`[]} changeRecords 变更记录
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
|
||||||
|
::: details 查看 ChangeRecord 类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} 更新后的数据源配置
|
- {`DataSourceSchema`} 更新后的数据源配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
更新数据源
|
更新数据源
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
更新成功会自动调用 `historyService.pushDataSource` 入栈一条 `oldSchema` / `newSchema`
|
||||||
|
均为对应 schema 的更新记录,传入的 `changeRecords` 也会一并写进 step;撤销/重做时调用方可据此按
|
||||||
|
`propPath` 局部回放,缺省才退化为整 schema 替换。传入 `doNotPushHistory: true` 可跳过写入历史栈。
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -326,6 +381,10 @@ console.log(updatedDs);
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` id 数据源id
|
- `{string}` id 数据源id
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见 [editorService 历史记录相关 options](./editorServiceMethods.md#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -334,6 +393,11 @@ console.log(updatedDs);
|
|||||||
|
|
||||||
删除指定id的数据源
|
删除指定id的数据源
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
对实际存在的数据源会自动调用 `historyService.pushDataSource` 入栈一条 `newSchema=null`
|
||||||
|
的删除记录;不存在的 id 不会入历史。传入 `doNotPushHistory: true` 也可显式跳过写入历史栈。
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@ -342,6 +406,78 @@ import { dataSourceService } from "@tmagic/editor";
|
|||||||
dataSourceService.remove("ds_123");
|
dataSourceService.remove("ds_123");
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## addAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [add](#add)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`string` | null} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true` 等)时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [add](#add) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,可用于精确引用 / 定位该条历史记录。
|
||||||
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { dataSourceService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const historyId = dataSourceService.addAndGetHistoryId({
|
||||||
|
type: "http",
|
||||||
|
title: "用户信息",
|
||||||
|
url: "/api/user",
|
||||||
|
});
|
||||||
|
console.log(historyId); // 本次新增对应的历史记录 uuid,或 null
|
||||||
|
```
|
||||||
|
|
||||||
|
## updateAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [update](#update)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`string` | null} 本次写入历史记录的 uuid;未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [update](#update) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## removeAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [remove](#remove)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`string` | null} 本次写入历史记录的 uuid;删除的 id 不存在或未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [remove](#remove) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## revertById
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{string}` uuid 目标历史记录的 uuid(通常由 [addAndGetHistoryId](#addandgethistoryid) 等方法返回)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`DataSourceStepValue` | null} 反向应用后产生的新 step;找不到对应 uuid / 该步未应用时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
通过历史记录 uuid「回滚」某条数据源历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
|
||||||
|
仅无需调用方再传 `dataSourceId` 与 `index`:内部会按 uuid 在全部数据源栈中定位对应步骤后再回滚。
|
||||||
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { dataSourceService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const historyId = dataSourceService.addAndGetHistoryId({ type: "http", title: "用户信息" });
|
||||||
|
if (historyId) {
|
||||||
|
dataSourceService.revertById(historyId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## createId
|
## createId
|
||||||
|
|
||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
@ -370,7 +506,7 @@ console.log(id); // 'ds_xxx-xxx-xxx'
|
|||||||
- `{string}` id 数据源id
|
- `{string}` id 数据源id
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221) | undefined} 数据源配置
|
- {`DataSourceSchema` | undefined} 数据源配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -385,12 +521,94 @@ const ds = dataSourceService.getDataSourceById("ds_123");
|
|||||||
console.log(ds);
|
console.log(ds);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## undo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 数据源id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`DataSourceStepValue` | null} 撤销的 step;栈不存在或已无可撤销时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
撤销指定数据源的最近一次变更。内部根据 [historyService](./historyServiceMethods.md) 取出 step 后,
|
||||||
|
复用 [add](#add) / [update](#update) / [remove](#remove) 写回,并自动带上 `doNotPushHistory: true`,
|
||||||
|
确保不会再次入栈。
|
||||||
|
|
||||||
|
写回会触发对应的 `add` / `update` / `remove` 事件,编辑器内部据此重新维护数据源相关的依赖收集
|
||||||
|
(`DepTargetType.DATA_SOURCE` / `DATA_SOURCE_COND` / `DATA_SOURCE_METHOD`),无需调用方额外处理。
|
||||||
|
|
||||||
|
对于带有 `changeRecords` 的更新 step,会按 `propPath` 局部 patch 当前数据源;缺省才退化为整 schema 替换,
|
||||||
|
避免冲掉同节点上的其它无关变更。
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { dataSourceService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
if (dataSourceService.canUndo("ds_123")) {
|
||||||
|
dataSourceService.undo("ds_123");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## redo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 数据源id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {`DataSourceStepValue` | null} 重做的 step;栈不存在或已无可重做时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
重做指定数据源的下一次变更。其它行为同 [undo](#undo)。
|
||||||
|
|
||||||
|
## canUndo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 数据源id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
当前指定数据源是否可撤销,等价于 `historyService.canUndoDataSource(id)`。
|
||||||
|
|
||||||
|
## canRedo
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id}` id 数据源id
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
当前指定数据源是否可重做,等价于 `historyService.canRedoDataSource(id)`。
|
||||||
|
|
||||||
## copyWithRelated
|
## copyWithRelated
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} config 组件节点配置
|
- {`MNode` | `MNode`[]} config 组件节点配置
|
||||||
- `{TargetOptions}` collectorOptions 可选的收集器配置
|
- `{TargetOptions}` collectorOptions 可选的收集器配置
|
||||||
|
|
||||||
|
::: details 查看 MNode 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MIteratorContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MApp{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
|
|
||||||
@ -505,3 +723,4 @@ import { dataSourceService } from "@tmagic/editor";
|
|||||||
|
|
||||||
dataSourceService.removeAllPlugins();
|
dataSourceService.removeAllPlugins();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -4,37 +4,71 @@
|
|||||||
|
|
||||||
- **详情:** dsl跟节点发生变化,[editorService.set('root', {})](./editorServiceMethods.md#set)后触发
|
- **详情:** dsl跟节点发生变化,[editorService.set('root', {})](./editorServiceMethods.md#set)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (value: [MApp](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts?plain=1#L66-L73), preValue?: [MApp](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts?plain=1#L66-L73)) => void
|
- **事件回调函数:** `(value: MApp, preValue?: MApp) => void`
|
||||||
|
|
||||||
|
::: details 查看 MApp 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MApp{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#NodeType{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockDSL{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceDeps{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## select
|
## select
|
||||||
|
|
||||||
- **详情:** 选中组件,[editorService.select()](./editorServiceMethods.md#select)后触发
|
- **详情:** 选中组件,[editorService.select()](./editorServiceMethods.md#select)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (node: [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)) => void
|
- **事件回调函数:** `(node: MNode) => void`
|
||||||
|
|
||||||
|
::: details 查看 MNode 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## add
|
## add
|
||||||
|
|
||||||
- **详情:** 添加节点后触发,[editorService.add()](./editorServiceMethods.md#add)后触发
|
- **详情:** 添加节点后触发,[editorService.add()](./editorServiceMethods.md#add)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (node: [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]) => void
|
- **事件回调函数:** `(node: MNode[]) => void`
|
||||||
|
|
||||||
## remove
|
## remove
|
||||||
|
|
||||||
- **详情:** 删除节点后触发,[editorService.remove()](./editorServiceMethods.md#remove)后触发
|
- **详情:** 删除节点后触发,[editorService.remove()](./editorServiceMethods.md#remove)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (node: [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]) => void
|
- **事件回调函数:** `(node: MNode[]) => void`
|
||||||
|
|
||||||
## update
|
## update
|
||||||
|
|
||||||
- **详情:** 更新组件后触发,[editorService.update()](./editorServiceMethods.md#update)后触发
|
- **详情:** 更新组件后触发,[editorService.update()](./editorServiceMethods.md#update)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (data: Array<{ newNode: [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210); oldNode: [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210); changeRecords?: [ChangeRecord](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/form/src/schema.ts#L27-L39)[] }>) => void
|
- **事件回调函数:** `(data: Array<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }>) => void`
|
||||||
|
|
||||||
|
::: details 查看 ChangeRecord 类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## move-layer
|
## move-layer
|
||||||
|
|
||||||
- **详情:** 移动节点层级后触发,[editorService.moveLayer()](./editorServiceMethods.md#movelayer)后触发
|
- **详情:** 移动节点层级后触发,[editorService.moveLayer()](./editorServiceMethods.md#movelayer)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (offset: number | [LayerOffset](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts)) => void
|
- **事件回调函数:** `(offset: number | LayerOffset) => void`
|
||||||
|
|
||||||
其中 `LayerOffset` 枚举值为 `'top'` / `'bottom'`
|
其中 `LayerOffset` 枚举值为 `'top'` / `'bottom'`
|
||||||
|
|
||||||
@ -42,10 +76,14 @@
|
|||||||
|
|
||||||
- **详情:** 拖拽节点到指定容器后触发,[editorService.dragTo()](./editorServiceMethods.md#dragto)后触发
|
- **详情:** 拖拽节点到指定容器后触发,[editorService.dragTo()](./editorServiceMethods.md#dragto)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (data: { targetIndex: number; configs: [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]; targetParent: [MContainer](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/schema/src/index.ts#L54-L59) }) => void
|
- **事件回调函数:** `(data: { targetIndex: number; configs: MNode | MNode[]; targetParent: MContainer }) => void`
|
||||||
|
|
||||||
|
::: details 查看 MContainer 类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MContainer{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## history-change
|
## history-change
|
||||||
|
|
||||||
- **详情:** 历史记录改变,[editorService.redo(),editorService.undo()](./editorServiceMethods.md#undo)后触发
|
- **详情:** 历史记录改变,[editorService.redo(),editorService.undo()](./editorServiceMethods.md#undo)后触发
|
||||||
|
|
||||||
- **事件回调函数:** (data: [MPage](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/schema/src/index.ts#L61) | [MPageFragment](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)) => void
|
- **事件回调函数:** `(data: MPage | MPageFragment) => void`
|
||||||
|
|||||||
@ -1,5 +1,48 @@
|
|||||||
# editorService方法
|
# editorService方法
|
||||||
|
|
||||||
|
## 历史记录相关 options
|
||||||
|
|
||||||
|
下列 DSL 操作方法([add](#add)、[remove](#remove)、[update](#update) 等)的 `options` / `data` 参数,以及
|
||||||
|
[codeBlockService](./codeBlockServiceMethods.md) / [dataSourceService](./dataSourceServiceMethods.md)
|
||||||
|
的 `options`,在 `doNotPushHistory` 之外还可传入:
|
||||||
|
|
||||||
|
- `{string}` **historyDescription**:入栈时附带的人类可读描述,用于历史面板展示;不影响 undo/redo 行为,缺省时面板会自动生成描述
|
||||||
|
- `{HistoryOpSource}` **historySource**:操作途径,用于历史面板展示与埋点;不影响 undo/redo 行为,缺省时面板视为「未知」
|
||||||
|
|
||||||
|
编辑器内置交互(画布、树面板、配置面板、右键菜单、快捷键等)会自动传入对应的 `historySource`;
|
||||||
|
业务侧程序化调用时建议显式传入(如 `api`),便于历史面板区分来源。
|
||||||
|
|
||||||
|
## 历史记录 uuid 与 \*AndGetHistoryId
|
||||||
|
|
||||||
|
每条历史记录入栈时都会自动生成一个唯一标识 `uuid`(见 [StepValue](#undo)),可用于精确引用 / 定位某一条历史记录(如埋点、回滚、跨端同步等)。
|
||||||
|
|
||||||
|
DSL 操作方法(`add` / `remove` / `update` 等)默认返回操作结果(节点 / 节点集合 / void),不会返回 `uuid`。若需要拿到本次写入历史记录的 `uuid`,可改用对应的 `*AndGetHistoryId` 方法:它们与原方法行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`(`string`)。当本次操作未写入历史(`doNotPushHistory: true`、无实际变更或提前返回)时返回 `null`。
|
||||||
|
|
||||||
|
| 原方法 | 取 uuid 的方法 | 返回值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| [add](#add) | [addAndGetHistoryId](#addandgethistoryid) | `Promise<string \| null>` |
|
||||||
|
| [remove](#remove) | [removeAndGetHistoryId](#removeandgethistoryid) | `Promise<string \| null>` |
|
||||||
|
| [update](#update) | [updateAndGetHistoryId](#updateandgethistoryid) | `Promise<string \| null>` |
|
||||||
|
| [moveLayer](#movelayer) | [moveLayerAndGetHistoryId](#movelayerandgethistoryid) | `Promise<string \| null>` |
|
||||||
|
| [moveToContainer](#movetocontainer) | [moveToContainerAndGetHistoryId](#movetocontainerandgethistoryid) | `Promise<string \| null>` |
|
||||||
|
| [dragTo](#dragto) | [dragToAndGetHistoryId](#dragtoandgethistoryid) | `Promise<string \| null>` |
|
||||||
|
|
||||||
|
[dataSourceService](./dataSourceServiceMethods.md) / [codeBlockService](./codeBlockServiceMethods.md) 也提供了同名约定的 `*AndGetHistoryId` 方法。
|
||||||
|
|
||||||
|
拿到 `uuid` 后,可在需要时按 uuid「回滚」对应的历史记录(类 git revert 语义,详见[历史记录面板](../../guide/advanced/history-list.md))。相比按 index 回滚,uuid 不会随栈内步骤增删而变化,更适合业务侧持有引用后再回滚:
|
||||||
|
|
||||||
|
- 页面:[editorService.revertPageStepById(uuid)](#revertpagestepbyid)
|
||||||
|
- 数据源:[dataSourceService.revertById(uuid)](./dataSourceServiceMethods.md#revertbyid)
|
||||||
|
- 代码块:[codeBlockService.revertById(uuid)](./codeBlockServiceMethods.md#revertbyid)
|
||||||
|
|
||||||
|
::: details 查看 HistoryOpOptions / DslOpOptions / HistoryOpSource 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpOptions{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#DslOpOptions{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## get
|
## get
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
@ -78,7 +121,19 @@ editorService.set("node", {
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[EditorNodeInfo](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/editor/src/type.ts#L139-L143)}
|
- {`EditorNodeInfo`}
|
||||||
|
|
||||||
|
::: details 查看 EditorNodeInfo 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#EditorNodeInfo{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -103,7 +158,23 @@ console.log(info.page);
|
|||||||
- `{boolean}` raw 是否使用toRaw,默认为true
|
- `{boolean}` raw 是否使用toRaw,默认为true
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} 组件节点配置
|
- {`MNode`} 组件节点配置
|
||||||
|
|
||||||
|
::: details 查看 MNode 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MIteratorContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MApp{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -126,7 +197,7 @@ console.log(node);
|
|||||||
- `{boolean}` raw 是否使用toRaw,默认为true
|
- `{boolean}` raw 是否使用toRaw,默认为true
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} 指点组件的父节点配置
|
- {`MNode`} 指点组件的父节点配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -142,16 +213,43 @@ const parent = editorService.getParentById("text_123");
|
|||||||
console.log(parent);
|
console.log(parent);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## isOnDifferentPage
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- {`MNode`} node 节点配置
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}` true 表示该节点位于非当前页面(即选中该节点将会引起当前页面切换)
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
判断给定节点是否位于非当前页面,通常用于配合 `doNotSwitchPage` 选项判断 DSL 操作是否会引起页面切换
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { editorService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const otherPageNode = editorService.getNodeById("text_456");
|
||||||
|
if (editorService.isOnDifferentPage(otherPageNode)) {
|
||||||
|
console.log("该节点在其它页面,操作会触发页面切换");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## getLayout
|
## getLayout
|
||||||
|
|
||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} parent
|
- {`MNode`} parent
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} node 可选
|
- {`MNode`} node 可选
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[Layout](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/editor/src/type.ts#L297-L302)>} 当前布局模式
|
- {Promise<`Layout`>} 当前布局模式
|
||||||
|
|
||||||
|
::: details 查看 Layout 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#Layout{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -179,10 +277,10 @@ editorService.getLayout(parent).then((layout) => {
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {number | string | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} config 需要选中的节点或节点ID
|
- {number | string | `MNode`} config 需要选中的节点或节点ID
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)>} 当前选中的节点配置
|
- {Promise<`MNode`>} 当前选中的节点配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -229,7 +327,7 @@ editorService.get("stage")?.multiSelect(["text_123", "button_123"]);
|
|||||||
## selectNextNode
|
## selectNextNode
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | null>} 选中后的节点配置
|
- {Promise<`MNode` | null>} 选中后的节点配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -238,7 +336,7 @@ editorService.get("stage")?.multiSelect(["text_123", "button_123"]);
|
|||||||
## selectNextPage
|
## selectNextPage
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)>} 选中后的页面配置
|
- {Promise<`MNode`>} 选中后的页面配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -258,7 +356,7 @@ editorService.get("stage")?.multiSelect(["text_123", "button_123"]);
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {number | string | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} config 需要高亮的节点或节点ID
|
- {number | string | `MNode`} config 需要高亮的节点或节点ID
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -280,12 +378,12 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} node 新组件节点
|
- {`MNode`} node 新组件节点
|
||||||
|
|
||||||
- {[MContainer](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L139)} parent 指定的容器节点
|
- {`MContainer`} parent 指定的容器节点
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)>} 新增的组件
|
- {Promise<`MNode`>} 新增的组件
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -296,12 +394,19 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} node 新组件节点配置或多个节点集合
|
- {`MNode` | `MNode`[]} node 新组件节点配置或多个节点集合
|
||||||
|
|
||||||
- {[MContainer](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L139)} parent 指定的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
|
- {`MContainer`} parent 指定的容器组件节点配置,如果不设置,默认为当前选中的组件的父节点
|
||||||
|
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotSelect 添加后是否不更新当前选中节点(默认 false,添加后会选中新增的节点)
|
||||||
|
- `{boolean}` doNotSwitchPage 添加后是否不切换当前页面(默认 false;新增页面 / 跨页新增时为 true 会跳过会引发页面切换的选中操作)
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]>} 新增的组件或组件集合
|
- {Promise<`MNode` | `MNode`[]>} 新增的组件或组件集合
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -319,7 +424,10 @@ editorService.highlight("text_123");
|
|||||||
|
|
||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} node 要删除的节点
|
- {`MNode`} node 要删除的节点
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotSelect 删除后是否不更新当前选中节点(默认 false)
|
||||||
|
- `{boolean}` doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -328,12 +436,22 @@ editorService.highlight("text_123");
|
|||||||
|
|
||||||
删除指定的组件或者页面
|
删除指定的组件或者页面
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
无论是否传入 `doNotSelect` / `doNotSwitchPage`,当被删除节点在当前选中列表中时,state 都会自动移除该节点的引用;当被删除的正好是当前页面时,state.page 也会同步清空,避免持有已删除节点
|
||||||
|
:::
|
||||||
|
|
||||||
## remove
|
## remove
|
||||||
|
|
||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[])} node 要删除的节点或节点集合
|
- {`MNode` | `MNode`[])} node 要删除的节点或节点集合
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotSelect 删除后是否不更新当前选中节点(默认 false,删除后会选中父节点或首个页面)
|
||||||
|
- `{boolean}` doNotSwitchPage 删除后是否不切换当前页面(默认 false;删除页面 / 页面片段时为 true 会跳过自动切换到首个剩余页面)
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -355,20 +473,27 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} config 新的节点
|
- {`MNode`} config 新的节点
|
||||||
- `{Object}` data 可选配置
|
- `{Object}` data 可选配置
|
||||||
- {[ChangeRecord](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form/src/schema.ts#L8)[]} changeRecords 变更记录
|
- {`ChangeRecord`[]} changeRecords 变更记录
|
||||||
- `{boolean}` selectedAfterUpdate 更新后是否将新节点同步到当前选中节点列表
|
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }>}` 更新前后的节点信息
|
- `{Promise<{ newNode: MNode; oldNode: MNode; changeRecords?: ChangeRecord[] }>}` 更新前后的节点信息
|
||||||
|
|
||||||
|
::: details 查看 ChangeRecord 类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
更新节点
|
更新节点
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
节点中应该要有id,不然不知道要更新哪个节点
|
节点中应该要有id,不然不知道要更新哪个节点
|
||||||
|
|
||||||
|
当被更新节点正好在当前选中列表中时,state 会自动同步到新的节点引用,无需调用方处理
|
||||||
|
|
||||||
|
当被更新节点正好是当前页面时,state.page 也会同步到新的节点引用;更新非当前页面(不同 ID)时不会把编辑器切到该页
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## update
|
## update
|
||||||
@ -376,13 +501,20 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} config 新的节点或节点集合
|
- {`MNode` | `MNode`[]} config 新的节点或节点集合
|
||||||
- `{Object}` data 可选配置
|
- `{Object}` data 可选配置
|
||||||
- {[ChangeRecord](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/form/src/schema.ts#L27-L39)[]} changeRecords 变更记录
|
- {`ChangeRecord`[]} changeRecords 单节点 form 端变更记录(多节点场景下被忽略,使用 `changeRecordList`)
|
||||||
- `{boolean}` selectedAfterUpdate 更新后是否同步到当前选中节点列表
|
- {`ChangeRecord`[][]} changeRecordList 多节点 form 端变更记录列表,按 `config` 数组同序对应每个节点;优先级高于 `changeRecords`
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
|
::: details 查看 ChangeRecord 类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]>} 新的节点或节点集合
|
- {Promise<`MNode` | `MNode`[]>} 新的节点或节点集合
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -396,6 +528,16 @@ editorService.highlight("text_123");
|
|||||||
编辑器内部更新组件都是调用update来实现的,update除了更新操作外,还会记录历史堆,还会更新[代码块](../../guide/advanced/code-block.md)关系链。
|
编辑器内部更新组件都是调用update来实现的,update除了更新操作外,还会记录历史堆,还会更新[代码块](../../guide/advanced/code-block.md)关系链。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
**多节点场景必须使用 `changeRecordList`**:每个节点应保留自己独立的 records,不能把多个节点的
|
||||||
|
records 合并到同一个 `changeRecords` 数组里,否则 `doUpdate` / 依赖收集 / 历史回放都会按错误的
|
||||||
|
`propPath` 处理。
|
||||||
|
|
||||||
|
写入历史时,每个节点的 records 会单独保存到 `updatedItems[i].changeRecords`;撤销/重做时若有
|
||||||
|
records,则仅按 `propPath` 局部更新对应字段,避免整节点替换冲掉同节点上的其它无关变更;缺省
|
||||||
|
才退化为整节点替换(如内部 `sort` / `moveLayer` / 拖动等纯快照场景)。
|
||||||
|
:::
|
||||||
|
|
||||||
## sort
|
## sort
|
||||||
|
|
||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
@ -403,6 +545,11 @@ editorService.highlight("text_123");
|
|||||||
- **参数:**
|
- **参数:**
|
||||||
- `{ string | number }` id1
|
- `{ string | number }` id1
|
||||||
- `{ string | number }` id2
|
- `{ string | number }` id2
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotSelect 排序后是否不更新当前选中节点(默认 false)
|
||||||
|
- `{boolean}` doNotSwitchPage 排序后是否不切换当前页面(排序只发生在同一父节点内,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -418,7 +565,7 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} config 需要复制的节点或节点集合
|
- {`MNode` | `MNode`[]} config 需要复制的节点或节点集合
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -432,7 +579,7 @@ editorService.highlight("text_123");
|
|||||||
## copyWithRelated
|
## copyWithRelated
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} config 需要复制的节点或节点集合
|
- {`MNode` | `MNode`[]} config 需要复制的节点或节点集合
|
||||||
- `{TargetOptions}` collectorOptions 可选的依赖收集器配置
|
- `{TargetOptions}` collectorOptions 可选的依赖收集器配置
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
@ -460,10 +607,22 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[PastePosition](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L152-L163)} position 粘贴的坐标
|
- {`PastePosition`} position 粘贴的坐标
|
||||||
|
|
||||||
|
::: details 查看 PastePosition 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#PastePosition{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
- `{TargetOptions}` collectorOptions 可选的依赖收集器配置
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotSelect 粘贴后是否不更新当前选中节点(默认 false)
|
||||||
|
- `{boolean}` doNotSwitchPage 粘贴后是否不切换当前页面(默认 false;跨页粘贴时为 true 会跳过页面切换)
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]>} 添加后的组件节点配置
|
- {Promise<`MNode` | `MNode`[]>} 添加后的组件节点配置
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -476,10 +635,10 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} config 需要居中的组件
|
- {`MNode`} config 需要居中的组件
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)>}
|
- {Promise<`MNode`>}
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -494,10 +653,16 @@ editorService.highlight("text_123");
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} config 需要居中的组件或者组件集合
|
- {`MNode` | `MNode`[]} config 需要居中的组件或者组件集合
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotSelect 居中后是否不更新当前选中节点(默认 false)
|
||||||
|
- `{boolean}` doNotSwitchPage 居中后是否不切换当前页面(居中只更新节点 style,方法内为空操作;保留以与其它 DSL 操作 API 一致)
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]>}
|
- {Promise<`MNode` | `MNode`[]>}
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -515,6 +680,10 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{number | 'top' | 'bottom'}` offset
|
- `{number | 'top' | 'bottom'}` offset
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -530,11 +699,17 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} config 需要移动的节点
|
- {`MNode`} config 需要移动的节点
|
||||||
- `{string | number}` targetId 容器ID
|
- `{string | number}` targetId 容器ID
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotSelect 移动后是否不更新当前选中节点(默认 false)
|
||||||
|
- `{boolean}` doNotSwitchPage 移动后是否不切换当前页面(默认 false;目标容器位于其它页面时为 true 会跳过自动选中以避免页面切换)
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | undefined>
|
- Promise<`MNode` | undefined>
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -543,9 +718,13 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
## dragTo
|
## dragTo
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} config 需要拖拽的节点或节点集合
|
- {`MNode` | `MNode`[]} config 需要拖拽的节点或节点集合
|
||||||
- {[MContainer](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L139)} targetParent 目标父容器
|
- {`MContainer`} targetParent 目标父容器
|
||||||
- `{number}` targetIndex 目标位置索引
|
- `{number}` targetIndex 目标位置索引
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -554,12 +733,131 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
|
|
||||||
将节点(支持多选)拖拽到目标容器的指定位置,会自动处理跨容器布局切换并记录历史
|
将节点(支持多选)拖拽到目标容器的指定位置,会自动处理跨容器布局切换并记录历史
|
||||||
|
|
||||||
|
## addAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [add](#add)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [add](#add) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,见[历史记录 uuid 与 \*AndGetHistoryId](#历史记录-uuid-与-andgethistoryid)
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { editorService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const historyId = await editorService.addAndGetHistoryId(
|
||||||
|
{ type: "text", text: "hello" },
|
||||||
|
parent,
|
||||||
|
{ historySource: "api" },
|
||||||
|
);
|
||||||
|
console.log(historyId); // 本次新增对应的历史记录 uuid,或 null
|
||||||
|
```
|
||||||
|
|
||||||
|
## removeAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [remove](#remove)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [remove](#remove) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## updateAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [update](#update)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [update](#update) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## moveLayerAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [moveLayer](#movelayer)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [moveLayer](#movelayer) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## moveToContainerAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [moveToContainer](#movetocontainer)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [moveToContainer](#movetocontainer) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## dragToAndGetHistoryId
|
||||||
|
|
||||||
|
- **参数:** 同 [dragTo](#dragto)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
与 [dragTo](#dragto) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
|
## revertPageStepById
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{string}` uuid 目标历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid)(通常由 `*AndGetHistoryId` 方法返回)
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- {Promise<`StepValue` | null>} 反向应用后产生的新 step;找不到对应 uuid / 该步未应用 / 反向失败时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
通过历史记录 uuid「回滚」当前页面的某条历史步骤(类 git revert 语义):不移动游标、不丢弃任何步骤,而是把目标 step 的修改**反向应用为一条全新的步骤**压入栈顶。语义与按 index 回滚一致,仅入参从 index 改为 uuid,更适合业务侧持有引用后再回滚。
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
`opType: 'update'` 的步骤必须携带 `changeRecords` 才支持回滚(否则只能整节点替换,会冲掉后续无关变更);未应用(已被撤销)的步骤无法回滚。
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { editorService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
// 执行操作时拿到本次历史记录 uuid
|
||||||
|
const historyId = await editorService.addAndGetHistoryId({ type: "text", text: "hello" });
|
||||||
|
|
||||||
|
// 之后任意时机按 uuid 回滚该步骤
|
||||||
|
if (historyId) {
|
||||||
|
await editorService.revertPageStepById(historyId);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## undo
|
## undo
|
||||||
|
|
||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[StepValue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L400-L404) | null>}
|
- {Promise<`StepValue` | null>}
|
||||||
|
|
||||||
|
::: details 查看 StepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -570,7 +868,17 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[StepValue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L400-L404) | null>}
|
- {Promise<`StepValue` | null>}
|
||||||
|
|
||||||
|
::: details 查看 StepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -583,6 +891,10 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
- **参数:**
|
- **参数:**
|
||||||
- `{number}` left
|
- `{number}` left
|
||||||
- `{number}` top
|
- `{number}` top
|
||||||
|
- `{Object}` options 可选配置
|
||||||
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -611,45 +923,11 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
|
|
||||||
移除所有事件监听,清空state,移除所有插件
|
移除所有事件监听,清空state,移除所有插件
|
||||||
|
|
||||||
## use
|
|
||||||
|
|
||||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
|
||||||
|
|
||||||
- **示例:**
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { editorService, getAddParent } from "@tmagic/editor";
|
|
||||||
import { ElMessageBox } from "element-plus";
|
|
||||||
|
|
||||||
editorService.use({
|
|
||||||
// 添加是否删除节点确认提示
|
|
||||||
async remove(node, next) {
|
|
||||||
await ElMessageBox.confirm("是否删除", "提示", {
|
|
||||||
confirmButtonText: "确定",
|
|
||||||
cancelButtonText: "取消",
|
|
||||||
type: "warning",
|
|
||||||
});
|
|
||||||
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
|
|
||||||
add(node, next) {
|
|
||||||
// text组件只能添加到container中
|
|
||||||
const parentNode = getAddParent(node);
|
|
||||||
if (node.type === "text" && parentNode?.type !== "container") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## usePlugin
|
## usePlugin
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||||
|
|
||||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||||
|
|
||||||
@ -673,3 +951,4 @@ editorService.usePlugin({
|
|||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
删掉当前设置的所有扩展
|
删掉当前设置的所有扩展
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,9 @@
|
|||||||
|
|
||||||
- **详情:** 编辑器右侧组件属性配置加载完毕后触发
|
- **详情:** 编辑器右侧组件属性配置加载完毕后触发
|
||||||
|
|
||||||
- **事件回调函数:** (instance: InstanceType<typeof [FormPanel](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/layouts/props-panel/FormPanel.vue)>) => void
|
- **事件回调函数:** `(instance: InstanceType<typeof FormPanel>) => void`
|
||||||
|
|
||||||
|
> [`FormPanel.vue`](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/layouts/props-panel/FormPanel.vue) 是属性面板组件实例
|
||||||
|
|
||||||
## props-panel-unmounted
|
## props-panel-unmounted
|
||||||
|
|
||||||
@ -16,7 +18,25 @@
|
|||||||
|
|
||||||
- **详情:** 当 [modelValue](./props.md#modelvalue-v-model)(DSL) 变化时触发,配合 `v-model` 使用
|
- **详情:** 当 [modelValue](./props.md#modelvalue-v-model)(DSL) 变化时触发,配合 `v-model` 使用
|
||||||
|
|
||||||
- **事件回调函数:** (value: [MApp](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts?plain=1#L66-L73) | null) => void
|
- **事件回调函数:** `(value: MApp | null) => void`
|
||||||
|
|
||||||
|
::: details 查看 MApp 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MApp{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#NodeType{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockDSL{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceDeps{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## props-form-error
|
## props-form-error
|
||||||
|
|
||||||
@ -38,7 +58,13 @@
|
|||||||
|
|
||||||
默认行为(切换可展开节点的展开/收起状态)会先于该事件执行;可通过 [`beforeLayerNodeDblclick`](./props.md#beforelayernodedblclick) 钩子拦截,返回 `false` 时该事件不会被触发
|
默认行为(切换可展开节点的展开/收起状态)会先于该事件执行;可通过 [`beforeLayerNodeDblclick`](./props.md#beforelayernodedblclick) 钩子拦截,返回 `false` 时该事件不会被触发
|
||||||
|
|
||||||
- **事件回调函数:** (event: MouseEvent, data: [TreeNodeData](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/type.ts)) => void
|
- **事件回调函数:** `(event: MouseEvent, data: TreeNodeData) => void`
|
||||||
|
|
||||||
|
::: details 查看 TreeNodeData 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#TreeNodeData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,11 @@
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
|
|
||||||
- {Record<string, [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]>} events 事件配置对象
|
- {Record<string, `EventOption`[]>} events 事件配置对象
|
||||||
|
|
||||||
|
::: details 查看 EventOption 类型定义
|
||||||
|
<<< @/../packages/core/src/utils.ts#EventOption{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
@ -35,7 +39,7 @@ eventsService.setEvents({
|
|||||||
- **参数:**
|
- **参数:**
|
||||||
|
|
||||||
- `{string}` type 组件类型
|
- `{string}` type 组件类型
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} events 事件列表
|
- {`EventOption`[]} events 事件列表
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
@ -64,7 +68,7 @@ eventsService.setEvent('button', [
|
|||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 事件列表
|
- {`EventOption`[]} 事件列表
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -83,7 +87,7 @@ console.log(events); // [{ label: '点击', value: 'click' }, ...]
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
|
|
||||||
- {Record<string, [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]>} methods 方法配置对象
|
- {Record<string, `EventOption`[]>} methods 方法配置对象
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
@ -115,7 +119,7 @@ eventsService.setMethods({
|
|||||||
- **参数:**
|
- **参数:**
|
||||||
|
|
||||||
- `{string}` type 组件类型
|
- `{string}` type 组件类型
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} methods 方法列表
|
- {`EventOption`[]} methods 方法列表
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
@ -146,7 +150,7 @@ eventsService.setMethod('video', [
|
|||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
|
|
||||||
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 方法列表
|
- {`EventOption`[]} 方法列表
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -200,3 +204,4 @@ import { eventsService } from '@tmagic/editor';
|
|||||||
|
|
||||||
eventsService.destroy();
|
eventsService.destroy();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -4,14 +4,105 @@
|
|||||||
|
|
||||||
- **详情:** 页面切换
|
- **详情:** 页面切换
|
||||||
|
|
||||||
- **事件回调函数:** (undoRedo: [UndoRedo](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/utils/undo-redo.ts)) => void
|
- **事件回调函数:** `(undoRedo: UndoRedo) => void`
|
||||||
|
|
||||||
|
::: details 查看 UndoRedo 类定义
|
||||||
|
<<< @/../packages/editor/src/utils/undo-redo.ts#UndoRedo{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## change
|
## change
|
||||||
|
|
||||||
- **详情:** 历史记录发生变化
|
- **详情:** 历史记录发生变化
|
||||||
|
|
||||||
- **事件回调函数:** (state: [StepValue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L400-L404) | null) => void
|
- **事件回调函数:** `(state: StepValue | null) => void`
|
||||||
|
|
||||||
|
::: details 查看 StepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
当游标处于历史栈边界(已经无法继续撤销或重做)时,`UndoRedo.undo()` / `redo()` 返回 `null`,对应 `change` 回调收到的 `state` 为 `null`
|
当游标处于历史栈边界(已经无法继续撤销或重做)时,`UndoRedo.undo()` / `redo()` 返回 `null`,对应 `change` 回调收到的 `state` 为 `null`
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## code-block-history-change
|
||||||
|
|
||||||
|
- **详情:** 代码块历史记录发生变化(`pushCodeBlock` / `undoCodeBlock` / `redoCodeBlock` 成功时触发)
|
||||||
|
|
||||||
|
- **事件回调函数:** `(codeBlockId: Id, step: CodeBlockStepValue) => void`
|
||||||
|
|
||||||
|
::: details 查看 CodeBlockStepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#CodeBlockStepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockContent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
- 新增触发的 step 中 `oldContent` 为 `null`
|
||||||
|
- 删除触发的 step 中 `newContent` 为 `null`
|
||||||
|
- `undo` / `redo` 返回 `null`(边界状态)时不会触发该事件
|
||||||
|
:::
|
||||||
|
|
||||||
|
## data-source-history-change
|
||||||
|
|
||||||
|
- **详情:** 数据源历史记录发生变化(`pushDataSource` / `undoDataSource` / `redoDataSource` 成功时触发)
|
||||||
|
|
||||||
|
- **事件回调函数:** `(dataSourceId: Id, step: DataSourceStepValue) => void`
|
||||||
|
|
||||||
|
::: details 查看 DataSourceStepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#DataSourceStepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip
|
||||||
|
- 新增触发的 step 中 `oldSchema` 为 `null`
|
||||||
|
- 删除触发的 step 中 `newSchema` 为 `null`
|
||||||
|
- `undo` / `redo` 返回 `null`(边界状态)时不会触发该事件
|
||||||
|
:::
|
||||||
|
|
||||||
|
## mark-saved
|
||||||
|
|
||||||
|
- **详情:** 调用 `markSaved` / `markPageSaved` / `markCodeBlockSaved` / `markDataSourceSaved` 标记「已保存」记录时触发
|
||||||
|
|
||||||
|
- **事件回调函数:** `(payload: { kind: 'all' | 'page' | 'code-block' | 'data-source'; id?: Id }) => void`
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
- `markSaved` 触发时 `kind` 为 `all`,无 `id`
|
||||||
|
- 细粒度方法触发时 `kind` 对应类别,`id` 为目标页面 / 代码块 / 数据源 id
|
||||||
|
:::
|
||||||
|
|
||||||
|
## save-to-indexed-db
|
||||||
|
|
||||||
|
- **详情:** `saveToIndexedDB` 把历史记录写入本地 IndexedDB 成功时触发
|
||||||
|
|
||||||
|
- **事件回调函数:** `(snapshot: PersistedHistoryState) => void`
|
||||||
|
|
||||||
|
::: details 查看 PersistedHistoryState 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#PersistedHistoryState{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/utils/undo-redo.ts#SerializedUndoRedo{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
## restore-from-indexed-db
|
||||||
|
|
||||||
|
- **详情:** `restoreFromIndexedDB` 从本地 IndexedDB 读取并重建历史记录成功时触发(找不到记录时不触发)
|
||||||
|
|
||||||
|
- **事件回调函数:** `(snapshot: PersistedHistoryState) => void`
|
||||||
|
|
||||||
|
::: details 查看 PersistedHistoryState 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#PersistedHistoryState{ts}
|
||||||
|
:::
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
重置记录
|
重置全部历史记录(包括页面节点栈、代码块栈、数据源栈),并重置当前页面 id / canRedo / canUndo
|
||||||
|
|
||||||
## resetPage
|
## resetPage
|
||||||
|
|
||||||
@ -16,12 +16,18 @@
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
重置历史记录全部内部状态(清空 pageId、pageSteps、canRedo、canUndo)
|
重置历史记录全部内部状态(清空 pageId、pageSteps、canRedo、canUndo、codeBlockState、dataSourceState)
|
||||||
|
|
||||||
## changePage
|
## changePage
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MPage](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L157) | [MPageFragment](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L162)} page
|
- `{MPage | MPageFragment} page`
|
||||||
|
|
||||||
|
::: details 查看 MPage / MPageFragment 类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -30,35 +36,349 @@
|
|||||||
## push
|
## push
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[StepValue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L400-L404)} state
|
- `{StepValue} state`
|
||||||
|
|
||||||
|
::: details 查看 StepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[StepValue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L400-L404) | null}
|
- `{StepValue | null}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
添加一条历史记录
|
添加一条历史记录
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
`opType: 'update'` 的每个 `updatedItems[i]` 上可携带 `changeRecords`,用于撤销 / 重做时仅按
|
||||||
|
`propPath` 局部更新对应字段,避免整节点替换冲掉同节点上的其它无关变更;不带
|
||||||
|
`changeRecords` 时退化为整节点替换(如 `sort` / `moveLayer` / 拖动等纯快照场景)。
|
||||||
|
|
||||||
|
`StepValue` 上的 `historyDescription` / `source` 仅用于历史面板展示与埋点,不影响 undo/redo 行为。
|
||||||
|
|
||||||
|
入栈时会为每条记录自动生成唯一标识 `uuid`(调用方未指定时),可用于精确引用 / 定位某一条历史记录。
|
||||||
|
若需要在执行 DSL 操作后拿到本次写入记录的 `uuid`,可使用 editorService / dataSourceService /
|
||||||
|
codeBlockService 提供的 `*AndGetHistoryId` 方法,参见
|
||||||
|
[editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
`pushCodeBlock` / `pushDataSource` 同样会自动写入 `uuid`。
|
||||||
|
:::
|
||||||
|
|
||||||
## undo
|
## undo
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[StepValue](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/editor/src/type.ts#L554-L573) | null}
|
- `{StepValue | null}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
撤销当前操作
|
撤销当前操作。`opType: 'update'` 时,若 `updatedItems[i].changeRecords` 存在,会按
|
||||||
|
`propPath` 从 `oldNode` 取值做局部回滚;否则用 `oldNode` 整节点替换。
|
||||||
|
|
||||||
## redo
|
## redo
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[StepValue](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/editor/src/type.ts#L554-L573) | null}
|
- `{StepValue | null}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
恢复到下一步
|
恢复到下一步。`opType: 'update'` 时,若 `updatedItems[i].changeRecords` 存在,会按
|
||||||
|
`propPath` 从 `newNode` 取值做局部重做;否则用 `newNode` 整节点替换。
|
||||||
|
|
||||||
|
## pushCodeBlock
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId` 代码块 id
|
||||||
|
- `{Object} payload`
|
||||||
|
- `{CodeBlockContent | null} oldContent` 变更前的代码块内容;新增时为 `null`
|
||||||
|
- `{CodeBlockContent | null} newContent` 变更后的代码块内容;删除时为 `null`
|
||||||
|
- `{ChangeRecord[]} changeRecords` 可选;form 端 propPath/value 变更列表,撤销/重做时若有则按 propPath 局部更新;缺省(或空数组)才退化为整内容替换
|
||||||
|
- `{string}` historyDescription 可选;人类可读描述,用于历史面板展示;不影响 undo/redo 行为
|
||||||
|
- `{HistoryOpSource}` source 可选;操作途径,用于历史面板展示与埋点;不影响 undo/redo 行为
|
||||||
|
|
||||||
|
::: details 查看 CodeBlockStepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#CodeBlockStepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockContent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{CodeBlockStepValue | null}` 入栈失败(未传 id)时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
推入一条代码块变更记录。与页面 / 节点完全无关,按 `codeBlockId` 维度独立一份 `UndoRedo` 栈,
|
||||||
|
栈实例存放在 `historyService.state.codeBlockState[codeBlockId]`。
|
||||||
|
|
||||||
|
入栈成功后会触发 `code-block-history-change` 事件。
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
`codeBlockService.setCodeDslByIdSync` 与 `codeBlockService.deleteCodeDslByIds` 内部已经
|
||||||
|
自动调用本方法,业务代码通常无需手动调用。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## undoCodeBlock
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{CodeBlockStepValue | null}` 栈不存在或已无可撤销记录时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
撤销指定代码块的最近一次变更。成功时会触发 `code-block-history-change` 事件。
|
||||||
|
拿到 step 后由调用方根据 `step.oldContent` 写回 `codeBlockService`(本方法不会自动回放)。
|
||||||
|
|
||||||
|
## redoCodeBlock
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{CodeBlockStepValue | null}` 栈不存在或已无可重做记录时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
重做指定代码块的下一次变更。成功时会触发 `code-block-history-change` 事件。
|
||||||
|
|
||||||
|
## canUndoCodeBlock
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
指定代码块当前是否可撤销。栈不存在时返回 `false`。
|
||||||
|
|
||||||
|
## canRedoCodeBlock
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
指定代码块当前是否可重做。栈不存在时返回 `false`。
|
||||||
|
|
||||||
|
## pushDataSource
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId` 数据源 id
|
||||||
|
- `{Object} payload`
|
||||||
|
- `{DataSourceSchema | null} oldSchema` 变更前的数据源 schema;新增时为 `null`
|
||||||
|
- `{DataSourceSchema | null} newSchema` 变更后的数据源 schema;删除时为 `null`
|
||||||
|
- `{ChangeRecord[]} changeRecords` 可选;form 端 propPath/value 变更列表,撤销/重做时若有则按 propPath 局部更新;缺省(或空数组)才退化为整 schema 替换
|
||||||
|
- `{string}` historyDescription 可选;人类可读描述,用于历史面板展示;不影响 undo/redo 行为
|
||||||
|
- `{HistoryOpSource}` source 可选;操作途径,用于历史面板展示与埋点;不影响 undo/redo 行为
|
||||||
|
|
||||||
|
::: details 查看 DataSourceStepValue 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#DataSourceStepValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{DataSourceStepValue | null}` 入栈失败(未传 id)时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
推入一条数据源变更记录。与页面 / 节点完全无关,按 `dataSourceId` 维度独立一份 `UndoRedo` 栈,
|
||||||
|
栈实例存放在 `historyService.state.dataSourceState[dataSourceId]`。
|
||||||
|
|
||||||
|
入栈成功后会触发 `data-source-history-change` 事件。
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
`dataSourceService.add` / `update` / `remove` 内部已经自动调用本方法,业务代码通常无需手动调用。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## undoDataSource
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{DataSourceStepValue | null}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
撤销指定数据源的最近一次变更。成功时会触发 `data-source-history-change` 事件。
|
||||||
|
拿到 step 后由调用方根据 `step.oldSchema` 写回 `dataSourceService`(本方法不会自动回放)。
|
||||||
|
|
||||||
|
## redoDataSource
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{DataSourceStepValue | null}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
重做指定数据源的下一次变更。成功时会触发 `data-source-history-change` 事件。
|
||||||
|
|
||||||
|
## canUndoDataSource
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
指定数据源当前是否可撤销。栈不存在时返回 `false`。
|
||||||
|
|
||||||
|
## canRedoDataSource
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId`
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{boolean}`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
指定数据源当前是否可重做。栈不存在时返回 `false`。
|
||||||
|
|
||||||
|
## markSaved
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
标记「整份 DSL 已保存」:把页面 / 代码块 / 数据源所有栈当前游标所在的记录都标记为已保存(`saved = true`)。
|
||||||
|
|
||||||
|
同一栈内任意时刻最多保留一条已保存记录(标记前会清除该栈内全部旧标记);某个栈处于「全部已撤销」(cursor 为 0)时不会留下已保存记录,从 IndexedDB 恢复时其游标会回到 0。
|
||||||
|
|
||||||
|
通常在 DSL 整体落库(保存到后端 / 本地)成功后调用,配合 [`restoreFromIndexedDB`](#restorefromindexeddb) 把游标恢复到此处。仅保存了其中一类时请改用更细粒度的 `markPageSaved` / `markCodeBlockSaved` / `markDataSourceSaved`。
|
||||||
|
|
||||||
|
调用后会触发 `mark-saved` 事件(`{ kind: 'all' }`)。
|
||||||
|
|
||||||
|
## markPageSaved
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} pageId` 可选;缺省为当前活动页
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
标记指定页面(缺省当前活动页)历史栈的当前记录为已保存,仅影响该页面自己的栈。触发 `mark-saved` 事件(`{ kind: 'page', id }`)。
|
||||||
|
|
||||||
|
## markCodeBlockSaved
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
标记指定代码块历史栈的当前记录为已保存,仅影响该代码块自己的栈。触发 `mark-saved` 事件(`{ kind: 'code-block', id }`)。
|
||||||
|
|
||||||
|
## markDataSourceSaved
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
标记指定数据源历史栈的当前记录为已保存,仅影响该数据源自己的栈。触发 `mark-saved` 事件(`{ kind: 'data-source', id }`)。
|
||||||
|
|
||||||
|
## clearPage
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} pageId` 可选;缺省为当前活动页
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
清空指定页面(缺省当前活动页)的历史记录栈。仅删除撤销/重做记录,不会改动当前 DSL;清空后该页将无法再撤销/重做之前的操作。清空当前活动页时会同步刷新 `canUndo` / `canRedo` 并触发 `change` 事件。
|
||||||
|
|
||||||
|
## clearCodeBlock
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId` 可选;缺省清空全部代码块
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
清空代码块历史记录栈:传入 `codeBlockId` 仅清空该代码块,缺省清空全部代码块。仅删除撤销/重做记录,不会改动代码块本身。
|
||||||
|
|
||||||
|
## clearDataSource
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId` 可选;缺省清空全部数据源
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
清空数据源历史记录栈:传入 `dataSourceId` 仅清空该数据源,缺省清空全部数据源。仅删除撤销/重做记录,不会改动数据源本身。
|
||||||
|
|
||||||
|
## saveToIndexedDB
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{HistoryPersistOptions} options` 可选
|
||||||
|
|
||||||
|
::: details 查看 HistoryPersistOptions / PersistedHistoryState 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryPersistOptions{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#PersistedHistoryState{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/utils/undo-redo.ts#SerializedUndoRedo{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{Promise<PersistedHistoryState>}` 写入成功的快照对象
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
把当前内存中的全部历史栈(页面 / 代码块 / 数据源)连同各自游标、容量序列化后写入本地 IndexedDB。
|
||||||
|
|
||||||
|
- 最终库名为 `${dbName}-${当前 DSL app id}`,按应用隔离;
|
||||||
|
- `key` 用于在同一 store 下区分不同记录,缺省为 `default`;
|
||||||
|
- 历史记录里可能包含函数(代码块内容 / 节点事件等),内部使用 `serialize-javascript` 序列化为字符串后写入,恢复时再用 `parseDSL` 还原,因此可安全持久化函数 / `Map` 等;
|
||||||
|
- 不支持 IndexedDB 的环境(如 SSR)会 reject。
|
||||||
|
|
||||||
|
写入成功后触发 `save-to-indexed-db` 事件。
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
`beforeunload` / `pagehide` 阶段浏览器不会等待异步 IndexedDB 事务提交,单纯依赖卸载时写入可能丢失最近一次编辑。建议在历史变更时(防抖)即调用本方法持久化,确保刷新后能完整恢复。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## restoreFromIndexedDB
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{HistoryPersistOptions} options` 可选
|
||||||
|
|
||||||
|
- **返回:**
|
||||||
|
- `{Promise<PersistedHistoryState | null>}` 找不到记录时返回 `null`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
从本地 IndexedDB 读取此前保存的历史快照并重建全部撤销/重做栈。
|
||||||
|
|
||||||
|
- 每个栈都会按 `listMaxSize` 裁剪并还原游标;
|
||||||
|
- 若某个栈存在已保存记录(见 `markSaved`),其游标会被定位到「最近一条已保存记录」之后,使恢复后的状态与落库的 DSL 对齐;
|
||||||
|
- 会整体覆盖当前内存中的历史状态,并把活动页恢复为快照中的 `pageId`;
|
||||||
|
- 找不到对应记录时返回 `null` 且不改动当前状态;不支持 IndexedDB 的环境会 reject。
|
||||||
|
|
||||||
|
成功后触发 `restore-from-indexed-db` 与 `change` 事件。
|
||||||
|
|
||||||
## destroy
|
## destroy
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
销毁
|
销毁
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,25 @@
|
|||||||
- **默认值:** `{}`
|
- **默认值:** `{}`
|
||||||
|
|
||||||
|
|
||||||
- **类型:** [MApp](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/schema/src/index.ts?plain=1#L66-L73)[]
|
- **类型:** `MApp[]`
|
||||||
|
|
||||||
|
::: details 查看 MApp 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MApp{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#NodeType{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockDSL{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceDeps{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -49,7 +67,13 @@ const dsl = ref({
|
|||||||
|
|
||||||
- **默认值:** `[]`
|
- **默认值:** `[]`
|
||||||
|
|
||||||
- **类型:** [ComponentGroup](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/editor/src/type.ts#L355)
|
- **类型:** `ComponentGroup[]`
|
||||||
|
|
||||||
|
::: details 查看 ComponentGroup 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#ComponentGroup{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#ComponentItem{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
icon使用的是[element-plus icon](https://element-plus.org/zh-CN/component/icon.html)
|
icon使用的是[element-plus icon](https://element-plus.org/zh-CN/component/icon.html)
|
||||||
@ -128,7 +152,11 @@ const componentGroupList = ref([
|
|||||||
|
|
||||||
- **默认值:** `[]`
|
- **默认值:** `[]`
|
||||||
|
|
||||||
- **类型:** [DatasourceTypeOption](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/editor/src/type.ts#L589)
|
- **类型:** `DatasourceTypeOption[]`
|
||||||
|
|
||||||
|
::: details 查看 DatasourceTypeOption 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#DatasourceTypeOption{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -169,7 +197,21 @@ const datasourceTypeList = ref([
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- **类型:** [SideBarData](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L258-L265)
|
- **类型:** `SideBarData`
|
||||||
|
|
||||||
|
::: details 查看 SideBarData 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#SideBarData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#SideItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#SideItemKey{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#SideComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#MenuComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#Services{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -218,7 +260,7 @@ icon使用的是[element-plus icon](https://element-plus.org/zh-CN/component/ico
|
|||||||
|
|
||||||
顶部工具栏
|
顶部工具栏
|
||||||
|
|
||||||
系统提供了几个常用功能: `'/' | 'delete' | 'undo' | 'redo' | 'zoom-in' | 'zoom-out' | 'zoom' | 'guides' | 'rule' | 'scale-to-original' | 'scale-to-fit'`
|
系统提供了几个常用功能: `'/' | 'delete' | 'undo' | 'redo' | 'zoom-in' | 'zoom-out' | 'zoom' | 'guides' | 'rule' | 'scale-to-original' | 'scale-to-fit' | 'history-list'`
|
||||||
|
|
||||||
'/': 分隔符
|
'/': 分隔符
|
||||||
|
|
||||||
@ -242,13 +284,29 @@ icon使用的是[element-plus icon](https://element-plus.org/zh-CN/component/ico
|
|||||||
|
|
||||||
'scale-to-fit': 缩放以适应
|
'scale-to-fit': 缩放以适应
|
||||||
|
|
||||||
|
'history-list': 历史记录面板(按 页面 / 数据源 / 代码块 三个 tab 展示操作历史,相邻同目标修改自动合并,支持点击跳转、回到初始状态、单步回滚及差异对比,详见[历史记录面板](/guide/advanced/history-list.md))
|
||||||
|
|
||||||
- **默认值:**
|
- **默认值:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
{ left: [], center: [], right: [] }
|
{ left: [], center: [], right: [] }
|
||||||
```
|
```
|
||||||
|
|
||||||
- **类型:** [MenuBarData](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L235-L242)
|
- **类型:** `MenuBarData`
|
||||||
|
|
||||||
|
::: details 查看 MenuBarData 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#MenuBarData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#ColumnLayout{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#MenuItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#MenuButton{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#MenuComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#Services{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -296,7 +354,15 @@ const menu = ref({
|
|||||||
|
|
||||||
- **默认值:** `[]`
|
- **默认值:** `[]`
|
||||||
|
|
||||||
- **类型:** ([MenuButton](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L168-L195) | [MenuComponent](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L197-L210))[]
|
- **类型:** `(MenuButton | MenuComponent)[]`
|
||||||
|
|
||||||
|
::: details 查看 MenuButton / MenuComponent 及关联类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#MenuButton{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#MenuComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/editor/src/type.ts#Services{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -330,7 +396,9 @@ const layerContentMenu = ref([
|
|||||||
|
|
||||||
- **默认值:** `[]`
|
- **默认值:** `[]`
|
||||||
|
|
||||||
- **类型:** ([MenuButton](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L168-L195) | [MenuComponent](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/type.ts#L197-L210))[]
|
- **类型:** `(MenuButton | MenuComponent)[]`
|
||||||
|
|
||||||
|
> 已在上面 [layerContentMenu](#layercontentmenu) 段落展开过相同类型,参考即可。
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -471,7 +539,19 @@ const renderFunction = async (stage) => {
|
|||||||
|
|
||||||
- **默认值:** `{}`
|
- **默认值:** `{}`
|
||||||
-
|
-
|
||||||
- **类型:** Record<string, [FormConfig](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/form/src/schema.ts#L706)>
|
- **类型:** `Record<string, FormConfig>`
|
||||||
|
|
||||||
|
::: details 查看 FormConfig 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItemConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChildConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DynamicTypeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -552,7 +632,11 @@ const propsValues = {
|
|||||||
|
|
||||||
- **默认值:** `{}`
|
- **默认值:** `{}`
|
||||||
|
|
||||||
- **类型:** Record<string, { events: [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]; methods: [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[] }>
|
- **类型:** `Record<string, { events: EventOption[]; methods: EventOption[] }>`
|
||||||
|
|
||||||
|
::: details 查看 EventOption 类型定义
|
||||||
|
<<< @/../packages/core/src/utils.ts#EventOption{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -593,7 +677,23 @@ const eventMethodList = {
|
|||||||
|
|
||||||
- **默认值:** `{}`
|
- **默认值:** `{}`
|
||||||
|
|
||||||
- **类型:** Record<string, Partial<[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)>>
|
- **类型:** `Record<string, Partial<DataSourceSchema>>`
|
||||||
|
|
||||||
|
::: details 查看 DataSourceSchema 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSourceSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#DataSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MockSchema{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeBlockContent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#CodeParam{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#EventConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#JsEngine{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -634,7 +734,9 @@ const datasourceValues = {
|
|||||||
|
|
||||||
- **默认值:** `{}`
|
- **默认值:** `{}`
|
||||||
|
|
||||||
- **类型:** Record<string, [FormConfig](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/form/src/schema.ts#L706)>
|
- **类型:** `Record<string, FormConfig>`
|
||||||
|
|
||||||
|
> 已在上面 [propsConfigs](#propsconfigs) 段落展开过 `FormConfig` 类型定义,参考即可。
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -675,7 +777,11 @@ 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) => MoveableOptions) | `[`MoveableOptions`](https://daybrush.com/moveable/release/latest/doc/)
|
||||||
|
|
||||||
|
::: details 查看 CustomizeMoveableOptionsCallbackConfig 类型定义
|
||||||
|
<<< @/../packages/stage/src/types.ts#CustomizeMoveableOptionsCallbackConfig{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -1114,6 +1220,28 @@ const guidesOptions = {
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## disabledFlashTip
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
禁用「非点击画布选中组件时的高亮闪烁提示」。
|
||||||
|
|
||||||
|
当组件不是通过点击画布选中(如从组件树、面包屑等外部方式选中)时,编辑器会在画布上对选中区域做一次高亮闪烁,帮助用户快速定位组件在画布中的位置。设置为 `true` 可关闭该提示。
|
||||||
|
|
||||||
|
注:选中页面(`magic-ui-page`)时不会触发闪烁。
|
||||||
|
|
||||||
|
- **默认值:** `false`
|
||||||
|
|
||||||
|
- **类型:** `boolean`
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<m-editor :disabled-flash-tip="true"></m-editor>
|
||||||
|
</template>
|
||||||
|
```
|
||||||
|
|
||||||
## disabledStageOverlay
|
## disabledStageOverlay
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
@ -1402,6 +1530,55 @@ const extendFormState = async (state) => {
|
|||||||
```
|
```
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## historyListExtraTabs
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
[历史记录面板](/guide/advanced/history-list.md) 的自定义扩展 tab。
|
||||||
|
|
||||||
|
业务方可借此在历史记录面板内置的「页面 / 数据源 / 代码块」三个 tab 之后追加自定义模块的历史 tab,例如某个自定义模块维护自己的操作历史时,可在面板中增加一个独立的 tab 来展示与回滚。
|
||||||
|
|
||||||
|
- **默认值:** `[]`
|
||||||
|
|
||||||
|
- **类型:** `HistoryListExtraTab[]`
|
||||||
|
|
||||||
|
::: details 查看 HistoryListExtraTab 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#HistoryListExtraTab{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<m-editor :menu="menu" :history-list-extra-tabs="historyListExtraTabs"></m-editor>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
|
import MyModuleHistoryTab from './MyModuleHistoryTab.vue';
|
||||||
|
|
||||||
|
const historyListExtraTabs = [
|
||||||
|
{
|
||||||
|
name: 'my-module',
|
||||||
|
// label 支持字符串或函数,函数形式便于展示动态数量
|
||||||
|
label: () => '我的模块',
|
||||||
|
component: markRaw(MyModuleHistoryTab),
|
||||||
|
// 传入内容组件的 props
|
||||||
|
props: { foo: 'bar' },
|
||||||
|
// 内容组件的事件监听
|
||||||
|
listeners: {
|
||||||
|
goto: (cursor) => console.log(cursor),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
内容组件内部可自行通过 `useServices()` 获取 `historyService` 等服务来读取与回滚自定义模块的历史。
|
||||||
|
:::
|
||||||
|
|
||||||
## pageBarSortOptions
|
## pageBarSortOptions
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
@ -1412,7 +1589,11 @@ const extendFormState = async (state) => {
|
|||||||
|
|
||||||
- **默认值:** `undefined`
|
- **默认值:** `undefined`
|
||||||
|
|
||||||
- **类型:** [PageBarSortOptions](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/type.ts)
|
- **类型:** `PageBarSortOptions`
|
||||||
|
|
||||||
|
::: details 查看 PageBarSortOptions 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#PageBarSortOptions{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
|
|||||||
@ -47,11 +47,23 @@
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)} config
|
- {`FormConfig`} config
|
||||||
- `{string}` labelWidth 表单项 label 宽度,默认 `'80px'`
|
- `{string}` labelWidth 表单项 label 宽度,默认 `'80px'`
|
||||||
|
|
||||||
|
::: details 查看 FormConfig 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItemConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChildConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DynamicTypeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)>}
|
- {Promise<`FormConfig`>}
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -60,7 +72,11 @@
|
|||||||
## setPropsConfigs
|
## setPropsConfigs
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {Record<string, [FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864) | [PropsFormConfigFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/editor/src/type.ts#L721)>} configs
|
- {Record<string, `FormConfig` | `PropsFormConfigFunction`>} configs
|
||||||
|
|
||||||
|
::: details 查看 PropsFormConfigFunction 类型定义
|
||||||
|
<<< @/../packages/editor/src/type.ts#PropsFormConfigFunction{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -75,7 +91,7 @@
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` type 组件类型
|
- `{string}` type 组件类型
|
||||||
- {[FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)} config 属性表单配置DSL
|
- {`FormConfig`} config 属性表单配置DSL
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -91,10 +107,26 @@
|
|||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` type 组件类型
|
- `{string}` type 组件类型
|
||||||
- `{Object}` data 可选参数
|
- `{Object}` data 可选参数
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210) | null} node 当前节点
|
- {`MNode` | null} node 当前节点
|
||||||
|
|
||||||
|
::: details 查看 MNode 及关联类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MNode{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MComponent{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MIteratorContainer{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MApp{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)>}
|
- {Promise<`FormConfig`>}
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -103,7 +135,7 @@
|
|||||||
## setPropsValues
|
## setPropsValues
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {Record<string, [MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)>} values
|
- {Record<string, `MNode`>} values
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{void}`
|
- `{void}`
|
||||||
@ -116,7 +148,7 @@
|
|||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string}` type 组件类型
|
- `{string}` type 组件类型
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} value 组件初始值
|
- {`MNode`} value 组件初始值
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{Promise<void>}`
|
- `{Promise<void>}`
|
||||||
@ -134,7 +166,7 @@
|
|||||||
- `{Object}` defaultValue 组件默认值,可选
|
- `{Object}` defaultValue 组件默认值,可选
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)>} 合并默认配置后的节点对象
|
- {Promise<`MNode`>} 合并默认配置后的节点对象
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -159,11 +191,11 @@
|
|||||||
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
- **[扩展支持](../../guide/editor-expand#行为扩展):** 是
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} config
|
- {`MNode`} config
|
||||||
- `{boolean}` force 是否强制设置新ID,默认 `true`
|
- `{boolean}` force 是否强制设置新ID,默认 `true`
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)} 处理后的节点
|
- {`MNode`} 处理后的节点
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
@ -186,8 +218,8 @@
|
|||||||
## replaceRelateId
|
## replaceRelateId
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} originConfigs 原始组件配置
|
- {`MNode`[]} originConfigs 原始组件配置
|
||||||
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/schema/src/index.ts#L210)[]} targetConfigs 待替换的组件配置
|
- {`MNode`[]} targetConfigs 待替换的组件配置
|
||||||
- `{TargetOptions}` collectorOptions 依赖收集器配置
|
- `{TargetOptions}` collectorOptions 依赖收集器配置
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
@ -222,15 +254,11 @@
|
|||||||
|
|
||||||
销毁propsService
|
销毁propsService
|
||||||
|
|
||||||
## use
|
|
||||||
|
|
||||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
|
||||||
|
|
||||||
## usePlugin
|
## usePlugin
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||||
|
|
||||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||||
|
|
||||||
@ -239,3 +267,4 @@
|
|||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
删掉当前设置的所有扩展
|
删掉当前设置的所有扩展
|
||||||
|
|
||||||
|
|||||||
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
- **详情:** 编辑器顶部菜单栏
|
- **详情:** 编辑器顶部菜单栏
|
||||||
|
|
||||||
- **默认:** [NavMenu.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/NavMenu.vue)
|
- **默认:** [NavMenu.vue](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/layouts/NavMenu.vue)
|
||||||
|
|
||||||
- **插槽 Props:**
|
- **插槽 Props:**
|
||||||
- `editorService`: editorService 实例
|
- `editorService`: editorService 实例
|
||||||
@ -64,7 +64,7 @@
|
|||||||
|
|
||||||
- **详情:** 左边栏
|
- **详情:** 左边栏
|
||||||
|
|
||||||
- **默认:** [Sidebar.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/sidebar/Sidebar.vue)
|
- **默认:** [Sidebar.vue](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/layouts/sidebar/Sidebar.vue)
|
||||||
|
|
||||||
- **插槽 Props:**
|
- **插槽 Props:**
|
||||||
- `editorService`: editorService 实例
|
- `editorService`: editorService 实例
|
||||||
@ -259,7 +259,7 @@
|
|||||||
|
|
||||||
- **详情:** 编辑器中间区域
|
- **详情:** 编辑器中间区域
|
||||||
|
|
||||||
- **默认:** [Workspace.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/workspace/Workspace.vue)
|
- **默认:** [Workspace.vue](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/layouts/workspace/Workspace.vue)
|
||||||
|
|
||||||
- **插槽 Props:**
|
- **插槽 Props:**
|
||||||
- `editorService`: editorService 实例
|
- `editorService`: editorService 实例
|
||||||
@ -268,7 +268,7 @@
|
|||||||
|
|
||||||
- **详情:** 画布
|
- **详情:** 画布
|
||||||
|
|
||||||
- **默认:** [Stage.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/workspace/Stage.vue)
|
- **默认:** [Stage.vue](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/layouts/workspace/Stage.vue)
|
||||||
|
|
||||||
## stage-top
|
## stage-top
|
||||||
|
|
||||||
@ -380,7 +380,7 @@
|
|||||||
|
|
||||||
- **详情:** 当前没有页面时,编辑器中间区域
|
- **详情:** 当前没有页面时,编辑器中间区域
|
||||||
|
|
||||||
- **默认:** [AddPageBox.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/AddPageBox.vue)
|
- **默认:** [AddPageBox.vue](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/layouts/AddPageBox.vue)
|
||||||
|
|
||||||
- **插槽 Props:**
|
- **插槽 Props:**
|
||||||
- `editorService`: editorService 实例
|
- `editorService`: editorService 实例
|
||||||
|
|||||||
@ -231,28 +231,11 @@ import { storageService } from '@tmagic/editor';
|
|||||||
storageService.destroy();
|
storageService.destroy();
|
||||||
```
|
```
|
||||||
|
|
||||||
## use
|
|
||||||
|
|
||||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
|
||||||
|
|
||||||
- **示例:**
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { storageService } from '@tmagic/editor';
|
|
||||||
|
|
||||||
storageService.use({
|
|
||||||
getItem(key, options, next) {
|
|
||||||
console.log('获取存储项:', key);
|
|
||||||
return next();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## usePlugin
|
## usePlugin
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||||
|
|
||||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||||
|
|
||||||
|
|||||||
@ -179,29 +179,11 @@ import { uiService } from '@tmagic/editor';
|
|||||||
uiService.destroy();
|
uiService.destroy();
|
||||||
```
|
```
|
||||||
|
|
||||||
## use
|
|
||||||
|
|
||||||
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
|
|
||||||
|
|
||||||
- **示例:**
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { uiService } from '@tmagic/editor';
|
|
||||||
|
|
||||||
uiService.use({
|
|
||||||
async zoom(value, next) {
|
|
||||||
console.log('缩放前:', uiService.get('zoom'));
|
|
||||||
await next();
|
|
||||||
console.log('缩放后:', uiService.get('zoom'));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## usePlugin
|
## usePlugin
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
usePlugin支持灵活细致的扩展, 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
|
||||||
|
|
||||||
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为,before可以用于修改传入参数,after可以用于修改返回的值
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,19 @@
|
|||||||
|
|
||||||
- **默认值:** `[]`
|
- **默认值:** `[]`
|
||||||
|
|
||||||
- **类型:** [FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)
|
- **类型:** `FormConfig`
|
||||||
|
|
||||||
|
::: details 查看 FormConfig 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItemConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChildConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DynamicTypeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
- **详情:** 提交表单,先执行校验,校验通过后清空 `changeRecords` 并返回当前表单值
|
- **详情:** 提交表单,先执行校验,校验通过后清空 `changeRecords` 并返回当前表单值
|
||||||
|
|
||||||
|
- **相关:** 如果你想脱离组件树以函数方式完成一次表单提交,参见 [`submitForm` 函数](./submit-form.md)
|
||||||
|
|
||||||
## changeHandler
|
## changeHandler
|
||||||
|
|
||||||
- **签名:** `(prop: string, value: any, eventData?: ContainerChangeEventData) => void`
|
- **签名:** `(prop: string, value: any, eventData?: ContainerChangeEventData) => void`
|
||||||
|
|||||||
@ -6,7 +6,19 @@
|
|||||||
|
|
||||||
- **默认值:** `[]`
|
- **默认值:** `[]`
|
||||||
|
|
||||||
- **类型:** [FormConfig](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L864)
|
- **类型:** `FormConfig`
|
||||||
|
|
||||||
|
::: details 查看 FormConfig 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItemConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChildConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DynamicTypeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -73,6 +85,44 @@
|
|||||||
|
|
||||||
- **类型:** `boolean`
|
- **类型:** `boolean`
|
||||||
|
|
||||||
|
## showDiff
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
自定义“是否展示对比内容”的判断函数(仅在 `isCompare === true` 时生效)。
|
||||||
|
|
||||||
|
- 不传:使用默认逻辑 `!isEqual(curValue, lastValue)`(基于 lodash `isEqual`);
|
||||||
|
- 传函数:完全以函数返回值为准,返回 `true` 才展示前后两份对比内容。
|
||||||
|
|
||||||
|
该 prop 通过 `formState` 透传到所有层级的 Container 中,调用方只需在 MForm 这一层传一次即可对整棵表单生效。
|
||||||
|
|
||||||
|
典型场景:某些字段语义上相等但结构不同(例如 `code-select` 字段中 `''` 与 `{ hookType: 'code', hookData: [] }` 应视为相等),调用方在此处显式声明,避免被 `isEqual` 误判为差异。
|
||||||
|
|
||||||
|
- **类型:** `(data: { curValue: any; lastValue: any; config: FormItemConfig }) => boolean`
|
||||||
|
|
||||||
|
- **示例:**
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<m-form :config="config" :is-compare="true" :last-values="lastValues" :show-diff="showDiff"></m-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
|
const showDiff = ({ curValue, lastValue, config }) => {
|
||||||
|
if (config?.type === 'code-select') {
|
||||||
|
// 业务侧自定义:双方都是“空形态”视为相等,不展示对比
|
||||||
|
const isEmpty = (v) =>
|
||||||
|
v === '' || v === undefined || v === null ||
|
||||||
|
(typeof v === 'object' && v.hookType === 'code' && Array.isArray(v.hookData) && v.hookData.length === 0);
|
||||||
|
if (isEmpty(curValue) && isEmpty(lastValue)) return false;
|
||||||
|
}
|
||||||
|
return !isEqual(curValue, lastValue);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
## parentValues
|
## parentValues
|
||||||
|
|
||||||
- **详情:** 父级表单值
|
- **详情:** 父级表单值
|
||||||
|
|||||||
218
docs/api/form/submit-form.md
Normal file
218
docs/api/form/submit-form.md
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
# submitForm 函数
|
||||||
|
|
||||||
|
以命令式方式调用 `MForm` 组件完成一次表单校验/提交,类似 `ElMessage` 的用法。
|
||||||
|
|
||||||
|
调用时函数内部会临时挂载一个不可见的 `MForm` 实例,把入参作为 props 透传给它,等待初始化完成后调用其 `submitForm` 方法。校验通过则 `resolve` 表单值,校验失败则 `reject` 错误信息,最后自动卸载实例并清理 DOM。
|
||||||
|
|
||||||
|
适用于一些没有合适的容器、但又需要复用 `MForm` 校验逻辑的场景,例如:
|
||||||
|
|
||||||
|
- 通过快捷菜单/命令面板触发一次性表单
|
||||||
|
- 在脚本/服务层完成一次表单值校验后再发请求
|
||||||
|
- 把 `config` 配置当作"可执行的校验规则"使用
|
||||||
|
|
||||||
|
## 签名
|
||||||
|
|
||||||
|
```ts
|
||||||
|
function submitForm(options: SubmitFormOptions): Promise<any>;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 参数
|
||||||
|
|
||||||
|
`options` 与 `MForm` 组件的 props 基本对齐,额外提供了 `native`、`returnChangeRecords`、`appContext`、`timeout` 等参数。
|
||||||
|
|
||||||
|
| 名称 | 类型 | 默认值 | 说明 |
|
||||||
|
| ---------------------- | ------------------------------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |
|
||||||
|
| `config` | `FormConfig` | — | 必填,表单配置 |
|
||||||
|
| `initValues` | `Record<string, any>` | `{}` | 表单初始值 |
|
||||||
|
| `lastValues` | `Record<string, any>` | `{}` | 需对比的值(开启对比模式时传入) |
|
||||||
|
| `isCompare` | `boolean` | `false` | 是否开启对比模式 |
|
||||||
|
| `parentValues` | `Record<string, any>` | `{}` | 父级 values,透传给字段的回调 |
|
||||||
|
| `labelWidth` | `string` | `'200px'` | label 宽度 |
|
||||||
|
| `disabled` | `boolean` | `false` | 是否禁用 |
|
||||||
|
| `height` | `string` | `'auto'` | 表单高度 |
|
||||||
|
| `stepActive` | `string \| number` | `1` | 步骤表单当前激活步骤 |
|
||||||
|
| `size` | `'small' \| 'default' \| 'large'` | — | 组件尺寸 |
|
||||||
|
| `inline` | `boolean` | `false` | 是否行内表单 |
|
||||||
|
| `labelPosition` | `string` | `'right'` | label 对齐方式 |
|
||||||
|
| `keyProp` | `string` | `'__key'` | 配置项的唯一 key |
|
||||||
|
| `popperClass` | `string` | — | 弹层 className |
|
||||||
|
| `preventSubmitDefault` | `boolean` | — | 是否阻止表单原生 submit |
|
||||||
|
| `extendState` | `(state: FormState) => Record<string, any> \| Promise<Record<string, any>>` | — | 扩展 `formState` |
|
||||||
|
| `native` | `boolean` | `false` | 透传给 `Form.submitForm`。`true` 时返回内部响应式 `values`,否则返回 `cloneDeep(toRaw(values))` |
|
||||||
|
| `returnChangeRecords` | `boolean` | `false` | `true` 时 resolve 结果为 `{ values, changeRecords }`,携带表单变更记录;否则仅 resolve `values` |
|
||||||
|
| `appContext` | `AppContext \| null` | `null` | 父级 Vue 应用上下文。需要继承全局组件、指令、provide 等时传入,常通过 `app._context` 或 `getCurrentInstance()?.appContext` 获取 |
|
||||||
|
| `timeout` | `number` | `10000` | 等待表单初始化的最长时间(毫秒)。超时将以错误 reject。设为 `<= 0` 时关闭超时兜底 |
|
||||||
|
|
||||||
|
## 返回值
|
||||||
|
|
||||||
|
- `校验通过` — `Promise<any>` resolve 当前表单值(`native` 决定是否克隆);当 `returnChangeRecords` 为 `true` 时,resolve `{ values, changeRecords }`
|
||||||
|
- `校验失败` — `Promise<any>` reject 一个 `Error`,`message` 中包含逐条字段错误信息(格式 `${text} -> ${message}`,多条用 `<br>` 分隔)
|
||||||
|
- `初始化超时` — `Promise<any>` reject `Error('submitForm timeout after ${timeout}ms: form is not initialized.')`
|
||||||
|
|
||||||
|
无论成功或失败,函数都会在最后自动 `unmount` 内部 app 并移除挂载用的 DOM 容器,无需调用方手动清理。
|
||||||
|
|
||||||
|
::: tip 关于 changeRecords
|
||||||
|
`changeRecords` 记录的是表单挂载后发生的字段变更(由各字段的 `change` 事件累积而来)。在 `submitForm` 这种命令式、无用户交互的场景下,通常为空数组;只有在 `extendState` 或字段联动等逻辑中触发了变更时才会有内容。`MForm` 内部的 `submitForm` 在校验通过后会清空变更记录,因此本函数会在调用前先对其做快照再返回。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { submitForm } from '@tmagic/form';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const values = await submitForm({
|
||||||
|
config: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'username',
|
||||||
|
text: '用户名',
|
||||||
|
rules: [{ required: true, message: '请输入用户名' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initValues: { username: '' },
|
||||||
|
});
|
||||||
|
console.log('提交成功', values);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('校验失败', e);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 同时获取变更记录(changeRecords)
|
||||||
|
|
||||||
|
设置 `returnChangeRecords: true` 后,resolve 的结果会从单纯的 `values` 变为 `{ values, changeRecords }`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { submitForm } from '@tmagic/form';
|
||||||
|
|
||||||
|
const { values, changeRecords } = await submitForm({
|
||||||
|
config: [{ type: 'text', name: 'username', text: '用户名' }],
|
||||||
|
initValues: { username: 'foo' },
|
||||||
|
returnChangeRecords: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(values); // { username: 'foo' }
|
||||||
|
console.log(changeRecords); // ChangeRecord[]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 在组件中继承父级应用上下文
|
||||||
|
|
||||||
|
`MForm` 内部使用 `@tmagic/design` 的组件(背后可能是 `element-plus` 或 `tdesign`),需要宿主应用先完成相应的 `app.use(...)` 安装。在 `submitForm` 这种脱离常规组件树的命令式调用中,可通过 `appContext` 把父级应用上下文带过去:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { getCurrentInstance } from 'vue';
|
||||||
|
|
||||||
|
import { submitForm } from '@tmagic/form';
|
||||||
|
|
||||||
|
const { appContext } = getCurrentInstance()!;
|
||||||
|
|
||||||
|
const onClick = async () => {
|
||||||
|
const values = await submitForm({
|
||||||
|
config: [{ type: 'text', name: 'text', text: '文本' }],
|
||||||
|
initValues: { text: 'hello' },
|
||||||
|
appContext,
|
||||||
|
});
|
||||||
|
console.log(values);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
也可以在初始化 app 时把上下文缓存下来,再在任意位置复用:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createApp } from 'vue';
|
||||||
|
import ElementPlus from 'element-plus';
|
||||||
|
import MagicForm, { type SubmitFormOptions, submitForm as rawSubmitForm } from '@tmagic/form';
|
||||||
|
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
const app = createApp(App);
|
||||||
|
app.use(ElementPlus);
|
||||||
|
app.use(MagicForm);
|
||||||
|
app.mount('#app');
|
||||||
|
|
||||||
|
export const submitForm = (options: Omit<SubmitFormOptions, 'appContext'>) =>
|
||||||
|
rawSubmitForm({ ...options, appContext: app._context });
|
||||||
|
```
|
||||||
|
|
||||||
|
## 处理校验错误
|
||||||
|
|
||||||
|
校验失败时 reject 的 `Error.message` 已经把出错字段拼好,可以直接展示到用户:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { tMagicMessage } from '@tmagic/design';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const values = await submitForm({ config, initValues });
|
||||||
|
await save(values);
|
||||||
|
} catch (e: any) {
|
||||||
|
tMagicMessage.error({
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
message: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 运行环境
|
||||||
|
|
||||||
|
`submitForm` 内部依赖 `document` / `window` 来挂载临时 Vue 实例,因此**只能在浏览器或具备 DOM 环境的运行时中使用**。
|
||||||
|
|
||||||
|
| 环境 | 是否可用 | 说明 |
|
||||||
|
| ----------------------------------------------- | -------- | --------------------------------------------------------------------------------- |
|
||||||
|
| 浏览器 / Electron 渲染进程 / 浏览器扩展 | ✅ | 直接可用 |
|
||||||
|
| Vitest / Jest + `happy-dom` / `jsdom` | ✅ | 项目自身的单测就跑在这种环境下 |
|
||||||
|
| 纯 Node.js / Bun / Deno(无 DOM polyfill) | ❌ | 模块顶层就会读 `document`,会抛 `document is not defined` |
|
||||||
|
| Node.js + 手动注入 `happy-dom` / `jsdom` | ⚠️ | 可用,需要在 import `@tmagic/form` **之前**完成全局变量注入;校验行为不一定与浏览器完全一致 |
|
||||||
|
|
||||||
|
### 在 Node.js 中使用(需要先准备 DOM)
|
||||||
|
|
||||||
|
下面是一个在 Node 脚本里调用 `submitForm` 的完整例子,使用 [`happy-dom`](https://github.com/capricorn86/happy-dom) 作为 DOM polyfill:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// scripts/check-form.ts
|
||||||
|
import { Window } from 'happy-dom';
|
||||||
|
|
||||||
|
const window = new Window();
|
||||||
|
Object.assign(globalThis, {
|
||||||
|
window,
|
||||||
|
document: window.document,
|
||||||
|
navigator: window.navigator,
|
||||||
|
HTMLElement: window.HTMLElement,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 注意:DOM polyfill 必须先注入到 globalThis,再用动态 import
|
||||||
|
// 加载业务模块,否则 @tmagic/design 等模块顶层执行时就会读 document
|
||||||
|
const { createApp } = await import('vue');
|
||||||
|
const ElementPlus = (await import('element-plus')).default;
|
||||||
|
const MagicForm = (await import('@tmagic/form')).default;
|
||||||
|
const { submitForm } = await import('@tmagic/form');
|
||||||
|
|
||||||
|
const parentApp = createApp({ render: () => null });
|
||||||
|
parentApp.use(ElementPlus);
|
||||||
|
parentApp.use(MagicForm);
|
||||||
|
|
||||||
|
const values = await submitForm({
|
||||||
|
config: [{ type: 'text', name: 'username', text: '用户名' }],
|
||||||
|
initValues: { username: 'foo' },
|
||||||
|
appContext: parentApp._context,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(values);
|
||||||
|
```
|
||||||
|
|
||||||
|
::: warning 注意
|
||||||
|
- DOM polyfill 必须在 **import 业务模块之前** 注入到 `globalThis`,否则模块顶层执行时仍会失败
|
||||||
|
- 在 `happy-dom` / `jsdom` 中,`element-plus` 的部分 `validate()` 行为不一定能 1:1 复现真实浏览器(例如某些场景下必填规则可能不触发),建议关键校验使用自定义 `validator` 函数确保稳定
|
||||||
|
- 如果只是想在 Node 端做一次纯校验,更稳妥的做法是直接复用 [`async-validator`](https://github.com/yiminghe/async-validator)(element-plus 内部用的就是它),绕开整个 Vue 渲染层
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 类型定义
|
||||||
|
|
||||||
|
::: details 查看 `SubmitFormOptions` 类型定义
|
||||||
|
<<< @/../packages/form/src/submitForm.ts#SubmitFormOptions{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
::: details 查看 `SubmitFormResult` 类型定义
|
||||||
|
<<< @/../packages/form/src/submitForm.ts#SubmitFormResult{ts}
|
||||||
|
:::
|
||||||
@ -1,7 +1,30 @@
|
|||||||
# 表单对比
|
# 表单对比
|
||||||
tmagic-form可以支持两个版本的表单值对比,如果有容器嵌套,将在tab标签页展示对应tab下存在的差异数,便于在复杂嵌套表单场景下直观的看到差异情况
|
tmagic-form可以支持两个版本的表单值对比,如果有容器嵌套,将在tab标签页展示对应tab下存在的差异数,便于在复杂嵌套表单场景下直观的看到差异情况
|
||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
在初始化表单时,需要传入当前版本的表单值,上一版本的表单值,以及表单配置,具体可参见[Form Playground](https://tencent.github.io/tmagic-editor/playground/index.html#/form)的demo演示
|
在初始化表单时,开启对比模式 `is-compare`,并传入当前版本的表单值(`init-values`)、上一版本的表单值(`last-values`)以及表单配置,具体可参见[Form Playground](https://tencent.github.io/tmagic-editor/playground/index.html#/form)的demo演示。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<m-form
|
||||||
|
:config="config"
|
||||||
|
:is-compare="true"
|
||||||
|
:init-values="curValues"
|
||||||
|
:last-values="lastValues"
|
||||||
|
></m-form>
|
||||||
|
```
|
||||||
|
|
||||||
|
相关属性详见 Form 组件 props:
|
||||||
|
|
||||||
|
- [`isCompare`](/api/form/form-props.html#iscompare):是否开启对比模式;
|
||||||
|
- [`lastValues`](/api/form/form-props.html#lastvalues):需对比的上一版本表单值;
|
||||||
|
- [`showDiff`](/api/form/form-props.html#showdiff):自定义「是否展示对比内容」的判断函数,用于规避语义相等但结构不同导致的误判。
|
||||||
|
|
||||||
|
## 对比模式下的字段行为
|
||||||
|
对比模式下,表单仅做只读展示:增删、复制、排序、导入、编辑等写操作按钮会被隐藏。对于由列表或嵌套子表单组成的复合字段(如 `event-select`、`code-select`、`code-select-col`),表单会按索引对齐前后值,逐项展示新增 / 删除 / 修改的高亮差异,而不会渲染出两套独立组件。
|
||||||
|
|
||||||
|
## 应用场景
|
||||||
|
编辑器的[历史记录面板](/guide/advanced/history-list.md)即基于该能力,对历史步骤的前后值做表单形式的差异对比。
|
||||||
|
|
||||||
## 效果展示
|
## 效果展示
|
||||||
<img src="https://vip.image.video.qpic.cn/vupload/20230301/c626071677661813135.png" alt="表单对比"/>
|
<img src="https://vip.image.video.qpic.cn/vupload/20230301/c626071677661813135.png" alt="表单对比"/>
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,29 @@
|
|||||||
| ----------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| ----------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| formTitle | 弹窗标题 | string | — | — |
|
| formTitle | 弹窗标题 | string | — | — |
|
||||||
| codeOptions | 代码编辑器配置项 | object | — | — |
|
| codeOptions | 代码编辑器配置项 | object | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 CodeLinkConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#CodeLinkConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -33,6 +33,28 @@
|
|||||||
| ----------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| ----------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| notEditable | 是否不可编辑代码块(disable控制是否可选择) | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| notEditable | 是否不可编辑代码块(disable控制是否可选择) | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 CodeSelectColConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#CodeSelectColConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -30,6 +30,28 @@ CodeSelect 组件支持:
|
|||||||
| --------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| --------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| className | 自定义类名 | string | — | — |
|
| className | 自定义类名 | string | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 CodeSelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#CodeSelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -47,14 +47,36 @@
|
|||||||
| ------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------ |
|
| ------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| language | 代码语言 | string | javascript/typescript/json等 | — |
|
| language | 代码语言 | string | javascript/typescript/json等 | — |
|
||||||
| height | 编辑器高度 | string | — | — |
|
| height | 编辑器高度 | string | — | — |
|
||||||
| parse | 是否解析代码 | boolean | — | false |
|
| parse | 是否解析代码 | boolean | — | false |
|
||||||
| options | 编辑器配置项 | object | — | — |
|
| options | 编辑器配置项 | object | — | — |
|
||||||
| autosize | 自动调整大小配置 | object | — | — |
|
| autosize | 自动调整大小配置 | object | — | — |
|
||||||
| mFormItemType | 传入代码编辑器的自定义类型 | string | — | — |
|
| mFormItemType | 传入代码编辑器的自定义类型 | string | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 CodeConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#CodeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## autosize Attributes
|
## autosize Attributes
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,28 @@
|
|||||||
| ------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| ------------ | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| parentFields | 父级字段 | string[] | — | — |
|
| parentFields | 父级字段 | string[] | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 CondOpSelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#CondOpSelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -55,13 +55,35 @@
|
|||||||
| ------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------ |
|
| ------------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| value | 返回值类型 | string | key/value | — |
|
| value | 返回值类型 | string | key/value | — |
|
||||||
| checkStrictly | 是否严格遵守父子节点不互相关联 | boolean / Function | — | — |
|
| checkStrictly | 是否严格遵守父子节点不互相关联 | boolean / Function | — | — |
|
||||||
| dataSourceFieldType | 允许选择的字段类型 | DataSourceFieldType[] | — | — |
|
| dataSourceFieldType | 允许选择的字段类型 | DataSourceFieldType[] | — | — |
|
||||||
| fieldConfig | 自定义字段配置 | ChildConfig | — | — |
|
| fieldConfig | 自定义字段配置 | ChildConfig | — | — |
|
||||||
| notEditable | 是否不可编辑数据源(disable控制是否可选择) | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| notEditable | 是否不可编辑数据源(disable控制是否可选择) | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DataSourceFieldSelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DataSourceFieldSelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## value说明
|
## value说明
|
||||||
|
|
||||||
|
|||||||
@ -22,5 +22,27 @@
|
|||||||
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DataSourceFieldsConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DataSourceFieldsConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -22,5 +22,27 @@
|
|||||||
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DataSourceInputConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DataSourceInputConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -33,6 +33,28 @@
|
|||||||
| ----------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| ----------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| notEditable | 是否不可编辑数据源(disable控制是否可选择) | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| notEditable | 是否不可编辑数据源(disable控制是否可选择) | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DataSourceMethodSelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DataSourceMethodSelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -22,5 +22,27 @@
|
|||||||
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DataSourceMethodsConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DataSourceMethodsConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -22,5 +22,27 @@
|
|||||||
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DataSourceMocksConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DataSourceMocksConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -45,11 +45,35 @@
|
|||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| dataSourceType | 数据源类型过滤 | string | base/http等 | — |
|
| dataSourceType | 数据源类型过滤 | string | base/http等 | — |
|
||||||
| value | 返回值类型 | string | id/value | — |
|
| value | 返回值类型 | string | id/value | — |
|
||||||
| notEditable | 是否不可编辑数据源(disable控制是否可选择) | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| notEditable | 是否不可编辑数据源(disable控制是否可选择) | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DataSourceSelect 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DataSourceSelect{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#Input{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## value说明
|
## value说明
|
||||||
|
|
||||||
|
|||||||
@ -33,7 +33,29 @@
|
|||||||
| ------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| ------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| titlePrefix | 标题前缀 | string | — | — |
|
| titlePrefix | 标题前缀 | string | — | — |
|
||||||
| parentFields | 父级字段 | string[] / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | — |
|
| parentFields | 父级字段 | string[] / `FilterFunction` | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DisplayCondsConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#DisplayCondsConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
| ---------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ------ |
|
| ---------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| src | 事件来源 | string | datasource/component | — |
|
| src | 事件来源 | string | datasource/component | — |
|
||||||
| labelWidth | 标签宽度 | string | — | — |
|
| labelWidth | 标签宽度 | string | — | — |
|
||||||
| eventNameConfig | 事件名称表单配置 | FormItem | — | — |
|
| eventNameConfig | 事件名称表单配置 | FormItem | — | — |
|
||||||
@ -43,7 +43,29 @@
|
|||||||
| compActionConfig | 联动组件动作配置 | FormItem | — | — |
|
| compActionConfig | 联动组件动作配置 | FormItem | — | — |
|
||||||
| codeActionConfig | 联动代码配置 | FormItem | — | — |
|
| codeActionConfig | 联动代码配置 | FormItem | — | — |
|
||||||
| dataSourceActionConfig | 联动数据源配置 | FormItem | — | — |
|
| dataSourceActionConfig | 联动数据源配置 | FormItem | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 EventSelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#EventSelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## src说明
|
## src说明
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,28 @@
|
|||||||
| -------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| advanced | 是否支持高级模式(代码编辑) | boolean | — | false |
|
| advanced | 是否支持高级模式(代码编辑) | boolean | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 KeyValueConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#KeyValueConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -22,8 +22,30 @@
|
|||||||
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 PageFragmentSelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#PageFragmentSelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 使用说明
|
## 使用说明
|
||||||
|
|
||||||
|
|||||||
@ -22,8 +22,30 @@
|
|||||||
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 UISelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/editor.ts#UISelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 使用说明
|
## 使用说明
|
||||||
|
|
||||||
|
|||||||
@ -423,16 +423,40 @@ options 支持传入函数,可根据表单其他字段动态生成选项列表
|
|||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| multiple | 是否多选 | boolean | — | false |
|
| multiple | 是否多选 | boolean | — | false |
|
||||||
| emitPath | 在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值 | boolean | — | true |
|
| emitPath | 在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false,则只返回该节点的值 | boolean | — | true |
|
||||||
| checkStrictly | 是否严格的遵守父子节点不互相关联 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | false |
|
| checkStrictly | 是否严格的遵守父子节点不互相关联 | boolean / `FilterFunction` | — | false |
|
||||||
| valueSeparator | 合并成字符串时的分隔符 | string / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | — |
|
| valueSeparator | 合并成字符串时的分隔符 | string / `FilterFunction` | — | — |
|
||||||
| popperClass | 弹出内容的自定义类名 | string | — | — |
|
| popperClass | 弹出内容的自定义类名 | string | — | — |
|
||||||
| remote | 是否为远程搜索 | boolean | — | false |
|
| remote | 是否为远程搜索 | boolean | — | false |
|
||||||
| options | 选项数据源 | Array / Function | — | — |
|
| options | 选项数据源 | Array / Function | — | — |
|
||||||
| option | 远程选项配置 | Object | — | — |
|
| option | 远程选项配置 | Object | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | — |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | — |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 CascaderConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#CascaderConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#Input{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## options item
|
## options item
|
||||||
|
|
||||||
|
|||||||
@ -154,12 +154,36 @@ options 支持函数形式,可根据表单状态动态生成选项。
|
|||||||
|------|------|------|--------|--------|
|
|------|------|------|--------|--------|
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| activeValue | 选中时的值 | string / number | — | true(filter 为 'number' 时默认 1) |
|
| activeValue | 选中时的值 | string / number | — | true(filter 为 'number' 时默认 1) |
|
||||||
| inactiveValue | 未选中时的值 | string / number | — | false(filter 为 'number' 时默认 0) |
|
| inactiveValue | 未选中时的值 | string / number | — | false(filter 为 'number' 时默认 0) |
|
||||||
| useLabel | 是否使用外部 label 显示 | boolean | — | false |
|
| useLabel | 是否使用外部 label 显示 | boolean | — | false |
|
||||||
| filter | 值过滤器 | 'number' / Function | — | — |
|
| filter | 值过滤器 | 'number' / Function | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | — |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | — |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 CheckboxConfig / CheckboxGroupConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#CheckboxConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#CheckboxGroupConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## CheckboxGroup Attributes
|
## CheckboxGroup Attributes
|
||||||
|
|
||||||
@ -167,9 +191,9 @@ options 支持函数形式,可根据表单状态动态生成选项。
|
|||||||
|------|------|------|--------|--------|
|
|------|------|------|--------|--------|
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| options | 选项列表 | Array / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | — |
|
| options | 选项列表 | Array / `FilterFunction` | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | — |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | — |
|
||||||
|
|
||||||
## options item
|
## options item
|
||||||
|
|
||||||
|
|||||||
@ -69,9 +69,31 @@
|
|||||||
|------|------|------|--------|--------|
|
|------|------|------|--------|--------|
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| defaultValue | 默认颜色值 | string | — | — |
|
| defaultValue | 默认颜色值 | string | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/master/packages/form-schema/src/base.ts) | — | — |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | — |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 ColorPickConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ColorPickConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 颜色格式说明
|
## 颜色格式说明
|
||||||
|
|
||||||
|
|||||||
@ -99,10 +99,34 @@
|
|||||||
| name | 绑定值的字段名 | string | — | — |
|
| name | 绑定值的字段名 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| format | 显示在输入框中的格式 | string | 见[日期格式](#日期格式) | YYYY/MM/DD |
|
| format | 显示在输入框中的格式 | string | 见[日期格式](#日期格式) | YYYY/MM/DD |
|
||||||
| valueFormat | 绑定值的格式。不指定则绑定值为 Date 对象 | string | 见[日期格式](#日期格式) | YYYY/MM/DD |
|
| valueFormat | 绑定值的格式。不指定则绑定值为 Date 对象 | string | 见[日期格式](#日期格式) | YYYY/MM/DD |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | — |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | — |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DateConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DateConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#Input{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## TypeScript 定义
|
## TypeScript 定义
|
||||||
|
|
||||||
|
|||||||
@ -41,9 +41,31 @@ type为'daterange'
|
|||||||
| name | 绑定值(数组形式) | string | — | — |
|
| name | 绑定值(数组形式) | string | — | — |
|
||||||
| names | 绑定值(拆分为两个字段) | string[] | — | — |
|
| names | 绑定值(拆分为两个字段) | string[] | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| dateFormat | 日期格式 | string | — | YYYY/MM/DD |
|
| dateFormat | 日期格式 | string | — | YYYY/MM/DD |
|
||||||
| timeFormat | 时间格式 | string | — | HH:mm:ss |
|
| timeFormat | 时间格式 | string | — | HH:mm:ss |
|
||||||
| valueFormat | 绑定值的格式 | string | — | YYYY/MM/DD HH:mm:ss |
|
| valueFormat | 绑定值的格式 | string | — | YYYY/MM/DD HH:mm:ss |
|
||||||
| defaultTime | 默认时间 | Date[] | — | — |
|
| defaultTime | 默认时间 | Date[] | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DaterangeConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DaterangeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -101,8 +101,32 @@
|
|||||||
| name | 绑定值的字段名 | string | — | — |
|
| name | 绑定值的字段名 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| format | 显示在输入框中的格式 | string | 见[日期格式](#日期格式) | YYYY/MM/DD HH:mm:ss |
|
| format | 显示在输入框中的格式 | string | 见[日期格式](#日期格式) | YYYY/MM/DD HH:mm:ss |
|
||||||
| valueFormat | 绑定值的格式 | string | 见[日期格式](#日期格式) | YYYY/MM/DD HH:mm:ss |
|
| valueFormat | 绑定值的格式 | string | 见[日期格式](#日期格式) | YYYY/MM/DD HH:mm:ss |
|
||||||
| defaultTime | 选择日期后的默认时间值 | Date | — | — |
|
| defaultTime | 选择日期后的默认时间值 | Date | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | — |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | — |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DateTimeConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DateTimeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#Input{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
用于显示,不可编辑
|
用于显示,不可编辑
|
||||||
|
|
||||||
## TS 定义
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface Display extends FormItem {
|
|
||||||
type: "display";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
点击查看[FormItem](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90)的定义
|
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
<demo-block type="form" :config="[{
|
<demo-block type="form" :config="[{
|
||||||
@ -33,3 +23,12 @@ interface Display extends FormItem {
|
|||||||
| ---- | -------- | ------ | ------ | ------ |
|
| ---- | -------- | ------ | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DisplayConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DisplayConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -50,3 +50,12 @@
|
|||||||
| name | 字段名 | string | — |
|
| name | 字段名 | string | — |
|
||||||
| label | 标签名 | string | — |
|
| label | 标签名 | string | — |
|
||||||
| defaultValue | 默认值 | any | — |
|
| defaultValue | 默认值 | any | — |
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 DynamicFieldConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#DynamicFieldConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -2,16 +2,6 @@
|
|||||||
|
|
||||||
改值体现于最终提交的表单中,用于例如编辑记录的id这种场景中
|
改值体现于最终提交的表单中,用于例如编辑记录的id这种场景中
|
||||||
|
|
||||||
## TS 定义
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface Hidden extends FormItem {
|
|
||||||
type: "hidden";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
点击查看[FormItem](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90)的定义
|
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
<demo-block type="form" :config="[{
|
<demo-block type="form" :config="[{
|
||||||
@ -30,3 +20,12 @@ interface Hidden extends FormItem {
|
|||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
| ---- | ------ | ------ | ------ | ------ |
|
| ---- | ------ | ------ | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 HiddenConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#HiddenConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -2,28 +2,6 @@
|
|||||||
|
|
||||||
用于显示,不可编辑
|
用于显示,不可编辑
|
||||||
|
|
||||||
## TS 定义
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface Link extends FormItem {
|
|
||||||
type: "link";
|
|
||||||
href?: string | typeof LinkHrefFunction;
|
|
||||||
css?: {
|
|
||||||
[key: string]: string | number;
|
|
||||||
};
|
|
||||||
disabledCss?: {
|
|
||||||
[key: string]: string | number;
|
|
||||||
};
|
|
||||||
formTitle?: string;
|
|
||||||
formWidth?: number | string;
|
|
||||||
displayText?: string | typeof LinkDisplayTextFunction;
|
|
||||||
form: FormConfig | typeof LinkFormFunction;
|
|
||||||
fullscreen?: boolean;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
点击查看[FormItem](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90)的定义
|
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
<demo-block type="form" :config="[{
|
<demo-block type="form" :config="[{
|
||||||
@ -63,3 +41,12 @@ interface Link extends FormItem {
|
|||||||
| ---- | -------- | ------ | ------ | ------ |
|
| ---- | -------- | ------ | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 LinkConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#LinkConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -23,6 +23,28 @@ type为'number-range'
|
|||||||
| --------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| --------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值(数组形式 [min, max]) | string | — | — |
|
| name | 绑定值(数组形式 [min, max]) | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| clearable | 是否可清空 | boolean | — | true |
|
| clearable | 是否可清空 | boolean | — | true |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 NumberRangeConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#NumberRangeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -58,9 +58,31 @@ disabled 属性接受一个 Boolean,设置为 true 即可禁用整个组件,
|
|||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| min | 设置计数器允许的最小值 | number | — | -Infinity |
|
| min | 设置计数器允许的最小值 | number | — | -Infinity |
|
||||||
| max | 设置计数器允许的最大值 | number | — | Infinity |
|
| max | 设置计数器允许的最大值 | number | — | Infinity |
|
||||||
| step | 计数器步长 | number | — | 1 |
|
| step | 计数器步长 | number | — | 1 |
|
||||||
| tooltip | 输入框提示信息 | string | — | — |
|
| tooltip | 输入框提示信息 | string | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 NumberConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#NumberConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -2,23 +2,6 @@
|
|||||||
|
|
||||||
在一组备选项中进行单选
|
在一组备选项中进行单选
|
||||||
|
|
||||||
## TS 定义
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
interface RadioGroup extends FormItem {
|
|
||||||
type: "radio-group";
|
|
||||||
childType?: "default" | "button";
|
|
||||||
options: {
|
|
||||||
value: any;
|
|
||||||
text?: string;
|
|
||||||
icon?: any;
|
|
||||||
tooltip?: string;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
点击查看[FormItem](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90)的定义
|
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
由于选项默认可见,不宜过多,若选项过多,建议使用 Select 选择器。
|
由于选项默认可见,不宜过多,若选项过多,建议使用 Select 选择器。
|
||||||
@ -68,10 +51,34 @@ interface RadioGroup extends FormItem {
|
|||||||
| --------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------- |
|
| --------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------- |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| childType | 子项展示形式 | string | default / button | default |
|
| childType | 子项展示形式 | string | default / button | default |
|
||||||
| options | 选项 | Array | — | - |
|
| options | 选项 | Array | — | - |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler ](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FormItem / FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 RadioGroupConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#RadioGroupConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## options item
|
## options item
|
||||||
|
|
||||||
|
|||||||
@ -188,16 +188,40 @@ app.use(MagicForm, {
|
|||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| multiple | 是否多选 | boolean | — | false |
|
| multiple | 是否多选 | boolean | — | false |
|
||||||
| valueKey | 作为 value 唯一标识的键名,绑定值为对象类型时必填 | string | — | value |
|
| valueKey | 作为 value 唯一标识的键名,绑定值为对象类型时必填 | string | — | value |
|
||||||
| allowCreate | 是否允许用户创建新条目 | boolean | — | false |
|
| allowCreate | 是否允许用户创建新条目 | boolean | — | false |
|
||||||
| remote | 是否为远程搜索 | boolean | — | false |
|
| remote | 是否为远程搜索 | boolean | — | false |
|
||||||
| group | 是否选择分组 | boolean | — | false |
|
| group | 是否选择分组 | boolean | — | false |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler ](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
| options | 选项 | Array | — | - |
|
| options | 选项 | Array | — | - |
|
||||||
| option | 选项 | Object | — | - |
|
| option | 选项 | Object | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 SelectConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#SelectConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#Input{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## options item
|
## options item
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -46,6 +46,20 @@
|
|||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
| -------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------ | ------ |
|
||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [Function](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| active-value | switch 打开时的值 | boolean / string / number | — | true |
|
| active-value | switch 打开时的值 | boolean / string / number | — | true |
|
||||||
| inactive-value | switch 关闭时的值 | boolean / string / number | — | false |
|
| inactive-value | switch 关闭时的值 | boolean / string / number | — | false |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 SwitchConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#SwitchConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -119,14 +119,40 @@ Input输入框的type为'text', 是type的默认值,所以可以不配置
|
|||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| clearable | 是否可清空 | boolean | — | true |
|
| clearable | 是否可清空 | boolean | — | true |
|
||||||
| tooltip | 输入时显示内容 | string / [ToolTipConfigType](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90) | — | — |
|
| tooltip | 输入时显示内容 | string / `ToolTipConfigType` | — | — |
|
||||||
| trim | 是否去掉首尾空格 | boolean | — | false |
|
| trim | 是否去掉首尾空格 | boolean | — | false |
|
||||||
| filter | 过滤值 | string / Function | number | - |
|
| filter | 过滤值 | string / Function | number | - |
|
||||||
| prepend | 前置内容 | string | — | - |
|
| prepend | 前置内容 | string | — | - |
|
||||||
| append | 后置内容 | string / Object | — | - |
|
| append | 后置内容 | string / Object | — | - |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler / ToolTipConfigType 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ToolTipConfigType{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 TextConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TextConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#Input{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## append Attributes
|
## append Attributes
|
||||||
|
|
||||||
|
|||||||
@ -38,8 +38,30 @@
|
|||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| trim | 是否去掉首尾空格 | boolean | — | false |
|
| trim | 是否去掉首尾空格 | boolean | — | false |
|
||||||
| filter | 过滤值 | string / Function | number | - |
|
| filter | 过滤值 | string / Function | number | - |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler ](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 TextareaConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TextareaConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -38,4 +38,20 @@
|
|||||||
| name | 绑定值 | string | — | — |
|
| name | 绑定值 | string | — | — |
|
||||||
| placeholder | 输入框占位文本 | string | — | — |
|
| placeholder | 输入框占位文本 | string | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [Function](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L90) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 TimeConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TimeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#Input{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -41,8 +41,30 @@ type为'timerange'
|
|||||||
| name | 绑定值(数组形式) | string | — | — |
|
| name | 绑定值(数组形式) | string | — | — |
|
||||||
| names | 绑定值(拆分为两个字段) | string[] | — | — |
|
| names | 绑定值(拆分为两个字段) | string[] | — | — |
|
||||||
| text | 表单标签 | string | — | — |
|
| text | 表单标签 | string | — | — |
|
||||||
| disabled | 是否禁用 | boolean / [FilterFunction](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L195) | — | false |
|
| disabled | 是否禁用 | boolean / `FilterFunction` | — | false |
|
||||||
| format | 显示格式 | string | — | HH:mm:ss |
|
| format | 显示格式 | string | — | HH:mm:ss |
|
||||||
| valueFormat | 绑定值的格式 | string | — | HH:mm:ss |
|
| valueFormat | 绑定值的格式 | string | — | HH:mm:ss |
|
||||||
| defaultTime | 默认时间 | Date[] | — | — |
|
| defaultTime | 默认时间 | Date[] | — | — |
|
||||||
| onChange | 值变化时触发的函数 | [OnChangeHandler](https://github.com/Tencent/tmagic-editor/blob/cce8b63fc3618b5b811aa33c703de21c22be8a6a/packages/form-schema/src/base.ts#L30) | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
|
|
||||||
|
::: details 查看 FilterFunction / OnChangeHandler 及关联类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FilterFunction{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandler{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#OnChangeHandlerData{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 TimerangeConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TimerangeConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|||||||
@ -1,5 +1,36 @@
|
|||||||
# 布局
|
# 布局
|
||||||
|
|
||||||
|
## 配置类型
|
||||||
|
|
||||||
|
::: details 查看 ContainerCommonConfig / RowConfig / TabConfig / TabPaneConfig / FieldsetConfig / PanelConfig / StepConfig / FlexLayoutConfig / GroupListConfig / TableConfig / TableColumnConfig / TableGroupListCommonConfig 配置类型定义
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#ContainerCommonConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#RowConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TabConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TabPaneConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FieldsetConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#PanelConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#StepConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FlexLayoutConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#GroupListConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TableConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TableColumnConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#TableGroupListCommonConfig{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/form-schema/src/base.ts#FormItem{ts}
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
## 基础用法
|
## 基础用法
|
||||||
|
|
||||||
<demo-block type="form" :config="[{
|
<demo-block type="form" :config="[{
|
||||||
@ -104,6 +135,18 @@
|
|||||||
}]
|
}]
|
||||||
}]"></demo-block>
|
}]"></demo-block>
|
||||||
|
|
||||||
|
`legend` 除了支持字符串,也支持函数,函数返回值作为标题展示,可根据表单数据动态生成:
|
||||||
|
|
||||||
|
<demo-block type="form" :config="[{
|
||||||
|
type: 'fieldset',
|
||||||
|
labelWidth: '100px',
|
||||||
|
legend: (mForm, { formValue }) => `当前值:${formValue.text || '空'}`,
|
||||||
|
items: [{
|
||||||
|
name: 'text',
|
||||||
|
text: '配置1',
|
||||||
|
}]
|
||||||
|
}]"></demo-block>
|
||||||
|
|
||||||
### panel
|
### panel
|
||||||
|
|
||||||
<demo-block type="form" :config="[{
|
<demo-block type="form" :config="[{
|
||||||
|
|||||||
@ -30,7 +30,7 @@ tmagic-editor的联动,指这两种情况:
|
|||||||
当然我们也可以通过上述的参数传入,以及其他函数 API 实现更多灵活的表单联动,具体参考[表单 API](../../form-config/relate)。
|
当然我们也可以通过上述的参数传入,以及其他函数 API 实现更多灵活的表单联动,具体参考[表单 API](../../form-config/relate)。
|
||||||
|
|
||||||
## 组件联动
|
## 组件联动
|
||||||
tmagic-editor在 @tmagic/core 中,实现了组件的事件绑定/分发机制。在组件渲染时,每个组件在 @tmagic/ui 中经过基础组件渲染时,会被基础组件注入公共方法的实现。如下对按钮配置了**点击使文本隐藏**的联动事件,那么在对应按钮被点击时,将会触发对应绑定文本的隐藏。
|
tmagic-editor在 `@tmagic/core` 中,实现了组件的事件绑定/分发机制。在组件渲染时,每个组件在经过 `@tmagic/vue-container`(vue 端)或 `@tmagic/react-container`(react 端)等基础渲染组件渲染时,会被基础组件注入公共方法的实现。如下对按钮配置了**点击使文本隐藏**的联动事件,那么在对应按钮被点击时,将会触发对应绑定文本的隐藏。
|
||||||
|
|
||||||
<img src="https://image.video.qpic.cn/oa_88b7d-10_2117738923_1637238863127559">
|
<img src="https://image.video.qpic.cn/oa_88b7d-10_2117738923_1637238863127559">
|
||||||
|
|
||||||
|
|||||||
134
docs/guide/advanced/history-list.md
Normal file
134
docs/guide/advanced/history-list.md
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# 历史记录面板
|
||||||
|
|
||||||
|
编辑器内置了一个可视化的「历史记录面板」,用于查看与回溯编辑过程中产生的所有操作。相比顶部菜单栏只能「撤销 / 重做」相邻一步,历史记录面板提供了对整条历史栈的全局视角:可以按页面、数据源、代码块分类浏览,点击任意一步直接跳转,查看每一步的前后差异,甚至像 `git revert` 一样单独回滚某一步而不破坏后续操作。
|
||||||
|
|
||||||
|
## 开启面板
|
||||||
|
|
||||||
|
历史记录面板以一个内置菜单项 `'history-list'` 的形式提供,将它加入 [`menu`](/api/editor/props.html#menu) 配置即可在顶部工具栏出现一个时钟图标,点击展开面板:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<m-editor :menu="menu"></m-editor>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
const menu = ref({
|
||||||
|
left: [],
|
||||||
|
center: ['delete', 'undo', 'redo', '/', 'history-list'],
|
||||||
|
right: [],
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 面板结构
|
||||||
|
|
||||||
|
面板分为三个 tab,分别对应三类可被历史记录追踪的对象,tab 标题后的数字为各自的分组数量:
|
||||||
|
|
||||||
|
| Tab | 内容 | 跳转 API |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 页面 | 当前活动页面的节点操作历史 | `editorService.gotoPageStep(cursor)` |
|
||||||
|
| 数据源 | 按 `dataSource.id` 分组的数据源变更历史 | `dataSourceService.goto(id, cursor)` |
|
||||||
|
| 代码块 | 按 `codeBlock.id` 分组的代码块变更历史 | `codeBlockService.goto(id, cursor)` |
|
||||||
|
|
||||||
|
### 相邻同目标自动合并
|
||||||
|
|
||||||
|
为了避免「连续微调同一个节点 / 数据源 / 代码块」时产生大量碎片化记录,面板会把**相邻的、针对同一目标的连续 `update`** 自动合并成一个分组:
|
||||||
|
|
||||||
|
- 页面 tab:连续修改同一节点(按节点 id 判定)的多步合并为一组,点击组头部可展开查看每一子步;
|
||||||
|
- 数据源 / 代码块 tab:相邻的连续 `update` 按目标 id 合并;`add` / `remove` 始终独立成组(语义上是一次性事件)。
|
||||||
|
|
||||||
|
> 合并仅作用于展示与交互,不改变底层 undo/redo 栈的真实结构。
|
||||||
|
|
||||||
|
## 交互能力
|
||||||
|
|
||||||
|
每个分组 / 步骤支持以下操作:
|
||||||
|
|
||||||
|
### 1. 点击跳转
|
||||||
|
|
||||||
|
点击任意一条记录,编辑器会跳转到「应用至该步完成」的状态。其本质是把对应栈的游标(cursor)移动到 `step.index + 1`,由 service 层的 undo/redo 链路完成中间步骤的批量正向 / 反向应用。
|
||||||
|
|
||||||
|
### 2. 回到初始状态
|
||||||
|
|
||||||
|
每个 tab 列表底部提供「回到初始状态」入口,等价于把对应栈游标移到 `0`(所有真实步骤全部撤销)。
|
||||||
|
|
||||||
|
### 3. 单步回滚(类 git revert)
|
||||||
|
|
||||||
|
对于历史中间的某一步,可以单独「回滚」它,而保留它之后的所有操作。该行为不会倒带游标,而是把目标步骤的修改**反向应用为一次全新的操作**并压入栈顶,因此不会破坏既有历史结构:
|
||||||
|
|
||||||
|
- 页面:`editorService.revertPageStep(index)`
|
||||||
|
- 数据源:`dataSourceService.revert(id, index)`
|
||||||
|
- 代码块:`codeBlockService.revert(id, index)`
|
||||||
|
|
||||||
|
如果业务侧在执行操作时已通过 `*AndGetHistoryId` 拿到了该条记录的 [uuid](/api/editor/editorServiceMethods.md#历史记录-uuid-与-andgethistoryid),也可以直接按 uuid 回滚(无需再关心 index / id,且 uuid 不会随栈内步骤增删而变化):
|
||||||
|
|
||||||
|
- 页面:`editorService.revertPageStepById(uuid)`
|
||||||
|
- 数据源:`dataSourceService.revertById(uuid)`
|
||||||
|
- 代码块:`codeBlockService.revertById(uuid)`
|
||||||
|
|
||||||
|
### 4. 差异对比
|
||||||
|
|
||||||
|
在前后值都存在的 `update` 步骤上提供「查看差异」入口,点击后弹出差异对话框。对话框支持两个维度的切换:
|
||||||
|
|
||||||
|
- **对比对象**
|
||||||
|
- `与修改前对比`:该步骤修改前 vs 修改后(默认,体现这一步带来的变化);
|
||||||
|
- `与当前对比`:该步骤修改后 vs 编辑器中的最新值(用于确认「这一步之后是否又被改动过」,当前值缺失时禁用)。
|
||||||
|
- **展示形态**
|
||||||
|
- `表单对比`:以属性表单形式逐字段对比,可读性更好(基于 [表单对比](/form-config/compare.md) 能力);
|
||||||
|
- `源码对比`:以 JSON 源码做整体 diff(基于 monaco diff 编辑器),可以看到表单未覆盖到的字段。
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
表单对比依赖 `@tmagic/form` 的对比模式(`isCompare` / `lastValues`)。对于 `event-select`、`code-select`、`code-select-col` 等由列表或嵌套子表单组成的复合字段,表单会逐项展示新增 / 删除 / 修改的高亮差异,并在对比模式下隐藏「添加 / 删除 / 编辑」等写操作按钮,仅保留只读展示。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 扩展自定义 tab
|
||||||
|
|
||||||
|
内置的三个 tab 之外,业务方可以通过 Editor 的 [`historyListExtraTabs`](/api/editor/props.html#historylistextratabs) 在面板中追加自定义的历史 tab,追加在「页面 / 数据源 / 代码块」之后。适用于某个自定义模块维护自己的操作历史,需要在历史记录面板中独立展示与回滚的场景。
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<m-editor :menu="menu" :history-list-extra-tabs="historyListExtraTabs"></m-editor>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { markRaw } from 'vue';
|
||||||
|
|
||||||
|
import MyModuleHistoryTab from './MyModuleHistoryTab.vue';
|
||||||
|
|
||||||
|
const historyListExtraTabs = [
|
||||||
|
{
|
||||||
|
name: 'my-module',
|
||||||
|
// label 支持字符串或函数,函数形式便于展示动态数量
|
||||||
|
label: () => `我的模块 (${getMyModuleHistory().length})`,
|
||||||
|
component: markRaw(MyModuleHistoryTab),
|
||||||
|
props: { foo: 'bar' },
|
||||||
|
listeners: {
|
||||||
|
goto: (cursor) => console.log(cursor),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
每个扩展 tab 的字段说明:
|
||||||
|
|
||||||
|
| 字段 | 必填 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `name` | 是 | tab 唯一标识,作为内部 `TMagicTabs` 的 `name` |
|
||||||
|
| `label` | 是 | tab 显示文案,支持字符串或返回字符串的函数(便于展示动态数量) |
|
||||||
|
| `component` | 是 | tab 内容区渲染的组件 |
|
||||||
|
| `props` | 否 | 传入内容组件的 props |
|
||||||
|
| `listeners` | 否 | 内容组件的事件监听 |
|
||||||
|
|
||||||
|
> 内容组件内部可自行通过 `useServices()` 拿到 `historyService` 等服务,读取并回滚自定义模块自己维护的历史。
|
||||||
|
|
||||||
|
## 自定义对比判断
|
||||||
|
|
||||||
|
差异对话框中的「表单对比」最终透传到 `MForm`,你可以通过 Editor 顶层注入的 `extendFormState` 让对比表单拿到完整业务上下文,从而让依赖上下文的 `display` / `disabled` 等 `filterFunction` 正常工作。
|
||||||
|
|
||||||
|
若某些字段语义上相等但结构不同(例如 `code-select` 字段中 `''` 与 `{ hookType: 'code', hookData: [] }` 应视为相等),可借助 `@tmagic/form` 的 [`showDiff`](/api/form/form-props.html#showdiff) 自定义判断函数避免被误判为差异。
|
||||||
|
|
||||||
|
## 相关 API
|
||||||
|
|
||||||
|
历史面板的数据均来自 `historyService` 暴露的聚合方法,详见 [historyService 方法](/api/editor/historyServiceMethods.md)。
|
||||||
@ -1,5 +1,5 @@
|
|||||||
# 页面渲染
|
# 页面渲染
|
||||||
tmagic-editor的页面渲染,是通过在载入编辑器中保存的 DSL 配置,通过 ui 渲染器渲染页面。在容器布局原理里我们提到过,容器和组件在配置中呈树状结构,所以渲染页面的时候,渲染器会递归配置内容,从而渲染出页面所有组件。
|
tmagic-editor的页面渲染,是通过在载入编辑器中保存的 DSL 配置,通过基础渲染组件(vue 下为 `@tmagic/vue-container`,react 下为 `@tmagic/react-container`)渲染页面。在容器布局原理里我们提到过,容器和组件在配置中呈树状结构,所以渲染页面的时候,渲染器会递归配置内容,从而渲染出页面所有组件。
|
||||||
|
|
||||||
<img src="https://vfiles.gtimg.cn/vupload/20211009/f4d3031633778551251.png">
|
<img src="https://vfiles.gtimg.cn/vupload/20211009/f4d3031633778551251.png">
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ export default {
|
|||||||
```
|
```
|
||||||
|
|
||||||
## 组件渲染
|
## 组件渲染
|
||||||
所有tmagic-editor组件,都通过一个tmagic-editor基础组件来渲染。这个基础组件会识别当前渲染组件的类型。如果当前渲染组件是普通组件(包括ui中提供的基础组件和业务开发的业务组件),则直接渲染;如果当前渲染组件是容器,则回到[容器渲染](#容器渲染)逻辑中。
|
所有tmagic-editor组件,都通过一个tmagic-editor基础组件来渲染。这个基础组件会识别当前渲染组件的类型。如果当前渲染组件是普通组件(包括 `vue-components` / `react-components` 中提供的基础组件和业务开发的业务组件),则直接渲染;如果当前渲染组件是容器,则回到[容器渲染](#容器渲染)逻辑中。
|
||||||
|
|
||||||
基础组件的具体形式为:
|
基础组件的具体形式为:
|
||||||
```vue
|
```vue
|
||||||
@ -59,6 +59,6 @@ export default defineComponent({
|
|||||||
```
|
```
|
||||||
|
|
||||||
## 渲染器示例
|
## 渲染器示例
|
||||||
在tmagic-editor的示例项目中,我们提供了三个版本的 @tmagic/ui。可以参考对应前端框架的渲染器实现。
|
在tmagic-editor的示例项目中,我们针对 vue 和 react 分别提供了基础渲染组件的实现,可以参考对应前端框架的渲染器实现。
|
||||||
- [vue 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/vue-components/container/src/Container.vue)
|
- [vue 渲染器(`@tmagic/vue-container`)](https://github.com/Tencent/tmagic-editor/blob/master/vue-components/container/src/Container.vue)
|
||||||
- [react 渲染器](https://github.com/Tencent/tmagic-editor/blob/master/react-components/container/src/Container.tsx)
|
- [react 渲染器(`@tmagic/react-container`)](https://github.com/Tencent/tmagic-editor/blob/master/react-components/container/src/Container.tsx)
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# @tmagic/ui
|
|
||||||
在前面[页面渲染](../advanced/page)中提到的 UI 渲染器,就是包含在 @tmagic/ui 中的渲染器组件。
|
|
||||||
|
|
||||||
tmagic-editor的设计是希望发布的页面支持多个前端框架,即各个业务方可以根据自己熟悉的语言来开发组件、发布页面。也可以通过 [实现一个 runtime](../runtime.html) 的方式,来实现一个自己的 @tmagic/ui。
|
|
||||||
|
|
||||||
所以tmagic-editor的设计中,针对每个前端框架,都需要有一个对应的 @tmagic/ui 来承担渲染器职责。同时,也需要一个使用和 @tmagic/ui 相同前端框架的 runtime 用来加载 vue-components 和业务组件,具体 runtime 概念,可以参考[页面发布](../publish)。
|
|
||||||
|
|
||||||
我们以项目代码中提供的 vue 版本的 vue-components 作为示例介绍其中包含的内容(参考 `vue-components/` 目录下的源码)。
|
|
||||||
|
|
||||||
## 渲染器
|
|
||||||
在 vue 中,实现渲染器的具体形式参考[页面渲染](../advanced/page)中描述的[容器渲染](../advanced/page.html#容器渲染)和[组件渲染](../advanced/page.html#组件渲染)。
|
|
||||||
|
|
||||||
## 基础组件
|
|
||||||
在 vue-components 中,我们提供了几个基础组件,可以在项目源码中找到对应内容。
|
|
||||||
|
|
||||||
- page tmagic-editor的页面基础
|
|
||||||
- container tmagic-editor的容器渲染器
|
|
||||||
- Component.vue tmagic-editor的组件渲染器
|
|
||||||
- button/text 基础组件示例
|
|
||||||
|
|
||||||
其中 page/container/Component 是 UI 的基础,是每个框架的 UI 都应该实现的。
|
|
||||||
|
|
||||||
button/text 其实就是一个组件开发的示例,具体组件开发相关规范可以参考[组件开发](../component)。
|
|
||||||
@ -1,6 +1,15 @@
|
|||||||
# 快速开始
|
# 快速开始
|
||||||
|
|
||||||
tmagic-editor的编辑器我们已经封装成一个 npm 包,可以直接安装使用。编辑器是使用 vue3 开发的(仅支持vue3),但使用编辑器的业务(runtime)可以不限框架,可以用 vue2、react 等开发业务组件。
|
tmagic-editor 的编辑器已经封装成 npm 包,可以直接安装使用。编辑器使用 Vue 3 开发(**仅支持 Vue 3**),但承载真实业务的 runtime 不限框架,可以使用 Vue 2、Vue 3、React 等开发业务组件。
|
||||||
|
|
||||||
|
整个项目结构由两部分组成:
|
||||||
|
|
||||||
|
- **admin-client**(编辑器 / 管理端):基于 `@tmagic/editor`,加载 runtime iframe、提供拖拽/属性配置/发布等能力。
|
||||||
|
- **runtime**(运行时):负责解析 DSL 并渲染页面,分为编辑器内嵌的 `playground` 和线上发布使用的 `page` 两个产物。
|
||||||
|
|
||||||
|
> 仓库 [`playground/`](https://github.com/Tencent/tmagic-editor/tree/master/playground) 与 [`runtime/vue/`](https://github.com/Tencent/tmagic-editor/tree/master/runtime/vue) 就是一份完整可运行的最小实践,本节内容均与之对齐,可以对照阅读源码。
|
||||||
|
|
||||||
|
## 使用脚手架创建(推荐)
|
||||||
|
|
||||||
::: code-group
|
::: code-group
|
||||||
|
|
||||||
@ -11,220 +20,423 @@ $ npm create tmagic@latest
|
|||||||
```bash [pnpm]
|
```bash [pnpm]
|
||||||
$ pnpm create tmagic
|
$ pnpm create tmagic
|
||||||
```
|
```
|
||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
按照提示操作可以创建`6`种项目:
|
按照交互式提示,可以创建以下 `6` 种项目:
|
||||||
|
|
||||||
* runtime:运行时(DSL渲染)
|
| 类型 | 说明 |
|
||||||
* admin-client:管理端(编辑器)
|
| -------------- | ------------------------------ |
|
||||||
* components:组件库(组件/插件/数据源)
|
| `runtime` | 运行时(DSL 渲染) |
|
||||||
* component:组件
|
| `admin-client` | 管理端(编辑器) |
|
||||||
* data-source:数据源
|
| `components` | 组件库(组件 / 插件 / 数据源) |
|
||||||
* plugin:插件
|
| `component` | 单个组件 |
|
||||||
|
| `data-source` | 单个数据源 |
|
||||||
|
| `plugin` | 单个插件 |
|
||||||
|
|
||||||
至少需要一个runtime与admin-client后,就可以运行起一个最简单的项目了。
|
最少需要一个 `runtime` 加一个 `admin-client`,就能跑起一个完整的可视化搭建流程。后续可以再陆续创建组件、插件、数据源;新建好后到 `runtime/tmagic.config.ts` 的 `packages` 中注册即可,参考[组件开发](./component.md) 与[页面发布 § @tmagic/cli](./publish.md#tmagic-cli)。
|
||||||
|
|
||||||
后续还需要新增组件、插件、数据源等,可以继续添加后面几种类型的项目。
|
|
||||||
|
|
||||||
新增好一个组件/插件/数据源后可以到runtime/tmagic.config.ts中配置到packages中
|
|
||||||
|
|
||||||
## 手动安装
|
## 手动安装
|
||||||
|
|
||||||
node.js >= 18
|
::: tip 环境要求
|
||||||
|
|
||||||
可以通过[Vite](https://cn.vitejs.dev/) 或 [Vue CLI](https://cli.vuejs.org/zh/)快速创建项目。
|
- Node.js `^20.19.0 || >=22.12.0`
|
||||||
|
- 推荐使用 [Vite](https://cn.vitejs.dev/);如果使用 [Vue CLI](https://cli.vuejs.org/zh/) 需要在 `vue.config.js` 中加上 `transpileDependencies: [/@tmagic/]`
|
||||||
|
:::
|
||||||
|
|
||||||
> 使用Vue CLI生成的项目需要在vue.config.js中加上配置:transpileDependencies: [/@tmagic/]
|
### 1. 安装编辑器依赖
|
||||||
|
|
||||||
|
`@tmagic/editor` 把内部使用到的 UI 组件抽象到了 `@tmagic/design`,通过 **adapter** 的形式接入具体的 UI 组件库。我们提供了:
|
||||||
|
|
||||||
|
- [`@tmagic/element-plus-adapter`](https://github.com/Tencent/tmagic-editor/tree/master/packages/element-plus-adapter):接入 [Element Plus](https://element-plus.org/)
|
||||||
|
- [`@tmagic/tdesign-vue-next-adapter`](https://github.com/Tencent/tmagic-editor/tree/master/packages/tdesign-vue-next-adapter):接入 [TDesign Vue Next](https://tdesign.tencent.com/vue-next/overview)
|
||||||
|
|
||||||
|
任选其一即可,下面以 Element Plus 为例:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm install @tmagic/editor -S
|
$ npm install @tmagic/editor @tmagic/core @tmagic/element-plus-adapter element-plus -S
|
||||||
```
|
```
|
||||||
|
|
||||||
由于在实际应用中项目常常会用到例如[element-plus](https://element-plus.org/)、[tdesign-vue-next](https://tdesign.tencent.com/vue-next/overview)等UI组件库。为了能让使用者能够选择不同UI库,[@tmagic/editor](https://github.com/Tencent/tmagic-editor/tree/master/packages/editor)将其中使用到的UI组件封装到[@tmagic/design](https://github.com/Tencent/tmagic-editor/tree/master/packages/design)中,然后通过不同的adapter来指定使用具体的对应的UI库,我们提供了[@tmagic/element-plus-adapter](https://github.com/Tencent/tmagic-editor/tree/master/packages/element-plus-adapter)来支持[element-plus](https://element-plus.org/),所以还需要安装相关的依赖。
|
`@tmagic/editor` 内部使用了 [monaco-editor](https://microsoft.github.io/monaco-editor/) 作为代码编辑器,需要额外安装并按照官方[配置指引](https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md)注入 worker:
|
||||||
|
|
||||||
```bash
|
|
||||||
$ npm install @tmagic/element-plus-adapter element-plus -S
|
|
||||||
```
|
|
||||||
|
|
||||||
editor 中还包含了[monaco-editor](https://microsoft.github.io/monaco-editor/),所以还需安装monaco-editor,可以参考 monaco-editor 的[配置指引](https://github.com/microsoft/monaco-editor/blob/main/docs/integrate-esm.md)。
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm install monaco-editor -S
|
$ npm install monaco-editor -S
|
||||||
```
|
```
|
||||||
|
|
||||||
## 快速上手
|
### 2. 引入 @tmagic/editor
|
||||||
|
|
||||||
## 引入 @tmagic/editor
|
参考 [`playground/src/main.ts`](https://github.com/Tencent/tmagic-editor/blob/master/playground/src/main.ts),在入口文件中按以下顺序完成 Monaco worker、UI 库样式、editor 样式与 adapter 的注入:
|
||||||
|
|
||||||
在 main.js 中写入以下内容:
|
```ts
|
||||||
|
import { createApp } from "vue";
|
||||||
|
import EditorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
|
||||||
|
import CssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
|
||||||
|
import HtmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
|
||||||
|
import JsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
|
||||||
|
import TsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
|
||||||
|
|
||||||
```js
|
import editorPlugin from "@tmagic/editor";
|
||||||
import { createApp } from 'vue';
|
import MagicElementPlusAdapter from "@tmagic/element-plus-adapter";
|
||||||
import ElementPlus from 'element-plus';
|
|
||||||
import zhCn from 'element-plus/es/locale/lang/zh-cn';
|
|
||||||
|
|
||||||
import editorPlugin from '@tmagic/editor';
|
import App from "./App.vue";
|
||||||
import MagicElementPlusAdapter from '@tmagic/element-plus-adapter';
|
|
||||||
|
|
||||||
import App from './App.vue';
|
import "element-plus/dist/index.css";
|
||||||
|
import "@tmagic/editor/dist/style.css";
|
||||||
|
|
||||||
import 'element-plus/dist/index.css';
|
// @ts-ignore
|
||||||
import '@tmagic/editor/dist/style.css';
|
globalThis.MonacoEnvironment = {
|
||||||
|
getWorker(_: any, label: string) {
|
||||||
|
if (label === "json") return new JsonWorker();
|
||||||
|
if (["css", "scss", "less"].includes(label)) return new CssWorker();
|
||||||
|
if (["html", "handlebars", "razor"].includes(label))
|
||||||
|
return new HtmlWorker();
|
||||||
|
if (["typescript", "javascript"].includes(label)) return new TsWorker();
|
||||||
|
return new EditorWorker();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const app = createApp(App);
|
createApp(App).use(editorPlugin, MagicElementPlusAdapter).mount("#app");
|
||||||
app.use(ElementPlus, {
|
|
||||||
locale: zhCn,
|
|
||||||
});
|
|
||||||
app.use(editorPlugin, MagicElementPlusAdapter);
|
|
||||||
app.mount('#app');
|
|
||||||
```
|
```
|
||||||
|
|
||||||
以上代码便完成了 @tmagic/editor 的引入。需要注意的是,样式文件需要单独引入。
|
::: tip 切换 UI 适配器
|
||||||
|
playground 通过 `sessionStorage` 来切换 adapter,参考实现:
|
||||||
|
|
||||||
可以参考我们提供的[Playground](https://github.com/Tencent/tmagic-editor/blob/master/playground/src/main.ts)示例实现代码
|
```ts
|
||||||
|
const adapter =
|
||||||
|
sessionStorage.getItem("tmagic-playground-ui-adapter") || "element-plus";
|
||||||
|
const adapterModule =
|
||||||
|
adapter === "tdesign-vue-next"
|
||||||
|
? import("@tmagic/tdesign-vue-next-adapter")
|
||||||
|
: import("@tmagic/element-plus-adapter");
|
||||||
|
```
|
||||||
|
|
||||||
## 使用 m-editor 组件
|
:::
|
||||||
|
|
||||||
在 App.vue 中写入以下内容:
|
::: tip 常见报错
|
||||||
|
|
||||||
```html
|
1. `Preprocessor dependency "sass" not found.` —— 安装 sass:`npm i sass -D`
|
||||||
|
2. `Uncaught ReferenceError: global is not defined` —— Vite 项目需要在 `vite.config.ts` 中加上:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite 8以下版本
|
||||||
|
optimizeDeps: {
|
||||||
|
esbuildOptions: {
|
||||||
|
define: { global: 'globalThis' },
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// vite 8及以上
|
||||||
|
define: {
|
||||||
|
global: 'globalThis',
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
### 3. 渲染 m-editor
|
||||||
|
|
||||||
|
在 `App.vue` 中渲染 `<TMagicEditor />`(即 `m-editor` 组件),最少需要传入 `v-model`、`runtime-url`、`component-group-list`、`props-configs`、`props-values` 五个核心属性:
|
||||||
|
|
||||||
|
```vue
|
||||||
<template>
|
<template>
|
||||||
<m-editor
|
<div class="editor-app">
|
||||||
v-model="dsl"
|
<TMagicEditor
|
||||||
:menu="menu"
|
v-model="value"
|
||||||
:runtime-url="runtimeUrl"
|
ref="editor"
|
||||||
:props-configs="propsConfigs"
|
:menu="menu"
|
||||||
:props-values="propsValues"
|
:runtime-url="runtimeUrl"
|
||||||
:component-group-list="componentGroupList"
|
:props-configs="propsConfigs"
|
||||||
>
|
:props-values="propsValues"
|
||||||
</m-editor>
|
:event-method-list="eventMethodList"
|
||||||
|
:datasource-configs="datasourceConfigs"
|
||||||
|
:datasource-values="datasourceValues"
|
||||||
|
:datasource-event-method-list="datasourceEventMethodList"
|
||||||
|
:component-group-list="componentGroupList"
|
||||||
|
:default-selected="defaultSelected"
|
||||||
|
:stage-rect="stageRect"
|
||||||
|
:auto-scroll-into-view="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref } from "vue";
|
import { ref, shallowRef } from "vue";
|
||||||
|
import type { MApp } from "@tmagic/core";
|
||||||
|
import { TMagicEditor } from "@tmagic/editor";
|
||||||
|
|
||||||
export default defineComponent({
|
import componentGroupList from "./configs/componentGroupList";
|
||||||
name: "App",
|
import dsl from "./configs/dsl";
|
||||||
|
import { useEditorRes } from "./composables/use-editor-res";
|
||||||
|
|
||||||
setup() {
|
const editor = shallowRef<InstanceType<typeof TMagicEditor>>();
|
||||||
return {
|
const value = ref<MApp>(dsl);
|
||||||
menu: ref({
|
const defaultSelected = ref(dsl.items[0].id);
|
||||||
left: [
|
const stageRect = ref({ width: 375, height: 817 });
|
||||||
// 顶部左侧菜单按钮
|
|
||||||
],
|
|
||||||
center: [
|
|
||||||
// 顶部中间菜单按钮
|
|
||||||
],
|
|
||||||
right: [
|
|
||||||
// 顶部右侧菜单按钮
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
|
|
||||||
dsl: ref({
|
const { VITE_RUNTIME_PATH } = import.meta.env;
|
||||||
// 初始化页面数据
|
const runtimeUrl = `${VITE_RUNTIME_PATH}/playground/index.html`;
|
||||||
}),
|
|
||||||
|
|
||||||
runtimeUrl: "/runtime/vue/playground/index.html",
|
const {
|
||||||
|
propsValues,
|
||||||
|
propsConfigs,
|
||||||
|
eventMethodList,
|
||||||
|
datasourceConfigs,
|
||||||
|
datasourceValues,
|
||||||
|
datasourceEventMethodList,
|
||||||
|
} = useEditorRes();
|
||||||
|
|
||||||
propsConfigs: [
|
const menu = {
|
||||||
// 组件属性列表
|
left: [{ type: "text", text: "魔方" }],
|
||||||
],
|
center: ["delete", "undo", "redo", "guides", "rule", "zoom"],
|
||||||
propsValues: [
|
right: [
|
||||||
// 组件默认值
|
{
|
||||||
],
|
type: "button",
|
||||||
|
text: "保存",
|
||||||
componentGroupList: ref([
|
handler: () =>
|
||||||
// 组件列表
|
localStorage.setItem("magicDSL", JSON.stringify(value.value)),
|
||||||
]),
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
});
|
],
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
html, body {
|
html,
|
||||||
width: 100%;
|
body,
|
||||||
height: 100%;
|
#app {
|
||||||
margin: 0;
|
width: 100%;
|
||||||
padding: 0;
|
height: 100%;
|
||||||
overflow: hidden;
|
margin: 0;
|
||||||
}
|
padding: 0;
|
||||||
|
}
|
||||||
#app {
|
.editor-app {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
}
|
||||||
}
|
.editor-app .m-editor {
|
||||||
|
flex: 1;
|
||||||
.m-editor {
|
height: 100%;
|
||||||
flex: 1;
|
}
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
```
|
```
|
||||||
|
|
||||||
关于 [@tmagic/editor](https://github.com/Tencent/tmagic-editor/tree/master/packages/editor) 组件,更多的属性配置详情请参考[编辑器 API](../api/editor/props.md)。
|
完整的菜单/预览/键盘快捷键实现可以参考 [`playground/src/pages/Editor.vue`](https://github.com/Tencent/tmagic-editor/blob/master/playground/src/pages/Editor.vue)。
|
||||||
|
|
||||||
其中,**有四个需要注意的属性配置项**:`runtimeUrl` `propsValues` `propsConfigs` `componentGroupList`。这是能让我们的编辑器正常运行的关键。
|
更多 prop 详见[编辑器 API](../api/editor/props.md),下文重点介绍最关键的 4 个:`runtimeUrl`、`componentGroupList`、`propsConfigs/propsValues`、初始 DSL(`v-model`)。
|
||||||
|
|
||||||
:::tip
|
## runtimeUrl
|
||||||
如果出现```Preprocessor dependency "sass" not found. Did you install it?```,那么需要install sass
|
|
||||||
|
|
||||||
```bash
|
编辑器中央的模拟器画布是一个 `iframe`,`runtimeUrl` 就是这个 iframe 加载的地址,里面运行着一份 **playground runtime**,负责响应编辑器中组件的增删改查。
|
||||||
npm install sass -D
|
|
||||||
```
|
|
||||||
:::
|
|
||||||
|
|
||||||
:::tip
|
playground 中通过 Vite proxy 把 runtime 服务(默认端口 `8078`)代理到了同一个域:
|
||||||
如果是使用vite构建工具,如果出现 ```Uncaught ReferenceError: global is not defined```,那么需要再vite.config.js中添加如下配置:
|
|
||||||
|
|
||||||
```js
|
```ts
|
||||||
{
|
server: {
|
||||||
optimizeDeps: {
|
port: 8098,
|
||||||
esbuildOptions: {
|
proxy: {
|
||||||
define: {
|
'^/tmagic-editor/playground/runtime': {
|
||||||
global: 'globalThis',
|
target: 'http://127.0.0.1:8078',
|
||||||
},
|
changeOrigin: true,
|
||||||
|
prependPath: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
:::
|
|
||||||
|
|
||||||
## runtimeUrl
|
实际项目中可以使用 `npm create tmagic` 快速生成一个 runtime 项目,详见[RUNTIME](./runtime.md)。
|
||||||
|
|
||||||
该配置涉及到 [runtime 概念](runtime.md),tmagic-editor编辑器中心的模拟器画布,是一个 iframe(这里的 `runtimeUrl` 配置的,就是你提供的 iframe 的 url),其中渲染了一个 runtime,用来响应编辑器中的组件增删改等操作。
|
|
||||||
:::tip
|
|
||||||
可以使用`npm create tmagic` 来快速创建一个runtime项目。
|
|
||||||
:::
|
|
||||||
|
|
||||||
## componentGroupList
|
## componentGroupList
|
||||||
|
|
||||||
`componentGroupList` 是指定左侧组件库内容的配置。此处定义了在编辑器组件库中有什么组件。在添加的时候通过组件 `type` 来确定 runtime 中要渲染什么组件。可以参考 [componentGroupList 配置](../api/editor/props.html#componentgrouplist)。
|
`componentGroupList` 决定左侧组件库展示哪些组件分组。每个 item 通过 `type` 与 runtime 中注册的组件类型一一对应,添加到画布时编辑器会基于 `type` 通知 runtime 渲染对应组件。
|
||||||
|
|
||||||
## propsConfigs/propsValues
|
```ts
|
||||||
|
import {
|
||||||
|
Files,
|
||||||
|
FolderOpened,
|
||||||
|
PictureFilled,
|
||||||
|
SwitchButton,
|
||||||
|
Tickets,
|
||||||
|
} from "@element-plus/icons-vue";
|
||||||
|
import type { ComponentGroup } from "@tmagic/editor";
|
||||||
|
|
||||||
`propsConfigs` `propsValues` 和 `componentGroupList` 中声明的组件是一一对应的,通过 `type` 来识别属于哪个组件,该配置涉及的内容,就是组件的表单配置描述,在[组件开发中](./component.md)会通过 formConfig 配置来声明这份内容。
|
export default [
|
||||||
|
{
|
||||||
`configs` 既可以通过 hardcode 方式写上每个组件的表单配置,也可以通过组件打包方式得到对应内容,然后通过异步加载来载入。比如:
|
title: "示例容器",
|
||||||
|
items: [
|
||||||
```javascript
|
{ icon: FolderOpened, text: "组", type: "container" },
|
||||||
setup() {
|
{ icon: FolderOpened, text: "蒙层", type: "overlay" },
|
||||||
asyncLoadJs(`/runtime/vue/assets/config.js`).then(() => {
|
{ icon: Files, text: "迭代器容器", type: "iterator-container" },
|
||||||
propsConfigs.value = window.magicPresetConfigs;
|
],
|
||||||
});
|
},
|
||||||
asyncLoadJs(`/runtime/vue/assets/value.js`).then(() => {
|
{
|
||||||
propsValues.value = window.magicPresetValues;
|
title: "示例组件",
|
||||||
});
|
items: [
|
||||||
}
|
{ icon: Tickets, text: "文本", type: "text" },
|
||||||
|
{ icon: SwitchButton, text: "按钮", type: "button" },
|
||||||
|
{ icon: PictureFilled, text: "图片", type: "img" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// 也可以提供完整 schema 作为「组合」,添加时直接落入完整子树
|
||||||
|
{
|
||||||
|
title: "组合",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
icon: Tickets,
|
||||||
|
text: "弹窗",
|
||||||
|
data: {
|
||||||
|
type: "overlay",
|
||||||
|
name: "弹窗",
|
||||||
|
style: {
|
||||||
|
position: "fixed",
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
/* ... */
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as ComponentGroup[];
|
||||||
```
|
```
|
||||||
|
|
||||||
::: tip 如何快速得到一个 configs/values
|
完整字段参考 [`componentGroupList`](../api/editor/props.md#componentgrouplist)。
|
||||||
上述的 runtime 产物中,dist 目录中即包含一个 entry 文件夹,在你的项目组件初始化之后,分别异步加载里面的config/index.umd.js、value/index.umd.js。并如上面代码中,赋值给 configs/values 即可。
|
|
||||||
|
## propsConfigs / propsValues
|
||||||
|
|
||||||
|
`propsConfigs` `propsValues` 与 `componentGroupList` 中声明的组件通过 `type` 一一对应:
|
||||||
|
|
||||||
|
- `propsConfigs[type]`:组件**右侧表单**的配置描述(在组件中 `formConfig` 字段提供)。
|
||||||
|
- `propsValues[type]`:组件被添加到画布时的**初始默认值**(在组件中 `initValue` 字段提供)。
|
||||||
|
|
||||||
|
这些内容会通过 `@tmagic/cli` 在 runtime 构建时打包出对应的 UMD 文件,编辑器异步加载即可。playground 中的真实做法([`use-editor-res.ts`](https://github.com/Tencent/tmagic-editor/blob/master/playground/src/pages/composables/use-editor-res.ts)):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { ref } from "vue";
|
||||||
|
import { asyncLoadJs } from "@tmagic/editor";
|
||||||
|
|
||||||
|
const { VITE_ENTRY_PATH } = import.meta.env;
|
||||||
|
|
||||||
|
export const useEditorRes = () => {
|
||||||
|
const propsValues = ref<Record<string, any>>({});
|
||||||
|
const propsConfigs = ref<Record<string, any>>({});
|
||||||
|
const eventMethodList = ref<Record<string, any>>({});
|
||||||
|
const datasourceConfigs = ref<Record<string, any>>({});
|
||||||
|
const datasourceValues = ref<Record<string, any>>({});
|
||||||
|
const datasourceEventMethodList = ref<Record<string, any>>({
|
||||||
|
base: { events: [], methods: [] },
|
||||||
|
});
|
||||||
|
|
||||||
|
asyncLoadJs(`${VITE_ENTRY_PATH}/config/index.umd.cjs`).then(() => {
|
||||||
|
propsConfigs.value = (globalThis as any).magicPresetConfigs;
|
||||||
|
});
|
||||||
|
asyncLoadJs(`${VITE_ENTRY_PATH}/value/index.umd.cjs`).then(() => {
|
||||||
|
propsValues.value = (globalThis as any).magicPresetValues;
|
||||||
|
});
|
||||||
|
asyncLoadJs(`${VITE_ENTRY_PATH}/event/index.umd.cjs`).then(() => {
|
||||||
|
eventMethodList.value = (globalThis as any).magicPresetEvents;
|
||||||
|
});
|
||||||
|
asyncLoadJs(`${VITE_ENTRY_PATH}/ds-config/index.umd.cjs`).then(() => {
|
||||||
|
datasourceConfigs.value = (globalThis as any).magicPresetDsConfigs;
|
||||||
|
});
|
||||||
|
asyncLoadJs(`${VITE_ENTRY_PATH}/ds-value/index.umd.cjs`).then(() => {
|
||||||
|
datasourceValues.value = (globalThis as any).magicPresetDsValues;
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
propsValues,
|
||||||
|
propsConfigs,
|
||||||
|
eventMethodList,
|
||||||
|
datasourceConfigs,
|
||||||
|
datasourceValues,
|
||||||
|
datasourceEventMethodList,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip 怎样得到这些 UMD 文件?
|
||||||
|
在 runtime 项目中执行 `npm run build:libs`(参考 [`runtime/vue/package.json`](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue/package.json)),会在 `dist/entry/` 下生成 `config/value/event/ds-config/ds-value` 五个目录的 UMD 文件,全局变量分别为 `magicPresetConfigs` `magicPresetValues` `magicPresetEvents` `magicPresetDsConfigs` `magicPresetDsValues`。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## 更多
|
如果是在调试期,也可以直接 hardcode 一份 `propsConfigs` / `propsValues`,比如:
|
||||||
|
|
||||||
通过上述步骤,可以快速得到一个初始化的简单编辑器。
|
```ts
|
||||||
|
const propsConfigs = ref({
|
||||||
|
text: [{ name: "text", text: "文案" }],
|
||||||
|
button: [{ name: "text", text: "按钮文案" }],
|
||||||
|
});
|
||||||
|
|
||||||
除了上述内容外,文档的其他章节中,也会更深入的描述整个tmagic-editor的设计理念和实现细节。同时你也可以查看我们的[项目源码](https://github.com/Tencent/tmagic-editor),从源码提供的 playground 和 runtime 示例来开发和理解tmagic-editor。
|
const propsValues = ref({
|
||||||
|
text: { text: "一段文字" },
|
||||||
|
button: { text: "按钮" },
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## v-model:DSL 初始值
|
||||||
|
|
||||||
|
`v-model` 绑定的是整个页面的 [DSL](./advanced/js-schema.md),最简的初始 DSL 长这样:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { type MApp, NodeType } from "@tmagic/core";
|
||||||
|
|
||||||
|
const dsl: MApp = {
|
||||||
|
id: "1",
|
||||||
|
name: "demo",
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: "page_1",
|
||||||
|
name: "index",
|
||||||
|
layout: "absolute",
|
||||||
|
style: { position: "relative", width: "100%", height: "100%" },
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
完整含数据源、代码块、事件联动的 DSL 示例见 [`playground/src/configs/dsl.ts`](https://github.com/Tencent/tmagic-editor/blob/master/playground/src/configs/dsl.ts)。
|
||||||
|
|
||||||
|
::: tip 持久化与历史记录
|
||||||
|
playground 用 `localStorage` + `serialize-javascript` 做了一个本地持久化方案,并在保存后调用 `editor.editorService.resetModifiedNodeId()` 重置修改状态,可以直接复用。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 进阶:编辑器服务与插件
|
||||||
|
|
||||||
|
`@tmagic/editor` 提供了多组 **service**(`editorService` / `propsService` / `historyService` / `uiService` …)和 **插件机制**,可以非侵入式扩展行为。例如 playground 中:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { editorService, propsService } from "@tmagic/editor";
|
||||||
|
|
||||||
|
editorService.usePlugin({
|
||||||
|
beforeDoAdd: (config, parent) => {
|
||||||
|
if (config.type === "overlay") {
|
||||||
|
// 蒙层始终插入到当前 page 下,并钉到 (0, 0)
|
||||||
|
config.style = { ...config.style, left: 0, top: 0 };
|
||||||
|
return [config, editorService.get("page")];
|
||||||
|
}
|
||||||
|
return [config, parent];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
propsService.usePlugin({
|
||||||
|
beforeFillConfig: (config) => [config, "100px"],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
更多扩展能力见[编辑器扩展](./editor-expand.md)与各 service 的 [API 文档](../api/editor/props.md)。
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
- [基础概念](./conception.md):编辑器 / 模拟器 / runtime / DSL 的关系
|
||||||
|
- [RUNTIME](./runtime.md):实现并打包一个 runtime
|
||||||
|
- [组件开发](./component.md):自定义业务组件
|
||||||
|
- [页面发布](./publish.md):基于 `@tmagic/cli` 的产物结构与发布流程
|
||||||
|
- [Playground 源码](https://github.com/Tencent/tmagic-editor/tree/master/playground):与本节示例完全对应
|
||||||
|
|
||||||
|
通过 `pnpm bootstrap && pnpm pg` 即可在仓库本地启动这份 playground,自由调试。
|
||||||
|
|||||||
@ -18,7 +18,7 @@ runtime 的概念,是理解tmagic-editor项目页运行的重要概念,runti
|
|||||||
各个 runtime 的作用除了作为不同场景下的渲染环境,同时也是不同环境的打包构建载体。tmagic-editor示例代码中的打包就是基于 runtime 进行的。
|
各个 runtime 的作用除了作为不同场景下的渲染环境,同时也是不同环境的打包构建载体。tmagic-editor示例代码中的打包就是基于 runtime 进行的。
|
||||||
|
|
||||||
### 业务相关
|
### 业务相关
|
||||||
由于 runtime 是页面渲染的承载环境,其中会加载 @tmagic/ui 以及各个业务组件,业务发布项目页也是基于 runtime,所以在 runtime 中实现业务方的自定义逻辑是最合适的。runtime 可以提供一些全局 API,供业务组件调用。我们可以把下面的模拟器中的 runtime 视为一个业务方runtime。
|
由于 runtime 是页面渲染的承载环境,其中会加载 `@tmagic/vue-container`(或 `@tmagic/react-container`)等基础渲染组件以及各个业务组件,业务发布项目页也是基于 runtime,所以在 runtime 中实现业务方的自定义逻辑是最合适的。runtime 可以提供一些全局 API,供业务组件调用。我们可以把下面的模拟器中的 runtime 视为一个业务方runtime。
|
||||||
|
|
||||||
tmagic-editor提供了三个版本的 runtime 示例,可以参考:
|
tmagic-editor提供了三个版本的 runtime 示例,可以参考:
|
||||||
- [vue runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue)
|
- [vue runtime](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue)
|
||||||
|
|||||||
@ -1,29 +1,328 @@
|
|||||||
# RUNTIME
|
# RUNTIME
|
||||||
本章详细介绍如何深入理解tmagic-editor的打包,以及如何根据需求定制,修改tmagic-editor的页面打包发布方案。页面发布、打包相关的定制化开发,需要使用tmagic-editor的业务方,搭建好基于开源tmagic-editor的管理平台、存储服务等配套设施。
|
|
||||||
|
本章详细介绍 tmagic-editor 中 runtime 的概念、目录结构与实现方式。所有内容均与开源仓库 [`runtime/vue/`](https://github.com/Tencent/tmagic-editor/tree/master/runtime/vue) 一一对应,可以对照阅读。
|
||||||
|
|
||||||
## runtime 是什么
|
## runtime 是什么
|
||||||
|
|
||||||
runtime是用来解析DSL的执行环境,用于渲染 DSL 呈现页面。
|
**runtime 是用来解析 DSL 的执行环境**。编辑器只负责生成 DSL,最终把它**渲染成可见页面**的工作交给 runtime。
|
||||||
|
|
||||||
编辑器生成出来的DSL需要通过 runtime 来渲染。
|
在一份完整的 tmagic-editor 项目中,runtime 同时承担两个角色:
|
||||||
|
|
||||||
## 实现一个 runtime
|
| 角色 | 入口 | 用途 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **page** | `runtime/vue/page/` | 线上发布产物,加载 `window.magicDSL` 渲染真实页面 |
|
||||||
|
| **playground** | `runtime/vue/playground/` | 编辑器中央 iframe 加载的画布,响应增删改并渲染所见即所得 |
|
||||||
|
|
||||||
:::tip
|
两者共用同一份组件、插件、数据源代码,只在入口(`main.ts` / `App.vue`)上有差异。
|
||||||
可以使用`npm create tmagic` 来快速创建一个runtime项目。
|
|
||||||
|
::: tip
|
||||||
|
DSL、playground 与 editor 之间的通信原理可以前往[教程](/guide/tutorial/)继续了解。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
创建出来的项目会包含page、playground两个目录。
|
## 创建 runtime 项目
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
推荐用 `npm create tmagic@latest` / `pnpm create tmagic` 快速生成 runtime 模板,按提示选择 `runtime` 即可。
|
||||||
|
:::
|
||||||
|
|
||||||
|
生成的项目结构如下(与 [`runtime/vue/`](https://github.com/Tencent/tmagic-editor/tree/master/runtime/vue) 完全一致):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.
|
runtime/vue
|
||||||
├── page
|
├── page/ # 线上 page 入口
|
||||||
├── playground
|
│ ├── App.vue
|
||||||
|
│ ├── index.html
|
||||||
|
│ ├── main.ts
|
||||||
|
│ └── utils/
|
||||||
|
├── playground/ # 编辑器内 iframe 入口
|
||||||
|
│ ├── App.vue
|
||||||
|
│ ├── index.html
|
||||||
|
│ └── main.ts
|
||||||
|
├── public/
|
||||||
|
├── scripts/ # build 脚本(res / page / playground / all)
|
||||||
|
├── tmagic.config.ts # @tmagic/cli 配置:声明组件、插件、数据源
|
||||||
|
├── tmagic.config.local.ts# 本地覆盖配置(可选)
|
||||||
|
└── vite.config.ts # 多入口构建:page + playground
|
||||||
```
|
```
|
||||||
|
|
||||||
page用于生产环境
|
## tmagic.config.ts:声明组件 / 插件 / 数据源
|
||||||
|
|
||||||
playground用于编辑器中
|
`tmagic.config.ts` 是 [@tmagic/cli](./publish.md#tmagic-cli) 的入口,它会扫描 `packages` 列表,生成 `.tmagic/comp-entry.ts` 等 5 个入口文件,runtime 只需要从这些入口里 `import` 即可:
|
||||||
|
|
||||||
:::tip
|
```ts
|
||||||
想要了解DSL的解析以及runtime与编辑器的通信,可以前往[教程](/guide/tutorial/)
|
import { defineConfig } from '@tmagic/cli';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
componentFileAffix: '.vue',
|
||||||
|
// 是否使用 vite + 异步组件,详见 page/main.ts 中的 defineAsyncComponent
|
||||||
|
dynamicImport: true,
|
||||||
|
npmConfig: {
|
||||||
|
client: 'pnpm',
|
||||||
|
keepPackageJsonClean: true,
|
||||||
|
},
|
||||||
|
packages: [
|
||||||
|
{
|
||||||
|
// key 为组件 type,需要与编辑器中 componentGroupList 的 type 对应
|
||||||
|
button: '@tmagic/vue-button',
|
||||||
|
container: '@tmagic/vue-container',
|
||||||
|
img: '@tmagic/vue-img',
|
||||||
|
'iterator-container': '@tmagic/vue-iterator-container',
|
||||||
|
overlay: '@tmagic/vue-overlay',
|
||||||
|
page: '@tmagic/vue-page',
|
||||||
|
'page-fragment': '@tmagic/vue-page-fragment',
|
||||||
|
'page-fragment-container': '@tmagic/vue-page-fragment-container',
|
||||||
|
qrcode: '@tmagic/vue-qrcode',
|
||||||
|
text: '@tmagic/vue-text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`tmagic.config.local.ts` 用于本地覆盖(不会被提交),常见用法是把线上 npm 包临时替换为本地组件目录调试。
|
||||||
|
|
||||||
|
执行 `npm run tmagic`(即 `tmagic entry`)后,runtime 根目录下会生成:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.tmagic/
|
||||||
|
├── comp-entry.ts # page 同步组件入口
|
||||||
|
├── async-comp-entry.ts # page 异步组件入口(dynamicImport 时使用)
|
||||||
|
├── config-entry.ts # 编辑器右侧表单配置
|
||||||
|
├── value-entry.ts # 组件初始值
|
||||||
|
├── event-entry.ts # 组件事件 / 方法列表
|
||||||
|
├── plugin-entry.ts # 插件入口
|
||||||
|
├── datasource-entry.ts # 同步数据源入口
|
||||||
|
└── async-datasource-entry.ts # 异步数据源入口
|
||||||
|
```
|
||||||
|
|
||||||
|
> 详细产物说明见[页面发布 § @tmagic/cli](./publish.md#tmagic-cli)。
|
||||||
|
|
||||||
|
## playground runtime 实现
|
||||||
|
|
||||||
|
playground 是编辑器中央 iframe 加载的画布,最关键的逻辑就是把编辑器派发的 DSL 变更同步到本地 Vue 状态并触发重新渲染。
|
||||||
|
|
||||||
|
`@tmagic/vue-runtime-help` 提供的 `useEditorDsl` Hook 已经帮我们实现了与编辑器的通信(`onRuntimeReady` / `updateRootConfig` / `updatePageId` / `add` / `update` / `remove` 等);只需要在入口里:
|
||||||
|
|
||||||
|
1. 创建 `TMagicApp` 实例,注册组件、数据源、插件;
|
||||||
|
2. 通过 `provide('app', app)` 把实例注入子组件;
|
||||||
|
3. 在 `App.vue` 里使用 `useEditorDsl()` + `useComponent('page')` 渲染页面。
|
||||||
|
|
||||||
|
完整的 [`runtime/vue/playground/main.ts`](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue/playground/main.ts):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createApp } from 'vue';
|
||||||
|
import TMagicApp, { DataSourceManager, DeepObservedData } from '@tmagic/core';
|
||||||
|
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
import '@tmagic/core/resetcss.css';
|
||||||
|
|
||||||
|
DataSourceManager.registerObservedData(DeepObservedData);
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
import('../.tmagic/comp-entry'),
|
||||||
|
import('../.tmagic/plugin-entry'),
|
||||||
|
import('../.tmagic/datasource-entry'),
|
||||||
|
]).then(([components, plugins, dataSources]) => {
|
||||||
|
const vueApp = createApp(App);
|
||||||
|
|
||||||
|
const app = new TMagicApp({
|
||||||
|
ua: window.navigator.userAgent,
|
||||||
|
platform: 'editor',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (app.env.isWeb) {
|
||||||
|
app.setDesignWidth(window.document.documentElement.getBoundingClientRect().width);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(components.default).forEach(([type, component]: [string, any]) => {
|
||||||
|
app.registerComponent(type, component);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.entries(dataSources.default).forEach(([type, ds]: [string, any]) => {
|
||||||
|
DataSourceManager.register(type, ds);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.values(plugins.default).forEach((plugin: any) => {
|
||||||
|
vueApp.use(plugin, { app });
|
||||||
|
});
|
||||||
|
|
||||||
|
window.appInstance = app;
|
||||||
|
vueApp.config.globalProperties.app = app;
|
||||||
|
vueApp.provide('app', app);
|
||||||
|
|
||||||
|
vueApp.mount('#app');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
[`playground/App.vue`](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue/playground/App.vue) 出乎意料地短:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<component v-if="pageConfig" :is="pageComponent" :key="pageConfig.id" :config="pageConfig"></component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { useComponent, useEditorDsl } from '@tmagic/vue-runtime-help';
|
||||||
|
|
||||||
|
const { pageConfig } = useEditorDsl();
|
||||||
|
const pageComponent = useComponent('page');
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
::: tip 关键点
|
||||||
|
- `platform: 'editor'` 告知 `@tmagic/core` 进入编辑模式;
|
||||||
|
- `useEditorDsl()` 内部已经调用 `window.magic?.onRuntimeReady({...})`,把 add/update/remove 等回调挂载到全局,编辑器通过 `iframe.contentWindow.magic` 触发;
|
||||||
|
- 当 DSL 变化时,`pageConfig` 自动更新;当页面 DOM 渲染完成,`useEditorDsl` 会调用 `magic.onPageElUpdate(...)` 把页面元素同步给编辑器,让选中框能够吸附。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## page runtime 实现(线上发布)
|
||||||
|
|
||||||
|
[`runtime/vue/page/main.ts`](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue/page/main.ts) 与 playground 的差别在于:
|
||||||
|
|
||||||
|
1. 不需要响应编辑器消息,直接读取 `window.magicDSL`(或 `localPreview` 模式下从 `localStorage` 读取);
|
||||||
|
2. 使用 `defineAsyncComponent` + 异步入口,按需加载组件,**减小首屏体积**;
|
||||||
|
3. 数据源走 `registerDataSourceOnDemand`,只注册当前 DSL 用到的;
|
||||||
|
4. 注入 `request` 与 `userRender` 等业务侧 API 给组件复用。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createApp, defineAsyncComponent, resolveDirective, withDirectives } from 'vue';
|
||||||
|
|
||||||
|
import TMagicApp, { DataSourceManager, DeepObservedData, getUrlParam, registerDataSourceOnDemand } from '@tmagic/core';
|
||||||
|
|
||||||
|
import components from '../.tmagic/async-comp-entry';
|
||||||
|
import asyncDataSources from '../.tmagic/async-datasource-entry';
|
||||||
|
import plugins from '../.tmagic/plugin-entry';
|
||||||
|
|
||||||
|
import request, { service } from './utils/request';
|
||||||
|
import AppComponent from './App.vue';
|
||||||
|
import { getLocalConfig } from './utils';
|
||||||
|
|
||||||
|
import '@tmagic/core/resetcss.css';
|
||||||
|
|
||||||
|
DataSourceManager.registerObservedData(DeepObservedData);
|
||||||
|
|
||||||
|
const vueApp = createApp(AppComponent);
|
||||||
|
vueApp.use(request);
|
||||||
|
|
||||||
|
const dsl = ((getUrlParam('localPreview') ? getLocalConfig() : window.magicDSL) || [])[0] || {};
|
||||||
|
|
||||||
|
const app = new TMagicApp({
|
||||||
|
ua: window.navigator.userAgent,
|
||||||
|
config: dsl,
|
||||||
|
request: service,
|
||||||
|
curPage: getUrlParam('page'),
|
||||||
|
useMock: Boolean(getUrlParam('useMock')),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.setDesignWidth(app.env.isWeb ? window.document.documentElement.getBoundingClientRect().width : 375);
|
||||||
|
|
||||||
|
Object.entries(components).forEach(([type, component]: [string, any]) => {
|
||||||
|
app.registerComponent(type, defineAsyncComponent(component));
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.values(plugins).forEach((plugin: any) => {
|
||||||
|
vueApp.use(plugin, { app });
|
||||||
|
});
|
||||||
|
|
||||||
|
registerDataSourceOnDemand(dsl, asyncDataSources).then((dataSources) => {
|
||||||
|
Object.entries(dataSources).forEach(([type, ds]: [string, any]) => {
|
||||||
|
DataSourceManager.register(type, ds);
|
||||||
|
});
|
||||||
|
|
||||||
|
vueApp.config.globalProperties.app = app;
|
||||||
|
vueApp.provide('app', app);
|
||||||
|
vueApp.mount('#app');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
[`page/App.vue`](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue/page/App.vue) 用 `useDsl()`(注意不是 `useEditorDsl`):
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<component :is="pageComponent" :config="pageConfig as MPage"></component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { MPage } from '@tmagic/core';
|
||||||
|
import { useComponent, useDsl } from '@tmagic/vue-runtime-help';
|
||||||
|
|
||||||
|
const { pageConfig, app } = useDsl();
|
||||||
|
const pageComponent = useComponent('page');
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## vite 多入口构建
|
||||||
|
|
||||||
|
`runtime/vue` 通过单个 vite 工程构建出两份产物([`vite.config.ts`](https://github.com/Tencent/tmagic-editor/blob/master/runtime/vue/vite.config.ts)):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
build: {
|
||||||
|
rolldownOptions: {
|
||||||
|
input: {
|
||||||
|
page: path.resolve(__dirname, './page/index.html'),
|
||||||
|
playground: path.resolve(__dirname, './playground/index.html'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
加上 `package.json` 中提供的 build 脚本:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"tmagic": "tmagic entry",
|
||||||
|
"dev": "vite --force",
|
||||||
|
"build": "rimraf ./dist && node scripts/build.mjs --type=all",
|
||||||
|
"build:libs": "node scripts/build.mjs --type=res",
|
||||||
|
"build:page": "node scripts/build.mjs --type=page",
|
||||||
|
"build:playground": "node scripts/build.mjs --type=playground"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
最常用的两个:
|
||||||
|
|
||||||
|
- `npm run build:libs`:构建 **编辑器侧**用到的 `config / value / event / ds-config / ds-value` 五份 UMD 资源(输出到 `dist/entry/`),编辑器通过 `asyncLoadJs` 异步加载(参考[快速开始 § propsConfigs / propsValues](./index.md#propsconfigs-propsvalues))。
|
||||||
|
- `npm run build`:同时产出 `playground/index.html`、`page/index.html` 与 `entry/`,可以一份产物覆盖编辑器、预览、线上三种场景。
|
||||||
|
|
||||||
|
## @tmagic/vue-runtime-help 常用 Hook
|
||||||
|
|
||||||
|
| Hook | 作用 |
|
||||||
|
| --- | --- |
|
||||||
|
| `useEditorDsl()` | playground 入口使用,建立与编辑器通信、维护当前页面 `pageConfig` |
|
||||||
|
| `useDsl()` | page 入口使用,从 `window.magicDSL` 中读取并维护 `pageConfig` |
|
||||||
|
| `useComponent(type)` | 通过组件 type 解析出已注册的 Vue 组件(找不到时会回退到 `magic-ui-${type}`) |
|
||||||
|
| `useApp()` | 取出注入的 `TMagicApp` 实例 |
|
||||||
|
| `useComponentStatus()` | 获取组件在编辑器中的展示/禁用状态 |
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
React runtime 的实现思路完全一致,对应包是 [`@tmagic/react-runtime-help`](https://github.com/Tencent/tmagic-editor/tree/master/runtime/react-runtime-help),可以参照本节自行迁移。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## 跨域
|
||||||
|
|
||||||
|
playground 是被编辑器以 iframe 形式加载的,开发期需要保证 runtime 服务允许跨域。仓库里的做法是用 Vite 的 proxy 把 runtime 反代到 playground 同域:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// playground/vite.config.ts
|
||||||
|
server: {
|
||||||
|
port: 8098,
|
||||||
|
proxy: {
|
||||||
|
'^/tmagic-editor/playground/runtime': {
|
||||||
|
target: 'http://127.0.0.1:8078',
|
||||||
|
changeOrigin: true,
|
||||||
|
prependPath: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
如果编辑器和 runtime 跨域部署,需要在 runtime 服务侧返回 `Access-Control-Allow-Origin`,并保证 iframe 的 `postMessage` 同源策略允许双方通信。
|
||||||
|
|
||||||
|
## 进一步阅读
|
||||||
|
|
||||||
|
- [基础概念](./conception.md):编辑器、模拟器、runtime 的关系
|
||||||
|
- [组件开发](./component.md):组件四件套(component / form-config / init-value / event)
|
||||||
|
- [页面发布](./publish.md):page.html 注入 DSL 的发布流程
|
||||||
|
- [教程](./tutorial/index.md):从零实现一份 runtime,理解 magic API 与 DSL 解析
|
||||||
|
|||||||
@ -1,12 +1,26 @@
|
|||||||
# 3.[DSL](../conception.md#dsl) 解析渲染
|
# 3.[DSL](../conception.md#dsl) 解析渲染
|
||||||
|
|
||||||
tmagic 提供了 vue/react 两个版本的解析渲染组件,可以直接使用
|
tmagic 提供了 vue/react 两个版本的解析渲染组件,可以直接使用。基础渲染组件以 container 为核心,配合 page、button、img、text 等多个独立的 npm 包,分别发布在 `vue-components/` 与 `react-components/` 下:
|
||||||
|
|
||||||
[@tmagic/ui](https://www.npmjs.com/package/@tmagic/ui)
|
vue 版本:
|
||||||
|
|
||||||
[@tmagic/ui-react](https://www.npmjs.com/package/@tmagic/ui-react)
|
- [@tmagic/vue-container](https://www.npmjs.com/package/@tmagic/vue-container)
|
||||||
|
- [@tmagic/vue-page](https://www.npmjs.com/package/@tmagic/vue-page)
|
||||||
|
- [@tmagic/vue-button](https://www.npmjs.com/package/@tmagic/vue-button)
|
||||||
|
- [@tmagic/vue-img](https://www.npmjs.com/package/@tmagic/vue-img)
|
||||||
|
- [@tmagic/vue-text](https://www.npmjs.com/package/@tmagic/vue-text)
|
||||||
|
- 其他:`@tmagic/vue-overlay`、`@tmagic/vue-qrcode`、`@tmagic/vue-page-fragment`、`@tmagic/vue-page-fragment-container`、`@tmagic/vue-iterator-container`
|
||||||
|
|
||||||
接下来是以vue为基础,来讲述如何实现一个[@tmagic/ui](https://www.npmjs.com/package/@tmagic/ui)
|
react 版本:
|
||||||
|
|
||||||
|
- [@tmagic/react-container](https://www.npmjs.com/package/@tmagic/react-container)
|
||||||
|
- [@tmagic/react-page](https://www.npmjs.com/package/@tmagic/react-page)
|
||||||
|
- [@tmagic/react-button](https://www.npmjs.com/package/@tmagic/react-button)
|
||||||
|
- [@tmagic/react-img](https://www.npmjs.com/package/@tmagic/react-img)
|
||||||
|
- [@tmagic/react-text](https://www.npmjs.com/package/@tmagic/react-text)
|
||||||
|
- 其他:`@tmagic/react-overlay`、`@tmagic/react-qrcode`、`@tmagic/react-page-fragment`、`@tmagic/react-page-fragment-container`、`@tmagic/react-iterator-container`
|
||||||
|
|
||||||
|
接下来是以 vue 为基础,来讲述如何实现一个类似 [@tmagic/vue-container](https://www.npmjs.com/package/@tmagic/vue-container) 的渲染器
|
||||||
|
|
||||||
## 准备工作
|
## 准备工作
|
||||||
|
|
||||||
|
|||||||
@ -349,6 +349,43 @@ export default {
|
|||||||
lib: 'always',
|
lib: 'always',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
/**
|
||||||
|
* 禁止匿名 default class / default function 导出
|
||||||
|
* @reason 匿名 default 导出在 dts 聚合(rolldown / api-extractor / vue-tsc 等)时会被命名为
|
||||||
|
* `export_default`,导致跨包继承链在 .vue / .tsx 文件下解析失败,
|
||||||
|
* 父类成员(如 EventEmitter 的 on/off)无法被 ts-plugin 推断出来。
|
||||||
|
* 必须使用具名形式:先 `export class Foo {}` 再 `export default Foo;`,
|
||||||
|
* 或 `export default class Foo {}`,确保类型聚合后保留原标识符。
|
||||||
|
*
|
||||||
|
* 注:此处需要重申 base.mjs 中已有的 no-restricted-syntax 选择器
|
||||||
|
* (ForIn / Labeled / With),否则在 .ts/.tsx 下会被本规则整体覆盖。
|
||||||
|
*/
|
||||||
|
'no-restricted-syntax': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
selector: 'ExportDefaultDeclaration > ClassDeclaration[id=null]',
|
||||||
|
message:
|
||||||
|
'禁止匿名 default class 导出。请改为具名形式(如 `export default class Foo extends Bar {}`),否则聚合 dts 会丢失类型信息,导致跨包继承的成员(on/off/emit 等)无法被推断。',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'ExportDefaultDeclaration > FunctionDeclaration[id=null]',
|
||||||
|
message:
|
||||||
|
'禁止匿名 default function 导出。请改为具名形式(如 `export default function foo() {}`),便于 dts 聚合保留原标识符与跨包类型推断。',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'ForInStatement',
|
||||||
|
message:
|
||||||
|
'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'LabeledStatement',
|
||||||
|
message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'WithStatement',
|
||||||
|
message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
|
||||||
|
},
|
||||||
|
],
|
||||||
/**
|
/**
|
||||||
* 在类型注释周围需要一致的间距
|
* 在类型注释周围需要一致的间距
|
||||||
*/
|
*/
|
||||||
|
|||||||
15
package.json
15
package.json
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.14-beta.0",
|
"version": "1.8.0-beta.4",
|
||||||
"name": "tmagic",
|
"name": "tmagic",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.32.1",
|
"packageManager": "pnpm@10.33.4",
|
||||||
"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",
|
||||||
@ -56,6 +56,7 @@
|
|||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"eslint": "^10.3.0",
|
"eslint": "^10.3.0",
|
||||||
"execa": "^9.6.0",
|
"execa": "^9.6.0",
|
||||||
|
"happy-dom": "^20.9.0",
|
||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jsdom": "^27.2.0",
|
"jsdom": "^27.2.0",
|
||||||
@ -65,18 +66,18 @@
|
|||||||
"prettier": "^3.8.3",
|
"prettier": "^3.8.3",
|
||||||
"recast": "^0.23.11",
|
"recast": "^0.23.11",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rolldown": "^1.0.0",
|
"rolldown": "^1.0.1",
|
||||||
"rolldown-plugin-dts": "^0.25.0",
|
"rolldown-plugin-dts": "^0.25.1",
|
||||||
"sass-embedded": "^1.99.0",
|
"sass-embedded": "^1.99.0",
|
||||||
"semver": "^7.7.3",
|
"semver": "^7.7.3",
|
||||||
"serialize-javascript": "^7.0.0",
|
"serialize-javascript": "^7.0.0",
|
||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"vitepress": "^1.6.4",
|
"vitepress": "^2.0.0-alpha.17",
|
||||||
"vitest": "^4.1.5",
|
"vitest": "^4.1.6",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-tsc": "^3.2.8"
|
"vue-tsc": "^3.2.9"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.14-beta.0",
|
"version": "1.8.0-beta.4",
|
||||||
"name": "@tmagic/cli",
|
"name": "@tmagic/cli",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
|||||||
@ -1,18 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
|
||||||
import { describe, expect, test } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import Core from '../src/Core';
|
import Core from '../src/Core';
|
||||||
|
import { ModuleMainFilePath, UserConfig } from '../src/types';
|
||||||
|
|
||||||
|
const emptyModuleMap: ModuleMainFilePath = {
|
||||||
|
componentPackage: {},
|
||||||
|
componentMap: {},
|
||||||
|
pluginPakcage: {},
|
||||||
|
pluginMap: {},
|
||||||
|
configMap: {},
|
||||||
|
valueMap: {},
|
||||||
|
eventMap: {},
|
||||||
|
datasourcePackage: {},
|
||||||
|
datasourceMap: {},
|
||||||
|
dsConfigMap: {},
|
||||||
|
dsValueMap: {},
|
||||||
|
dsEventMap: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prepareEntryFile 内部调用 writeTemp 后未 await 异步写入,
|
||||||
|
* 这里通过轮询等待文件落盘后再断言。
|
||||||
|
*/
|
||||||
|
const waitForFile = async (filePath: string, timeoutMs = 2000) => {
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
if (fs.existsSync(filePath)) return true;
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
describe('Core', () => {
|
describe('Core', () => {
|
||||||
test('instance', () => {
|
let tmpRoot: string;
|
||||||
const core = new Core({
|
|
||||||
packages: [],
|
|
||||||
source: './a',
|
|
||||||
temp: './b',
|
|
||||||
});
|
|
||||||
expect(core).toBeInstanceOf(Core);
|
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-core-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('实例化后基本字段齐备', () => {
|
||||||
|
const core = new Core({ packages: [], source: './a', temp: './b' });
|
||||||
|
expect(core).toBeInstanceOf(Core);
|
||||||
|
expect(typeof core.version).toBe('string');
|
||||||
|
expect(core.options.source).toBe('./a');
|
||||||
|
expect(core.moduleMainFilePath.componentMap).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dir.temp() 解析为 source/temp 的绝对路径', () => {
|
||||||
|
const core = new Core({ packages: [], source: './a', temp: './b' });
|
||||||
expect(core.dir.temp()).toBe(path.join(process.cwd(), './a/b'));
|
expect(core.dir.temp()).toBe(path.join(process.cwd(), './a/b'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('writeTemp 会按 temp 目录写入文件', async () => {
|
||||||
|
const core = new Core({ packages: [], source: tmpRoot, temp: 'tmp-out' });
|
||||||
|
await core.writeTemp('hello.txt', 'world');
|
||||||
|
const target = path.join(tmpRoot, 'tmp-out', 'hello.txt');
|
||||||
|
expect(fs.existsSync(target)).toBe(true);
|
||||||
|
expect(fs.readFileSync(target, 'utf-8')).toBe('world');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('init 在没有 packages 时使用默认的 resolveAppPackages 结果', async () => {
|
||||||
|
const core = new Core({ packages: [], source: tmpRoot, temp: 'tmp' });
|
||||||
|
await core.init();
|
||||||
|
expect(core.moduleMainFilePath).toMatchObject({
|
||||||
|
componentPackage: {},
|
||||||
|
componentMap: {},
|
||||||
|
datasourcePackage: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('init 优先使用 onInit 钩子覆写 moduleMainFilePath', async () => {
|
||||||
|
const onInit = vi.fn().mockResolvedValue({
|
||||||
|
...emptyModuleMap,
|
||||||
|
componentMap: { foo: 'bar' },
|
||||||
|
});
|
||||||
|
const options: UserConfig = {
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
onInit,
|
||||||
|
};
|
||||||
|
const core = new Core(options);
|
||||||
|
await core.init();
|
||||||
|
|
||||||
|
expect(onInit).toHaveBeenCalledWith(core);
|
||||||
|
expect(core.moduleMainFilePath.componentMap).toEqual({ foo: 'bar' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prepare 会写出 entry 文件,并触发 onPrepare 钩子', async () => {
|
||||||
|
const onPrepare = vi.fn();
|
||||||
|
const core = new Core({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp-entry',
|
||||||
|
useTs: true,
|
||||||
|
onPrepare,
|
||||||
|
});
|
||||||
|
|
||||||
|
await core.prepare();
|
||||||
|
|
||||||
|
const tempDir = path.join(tmpRoot, 'tmp-entry');
|
||||||
|
expect(await waitForFile(path.join(tempDir, 'comp-entry.ts'))).toBe(true);
|
||||||
|
expect(await waitForFile(path.join(tempDir, 'plugin-entry.ts'))).toBe(true);
|
||||||
|
expect(await waitForFile(path.join(tempDir, 'datasource-entry.ts'))).toBe(true);
|
||||||
|
expect(onPrepare).toHaveBeenCalledWith(core);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('prepare 在 useTs=false 时同时输出 .js 与 .d.ts', async () => {
|
||||||
|
const core = new Core({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp-js',
|
||||||
|
useTs: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
await core.prepare();
|
||||||
|
|
||||||
|
const tempDir = path.join(tmpRoot, 'tmp-js');
|
||||||
|
expect(await waitForFile(path.join(tempDir, 'comp-entry.js'))).toBe(true);
|
||||||
|
expect(await waitForFile(path.join(tempDir, 'comp-entry.d.ts'))).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
49
packages/cli/tests/allowTs.spec.ts
Normal file
49
packages/cli/tests/allowTs.spec.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import { allowTs, transformTsFileToCodeSync } from '../src/utils/allowTs';
|
||||||
|
|
||||||
|
describe('allowTs', () => {
|
||||||
|
let tmpRoot: string;
|
||||||
|
let tsFile: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-allowts-'));
|
||||||
|
tsFile = path.join(tmpRoot, 'sample.ts');
|
||||||
|
fs.writeFileSync(
|
||||||
|
tsFile,
|
||||||
|
`export const greet = (name: string): string => \`hi \${name}\`;\nexport default greet;\n`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
delete require.extensions['.ts'];
|
||||||
|
});
|
||||||
|
|
||||||
|
test('transformTsFileToCodeSync 输出 cjs 代码并保留逻辑', () => {
|
||||||
|
const code = transformTsFileToCodeSync(tsFile);
|
||||||
|
expect(code).toContain('exports');
|
||||||
|
expect(code).toContain('greet');
|
||||||
|
expect(code).toContain('hi');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allowTs 注册 .ts loader 后,require 可以加载 ts 文件', () => {
|
||||||
|
allowTs();
|
||||||
|
expect(typeof require.extensions['.ts']).toBe('function');
|
||||||
|
|
||||||
|
delete require.cache[tsFile];
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
|
const mod = require(tsFile);
|
||||||
|
const greet = mod.default ?? mod.greet;
|
||||||
|
expect(greet('world')).toBe('hi world');
|
||||||
|
});
|
||||||
|
});
|
||||||
56
packages/cli/tests/cli.spec.ts
Normal file
56
packages/cli/tests/cli.spec.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { cli } from '../src/cli';
|
||||||
|
|
||||||
|
describe('cli', () => {
|
||||||
|
let tmpRoot: string;
|
||||||
|
let originalArgv: string[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-cli-'));
|
||||||
|
originalArgv = process.argv;
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
process.argv = originalArgv;
|
||||||
|
delete require.extensions['.ts'];
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('调用后注册了 .ts 扩展并能解析 --version 参数', () => {
|
||||||
|
process.argv = ['node', 'tmagic', '--version'];
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
cli({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
}),
|
||||||
|
).not.toThrow();
|
||||||
|
|
||||||
|
expect(typeof require.extensions['.ts']).toBe('function');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未指定子命令时不会触发 entry 动作', () => {
|
||||||
|
process.argv = ['node', 'tmagic'];
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
cli({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
}),
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
112
packages/cli/tests/commands.spec.ts
Normal file
112
packages/cli/tests/commands.spec.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { scripts } from '../src/commands';
|
||||||
|
import Core from '../src/Core';
|
||||||
|
import { allowTs } from '../src/utils/allowTs';
|
||||||
|
|
||||||
|
const writeFile = (file: string, content: string) => {
|
||||||
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||||
|
fs.writeFileSync(file, content);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('scripts (entry 命令)', () => {
|
||||||
|
let tmpRoot: string;
|
||||||
|
let originalNodeEnv: string | undefined;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-cmd-'));
|
||||||
|
originalNodeEnv = process.env.NODE_ENV;
|
||||||
|
delete process.env.NODE_ENV;
|
||||||
|
allowTs();
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
if (originalNodeEnv === undefined) {
|
||||||
|
delete process.env.NODE_ENV;
|
||||||
|
} else {
|
||||||
|
process.env.NODE_ENV = originalNodeEnv;
|
||||||
|
}
|
||||||
|
delete require.extensions['.ts'];
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未指定 NODE_ENV 时默认设为 development,并返回初始化好的 App', async () => {
|
||||||
|
const entry = scripts({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = await entry();
|
||||||
|
|
||||||
|
expect(process.env.NODE_ENV).toBe('development');
|
||||||
|
expect(app).toBeInstanceOf(Core);
|
||||||
|
expect(app.options.source).toBe(tmpRoot);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cleanTemp=true 时会清空 temp 目录', async () => {
|
||||||
|
const tempDir = path.join(tmpRoot, 'tmp');
|
||||||
|
writeFile(path.join(tempDir, 'old.txt'), 'should be deleted');
|
||||||
|
|
||||||
|
const entry = scripts({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
cleanTemp: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await entry();
|
||||||
|
|
||||||
|
expect(fs.existsSync(path.join(tempDir, 'old.txt'))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('能够读取 source 下的 tmagic.config.js 并合并到默认配置中', async () => {
|
||||||
|
writeFile(path.join(tmpRoot, 'tmagic.config.js'), 'module.exports = { useTs: false, packages: [] };\n');
|
||||||
|
|
||||||
|
const entry = scripts({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
useTs: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = await entry();
|
||||||
|
|
||||||
|
expect(app.options.useTs).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('local 配置文件会覆盖普通配置,并且 packages 会被合并', async () => {
|
||||||
|
writeFile(path.join(tmpRoot, 'tmagic.config.js'), "module.exports = { useTs: false, packages: ['foo'] };\n");
|
||||||
|
writeFile(path.join(tmpRoot, 'tmagic.config.local.js'), "module.exports = { useTs: true, packages: ['bar'] };\n");
|
||||||
|
|
||||||
|
const entry = scripts({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
});
|
||||||
|
|
||||||
|
// packages 中的 'foo' 与 'bar' 都不是真实的 npm 包,
|
||||||
|
// 由于配置在合并后会触发 resolveAppPackages 解析,这里我们 mock 掉 init
|
||||||
|
// 以便仅校验配置合并行为。
|
||||||
|
const initSpy = vi.spyOn(Core.prototype, 'init').mockResolvedValue(undefined);
|
||||||
|
const prepareSpy = vi.spyOn(Core.prototype, 'prepare').mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
const app = await entry();
|
||||||
|
|
||||||
|
expect(initSpy).toHaveBeenCalled();
|
||||||
|
expect(prepareSpy).toHaveBeenCalled();
|
||||||
|
expect(app.options.useTs).toBe(true);
|
||||||
|
expect(app.options.packages).toEqual(['foo', 'bar']);
|
||||||
|
});
|
||||||
|
});
|
||||||
138
packages/cli/tests/prepareEntryFile.spec.ts
Normal file
138
packages/cli/tests/prepareEntryFile.spec.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import Core from '../src/Core';
|
||||||
|
import { EntryType } from '../src/types';
|
||||||
|
import { generateContent, makeCamelCase, prepareEntryFile, prettyCode } from '../src/utils/prepareEntryFile';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prepareEntryFile 内部的 writeTemp 是浮动 Promise,并且会对同一文件多次写入。
|
||||||
|
* 这里轮询直到文件内容包含期望子串再断言,避免读到中间状态的空文件。
|
||||||
|
*/
|
||||||
|
const waitForContent = async (filePath: string, expected: string, timeoutMs = 2000) => {
|
||||||
|
const start = Date.now();
|
||||||
|
while (Date.now() - start < timeoutMs) {
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
if (content.includes(expected)) return content;
|
||||||
|
}
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||||
|
}
|
||||||
|
return fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf-8') : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('makeCamelCase', () => {
|
||||||
|
test('短横线分隔的字符串转为驼峰', () => {
|
||||||
|
expect(makeCamelCase('foo-bar-baz')).toBe('fooBarBaz');
|
||||||
|
expect(makeCamelCase('foo')).toBe('foo');
|
||||||
|
expect(makeCamelCase('a-b-c-d')).toBe('aBCD');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('非字符串返回空字符串', () => {
|
||||||
|
expect(makeCamelCase(123 as unknown as string)).toBe('');
|
||||||
|
expect(makeCamelCase(null as unknown as string)).toBe('');
|
||||||
|
expect(makeCamelCase(undefined as unknown as string)).toBe('');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('prettyCode', () => {
|
||||||
|
test('转换反斜杠并美化代码', () => {
|
||||||
|
const out = prettyCode("const x: Record<string, any> = { 'a\\b': 1 };\nexport default x;");
|
||||||
|
expect(out).toContain("'a/b'");
|
||||||
|
expect(out).toContain('export default');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('generateContent', () => {
|
||||||
|
test('使用默认参数生成空对象的入口文件', () => {
|
||||||
|
const code = generateContent(true, EntryType.COMPONENT);
|
||||||
|
expect(code).toContain('const components: Record<string, any>');
|
||||||
|
expect(code).toContain('export default components');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('为组件 / 插件 / 数据源生成 default import', () => {
|
||||||
|
const code = generateContent(
|
||||||
|
true,
|
||||||
|
EntryType.COMPONENT,
|
||||||
|
{ 'foo-bar': 'foo-bar-pkg' },
|
||||||
|
{ 'foo-bar': './foo-bar/index' },
|
||||||
|
);
|
||||||
|
expect(code).toContain("import fooBar from './foo-bar/index'");
|
||||||
|
expect(code).toContain("'foo-bar': fooBar");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('config / value / event 类型并且 packagePath 与 packageMap 一致时使用具名导入', () => {
|
||||||
|
const code = generateContent(true, EntryType.CONFIG, { 'foo-bar': './pkg' }, { 'foo-bar': './pkg' });
|
||||||
|
expect(code).toContain("import { config as fooBar } from './pkg'");
|
||||||
|
expect(code).toContain("'foo-bar': fooBar");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dynamicImport 启用时使用 import() 语法', () => {
|
||||||
|
const code = generateContent(true, EntryType.COMPONENT, { foo: './foo' }, { foo: './foo/index' }, true);
|
||||||
|
expect(code).toContain("'foo': () => import('./foo/index')");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dynamicIgnore 中的 key 不走 dynamicImport', () => {
|
||||||
|
const code = generateContent(
|
||||||
|
true,
|
||||||
|
EntryType.COMPONENT,
|
||||||
|
{ foo: './foo', bar: './bar' },
|
||||||
|
{ foo: './foo/index', bar: './bar/index' },
|
||||||
|
true,
|
||||||
|
['foo'],
|
||||||
|
);
|
||||||
|
expect(code).toContain("import foo from './foo/index'");
|
||||||
|
expect(code).toContain("'foo': foo");
|
||||||
|
expect(code).toContain("'bar': () => import('./bar/index')");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('useTs=false 时不会添加类型注解', () => {
|
||||||
|
const code = generateContent(false, EntryType.COMPONENT, { foo: './foo' }, { foo: './foo' });
|
||||||
|
expect(code).not.toContain('Record<string, any>');
|
||||||
|
expect(code).toContain('const components');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('prepareEntryFile', () => {
|
||||||
|
let tmpRoot: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-prep-'));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeWriteEntry 钩子可以改写最终写入的内容', async () => {
|
||||||
|
const beforeWriteEntry = vi.fn(async (map: Record<string, string>) => ({
|
||||||
|
...map,
|
||||||
|
'comp-entry': '// custom comp entry\n',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const core = new Core({
|
||||||
|
packages: [],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
useTs: true,
|
||||||
|
hooks: { beforeWriteEntry },
|
||||||
|
});
|
||||||
|
|
||||||
|
await prepareEntryFile(core);
|
||||||
|
|
||||||
|
expect(beforeWriteEntry).toHaveBeenCalled();
|
||||||
|
|
||||||
|
const compEntry = path.join(tmpRoot, 'tmp', 'comp-entry.ts');
|
||||||
|
const content = await waitForContent(compEntry, 'custom comp entry');
|
||||||
|
expect(content).toContain('custom comp entry');
|
||||||
|
});
|
||||||
|
});
|
||||||
173
packages/cli/tests/resolveAppPackages.spec.ts
Normal file
173
packages/cli/tests/resolveAppPackages.spec.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import Core from '../src/Core';
|
||||||
|
import { resolveAppPackages } from '../src/utils/resolveAppPackages';
|
||||||
|
|
||||||
|
const writeFile = (filePath: string, content: string) => {
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
fs.writeFileSync(filePath, content);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('resolveAppPackages', () => {
|
||||||
|
let tmpRoot: string;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-resolve-'));
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('packages 为空时返回空的映射结构', () => {
|
||||||
|
const app = new Core({ packages: [], source: tmpRoot, temp: 'tmp' });
|
||||||
|
const result = resolveAppPackages(app);
|
||||||
|
expect(result).toEqual({
|
||||||
|
componentPackage: {},
|
||||||
|
componentMap: {},
|
||||||
|
configMap: {},
|
||||||
|
eventMap: {},
|
||||||
|
valueMap: {},
|
||||||
|
pluginPakcage: {},
|
||||||
|
pluginMap: {},
|
||||||
|
datasourcePackage: {},
|
||||||
|
datasourceMap: {},
|
||||||
|
dsConfigMap: {},
|
||||||
|
dsEventMap: {},
|
||||||
|
dsValueMap: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('解析普通组件目录', () => {
|
||||||
|
const pkgDir = path.join(tmpRoot, 'my-comp');
|
||||||
|
writeFile(
|
||||||
|
path.join(pkgDir, 'index.js'),
|
||||||
|
"import Foo from './Foo';\nexport default Foo;\nexport const config = {};\nexport const value = {};\n",
|
||||||
|
);
|
||||||
|
writeFile(path.join(pkgDir, 'Foo.vue'), '<template></template>');
|
||||||
|
|
||||||
|
const app = new Core({
|
||||||
|
packages: [{ 'my-comp': pkgDir }],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
componentFileAffix: '.vue',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveAppPackages(app);
|
||||||
|
|
||||||
|
expect(Object.keys(result.componentPackage)).toContain('my-comp');
|
||||||
|
expect(result.componentMap['my-comp']).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('解析插件 (export default 含 install 的对象)', () => {
|
||||||
|
const pkgDir = path.join(tmpRoot, 'my-plugin');
|
||||||
|
writeFile(path.join(pkgDir, 'index.js'), 'export default { install() {} };\n');
|
||||||
|
|
||||||
|
const app = new Core({
|
||||||
|
packages: [{ 'my-plugin': pkgDir }],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveAppPackages(app);
|
||||||
|
|
||||||
|
expect(result.pluginPakcage['my-plugin']).toBeTruthy();
|
||||||
|
expect(result.pluginMap['my-plugin']).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('解析数据源 (export default class extends DataSource)', () => {
|
||||||
|
const pkgDir = path.join(tmpRoot, 'my-ds');
|
||||||
|
writeFile(path.join(pkgDir, 'index.js'), 'export default class MyDataSource extends DataSource {}\n');
|
||||||
|
|
||||||
|
const app = new Core({
|
||||||
|
packages: [{ 'my-ds': pkgDir }],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveAppPackages(app);
|
||||||
|
|
||||||
|
expect(result.datasourcePackage['my-ds']).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('解析自定义父类的数据源 (datasoucreSuperClass)', () => {
|
||||||
|
const pkgDir = path.join(tmpRoot, 'my-custom-ds');
|
||||||
|
writeFile(path.join(pkgDir, 'index.js'), 'export default class MyDataSource extends MyBaseDS {}\n');
|
||||||
|
|
||||||
|
const app = new Core({
|
||||||
|
packages: [{ 'my-custom-ds': pkgDir }],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
datasoucreSuperClass: ['MyBaseDS'],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveAppPackages(app);
|
||||||
|
|
||||||
|
expect(result.datasourcePackage['my-custom-ds']).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('解析组件包 (export default 是包含多个子组件的对象)', () => {
|
||||||
|
const pkgDir = path.join(tmpRoot, 'my-pkg');
|
||||||
|
writeFile(path.join(pkgDir, 'package.json'), JSON.stringify({ name: 'my-pkg', main: 'index.js' }));
|
||||||
|
writeFile(
|
||||||
|
path.join(pkgDir, 'index.js'),
|
||||||
|
"import foo from './foo';\nimport bar from './bar';\nexport default { foo, bar };\n",
|
||||||
|
);
|
||||||
|
writeFile(path.join(pkgDir, 'foo/package.json'), JSON.stringify({ name: 'foo', main: 'index.js' }));
|
||||||
|
writeFile(path.join(pkgDir, 'foo/index.js'), "import FooComp from './FooComp';\nexport default FooComp;\n");
|
||||||
|
writeFile(path.join(pkgDir, 'foo/FooComp.vue'), '<template></template>');
|
||||||
|
writeFile(path.join(pkgDir, 'bar/package.json'), JSON.stringify({ name: 'bar', main: 'index.js' }));
|
||||||
|
writeFile(path.join(pkgDir, 'bar/index.js'), "import BarComp from './BarComp';\nexport default BarComp;\n");
|
||||||
|
writeFile(path.join(pkgDir, 'bar/BarComp.vue'), '<template></template>');
|
||||||
|
|
||||||
|
const app = new Core({
|
||||||
|
packages: [pkgDir],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
componentFileAffix: '.vue',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveAppPackages(app);
|
||||||
|
|
||||||
|
expect(result.componentPackage.foo).toBeTruthy();
|
||||||
|
expect(result.componentPackage.bar).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('字符串形式 packages 没有 key 时仅做解析不写入映射', () => {
|
||||||
|
const pkgDir = path.join(tmpRoot, 'no-key-comp');
|
||||||
|
writeFile(path.join(pkgDir, 'index.js'), "import Foo from './Foo';\nexport default Foo;\n");
|
||||||
|
writeFile(path.join(pkgDir, 'Foo.vue'), '<template></template>');
|
||||||
|
|
||||||
|
const app = new Core({
|
||||||
|
packages: [pkgDir],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
componentFileAffix: '.vue',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = resolveAppPackages(app);
|
||||||
|
|
||||||
|
expect(Object.keys(result.componentPackage)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('packages 为对象但找不到合法 moduleName 时抛错', () => {
|
||||||
|
const app = new Core({
|
||||||
|
packages: [{ foo: '' }],
|
||||||
|
source: tmpRoot,
|
||||||
|
temp: 'tmp',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(() => resolveAppPackages(app)).toThrowError(/packages中包含非法配置/);
|
||||||
|
});
|
||||||
|
});
|
||||||
206
packages/cli/tests/utils.spec.ts
Normal file
206
packages/cli/tests/utils.spec.ts
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
backupFile,
|
||||||
|
backupLock,
|
||||||
|
backupNpmLock,
|
||||||
|
backupPackageJson,
|
||||||
|
backupPnpmLock,
|
||||||
|
backupYarnLock,
|
||||||
|
isRootPath,
|
||||||
|
restoreFile,
|
||||||
|
restoreLock,
|
||||||
|
restoreNpmLock,
|
||||||
|
restorePackageJson,
|
||||||
|
restorePnpmLock,
|
||||||
|
restoreYarnLock,
|
||||||
|
} from '../src/utils/backupPackageFile';
|
||||||
|
import { defineConfig } from '../src/utils/defineUserConfig';
|
||||||
|
import { hasExportDefault, isPlainObject, loadUserConfig } from '../src/utils/loadUserConfig';
|
||||||
|
import * as logger from '../src/utils/logger';
|
||||||
|
|
||||||
|
describe('logger', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('info / error / success / execInfo 都会调用 console.log', () => {
|
||||||
|
logger.info('a');
|
||||||
|
logger.error('b');
|
||||||
|
logger.success('c');
|
||||||
|
logger.execInfo('d');
|
||||||
|
expect((console.log as any).mock.calls.length).toBe(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isRootPath', () => {
|
||||||
|
test('非字符串输入抛错', () => {
|
||||||
|
expect(() => isRootPath(123 as any)).toThrow(TypeError);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('空字符串与超长字符串返回 false', () => {
|
||||||
|
expect(isRootPath('')).toBe(false);
|
||||||
|
expect(isRootPath('x'.repeat(101))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Linux 根路径返回 true', () => {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
expect(isRootPath('/')).toBe(true);
|
||||||
|
expect(isRootPath('/foo')).toBe(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('两侧空白会被裁剪', () => {
|
||||||
|
if (process.platform !== 'win32') {
|
||||||
|
expect(isRootPath(' / ')).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('backupFile / restoreFile - 根路径短路', () => {
|
||||||
|
test('isRootPath 为 true 时 backupFile 与 restoreFile 直接返回', () => {
|
||||||
|
if (process.platform === 'win32') return;
|
||||||
|
// 不应抛错也不应有副作用
|
||||||
|
expect(() => backupFile('/', 'package.json')).not.toThrow();
|
||||||
|
expect(() => restoreFile('/', 'package.json')).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('backupFile / restoreFile 流程', () => {
|
||||||
|
let tmpRoot: string;
|
||||||
|
let nested: string;
|
||||||
|
beforeEach(() => {
|
||||||
|
tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-bk-'));
|
||||||
|
nested = path.join(tmpRoot, 'nested');
|
||||||
|
fs.mkdirSync(nested, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'package.json'), '{}');
|
||||||
|
});
|
||||||
|
afterEach(() => {
|
||||||
|
fs.rmSync(tmpRoot, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('在嵌套目录中向上递归找到目标后备份', () => {
|
||||||
|
backupFile(nested, 'package.json');
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'package.json.bak'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('restore 回滚备份', () => {
|
||||||
|
backupFile(nested, 'package.json');
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'package.json'), '{"changed":true}');
|
||||||
|
restoreFile(nested, 'package.json');
|
||||||
|
const restored = JSON.parse(fs.readFileSync(path.join(tmpRoot, 'package.json'), 'utf-8'));
|
||||||
|
expect(restored).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('便利函数全部能调用', () => {
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'pnpm-lock.yaml'), '');
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'yarn-lock.json'), '');
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'package-lock.json'), '');
|
||||||
|
backupPnpmLock(tmpRoot);
|
||||||
|
backupYarnLock(tmpRoot);
|
||||||
|
backupNpmLock(tmpRoot);
|
||||||
|
backupPackageJson(tmpRoot);
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'pnpm-lock.yaml.bak'))).toBe(true);
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'yarn-lock.json.bak'))).toBe(true);
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'package-lock.json.bak'))).toBe(true);
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'package.json.bak'))).toBe(true);
|
||||||
|
|
||||||
|
restorePnpmLock(tmpRoot);
|
||||||
|
restoreYarnLock(tmpRoot);
|
||||||
|
restoreNpmLock(tmpRoot);
|
||||||
|
restorePackageJson(tmpRoot);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('backupLock / restoreLock 走对应 npm 类型', () => {
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'pnpm-lock.yaml'), '');
|
||||||
|
backupLock(tmpRoot, 'pnpm');
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'pnpm-lock.yaml.bak'))).toBe(true);
|
||||||
|
restoreLock(tmpRoot, 'pnpm');
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'yarn-lock.json'), '');
|
||||||
|
backupLock(tmpRoot, 'yarn');
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'yarn-lock.json.bak'))).toBe(true);
|
||||||
|
restoreLock(tmpRoot, 'yarn');
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(tmpRoot, 'package-lock.json'), '');
|
||||||
|
backupLock(tmpRoot, 'npm');
|
||||||
|
expect(fs.existsSync(path.join(tmpRoot, 'package-lock.json.bak'))).toBe(true);
|
||||||
|
restoreLock(tmpRoot, 'npm');
|
||||||
|
|
||||||
|
backupLock(tmpRoot, 'unknown');
|
||||||
|
restoreLock(tmpRoot, 'unknown');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('defineConfig', () => {
|
||||||
|
test('原样返回输入', () => {
|
||||||
|
const cfg = { source: '.', temp: 'tmp', packages: [] };
|
||||||
|
expect(defineConfig(cfg as any)).toBe(cfg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loadUserConfig 与 isPlainObject / hasExportDefault', () => {
|
||||||
|
test('isPlainObject', () => {
|
||||||
|
expect(isPlainObject({})).toBe(true);
|
||||||
|
expect(isPlainObject({ a: 1 })).toBe(true);
|
||||||
|
expect(isPlainObject([])).toBe(false);
|
||||||
|
expect(isPlainObject(null)).toBe(false);
|
||||||
|
expect(isPlainObject('s')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasExportDefault 仅识别 __esModule + default', () => {
|
||||||
|
expect(hasExportDefault({ __esModule: true, default: 1 })).toBe(true);
|
||||||
|
expect(hasExportDefault({ default: 1 })).toBe(false);
|
||||||
|
expect(hasExportDefault({ __esModule: true })).toBe(false);
|
||||||
|
expect(hasExportDefault('x')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loadUserConfig - 没有 path 时返回 {}', async () => {
|
||||||
|
expect(await loadUserConfig()).toEqual({});
|
||||||
|
expect(await loadUserConfig('')).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loadUserConfig - 不匹配的扩展名返回 {}', async () => {
|
||||||
|
expect(await loadUserConfig('/path/file.json')).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loadUserConfig - 加载真实 .js 配置文件 (CommonJS 默认导出)', async () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-cfg-'));
|
||||||
|
const cfg = path.join(tmp, 'cfg.js');
|
||||||
|
fs.writeFileSync(cfg, "module.exports = { source: '.', temp: 'tmp', packages: [], useTs: true };\n");
|
||||||
|
try {
|
||||||
|
const config = await loadUserConfig(cfg);
|
||||||
|
expect(config).toMatchObject({ useTs: true, packages: [] });
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tmp, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('loadUserConfig - 加载 ESM-style __esModule + default 配置', async () => {
|
||||||
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-cfg-esm-'));
|
||||||
|
const cfg = path.join(tmp, 'cfg.js');
|
||||||
|
fs.writeFileSync(
|
||||||
|
cfg,
|
||||||
|
"Object.defineProperty(exports, '__esModule', { value: true });\n" +
|
||||||
|
"exports.default = { source: '.', temp: 'tmp', packages: [], useTs: false };\n",
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
const config = await loadUserConfig(cfg);
|
||||||
|
expect(config).toMatchObject({ useTs: false });
|
||||||
|
} finally {
|
||||||
|
fs.rmSync(tmp, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.14-beta.0",
|
"version": "1.8.0-beta.4",
|
||||||
"name": "@tmagic/core",
|
"name": "@tmagic/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -244,7 +244,7 @@ class App extends EventEmitter {
|
|||||||
node.data?.id &&
|
node.data?.id &&
|
||||||
node.eventKeys.has(`${String(name)}_${node.data.id}`)
|
node.eventKeys.has(`${String(name)}_${node.data.id}`)
|
||||||
) {
|
) {
|
||||||
return this.eventHelper.emit(node.eventKeys.get(`${String(name)}_${node.data.id}`)!, node, ...otherArgs);
|
this.eventHelper.emit(node.eventKeys.get(`${String(name)}_${node.data.id}`)!, node, ...otherArgs);
|
||||||
}
|
}
|
||||||
return super.emit(name, ...args);
|
return super.emit(name, ...args);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -134,7 +134,9 @@ export const transformStyle = (style: Record<string, any> | string, jsEngine: Js
|
|||||||
export const COMMON_EVENT_PREFIX = 'magic:common:events:';
|
export const COMMON_EVENT_PREFIX = 'magic:common:events:';
|
||||||
export const COMMON_METHOD_PREFIX = 'magic:common:actions:';
|
export const COMMON_METHOD_PREFIX = 'magic:common:actions:';
|
||||||
|
|
||||||
|
// #region EventOption
|
||||||
export interface EventOption {
|
export interface EventOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
// #endregion EventOption
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import { MApp, NodeType } from '@tmagic/schema';
|
import { MApp, NodeType } from '@tmagic/schema';
|
||||||
|
|
||||||
import App from '../src/App';
|
import App from '../src/App';
|
||||||
import TMagicIteratorContainer from '../src/IteratorContainer';
|
import TMagicIteratorContainer from '../src/IteratorContainer';
|
||||||
|
import Node from '../src/Node';
|
||||||
|
|
||||||
const createAppDsl = (pageLength: number, nodeLength = 0) => {
|
const createAppDsl = (pageLength: number, nodeLength = 0) => {
|
||||||
const dsl: MApp = {
|
const dsl: MApp = {
|
||||||
@ -263,3 +264,236 @@ describe('App', () => {
|
|||||||
expect(ic2?.nodes.length).toBe(0);
|
expect(ic2?.nodes.length).toBe(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('App 配置/方法/组件注册', () => {
|
||||||
|
test('platform=editor 时不创建 eventHelper', () => {
|
||||||
|
const app = new App({ platform: 'editor' });
|
||||||
|
expect(app.eventHelper).toBeUndefined();
|
||||||
|
expect(app.platform).toBe('editor');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('disabledFlexible 时不创建 flexible', () => {
|
||||||
|
const app = new App({ disabledFlexible: true });
|
||||||
|
expect((app as any).flexible).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('设置自定义 iteratorContainerType / pageFragmentContainerType', () => {
|
||||||
|
const app = new App({
|
||||||
|
iteratorContainerType: ['my-iter', 'custom-iter'],
|
||||||
|
pageFragmentContainerType: 'my-frag',
|
||||||
|
});
|
||||||
|
expect(app.iteratorContainerType.has('my-iter')).toBe(true);
|
||||||
|
expect(app.iteratorContainerType.has('custom-iter')).toBe(true);
|
||||||
|
expect(app.pageFragmentContainerType.has('my-frag')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('useMock=true 透传到 DataSourceManager', () => {
|
||||||
|
const app = new App({ useMock: true });
|
||||||
|
expect(app.useMock).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registerComponent / resolveComponent / unregisterComponent', () => {
|
||||||
|
const app = new App({});
|
||||||
|
const comp = { tag: 'x' };
|
||||||
|
app.registerComponent('my', comp);
|
||||||
|
expect(app.resolveComponent('my')).toBe(comp);
|
||||||
|
app.unregisterComponent('my');
|
||||||
|
expect(app.resolveComponent('my')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registerNode 静态方法存入 nodeClassMap', () => {
|
||||||
|
class Custom extends Node {}
|
||||||
|
App.registerNode('custom-type', Custom);
|
||||||
|
expect(App.nodeClassMap.get('custom-type')).toBe(Custom);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setEnv 接受字符串/Env 实例', () => {
|
||||||
|
const app = new App({});
|
||||||
|
app.setEnv();
|
||||||
|
expect(app.env).toBeDefined();
|
||||||
|
app.setEnv('Mozilla/5.0');
|
||||||
|
expect(app.env).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getPage / getNode 默认返回当前 page', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ type: NodeType.PAGE, id: 'p1', items: [{ id: 'btn', type: 'button' }] }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(app.getPage()).toBe(app.page);
|
||||||
|
expect(app.getPage('p1')).toBe(app.page);
|
||||||
|
expect(app.getPage('not-exist')).toBeUndefined();
|
||||||
|
expect(app.getNode('btn')?.data.id).toBe('btn');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setPage 不存在时清空当前 page', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.setPage('not-exist');
|
||||||
|
expect(app.page).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runCode 执行代码块', async () => {
|
||||||
|
const fn = vi.fn();
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
|
||||||
|
codeBlocks: { c1: { name: 'c1', content: fn, params: [] } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await app.runCode('c1', { p: 1 }, []);
|
||||||
|
expect(fn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runCode 抛错时进入 errorHandler', async () => {
|
||||||
|
const errorHandler = vi.fn();
|
||||||
|
const app = new App({
|
||||||
|
errorHandler,
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
|
||||||
|
codeBlocks: {
|
||||||
|
c1: {
|
||||||
|
name: 'c1',
|
||||||
|
content: () => {
|
||||||
|
throw new Error('boom');
|
||||||
|
},
|
||||||
|
params: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await app.runCode('c1', {}, []);
|
||||||
|
expect(errorHandler).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runDataSourceMethod 调用 schema methods 中的 content', async () => {
|
||||||
|
const fn = vi.fn().mockResolvedValue('ok');
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_1',
|
||||||
|
fields: [],
|
||||||
|
methods: [{ name: 'doIt', content: fn, params: [] }],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
await app.runDataSourceMethod('ds_1', 'doIt', { p: 1 }, []);
|
||||||
|
expect(fn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runDataSourceMethod 不存在的数据源直接返回', async () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ type: NodeType.PAGE, id: 'p1', items: [] }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await expect(app.runDataSourceMethod('not', 'm', {}, [])).resolves.toBeUndefined();
|
||||||
|
await expect(app.runDataSourceMethod('', '', {}, [])).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit 触发 node 事件时走 eventHelper', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'p1',
|
||||||
|
items: [{ id: 'btn', type: 'button', events: [{ name: 'click', actions: [] }] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
const node = app.getNode('btn')!;
|
||||||
|
const result = app.emit('click', node, 'arg1');
|
||||||
|
expect(typeof result).toBe('boolean');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 回归用例:节点配置了 events 时,eventHelper 派发不能短路掉 super.emit,
|
||||||
|
// 即 app.on(name, cb) 注册的回调依然要被触发。
|
||||||
|
test('emit: 节点已绑定 events 时,app.on 注册的监听器仍然会被调用', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'p1',
|
||||||
|
items: [{ id: 'btn', type: 'button', events: [{ name: 'click', actions: [] }] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
const node = app.getNode('btn')!;
|
||||||
|
const cb = vi.fn();
|
||||||
|
app.on('click', cb);
|
||||||
|
|
||||||
|
const result = app.emit('click', node, 'arg1');
|
||||||
|
|
||||||
|
expect(cb).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cb).toHaveBeenCalledWith(node, 'arg1');
|
||||||
|
// EventEmitter.emit 在有 listener 时返回 true
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit: 未命中节点 eventKeys 时,app.on 注册的监听器正常被调用', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'p1',
|
||||||
|
items: [{ id: 'btn', type: 'button' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
const node = app.getNode('btn')!;
|
||||||
|
const cb = vi.fn();
|
||||||
|
app.on('click', cb);
|
||||||
|
|
||||||
|
app.emit('click', node, 'arg1');
|
||||||
|
|
||||||
|
expect(cb).toHaveBeenCalledTimes(1);
|
||||||
|
expect(cb).toHaveBeenCalledWith(node, 'arg1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 清理所有资源', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ type: NodeType.PAGE, id: 'p1', items: [{ id: 'btn', type: 'button' }] }],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
app.destroy();
|
||||||
|
expect(app.page).toBeUndefined();
|
||||||
|
expect(app.dsl).toBeUndefined();
|
||||||
|
expect(app.components.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
834
packages/core/tests/EventHelper.spec.ts
Normal file
834
packages/core/tests/EventHelper.spec.ts
Normal file
@ -0,0 +1,834 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*/
|
||||||
|
import { beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActionType,
|
||||||
|
type MApp,
|
||||||
|
NODE_DISABLE_CODE_BLOCK_KEY,
|
||||||
|
NODE_DISABLE_DATA_SOURCE_KEY,
|
||||||
|
NodeType,
|
||||||
|
} from '@tmagic/schema';
|
||||||
|
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
|
||||||
|
|
||||||
|
import App from '../src/App';
|
||||||
|
import EventHelper from '../src/EventHelper';
|
||||||
|
import FlowState from '../src/FlowState';
|
||||||
|
|
||||||
|
const flushAsync = () => new Promise((r) => setTimeout(r, 0));
|
||||||
|
|
||||||
|
const createDsl = (overrides: Partial<MApp> = {}): MApp => ({
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'btn_2',
|
||||||
|
type: 'button',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EventHelper 构造与销毁', () => {
|
||||||
|
test('实例化继承 EventEmitter 并保存 app 引用', () => {
|
||||||
|
const app = new App({});
|
||||||
|
const helper = new EventHelper({ app });
|
||||||
|
expect(helper).toBeInstanceOf(EventHelper);
|
||||||
|
expect(helper.app).toBe(app);
|
||||||
|
expect(helper.eventQueue).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 清空内部状态与监听器', () => {
|
||||||
|
const app = new App({ config: createDsl() });
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
const handler = vi.fn();
|
||||||
|
helper.on('foo', handler);
|
||||||
|
|
||||||
|
helper.destroy();
|
||||||
|
helper.emit('foo');
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
expect((helper as any).nodeEventList.size).toBe(0);
|
||||||
|
expect((helper as any).dataSourceEventList.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EventHelper - bindNodeEvents / initEvents / removeNodeEvents', () => {
|
||||||
|
test('忽略没有 name 的事件配置', () => {
|
||||||
|
const app = new App({ config: createDsl() });
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
const node = app.getNode('btn_1')!;
|
||||||
|
|
||||||
|
node.events = [{ name: '', actions: [] } as any];
|
||||||
|
helper.bindNodeEvents(node);
|
||||||
|
|
||||||
|
expect((helper as any).nodeEventList.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('为带 name 的事件创建 symbol 并写入 eventKeys', () => {
|
||||||
|
const app = new App({ config: createDsl() });
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
const node = app.getNode('btn_1')!;
|
||||||
|
|
||||||
|
node.events = [{ name: 'click', actions: [] }];
|
||||||
|
node.eventKeys.clear();
|
||||||
|
helper.bindNodeEvents(node);
|
||||||
|
|
||||||
|
expect(node.eventKeys.has(`click_${node.data.id}`)).toBe(true);
|
||||||
|
expect((helper as any).nodeEventList.size).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('已存在的 eventKey 会被复用而不是重新创建', () => {
|
||||||
|
const app = new App({ config: createDsl() });
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
const node = app.getNode('btn_1')!;
|
||||||
|
|
||||||
|
const existingSymbol = Symbol('click_btn_1');
|
||||||
|
node.eventKeys.set(`click_${node.data.id}`, existingSymbol);
|
||||||
|
node.events = [{ name: 'click', actions: [] }];
|
||||||
|
|
||||||
|
helper.bindNodeEvents(node);
|
||||||
|
expect(node.eventKeys.get(`click_${node.data.id}`)).toBe(existingSymbol);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('${nodeId}.${eventName} 形式将命名空间转换为 ${eventName}_${nodeId}', () => {
|
||||||
|
const app = new App({ config: createDsl() });
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
const node = app.getNode('btn_1')!;
|
||||||
|
|
||||||
|
node.events = [{ name: 'btn_2.click', actions: [] }];
|
||||||
|
node.eventKeys.clear();
|
||||||
|
|
||||||
|
helper.bindNodeEvents(node);
|
||||||
|
expect(node.eventKeys.has('click_btn_2')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initEvents 会为 page 和 pageFragments 内的节点都绑定事件', () => {
|
||||||
|
const dsl = createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'pf_container',
|
||||||
|
type: 'page-fragment-container',
|
||||||
|
pageFragmentId: 'pf_1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'btn_in_page',
|
||||||
|
type: 'button',
|
||||||
|
events: [{ name: 'click', actions: [] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_in_pf',
|
||||||
|
type: 'button',
|
||||||
|
events: [{ name: 'click', actions: [] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
const app = new App({ config: dsl });
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
expect(app.pageFragments.size).toBe(1);
|
||||||
|
|
||||||
|
helper.initEvents();
|
||||||
|
expect((helper as any).nodeEventList.size).toBeGreaterThanOrEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeNodeEvents 会移除全部 node 上注册的监听', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [{ name: 'click', actions: [] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
expect((helper as any).nodeEventList.size).toBe(1);
|
||||||
|
|
||||||
|
helper.removeNodeEvents();
|
||||||
|
expect((helper as any).nodeEventList.size).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EventHelper - 事件队列', () => {
|
||||||
|
test('addEventToQueue / getEventQueue', () => {
|
||||||
|
const app = new App({});
|
||||||
|
const helper = new EventHelper({ app });
|
||||||
|
|
||||||
|
helper.addEventToQueue({ toId: 'x', method: 'm', fromCpt: null, args: [1] });
|
||||||
|
expect(helper.getEventQueue()).toHaveLength(1);
|
||||||
|
expect(helper.getEventQueue()[0].toId).toBe('x');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EventHelper - eventHandler / actionHandler 流程', () => {
|
||||||
|
let beforeHandler: ReturnType<typeof vi.fn>;
|
||||||
|
let afterHandler: ReturnType<typeof vi.fn>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
beforeHandler = vi.fn();
|
||||||
|
afterHandler = vi.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('emit click 时执行 EventConfig.actions 并触发 before/after 钩子', async () => {
|
||||||
|
const fromInstance = { doIt: vi.fn() };
|
||||||
|
const toInstance = { doIt: vi.fn() };
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
beforeEventHandler: beforeHandler,
|
||||||
|
afterEventHandler: afterHandler,
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [{ actionType: ActionType.COMP, to: 'btn_2', method: 'doIt' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: 'btn_2', type: 'button' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
const fromNode = app.getNode('btn_1')!;
|
||||||
|
const toNode = app.getNode('btn_2')!;
|
||||||
|
fromNode.setInstance(fromInstance);
|
||||||
|
toNode.setInstance(toInstance);
|
||||||
|
|
||||||
|
app.emit('click', fromNode, 'extraArg');
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect(beforeHandler).toHaveBeenCalled();
|
||||||
|
expect(afterHandler).toHaveBeenCalled();
|
||||||
|
expect(toInstance.doIt).toHaveBeenCalled();
|
||||||
|
expect(toInstance.doIt.mock.calls[0][1]).toBe('extraArg');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('actions 中如果 flowState.isAbort 为 true 会中断后续 action', async () => {
|
||||||
|
const action2Spy = vi.fn();
|
||||||
|
const codeBlocks = {
|
||||||
|
abortCode: {
|
||||||
|
name: 'abortCode',
|
||||||
|
params: [],
|
||||||
|
content: ({ flowState }: any) => {
|
||||||
|
flowState.abort();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldNotRun: {
|
||||||
|
name: 'shouldNotRun',
|
||||||
|
params: [],
|
||||||
|
content: action2Spy,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [
|
||||||
|
{ actionType: ActionType.CODE, codeId: 'abortCode' } as any,
|
||||||
|
{ actionType: ActionType.CODE, codeId: 'shouldNotRun' } as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
codeBlocks,
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect(action2Spy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('CODE action 在 NODE_DISABLE_CODE_BLOCK_KEY=true 时跳过', async () => {
|
||||||
|
const codeFn = vi.fn();
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
codeBlocks: { c: { name: 'c', params: [], content: codeFn } },
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
[NODE_DISABLE_CODE_BLOCK_KEY]: true,
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [{ actionType: ActionType.CODE, codeId: 'c' } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
expect(codeFn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DATA_SOURCE action 正常执行时通过 runDataSourceMethod 调用', async () => {
|
||||||
|
const methodFn = vi.fn().mockResolvedValue('ok');
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [],
|
||||||
|
events: [],
|
||||||
|
methods: [{ name: 'fetch', params: [], content: methodFn }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
actionType: ActionType.DATA_SOURCE,
|
||||||
|
dataSourceMethod: ['ds_1', 'fetch'],
|
||||||
|
params: { x: 1 },
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
await flushAsync();
|
||||||
|
expect(methodFn).toHaveBeenCalled();
|
||||||
|
expect(methodFn.mock.calls[0][0].params).toEqual({ x: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('DATA_SOURCE action 在 NODE_DISABLE_DATA_SOURCE_KEY=true 时跳过', async () => {
|
||||||
|
const methodFn = vi.fn();
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [],
|
||||||
|
events: [],
|
||||||
|
methods: [{ name: 'fetch', params: [], content: methodFn }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
[NODE_DISABLE_DATA_SOURCE_KEY]: true,
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
actionType: ActionType.DATA_SOURCE,
|
||||||
|
dataSourceMethod: ['ds_1', 'fetch'],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
expect(methodFn).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('actionHandler 抛错时调用 errorHandler', async () => {
|
||||||
|
const errorHandler = vi.fn();
|
||||||
|
const app = new App({
|
||||||
|
errorHandler,
|
||||||
|
config: createDsl({
|
||||||
|
codeBlocks: {
|
||||||
|
boom: {
|
||||||
|
name: 'boom',
|
||||||
|
params: [],
|
||||||
|
content: () => {
|
||||||
|
throw new Error('boom');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [{ actionType: ActionType.CODE, codeId: 'boom' } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
await flushAsync();
|
||||||
|
expect(errorHandler).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('兼容 DeprecatedEventConfig:没有 actions 字段时走 compActionHandler', async () => {
|
||||||
|
const targetInstance = { ping: vi.fn() };
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [{ name: 'click', to: 'btn_2', method: 'ping' } as any],
|
||||||
|
},
|
||||||
|
{ id: 'btn_2', type: 'button' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
const fromNode = app.getNode('btn_1')!;
|
||||||
|
app.getNode('btn_2')!.setInstance(targetInstance);
|
||||||
|
|
||||||
|
app.emit('click', fromNode);
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect(targetInstance.ping).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compActionHandler 找不到目标节点时进入 eventQueue', async () => {
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [{ actionType: ActionType.COMP, to: 'not-exist', method: 'foo' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect(app.eventHelper!.getEventQueue()).toHaveLength(1);
|
||||||
|
expect(app.eventHelper!.getEventQueue()[0].toId).toBe('not-exist');
|
||||||
|
expect(app.eventHelper!.getEventQueue()[0].method).toBe('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compActionHandler:目标节点没有 instance 时方法入 node.eventQueue', async () => {
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [{ actionType: ActionType.COMP, to: 'btn_2', method: 'foo' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: 'btn_2', type: 'button' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
const targetNode = app.getNode('btn_2')!;
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect((targetNode as any).eventQueue).toHaveLength(1);
|
||||||
|
expect((targetNode as any).eventQueue[0].method).toBe('foo');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compActionHandler:method 是数组时取 [to, method]', async () => {
|
||||||
|
const targetInstance = { hi: vi.fn() };
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [{ actionType: ActionType.COMP, method: ['btn_2', 'hi'] } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ id: 'btn_2', type: 'button' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
app.getNode('btn_2')!.setInstance(targetInstance);
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect(targetInstance.hi).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compActionHandler:当前没有 page 时抛错被 errorHandler 捕获(兼容旧配置)', async () => {
|
||||||
|
const errorHandler = vi.fn();
|
||||||
|
const app = new App({
|
||||||
|
errorHandler,
|
||||||
|
config: createDsl({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [{ name: 'click', to: 'btn_2', method: 'foo' } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
const node = app.getNode('btn_1')!;
|
||||||
|
app.page = undefined;
|
||||||
|
|
||||||
|
app.emit('click', node);
|
||||||
|
await flushAsync();
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect(errorHandler).toHaveBeenCalled();
|
||||||
|
const lastErr = errorHandler.mock.calls[errorHandler.mock.calls.length - 1][0];
|
||||||
|
expect(lastErr).toBeInstanceOf(Error);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compActionHandler:在 pageFragments 中也能找到目标节点', async () => {
|
||||||
|
const app = new App({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{ id: 'pf_container', type: 'page-fragment-container', pageFragmentId: 'pf_1' },
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'button',
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'click',
|
||||||
|
actions: [{ actionType: ActionType.COMP, to: 'btn_in_pf', method: 'go' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [{ id: 'btn_in_pf', type: 'button' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
const target = app.pageFragments.get('pf_container')!.getNode('btn_in_pf')!;
|
||||||
|
const inst = { go: vi.fn() };
|
||||||
|
target.setInstance(inst);
|
||||||
|
|
||||||
|
app.emit('click', app.getNode('btn_1')!);
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
expect(inst.go).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EventHelper - bindDataSourceEvents / removeDataSourceEvents', () => {
|
||||||
|
test('为数据源 schema.events 中自定义事件绑定监听', async () => {
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [],
|
||||||
|
methods: [],
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'change',
|
||||||
|
actions: [{ actionType: ActionType.CODE, codeId: 'logCode' } as any],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
codeBlocks: {
|
||||||
|
logCode: { name: 'logCode', params: [], content: vi.fn() },
|
||||||
|
},
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
await flushAsync();
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
expect(helper).toBeDefined();
|
||||||
|
expect((helper as any).dataSourceEventList.has('ds_1')).toBe(true);
|
||||||
|
const dsEvents: Map<string, any> = (helper as any).dataSourceEventList.get('ds_1');
|
||||||
|
expect(dsEvents.has('change')).toBe(true);
|
||||||
|
|
||||||
|
const ds = app.dataSourceManager!.get('ds_1')!;
|
||||||
|
expect(ds.listenerCount('change')).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('数据源字段变更事件 (DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) 通过 onDataChange 注册', async () => {
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ type: 'string', name: 'foo', title: 'foo', defaultValue: '', enable: true }],
|
||||||
|
methods: [],
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: `${DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX}.foo`,
|
||||||
|
actions: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
await flushAsync();
|
||||||
|
|
||||||
|
const ds = app.dataSourceManager!.get('ds_1')!;
|
||||||
|
const onDataChangeSpy = vi.spyOn(ds, 'onDataChange');
|
||||||
|
app.eventHelper!.bindDataSourceEvents();
|
||||||
|
expect(onDataChangeSpy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('event.name 为空字符串时跳过绑定', () => {
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [],
|
||||||
|
methods: [],
|
||||||
|
events: [{ name: '', actions: [] } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
expect(() => helper.bindDataSourceEvents()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeDataSourceEvents:当 dataSourceEventList 为空时直接返回', () => {
|
||||||
|
const app = new App({});
|
||||||
|
const helper = new EventHelper({ app });
|
||||||
|
expect(() => helper.removeDataSourceEvents([])).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeDataSourceEvents 会同时清理 onDataChange 与普通事件', async () => {
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ type: 'string', name: 'foo', title: 'foo', defaultValue: '', enable: true }],
|
||||||
|
methods: [],
|
||||||
|
events: [
|
||||||
|
{ name: 'change', actions: [] } as any,
|
||||||
|
{ name: `${DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX}.foo`, actions: [] } as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
await flushAsync();
|
||||||
|
const helper = app.eventHelper!;
|
||||||
|
const ds = app.dataSourceManager!.get('ds_1')!;
|
||||||
|
const offSpy = vi.spyOn(ds, 'off');
|
||||||
|
const offDataChangeSpy = vi.spyOn(ds, 'offDataChange');
|
||||||
|
|
||||||
|
helper.removeDataSourceEvents([ds]);
|
||||||
|
|
||||||
|
expect(offSpy).toHaveBeenCalled();
|
||||||
|
expect(offDataChangeSpy).toHaveBeenCalled();
|
||||||
|
expect((helper as any).dataSourceEventList.size).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('数据源触发自定义事件后会调用配置的 action', async () => {
|
||||||
|
const codeFn = vi.fn();
|
||||||
|
const app = new App({
|
||||||
|
config: createDsl({
|
||||||
|
codeBlocks: { c: { name: 'c', params: [], content: codeFn } },
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [],
|
||||||
|
methods: [],
|
||||||
|
events: [
|
||||||
|
{
|
||||||
|
name: 'change',
|
||||||
|
actions: [{ actionType: ActionType.CODE, codeId: 'c' } as any],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as any),
|
||||||
|
});
|
||||||
|
|
||||||
|
await flushAsync();
|
||||||
|
const ds = app.dataSourceManager!.get('ds_1')!;
|
||||||
|
ds.setData({ a: 1 });
|
||||||
|
await flushAsync();
|
||||||
|
expect(codeFn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EventHelper - flowState 状态管理', () => {
|
||||||
|
test('FlowState abort/reset 行为', () => {
|
||||||
|
const fs = new FlowState();
|
||||||
|
expect(fs.isAbort).toBe(false);
|
||||||
|
fs.abort();
|
||||||
|
expect(fs.isAbort).toBe(true);
|
||||||
|
fs.reset();
|
||||||
|
expect(fs.isAbort).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
57
packages/core/tests/Flexible.spec.ts
Normal file
57
packages/core/tests/Flexible.spec.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import Flexible from '../src/Flexible';
|
||||||
|
|
||||||
|
describe('Flexible', () => {
|
||||||
|
test('实例化默认 designWidth=375 并设置 fontSize', () => {
|
||||||
|
const f = new Flexible();
|
||||||
|
expect(f.designWidth).toBe(375);
|
||||||
|
expect(globalThis.document.body.style.fontSize).toBeDefined();
|
||||||
|
f.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('options.designWidth 触发 refreshRem 与 fontSize 写入', () => {
|
||||||
|
const f = new Flexible({ designWidth: 750 });
|
||||||
|
expect(f.designWidth).toBe(750);
|
||||||
|
expect(globalThis.document.documentElement.style.fontSize).toMatch(/px$/);
|
||||||
|
f.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setDesignWidth 更新数值并 refresh', () => {
|
||||||
|
const f = new Flexible();
|
||||||
|
f.setDesignWidth(414);
|
||||||
|
expect(f.designWidth).toBe(414);
|
||||||
|
f.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('correctRem 根据计算偏差调整字体', () => {
|
||||||
|
const f = new Flexible();
|
||||||
|
const fontSize = 100;
|
||||||
|
const result = f.correctRem(fontSize);
|
||||||
|
expect(typeof result).toBe('number');
|
||||||
|
f.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('resize 事件 debounce 调用 refreshRem', async () => {
|
||||||
|
const f = new Flexible();
|
||||||
|
const spy = vi.spyOn(f, 'refreshRem').mockImplementation(() => undefined);
|
||||||
|
globalThis.dispatchEvent(new Event('resize'));
|
||||||
|
await new Promise((r) => setTimeout(r, 350));
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
spy.mockRestore();
|
||||||
|
f.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pageshow persisted 触发 resize 处理', () => {
|
||||||
|
const f = new Flexible();
|
||||||
|
const evt = new Event('pageshow') as any;
|
||||||
|
evt.persisted = true;
|
||||||
|
globalThis.dispatchEvent(evt);
|
||||||
|
f.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
126
packages/core/tests/Node.spec.ts
Normal file
126
packages/core/tests/Node.spec.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { type MApp, NodeType } from '@tmagic/schema';
|
||||||
|
|
||||||
|
import App from '../src/App';
|
||||||
|
import Node from '../src/Node';
|
||||||
|
|
||||||
|
const baseDsl = (): MApp => ({
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'p1',
|
||||||
|
items: [{ id: 'btn', type: 'button' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Node 基础', () => {
|
||||||
|
test('实例化时初始化 events / style 默认值', () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
expect(node).toBeInstanceOf(Node);
|
||||||
|
expect(node.events).toEqual([]);
|
||||||
|
expect(node.style).toEqual({});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setData 更新 events / style 并触发 update-data 事件', () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
const handler = vi.fn();
|
||||||
|
node.on('update-data', handler);
|
||||||
|
node.setData({
|
||||||
|
id: 'btn',
|
||||||
|
type: 'button',
|
||||||
|
events: [{ name: 'click', actions: [] }],
|
||||||
|
style: { color: 'red' },
|
||||||
|
} as any);
|
||||||
|
expect(handler).toHaveBeenCalled();
|
||||||
|
expect(node.events).toHaveLength(1);
|
||||||
|
expect(node.style.color).toBe('red');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setInstance 与 setData 同步实例的 config', () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
const instance: any = {};
|
||||||
|
node.setInstance(instance);
|
||||||
|
node.setData({ id: 'btn', type: 'button', text: 'changed' } as any);
|
||||||
|
expect(instance.config?.text).toBe('changed');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('frozen instance 时 setData 不抛错', () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
const frozen = Object.freeze({ __isVue: false });
|
||||||
|
node.setInstance(frozen);
|
||||||
|
expect(() => node.setData({ id: 'btn', type: 'button' } as any)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('addEventToQueue 入队', () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
node.addEventToQueue({ method: 'm', fromCpt: null, args: [1, 2] });
|
||||||
|
expect((node as any).eventQueue).toHaveLength(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registerMethod (deprecated) 注入实例方法', () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
node.registerMethod({ doIt: () => 'ok', notFn: 'x' as any });
|
||||||
|
expect(node.instance.doIt()).toBe('ok');
|
||||||
|
expect(node.instance.notFn).toBeUndefined();
|
||||||
|
node.registerMethod(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runHookCode 函数式回退', async () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
const fn = vi.fn();
|
||||||
|
(node.data as any).created = fn;
|
||||||
|
await node.runHookCode('created');
|
||||||
|
expect(fn).toHaveBeenCalledWith(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('runHookCode 数据格式不匹配时不报错', async () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
(node.data as any).onSomething = { hookType: 'other' };
|
||||||
|
await expect(node.runHookCode('onSomething')).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 清理状态与监听', () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
const handler = vi.fn();
|
||||||
|
node.on('test', handler);
|
||||||
|
node.destroy();
|
||||||
|
node.emit('test');
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
expect(node.instance).toBeNull();
|
||||||
|
expect(node.events).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('created/destroy 生命周期触发 hook', async () => {
|
||||||
|
const app = new App({ config: baseDsl() });
|
||||||
|
const codeFn = vi.fn();
|
||||||
|
app.codeDsl = {
|
||||||
|
hello: { name: 'hello', content: codeFn, params: [] },
|
||||||
|
} as any;
|
||||||
|
const node = app.page!.getNode('btn')!;
|
||||||
|
(node.data as any).created = {
|
||||||
|
hookType: 'code',
|
||||||
|
hookData: [{ codeId: 'hello', params: {} }],
|
||||||
|
};
|
||||||
|
node.emit('created', null);
|
||||||
|
await new Promise((r) => setTimeout(r, 0));
|
||||||
|
expect(codeFn).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.14-beta.0",
|
"version": "1.8.0-beta.4",
|
||||||
"name": "@tmagic/data-source",
|
"name": "@tmagic/data-source",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -241,7 +241,7 @@ export const registerDataSourceOnDemand = async (
|
|||||||
dsl: MApp,
|
dsl: MApp,
|
||||||
dataSourceModules: Record<string, () => Promise<AsyncDataSourceResolveResult>>,
|
dataSourceModules: Record<string, () => Promise<AsyncDataSourceResolveResult>>,
|
||||||
) => {
|
) => {
|
||||||
const { dataSourceMethodsDeps = {}, dataSourceCondDeps = {}, dataSourceDeps = {}, dataSources = [] } = dsl;
|
const { dataSourceMethodDeps = {}, dataSourceCondDeps = {}, dataSourceDeps = {}, dataSources = [] } = dsl;
|
||||||
|
|
||||||
const dsModuleMap: Record<string, () => Promise<AsyncDataSourceResolveResult>> = {};
|
const dsModuleMap: Record<string, () => Promise<AsyncDataSourceResolveResult>> = {};
|
||||||
|
|
||||||
@ -253,7 +253,7 @@ export const registerDataSourceOnDemand = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!Object.keys(dep).length) {
|
if (!Object.keys(dep).length) {
|
||||||
dep = dataSourceMethodsDeps[ds.id] || {};
|
dep = dataSourceMethodDeps[ds.id] || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Object.keys(dep).length && dataSourceModules[ds.type]) {
|
if (Object.keys(dep).length && dataSourceModules[ds.type]) {
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import App from '@tmagic/core';
|
import App from '@tmagic/core';
|
||||||
|
|
||||||
import { DataSource } from '@data-source/index';
|
import { DataSource } from '@data-source/index';
|
||||||
|
import { DeepObservedData } from '@data-source/observed-data';
|
||||||
|
|
||||||
describe('DataSource', () => {
|
describe('DataSource', () => {
|
||||||
test('instance', () => {
|
test('instance', () => {
|
||||||
@ -111,3 +112,130 @@ describe('DataSource setData', () => {
|
|||||||
expect(ds.data.obj.a).toBe('a1');
|
expect(ds.data.obj.a).toBe('a1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DataSource lifecycle / mock', () => {
|
||||||
|
test('编辑器中使用 mock 数据', () => {
|
||||||
|
const app = new App({}) as any;
|
||||||
|
app.platform = 'editor';
|
||||||
|
const ds = new DataSource({
|
||||||
|
app,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: '1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
mocks: [{ useInEditor: true, data: { name: 'mock' }, enable: true }],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
expect(ds.data.name).toBe('mock');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('useMock=true 在运行时使用 mock', () => {
|
||||||
|
const ds = new DataSource({
|
||||||
|
app: new App({}),
|
||||||
|
useMock: true,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: '1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
mocks: [{ enable: true, data: { name: 'enabled-mock' } }],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
expect(ds.data.name).toBe('enabled-mock');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initialData 优先时设置 isInit', () => {
|
||||||
|
const ds = new DataSource({
|
||||||
|
app: new App({}),
|
||||||
|
initialData: { name: 'preset' },
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: '1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(ds.isInit).toBe(true);
|
||||||
|
expect(ds.data.name).toBe('preset');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('支持自定义 ObservedDataClass', () => {
|
||||||
|
const ds = new DataSource({
|
||||||
|
app: new App({}),
|
||||||
|
ObservedDataClass: DeepObservedData,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: '1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const cb = vi.fn();
|
||||||
|
ds.onDataChange('name', cb);
|
||||||
|
ds.setData('next', 'name');
|
||||||
|
expect(cb).toHaveBeenCalled();
|
||||||
|
ds.offDataChange('name', cb);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setValue 等价于按 path 的 setData 并发出 change', () => {
|
||||||
|
const ds = new DataSource({
|
||||||
|
app: new App({}),
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: '1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const change = vi.fn();
|
||||||
|
ds.on('change', change);
|
||||||
|
ds.setValue('name', 'V');
|
||||||
|
expect(ds.data.name).toBe('V');
|
||||||
|
expect(change).toHaveBeenCalledWith({ updateData: 'V', path: 'name' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setFields / setMethods / DATA_SOURCE_SET_DATA_METHOD_NAME 自动注入', () => {
|
||||||
|
const ds = new DataSource({
|
||||||
|
app: new App({}),
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: '1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ds.setFields([{ name: 'foo' }] as any);
|
||||||
|
expect(ds.fields[0].name).toBe('foo');
|
||||||
|
ds.setMethods([{ name: 'doIt' } as any]);
|
||||||
|
expect(ds.methods[0].name).toBe('doIt');
|
||||||
|
|
||||||
|
(ds as any).setDataFromEvent({ params: { field: ['name'], data: 'X' } });
|
||||||
|
expect(ds.data.name).toBe('X');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 清理 fields 与监听', () => {
|
||||||
|
const ds = new DataSource({
|
||||||
|
app: new App({}),
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: '1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const handler = vi.fn();
|
||||||
|
ds.on('change', handler);
|
||||||
|
ds.destroy();
|
||||||
|
expect(ds.fields).toHaveLength(0);
|
||||||
|
ds.emit('change', {});
|
||||||
|
expect(handler).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,8 +1,15 @@
|
|||||||
import { afterAll, describe, expect, test } from 'vitest';
|
import { afterAll, afterEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import TMagicApp, { NodeType } from '@tmagic/core';
|
import TMagicApp, {
|
||||||
|
type MApp,
|
||||||
|
NODE_CONDS_KEY,
|
||||||
|
NODE_CONDS_RESULT_KEY,
|
||||||
|
NODE_DISABLE_DATA_SOURCE_KEY,
|
||||||
|
NodeType,
|
||||||
|
} from '@tmagic/core';
|
||||||
|
|
||||||
import { DataSource, DataSourceManager } from '@data-source/index';
|
import { DataSource, DataSourceManager } from '@data-source/index';
|
||||||
|
import { SimpleObservedData } from '@data-source/observed-data/SimpleObservedData';
|
||||||
|
|
||||||
const app = new TMagicApp({
|
const app = new TMagicApp({
|
||||||
config: {
|
config: {
|
||||||
@ -93,3 +100,628 @@ describe('DataSourceManager', () => {
|
|||||||
expect(dsm.get('1')).toBeInstanceOf(DataSource);
|
expect(dsm.get('1')).toBeInstanceOf(DataSource);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - 注册 / 等待 / observedData', () => {
|
||||||
|
test('register 注册新的数据源类', () => {
|
||||||
|
class Custom extends DataSource {}
|
||||||
|
DataSourceManager.register('custom-1', Custom as any);
|
||||||
|
expect(DataSourceManager.getDataSourceClass('custom-1')).toBe(Custom);
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
expect(DataSourceManager.getDataSourceClass('custom-1')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initialData 在构造时被合并到 data', () => {
|
||||||
|
const dsm = new DataSourceManager({
|
||||||
|
app: new TMagicApp({}),
|
||||||
|
initialData: { 1: { name: 'preset' } },
|
||||||
|
});
|
||||||
|
expect(dsm.data['1']).toEqual({ name: 'preset' });
|
||||||
|
expect(dsm.initialData['1']).toEqual({ name: 'preset' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('useMock 可被读取', () => {
|
||||||
|
const dsm = new DataSourceManager({ app: new TMagicApp({}), useMock: true });
|
||||||
|
expect(dsm.useMock).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registerObservedData 静态方法', () => {
|
||||||
|
class Fake {}
|
||||||
|
expect(() => DataSourceManager.registerObservedData(Fake as any)).not.toThrow();
|
||||||
|
// 用完恢复,避免污染后续用例
|
||||||
|
DataSourceManager.registerObservedData(SimpleObservedData);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - init 生命周期', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
const createApp = (jsEngine?: any) =>
|
||||||
|
new TMagicApp({
|
||||||
|
// jsEngine 选填,用于走 init 中的 jsEngine 分支
|
||||||
|
...(jsEngine ? { jsEngine } : {}),
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_init',
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
test('ds.isInit 为 true 时直接跳过', async () => {
|
||||||
|
const dsm = new DataSourceManager({ app: createApp() });
|
||||||
|
const ds = new DataSource({
|
||||||
|
app: createApp(),
|
||||||
|
schema: { type: 'base', id: 'ds_skip', fields: [], methods: [], events: [] },
|
||||||
|
});
|
||||||
|
ds.isInit = true;
|
||||||
|
await dsm.init(ds);
|
||||||
|
// isInit 仍为 true,且没有抛错
|
||||||
|
expect(ds.isInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('jsEngine 命中 disabledInitInJsEngine 时跳过 init', async () => {
|
||||||
|
const app = createApp('nodejs');
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
const ds = new DataSource({
|
||||||
|
app,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_disabled',
|
||||||
|
fields: [],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
disabledInitInJsEngine: ['nodejs'],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
expect(ds.isInit).toBe(false);
|
||||||
|
await dsm.init(ds);
|
||||||
|
expect(ds.isInit).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('methods 中 timing=beforeInit 的 content 会在 ds.init 之前调用', async () => {
|
||||||
|
const app = createApp();
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
const beforeContent = vi.fn();
|
||||||
|
const ds = new DataSource({
|
||||||
|
app,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_before',
|
||||||
|
fields: [],
|
||||||
|
events: [],
|
||||||
|
methods: [{ name: 'before', content: beforeContent, timing: 'beforeInit', params: [] }],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
await dsm.init(ds);
|
||||||
|
expect(beforeContent).toHaveBeenCalledTimes(1);
|
||||||
|
const arg = beforeContent.mock.calls[0][0];
|
||||||
|
expect(arg.dataSource).toBe(ds);
|
||||||
|
expect(arg.app).toBe(app);
|
||||||
|
expect(ds.isInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('methods 中 timing=afterInit 的 content 会在 ds.init 之后调用', async () => {
|
||||||
|
const app = createApp();
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
const order: string[] = [];
|
||||||
|
const afterContent = vi.fn(() => {
|
||||||
|
order.push('after');
|
||||||
|
});
|
||||||
|
const ds = new DataSource({
|
||||||
|
app,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_after',
|
||||||
|
fields: [],
|
||||||
|
events: [],
|
||||||
|
methods: [{ name: 'after', content: afterContent, timing: 'afterInit', params: [] }],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
const origInit = ds.init.bind(ds);
|
||||||
|
ds.init = async () => {
|
||||||
|
order.push('init');
|
||||||
|
await origInit();
|
||||||
|
};
|
||||||
|
await dsm.init(ds);
|
||||||
|
expect(afterContent).toHaveBeenCalledTimes(1);
|
||||||
|
expect(order).toEqual(['init', 'after']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('method.content 非函数时 init 提前返回,不会执行 ds.init', async () => {
|
||||||
|
const app = createApp();
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
const ds = new DataSource({
|
||||||
|
app,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_bad_method',
|
||||||
|
fields: [],
|
||||||
|
events: [],
|
||||||
|
methods: [{ name: 'bad', content: 'not-a-function', timing: 'beforeInit', params: [] } as any],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
const initSpy = vi.spyOn(ds, 'init');
|
||||||
|
await dsm.init(ds);
|
||||||
|
expect(initSpy).not.toHaveBeenCalled();
|
||||||
|
expect(ds.isInit).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('afterInit 阶段遇到非函数 content 也会提前返回', async () => {
|
||||||
|
const app = createApp();
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
const afterFn = vi.fn();
|
||||||
|
|
||||||
|
const ds = new DataSource({
|
||||||
|
app,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_after_bad',
|
||||||
|
fields: [],
|
||||||
|
events: [],
|
||||||
|
methods: [{ name: 'before', content: () => undefined, timing: 'beforeInit', params: [] } as any],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
// ds.init 执行之后再向 methods 中追加一个 content 非函数的 afterInit 项
|
||||||
|
const origInit = ds.init.bind(ds);
|
||||||
|
ds.init = async () => {
|
||||||
|
await origInit();
|
||||||
|
ds.setMethods([
|
||||||
|
{ name: 'bad', content: 'not-a-function', timing: 'afterInit', params: [] } as any,
|
||||||
|
{ name: 'after', content: afterFn, timing: 'afterInit', params: [] } as any,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
await dsm.init(ds);
|
||||||
|
// 第二个循环在第一个非函数 content 处提前返回,afterFn 不会被调用
|
||||||
|
expect(afterFn).not.toHaveBeenCalled();
|
||||||
|
expect(ds.isInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeInit / afterInit 同时存在但 timing 不匹配时安全跳过', async () => {
|
||||||
|
const app = createApp();
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
const beforeFn = vi.fn();
|
||||||
|
const afterFn = vi.fn();
|
||||||
|
const ds = new DataSource({
|
||||||
|
app,
|
||||||
|
schema: {
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_mixed',
|
||||||
|
fields: [],
|
||||||
|
events: [],
|
||||||
|
methods: [
|
||||||
|
{ name: 'b', content: beforeFn, timing: 'beforeInit', params: [] } as any,
|
||||||
|
{ name: 'a', content: afterFn, timing: 'afterInit', params: [] } as any,
|
||||||
|
],
|
||||||
|
} as any,
|
||||||
|
});
|
||||||
|
await dsm.init(ds);
|
||||||
|
expect(beforeFn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(afterFn).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - addDataSource 边界', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('config 为空时直接返回 undefined', () => {
|
||||||
|
const dsm = new DataSourceManager({ app: new TMagicApp({}) });
|
||||||
|
expect(dsm.addDataSource(undefined)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 后 waitInitSchemaList 为空,再次加入未知类型会重建 listMap', () => {
|
||||||
|
const dsm = new DataSourceManager({ app: new TMagicApp({}) });
|
||||||
|
dsm.destroy();
|
||||||
|
const ret = dsm.addDataSource({
|
||||||
|
id: 'ds_unknown_after_destroy',
|
||||||
|
type: 'never-registered',
|
||||||
|
fields: [{ name: 'a', defaultValue: 1 }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
} as any);
|
||||||
|
expect(ret).toBeUndefined();
|
||||||
|
expect(dsm.data.ds_unknown_after_destroy).toEqual({ a: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('多次加入同一未知类型会推到等待列表', () => {
|
||||||
|
const dsm = new DataSourceManager({ app: new TMagicApp({}) });
|
||||||
|
dsm.addDataSource({
|
||||||
|
id: 'pending_1',
|
||||||
|
type: 'pending-shared',
|
||||||
|
fields: [],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
} as any);
|
||||||
|
dsm.addDataSource({
|
||||||
|
id: 'pending_2',
|
||||||
|
type: 'pending-shared',
|
||||||
|
fields: [],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
class SharedDS extends DataSource {}
|
||||||
|
DataSourceManager.register('pending-shared', SharedDS as any);
|
||||||
|
|
||||||
|
expect(dsm.get('pending_1')).toBeInstanceOf(SharedDS);
|
||||||
|
expect(dsm.get('pending_2')).toBeInstanceOf(SharedDS);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - updateSchema 边界', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('传入的 schema 在 manager 中不存在时直接 return', () => {
|
||||||
|
const dsm = new DataSourceManager({
|
||||||
|
app: new TMagicApp({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_us',
|
||||||
|
items: [],
|
||||||
|
dataSources: [{ type: 'base', id: 'real', fields: [{ name: 'a' }], methods: [], events: [] }],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(dsm.get('real')).toBeInstanceOf(DataSource);
|
||||||
|
dsm.updateSchema([
|
||||||
|
{ type: 'base', id: 'not_exist', fields: [{ name: 'b' }], methods: [], events: [] },
|
||||||
|
{ type: 'base', id: 'real', fields: [{ name: 'a' }], methods: [], events: [] },
|
||||||
|
]);
|
||||||
|
// real 没有被删除/重建(因为遇到 not_exist 时整个 updateSchema 提前 return)
|
||||||
|
expect(dsm.get('real')).toBeInstanceOf(DataSource);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateSchema 中新 type 未注册时不会调用 init', () => {
|
||||||
|
const dsm = new DataSourceManager({
|
||||||
|
app: new TMagicApp({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_us2',
|
||||||
|
items: [],
|
||||||
|
dataSources: [{ type: 'base', id: 'X', fields: [], methods: [], events: [] }],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(dsm.get('X')).toBeInstanceOf(DataSource);
|
||||||
|
dsm.updateSchema([{ type: 'never-registered', id: 'X', fields: [], methods: [], events: [] } as any]);
|
||||||
|
expect(dsm.get('X')).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - compiledNode 边界', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
const createManager = () =>
|
||||||
|
new DataSourceManager({
|
||||||
|
app: new TMagicApp({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_cn',
|
||||||
|
items: [],
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_cn',
|
||||||
|
fields: [{ name: 'val', defaultValue: 'V' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_cn: {
|
||||||
|
text_a: { name: 'text', keys: ['text'] },
|
||||||
|
},
|
||||||
|
} as any,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
test('节点带 NODE_DISABLE_DATA_SOURCE_KEY 时直接返回原节点', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const node: any = {
|
||||||
|
id: 'text_a',
|
||||||
|
type: 'text',
|
||||||
|
text: 'hello ${ds_cn.val}',
|
||||||
|
[NODE_DISABLE_DATA_SOURCE_KEY]: true,
|
||||||
|
};
|
||||||
|
expect(dsm.compiledNode(node)).toBe(node);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deep=true 时数组 items 会递归编译', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const node: any = {
|
||||||
|
id: 'wrap',
|
||||||
|
type: 'container',
|
||||||
|
items: [{ id: 'text_a', type: 'text', text: 'hi ${ds_cn.val}' }],
|
||||||
|
};
|
||||||
|
const compiled: any = dsm.compiledNode(node, undefined, true);
|
||||||
|
expect(compiled.items[0].text).toBe('hi V');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deep=false 时 items 保持原样', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const items = [{ id: 'text_a', type: 'text', text: 'hi ${ds_cn.val}' }];
|
||||||
|
const node: any = { id: 'wrap', type: 'container', items };
|
||||||
|
const compiled: any = dsm.compiledNode(node);
|
||||||
|
expect(compiled.items).toBe(items);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('节点 condResult=false 时跳过模板编译', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const node: any = {
|
||||||
|
id: 'text_a',
|
||||||
|
type: 'text',
|
||||||
|
text: 'hi ${ds_cn.val}',
|
||||||
|
condResult: false,
|
||||||
|
};
|
||||||
|
const compiled: any = dsm.compiledNode(node);
|
||||||
|
expect(compiled.text).toBe('hi ${ds_cn.val}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('condResult=undefined 且 NODE_CONDS_RESULT_KEY=true 时也跳过模板编译', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const node: any = {
|
||||||
|
id: 'text_a',
|
||||||
|
type: 'text',
|
||||||
|
text: 'hi ${ds_cn.val}',
|
||||||
|
[NODE_CONDS_RESULT_KEY]: true,
|
||||||
|
};
|
||||||
|
const compiled: any = dsm.compiledNode(node);
|
||||||
|
expect(compiled.text).toBe('hi ${ds_cn.val}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('dsl.dataSourceDeps 缺失时使用空依赖对象', () => {
|
||||||
|
const app = new TMagicApp({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_no_deps',
|
||||||
|
items: [],
|
||||||
|
dataSources: [
|
||||||
|
{ type: 'base', id: 'ds_nd', fields: [{ name: 'v', defaultValue: 'V' }], methods: [], events: [] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(app.dsl?.dataSourceDeps).toBeUndefined();
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
const node: any = { id: 'p', type: 'text', text: 'hi' };
|
||||||
|
const compiled = dsm.compiledNode(node) as any;
|
||||||
|
expect(compiled.text).toBe('hi');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - compliedConds 边界', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('NODE_DISABLE_DATA_SOURCE_KEY=true 时直接返回 true', () => {
|
||||||
|
const dsm = new DataSourceManager({ app: new TMagicApp({}) });
|
||||||
|
expect(
|
||||||
|
dsm.compliedConds({
|
||||||
|
[NODE_DISABLE_DATA_SOURCE_KEY]: true,
|
||||||
|
[NODE_CONDS_KEY]: [{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 1 }] }] as any,
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('NODE_CONDS_RESULT_KEY 为真时会对条件结果取反', () => {
|
||||||
|
const dsm = new DataSourceManager({ app: new TMagicApp({}) });
|
||||||
|
dsm.data.ds_x = { a: 1 };
|
||||||
|
// 条件成立 -> compliedConditions 返回 true,再取反应为 false
|
||||||
|
expect(
|
||||||
|
dsm.compliedConds({
|
||||||
|
[NODE_CONDS_KEY]: [{ cond: [{ field: ['ds_x', 'a'], op: '=', value: 1 }] }] as any,
|
||||||
|
[NODE_CONDS_RESULT_KEY]: true,
|
||||||
|
}),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - 迭代器相关方法', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
const createManager = () =>
|
||||||
|
new DataSourceManager({
|
||||||
|
app: new TMagicApp({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_iter',
|
||||||
|
items: [],
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
type: 'base',
|
||||||
|
id: 'ds_iter',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: 'list',
|
||||||
|
type: 'array',
|
||||||
|
fields: [{ name: 'label' }],
|
||||||
|
defaultValue: [{ label: 'A' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compliedIteratorItemConds: dataSourceField 指向未知数据源时返回 true', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const result = dsm.compliedIteratorItemConds(
|
||||||
|
{ label: 'x' },
|
||||||
|
{ [NODE_CONDS_KEY]: [{ cond: [{ field: ['ds_iter', 'list', 'label'], op: '=', value: 'x' }] }] } as any,
|
||||||
|
['no_such_ds', 'list'],
|
||||||
|
);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compliedIteratorItemConds: 使用迭代上下文计算条件', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const node: any = {
|
||||||
|
[NODE_CONDS_KEY]: [{ cond: [{ field: ['ds_iter', 'list', 'label'], op: '=', value: 'B' }] }],
|
||||||
|
};
|
||||||
|
expect(dsm.compliedIteratorItemConds({ label: 'B' }, node, ['ds_iter', 'list'])).toBe(true);
|
||||||
|
expect(dsm.compliedIteratorItemConds({ label: 'A' }, node, ['ds_iter', 'list'])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compliedIteratorItems: 未知数据源时原样返回 nodes', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const nodes: any = [{ id: 'iter_1', type: 'text', text: '${ds_iter.list.label}' }];
|
||||||
|
expect(dsm.compliedIteratorItems({ label: 'B' }, nodes, ['no_such_ds'])).toBe(nodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compliedIteratorItems: 无 deps / condDeps 时原样返回 nodes', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const nodes: any = [{ id: 'plain', type: 'text', text: 'plain' }];
|
||||||
|
expect(dsm.compliedIteratorItems({ label: 'B' }, nodes, ['ds_iter', 'list'])).toBe(nodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compliedIteratorItems: 命中 deps 时按迭代上下文进行编译', () => {
|
||||||
|
const dsm = createManager();
|
||||||
|
const nodes: any = [{ id: 'iter_text', type: 'text', text: 'hello ${ds_iter.list.label}' }];
|
||||||
|
const compiled = dsm.compliedIteratorItems({ label: 'B' }, nodes, ['ds_iter', 'list']);
|
||||||
|
expect(compiled[0]).not.toBe(nodes[0]);
|
||||||
|
expect((compiled[0] as any).text).toBe('hello B');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - onDataChange / offDataChange', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('onDataChange / offDataChange 转发到对应数据源', () => {
|
||||||
|
const dsm = new DataSourceManager({
|
||||||
|
app: new TMagicApp({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_odc',
|
||||||
|
items: [],
|
||||||
|
dataSources: [{ type: 'base', id: 'ds_odc', fields: [{ name: 'name' }], methods: [], events: [] }],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const callback = vi.fn();
|
||||||
|
dsm.onDataChange('ds_odc', 'name', callback);
|
||||||
|
|
||||||
|
const ds = dsm.get('ds_odc')!;
|
||||||
|
ds.setData('A', 'name');
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
dsm.offDataChange('ds_odc', 'name', callback);
|
||||||
|
ds.setData('B', 'name');
|
||||||
|
expect(callback).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('数据源不存在时 onDataChange / offDataChange 安全返回 undefined', () => {
|
||||||
|
const dsm = new DataSourceManager({ app: new TMagicApp({}) });
|
||||||
|
const callback = vi.fn();
|
||||||
|
expect(dsm.onDataChange('no_id', 'a', callback)).toBeUndefined();
|
||||||
|
expect(dsm.offDataChange('no_id', 'a', callback)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DataSourceManager - callDsInit 异常 / 兼容分支', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
vi.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
const buildConfig = (id: string): MApp => ({
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id,
|
||||||
|
items: [],
|
||||||
|
dataSources: [
|
||||||
|
{ type: 'base', id: 'ds_ok', fields: [{ name: 'a', defaultValue: 1 }], methods: [], events: [] },
|
||||||
|
{ type: 'base', id: 'ds_err', fields: [{ name: 'b', defaultValue: 2 }], methods: [], events: [] },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
test('init 完成但 this.data[dsId] 为空时走 delete 分支', async () => {
|
||||||
|
const app = new TMagicApp({ config: buildConfig('app_empty_data') });
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
// 在 Promise.allSettled 的 .then() 微任务执行之前把 data 清空
|
||||||
|
dsm.data = {} as any;
|
||||||
|
|
||||||
|
const [data, errors] = await new Promise<any[]>((resolve) => {
|
||||||
|
dsm.once('init', (...args: any[]) => resolve(args));
|
||||||
|
});
|
||||||
|
// 由于 this.data[dsId] 为空,data 中也不会包含对应 dsId
|
||||||
|
expect(data.ds_ok).toBeUndefined();
|
||||||
|
expect(data.ds_err).toBeUndefined();
|
||||||
|
expect(Object.keys(errors)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('init 抛错时通过 Promise.allSettled 的 rejected 分支收集 errors', async () => {
|
||||||
|
const initSpy = vi.spyOn(DataSource.prototype, 'init').mockImplementation(async function (this: DataSource) {
|
||||||
|
if (this.id === 'ds_err') {
|
||||||
|
throw new Error('boom');
|
||||||
|
}
|
||||||
|
// ok 路径
|
||||||
|
(this as any).isInit = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = new TMagicApp({ config: buildConfig('app_err') });
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
|
||||||
|
const [data, errors] = await new Promise<any[]>((resolve) => {
|
||||||
|
dsm.once('init', (...args: any[]) => resolve(args));
|
||||||
|
});
|
||||||
|
expect(data.ds_ok).toEqual({ a: 1 });
|
||||||
|
expect(data.ds_err).toBeUndefined();
|
||||||
|
expect(errors.ds_err).toBeInstanceOf(Error);
|
||||||
|
expect(errors.ds_err.message).toBe('boom');
|
||||||
|
|
||||||
|
initSpy.mockRestore();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Promise.allSettled 不可用时走 Promise.all 兼容分支并发出 init 事件', async () => {
|
||||||
|
const original = Promise.allSettled;
|
||||||
|
(Promise as any).allSettled = undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const app = new TMagicApp({ config: buildConfig('app_compat') });
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
|
||||||
|
await new Promise<void>((resolve) => {
|
||||||
|
dsm.once('init', () => resolve());
|
||||||
|
});
|
||||||
|
expect(dsm.data.ds_ok).toEqual({ a: 1 });
|
||||||
|
expect(dsm.data.ds_err).toEqual({ b: 2 });
|
||||||
|
} finally {
|
||||||
|
(Promise as any).allSettled = original;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Promise.allSettled 不可用且 init 抛错时进入 catch 分支', async () => {
|
||||||
|
const original = Promise.allSettled;
|
||||||
|
(Promise as any).allSettled = undefined;
|
||||||
|
const initSpy = vi.spyOn(DataSource.prototype, 'init').mockRejectedValue(new Error('compat-boom'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const app = new TMagicApp({ config: buildConfig('app_compat_err') });
|
||||||
|
const dsm = new DataSourceManager({ app });
|
||||||
|
|
||||||
|
// 在兼容路径下,catch 分支也会发 init 事件
|
||||||
|
const data = await new Promise<any>((resolve) => {
|
||||||
|
dsm.once('init', (...args: any[]) => resolve(args[0]));
|
||||||
|
});
|
||||||
|
expect(data).toBeDefined();
|
||||||
|
} finally {
|
||||||
|
(Promise as any).allSettled = original;
|
||||||
|
initSpy.mockRestore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
237
packages/data-source/tests/HttpDataSource.spec.ts
Normal file
237
packages/data-source/tests/HttpDataSource.spec.ts
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
*/
|
||||||
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import App from '@tmagic/core';
|
||||||
|
|
||||||
|
import { HttpDataSource } from '@data-source/data-sources';
|
||||||
|
|
||||||
|
const createSchema = (overrides: Partial<any> = {}) => ({
|
||||||
|
type: 'http',
|
||||||
|
id: 'http_1',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
options: {
|
||||||
|
url: 'https://example.com/api',
|
||||||
|
method: 'GET',
|
||||||
|
params: {},
|
||||||
|
data: {},
|
||||||
|
headers: {},
|
||||||
|
},
|
||||||
|
...overrides,
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('HttpDataSource 基础', () => {
|
||||||
|
test('实例化时记录 httpOptions / type', () => {
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema() as any,
|
||||||
|
app: new App({}),
|
||||||
|
});
|
||||||
|
expect(ds).toBeInstanceOf(HttpDataSource);
|
||||||
|
expect(ds.type).toBe('http');
|
||||||
|
expect(ds.httpOptions.url).toBe('https://example.com/api');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('优先使用自定义 request', async () => {
|
||||||
|
const request = vi.fn().mockResolvedValue({ name: 'from-request' });
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema() as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
expect(request).toHaveBeenCalled();
|
||||||
|
expect(ds.data.name).toBe('from-request');
|
||||||
|
expect(ds.error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('autoFetch=true 在 init 时主动请求', async () => {
|
||||||
|
const request = vi.fn().mockResolvedValue({ name: 'auto' });
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({ autoFetch: true }) as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.init();
|
||||||
|
expect(request).toHaveBeenCalledTimes(1);
|
||||||
|
expect(ds.isInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeRequest / afterResponse 钩子被调用', async () => {
|
||||||
|
const beforeRequest = vi.fn(async (opt: any) => ({ ...opt, params: { extra: 1 } }));
|
||||||
|
const afterResponse = vi.fn(async (res: any) => ({ ...res, name: 'after' }));
|
||||||
|
const request = vi.fn().mockResolvedValue({ name: 'origin' });
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({ beforeRequest, afterResponse }) as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
expect(beforeRequest).toHaveBeenCalled();
|
||||||
|
expect(afterResponse).toHaveBeenCalled();
|
||||||
|
expect(ds.data.name).toBe('after');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('responseOptions.dataPath 截取响应字段', async () => {
|
||||||
|
const request = vi.fn().mockResolvedValue({ data: { name: 'inner' } });
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({ responseOptions: { dataPath: 'data' } }) as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
expect(ds.data.name).toBe('inner');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('请求失败时填充 error 并触发 error 事件', async () => {
|
||||||
|
const request = vi.fn().mockRejectedValue(new Error('boom'));
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema() as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
const errorHandler = vi.fn();
|
||||||
|
ds.on('error', errorHandler);
|
||||||
|
await ds.request();
|
||||||
|
expect(ds.isLoading).toBe(false);
|
||||||
|
expect(ds.error?.msg).toBe('boom');
|
||||||
|
expect(errorHandler).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('GET / POST 包装方法', async () => {
|
||||||
|
const request = vi.fn().mockResolvedValue({ name: 'ok' });
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema() as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.get({ url: 'https://x.com/g' });
|
||||||
|
expect(request.mock.calls[0][0].method).toBe('GET');
|
||||||
|
|
||||||
|
await ds.post({ url: 'https://x.com/p' });
|
||||||
|
expect(request.mock.calls[1][0].method).toBe('POST');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('options 中 url/params 等可以是函数', async () => {
|
||||||
|
const request = vi.fn().mockResolvedValue({});
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({
|
||||||
|
options: {
|
||||||
|
url: ({ dataSource }: any) => `https://x/${dataSource.id}`,
|
||||||
|
params: () => ({ p: 1 }),
|
||||||
|
data: () => ({ d: 1 }),
|
||||||
|
headers: () => ({ 'X-Custom': '1' }),
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
const opt = request.mock.calls[0][0];
|
||||||
|
expect(opt.url).toBe('https://x/http_1');
|
||||||
|
expect(opt.params).toEqual({ p: 1 });
|
||||||
|
expect(opt.data).toEqual({ d: 1 });
|
||||||
|
expect(opt.headers).toEqual({ 'X-Custom': '1' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('编辑器中使用 mockData 而非真实请求', async () => {
|
||||||
|
const request = vi.fn();
|
||||||
|
const app = new App({}) as any;
|
||||||
|
app.platform = 'editor';
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({
|
||||||
|
mocks: [{ useInEditor: true, data: { name: 'mock-name' } }],
|
||||||
|
}) as any,
|
||||||
|
app,
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
expect(request).not.toHaveBeenCalled();
|
||||||
|
expect(ds.data.name).toBe('mock-name');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('beforeRequest/afterRequest method 被注册', async () => {
|
||||||
|
const before = vi.fn();
|
||||||
|
const after = vi.fn();
|
||||||
|
const request = vi.fn().mockResolvedValue({});
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({
|
||||||
|
methods: [
|
||||||
|
{ name: 'b', timing: 'beforeRequest', content: before, params: [] },
|
||||||
|
{ name: 'a', timing: 'afterRequest', content: after, params: [] },
|
||||||
|
{ name: 'noop', content: 'not-a-function' as any, params: [] },
|
||||||
|
],
|
||||||
|
}) as any,
|
||||||
|
app: new App({}),
|
||||||
|
request,
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
expect(before).toHaveBeenCalled();
|
||||||
|
expect(after).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('webRequest 默认实现', () => {
|
||||||
|
test('未传自定义 request 时使用 fetch,非 GET 携带 body', async () => {
|
||||||
|
const fetchMock = vi.fn().mockResolvedValue({
|
||||||
|
json: async () => ({ name: 'fetched' }),
|
||||||
|
});
|
||||||
|
const original = globalThis.fetch;
|
||||||
|
(globalThis as any).fetch = fetchMock;
|
||||||
|
try {
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({
|
||||||
|
options: {
|
||||||
|
url: 'https://x.com/api',
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
data: { foo: 'bar' },
|
||||||
|
params: { q: 'v' },
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
app: new App({}),
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
||||||
|
const [url, init] = fetchMock.mock.calls[0];
|
||||||
|
expect(url).toContain('q=v');
|
||||||
|
expect(init.method).toBe('POST');
|
||||||
|
expect(init.body).toContain('foo');
|
||||||
|
expect(ds.data.name).toBe('fetched');
|
||||||
|
} finally {
|
||||||
|
(globalThis as any).fetch = original;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Content-Type 为 form-urlencoded 时 body 用 url 编码', async () => {
|
||||||
|
const fetchMock = vi.fn().mockResolvedValue({ json: async () => ({}) });
|
||||||
|
const original = globalThis.fetch;
|
||||||
|
(globalThis as any).fetch = fetchMock;
|
||||||
|
try {
|
||||||
|
const ds = new HttpDataSource({
|
||||||
|
schema: createSchema({
|
||||||
|
options: {
|
||||||
|
url: 'https://x.com/api',
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
data: { a: 1, b: { x: 1 }, c: undefined },
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
app: new App({}),
|
||||||
|
});
|
||||||
|
await ds.request();
|
||||||
|
const [, init] = fetchMock.mock.calls[0];
|
||||||
|
expect(init.body).toContain('a=1');
|
||||||
|
expect(init.body).toContain('b=');
|
||||||
|
expect(init.body).not.toContain('c=');
|
||||||
|
} finally {
|
||||||
|
(globalThis as any).fetch = original;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
87
packages/data-source/tests/ObservedData.spec.ts
Normal file
87
packages/data-source/tests/ObservedData.spec.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
|
import { DeepObservedData, SimpleObservedData } from '@data-source/observed-data';
|
||||||
|
|
||||||
|
describe('SimpleObservedData', () => {
|
||||||
|
test('update / getData 全量与按路径', () => {
|
||||||
|
const od = new SimpleObservedData({ a: 1, b: { c: 2 } });
|
||||||
|
expect(od.getData('')).toEqual({ a: 1, b: { c: 2 } });
|
||||||
|
expect(od.getData('b.c')).toBe(2);
|
||||||
|
|
||||||
|
od.update({ a: 9 });
|
||||||
|
expect(od.data.a).toBe(9);
|
||||||
|
|
||||||
|
od.update(99, 'a');
|
||||||
|
expect(od.data.a).toBe(99);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('on / off 监听变更, immediate 立即触发一次', () => {
|
||||||
|
const od = new SimpleObservedData({ a: 1 });
|
||||||
|
const cb = vi.fn();
|
||||||
|
od.on('a', cb, { immediate: true });
|
||||||
|
expect(cb).toHaveBeenCalledTimes(1);
|
||||||
|
od.update(2, 'a');
|
||||||
|
expect(cb).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
|
od.off('a', cb);
|
||||||
|
od.update(3, 'a');
|
||||||
|
expect(cb).toHaveBeenCalledTimes(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('全量更新触发空 path 监听器', () => {
|
||||||
|
const od = new SimpleObservedData({ a: 1 });
|
||||||
|
const cb = vi.fn();
|
||||||
|
od.on('', cb);
|
||||||
|
od.update({ a: 2 });
|
||||||
|
expect(cb).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 不抛错', () => {
|
||||||
|
const od = new SimpleObservedData({});
|
||||||
|
expect(() => od.destroy()).not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DeepObservedData', () => {
|
||||||
|
test('on/update/off/getData 完整链路', () => {
|
||||||
|
const od = new DeepObservedData({ a: 1, list: [{ name: 'x' }] });
|
||||||
|
|
||||||
|
const cb = vi.fn();
|
||||||
|
od.on('a', cb);
|
||||||
|
od.update(2, 'a');
|
||||||
|
expect(cb).toHaveBeenCalled();
|
||||||
|
expect(od.getData('a')).toBe(2);
|
||||||
|
|
||||||
|
od.off('a', cb);
|
||||||
|
cb.mockClear();
|
||||||
|
od.update(3, 'a');
|
||||||
|
expect(cb).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('immediate 选项立刻触发一次回调', () => {
|
||||||
|
const od = new DeepObservedData({ a: 1 });
|
||||||
|
const cb = vi.fn();
|
||||||
|
od.on('a', cb, { immediate: true });
|
||||||
|
expect(cb).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('off 不存在的 callback 不抛错', () => {
|
||||||
|
const od = new DeepObservedData({ a: 1 });
|
||||||
|
expect(() => od.off('a', () => undefined)).not.toThrow();
|
||||||
|
expect(() => od.off('not-exist', () => undefined)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('destroy 解除所有监听', () => {
|
||||||
|
const od = new DeepObservedData({ a: 1 });
|
||||||
|
const cb = vi.fn();
|
||||||
|
od.on('a', cb);
|
||||||
|
od.destroy();
|
||||||
|
od.update(2, 'a');
|
||||||
|
expect(cb).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import TMagicApp, { type MApp, NodeType } from '@tmagic/core';
|
import TMagicApp, { type MApp, NodeType } from '@tmagic/core';
|
||||||
|
|
||||||
import { createDataSourceManager, DataSourceManager } from '@data-source/index';
|
import { createDataSourceManager, DataSource, DataSourceManager } from '@data-source/index';
|
||||||
|
|
||||||
const dsl: MApp = {
|
const createDsl = (): MApp => ({
|
||||||
type: NodeType.ROOT,
|
type: NodeType.ROOT,
|
||||||
id: 'app_1',
|
id: 'app_1',
|
||||||
items: [
|
items: [
|
||||||
@ -41,13 +41,803 @@ const dsl: MApp = {
|
|||||||
events: [],
|
events: [],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
});
|
||||||
|
|
||||||
describe('createDataSourceManager', () => {
|
afterEach(() => {
|
||||||
|
DataSourceManager.clearDataSourceClass();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - 基础', () => {
|
||||||
test('instance', () => {
|
test('instance', () => {
|
||||||
const manager = createDataSourceManager(new TMagicApp({ config: dsl }));
|
const manager = createDataSourceManager(new TMagicApp({ config: createDsl() }));
|
||||||
expect(manager).toBeInstanceOf(DataSourceManager);
|
expect(manager).toBeInstanceOf(DataSourceManager);
|
||||||
|
});
|
||||||
|
|
||||||
DataSourceManager.clearDataSourceClass();
|
test('dsl 中没有 dataSources 时返回 undefined', () => {
|
||||||
|
const app = new TMagicApp({
|
||||||
|
config: {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_no_ds',
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
expect(manager).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('app 没有 dsl 时返回 undefined', () => {
|
||||||
|
const app = new TMagicApp({});
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
expect(manager).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('useMock 透传到 DataSourceManager', () => {
|
||||||
|
const manager = createDataSourceManager(new TMagicApp({ config: createDsl() }), true);
|
||||||
|
expect(manager?.useMock).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('initialData 透传到 DataSourceManager', () => {
|
||||||
|
const manager = createDataSourceManager(new TMagicApp({ config: createDsl() }), false, {
|
||||||
|
ds_bebcb2d5: { text: 'preset' },
|
||||||
|
});
|
||||||
|
expect(manager?.initialData.ds_bebcb2d5).toEqual({ text: 'preset' });
|
||||||
|
expect(manager?.data.ds_bebcb2d5.text).toBe('preset');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - 初始化阶段编译', () => {
|
||||||
|
test('platform!=editor && 存在 dataSourceCondDeps 时按节点写入 condResult', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_cond',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
id: 'cond_node',
|
||||||
|
text: 'hello',
|
||||||
|
displayConds: [{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 1 }] }],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceCondDeps: {
|
||||||
|
ds_1: {
|
||||||
|
cond_node: { name: '文本', keys: ['displayConds'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSourceDeps: {},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'a', defaultValue: 1 }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'mobile' });
|
||||||
|
createDataSourceManager(app);
|
||||||
|
const node: any = (app.dsl?.items[0] as any).items[0];
|
||||||
|
expect(node.condResult).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('platform=editor 时初始化不写入 condResult', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_cond_editor',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
id: 'cond_node',
|
||||||
|
text: 'hello',
|
||||||
|
displayConds: [{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 1 }] }],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceCondDeps: {
|
||||||
|
ds_1: {
|
||||||
|
cond_node: { name: '文本', keys: ['displayConds'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSourceDeps: {},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'a', defaultValue: 1 }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'editor' });
|
||||||
|
createDataSourceManager(app);
|
||||||
|
const node: any = (app.dsl?.items[0] as any).items[0];
|
||||||
|
expect(node.condResult).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('存在 dataSourceDeps 时初始化即编译节点字段(模板)', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_dep',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
id: 'dep_node',
|
||||||
|
text: 'hello ${ds_1.name}',
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
dep_node: { name: '文本', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'world' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'mobile' });
|
||||||
|
createDataSourceManager(app);
|
||||||
|
const node: any = (app.dsl?.items[0] as any).items[0];
|
||||||
|
expect(node.text).toBe('hello world');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - jsEngine=nodejs', () => {
|
||||||
|
test('nodejs 环境下不监听 change,触发 setData 不会走 update-data', () => {
|
||||||
|
const app = new TMagicApp({ config: createDsl(), jsEngine: 'nodejs' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
expect(manager).toBeInstanceOf(DataSourceManager);
|
||||||
|
expect(manager?.listenerCount('change')).toBe(0);
|
||||||
|
|
||||||
|
const updateSpy = vi.fn();
|
||||||
|
manager?.on('update-data', updateSpy);
|
||||||
|
const ds = manager?.get('ds_bebcb2d5');
|
||||||
|
ds?.setData({ text: 'changed' });
|
||||||
|
expect(updateSpy).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - change 事件', () => {
|
||||||
|
let app: TMagicApp;
|
||||||
|
let manager: DataSourceManager | undefined;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_change',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
id: 'text_1',
|
||||||
|
text: 'origin ${ds_1.name}',
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
text_1: { name: '文本', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'world' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
app = new TMagicApp({ config: dsl, platform: 'mobile' });
|
||||||
|
manager = createDataSourceManager(app);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('change 事件触发后会发出 update-data,并携带新节点 / sourceId / pageId', () => {
|
||||||
|
const update = vi.fn();
|
||||||
|
manager?.on('update-data', update);
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'new' });
|
||||||
|
|
||||||
|
expect(update).toHaveBeenCalledTimes(1);
|
||||||
|
const [newNodes, sourceId, , pageId] = update.mock.calls[0];
|
||||||
|
expect(sourceId).toBe('ds_1');
|
||||||
|
expect(pageId).toBe('page_1');
|
||||||
|
expect(newNodes[0].id).toBe('text_1');
|
||||||
|
expect(newNodes[0].text).toBe('origin new');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('change 事件会调用 page.setData 并触发节点 setData', () => {
|
||||||
|
const node = app.getNode('text_1');
|
||||||
|
const setDataSpy = vi.spyOn(node!, 'setData');
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'second' });
|
||||||
|
|
||||||
|
expect(setDataSpy).toHaveBeenCalled();
|
||||||
|
const calledArg = setDataSpy.mock.calls[0][0] as any;
|
||||||
|
expect(calledArg.text).toBe('origin second');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('依赖中的节点不存在时不会发出 update-data', () => {
|
||||||
|
const update = vi.fn();
|
||||||
|
manager?.on('update-data', update);
|
||||||
|
|
||||||
|
if (app.dsl?.dataSourceDeps) {
|
||||||
|
app.dsl.dataSourceDeps = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'noop' });
|
||||||
|
|
||||||
|
expect(update).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('page 自身被命中时调用 app.page.setData', () => {
|
||||||
|
// 把 page 自己加入到依赖中
|
||||||
|
if (app.dsl?.dataSourceDeps) {
|
||||||
|
app.dsl.dataSourceDeps.ds_1 = {
|
||||||
|
page_1: { name: 'page', keys: ['style'] },
|
||||||
|
} as any;
|
||||||
|
}
|
||||||
|
const pageSetData = vi.spyOn(app.page!, 'setData');
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'X' });
|
||||||
|
expect(pageSetData).toHaveBeenCalled();
|
||||||
|
const arg: any = pageSetData.mock.calls[0][0];
|
||||||
|
expect(arg.id).toBe('page_1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('page 没有 instance 时通过 replaceChildNode 写回 page.data', () => {
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
expect(app.page?.instance).toBeFalsy();
|
||||||
|
|
||||||
|
ds?.setData({ name: 'replaced' });
|
||||||
|
|
||||||
|
const replacedText = (app.page?.data as any).items[0].text;
|
||||||
|
expect(replacedText).toBe('origin replaced');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - editor 平台', () => {
|
||||||
|
test('editor 平台会遍历所有页面,而非仅当前页', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_editor',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [{ type: 'text', id: 'text_a', text: 'a ${ds_1.name}' } as any],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_2',
|
||||||
|
items: [{ type: 'text', id: 'text_b', text: 'b ${ds_1.name}' } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
text_a: { name: '文本', keys: ['text'] },
|
||||||
|
text_b: { name: '文本', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'editor' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const update = vi.fn();
|
||||||
|
manager?.on('update-data', update);
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'V' });
|
||||||
|
|
||||||
|
expect(update).toHaveBeenCalledTimes(2);
|
||||||
|
const pageIds = update.mock.calls.map((c) => c[3]);
|
||||||
|
expect(pageIds).toContain('page_1');
|
||||||
|
expect(pageIds).toContain('page_2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('非 editor 平台只处理当前页', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_runtime',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [{ type: 'text', id: 'text_a', text: 'a ${ds_1.name}' } as any],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_2',
|
||||||
|
items: [{ type: 'text', id: 'text_b', text: 'b ${ds_1.name}' } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
text_a: { name: '文本', keys: ['text'] },
|
||||||
|
text_b: { name: '文本', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'mobile', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const update = vi.fn();
|
||||||
|
manager?.on('update-data', update);
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'V' });
|
||||||
|
|
||||||
|
expect(update).toHaveBeenCalledTimes(1);
|
||||||
|
expect(update.mock.calls[0][3]).toBe('page_1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('非 editor 平台命中 isPageFragment 分支也会被处理', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_pf',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [{ type: 'text', id: 'text_a', text: 'a' } as any],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [{ type: 'text', id: 'text_b', text: 'b ${ds_1.name}' } as any],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
text_b: { name: '文本', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'mobile', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const update = vi.fn();
|
||||||
|
manager?.on('update-data', update);
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'V' });
|
||||||
|
|
||||||
|
expect(update).toHaveBeenCalledTimes(1);
|
||||||
|
expect(update.mock.calls[0][3]).toBe('pf_1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - pageFragments 同步', () => {
|
||||||
|
test('当 newNode 为 pageFragment 自身时,调用 pageFragment.setData', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_pf_self',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'page-fragment-container',
|
||||||
|
id: 'pfc_1',
|
||||||
|
pageFragmentId: 'pf_1',
|
||||||
|
items: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||||||
|
extra: '${ds_1.name}',
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
pf_1: { name: 'pf', keys: ['extra'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
expect(app.pageFragments.size).toBeGreaterThan(0);
|
||||||
|
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||||||
|
const pfSetData = vi.spyOn(pageFragment, 'setData');
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'X' });
|
||||||
|
|
||||||
|
expect(pfSetData).toHaveBeenCalled();
|
||||||
|
const arg: any = pfSetData.mock.calls[0][0];
|
||||||
|
expect(arg.id).toBe('pf_1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('当 newNode 是 pageFragment 内子节点时,pageFragment 内同步并 replaceChildNode', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_pf_child',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'page-fragment-container',
|
||||||
|
id: 'pfc_1',
|
||||||
|
pageFragmentId: 'pf_1',
|
||||||
|
items: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
pf_text: { name: 'pf_text', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||||||
|
const innerNode = pageFragment.getNode('pf_text', { strict: true })!;
|
||||||
|
const innerSetData = vi.spyOn(innerNode, 'setData');
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'Y' });
|
||||||
|
|
||||||
|
expect(innerSetData).toHaveBeenCalled();
|
||||||
|
const arg: any = innerSetData.mock.calls[0][0];
|
||||||
|
expect(arg.text).toBe('pf Y');
|
||||||
|
expect((pageFragment.data as any).items[0].text).toBe('pf Y');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - app.page 不存在', () => {
|
||||||
|
test('app.page 缺失时跳过 page.setData / 节点 setData,但仍发出 update-data', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_no_page',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [{ type: 'text', id: 'text_a', text: 'a ${ds_1.name}' } as any],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
text_a: { name: '文本', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
// curPage 指向不存在的页,setPage 会调用 deletePage 让 app.page = undefined
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'not_exist' });
|
||||||
|
expect(app.page).toBeUndefined();
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const update = vi.fn();
|
||||||
|
manager?.on('update-data', update);
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
expect(() => ds?.setData({ name: 'V' })).not.toThrow();
|
||||||
|
|
||||||
|
expect(update).toHaveBeenCalledTimes(1);
|
||||||
|
expect(update.mock.calls[0][3]).toBe('page_1');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - pageFragment 与被遍历 page 同 id', () => {
|
||||||
|
test('editor 平台遍历到 pageFragment 自身页时进入 pageFragment.data.id === page.id 分支', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_pf_iter',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'page-fragment-container',
|
||||||
|
id: 'pfc_1',
|
||||||
|
pageFragmentId: 'pf_1',
|
||||||
|
items: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
pf_text: { name: 'pf_text', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'editor', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||||||
|
const innerNode = pageFragment.getNode('pf_text', { strict: true })!;
|
||||||
|
const innerSetData = vi.spyOn(innerNode, 'setData');
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'Z' });
|
||||||
|
|
||||||
|
expect(innerSetData).toHaveBeenCalled();
|
||||||
|
const arg: any = innerSetData.mock.calls[0][0];
|
||||||
|
expect(arg.text).toBe('pf Z');
|
||||||
|
expect((pageFragment.data as any).items[0].text).toBe('pf Z');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - pageFragment 边界分支', () => {
|
||||||
|
const buildDsl = (): MApp => ({
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_pf_edge',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: 'page-fragment-container',
|
||||||
|
id: 'pfc_1',
|
||||||
|
pageFragmentId: 'pf_1',
|
||||||
|
items: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [{ type: 'text', id: 'pf_text', text: 'pf ${ds_1.name}' } as any],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
pf_text: { name: 'pf_text', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pageFragment.getNode 返回 undefined 时安全跳过 setData', () => {
|
||||||
|
const app = new TMagicApp({ config: buildDsl(), platform: 'editor', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||||||
|
// 模拟 pageFragment 内对应节点已被移除的边界
|
||||||
|
pageFragment.nodes.delete('pf_text');
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
expect(() => ds?.setData({ name: 'A' })).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pageFragment 与当前遍历的 page、newNode 都无关时不会进入 pageFragment 同步分支', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_pf_unrelated',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
id: 'page_1',
|
||||||
|
items: [
|
||||||
|
{ type: 'text', id: 'plain_text', text: 'a ${ds_1.name}' } as any,
|
||||||
|
{
|
||||||
|
type: 'page-fragment-container',
|
||||||
|
id: 'pfc_1',
|
||||||
|
pageFragmentId: 'pf_1',
|
||||||
|
items: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeType.PAGE_FRAGMENT,
|
||||||
|
id: 'pf_1',
|
||||||
|
items: [{ type: 'text', id: 'pf_text', text: 'pf' } as any],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
dataSourceDeps: {
|
||||||
|
ds_1: {
|
||||||
|
plain_text: { name: 'plain_text', keys: ['text'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_1',
|
||||||
|
type: 'base',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'init' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl, platform: 'mobile', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||||||
|
const pfSetData = vi.spyOn(pageFragment, 'setData');
|
||||||
|
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'C' });
|
||||||
|
|
||||||
|
// pageFragment 与本次更新无关,不会被同步
|
||||||
|
expect(pfSetData).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('pageFragment.instance 为真时跳过 replaceChildNode', () => {
|
||||||
|
const app = new TMagicApp({ config: buildDsl(), platform: 'editor', curPage: 'page_1' });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
|
||||||
|
const pageFragment = app.pageFragments.get('pfc_1')!;
|
||||||
|
pageFragment.setInstance({ __isVue: true });
|
||||||
|
|
||||||
|
const before = (pageFragment.data as any).items[0].text;
|
||||||
|
const ds = manager?.get('ds_1');
|
||||||
|
ds?.setData({ name: 'B' });
|
||||||
|
|
||||||
|
// 因为 instance 存在,pageFragment.data 不会被 replaceChildNode 改写
|
||||||
|
expect((pageFragment.data as any).items[0].text).toBe(before);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDataSourceManager - 自定义数据源类型尚未注册', () => {
|
||||||
|
test('未知类型在初始化时不抛错,仅写入默认数据', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_pending',
|
||||||
|
items: [],
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_unknown',
|
||||||
|
type: 'custom-not-registered',
|
||||||
|
fields: [{ name: 'name', defaultValue: 'd' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
expect(manager).toBeInstanceOf(DataSourceManager);
|
||||||
|
expect(manager?.data.ds_unknown).toEqual({ name: 'd' });
|
||||||
|
expect(manager?.get('ds_unknown')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('在未注册期间通过 register 触发延迟初始化', () => {
|
||||||
|
const dsl: MApp = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app_lazy',
|
||||||
|
items: [],
|
||||||
|
dataSources: [
|
||||||
|
{
|
||||||
|
id: 'ds_lazy',
|
||||||
|
type: 'lazy-type',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
methods: [],
|
||||||
|
events: [],
|
||||||
|
} as any,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const app = new TMagicApp({ config: dsl });
|
||||||
|
const manager = createDataSourceManager(app);
|
||||||
|
expect(manager?.get('ds_lazy')).toBeUndefined();
|
||||||
|
|
||||||
|
class LazyDataSource extends DataSource {}
|
||||||
|
DataSourceManager.register('lazy-type', LazyDataSource as any);
|
||||||
|
|
||||||
|
expect(manager?.get('ds_lazy')).toBeInstanceOf(LazyDataSource);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
57
packages/data-source/tests/depsCache.spec.ts
Normal file
57
packages/data-source/tests/depsCache.spec.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2025 Tencent.
|
||||||
|
*/
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import { getDeps } from '@data-source/depsCache';
|
||||||
|
|
||||||
|
describe('getDeps', () => {
|
||||||
|
test('从节点收集普通字段依赖', () => {
|
||||||
|
const ds: any = {
|
||||||
|
id: 'ds_1',
|
||||||
|
fields: [{ name: 'name', type: 'string' }],
|
||||||
|
};
|
||||||
|
const nodes: any[] = [
|
||||||
|
{
|
||||||
|
id: 'page_1',
|
||||||
|
type: 'page',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'btn_1',
|
||||||
|
type: 'text',
|
||||||
|
text: '${ds_1.name}',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = getDeps(ds, nodes, false);
|
||||||
|
expect(result.deps).toBeDefined();
|
||||||
|
expect(result.condDeps).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('inEditor=true 时缓存键包含所有 traverse 节点', () => {
|
||||||
|
const ds: any = {
|
||||||
|
id: 'ds_2',
|
||||||
|
fields: [{ name: 'name' }],
|
||||||
|
};
|
||||||
|
const nodes: any[] = [
|
||||||
|
{
|
||||||
|
id: 'page_1',
|
||||||
|
type: 'page',
|
||||||
|
items: [{ id: 'btn_1', type: 'text', text: '${ds_2.name}' }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const result = getDeps(ds, nodes, true);
|
||||||
|
expect(result.deps).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cache 命中时返回同一对象', () => {
|
||||||
|
const ds: any = { id: 'ds_3', fields: [{ name: 'n' }] };
|
||||||
|
const nodes: any[] = [{ id: 'p', type: 'page', items: [] }];
|
||||||
|
const r1 = getDeps(ds, nodes, false);
|
||||||
|
const r2 = getDeps(ds, nodes, false);
|
||||||
|
expect(r1).toBe(r2);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,8 +1,18 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test, vi } from 'vitest';
|
||||||
|
|
||||||
import { dataSourceTemplateRegExp } from '@tmagic/core';
|
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, dataSourceTemplateRegExp, NodeType } from '@tmagic/core';
|
||||||
|
|
||||||
import { compiledCondition, createIteratorContentData, template } from '@data-source/utils';
|
import {
|
||||||
|
compiledCondition,
|
||||||
|
compiledNodeField,
|
||||||
|
compliedConditions,
|
||||||
|
compliedDataSourceField,
|
||||||
|
compliedIteratorItem,
|
||||||
|
createIteratorContentData,
|
||||||
|
registerDataSourceOnDemand,
|
||||||
|
template,
|
||||||
|
updateNode,
|
||||||
|
} from '@data-source/utils';
|
||||||
|
|
||||||
describe('compiledCondition', () => {
|
describe('compiledCondition', () => {
|
||||||
test('=,true', () => {
|
test('=,true', () => {
|
||||||
@ -184,3 +194,207 @@ describe('createIteratorContentData', () => {
|
|||||||
expect(ctxData.ds.a.c.a).toBe(1);
|
expect(ctxData.ds.a.c.a).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('compliedConditions', () => {
|
||||||
|
test('未配置 conditions 时直接返回 true', () => {
|
||||||
|
expect(compliedConditions({}, {})).toBe(true);
|
||||||
|
expect(compliedConditions({ ['displayConds' as any]: [] } as any, {})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('任一 cond 通过即返回 true', () => {
|
||||||
|
const node: any = {
|
||||||
|
displayConds: [
|
||||||
|
{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 2 }] },
|
||||||
|
{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 1 }] },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
expect(compliedConditions(node, { ds_1: { a: 1 } })).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('全部不通过则返回 false', () => {
|
||||||
|
const node: any = {
|
||||||
|
displayConds: [{ cond: [{ field: ['ds_1', 'a'], op: '=', value: 2 }] }],
|
||||||
|
};
|
||||||
|
expect(compliedConditions(node, { ds_1: { a: 1 } })).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cond 为空被跳过', () => {
|
||||||
|
const node: any = { displayConds: [{ cond: undefined }] };
|
||||||
|
expect(compliedConditions(node, {})).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compiledCondition 边界', () => {
|
||||||
|
test('数据源不存在时直接 break 视为通过', () => {
|
||||||
|
expect(compiledCondition([{ field: ['unknown', 'a'], op: '=', value: 1 }], {})).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('field 取值异常(如类型错)时 console.warn 不阻断', () => {
|
||||||
|
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||||||
|
const result = compiledCondition([{ field: ['ds', 'a', 'b', 'c'], op: '=', value: 1 }], { ds: { a: 'string' } });
|
||||||
|
expect(result).toBe(true);
|
||||||
|
warn.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateNode', () => {
|
||||||
|
test('页面节点直接替换 dsl.items', () => {
|
||||||
|
const dsl: any = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [{ id: 'p1', type: NodeType.PAGE, items: [{ id: 'btn' }] }],
|
||||||
|
};
|
||||||
|
updateNode({ id: 'p1', type: NodeType.PAGE, items: [{ id: 'btn2' }] } as any, dsl);
|
||||||
|
expect(dsl.items[0].items[0].id).toBe('btn2');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('非页面节点走 replaceChildNode', () => {
|
||||||
|
const dsl: any = {
|
||||||
|
type: NodeType.ROOT,
|
||||||
|
id: 'app',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: 'p1',
|
||||||
|
type: NodeType.PAGE,
|
||||||
|
items: [{ id: 'btn', type: 'button', text: 'old' }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
updateNode({ id: 'btn', type: 'button', text: 'new' } as any, dsl);
|
||||||
|
expect(dsl.items[0].items[0].text).toBe('new');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compliedDataSourceField', () => {
|
||||||
|
test('不带前缀直接返回原值', () => {
|
||||||
|
expect(compliedDataSourceField(['no-prefix-id', 'name'], { id: { name: 'x' } })).toEqual(['no-prefix-id', 'name']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('数据源不存在时返回原值', () => {
|
||||||
|
expect(compliedDataSourceField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}id`, 'name'], {})).toEqual([
|
||||||
|
`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}id`,
|
||||||
|
'name',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('正常解析数据源字段', () => {
|
||||||
|
const value = compliedDataSourceField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}id`, 'name'], {
|
||||||
|
id: { name: 'x' },
|
||||||
|
});
|
||||||
|
expect(value).toBe('x');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('字段路径不存在时返回原值', () => {
|
||||||
|
expect(
|
||||||
|
compliedDataSourceField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}id`, 'name', 'sub'], { id: { name: 'x' } }),
|
||||||
|
).toEqual([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}id`, 'name', 'sub']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compiledNodeField', () => {
|
||||||
|
const data = { id: { name: 'world' } };
|
||||||
|
|
||||||
|
test('字符串模板', () => {
|
||||||
|
expect(compiledNodeField('hello ${id.name}', data)).toBe('hello world');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isBindDataSource 直接取整个数据源', () => {
|
||||||
|
expect(compiledNodeField({ isBindDataSource: true, dataSourceId: 'id' }, data)).toEqual({ name: 'world' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isBindDataSourceField 走模板', () => {
|
||||||
|
expect(compiledNodeField({ isBindDataSourceField: true, dataSourceId: 'id', template: 'hi ${name}' }, data)).toBe(
|
||||||
|
'hi world',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('数组形式走 compliedDataSourceField', () => {
|
||||||
|
expect(compiledNodeField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}id`, 'name'], data)).toBe('world');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('未匹配格式直接返回原值', () => {
|
||||||
|
expect(compiledNodeField(123 as any, data)).toBe(123);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('compliedIteratorItem', () => {
|
||||||
|
test('递归 compile items 并应用条件', () => {
|
||||||
|
const item: any = {
|
||||||
|
id: 'parent',
|
||||||
|
items: [{ id: 'child', text: 'origin' }],
|
||||||
|
};
|
||||||
|
const ctxData = { ds: { name: 'V' } };
|
||||||
|
const result = compliedIteratorItem({
|
||||||
|
compile: (v: any) => `compiled-${v}`,
|
||||||
|
dsId: 'ds',
|
||||||
|
item,
|
||||||
|
deps: { child: { name: 'c', keys: ['text'] } },
|
||||||
|
condDeps: {},
|
||||||
|
inEditor: false,
|
||||||
|
ctxData,
|
||||||
|
});
|
||||||
|
expect(result.items[0].text).toBe('compiled-origin');
|
||||||
|
expect(result.id).toBe('parent');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('items 不是数组时保留原值', () => {
|
||||||
|
const result = compliedIteratorItem({
|
||||||
|
compile: (v: any) => v,
|
||||||
|
dsId: 'ds',
|
||||||
|
item: { id: 'p', items: 'not-array' as any } as any,
|
||||||
|
deps: {},
|
||||||
|
condDeps: {},
|
||||||
|
inEditor: true,
|
||||||
|
ctxData: {},
|
||||||
|
});
|
||||||
|
expect(result.items).toBe('not-array');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('条件依赖在非编辑器中会写入 condResult', () => {
|
||||||
|
const result = compliedIteratorItem({
|
||||||
|
compile: (v: any) => v,
|
||||||
|
dsId: 'ds',
|
||||||
|
item: {
|
||||||
|
id: 'p',
|
||||||
|
displayConds: [{ cond: [{ field: ['ds', 'a'], op: '=', value: 1 }] }],
|
||||||
|
} as any,
|
||||||
|
deps: {},
|
||||||
|
condDeps: { p: { name: 'p', keys: ['displayConds'] } },
|
||||||
|
inEditor: false,
|
||||||
|
ctxData: { ds: { a: 1 } },
|
||||||
|
});
|
||||||
|
expect(result.condResult).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('registerDataSourceOnDemand', () => {
|
||||||
|
test('按依赖按需返回模块', async () => {
|
||||||
|
const dsl: any = {
|
||||||
|
dataSources: [
|
||||||
|
{ id: 'a', type: 'http' },
|
||||||
|
{ id: 'b', type: 'mock' },
|
||||||
|
{ id: 'c', type: 'http' },
|
||||||
|
],
|
||||||
|
dataSourceDeps: { a: { node1: { name: 'n', keys: ['x'] } } },
|
||||||
|
dataSourceCondDeps: { c: { node2: { name: 'n', keys: ['y'] } } },
|
||||||
|
dataSourceMethodDeps: {},
|
||||||
|
};
|
||||||
|
const httpModule = { default: class HttpDS {} };
|
||||||
|
const mockModule = { default: class MockDS {} };
|
||||||
|
const modules = await registerDataSourceOnDemand(dsl, {
|
||||||
|
http: () => Promise.resolve(httpModule as any),
|
||||||
|
mock: () => Promise.resolve(mockModule as any),
|
||||||
|
});
|
||||||
|
expect(modules.http).toBe(httpModule.default);
|
||||||
|
expect(modules.mock).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('找不到对应模块时跳过', async () => {
|
||||||
|
const dsl: any = {
|
||||||
|
dataSources: [{ id: 'a', type: 'unknown' }],
|
||||||
|
dataSourceDeps: { a: { node: { name: 'n', keys: ['x'] } } },
|
||||||
|
};
|
||||||
|
const modules = await registerDataSourceOnDemand(dsl, {});
|
||||||
|
expect(Object.keys(modules)).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.14-beta.0",
|
"version": "1.8.0-beta.4",
|
||||||
"name": "@tmagic/dep",
|
"name": "@tmagic/dep",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -127,10 +127,38 @@ export default class Watcher {
|
|||||||
deep = false,
|
deep = false,
|
||||||
type?: DepTargetType | string,
|
type?: DepTargetType | string,
|
||||||
) {
|
) {
|
||||||
this.collectByCallback(nodes, type, ({ node, target }) => {
|
const targets = this.getCollectableTargets(type);
|
||||||
this.removeTargetDep(target, node);
|
|
||||||
this.collectItem(node, target, depExtendedData, deep);
|
if (!targets.length) {
|
||||||
});
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 整棵树只遍历一次、在每个属性上检查所有 target,把结构遍历开销从 ×targets 降到 ×1(详见 collectItems)
|
||||||
|
for (const node of nodes) {
|
||||||
|
this.removeTargetsDep(targets, node);
|
||||||
|
this.collectItems(node, targets, depExtendedData, deep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取本次需要参与收集的 target(过滤规则与 collectByCallback 一致)
|
||||||
|
*
|
||||||
|
* 注:供 editor 的 dep service / worker 跨包批量收集时复用,因此为 public。
|
||||||
|
* @param type 强制收集指定类型的依赖
|
||||||
|
*/
|
||||||
|
public getCollectableTargets(type?: DepTargetType | string): Target[] {
|
||||||
|
const targets: Target[] = [];
|
||||||
|
traverseTarget(
|
||||||
|
this.targetsList,
|
||||||
|
(target) => {
|
||||||
|
if (!type && !target.isCollectByDefault) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
targets.push(target);
|
||||||
|
},
|
||||||
|
type,
|
||||||
|
);
|
||||||
|
return targets;
|
||||||
}
|
}
|
||||||
|
|
||||||
public collectByCallback(
|
public collectByCallback(
|
||||||
@ -195,53 +223,11 @@ export default class Watcher {
|
|||||||
this.clear(nodes, type);
|
this.clear(nodes, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集单个 target 的依赖,等价于 collectItems(node, [target], ...)
|
||||||
|
*/
|
||||||
public collectItem(node: TargetNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
|
public collectItem(node: TargetNode, target: Target, depExtendedData: DepExtendedData = {}, deep = false) {
|
||||||
if (node[NODE_DISABLE_DATA_SOURCE_KEY] && DATA_SOURCE_TARGET_TYPES.has(target.type)) {
|
this.collectItems(node, [target], depExtendedData, deep);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node[NODE_DISABLE_CODE_BLOCK_KEY] && target.type === DepTargetType.CODE_BLOCK) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const collectTarget = (config: Record<string | number, any>, prop = '') => {
|
|
||||||
const doCollect = (key: string, value: any) => {
|
|
||||||
const keyIsItems = key === this.childrenProp;
|
|
||||||
const fullKey = prop ? `${prop}.${key}` : key;
|
|
||||||
|
|
||||||
if (target.isTarget(fullKey, value)) {
|
|
||||||
target.updateDep({
|
|
||||||
id: node[this.idProp],
|
|
||||||
name: `${node[this.nameProp] || node[this.idProp]}`,
|
|
||||||
data: depExtendedData,
|
|
||||||
key: fullKey,
|
|
||||||
});
|
|
||||||
} else if (!keyIsItems && Array.isArray(value)) {
|
|
||||||
for (let i = 0, l = value.length; i < l; i++) {
|
|
||||||
const item = value[i];
|
|
||||||
if (isObject(item)) {
|
|
||||||
collectTarget(item, `${fullKey}[${i}]`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isObject(value)) {
|
|
||||||
collectTarget(value, fullKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyIsItems && deep && Array.isArray(value)) {
|
|
||||||
for (const child of value) {
|
|
||||||
this.collectItem(child, target, depExtendedData, deep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(config)) {
|
|
||||||
if (typeof value === 'undefined' || value === '') continue;
|
|
||||||
|
|
||||||
doCollect(key, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
collectTarget(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public removeTargetDep(target: Target, node: TargetNode, key?: string | number) {
|
public removeTargetDep(target: Target, node: TargetNode, key?: string | number) {
|
||||||
@ -252,4 +238,117 @@ export default class Watcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与 removeTargetDep 等价,但一次子树递归同时处理多个 target,
|
||||||
|
* 把删除阶段的结构遍历从 ×targets 降到 ×1。
|
||||||
|
*
|
||||||
|
* 注:供 editor 的 dep service 跨包批量删除时复用,因此为 public。
|
||||||
|
*/
|
||||||
|
public removeTargetsDep(targets: Target[], node: TargetNode, key?: string | number) {
|
||||||
|
const id = node[this.idProp];
|
||||||
|
for (const target of targets) {
|
||||||
|
target.removeDep(id, key);
|
||||||
|
}
|
||||||
|
if (typeof key === 'undefined' && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) {
|
||||||
|
for (const item of node[this.childrenProp] as TargetNode[]) {
|
||||||
|
this.removeTargetsDep(targets, item, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与 collectItem 等价,但一次遍历同时处理多个 target(不含删除阶段)。
|
||||||
|
*
|
||||||
|
* 关键优化:原实现对每个 target 都完整遍历一遍节点树(O(targets × 树规模)),大页面 + 大量数据源时,
|
||||||
|
* 结构遍历(Object.entries / 递归 / fullKey 字符串拼接)会被重复 targets 次。这里改为「整棵树只遍历一次,
|
||||||
|
* 在每个属性上检查所有 target」,把结构遍历开销从 ×targets 降到 ×1,isTarget 调用次数不变,收集结果完全一致。
|
||||||
|
*
|
||||||
|
* 注:供 editor 的 dep service / worker 跨包批量收集时复用,因此为 public。
|
||||||
|
*/
|
||||||
|
public collectItems(node: TargetNode, targets: Target[], depExtendedData: DepExtendedData = {}, deep = false) {
|
||||||
|
// 对应 collectItem 开头的 NODE_DISABLE_* 判断:被禁用的 target 在该节点及其子树都不收集
|
||||||
|
const activeTargets = this.filterTargetsByNode(node, targets);
|
||||||
|
|
||||||
|
if (!activeTargets.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.collectTargetForTargets(node, node, '', activeTargets, depExtendedData, deep);
|
||||||
|
}
|
||||||
|
|
||||||
|
private filterTargetsByNode(node: TargetNode, targets: Target[]): Target[] {
|
||||||
|
const disableDataSource = Boolean(node[NODE_DISABLE_DATA_SOURCE_KEY]);
|
||||||
|
const disableCodeBlock = Boolean(node[NODE_DISABLE_CODE_BLOCK_KEY]);
|
||||||
|
|
||||||
|
if (!disableDataSource && !disableCodeBlock) {
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets.filter((target) => {
|
||||||
|
if (disableDataSource && DATA_SOURCE_TARGET_TYPES.has(target.type)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (disableCodeBlock && target.type === DepTargetType.CODE_BLOCK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private collectTargetForTargets(
|
||||||
|
node: TargetNode,
|
||||||
|
config: Record<string | number, any>,
|
||||||
|
prop: string,
|
||||||
|
targets: Target[],
|
||||||
|
depExtendedData: DepExtendedData,
|
||||||
|
deep: boolean,
|
||||||
|
) {
|
||||||
|
const id = node[this.idProp];
|
||||||
|
const name = `${node[this.nameProp] || node[this.idProp]}`;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(config)) {
|
||||||
|
if (typeof value === 'undefined' || value === '') continue;
|
||||||
|
|
||||||
|
const keyIsItems = key === this.childrenProp;
|
||||||
|
const fullKey = prop ? `${prop}.${key}` : key;
|
||||||
|
|
||||||
|
// 在该属性上检查所有 target:命中的更新依赖;未命中的留待递归到更深层
|
||||||
|
let notMatched: Target[] | null = null;
|
||||||
|
for (let i = 0, l = targets.length; i < l; i++) {
|
||||||
|
const target = targets[i];
|
||||||
|
if (target.isTarget(fullKey, value, config)) {
|
||||||
|
target.updateDep({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
data: depExtendedData,
|
||||||
|
key: fullKey,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
(notMatched || (notMatched = [])).push(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应原 doCollect 的 else-if 分支:仅未命中的 target 才继续往 value 内部递归
|
||||||
|
if (notMatched) {
|
||||||
|
if (!keyIsItems && Array.isArray(value)) {
|
||||||
|
for (let i = 0, l = value.length; i < l; i++) {
|
||||||
|
const item = value[i];
|
||||||
|
if (isObject(item)) {
|
||||||
|
this.collectTargetForTargets(node, item, `${fullKey}[${i}]`, notMatched, depExtendedData, deep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isObject(value)) {
|
||||||
|
this.collectTargetForTargets(node, value, fullKey, notMatched, depExtendedData, deep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对应原 doCollect 末尾的无条件子节点递归
|
||||||
|
if (keyIsItems && deep && Array.isArray(value)) {
|
||||||
|
for (const child of value) {
|
||||||
|
this.collectItems(child, targets, depExtendedData, deep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,7 @@ export enum DepTargetType {
|
|||||||
DATA_SOURCE_COND = 'data-source-cond',
|
DATA_SOURCE_COND = 'data-source-cond',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IsTarget = (key: string | number, value: any) => boolean;
|
export type IsTarget = (key: string | number, value: any, data?: Record<string, any>) => boolean;
|
||||||
|
|
||||||
export interface TargetOptions {
|
export interface TargetOptions {
|
||||||
isTarget: IsTarget;
|
isTarget: IsTarget;
|
||||||
|
|||||||
@ -25,4 +25,71 @@ describe('Target', () => {
|
|||||||
expect(defaultTarget.type).toBe('default');
|
expect(defaultTarget.type).toBe('default');
|
||||||
expect(target.type).toBe('target');
|
expect(target.type).toBe('target');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('initialDeps / name / isCollectByDefault 默认值', () => {
|
||||||
|
const t = new Target({
|
||||||
|
isTarget: () => true,
|
||||||
|
id: 't1',
|
||||||
|
name: 'first',
|
||||||
|
initialDeps: { node_1: { name: 'n', keys: ['k1'] } },
|
||||||
|
});
|
||||||
|
expect(t.name).toBe('first');
|
||||||
|
expect(t.deps.node_1.keys).toEqual(['k1']);
|
||||||
|
expect(t.isCollectByDefault).toBe(true);
|
||||||
|
|
||||||
|
const t2 = new Target({
|
||||||
|
isTarget: () => true,
|
||||||
|
id: 't2',
|
||||||
|
isCollectByDefault: false,
|
||||||
|
});
|
||||||
|
expect(t2.isCollectByDefault).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updateDep 累加 keys 并保留 name/data', () => {
|
||||||
|
const t = new Target({ isTarget: () => true, id: 't' });
|
||||||
|
t.updateDep({ id: 'n1', name: 'n1-name', key: 'key1', data: { foo: 1 } });
|
||||||
|
expect(t.deps.n1.name).toBe('n1-name');
|
||||||
|
expect(t.deps.n1.keys).toEqual(['key1']);
|
||||||
|
expect((t.deps.n1 as any).data).toEqual({ foo: 1 });
|
||||||
|
|
||||||
|
t.updateDep({ id: 'n1', name: 'n1-name', key: 'key2', data: { foo: 2 } });
|
||||||
|
expect(t.deps.n1.keys).toEqual(['key1', 'key2']);
|
||||||
|
|
||||||
|
t.updateDep({ id: 'n1', name: 'n1-name', key: 'key1', data: { foo: 3 } });
|
||||||
|
expect(t.deps.n1.keys).toEqual(['key1', 'key2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('removeDep 全删 / 删指定 id / 按 key 删', () => {
|
||||||
|
const t = new Target({ isTarget: () => true, id: 't' });
|
||||||
|
t.updateDep({ id: 'n1', name: 'n', key: 'k1', data: {} });
|
||||||
|
t.updateDep({ id: 'n1', name: 'n', key: 'k2', data: {} });
|
||||||
|
t.updateDep({ id: 'n2', name: 'n', key: 'k1', data: {} });
|
||||||
|
|
||||||
|
t.removeDep('n1', 'k1');
|
||||||
|
expect(t.deps.n1.keys).toEqual(['k2']);
|
||||||
|
|
||||||
|
t.removeDep('n1', 'k2');
|
||||||
|
expect(t.deps.n1).toBeUndefined();
|
||||||
|
|
||||||
|
t.removeDep('n2');
|
||||||
|
expect(t.deps.n2).toBeUndefined();
|
||||||
|
|
||||||
|
t.updateDep({ id: 'a', name: 'a', key: 'k', data: {} });
|
||||||
|
t.updateDep({ id: 'b', name: 'b', key: 'k', data: {} });
|
||||||
|
t.removeDep();
|
||||||
|
expect(Object.keys(t.deps)).toHaveLength(0);
|
||||||
|
|
||||||
|
t.removeDep('not-exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasDep / destroy', () => {
|
||||||
|
const t = new Target({ isTarget: () => true, id: 't' });
|
||||||
|
t.updateDep({ id: 'n1', name: 'n', key: 'k', data: {} });
|
||||||
|
expect(t.hasDep('n1', 'k')).toBe(true);
|
||||||
|
expect(t.hasDep('n1', 'other')).toBe(false);
|
||||||
|
expect(t.hasDep('not-exist', 'k')).toBe(false);
|
||||||
|
|
||||||
|
t.destroy();
|
||||||
|
expect(t.deps).toEqual({});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { describe, expect, test } from 'vitest';
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
import { DataSchema } from '@tmagic/schema';
|
import { DataSchema, NODE_CONDS_KEY } from '@tmagic/schema';
|
||||||
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX } from '@tmagic/utils';
|
||||||
|
|
||||||
|
import Target from '../src/Target';
|
||||||
|
import { DepTargetType } from '../src/types';
|
||||||
import * as utils from '../src/utils';
|
import * as utils from '../src/utils';
|
||||||
|
|
||||||
describe('utils', () => {
|
describe('utils', () => {
|
||||||
@ -193,4 +195,94 @@ describe('utils', () => {
|
|||||||
}),
|
}),
|
||||||
).toBeTruthy();
|
).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('isDataSourceTarget', () => {
|
||||||
|
const ds = { id: 'ds_1', fields: [{ name: 'name', type: 'string' }] as DataSchema[] };
|
||||||
|
|
||||||
|
expect(utils.isDataSourceTarget(ds, 'k', null)).toBe(false);
|
||||||
|
expect(utils.isDataSourceTarget(ds, 'k', 123)).toBe(false);
|
||||||
|
|
||||||
|
expect(utils.isDataSourceTarget(ds, `${NODE_CONDS_KEY}_x`, '${ds_1.name}')).toBe(false);
|
||||||
|
|
||||||
|
expect(utils.isDataSourceTarget(ds, 'text', '${ds_1.name}')).toBe(true);
|
||||||
|
expect(utils.isDataSourceTarget(ds, 'text', '${other.name}')).toBe(false);
|
||||||
|
|
||||||
|
expect(utils.isDataSourceTarget(ds, 'text', { isBindDataSource: true, dataSourceId: 'ds_1' })).toBe(true);
|
||||||
|
expect(utils.isDataSourceTarget(ds, 'text', { isBindDataSource: true, dataSourceId: 'other' })).toBe(false);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isDataSourceTarget(ds, 'text', {
|
||||||
|
isBindDataSourceField: true,
|
||||||
|
dataSourceId: 'ds_1',
|
||||||
|
template: 'foo${name}',
|
||||||
|
}),
|
||||||
|
).toBe(true);
|
||||||
|
|
||||||
|
expect(utils.isDataSourceTarget(ds, 'text', [`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}ds_1`, 'name'])).toBe(true);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
utils.isDataSourceTarget(
|
||||||
|
{ id: 'ds_1', fields: [{ name: 'arr', type: 'array', fields: [{ name: 'a' }] }] as DataSchema[] },
|
||||||
|
'text',
|
||||||
|
[`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}ds_1`, 'arr', 'a'],
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isDataSourceCondTarget', () => {
|
||||||
|
const ds = { id: 'ds_1', fields: [{ name: 'name' }] as DataSchema[] };
|
||||||
|
|
||||||
|
expect(utils.isDataSourceCondTarget(ds, 'k', 'not-array')).toBe(false);
|
||||||
|
expect(utils.isDataSourceCondTarget(ds, 'k', null as any)).toBe(false);
|
||||||
|
|
||||||
|
expect(utils.isDataSourceCondTarget(ds, `${NODE_CONDS_KEY}_x`, ['ds_1', 'name'])).toBe(true);
|
||||||
|
expect(utils.isDataSourceCondTarget(ds, 'k', ['ds_1', 'name'])).toBe(false);
|
||||||
|
expect(utils.isDataSourceCondTarget(ds, `${NODE_CONDS_KEY}_x`, ['other', 'name'])).toBe(false);
|
||||||
|
expect(utils.isDataSourceCondTarget(ds, `${NODE_CONDS_KEY}_x`, ['ds_1', 'unknown'])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('createDataSourceTarget / Cond / Method', () => {
|
||||||
|
const ds = { id: 'ds_1', fields: [{ name: 'name' }] as DataSchema[] };
|
||||||
|
const t1 = utils.createDataSourceTarget(ds);
|
||||||
|
expect(t1.type).toBe(DepTargetType.DATA_SOURCE);
|
||||||
|
expect(t1.isTarget('text', '${ds_1.name}')).toBe(true);
|
||||||
|
|
||||||
|
const t2 = utils.createDataSourceCondTarget(ds);
|
||||||
|
expect(t2.type).toBe(DepTargetType.DATA_SOURCE_COND);
|
||||||
|
expect(t2.isTarget(`${NODE_CONDS_KEY}_x`, ['ds_1', 'name'])).toBe(true);
|
||||||
|
|
||||||
|
const t3 = utils.createDataSourceMethodTarget({
|
||||||
|
id: 'ds_1',
|
||||||
|
methods: [{ name: 'load', content: () => undefined, params: [] } as any],
|
||||||
|
fields: [{ name: 'name' }] as DataSchema[],
|
||||||
|
});
|
||||||
|
expect(t3.type).toBe(DepTargetType.DATA_SOURCE_METHOD);
|
||||||
|
expect(t3.isTarget('k', ['ds_1', 'load'])).toBe(true);
|
||||||
|
expect(t3.isTarget('k', ['ds_1', 'name'])).toBe(false);
|
||||||
|
expect(t3.isTarget('k', ['other', 'load'])).toBe(false);
|
||||||
|
expect(t3.isTarget('k', 'not-array')).toBe(false);
|
||||||
|
expect(t3.isTarget('k', ['ds_1', ''])).toBe(false);
|
||||||
|
expect(t3.isTarget('k', ['ds_1', 'unknown'])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('traverseTarget 遍历所有 / 指定 type', () => {
|
||||||
|
const t1 = new Target({ id: '1', isTarget: () => true, type: 'a' });
|
||||||
|
const t2 = new Target({ id: '2', isTarget: () => true, type: 'b' });
|
||||||
|
const list = {
|
||||||
|
a: { 1: t1 },
|
||||||
|
b: { 2: t2 },
|
||||||
|
};
|
||||||
|
const visited: string[] = [];
|
||||||
|
utils.traverseTarget(list, (t) => visited.push(`${t.type}:${t.id}`));
|
||||||
|
expect(visited).toEqual(expect.arrayContaining(['a:1', 'b:2']));
|
||||||
|
|
||||||
|
const visitedA: string[] = [];
|
||||||
|
utils.traverseTarget(list, (t) => visitedA.push(`${t.type}:${t.id}`), 'a');
|
||||||
|
expect(visitedA).toEqual(['a:1']);
|
||||||
|
|
||||||
|
const visitedX: string[] = [];
|
||||||
|
utils.traverseTarget(list, (t) => visitedX.push(`${t.type}:${t.id}`), 'not-exist');
|
||||||
|
expect(visitedX).toEqual([]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.14-beta.0",
|
"version": "1.8.0-beta.4",
|
||||||
"name": "@tmagic/design",
|
"name": "@tmagic/design",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@ -32,6 +32,7 @@ export interface ButtonProps {
|
|||||||
circle?: boolean;
|
circle?: boolean;
|
||||||
icon?: any;
|
icon?: any;
|
||||||
variant?: string;
|
variant?: string;
|
||||||
|
bg?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardProps {
|
export interface CardProps {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user