mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-07-23 18:30:43 +08:00
Compare commits
No commits in common. "master" and "v1.8.0-beta.5" have entirely different histories.
master
...
v1.8.0-bet
1
.gitignore
vendored
1
.gitignore
vendored
@ -20,7 +20,6 @@ pnpm-debug.log*
|
|||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
.codebuddy
|
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
|||||||
10
AGENTS.md
10
AGENTS.md
@ -14,13 +14,12 @@ TMagic Editor 是魔方平台的可视化编辑器核心库,提供拖拽式组
|
|||||||
## 架构地图
|
## 架构地图
|
||||||
|
|
||||||
关键目录:
|
关键目录:
|
||||||
|
- `packages/` — 核心编辑器包(202 *.vue, 194 *.ts)
|
||||||
- `packages/` — 核心编辑器包
|
|
||||||
- `runtime/` — Vue/React Runtime 实现
|
- `runtime/` — Vue/React Runtime 实现
|
||||||
- `vue-components/` — Vue 组件封装
|
- `vue-components/` — Vue 组件封装
|
||||||
- `react-components/` — React 组件封装
|
- `react-components/` — React 组件封装
|
||||||
- `playground/` — 演示 playground
|
- `playground/` — 演示 playground
|
||||||
- `docs/` — VitePress 文档
|
- `docs/` — VitePress 文档(100 *.md)
|
||||||
- `scripts/` — 构建和发布脚本
|
- `scripts/` — 构建和发布脚本
|
||||||
- `eslint-config/` — 共享 ESLint 配置
|
- `eslint-config/` — 共享 ESLint 配置
|
||||||
|
|
||||||
@ -28,13 +27,10 @@ TMagic Editor 是魔方平台的可视化编辑器核心库,提供拖拽式组
|
|||||||
|
|
||||||
**分支策略:** dev=dev, test/prod=master
|
**分支策略:** dev=dev, test/prod=master
|
||||||
**提交规范:** commitlint + husky,`type: 描述`
|
**提交规范:** commitlint + husky,`type: 描述`
|
||||||
**测试覆盖率:** 新增或修改的代码必须补充单元测试,覆盖率不低于 85%
|
|
||||||
|
|
||||||
**禁止事项:**
|
**禁止事项:**
|
||||||
|
|
||||||
- 禁止在核心包中引入腾讯内部专有依赖(开源项目)
|
- 禁止在核心包中引入腾讯内部专有依赖(开源项目)
|
||||||
- 禁止直接修改 CHANGELOG.md,应通过 `pnpm changelog` 生成
|
- 禁止直接修改 CHANGELOG.md,应通过 `pnpm changelog` 生成
|
||||||
- 禁止提交未达 85% 测试覆盖率的新增/修改代码
|
|
||||||
|
|
||||||
## 常用命令
|
## 常用命令
|
||||||
|
|
||||||
@ -54,7 +50,7 @@ TMagic Editor 是魔方平台的可视化编辑器核心库,提供拖拽式组
|
|||||||
## 深入阅读
|
## 深入阅读
|
||||||
|
|
||||||
| 文档 | 说明 |
|
| 文档 | 说明 |
|
||||||
| --------------- | ---------------- |
|
|------|------|
|
||||||
| docs/ | VitePress 文档站 |
|
| docs/ | VitePress 文档站 |
|
||||||
| CONTRIBUTING.md | 贡献指南 |
|
| CONTRIBUTING.md | 贡献指南 |
|
||||||
| CHANGELOG.md | 变更日志 |
|
| CHANGELOG.md | 变更日志 |
|
||||||
|
|||||||
155
CHANGELOG.md
155
CHANGELOG.md
@ -1,158 +1,3 @@
|
|||||||
# [1.8.0-beta.14](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.13...v1.8.0-beta.14) (2026-07-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **editor:** 保留历史操作来源并补发插入事件 ([89689bb](https://github.com/Tencent/tmagic-editor/commit/89689bb9fd2d2d3edcc027b87932cbdd3a5755ad))
|
|
||||||
* **form:** 统一 extendState 合并逻辑 ([0feada8](https://github.com/Tencent/tmagic-editor/commit/0feada8d1fa4057444da746040b3824df09eef07))
|
|
||||||
* **stage:** 修复选中组件时 stage 外层滚动 ([09eecd5](https://github.com/Tencent/tmagic-editor/commit/09eecd5bedd49e5a002fa5ef122eaa1579dc895b))
|
|
||||||
* 调整编辑器配置项展示 ([8699dc1](https://github.com/Tencent/tmagic-editor/commit/8699dc12af9d52b1c460f61f3d3f48e4cf3fc5f6))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **design,editor:** 新增magic-admin主题样式,优化组件表单配置布局样式 ([b13cd34](https://github.com/Tencent/tmagic-editor/commit/b13cd3425b16eaeac33f897bf51e80f7984db782))
|
|
||||||
* playground 支持主题切换 ([ee5247c](https://github.com/Tencent/tmagic-editor/commit/ee5247ca9436c385a5b634d161726006912fe77c))
|
|
||||||
# [1.8.0-beta.13](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.11...v1.8.0-beta.13) (2026-07-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **adapters:** tag 非法 type 回退为默认主题 ([90492ad](https://github.com/Tencent/tmagic-editor/commit/90492ad3eac82f052e99e7a286772a3feac288f7))
|
|
||||||
* **core:** 在 initNode 前校验 config.items 为数组 ([aa71fe9](https://github.com/Tencent/tmagic-editor/commit/aa71fe9ddd011e98b40af04e898bdf79263e0e89))
|
|
||||||
* **editor:** 为 FloatingBox 拖拽遮罩添加失焦等兜底清理,避免残留导致编辑器无法点击 ([4a893d3](https://github.com/Tencent/tmagic-editor/commit/4a893d35df354cfedce8e57b0deaaa0c535f8fba))
|
|
||||||
* **editor:** 修复 FloatingBox 拖动时鼠标进入 iframe 区域事件丢失 ([284be0d](https://github.com/Tencent/tmagic-editor/commit/284be0d276d90fe58cdcd94372dbacb476fd919c))
|
|
||||||
* **editor:** 修复 Monaco 编辑器卸载时容器失效与 Canceled 未处理异常 ([4a15da2](https://github.com/Tencent/tmagic-editor/commit/4a15da21083a30d2590a03fe68c488c71ec44eb2))
|
|
||||||
* **editor:** 修正 StyleSetter 字段 prop 取值逻辑 ([e5e0da3](https://github.com/Tencent/tmagic-editor/commit/e5e0da37852c78728e6506980dc2de6928dd571c))
|
|
||||||
* **editor:** 延迟 FloatingBox 拖拽遮罩到实际位移时显示,修复关闭按钮点击失效 ([9aa251c](https://github.com/Tencent/tmagic-editor/commit/9aa251ce57e36120a1f6c3b4835ba3bcd604448d))
|
|
||||||
* **form,editor:** 完善表单校验与样式属性匹配 ([ef66e8a](https://github.com/Tencent/tmagic-editor/commit/ef66e8a59863f9ffeb7292adcb314e5620c238ef))
|
|
||||||
* **form:** radio group disabled不生效 ([42d45be](https://github.com/Tencent/tmagic-editor/commit/42d45bed187393ae02a1fef1df5d46b114f198a8))
|
|
||||||
* **form:** typeMatch 使用 toLine 统一类型转换并处理异步 type/defaultValue ([6cdf54a](https://github.com/Tencent/tmagic-editor/commit/6cdf54a4a663dd6531c70a069220b6d9a63ca7c0))
|
|
||||||
* **form:** 修复隐藏字段校验处理 ([78b34a4](https://github.com/Tencent/tmagic-editor/commit/78b34a452b1ac263b652ace81ef351f56fae7ff9))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **editor:** 扩展 customContentMenu 支持 getTarget 获取右键目标 ([bbe73aa](https://github.com/Tencent/tmagic-editor/commit/bbe73aae6437cabc8cfada24367c3d969687faa9))
|
|
||||||
* **editor:** 抽离表单视图与对比逻辑 ([6f3f321](https://github.com/Tencent/tmagic-editor/commit/6f3f321736a40b242e7b556b0bea456484f161ea))
|
|
||||||
* **editor:** 支持自定义历史对比/回滚弹窗内表单组件的尺寸 ([605f07c](https://github.com/Tencent/tmagic-editor/commit/605f07c8fe24fd67de22f5cb9adfdb151d3b8772))
|
|
||||||
* **editor:** 支持通过 uiService 配置列布局最小宽度 ([3c21f45](https://github.com/Tencent/tmagic-editor/commit/3c21f45e88dfe7c61552c704bd9cbd09215017fe))
|
|
||||||
* **editor:** 新增属性配置表单校验联动能力 ([0e46692](https://github.com/Tencent/tmagic-editor/commit/0e4669261fe50acaa8f72203dbc342e6eff324e6))
|
|
||||||
* **editor:** 注册编辑器字段内置 typeMatch 校验规则并补充文档 ([1c67b5e](https://github.com/Tencent/tmagic-editor/commit/1c67b5e77b726ded63b12d761faff37e97563ff2))
|
|
||||||
* **form,editor:** 新增 validateForm 校验能力并接入源码保存校验联动 ([d07e48b](https://github.com/Tencent/tmagic-editor/commit/d07e48bb101a3833b57b97001af3f053e2392c7a))
|
|
||||||
* **form:** 支持 rules.typeMatch 按字段 type 校验值类型与选项匹配 ([734e6a8](https://github.com/Tencent/tmagic-editor/commit/734e6a8ee6f9a948273c25461556eb4f54adc1ee))
|
|
||||||
* **table:** 为操作列新增 Popconfirm 二次确认能力 ([9e8272b](https://github.com/Tencent/tmagic-editor/commit/9e8272b521784265f18007e11f94d2ae5ee3faba))
|
|
||||||
* **table:** 抽取 ActionPopconfirm 并优化 sub-actions 展开交互 ([b183e71](https://github.com/Tencent/tmagic-editor/commit/b183e71f642fba84c82b0e8e63bcb03e0d27e610))
|
|
||||||
* **table:** 操作列支持 sub-actions 更多菜单并抽取 ActionButton 组件 ([78a1259](https://github.com/Tencent/tmagic-editor/commit/78a1259665c9d7057f3cf0066beef2604949345b))
|
|
||||||
# [1.8.0-beta.12](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.11...v1.8.0-beta.12) (2026-07-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **adapters:** tag 非法 type 回退为默认主题 ([90492ad](https://github.com/Tencent/tmagic-editor/commit/90492ad3eac82f052e99e7a286772a3feac288f7))
|
|
||||||
* **core:** 在 initNode 前校验 config.items 为数组 ([aa71fe9](https://github.com/Tencent/tmagic-editor/commit/aa71fe9ddd011e98b40af04e898bdf79263e0e89))
|
|
||||||
* **editor:** 为 FloatingBox 拖拽遮罩添加失焦等兜底清理,避免残留导致编辑器无法点击 ([4a893d3](https://github.com/Tencent/tmagic-editor/commit/4a893d35df354cfedce8e57b0deaaa0c535f8fba))
|
|
||||||
* **editor:** 修复 FloatingBox 拖动时鼠标进入 iframe 区域事件丢失 ([284be0d](https://github.com/Tencent/tmagic-editor/commit/284be0d276d90fe58cdcd94372dbacb476fd919c))
|
|
||||||
* **editor:** 修复 Monaco 编辑器卸载时容器失效与 Canceled 未处理异常 ([4a15da2](https://github.com/Tencent/tmagic-editor/commit/4a15da21083a30d2590a03fe68c488c71ec44eb2))
|
|
||||||
* **editor:** 修正 StyleSetter 字段 prop 取值逻辑 ([e5e0da3](https://github.com/Tencent/tmagic-editor/commit/e5e0da37852c78728e6506980dc2de6928dd571c))
|
|
||||||
* **editor:** 延迟 FloatingBox 拖拽遮罩到实际位移时显示,修复关闭按钮点击失效 ([9aa251c](https://github.com/Tencent/tmagic-editor/commit/9aa251ce57e36120a1f6c3b4835ba3bcd604448d))
|
|
||||||
* **form,editor:** 完善表单校验与样式属性匹配 ([ef66e8a](https://github.com/Tencent/tmagic-editor/commit/ef66e8a59863f9ffeb7292adcb314e5620c238ef))
|
|
||||||
* **form:** radio group disabled不生效 ([42d45be](https://github.com/Tencent/tmagic-editor/commit/42d45bed187393ae02a1fef1df5d46b114f198a8))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **editor:** 扩展 customContentMenu 支持 getTarget 获取右键目标 ([bbe73aa](https://github.com/Tencent/tmagic-editor/commit/bbe73aae6437cabc8cfada24367c3d969687faa9))
|
|
||||||
* **editor:** 支持自定义历史对比/回滚弹窗内表单组件的尺寸 ([605f07c](https://github.com/Tencent/tmagic-editor/commit/605f07c8fe24fd67de22f5cb9adfdb151d3b8772))
|
|
||||||
* **editor:** 支持通过 uiService 配置列布局最小宽度 ([3c21f45](https://github.com/Tencent/tmagic-editor/commit/3c21f45e88dfe7c61552c704bd9cbd09215017fe))
|
|
||||||
* **editor:** 新增属性配置表单校验联动能力 ([0e46692](https://github.com/Tencent/tmagic-editor/commit/0e4669261fe50acaa8f72203dbc342e6eff324e6))
|
|
||||||
* **editor:** 注册编辑器字段内置 typeMatch 校验规则并补充文档 ([1c67b5e](https://github.com/Tencent/tmagic-editor/commit/1c67b5e77b726ded63b12d761faff37e97563ff2))
|
|
||||||
* **form,editor:** 新增 validateForm 校验能力并接入源码保存校验联动 ([d07e48b](https://github.com/Tencent/tmagic-editor/commit/d07e48bb101a3833b57b97001af3f053e2392c7a))
|
|
||||||
* **form:** 支持 rules.typeMatch 按字段 type 校验值类型与选项匹配 ([734e6a8](https://github.com/Tencent/tmagic-editor/commit/734e6a8ee6f9a948273c25461556eb4f54adc1ee))
|
|
||||||
* **table:** 为操作列新增 Popconfirm 二次确认能力 ([9e8272b](https://github.com/Tencent/tmagic-editor/commit/9e8272b521784265f18007e11f94d2ae5ee3faba))
|
|
||||||
* **table:** 抽取 ActionPopconfirm 并优化 sub-actions 展开交互 ([b183e71](https://github.com/Tencent/tmagic-editor/commit/b183e71f642fba84c82b0e8e63bcb03e0d27e610))
|
|
||||||
* **table:** 操作列支持 sub-actions 更多菜单并抽取 ActionButton 组件 ([78a1259](https://github.com/Tencent/tmagic-editor/commit/78a1259665c9d7057f3cf0066beef2604949345b))
|
|
||||||
# [1.8.0-beta.11](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.10...v1.8.0-beta.11) (2026-06-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **editor:** support custom width and initial diff mode for history diff dialog ([83f4e52](https://github.com/Tencent/tmagic-editor/commit/83f4e528450be3971eea25b59dab126e04ac4df4))
|
|
||||||
* **form:** add debug mode to submitForm ([4330738](https://github.com/Tencent/tmagic-editor/commit/4330738b9f3d91c728826f5a74129cf1febbcf48))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.8.0-beta.10](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.9...v1.8.0-beta.10) (2026-06-26)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **vue-runtime-help:** handle remove when root is not initialized ([af19736](https://github.com/Tencent/tmagic-editor/commit/af197363cfaa5c555758c19354a0b3a5031b973e))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **editor:** add unified change events ([7bbb1f2](https://github.com/Tencent/tmagic-editor/commit/7bbb1f24c0f7180f301f573de1b916bfd0a35152))
|
|
||||||
* **editor:** refine history diff defaults ([f7afed6](https://github.com/Tencent/tmagic-editor/commit/f7afed66aaa8f57cdde02f0e25cca1dab825e7c0))
|
|
||||||
* **form:** support useFieldTextInError option ([fd359e4](https://github.com/Tencent/tmagic-editor/commit/fd359e493fdf205126957799a4a228f31267f3c2))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.8.0-beta.9](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.8...v1.8.0-beta.9) (2026-06-25)
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **editor:** 优化历史记录对比与回滚流程 ([ce65b18](https://github.com/Tencent/tmagic-editor/commit/ce65b18dbb2976db484ab35a98479858b4c426e4))
|
|
||||||
* 导出 componentListService 和 keybindingService ([40acb5b](https://github.com/Tencent/tmagic-editor/commit/40acb5b34cb3b492bc5234c75bde4fbf8e787be2))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.8.0-beta.8](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.7...v1.8.0-beta.8) (2026-06-23)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **playground:** 调整初始化时序避免历史恢复被覆盖 ([bfdaf2b](https://github.com/Tencent/tmagic-editor/commit/bfdaf2b244dfe7387cac5b399a84ccd614201e73))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **editor:** 历史记录支持展示操作人 ([1ade61d](https://github.com/Tencent/tmagic-editor/commit/1ade61d62e61c86fcff2033a3dd57c533ce4241c))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.8.0-beta.7](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.6...v1.8.0-beta.7) (2026-06-17)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **editor:** sidebar 侧栏项 key 与 SideItemKey 常量对齐 ([8933c46](https://github.com/Tencent/tmagic-editor/commit/8933c46b73525aa2aeca81aeea88610539a218d9))
|
|
||||||
* **editor:** 修复历史记录面板分组与展示逻辑 ([bbf79fd](https://github.com/Tencent/tmagic-editor/commit/bbf79fd6df505351bc28d63bea1a5c0cd16a5984))
|
|
||||||
* **editor:** 恢复新增组件在当前视窗中垂直居中 ([24b9b34](https://github.com/Tencent/tmagic-editor/commit/24b9b34f6569ed671edaf4e9f14622597688d72c))
|
|
||||||
* **editor:** 数据源配置面板默认激活「数据定义」tab ([95769d6](https://github.com/Tencent/tmagic-editor/commit/95769d6ae05d6171366ad5995f6e8ce79491d5cd))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.8.0-beta.6](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.5...v1.8.0-beta.6) (2026-06-15)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **editor:** 仅在使用 left/top 定位时修正节点位置 ([27fac02](https://github.com/Tencent/tmagic-editor/commit/27fac02e994b69417622d009cb69037d99eef10a))
|
|
||||||
* **editor:** 完善 fixed 与 absolute 定位切换逻辑 ([1298104](https://github.com/Tencent/tmagic-editor/commit/1298104732de1074ecc1398777459dd2fcbb74f7))
|
|
||||||
* **playground:** 修复历史恢复后加载 DSL 时缺少页面基线记录 ([4a82891](https://github.com/Tencent/tmagic-editor/commit/4a8289137b539a6597d1b978418bb9901392e5c3))
|
|
||||||
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
* **editor:** 增强 *AndGetHistoryId 与 revertById 的 historyIds 能力 ([6960bd5](https://github.com/Tencent/tmagic-editor/commit/6960bd50e16fe612e486eb78af7630642f5e6c8f))
|
|
||||||
* **editor:** 数据源与代码块历史记录不再合并相邻操作 ([89cef4e](https://github.com/Tencent/tmagic-editor/commit/89cef4e9a9b1ffb11ab72c33e8b283bf59377f47))
|
|
||||||
* **editor:** 查看/编辑时自动切换 sidebar 并定位到对应配置 ([ffcc734](https://github.com/Tencent/tmagic-editor/commit/ffcc7341021da525263819cb6559cf20c2afea6a))
|
|
||||||
* **editor:** 边框方向有配置时显示高亮状态 ([b35132e](https://github.com/Tencent/tmagic-editor/commit/b35132e93eba6136990ba82912e53887c17c0cc1))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [1.8.0-beta.5](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.4...v1.8.0-beta.5) (2026-06-11)
|
# [1.8.0-beta.5](https://github.com/Tencent/tmagic-editor/compare/v1.8.0-beta.4...v1.8.0-beta.5) (2026-06-11)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -111,10 +111,6 @@ export default defineConfig({
|
|||||||
text: '@tmagic/form',
|
text: '@tmagic/form',
|
||||||
link: '/guide/advanced/tmagic-form.md',
|
link: '/guide/advanced/tmagic-form.md',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: '属性配置表单校验联动',
|
|
||||||
link: '/guide/advanced/prop-form-validate.md',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -492,15 +488,6 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: '表单校验',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
text: '表单校验',
|
|
||||||
link: '/form-config/rules.md',
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
text: '表单对比',
|
text: '表单对比',
|
||||||
items: [
|
items: [
|
||||||
|
|||||||
@ -162,7 +162,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--el-color-primary,#409eff);;
|
color: #409eff;
|
||||||
background-color: #f9fafc;
|
background-color: #f9fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# codeBlockService方法
|
# codeBlockService方法
|
||||||
|
|
||||||
写入历史栈的方法([setCodeDslById](#setcodedslbyid)、[setCodeDslByIdSync](#setcodedslbyidsync)、[deleteCodeDslByIds](#deletecodedslbyids) 等)的 `options` 支持
|
写入历史栈的方法([setCodeDslById](#setcodedslbyid)、[setCodeDslByIdSync](#setcodedslbyidsync)、[deleteCodeDslByIds](#deletecodedslbyids) 等)的 `options` 支持
|
||||||
[historyDescription / historySource](./editorServiceMethods.md#历史记录相关-options),会透传到 `historyService.push('codeBlock', step, id)` 入栈记录的 `historyDescription` / `source` 字段。
|
[historyDescription / historySource](./editorServiceMethods.md#历史记录相关-options),会透传到 `historyService.pushCodeBlock` 的 `historyDescription` / `source` 字段。
|
||||||
|
|
||||||
## setCodeDsl
|
## setCodeDsl
|
||||||
|
|
||||||
@ -88,8 +88,8 @@
|
|||||||
同步版本的 [setCodeDslById](#setcodedslbyid),并会触发 `addOrUpdate` 事件
|
同步版本的 [setCodeDslById](#setcodedslbyid),并会触发 `addOrUpdate` 事件
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
写入成功时(`force=false` 且同 id 已存在的跳过场景除外)会自动调用 `historyService.push('codeBlock', step, id)`
|
写入成功时(`force=false` 且同 id 已存在的跳过场景除外)会自动调用 `historyService.pushCodeBlock`
|
||||||
把本次变更入历史栈,参见 [historyService.push](./historyServiceMethods.md#push)。
|
把本次变更入历史栈,参见 [historyService.pushCodeBlock](./historyServiceMethods.md#pushcodeblock)。
|
||||||
传入的 `changeRecords` 会一同写进 step,撤销/重做时调用方可据此按 `propPath` 局部回放。
|
传入的 `changeRecords` 会一同写进 step,撤销/重做时调用方可据此按 `propPath` 局部回放。
|
||||||
传入 `doNotPushHistory: true` 可跳过写入历史栈,常用于批量导入、外部同步等非用户操作场景。
|
传入 `doNotPushHistory: true` 可跳过写入历史栈,常用于批量导入、外部同步等非用户操作场景。
|
||||||
:::
|
:::
|
||||||
@ -231,8 +231,8 @@
|
|||||||
在dsl数据源中删除指定id的代码块,每删除一个会触发一次 `remove` 事件
|
在dsl数据源中删除指定id的代码块,每删除一个会触发一次 `remove` 事件
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
对每个实际存在并被删除的代码块,会自动调用 `historyService.push('codeBlock', step, id)` 入栈一条
|
对每个实际存在并被删除的代码块,会自动调用 `historyService.pushCodeBlock` 入栈一条
|
||||||
`newSchema=null` 的删除记录;不存在的 id 不会入历史。传入 `doNotPushHistory: true` 也可显式跳过写入历史栈。
|
`newContent=null` 的删除记录;不存在的 id 不会入历史。传入 `doNotPushHistory: true` 也可显式跳过写入历史栈。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## setCodeDslByIdAndGetHistoryId
|
## setCodeDslByIdAndGetHistoryId
|
||||||
@ -240,12 +240,11 @@
|
|||||||
- **参数:** 同 [setCodeDslById](#setcodedslbyid)
|
- **参数:** 同 [setCodeDslById](#setcodedslbyid)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {`Promise<`[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)`>`}
|
- {`Promise<string | null>`} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true` 等)时返回 `null`
|
||||||
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史(`doNotPushHistory: true` 等)时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [setCodeDslById](#setcodedslbyid) 行为完全一致,并在返回值中额外提供 `historyIds`,可用于精确引用 / 定位该条历史记录。
|
与 [setCodeDslById](#setcodedslbyid) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,可用于精确引用 / 定位该条历史记录。
|
||||||
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
@ -253,11 +252,11 @@
|
|||||||
```js
|
```js
|
||||||
import { codeBlockService } from "@tmagic/editor";
|
import { codeBlockService } from "@tmagic/editor";
|
||||||
|
|
||||||
const { historyIds } = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", {
|
const historyId = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", {
|
||||||
name: "代码块1",
|
name: "代码块1",
|
||||||
content: "() => {}",
|
content: "() => {}",
|
||||||
});
|
});
|
||||||
console.log(historyIds); // 本次变更对应的历史记录 uuid 列表,或 []
|
console.log(historyId); // 本次变更对应的历史记录 uuid,或 null
|
||||||
```
|
```
|
||||||
|
|
||||||
## setCodeDslByIdSyncAndGetHistoryId
|
## setCodeDslByIdSyncAndGetHistoryId
|
||||||
@ -265,46 +264,44 @@ console.log(historyIds); // 本次变更对应的历史记录 uuid 列表,或
|
|||||||
- **参数:** 同 [setCodeDslByIdSync](#setcodedslbyidsync)
|
- **参数:** 同 [setCodeDslByIdSync](#setcodedslbyidsync)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
|
- {`string | null`} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true`、或 `force=false` 跳过等)时返回 `null`
|
||||||
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史(`doNotPushHistory: true`、或 `force=false` 跳过等)时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [setCodeDslByIdSync](#setcodedslbyidsync) 行为完全一致(同步),并在返回值中额外提供 `historyIds`
|
与 [setCodeDslByIdSync](#setcodedslbyidsync) 行为完全一致(同步),仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## deleteCodeDslByIdsAndGetHistoryId
|
## deleteCodeDslByIdsAndGetHistoryId
|
||||||
|
|
||||||
- **参数:** 同 [deleteCodeDslByIds](#deletecodedslbyids)
|
- **参数:** 同 [deleteCodeDslByIds](#deletecodedslbyids)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {`Promise<`[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)`>`}
|
- {`Promise<string[]>`} 本次写入的全部历史记录 uuid(按删除顺序);未写入任何历史时返回空数组 `[]`
|
||||||
- `historyIds`:本次写入的全部历史记录 uuid(按删除顺序);未写入任何历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [deleteCodeDslByIds](#deletecodedslbyids) 行为完全一致。由于一次可删除多个代码块、会产生多条历史记录,因此使用 `historyIds` 数组(每条删除记录一个 uuid);不存在的 id 不会入历史,也不会出现在 `historyIds` 中。
|
与 [deleteCodeDslByIds](#deletecodedslbyids) 行为完全一致。由于一次可删除多个代码块、会产生多条历史记录,因此返回的是 uuid 数组(每条删除记录一个 uuid);不存在的 id 不会入历史,也不会出现在返回数组中。
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { codeBlockService } from "@tmagic/editor";
|
import { codeBlockService } from "@tmagic/editor";
|
||||||
|
|
||||||
const { historyIds } = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(["code_1", "code_2"]);
|
const historyIds = await codeBlockService.deleteCodeDslByIdsAndGetHistoryId(["code_1", "code_2"]);
|
||||||
console.log(historyIds); // ['xxxx', 'yyyy'],或 []
|
console.log(historyIds); // ['xxxx', 'yyyy'],或 []
|
||||||
```
|
```
|
||||||
|
|
||||||
## revertById
|
## revertById
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string[]}` uuids 目标历史记录的 uuid 列表(通常由 [setCodeDslByIdAndGetHistoryId](#setcodedslbyidandgethistoryid) 等方法返回的 `historyIds`)
|
- `{string}` uuid 目标历史记录的 uuid(通常由 [setCodeDslByIdAndGetHistoryId](#setcodedslbyidandgethistoryid) 等方法返回)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {`Promise<(`CodeBlockStepValue` | null)[]>`} 与入参同序的回滚结果列表,某项失败时为 `null`
|
- {`Promise<CodeBlockStepValue | null>`} 反向应用后产生的新 step;找不到对应 uuid / 该步未应用时返回 `null`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
通过历史记录 uuid「回滚」代码块历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
|
通过历史记录 uuid「回滚」某条代码块历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
|
||||||
仅无需调用方再传 `codeBlockId` 与 `index`。**按数组顺序依次回滚**。
|
仅无需调用方再传 `codeBlockId` 与 `index`:内部会按 uuid 在全部代码块栈中定位对应步骤后再回滚。
|
||||||
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
@ -312,9 +309,9 @@ console.log(historyIds); // ['xxxx', 'yyyy'],或 []
|
|||||||
```js
|
```js
|
||||||
import { codeBlockService } from "@tmagic/editor";
|
import { codeBlockService } from "@tmagic/editor";
|
||||||
|
|
||||||
const { historyIds } = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", { name: "代码块1" });
|
const historyId = await codeBlockService.setCodeDslByIdAndGetHistoryId("code_1234", { name: "代码块1" });
|
||||||
if (historyIds.length) {
|
if (historyId) {
|
||||||
await codeBlockService.revertById(historyIds);
|
await codeBlockService.revertById(historyId);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -370,7 +367,7 @@ if (codeBlockService.canUndo("code_1234")) {
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
当前指定代码块是否可撤销,等价于 `historyService.canUndo('codeBlock', id)`。
|
当前指定代码块是否可撤销,等价于 `historyService.canUndoCodeBlock(id)`。
|
||||||
|
|
||||||
## canRedo
|
## canRedo
|
||||||
|
|
||||||
@ -382,7 +379,7 @@ if (codeBlockService.canUndo("code_1234")) {
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
当前指定代码块是否可重做,等价于 `historyService.canRedo('codeBlock', id)`。
|
当前指定代码块是否可重做,等价于 `historyService.canRedoCodeBlock(id)`。
|
||||||
|
|
||||||
## setParamsColConfig
|
## setParamsColConfig
|
||||||
|
|
||||||
|
|||||||
@ -311,8 +311,8 @@ dataSourceService.setFormMethod("http", [
|
|||||||
添加一个数据源,如果配置中没有id或id已存在,会自动生成新的id
|
添加一个数据源,如果配置中没有id或id已存在,会自动生成新的id
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
添加成功会自动调用 `historyService.push('dataSource', step, id)` 入栈一条 `oldSchema=null` 的新增记录,
|
添加成功会自动调用 `historyService.pushDataSource` 入栈一条 `oldSchema=null` 的新增记录,
|
||||||
参见 [historyService.push](./historyServiceMethods.md#push)。
|
参见 [historyService.pushDataSource](./historyServiceMethods.md#pushdatasource)。
|
||||||
传入 `doNotPushHistory: true` 可跳过写入历史栈,常用于批量导入、外部同步等非用户操作场景。
|
传入 `doNotPushHistory: true` 可跳过写入历史栈,常用于批量导入、外部同步等非用户操作场景。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@ -355,7 +355,7 @@ console.log(newDs.id); // 自动生成的id
|
|||||||
更新数据源
|
更新数据源
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
更新成功会自动调用 `historyService.push('dataSource', step, id)` 入栈一条 `oldSchema` / `newSchema`
|
更新成功会自动调用 `historyService.pushDataSource` 入栈一条 `oldSchema` / `newSchema`
|
||||||
均为对应 schema 的更新记录,传入的 `changeRecords` 也会一并写进 step;撤销/重做时调用方可据此按
|
均为对应 schema 的更新记录,传入的 `changeRecords` 也会一并写进 step;撤销/重做时调用方可据此按
|
||||||
`propPath` 局部回放,缺省才退化为整 schema 替换。传入 `doNotPushHistory: true` 可跳过写入历史栈。
|
`propPath` 局部回放,缺省才退化为整 schema 替换。传入 `doNotPushHistory: true` 可跳过写入历史栈。
|
||||||
:::
|
:::
|
||||||
@ -394,7 +394,7 @@ console.log(updatedDs);
|
|||||||
删除指定id的数据源
|
删除指定id的数据源
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
对实际存在的数据源会自动调用 `historyService.push('dataSource', step, id)` 入栈一条 `newSchema=null`
|
对实际存在的数据源会自动调用 `historyService.pushDataSource` 入栈一条 `newSchema=null`
|
||||||
的删除记录;不存在的 id 不会入历史。传入 `doNotPushHistory: true` 也可显式跳过写入历史栈。
|
的删除记录;不存在的 id 不会入历史。传入 `doNotPushHistory: true` 也可显式跳过写入历史栈。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
@ -411,13 +411,11 @@ dataSourceService.remove("ds_123");
|
|||||||
- **参数:** 同 [add](#add)
|
- **参数:** 同 [add](#add)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[`DslOpWithHistoryIdsResult<DataSourceSchema>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
|
- {`string` | null} 本次写入历史记录的 uuid;未写入历史(`doNotPushHistory: true` 等)时返回 `null`
|
||||||
- `result`:同 [add](#add) 的返回值(新增的数据源配置)
|
|
||||||
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史(`doNotPushHistory: true` 等)时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [add](#add) 行为完全一致,并在返回值中额外提供 `historyIds`,可用于精确引用 / 定位该条历史记录。
|
与 [add](#add) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,可用于精确引用 / 定位该条历史记录。
|
||||||
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
@ -425,13 +423,12 @@ dataSourceService.remove("ds_123");
|
|||||||
```js
|
```js
|
||||||
import { dataSourceService } from "@tmagic/editor";
|
import { dataSourceService } from "@tmagic/editor";
|
||||||
|
|
||||||
const { result, historyIds } = dataSourceService.addAndGetHistoryId({
|
const historyId = dataSourceService.addAndGetHistoryId({
|
||||||
type: "http",
|
type: "http",
|
||||||
title: "用户信息",
|
title: "用户信息",
|
||||||
url: "/api/user",
|
url: "/api/user",
|
||||||
});
|
});
|
||||||
console.log(result); // 新增的数据源配置
|
console.log(historyId); // 本次新增对应的历史记录 uuid,或 null
|
||||||
console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或 []
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## updateAndGetHistoryId
|
## updateAndGetHistoryId
|
||||||
@ -439,38 +436,35 @@ console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或
|
|||||||
- **参数:** 同 [update](#update)
|
- **参数:** 同 [update](#update)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[`DslOpWithHistoryIdsResult<DataSourceSchema>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
|
- {`string` | null} 本次写入历史记录的 uuid;未写入历史时返回 `null`
|
||||||
- `result`:同 [update](#update) 的返回值(更新后的数据源配置)
|
|
||||||
- `historyIds`:本次写入历史记录的 uuid 列表;未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [update](#update) 行为完全一致,并在返回值中额外提供 `historyIds`
|
与 [update](#update) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## removeAndGetHistoryId
|
## removeAndGetHistoryId
|
||||||
|
|
||||||
- **参数:** 同 [remove](#remove)
|
- **参数:** 同 [remove](#remove)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {[`DslOpWithHistoryIdsResult<void>`](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)}
|
- {`string` | null} 本次写入历史记录的 uuid;删除的 id 不存在或未写入历史时返回 `null`
|
||||||
- `historyIds`:本次写入历史记录的 uuid 列表;删除的 id 不存在或未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [remove](#remove) 行为完全一致,并在返回值中额外提供 `historyIds`
|
与 [remove](#remove) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## revertById
|
## revertById
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string[]}` uuids 目标历史记录的 uuid 列表(通常由 [addAndGetHistoryId](#addandgethistoryid) 等方法返回的 `historyIds`)
|
- `{string}` uuid 目标历史记录的 uuid(通常由 [addAndGetHistoryId](#addandgethistoryid) 等方法返回)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {(`DataSourceStepValue` | null)[]} 与入参同序的回滚结果列表,某项失败时为 `null`
|
- {`DataSourceStepValue` | null} 反向应用后产生的新 step;找不到对应 uuid / 该步未应用时返回 `null`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
通过历史记录 uuid「回滚」数据源历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
|
通过历史记录 uuid「回滚」某条数据源历史步骤(类 git revert 语义),语义同按 `(id, index)` 回滚,
|
||||||
仅无需调用方再传 `dataSourceId` 与 `index`。**按数组顺序依次回滚**。
|
仅无需调用方再传 `dataSourceId` 与 `index`:内部会按 uuid 在全部数据源栈中定位对应步骤后再回滚。
|
||||||
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
参见 [editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
@ -478,9 +472,9 @@ console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或
|
|||||||
```js
|
```js
|
||||||
import { dataSourceService } from "@tmagic/editor";
|
import { dataSourceService } from "@tmagic/editor";
|
||||||
|
|
||||||
const { historyIds } = dataSourceService.addAndGetHistoryId({ type: "http", title: "用户信息" });
|
const historyId = dataSourceService.addAndGetHistoryId({ type: "http", title: "用户信息" });
|
||||||
if (historyIds.length) {
|
if (historyId) {
|
||||||
dataSourceService.revertById(historyIds);
|
dataSourceService.revertById(historyId);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -579,7 +573,7 @@ if (dataSourceService.canUndo("ds_123")) {
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
当前指定数据源是否可撤销,等价于 `historyService.canUndo('dataSource', id)`。
|
当前指定数据源是否可撤销,等价于 `historyService.canUndoDataSource(id)`。
|
||||||
|
|
||||||
## canRedo
|
## canRedo
|
||||||
|
|
||||||
@ -591,7 +585,7 @@ if (dataSourceService.canUndo("ds_123")) {
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
当前指定数据源是否可重做,等价于 `historyService.canRedo('dataSource', id)`。
|
当前指定数据源是否可重做,等价于 `historyService.canRedoDataSource(id)`。
|
||||||
|
|
||||||
## copyWithRelated
|
## copyWithRelated
|
||||||
|
|
||||||
|
|||||||
@ -87,35 +87,3 @@
|
|||||||
- **详情:** 历史记录改变,[editorService.redo(),editorService.undo()](./editorServiceMethods.md#undo)后触发
|
- **详情:** 历史记录改变,[editorService.redo(),editorService.undo()](./editorServiceMethods.md#undo)后触发
|
||||||
|
|
||||||
- **事件回调函数:** `(data: MPage | MPageFragment) => void`
|
- **事件回调函数:** `(data: MPage | MPageFragment) => void`
|
||||||
|
|
||||||
## change
|
|
||||||
|
|
||||||
- **详情:** DSL 发生变更后统一触发,免去分别监听 `add` / `remove` / `update` / `move-layer` / `drag-to`。在 [editorService.add()](./editorServiceMethods.md#add)、[editorService.remove()](./editorServiceMethods.md#remove)、[editorService.update()](./editorServiceMethods.md#update)、[editorService.moveLayer()](./editorServiceMethods.md#movelayer)、[editorService.dragTo()](./editorServiceMethods.md#dragto) 后触发。
|
|
||||||
|
|
||||||
回调参数 `event` 通过 `type` 区分操作类型,并携带本次变更的节点列表 `data`,每项包含变更的 `node` 及其所属的 `page`(可能为 `null`)。`move-layer` 额外携带层级偏移 `offset`,`drag-to` 额外携带目标位置 `targetIndex` / `targetParent`。
|
|
||||||
|
|
||||||
::: warning 注意
|
|
||||||
撤销 / 重做(`undo` / `redo`)内部同样会经由 `add` / `remove` / `update` 触发本事件;如需区分「用户操作」与「撤销重做」,请配合 [history-change](#history-change) 事件判断。
|
|
||||||
:::
|
|
||||||
|
|
||||||
- **事件回调函数:** `(event: EditorChangeEvent) => void`
|
|
||||||
|
|
||||||
::: details 查看 EditorChangeEvent 类型定义
|
|
||||||
<<< @/../packages/editor/src/type.ts#EditorChangeEvent{ts}
|
|
||||||
:::
|
|
||||||
|
|
||||||
## invalid-node-change
|
|
||||||
|
|
||||||
- **详情:** 节点校验错误状态发生变化时触发(记录 / 清除错误、删除节点、整体替换 root、撤销重做还原错误标记等场景均会触发),在 [editorService.setInvalidNode()](./editorServiceMethods.md#setinvalidnode)、[editorService.deleteInvalidNode()](./editorServiceMethods.md#deleteinvalidnode)、[editorService.resetInvalidNodeId()](./editorServiceMethods.md#resetinvalidnodeid) 及 DSL 操作 / 撤销重做内部调用后触发。
|
|
||||||
|
|
||||||
携带当前完整的错误 Map,供非响应式消费方(如自定义工具栏)订阅,实现组件树标红、保存拦截等。需响应式读取(如组件树节点内容)请直接读取 `editorService.get('invalidNodeIds')`。
|
|
||||||
|
|
||||||
- **事件回调函数:** `(invalidNodeIds: Map<Id, NodeInvalidInfo>) => void`
|
|
||||||
|
|
||||||
::: details 查看 NodeInvalidInfo 及关联类型定义
|
|
||||||
<<< @/../packages/editor/src/type.ts#NodeInvalidInfo{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#NodeInvalidSource{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/schema/src/index.ts#Id{ts}
|
|
||||||
:::
|
|
||||||
|
|||||||
@ -16,39 +16,37 @@
|
|||||||
|
|
||||||
每条历史记录入栈时都会自动生成一个唯一标识 `uuid`(见 [StepValue](#undo)),可用于精确引用 / 定位某一条历史记录(如埋点、回滚、跨端同步等)。
|
每条历史记录入栈时都会自动生成一个唯一标识 `uuid`(见 [StepValue](#undo)),可用于精确引用 / 定位某一条历史记录(如埋点、回滚、跨端同步等)。
|
||||||
|
|
||||||
DSL 操作方法(`add` / `remove` / `update` 等)默认返回操作结果(节点 / 节点集合 / void),不会返回 `uuid`。若需要同时拿到原操作结果与本次写入历史记录的 `uuid`,可改用对应的 `*AndGetHistoryId` 方法:它们与原方法行为完全一致,返回值类型为 [`DslOpWithHistoryIdsResult<T>`](#历史记录-uuid-与-andgethistoryid)(`result` 为原方法返回值,`historyIds` 为本次写入的 uuid 列表)。当本次操作未写入历史(`doNotPushHistory: true`、无实际变更或提前返回)时 `historyIds` 为 `[]`;单次操作通常返回含一个 uuid 的数组。
|
DSL 操作方法(`add` / `remove` / `update` 等)默认返回操作结果(节点 / 节点集合 / void),不会返回 `uuid`。若需要拿到本次写入历史记录的 `uuid`,可改用对应的 `*AndGetHistoryId` 方法:它们与原方法行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`(`string`)。当本次操作未写入历史(`doNotPushHistory: true`、无实际变更或提前返回)时返回 `null`。
|
||||||
|
|
||||||
| 原方法 | 取 uuid 的方法 | 返回值 |
|
| 原方法 | 取 uuid 的方法 | 返回值 |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| [add](#add) | [addAndGetHistoryId](#addandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<MNode \| MNode[]>>` |
|
| [add](#add) | [addAndGetHistoryId](#addandgethistoryid) | `Promise<string \| null>` |
|
||||||
| [remove](#remove) | [removeAndGetHistoryId](#removeandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<void>>` |
|
| [remove](#remove) | [removeAndGetHistoryId](#removeandgethistoryid) | `Promise<string \| null>` |
|
||||||
| [update](#update) | [updateAndGetHistoryId](#updateandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<MNode \| MNode[]>>` |
|
| [update](#update) | [updateAndGetHistoryId](#updateandgethistoryid) | `Promise<string \| null>` |
|
||||||
| [moveLayer](#movelayer) | [moveLayerAndGetHistoryId](#movelayerandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<void>>` |
|
| [moveLayer](#movelayer) | [moveLayerAndGetHistoryId](#movelayerandgethistoryid) | `Promise<string \| null>` |
|
||||||
| [moveToContainer](#movetocontainer) | [moveToContainerAndGetHistoryId](#movetocontainerandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<MNode \| MNode[]>>` |
|
| [moveToContainer](#movetocontainer) | [moveToContainerAndGetHistoryId](#movetocontainerandgethistoryid) | `Promise<string \| null>` |
|
||||||
| [dragTo](#dragto) | [dragToAndGetHistoryId](#dragtoandgethistoryid) | `Promise<DslOpWithHistoryIdsResult<void>>` |
|
| [dragTo](#dragto) | [dragToAndGetHistoryId](#dragtoandgethistoryid) | `Promise<string \| null>` |
|
||||||
|
|
||||||
[dataSourceService](./dataSourceServiceMethods.md) / [codeBlockService](./codeBlockServiceMethods.md) 也提供了同名约定的 `*AndGetHistoryId` 方法,返回值约定相同(`result` + `historyIds`)。
|
[dataSourceService](./dataSourceServiceMethods.md) / [codeBlockService](./codeBlockServiceMethods.md) 也提供了同名约定的 `*AndGetHistoryId` 方法。
|
||||||
|
|
||||||
拿到 `uuid` 后,可在需要时按 uuid「回滚」对应的历史记录(类 git revert 语义,详见[历史记录面板](../../guide/advanced/history-list.md))。相比按 index 回滚,uuid 不会随栈内步骤增删而变化,更适合业务侧持有引用后再回滚:
|
拿到 `uuid` 后,可在需要时按 uuid「回滚」对应的历史记录(类 git revert 语义,详见[历史记录面板](../../guide/advanced/history-list.md))。相比按 index 回滚,uuid 不会随栈内步骤增删而变化,更适合业务侧持有引用后再回滚:
|
||||||
|
|
||||||
- 页面:[editorService.revertPageStepById(uuids)](#revertpagestepbyid)
|
- 页面:[editorService.revertPageStepById(uuid)](#revertpagestepbyid)
|
||||||
- 数据源:[dataSourceService.revertById(uuids)](./dataSourceServiceMethods.md#revertbyid)
|
- 数据源:[dataSourceService.revertById(uuid)](./dataSourceServiceMethods.md#revertbyid)
|
||||||
- 代码块:[codeBlockService.revertById(uuids)](./codeBlockServiceMethods.md#revertbyid)
|
- 代码块:[codeBlockService.revertById(uuid)](./codeBlockServiceMethods.md#revertbyid)
|
||||||
|
|
||||||
::: details 查看 HistoryOpOptions / DslOpOptions / DslOpWithHistoryIdsResult / HistoryOpSource 类型定义
|
::: details 查看 HistoryOpOptions / DslOpOptions / HistoryOpSource 类型定义
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpOptions{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpOptions{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#DslOpOptions{ts}
|
<<< @/../packages/editor/src/type.ts#DslOpOptions{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#DslOpWithHistoryIdsResult{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## get
|
## get
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'invalidNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect' | 'alwaysMultiSelect'} name`
|
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect' | 'alwaysMultiSelect'} name`
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{any} value`
|
- `{any} value`
|
||||||
@ -71,8 +69,6 @@ DSL 操作方法(`add` / `remove` / `update` 等)默认返回操作结果(
|
|||||||
|
|
||||||
'modifiedNodeIds': 当前页面所有改动过的节点id
|
'modifiedNodeIds': 当前页面所有改动过的节点id
|
||||||
|
|
||||||
'invalidNodeIds': 校验失败的节点错误信息(`Map<Id, NodeInvalidInfo>`),供组件树标红提示与保存拦截读取
|
|
||||||
|
|
||||||
'pageLength': 所以页面个数
|
'pageLength': 所以页面个数
|
||||||
|
|
||||||
'pageFragmentLength': 页面片个数
|
'pageFragmentLength': 页面片个数
|
||||||
@ -95,7 +91,7 @@ const node = editorService.get("node");
|
|||||||
|
|
||||||
## set
|
## set
|
||||||
|
|
||||||
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'invalidNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect' | 'alwaysMultiSelect'} name`
|
- `{'root' | 'page' | 'parent' | 'node' | 'highlightNode' | 'nodes' | 'modifiedNodeIds' | 'pageLength' | 'pageFragmentLength' | 'stage' | 'stageLoading' | 'disabledMultiSelect' | 'alwaysMultiSelect'} name`
|
||||||
- `{any} value`
|
- `{any} value`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
@ -512,10 +508,6 @@ editorService.highlight("text_123");
|
|||||||
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
- `{boolean}` doNotPushHistory 是否不写入历史记录(默认 false)
|
||||||
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
- `{string}` historyDescription 见[历史记录相关 options](#历史记录相关-options)
|
||||||
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
- `{HistoryOpSource}` historySource 见[历史记录相关 options](#历史记录相关-options)
|
||||||
- `{Object}` invalidInfo 启用 [enablePropsFormValidate](./props.md#enablepropsformvalidate) 时,属性面板提交携带的校验错误信息,在写入历史记录之前落库,使历史快照与本次变更对齐
|
|
||||||
- `{Id}` id 节点 id
|
|
||||||
- `{'props' | 'style'}` source 错误来源:属性表单 / 样式表单
|
|
||||||
- `{string}` error 错误文案(可为含 `<br>` 的 HTML);为空时表示清除该来源的错误记录
|
|
||||||
|
|
||||||
::: details 查看 ChangeRecord 类型定义
|
::: details 查看 ChangeRecord 类型定义
|
||||||
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
<<< @/../packages/form-schema/src/base.ts#ChangeRecord{ts}
|
||||||
@ -746,26 +738,23 @@ alignCenter可以支持一次水平居中多个组件,alignCenter是通过调
|
|||||||
- **参数:** 同 [add](#add)
|
- **参数:** 同 [add](#add)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[`DslOpWithHistoryIdsResult<MNode | MNode[]>`](#历史记录-uuid-与-andgethistoryid)>}
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
- `result`:同 [add](#add) 的返回值(新增节点 / 节点数组)
|
|
||||||
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [add](#add) 行为完全一致,并在返回值中额外提供 `historyIds`,见[历史记录 uuid 与 \*AndGetHistoryId](#历史记录-uuid-与-andgethistoryid)
|
与 [add](#add) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`,见[历史记录 uuid 与 \*AndGetHistoryId](#历史记录-uuid-与-andgethistoryid)
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { editorService } from "@tmagic/editor";
|
import { editorService } from "@tmagic/editor";
|
||||||
|
|
||||||
const { result, historyIds } = await editorService.addAndGetHistoryId(
|
const historyId = await editorService.addAndGetHistoryId(
|
||||||
{ type: "text", text: "hello" },
|
{ type: "text", text: "hello" },
|
||||||
parent,
|
parent,
|
||||||
{ historySource: "api" },
|
{ historySource: "api" },
|
||||||
);
|
);
|
||||||
console.log(result); // 新增节点
|
console.log(historyId); // 本次新增对应的历史记录 uuid,或 null
|
||||||
console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或 []
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## removeAndGetHistoryId
|
## removeAndGetHistoryId
|
||||||
@ -773,74 +762,67 @@ console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或
|
|||||||
- **参数:** 同 [remove](#remove)
|
- **参数:** 同 [remove](#remove)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[`DslOpWithHistoryIdsResult<void>`](#历史记录-uuid-与-andgethistoryid)>}
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [remove](#remove) 行为完全一致,并在返回值中额外提供 `historyIds`
|
与 [remove](#remove) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## updateAndGetHistoryId
|
## updateAndGetHistoryId
|
||||||
|
|
||||||
- **参数:** 同 [update](#update)
|
- **参数:** 同 [update](#update)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[`DslOpWithHistoryIdsResult<MNode | MNode[]>`](#历史记录-uuid-与-andgethistoryid)>}
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
- `result`:同 [update](#update) 的返回值(更新后的节点 / 节点数组)
|
|
||||||
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [update](#update) 行为完全一致,并在返回值中额外提供 `historyIds`
|
与 [update](#update) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## moveLayerAndGetHistoryId
|
## moveLayerAndGetHistoryId
|
||||||
|
|
||||||
- **参数:** 同 [moveLayer](#movelayer)
|
- **参数:** 同 [moveLayer](#movelayer)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[`DslOpWithHistoryIdsResult<void>`](#历史记录-uuid-与-andgethistoryid)>}
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [moveLayer](#movelayer) 行为完全一致,并在返回值中额外提供 `historyIds`
|
与 [moveLayer](#movelayer) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## moveToContainerAndGetHistoryId
|
## moveToContainerAndGetHistoryId
|
||||||
|
|
||||||
- **参数:** 同 [moveToContainer](#movetocontainer)
|
- **参数:** 同 [moveToContainer](#movetocontainer)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[`DslOpWithHistoryIdsResult<MNode | MNode[]>`](#历史记录-uuid-与-andgethistoryid)>}
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
- `result`:同 [moveToContainer](#movetocontainer) 的返回值(移动后的节点 / 节点数组)
|
|
||||||
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [moveToContainer](#movetocontainer) 行为完全一致,并在返回值中额外提供 `historyIds`
|
与 [moveToContainer](#movetocontainer) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## dragToAndGetHistoryId
|
## dragToAndGetHistoryId
|
||||||
|
|
||||||
- **参数:** 同 [dragTo](#dragto)
|
- **参数:** 同 [dragTo](#dragto)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<[`DslOpWithHistoryIdsResult<void>`](#历史记录-uuid-与-andgethistoryid)>}
|
- {Promise<`string` | null>} 本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid);未写入历史时返回 `null`
|
||||||
- `historyIds`:本次写入历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid) 列表;未写入历史时为 `[]`
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
与 [dragTo](#dragto) 行为完全一致,并在返回值中额外提供 `historyIds`
|
与 [dragTo](#dragto) 行为完全一致,仅把返回值换成本次写入历史记录的 `uuid`
|
||||||
|
|
||||||
## revertPageStepById
|
## revertPageStepById
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{string[]}` uuids 目标历史记录的 uuid 列表(通常由 `*AndGetHistoryId` 方法返回的 `historyIds`)
|
- `{string}` uuid 目标历史记录的 [uuid](#历史记录-uuid-与-andgethistoryid)(通常由 `*AndGetHistoryId` 方法返回)
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- {Promise<(`StepValue` | null)[]>} 与入参同序的回滚结果列表,某项失败时为 `null`
|
- {Promise<`StepValue` | null>} 反向应用后产生的新 step;找不到对应 uuid / 该步未应用 / 反向失败时返回 `null`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
通过历史记录 uuid「回滚」当前页面的历史步骤(类 git revert 语义):不移动游标、不丢弃任何步骤,而是把目标 step 的修改**反向应用为一条全新的步骤**压入栈顶。**按数组顺序依次回滚**。
|
通过历史记录 uuid「回滚」当前页面的某条历史步骤(类 git revert 语义):不移动游标、不丢弃任何步骤,而是把目标 step 的修改**反向应用为一条全新的步骤**压入栈顶。语义与按 index 回滚一致,仅入参从 index 改为 uuid,更适合业务侧持有引用后再回滚。
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
`opType: 'update'` 的步骤必须携带 `changeRecords` 才支持回滚(否则只能整节点替换,会冲掉后续无关变更);未应用(已被撤销)的步骤无法回滚。
|
`opType: 'update'` 的步骤必须携带 `changeRecords` 才支持回滚(否则只能整节点替换,会冲掉后续无关变更);未应用(已被撤销)的步骤无法回滚。
|
||||||
@ -852,10 +834,12 @@ console.log(historyIds); // 本次新增对应的历史记录 uuid 列表,或
|
|||||||
import { editorService } from "@tmagic/editor";
|
import { editorService } from "@tmagic/editor";
|
||||||
|
|
||||||
// 执行操作时拿到本次历史记录 uuid
|
// 执行操作时拿到本次历史记录 uuid
|
||||||
const { historyIds } = await editorService.addAndGetHistoryId({ type: "text", text: "hello" });
|
const historyId = await editorService.addAndGetHistoryId({ type: "text", text: "hello" });
|
||||||
|
|
||||||
// 之后任意时机按 uuid 回滚(支持单个或整批 historyIds)
|
// 之后任意时机按 uuid 回滚该步骤
|
||||||
await editorService.revertPageStepById(historyIds);
|
if (historyId) {
|
||||||
|
await editorService.revertPageStepById(historyId);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## undo
|
## undo
|
||||||
@ -868,10 +852,6 @@ await editorService.revertPageStepById(historyIds);
|
|||||||
::: details 查看 StepValue 及关联类型定义
|
::: details 查看 StepValue 及关联类型定义
|
||||||
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#BaseStepValue{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#StepExtra{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
@ -893,10 +873,6 @@ await editorService.revertPageStepById(historyIds);
|
|||||||
::: details 查看 StepValue 及关联类型定义
|
::: details 查看 StepValue 及关联类型定义
|
||||||
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#BaseStepValue{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#StepExtra{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
@ -933,89 +909,6 @@ await editorService.revertPageStepById(historyIds);
|
|||||||
|
|
||||||
重置当前记录的修改过的节点id记录,通常用于保存之后
|
重置当前记录的修改过的节点id记录,通常用于保存之后
|
||||||
|
|
||||||
## setInvalidNode
|
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{Id}` id 节点 id
|
|
||||||
- `{'props' | 'style'}` source 错误来源:属性表单(`props`)/ 样式表单(`style`)
|
|
||||||
- `{string}` message 错误文案(可能为包含 `<br>` 的 HTML)
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
记录(或覆盖)某个节点在指定来源上的校验错误信息,写入 `invalidNodeIds` 状态并发出 [invalid-node-change](#invalid-node-change) 事件。
|
|
||||||
|
|
||||||
属性表单与样式表单是两个独立的 `FormPanel` 且均指向同一节点,故以来源为键分别保存,避免某个面板校验通过时误清另一个面板记录的错误。节点视为存在错误当且仅当任一来源存在非空文本。
|
|
||||||
|
|
||||||
- **示例:**
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { editorService } from "@tmagic/editor";
|
|
||||||
|
|
||||||
// 标记 text_123 的属性表单校验错误
|
|
||||||
editorService.setInvalidNode("text_123", "props", "标题不能为空");
|
|
||||||
```
|
|
||||||
|
|
||||||
## deleteInvalidNode
|
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{Id}` id 节点 id
|
|
||||||
- `{'props' | 'style'}` source 可选;指定来源则仅删除该来源错误,不传则删除该节点全部来源的错误
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
删除节点的校验错误记录。仅当该来源被清空且另一来源也无错误时,节点整体错误记录才会被移除;随后发出 [invalid-node-change](#invalid-node-change) 事件。
|
|
||||||
|
|
||||||
## getInvalidNodeIds
|
|
||||||
|
|
||||||
- **返回:**
|
|
||||||
- {`Map<Id, NodeInvalidInfo>`} 当前存在校验错误的节点错误 Map(key 为节点 id)
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
获取当前存在校验错误的节点错误 Map,供组件树标红提示与保存拦截读取。
|
|
||||||
|
|
||||||
::: details 查看 NodeInvalidInfo 及关联类型定义
|
|
||||||
<<< @/../packages/editor/src/type.ts#NodeInvalidInfo{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#NodeInvalidSource{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/schema/src/index.ts#Id{ts}
|
|
||||||
:::
|
|
||||||
|
|
||||||
- **示例:**
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { editorService } from "@tmagic/editor";
|
|
||||||
|
|
||||||
// 保存前检查是否存在校验错误的组件
|
|
||||||
const invalidNodeIds = editorService.getInvalidNodeIds();
|
|
||||||
if (invalidNodeIds.size > 0) {
|
|
||||||
const names = [...invalidNodeIds.keys()].map((id) => {
|
|
||||||
const node = editorService.getNodeById(id);
|
|
||||||
return node?.name ? `${node.name}(${id})` : `${id}`;
|
|
||||||
});
|
|
||||||
console.warn(`以下组件存在配置校验错误,请修复后再保存:${names.join("、")}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## getInvalidNodeInfo
|
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{Id}` id 节点 id
|
|
||||||
|
|
||||||
- **返回:**
|
|
||||||
- {`NodeInvalidInfo` | undefined} 指定节点的校验错误信息(含 `props` / `style` 来源的错误文案)
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
获取指定节点的校验错误信息。
|
|
||||||
|
|
||||||
## resetInvalidNodeId
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
清空全部校验错误记录(即 `invalidNodeIds` 状态),随后发出 [invalid-node-change](#invalid-node-change) 事件。
|
|
||||||
|
|
||||||
## resetState
|
## resetState
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|||||||
@ -1,18 +1,24 @@
|
|||||||
# historyService事件
|
# historyService事件
|
||||||
|
|
||||||
|
## page-change
|
||||||
|
|
||||||
|
- **详情:** 页面切换
|
||||||
|
|
||||||
|
- **事件回调函数:** `(undoRedo: UndoRedo) => void`
|
||||||
|
|
||||||
|
::: details 查看 UndoRedo 类定义
|
||||||
|
<<< @/../packages/editor/src/utils/undo-redo.ts#UndoRedo{ts}
|
||||||
|
:::
|
||||||
|
|
||||||
## change
|
## change
|
||||||
|
|
||||||
- **详情:** 页面历史记录发生变化(`page` 类型 `push` / `undo` / `redo` 成功时触发;与 `code-block-history-change` / `data-source-history-change` 同构)
|
- **详情:** 历史记录发生变化
|
||||||
|
|
||||||
- **事件回调函数:** `(pageId: Id, step: StepValue) => void`
|
- **事件回调函数:** `(state: StepValue | null) => void`
|
||||||
|
|
||||||
::: details 查看 StepValue 及关联类型定义
|
::: details 查看 StepValue 及关联类型定义
|
||||||
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#BaseStepValue{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#StepExtra{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
@ -23,36 +29,18 @@
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
回调签名已与其它历史类型统一为 `(id, step)`。当游标处于历史栈边界(已无法继续撤销 / 重做)时 `undo` / `redo` 返回 `null`,此时不会触发该事件。
|
当游标处于历史栈边界(已经无法继续撤销或重做)时,`UndoRedo.undo()` / `redo()` 返回 `null`,对应 `change` 回调收到的 `state` 为 `null`
|
||||||
:::
|
|
||||||
|
|
||||||
## marker-change
|
|
||||||
|
|
||||||
- **详情:** 通过 [`setMarker`](./historyServiceMethods.md#setmarker) 为某个历史栈种入 `initial` 基线时触发(适用于所有类型)
|
|
||||||
|
|
||||||
- **事件回调函数:** `(id: Id, marker: StepValue, stepType: HistoryStepType) => void`
|
|
||||||
|
|
||||||
## clear
|
|
||||||
|
|
||||||
- **详情:** 调用 [`clear`](./historyServiceMethods.md#clear) 清空历史栈时触发(适用于所有类型)
|
|
||||||
|
|
||||||
- **事件回调函数:** `(id: Id | undefined, stepType: HistoryStepType) => void`
|
|
||||||
|
|
||||||
:::tip
|
|
||||||
`id` 缺省(清空 `stepType` 下全部栈)时回调的 `id` 为 `undefined`。
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## code-block-history-change
|
## code-block-history-change
|
||||||
|
|
||||||
- **详情:** 代码块历史记录发生变化(`push('codeBlock', step, codeBlockId)` / `undo('codeBlock', id)` / `redo('codeBlock', id)` 成功时触发)
|
- **详情:** 代码块历史记录发生变化(`pushCodeBlock` / `undoCodeBlock` / `redoCodeBlock` 成功时触发)
|
||||||
|
|
||||||
- **事件回调函数:** `(codeBlockId: Id, step: CodeBlockStepValue) => void`
|
- **事件回调函数:** `(codeBlockId: Id, step: CodeBlockStepValue) => void`
|
||||||
|
|
||||||
::: details 查看 CodeBlockStepValue 及关联类型定义
|
::: details 查看 CodeBlockStepValue 及关联类型定义
|
||||||
<<< @/../packages/editor/src/type.ts#CodeBlockStepValue{ts}
|
<<< @/../packages/editor/src/type.ts#CodeBlockStepValue{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#BaseStepValue{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
<<< @/../packages/schema/src/index.ts#CodeBlockContent{ts}
|
<<< @/../packages/schema/src/index.ts#CodeBlockContent{ts}
|
||||||
@ -61,42 +49,40 @@
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
- 新增触发的 step 其 diff 项 `oldSchema` 为 `null`
|
- 新增触发的 step 中 `oldContent` 为 `null`
|
||||||
- 删除触发的 step 其 diff 项 `newSchema` 为 `null`
|
- 删除触发的 step 中 `newContent` 为 `null`
|
||||||
- `undo` / `redo` 返回 `null`(边界状态)时不会触发该事件
|
- `undo` / `redo` 返回 `null`(边界状态)时不会触发该事件
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## data-source-history-change
|
## data-source-history-change
|
||||||
|
|
||||||
- **详情:** 数据源历史记录发生变化(`push('dataSource', step, dataSourceId)` / `undo('dataSource', id)` / `redo('dataSource', id)` 成功时触发)
|
- **详情:** 数据源历史记录发生变化(`pushDataSource` / `undoDataSource` / `redoDataSource` 成功时触发)
|
||||||
|
|
||||||
- **事件回调函数:** `(dataSourceId: Id, step: DataSourceStepValue) => void`
|
- **事件回调函数:** `(dataSourceId: Id, step: DataSourceStepValue) => void`
|
||||||
|
|
||||||
::: details 查看 DataSourceStepValue 及关联类型定义
|
::: details 查看 DataSourceStepValue 及关联类型定义
|
||||||
<<< @/../packages/editor/src/type.ts#DataSourceStepValue{ts}
|
<<< @/../packages/editor/src/type.ts#DataSourceStepValue{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#BaseStepValue{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
|
|
||||||
<<< @/../packages/schema/src/index.ts#Id{ts}
|
<<< @/../packages/schema/src/index.ts#Id{ts}
|
||||||
:::
|
:::
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
- 新增触发的 step 其 diff 项 `oldSchema` 为 `null`
|
- 新增触发的 step 中 `oldSchema` 为 `null`
|
||||||
- 删除触发的 step 其 diff 项 `newSchema` 为 `null`
|
- 删除触发的 step 中 `newSchema` 为 `null`
|
||||||
- `undo` / `redo` 返回 `null`(边界状态)时不会触发该事件
|
- `undo` / `redo` 返回 `null`(边界状态)时不会触发该事件
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## mark-saved
|
## mark-saved
|
||||||
|
|
||||||
- **详情:** 调用 [`markSaved`](./historyServiceMethods.md#marksaved) 标记「已保存」记录时触发
|
- **详情:** 调用 `markSaved` / `markPageSaved` / `markCodeBlockSaved` / `markDataSourceSaved` 标记「已保存」记录时触发
|
||||||
|
|
||||||
- **事件回调函数:** `(payload: { kind: 'all' | HistoryStepType; id?: Id }) => void`
|
- **事件回调函数:** `(payload: { kind: 'all' | 'page' | 'code-block' | 'data-source'; id?: Id }) => void`
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
- `markSaved(stepType)`(缺省 id)触发时 `kind` 为 `all`,无 `id`(此时 `stepType` 不生效)
|
- `markSaved` 触发时 `kind` 为 `all`,无 `id`
|
||||||
- `markSaved(stepType, id)` 触发时 `kind` 为对应的 `stepType`(`page` / `codeBlock` / `dataSource` / 扩展),`id` 为目标栈 id
|
- 细粒度方法触发时 `kind` 对应类别,`id` 为目标页面 / 代码块 / 数据源 id
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## save-to-indexed-db
|
## save-to-indexed-db
|
||||||
|
|||||||
@ -4,71 +4,43 @@
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
重置全部历史记录:清空 `state.steps` 下的页面 / 代码块 / 数据源 / 扩展类型全部栈(保留已注册的扩展类型键)。
|
重置全部历史记录(包括页面节点栈、代码块栈、数据源栈),并重置当前页面 id / canRedo / canUndo
|
||||||
|
|
||||||
|
## resetPage
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
重置当前页面的历史记录状态(清空当前页面id,重置 canRedo/canUndo)
|
||||||
|
|
||||||
## resetState
|
## resetState
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
同 [`reset`](#reset),清空 `state.steps` 下全部栈。
|
重置历史记录全部内部状态(清空 pageId、pageSteps、canRedo、canUndo、codeBlockState、dataSourceState)
|
||||||
|
|
||||||
::: tip
|
## changePage
|
||||||
历史服务不再维护「当前活动页」状态(已移除 `state.pageId` / `state.canUndo` / `state.canRedo`)。
|
|
||||||
活动页由 `editorService` 维护,撤销 / 重做 / 读取页面历史时请显式传入 pageId。
|
- **参数:**
|
||||||
是否可撤销 / 重做请改用 [`canUndo`](#canundo) / [`canRedo`](#canredo)。
|
- `{MPage | MPageFragment} page`
|
||||||
|
|
||||||
|
::: details 查看 MPage / MPageFragment 类型定义
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPage{ts}
|
||||||
|
|
||||||
|
<<< @/../packages/schema/src/index.ts#MPageFragment{ts}
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## registerStepType
|
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{string} stepType` 自定义历史类型标识(勿与内置 `page` / `codeBlock` / `dataSource` 重名)
|
|
||||||
- `{Object} options` 可选
|
|
||||||
- `{string} event` push / undo / redo 后派发的事件名;缺省为 `${stepType}-history-change`
|
|
||||||
- `{string} name` 历史面板中的展示名称(tab / 分组标题等);缺省回退到 stepType 本身
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
注册一个扩展历史类型,使其可与内置 `page` / `codeBlock` / `dataSource` 一样走统一的
|
按页面切换历史堆栈
|
||||||
[`push`](#push) / [`undo`](#undo) / [`redo`](#redo)(按 id 分栈、独立 undo/redo)。
|
|
||||||
注册后该类型的栈存放在 `historyService.state.steps[stepType]`,展示名称存放在 `historyService.state.stepNames[stepType]`。
|
|
||||||
|
|
||||||
## getStepName
|
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
|
||||||
|
|
||||||
- **返回:**
|
|
||||||
- `{string}` 该类型的展示名称(用于历史面板 tab / 分组标题等);未登记时回退到 stepType 本身
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
读取指定历史类型的展示名称。内置 `page` / `codeBlock` / `dataSource` 默认分别为「页面 / 代码块 / 数据源」。
|
|
||||||
|
|
||||||
## setStepName
|
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
|
||||||
- `{string} name` 展示名称
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
设置指定历史类型的展示名称(写入 `historyService.state.stepNames`,历史面板会响应式刷新)。
|
|
||||||
内置 `page` / `codeBlock` / `dataSource` 也可在此覆盖默认中文名。
|
|
||||||
|
|
||||||
## push
|
## push
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{HistoryStepType} stepType` 历史类型,内置 `'page'` / `'codeBlock'` / `'dataSource'`,并支持通过 [`registerStepType`](#registersteptype) 扩展
|
- `{StepValue} state`
|
||||||
- `{StepValue | BaseStepValue} step` 已构造好的历史记录(缺省自动补全 `uuid` / `timestamp`)
|
|
||||||
- `{Id} id` 必填;目标栈 id(`page` 为 pageId,其余类型为对应资源 id)
|
|
||||||
|
|
||||||
::: details 查看 StepValue 及关联类型定义
|
::: details 查看 StepValue 及关联类型定义
|
||||||
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
<<< @/../packages/editor/src/type.ts#StepValue{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#BaseStepValue{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#StepExtra{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpType{ts}
|
||||||
|
|
||||||
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
<<< @/../packages/editor/src/type.ts#HistoryOpSource{ts}
|
||||||
@ -81,137 +53,278 @@
|
|||||||
:::
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{StepValue | BaseStepValue | null}` 入栈失败(未传 / 无效 id)时返回 `null`
|
- `{StepValue | null}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
添加一条历史记录。统一入口,所有类型(`page` / `codeBlock` / `dataSource` / 扩展)行为完全一致:按 `stepType` 选择目标栈类型、按 `id`(必填)选择具体栈,按需建栈后入栈,并派发对应的历史变更事件(`page` 为 `change`,其余如 `code-block-history-change` / `data-source-history-change`),回调签名统一为 `(id, step)`。
|
添加一条历史记录
|
||||||
|
|
||||||
跨页 / 跨资源操作(如把节点搬到其它页)必须显式传入目标 id。`codeBlock` / `dataSource` 的 step 通常由 `createStackStep` 等工具按 `oldValue` / `newValue` 构造后传入。
|
|
||||||
|
|
||||||
::: tip
|
::: tip
|
||||||
`opType: 'update'` 的每个 diff 项上可携带 `changeRecords`,用于撤销 / 重做时仅按
|
`opType: 'update'` 的每个 `updatedItems[i]` 上可携带 `changeRecords`,用于撤销 / 重做时仅按
|
||||||
`propPath` 局部更新对应字段,避免整节点替换冲掉同节点上的其它无关变更;不带
|
`propPath` 局部更新对应字段,避免整节点替换冲掉同节点上的其它无关变更;不带
|
||||||
`changeRecords` 时退化为整节点替换(如 `sort` / `moveLayer` / 拖动等纯快照场景)。
|
`changeRecords` 时退化为整节点替换(如 `sort` / `moveLayer` / 拖动等纯快照场景)。
|
||||||
|
|
||||||
`step` 上的 `historyDescription` / `source` 仅用于历史面板展示与埋点,不影响 undo/redo 行为。
|
`StepValue` 上的 `historyDescription` / `source` 仅用于历史面板展示与埋点,不影响 undo/redo 行为。
|
||||||
|
|
||||||
入栈时会为每条记录自动生成唯一标识 `uuid`(调用方未指定时),可用于精确引用 / 定位某一条历史记录。
|
入栈时会为每条记录自动生成唯一标识 `uuid`(调用方未指定时),可用于精确引用 / 定位某一条历史记录。
|
||||||
若需要在执行 DSL 操作后拿到本次写入记录的 `uuid`,可使用 editorService / dataSourceService /
|
若需要在执行 DSL 操作后拿到本次写入记录的 `uuid`,可使用 editorService / dataSourceService /
|
||||||
codeBlockService 提供的 `*AndGetHistoryId` 方法,参见
|
codeBlockService 提供的 `*AndGetHistoryId` 方法,参见
|
||||||
[editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
[editorService 历史记录 uuid 与 \*AndGetHistoryId](./editorServiceMethods.md#历史记录-uuid-与-andgethistoryid)。
|
||||||
|
`pushCodeBlock` / `pushDataSource` 同样会自动写入 `uuid`。
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## undo
|
## undo
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
|
||||||
- `{Id} id` 必填;目标栈 id(`page` 为 pageId,其余类型为对应资源 id)
|
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{StepValue | BaseStepValue | null}`
|
- `{StepValue | null}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
撤销指定历史栈的最近一次变更。所有类型行为一致:按 `stepType` + `id` 定位栈,不会越过 index 0 的 initial 基线(所有类型同等适用,见 [`setMarker`](#setmarker)),仅在确有可撤销 step 时派发对应的历史变更事件(`page` 为 `change`,回调签名 `(id, step)`)。
|
撤销当前操作。`opType: 'update'` 时,若 `updatedItems[i].changeRecords` 存在,会按
|
||||||
|
`propPath` 从 `oldNode` 取值做局部回滚;否则用 `oldNode` 整节点替换。
|
||||||
`page` 类型 `opType: 'update'` 时,若 diff 项的 `changeRecords` 存在,会按 `propPath` 从 `oldSchema` 取值做局部回滚;否则用 `oldSchema` 整节点替换。`codeBlock` / `dataSource` 拿到 step 后由调用方写回对应 service(本方法不会自动回放)。
|
|
||||||
|
|
||||||
## redo
|
## redo
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
|
||||||
- `{Id} id` 必填;目标栈 id(`page` 为 pageId,其余类型为对应资源 id)
|
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{StepValue | BaseStepValue | null}`
|
- `{StepValue | null}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
恢复指定历史栈到下一步,语义与 [`undo`](#undo) 对称。`page` 类型 `opType: 'update'` 时,若 diff 项的 `changeRecords` 存在,会按 `propPath` 从 `newSchema` 取值做局部重做;否则用 `newSchema` 整节点替换。
|
恢复到下一步。`opType: 'update'` 时,若 `updatedItems[i].changeRecords` 存在,会按
|
||||||
|
`propPath` 从 `newNode` 取值做局部重做;否则用 `newNode` 整节点替换。
|
||||||
|
|
||||||
## canUndo
|
## pushCodeBlock
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
- `{Id} codeBlockId` 代码块 id
|
||||||
- `{Id} id` 可选;目标栈 id;缺省 / 无效时返回 `false`
|
- `{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}`
|
- `{boolean}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
指定历史栈当前是否可撤销(游标高于 index 0 的 initial 基线底线)。适用于所有类型(`page` / `codeBlock` / `dataSource` / 扩展)。
|
指定代码块当前是否可撤销。栈不存在时返回 `false`。
|
||||||
|
|
||||||
## canRedo
|
## canRedoCodeBlock
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
- `{Id} codeBlockId`
|
||||||
- `{Id} id` 可选;目标栈 id;缺省 / 无效时返回 `false`
|
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{boolean}`
|
- `{boolean}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
指定历史栈当前是否可重做。适用于所有类型(`page` / `codeBlock` / `dataSource` / 扩展)。
|
指定代码块当前是否可重做。栈不存在时返回 `false`。
|
||||||
|
|
||||||
## setMarker
|
## pushDataSource
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
- `{Id} dataSourceId` 数据源 id
|
||||||
- `{Id} id` 目标栈 id(`page` 为 pageId,其余类型为对应资源 id)
|
- `{Object} payload`
|
||||||
- `{Object} options` 可选:`name` / `description` / `source`,用于基线的展示信息
|
- `{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}
|
||||||
|
:::
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{StepValue | null}` 已存在基线时返回原基线;栈非空(无基线)或 id 无效时返回 `null`
|
- `{DataSourceStepValue | null}` 入栈失败(未传 id)时返回 `null`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
为指定历史栈种入一条 `opType: 'initial'` 的「初始基线」记录,作为该栈 index 0 的固定底线:它是真实入栈并随栈持久化的 step,但被钉为撤销 / 回滚的下限,`undo` / `goto` / `revert` 都不会越过它。所有类型(含扩展类型)均可设置基线,仅当目标栈为空时种入。
|
推入一条数据源变更记录。与页面 / 节点完全无关,按 `dataSourceId` 维度独立一份 `UndoRedo` 栈,
|
||||||
|
栈实例存放在 `historyService.state.dataSourceState[dataSourceId]`。
|
||||||
|
|
||||||
## getMarker
|
入栈成功后会触发 `data-source-history-change` 事件。
|
||||||
|
|
||||||
|
::: tip
|
||||||
|
`dataSourceService.add` / `update` / `remove` 内部已经自动调用本方法,业务代码通常无需手动调用。
|
||||||
|
:::
|
||||||
|
|
||||||
|
## undoDataSource
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
- `{Id} dataSourceId`
|
||||||
- `{Id} id` 可选;目标栈 id;缺省 / 无效时返回 `undefined`
|
|
||||||
|
|
||||||
- **返回:**
|
- **返回:**
|
||||||
- `{StepValue | undefined}`
|
- `{DataSourceStepValue | null}`
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
读取指定历史栈的初始基线 step(栈 index 0 且 `opType: 'initial'`),不存在时返回 `undefined`。
|
撤销指定数据源的最近一次变更。成功时会触发 `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
|
## markSaved
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
标记「整份 DSL 已保存」:把页面 / 代码块 / 数据源所有栈当前游标所在的记录都标记为已保存(`saved = true`)。
|
||||||
|
|
||||||
|
同一栈内任意时刻最多保留一条已保存记录(标记前会清除该栈内全部旧标记);某个栈处于「全部已撤销」(cursor 为 0)时不会留下已保存记录,从 IndexedDB 恢复时其游标会回到 0。
|
||||||
|
|
||||||
|
通常在 DSL 整体落库(保存到后端 / 本地)成功后调用,配合 [`restoreFromIndexedDB`](#restorefromindexeddb) 把游标恢复到此处。仅保存了其中一类时请改用更细粒度的 `markPageSaved` / `markCodeBlockSaved` / `markDataSourceSaved`。
|
||||||
|
|
||||||
|
调用后会触发 `mark-saved` 事件(`{ kind: 'all' }`)。
|
||||||
|
|
||||||
|
## markPageSaved
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{HistoryStepType} stepType` 历史类型,内置另有 `'codeBlock'` / `'dataSource'`,并支持扩展(仅在传入 `id` 时生效)
|
- `{Id} pageId` 可选;缺省为当前活动页
|
||||||
- `{Id} id` 可选;目标栈 id。缺省表示标记全部类型、全部栈
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
标记历史记录为「已保存」(把对应栈当前游标所在的记录标记为 `saved = true`)。统一入口:
|
标记指定页面(缺省当前活动页)历史栈的当前记录为已保存,仅影响该页面自己的栈。触发 `mark-saved` 事件(`{ kind: 'page', id }`)。
|
||||||
|
|
||||||
- **缺省 `id`**:标记「整份 DSL 已保存」——把所有类型、所有栈当前游标所在的记录都标记为已保存(此时 `stepType` 不生效),触发 `mark-saved` 事件且 `{ kind: 'all' }`。通常在 DSL 整体落库成功后调用。
|
## markCodeBlockSaved
|
||||||
- **传入 `id`**:仅标记 `stepType` 下该 id 对应的栈,触发 `mark-saved` 事件且 `{ kind: stepType, id }`(如 `{ kind: 'page', id }` / `{ kind: 'codeBlock', id }` / `{ kind: 'dataSource', id }`)。
|
|
||||||
|
|
||||||
同一栈内任意时刻最多保留一条已保存记录(标记前会清除该栈内全部旧标记);某个栈处于「全部已撤销」(cursor 为 0)时不会留下已保存记录,从 IndexedDB 恢复时其游标会回到 0。配合 [`restoreFromIndexedDB`](#restorefromindexeddb) 把游标恢复到此处。
|
|
||||||
|
|
||||||
## clear
|
|
||||||
|
|
||||||
- **参数:**
|
- **参数:**
|
||||||
- `{HistoryStepType} stepType` 历史类型,内置另有 `'codeBlock'` / `'dataSource'`,并支持扩展
|
- `{Id} codeBlockId`
|
||||||
- `{Id} id` 可选;目标栈 id。缺省表示清空 `stepType` 下的全部栈
|
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
清空历史记录栈。统一入口,所有类型(page / codeBlock / dataSource / 扩展)行为一致:
|
标记指定代码块历史栈的当前记录为已保存,仅影响该代码块自己的栈。触发 `mark-saved` 事件(`{ kind: 'code-block', id }`)。
|
||||||
|
|
||||||
- **传入 `id`**:仅清空 `stepType` 下该 id 对应的栈;
|
## markDataSourceSaved
|
||||||
- **缺省 `id`**:清空 `stepType` 下的全部栈。
|
|
||||||
|
|
||||||
仅删除撤销/重做记录,不会改动 DSL / 代码块 / 数据源本身。清空时会**保留各栈原有的 initial 基线**(文案 / 来源,见 [`setMarker`](#setmarker)),无基线时清空成空栈。清空后触发 `clear` 事件(签名 `(id, stepType)`)。
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId`
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
标记指定数据源历史栈的当前记录为已保存,仅影响该数据源自己的栈。触发 `mark-saved` 事件(`{ kind: 'data-source', id }`)。
|
||||||
|
|
||||||
|
## clearPage
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} pageId` 可选;缺省为当前活动页
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
清空指定页面(缺省当前活动页)的历史记录栈。仅删除撤销/重做记录,不会改动当前 DSL;清空后该页将无法再撤销/重做之前的操作。清空当前活动页时会同步刷新 `canUndo` / `canRedo` 并触发 `change` 事件。
|
||||||
|
|
||||||
|
## clearCodeBlock
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} codeBlockId` 可选;缺省清空全部代码块
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
清空代码块历史记录栈:传入 `codeBlockId` 仅清空该代码块,缺省清空全部代码块。仅删除撤销/重做记录,不会改动代码块本身。
|
||||||
|
|
||||||
|
## clearDataSource
|
||||||
|
|
||||||
|
- **参数:**
|
||||||
|
- `{Id} dataSourceId` 可选;缺省清空全部数据源
|
||||||
|
|
||||||
|
- **详情:**
|
||||||
|
|
||||||
|
清空数据源历史记录栈:传入 `dataSourceId` 仅清空该数据源,缺省清空全部数据源。仅删除撤销/重做记录,不会改动数据源本身。
|
||||||
|
|
||||||
## saveToIndexedDB
|
## saveToIndexedDB
|
||||||
|
|
||||||
@ -231,7 +344,7 @@
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
把当前内存中的全部历史栈(页面 / 代码块 / 数据源 / 扩展类型)连同各自游标、容量序列化后写入本地 IndexedDB。
|
把当前内存中的全部历史栈(页面 / 代码块 / 数据源)连同各自游标、容量序列化后写入本地 IndexedDB。
|
||||||
|
|
||||||
- 最终库名为 `${dbName}-${当前 DSL app id}`,按应用隔离;
|
- 最终库名为 `${dbName}-${当前 DSL app id}`,按应用隔离;
|
||||||
- `key` 用于在同一 store 下区分不同记录,缺省为 `default`;
|
- `key` 用于在同一 store 下区分不同记录,缺省为 `default`;
|
||||||
@ -258,32 +371,14 @@
|
|||||||
|
|
||||||
- 每个栈都会按 `listMaxSize` 裁剪并还原游标;
|
- 每个栈都会按 `listMaxSize` 裁剪并还原游标;
|
||||||
- 若某个栈存在已保存记录(见 `markSaved`),其游标会被定位到「最近一条已保存记录」之后,使恢复后的状态与落库的 DSL 对齐;
|
- 若某个栈存在已保存记录(见 `markSaved`),其游标会被定位到「最近一条已保存记录」之后,使恢复后的状态与落库的 DSL 对齐;
|
||||||
- 会整体覆盖当前内存中的历史状态(活动页由 `editorService` 维护,不在此恢复);
|
- 会整体覆盖当前内存中的历史状态,并把活动页恢复为快照中的 `pageId`;
|
||||||
- 找不到对应记录时返回 `null` 且不改动当前状态;不支持 IndexedDB 的环境会 reject。
|
- 找不到对应记录时返回 `null` 且不改动当前状态;不支持 IndexedDB 的环境会 reject。
|
||||||
|
|
||||||
成功后触发 `restore-from-indexed-db` 事件。
|
成功后触发 `restore-from-indexed-db` 与 `change` 事件。
|
||||||
|
|
||||||
## findStepLocationByUuid
|
|
||||||
|
|
||||||
- **参数:**
|
|
||||||
- `{HistoryStepType} stepType` 历史类型
|
|
||||||
- `{string} uuid` 目标历史记录的 uuid
|
|
||||||
- `{Id} id` 可选;目标栈 id
|
|
||||||
|
|
||||||
- **返回:**
|
|
||||||
- `{ { id: Id; index: number } | null }` 找到时返回所属栈 id 与步骤索引;找不到时返回 `null`
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
按历史记录 uuid 在指定历史类型的栈中查找其所属 id 与索引,统一入口。
|
|
||||||
|
|
||||||
- **传入 `id`**:仅在该 id 对应的单个栈中查找(如页面历史按活动页查看,传入 pageId);
|
|
||||||
- **缺省 `id`**:遍历该类型下全部栈查找(代码块 / 数据源等按全部资源分桶的场景)。
|
|
||||||
|
|
||||||
供「按 uuid 回滚」等需要把 uuid 映射回 `(id, index)` 的场景使用,如 [editorService.revertPageStepById](./editorServiceMethods.md#revertpagestepbyid) / [codeBlockService.revertById](./codeBlockServiceMethods.md#revertbyid) / [dataSourceService.revertById](./dataSourceServiceMethods.md#revertbyid) 内部均通过本方法定位步骤。
|
|
||||||
|
|
||||||
## destroy
|
## destroy
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
销毁
|
销毁
|
||||||
|
|
||||||
|
|||||||
@ -1282,45 +1282,6 @@ const guidesOptions = {
|
|||||||
</template>
|
</template>
|
||||||
```
|
```
|
||||||
|
|
||||||
## enablePropsFormValidate
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
是否启用「属性配置表单校验」联动能力。
|
|
||||||
|
|
||||||
开启后(默认 `false` 关闭),当属性面板(属性表单 / 样式表单)校验失败时,编辑器会**仍按当前表单值更新节点**,并把错误信息集中记录到 `editorService`(`invalidNodeIds` 状态),用于:
|
|
||||||
|
|
||||||
- 组件树(图层)中对出错节点标红并显示错误图标,鼠标悬停可查看错误文案;
|
|
||||||
- 保存前拦截:业务可通过 `editorService.getInvalidNodeIds()` 读取错误节点,存在校验错误时阻止保存(参考 [playground 菜单保存拦截](../../guide/advanced/prop-form-validate.md#保存拦截))。
|
|
||||||
|
|
||||||
关闭时保持原行为:属性 / 样式表单校验失败则丢弃本次改动,不写入节点。
|
|
||||||
|
|
||||||
:::tip
|
|
||||||
校验错误以「来源」为维度分别记录 —— 属性表单来源记为 `props`,样式表单来源记为 `style`;两者指向同一节点,互不覆盖。节点只要任一来源存在错误即视为出错。
|
|
||||||
|
|
||||||
错误信息会随 DSL 操作写入历史记录快照,因此「撤销 / 重做」能正确还原校验错误状态(撤销一个「校验失败」的改动后错误消失,重做后错误恢复)。
|
|
||||||
:::
|
|
||||||
|
|
||||||
- **默认值:** `false`
|
|
||||||
|
|
||||||
- **类型:** `boolean`
|
|
||||||
|
|
||||||
- **示例:**
|
|
||||||
|
|
||||||
```html
|
|
||||||
<template>
|
|
||||||
<!-- 开启属性配置表单校验联动能力 -->
|
|
||||||
<m-editor :enable-props-form-validate="true"></m-editor>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
- **相关 API:**
|
|
||||||
|
|
||||||
- `editorService` 错误状态与方法:`get('invalidNodeIds')` / [setInvalidNode](#setinvalidnode) / [deleteInvalidNode](#deleteinvalidnode) / [getInvalidNodeIds](#getinvalidnodeids) / [getInvalidNodeInfo](#getinvalidnodeinfo) / [resetInvalidNodeId](#resetinvalidnodeid)
|
|
||||||
- 错误状态变化事件:[invalid-node-change](#invalid-node-change)
|
|
||||||
- 进阶用法见[属性配置表单校验联动](../../guide/advanced/prop-form-validate.md)
|
|
||||||
|
|
||||||
|
|
||||||
## disabledDataSource
|
## disabledDataSource
|
||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
@ -1404,34 +1365,13 @@ const guidesOptions = {
|
|||||||
|
|
||||||
- **详情:**
|
- **详情:**
|
||||||
|
|
||||||
用于自定义组件树(图层)、画布、数据源、代码块的右键菜单
|
用于自定义组件树与画布的右键菜单
|
||||||
|
|
||||||
该函数会在显示右键菜单前被调用,接收默认菜单项与菜单类型作为参数,返回最终显示的菜单项
|
该函数会在显示右键菜单前被调用,接收默认菜单项作为参数,返回最终显示的菜单项
|
||||||
|
|
||||||
第二个参数 `type` 用于区分菜单来源:`'layer'`(图层)、`'viewer'`(画布)、`'data-source'`(数据源)、`'code-block'`(代码块)
|
|
||||||
|
|
||||||
第三个参数 `getTarget` 仅在**数据源**与**代码块**面板下传入,用于读取当前右键目标(节点 `id` 与原始数据);图层 / 画布不会传入该参数
|
|
||||||
|
|
||||||
- **默认值:** `(menus) => menus`
|
- **默认值:** `(menus) => menus`
|
||||||
|
|
||||||
- **类型:**
|
- **类型:** `(menus: (MenuButton | MenuComponent)[], data: { node?: MNode; page?: MPage; parent?: MContainer; stage?: StageCore }) => (MenuButton | MenuComponent)[]`
|
||||||
|
|
||||||
```ts
|
|
||||||
interface ContentMenuTarget {
|
|
||||||
/** 目标 ID */
|
|
||||||
id: string;
|
|
||||||
/** 原始节点数据(树节点等) */
|
|
||||||
data?: TreeNodeData;
|
|
||||||
}
|
|
||||||
|
|
||||||
type ContentMenuType = 'layer' | 'data-source' | 'viewer' | 'code-block';
|
|
||||||
|
|
||||||
type CustomContentMenuFunction = (
|
|
||||||
menus: (MenuButton | MenuComponent)[],
|
|
||||||
type: ContentMenuType,
|
|
||||||
getTarget?: () => ContentMenuTarget | null,
|
|
||||||
) => (MenuButton | MenuComponent)[];
|
|
||||||
```
|
|
||||||
|
|
||||||
- **示例:**
|
- **示例:**
|
||||||
|
|
||||||
@ -1441,24 +1381,20 @@ type CustomContentMenuFunction = (
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const customContentMenu = (menus, type, getTarget) => {
|
const customContentMenu = (menus, { node }) => {
|
||||||
// 为数据源右键菜单追加自定义菜单
|
// 为特定类型的组件添加自定义菜单
|
||||||
if (type === 'data-source') {
|
if (node?.type === 'container') {
|
||||||
menus.push({
|
menus.push({
|
||||||
type: 'button',
|
type: 'button',
|
||||||
text: '查看详情',
|
text: '清空容器',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
// getTarget 仅在数据源 / 代码块面板下传入
|
// 清空容器的逻辑
|
||||||
const target = getTarget?.();
|
|
||||||
if (target) {
|
|
||||||
console.log(target.id, target.data);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 可以过滤掉某些菜单项
|
// 可以过滤掉某些菜单项
|
||||||
return menus.filter((menu) => menu.text !== '删除');
|
return menus.filter(menu => menu.text !== '删除');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|||||||
@ -150,14 +150,6 @@
|
|||||||
|
|
||||||
- **类型:** `boolean`
|
- **类型:** `boolean`
|
||||||
|
|
||||||
## useFieldTextInError
|
|
||||||
|
|
||||||
- **详情:** 透传给内部 `Form`,控制表单校验失败时错误提示前缀是否使用字段的 `text` 文案。`false` 时直接使用字段 `name`
|
|
||||||
|
|
||||||
- **默认值:** `true`
|
|
||||||
|
|
||||||
- **类型:** `boolean`
|
|
||||||
|
|
||||||
## closeOnClickModal
|
## closeOnClickModal
|
||||||
|
|
||||||
- **详情:** 是否可以通过点击 modal 关闭 Dialog
|
- **详情:** 是否可以通过点击 modal 关闭 Dialog
|
||||||
|
|||||||
@ -40,8 +40,6 @@
|
|||||||
|
|
||||||
- **详情:** 通过 `name` 从表单 `config` 中查找对应表单项的 `text`
|
- **详情:** 通过 `name` 从表单 `config` 中查找对应表单项的 `text`
|
||||||
|
|
||||||
- **相关:** 表单校验失败时是否使用该方法生成错误提示前缀,由 [`useFieldTextInError`](./form-props.md#usefieldtextinerror) prop 控制(默认 `true`)
|
|
||||||
|
|
||||||
## values
|
## values
|
||||||
|
|
||||||
- **类型:** `Ref<FormValue>`
|
- **类型:** `Ref<FormValue>`
|
||||||
|
|||||||
@ -206,19 +206,6 @@
|
|||||||
|
|
||||||
- **类型:** `boolean`
|
- **类型:** `boolean`
|
||||||
|
|
||||||
## useFieldTextInError
|
|
||||||
|
|
||||||
- **详情:**
|
|
||||||
|
|
||||||
表单校验失败时,错误提示前缀是否使用字段的 `text` 文案(通过 `getTextByName` 从 `config` 中查找)。
|
|
||||||
|
|
||||||
- `true`(默认):错误提示形如 `字段文案 -> 错误信息`,找不到 `text` 时回退为字段 `name`;
|
|
||||||
- `false`:跳过查找,直接使用字段 `name` 作为错误提示前缀(形如 `字段name -> 错误信息`)。
|
|
||||||
|
|
||||||
- **默认值:** `true`
|
|
||||||
|
|
||||||
- **类型:** `boolean`
|
|
||||||
|
|
||||||
## extendState
|
## extendState
|
||||||
|
|
||||||
- **详情:** 扩展 formState 的钩子函数,返回的对象会被合并到 formState 上
|
- **详情:** 扩展 formState 的钩子函数,返回的对象会被合并到 formState 上
|
||||||
|
|||||||
@ -37,7 +37,6 @@ function submitForm(options: SubmitFormOptions): Promise<any>;
|
|||||||
| `keyProp` | `string` | `'__key'` | 配置项的唯一 key |
|
| `keyProp` | `string` | `'__key'` | 配置项的唯一 key |
|
||||||
| `popperClass` | `string` | — | 弹层 className |
|
| `popperClass` | `string` | — | 弹层 className |
|
||||||
| `preventSubmitDefault` | `boolean` | — | 是否阻止表单原生 submit |
|
| `preventSubmitDefault` | `boolean` | — | 是否阻止表单原生 submit |
|
||||||
| `useFieldTextInError` | `boolean` | `true` | 校验失败时错误提示前缀是否使用字段的 `text` 文案;`false` 时直接使用字段 `name` |
|
|
||||||
| `extendState` | `(state: FormState) => Record<string, any> \| Promise<Record<string, any>>` | — | 扩展 `formState` |
|
| `extendState` | `(state: FormState) => Record<string, any> \| Promise<Record<string, any>>` | — | 扩展 `formState` |
|
||||||
| `native` | `boolean` | `false` | 透传给 `Form.submitForm`。`true` 时返回内部响应式 `values`,否则返回 `cloneDeep(toRaw(values))` |
|
| `native` | `boolean` | `false` | 透传给 `Form.submitForm`。`true` 时返回内部响应式 `values`,否则返回 `cloneDeep(toRaw(values))` |
|
||||||
| `returnChangeRecords` | `boolean` | `false` | `true` 时 resolve 结果为 `{ values, changeRecords }`,携带表单变更记录;否则仅 resolve `values` |
|
| `returnChangeRecords` | `boolean` | `false` | `true` 时 resolve 结果为 `{ values, changeRecords }`,携带表单变更记录;否则仅 resolve `values` |
|
||||||
@ -68,10 +67,7 @@ try {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
name: 'username',
|
name: 'username',
|
||||||
text: '用户名',
|
text: '用户名',
|
||||||
rules: [
|
rules: [{ required: true, message: '请输入用户名' }],
|
||||||
{ required: true, message: '请输入用户名' },
|
|
||||||
{ typeMatch: true, message: '用户名类型不合法' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
initValues: { username: '' },
|
initValues: { username: '' },
|
||||||
|
|||||||
@ -27,10 +27,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:要求 `string`,有代码块 DSL 时须为已有 codeId。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -24,10 +24,6 @@ CodeSelect 组件支持:
|
|||||||
- 选择数据源方法
|
- 选择数据源方法
|
||||||
- 配置代码块参数
|
- 配置代码块参数
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:做 `{ hookType: 'code', hookData }` 的浅层结构校验,`codeId` / 数据源方法存在性由内部单元格各自校验。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -41,10 +41,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:默认要求 `string`,`parse: true` 时跳过校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -27,10 +27,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为已知算子,能解析字段类型时按类型收窄。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -49,10 +49,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为数据源路径 `string[]`,有 `fieldConfig` 且非路径值时跳过。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -16,10 +16,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为数组并做 `name`/`type` 等浅层结构校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -16,10 +16,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:要求 `string`,`${...}` 绑定须指向已有数据源 / 字段。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -27,10 +27,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为 `[dsId, methodName]`,方法须在该数据源可选方法集中。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -16,10 +16,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为数组并做 `content`/`params` 等浅层结构校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -16,10 +16,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为数组并做 `title`/`enable`/`data` 等浅层结构校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -38,10 +38,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:`value: 'id'` 时须为已有 ds id,否则为含 `isBindDataSource` + `dataSourceId` 的对象。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -27,10 +27,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为数组并做 `cond[].field`/`op` 浅层结构校验,算子合法性 / 字段路径存在性由内部单元格各自校验。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -28,10 +28,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为数组并做兼容新旧格式的浅层结构校验,事件名 / 联动动作是否属于可选项由内部单元格各自校验。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -29,10 +29,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为普通对象,`advanced` 且值为 `function` 时放行。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -16,10 +16,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为 `string` 或 `number`,有节点树时须为已有页面片 id。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -16,10 +16,6 @@
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 值校验
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 后该字段会自动注册内置 `typeMatch` 校验规则:须为 `string` 或 `number`,有节点树时须为已有组件 id。服务数据未就绪时仅做基础形态校验。详见[表单校验 - Editor 字段内置规则](/form-config/rules.md#editor-字段内置规则)。
|
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
|
|
||||||
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
| 参数 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
|||||||
@ -24,26 +24,6 @@ type为'select'
|
|||||||
</template>
|
</template>
|
||||||
</demo-block>
|
</demo-block>
|
||||||
|
|
||||||
## 选项值校验
|
|
||||||
|
|
||||||
可在 `rules` 中开启 `typeMatch`,校验当前值是否落在 `options` 中(`multiple` 时校验数组元素)。详见[表单校验](/form-config/rules.md)。
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
type: 'select',
|
|
||||||
name: 'status',
|
|
||||||
text: '状态',
|
|
||||||
options: [
|
|
||||||
{ text: '启用', value: 1 },
|
|
||||||
{ text: '禁用', value: 0 },
|
|
||||||
],
|
|
||||||
rules: [
|
|
||||||
{ required: true, message: '请选择状态' },
|
|
||||||
{ typeMatch: true, message: '状态值不合法' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 有禁用选项
|
## 有禁用选项
|
||||||
|
|
||||||
<demo-block type="form" :config="[{
|
<demo-block type="form" :config="[{
|
||||||
|
|||||||
@ -82,21 +82,6 @@ Input输入框的type为'text', 是type的默认值,所以可以不配置
|
|||||||
</template>
|
</template>
|
||||||
</demo-block>
|
</demo-block>
|
||||||
|
|
||||||
## 表单校验
|
|
||||||
|
|
||||||
可通过 `rules` 配置校验规则。其中 `typeMatch: true` 会按字段 type(以及 `filter` / `valueFormat` 等)校验值是否合法,详见[表单校验](/form-config/rules.md)。
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
name: 'text',
|
|
||||||
text: '输入框',
|
|
||||||
rules: [
|
|
||||||
{ required: true, message: '请输入' },
|
|
||||||
{ typeMatch: true, message: '值类型不合法' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 去掉首尾空格
|
## 去掉首尾空格
|
||||||
|
|
||||||
<demo-block type="form" :config="[{
|
<demo-block type="form" :config="[{
|
||||||
@ -139,7 +124,6 @@ Input输入框的type为'text', 是type的默认值,所以可以不配置
|
|||||||
| tooltip | 输入时显示内容 | string / `ToolTipConfigType` | — | — |
|
| tooltip | 输入时显示内容 | string / `ToolTipConfigType` | — | — |
|
||||||
| trim | 是否去掉首尾空格 | boolean | — | false |
|
| trim | 是否去掉首尾空格 | boolean | — | false |
|
||||||
| filter | 过滤值 | string / Function | number | - |
|
| filter | 过滤值 | string / Function | number | - |
|
||||||
| rules | 表单验证规则 | `Rule[]` | — | — |
|
|
||||||
| prepend | 前置内容 | string | — | - |
|
| prepend | 前置内容 | string | — | - |
|
||||||
| append | 后置内容 | string / Object | — | - |
|
| append | 后置内容 | string / Object | — | - |
|
||||||
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
| onChange | 值变化时触发的函数 | `OnChangeHandler` | — | - |
|
||||||
@ -156,8 +140,6 @@ Input输入框的type为'text', 是type的默认值,所以可以不配置
|
|||||||
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
<<< @/../packages/form-schema/src/base.ts#FormValue{ts}
|
||||||
|
|
||||||
<<< @/../packages/form-schema/src/base.ts#ToolTipConfigType{ts}
|
<<< @/../packages/form-schema/src/base.ts#ToolTipConfigType{ts}
|
||||||
|
|
||||||
<<< @/../packages/form-schema/src/base.ts#Rule{ts}
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,206 +0,0 @@
|
|||||||
# 表单校验
|
|
||||||
|
|
||||||
`@tmagic/form` 字段可通过 `rules` 配置校验规则,兼容 [async-validator](https://github.com/yiminghe/async-validator)(Element Plus 内部使用)的常见能力,并额外支持按字段 `type` 做值类型 / 选项匹配校验(`typeMatch`)。
|
|
||||||
|
|
||||||
## 基础用法
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
name: 'title',
|
|
||||||
text: '名称',
|
|
||||||
rules: [
|
|
||||||
{ required: true, message: '请输入名称' },
|
|
||||||
{ typeMatch: true, message: '名称类型不合法' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`rules` 类型见下方 [`Rule`](#类型定义)。未配置 `typeMatch` 时,现有表单行为不变。
|
|
||||||
|
|
||||||
## typeMatch
|
|
||||||
|
|
||||||
开启 `typeMatch: true` 后,表单会按字段 `config.type`(及关联配置)校验当前值是否合法。空值(`undefined` / `null` / `''`,多选类空数组 `[]`)直接通过,必填请继续使用 `required`。
|
|
||||||
|
|
||||||
### 内置映射
|
|
||||||
|
|
||||||
| 字段 type | 期望值 |
|
|
||||||
| --- | --- |
|
|
||||||
| `text` / `textarea` / `color-picker` / `html` / 默认无 type | `string`;若 `filter: 'number'` 则为 `number`;`filter` 为自定义函数时跳过内置类型校验 |
|
|
||||||
| `display` / `hidden` | 不校验 |
|
|
||||||
| `number` | `number`(非 NaN) |
|
|
||||||
| `number-range` | 长度为 2 的数字数组 |
|
|
||||||
| `date` / `datetime` / `time` | 按 `valueFormat` 校验;`x` / `timestamp` 期望 `number`,其余按 [Day.js format](https://day.js.org/docs/en/display/format) 严格解析字符串 |
|
|
||||||
| `daterange` / `timerange` | 无 `names` 时为长度为 2 的数组,元素按 `valueFormat` 校验;有 `names` 时跳过 |
|
|
||||||
| `switch` / `checkbox` | 值必须是解析后的 `activeValue` / `inactiveValue` 之一(显式配置优先;未配置且 `filter === 'number'` 时为 `1`/`0`;否则为 `true`/`false`) |
|
|
||||||
| `select` | 单选:值 ∈ options;`multiple`:数组且每项 ∈ options;`allowCreate` / `remote`:只校验基础形态,不做 options 枚举 |
|
|
||||||
| `radio-group` / `radioGroup` | 值 ∈ options |
|
|
||||||
| `checkbox-group` / `checkboxGroup` | 数组且每项 ∈ options |
|
|
||||||
| `cascader` | 有 `valueSeparator` 时可为 `string` 或 `array`;默认 `emitPath` 为路径数组;`emitPath: false` 为叶子值;`multiple` 为数组;静态 options 校验路径/叶子;`remote` 只做形态校验 |
|
|
||||||
| `table` / `group-list` / `groupList` | `array` |
|
|
||||||
| 容器类(`row` / `tab` / `fieldset` / `panel` / `step` / `flex-layout` / `link` / `component` / `dynamic-field` 等) | 不校验 |
|
|
||||||
|
|
||||||
日期类默认 `valueFormat` 与字段组件一致:
|
|
||||||
|
|
||||||
- `date`:`YYYY/MM/DD`
|
|
||||||
- `datetime` / `daterange`:`YYYY/MM/DD HH:mm:ss`
|
|
||||||
- `time` / `timerange`:`HH:mm:ss`
|
|
||||||
|
|
||||||
### 与自定义 validator 共存
|
|
||||||
|
|
||||||
同一条 rule 同时配置了 `typeMatch` 与 `validator` 时,会先做类型匹配校验,通过后再执行自定义 `validator`。
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
name: 'age',
|
|
||||||
type: 'number',
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
typeMatch: true,
|
|
||||||
message: '年龄必须是数字',
|
|
||||||
validator: ({ value, callback }) => {
|
|
||||||
if (value < 0) {
|
|
||||||
callback(new Error('年龄不能小于 0'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 扩展自定义 type 规则
|
|
||||||
|
|
||||||
业务可覆盖内置规则,或为自定义字段 type 注册校验。自定义规则优先于内置规则。
|
|
||||||
|
|
||||||
### 运行时注册
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import {
|
|
||||||
registerTypeMatchRule,
|
|
||||||
registerTypeMatchRules,
|
|
||||||
deleteTypeMatchRule,
|
|
||||||
clearTypeMatchRules,
|
|
||||||
} from '@tmagic/form';
|
|
||||||
|
|
||||||
// 覆盖内置 text
|
|
||||||
registerTypeMatchRule('text', (value, { message }) => {
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return message || '值类型应为字符串';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 扩展业务字段
|
|
||||||
registerTypeMatchRule('vs-code', (value, { message }) => {
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return message || '代码字段应为字符串';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 批量注册
|
|
||||||
registerTypeMatchRules({
|
|
||||||
foo: (value) => (Array.isArray(value) ? undefined : '应为数组'),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 删除 / 清空
|
|
||||||
deleteTypeMatchRule('foo');
|
|
||||||
clearTypeMatchRules();
|
|
||||||
```
|
|
||||||
|
|
||||||
自定义校验器签名:`(value, context) => string | undefined`。返回错误文案表示失败,返回 `undefined` 表示通过。`context` 包含 `fieldType`、`mForm`、`props`、`message`。
|
|
||||||
|
|
||||||
### 安装时注册
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import MagicForm from '@tmagic/form';
|
|
||||||
|
|
||||||
app.use(MagicForm, {
|
|
||||||
typeMatchRules: {
|
|
||||||
'my-field': (value, { message }) => {
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return message || 'my-field 应为字符串';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Editor 字段内置规则
|
|
||||||
|
|
||||||
安装 `@tmagic/editor` 时会自动 `registerTypeMatchRules` 注册编辑器自定义字段规则。服务数据(数据源 / 代码块 / 节点树)未就绪时,只做基础形态校验,不做枚举或存在性失败。
|
|
||||||
|
|
||||||
| 字段 type | 期望值 |
|
|
||||||
| --- | --- |
|
|
||||||
| `key-value` / `style-setter` | 普通对象;`key-value` + `advanced` 且值为 `function` 时放行 |
|
|
||||||
| `cond-op-select` | 已知算子;能解析字段类型时按类型收窄 |
|
|
||||||
| `code-select-col` | `string`;有代码块 DSL 时须为已有 codeId |
|
|
||||||
| `page-fragment-select` / `ui-select` | `string \| number`;有节点树时须为已有页面片 / 组件 id |
|
|
||||||
| `data-source-input` | `string`;`${...}` 绑定须指向已有数据源/字段 |
|
|
||||||
| `data-source-method-select` | `[dsId, methodName]`,方法须在该数据源可选方法集中 |
|
|
||||||
| `data-source-field-select` | 数据源路径 `string[]`;有 `fieldConfig` 且非路径值时跳过 |
|
|
||||||
| `data-source-select` | `value: 'id'` 为已有 ds id;否则为含 `isBindDataSource` + `dataSourceId` 的对象 |
|
|
||||||
| `code-select` | `{ hookType: 'code', hookData }` 的浅层结构校验(`codeId` 存在性 / 数据源方法存在性由内部 `code-select-col`、`data-source-method-select` 单元格各自校验,只标红出错单元格) |
|
|
||||||
| `data-source-fields` / `data-source-mocks` / `data-source-methods` | 数组 + 浅层结构(`name`/`type`、`title`/`enable`/`data`、`content`/`params` 等) |
|
|
||||||
| `event-select` | 数组;兼容新旧格式的浅层结构校验(`name` / 联动组件 `method` 是否 ∈ 可选项由字段内 `eventNameConfig.rules`、`compActionConfig.rules` 单独校验,只标红对应 select) |
|
|
||||||
| `display-conds` | 数组 + `cond[].field`/`op` 浅层结构校验(`op` 是否为已知算子、字段路径是否存在由内部 `cond-op-select`、`field` 单元格各自校验,只标红出错单元格) |
|
|
||||||
|
|
||||||
> 容器类字段(`event-select` / `code-select` / `display-conds`)遵循同一约定:容器级 typeMatch 只做结构校验,「枚举 / 存在性」下沉到内部单元格各自的 typeMatch/rules,避免单个子项非法导致整块表单标红。
|
|
||||||
|
|
||||||
业务仍可用 `registerTypeMatchRule` 覆盖上述任一 type。
|
|
||||||
|
|
||||||
## 示例
|
|
||||||
|
|
||||||
### select 选项匹配
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
type: 'select',
|
|
||||||
options: [
|
|
||||||
{ text: '启用', value: 1 },
|
|
||||||
{ text: '禁用', value: 0 },
|
|
||||||
],
|
|
||||||
rules: [
|
|
||||||
{ required: true, message: '请选择状态' },
|
|
||||||
{ typeMatch: true, message: '状态值不合法' },
|
|
||||||
],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### date 按 valueFormat 校验
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
name: 'birthday',
|
|
||||||
type: 'date',
|
|
||||||
valueFormat: 'YYYY-MM-DD',
|
|
||||||
rules: [{ typeMatch: true, message: '日期格式不正确' }],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### text + filter: number
|
|
||||||
|
|
||||||
```ts
|
|
||||||
{
|
|
||||||
name: 'width',
|
|
||||||
type: 'text',
|
|
||||||
filter: 'number',
|
|
||||||
rules: [{ typeMatch: true, message: '宽度应为数字' }],
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 类型定义
|
|
||||||
|
|
||||||
::: details 查看 Rule 类型定义
|
|
||||||
<<< @/../packages/form-schema/src/base.ts#Rule{ts}
|
|
||||||
:::
|
|
||||||
|
|
||||||
::: details 查看 TypeMatchValidator / TypeMatchValidateContext 类型定义
|
|
||||||
<<< @/../packages/form/src/utils/typeMatch.ts#TypeMatchValidator{ts}
|
|
||||||
|
|
||||||
<<< @/../packages/form/src/utils/typeMatch.ts#TypeMatchValidateContext{ts}
|
|
||||||
:::
|
|
||||||
|
|
||||||
::: details 查看 FormInstallOptions 类型定义
|
|
||||||
<<< @/../packages/form/src/plugin.ts#FormInstallOptions{ts}
|
|
||||||
:::
|
|
||||||
@ -61,50 +61,11 @@ const menu = ref({
|
|||||||
- 数据源:`dataSourceService.revert(id, index)`
|
- 数据源:`dataSourceService.revert(id, index)`
|
||||||
- 代码块:`codeBlockService.revert(id, index)`
|
- 代码块:`codeBlockService.revert(id, index)`
|
||||||
|
|
||||||
如果业务侧在执行操作时已通过 `*AndGetHistoryId` 拿到了该条记录的 uuid(见 [uuid 与 *AndGetHistoryId](/api/editor/editorServiceMethods.md#历史记录-uuid-与-andgethistoryid) 返回的 `historyIds`),也可以直接按 uuid 回滚(无需再关心 index / id,且 uuid 不会随栈内步骤增删而变化):
|
如果业务侧在执行操作时已通过 `*AndGetHistoryId` 拿到了该条记录的 [uuid](/api/editor/editorServiceMethods.md#历史记录-uuid-与-andgethistoryid),也可以直接按 uuid 回滚(无需再关心 index / id,且 uuid 不会随栈内步骤增删而变化):
|
||||||
|
|
||||||
- 页面:`editorService.revertPageStepById(uuids)`
|
- 页面:`editorService.revertPageStepById(uuid)`
|
||||||
- 数据源:`dataSourceService.revertById(uuids)`
|
- 数据源:`dataSourceService.revertById(uuid)`
|
||||||
- 代码块:`codeBlockService.revertById(uuids)`
|
- 代码块:`codeBlockService.revertById(uuid)`
|
||||||
|
|
||||||
#### 复用面板的「交互式回滚 / 查看差异」流程
|
|
||||||
|
|
||||||
上面的 `revert*` 方法是**静默**的:它们直接执行反向应用,不做任何校验与二次确认。如果业务方想在自己的入口(自定义按钮、右键菜单等)里复用历史面板那一套**完整交互流程**——即「目标数据已删除的前置校验 + 失败提示」「可差异步骤弹差异确认弹窗 / 其余步骤弹普通二次确认框」「用户确认后才回滚」,以及「查看差异」弹窗——可以直接 import `useHistoryRevert`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import { useHistoryRevert } from '@tmagic/editor';
|
|
||||||
|
|
||||||
// editorRef 为 <m-editor> 组件实例,其 expose 出的即各 service 集合(Services)
|
|
||||||
const {
|
|
||||||
onPageRevert,
|
|
||||||
onDataSourceRevert,
|
|
||||||
onCodeBlockRevert,
|
|
||||||
onPageDiff,
|
|
||||||
onDataSourceDiff,
|
|
||||||
onCodeBlockDiff,
|
|
||||||
} = useHistoryRevert({}, editorRef.value);
|
|
||||||
|
|
||||||
// 回滚:可差异步骤弹出差异确认弹窗、其余步骤弹普通二次确认框;用户点「确定」后回滚第 index 步,
|
|
||||||
// 命中前置校验或用户取消时不执行,返回 null
|
|
||||||
await onPageRevert(index);
|
|
||||||
await onDataSourceRevert(id, index);
|
|
||||||
await onCodeBlockRevert(id, index);
|
|
||||||
|
|
||||||
// 查看差异:可差异步骤弹出只读差异弹窗展示前后值差异,无可对比内容时不弹窗
|
|
||||||
onPageDiff(index);
|
|
||||||
onDataSourceDiff(id, index);
|
|
||||||
onCodeBlockDiff(id, index);
|
|
||||||
```
|
|
||||||
|
|
||||||
回滚确认弹窗与查看差异弹窗均由 `useHistoryRevert` 内部**按需动态挂载** `HistoryDiffDialog` 实现,业务方无需自行挂载任何弹窗组件。第二个参数 `options` 可选:
|
|
||||||
|
|
||||||
| 字段 | 必填 | 说明 |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| `appContext` | 否 | 父级应用上下文,用于让动态挂载的差异确认弹窗继承全局组件 / 指令 / provide / 插件(Element Plus、`@tmagic/form` 字段组件等)。在组件 `setup` 中调用时会自动取当前组件的 `appContext`,无需手动传;仅当在组件 setup 之外调用时才需显式传入(如 `editorApp._context`)。 |
|
|
||||||
| `extendState` | 否 | 透传给差异确认弹窗的 `extendState`(同 Editor 的 [`extendFormState`](#自定义对比判断)),使对比表单中依赖业务上下文的 `display` / `disabled` 等 `filterFunction` 正常工作。 |
|
|
||||||
| `dialogWidth` | 否 | 内置页面 / 数据源 / 代码块的差异 / 回滚确认弹窗默认宽度(透传给 `TMagicDialog` 的 `width`),如 `'1200px'` / `'80%'`。缺省时使用弹窗内置默认宽度(`900px`)。业务自有历史可在 `viewDiff` / `confirmAndRevert` 调用时通过各自入参的 `width` 单独覆盖。 |
|
|
||||||
|
|
||||||
> 若只需要无确认、无校验的静默回滚,直接用上面的 `editorService.revertPageStep` 等即可,无需 `useHistoryRevert`。
|
|
||||||
|
|
||||||
### 4. 差异对比
|
### 4. 差异对比
|
||||||
|
|
||||||
|
|||||||
@ -1,121 +0,0 @@
|
|||||||
# 属性配置表单校验联动
|
|
||||||
|
|
||||||
编辑器在属性面板(属性表单 / 样式表单)中已支持 [表单校验](../../form-config/rules.md)。默认情况下,表单校验失败时本次改动会被丢弃,不会写入节点。
|
|
||||||
|
|
||||||
「属性配置表单校验联动」能力(`enablePropsFormValidate`)允许在**校验失败时仍按当前表单值更新节点**,并把错误信息集中记录到 `editorService`,从而实现:
|
|
||||||
|
|
||||||
- 组件树(图层)中对出错节点**标红并显示错误图标**,悬停可查看错误文案;
|
|
||||||
- 业务在**保存前拦截**存在校验错误的组件(见 [保存拦截](#保存拦截));
|
|
||||||
- 校验错误随 DSL 操作写入历史快照,**撤销 / 重做能正确还原**错误状态。
|
|
||||||
|
|
||||||
> 该能力默认关闭,需通过 `<m-editor :enable-props-form-validate="true">` 显式开启。
|
|
||||||
|
|
||||||
## 工作原理
|
|
||||||
|
|
||||||
### 错误来源维度
|
|
||||||
|
|
||||||
属性面板分「属性」与「样式」两个独立的 `FormPanel`,它们指向同一节点。为避免某个面板校验通过时误清另一个面板记录的错误,错误信息**以来源(`source`)为键分别保存**:
|
|
||||||
|
|
||||||
- `'props'`:属性表单校验错误
|
|
||||||
- `'style'`:样式表单校验错误
|
|
||||||
|
|
||||||
节点只要**任一来源存在非空错误文案**即视为出错。
|
|
||||||
|
|
||||||
内部类型定义见 `NodeInvalidInfo`:
|
|
||||||
|
|
||||||
```ts
|
|
||||||
interface NodeInvalidInfo {
|
|
||||||
/** 属性表单校验错误文案(可能为包含 <br> 的 HTML) */
|
|
||||||
props?: string;
|
|
||||||
/** 样式表单校验错误文案(可能为包含 <br> 的 HTML) */
|
|
||||||
style?: string;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数据流
|
|
||||||
|
|
||||||
1. 表单校验失败时(开启了 `enablePropsFormValidate`),`FormPanel` 仍以当前表单值提交,并把错误随提交事件一并抛出;
|
|
||||||
2. `PropsPanel` 在调用 `editorService.update()` 时,把 `invalidInfo: { id, source, error }` 一并传入;
|
|
||||||
3. `editorService` 在**写入历史记录之前**落库错误标记(`applyInvalidInfo`),使历史快照与本次变更对齐;
|
|
||||||
4. 错误被记录到 `invalidNodeIds` 状态,并触发 `invalid-node-change` 事件;
|
|
||||||
5. 组件树节点通过读取 `invalidNodeIds` 响应式地展示标红与错误图标。
|
|
||||||
|
|
||||||
校验成功时(或源码编辑器保存时)不携带 `invalidInfo`,保持已有错误状态不变(即不会清除错误)——只有对应来源的表单再次校验通过才会清除该来源的错误。
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
```html
|
|
||||||
<template>
|
|
||||||
<m-editor :enable-props-form-validate="true"></m-editor>
|
|
||||||
</template>
|
|
||||||
```
|
|
||||||
|
|
||||||
开启后无需其它配置,属性 / 样式表单校验失败即会自动记录错误并标红组件树节点。
|
|
||||||
|
|
||||||
## 读取与清除错误
|
|
||||||
|
|
||||||
业务侧可通过 `editorService` 读取或清除错误标记:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { editorService } from "@tmagic/editor";
|
|
||||||
|
|
||||||
// 读取全部错误节点(Map<Id, NodeInvalidInfo>)
|
|
||||||
const invalidNodeIds = editorService.getInvalidNodeIds();
|
|
||||||
|
|
||||||
// 读取单个节点的错误
|
|
||||||
const info = editorService.getInvalidNodeInfo("text_123");
|
|
||||||
|
|
||||||
// 手动记录 / 清除错误(一般无需手动调用,属性面板已自动维护)
|
|
||||||
editorService.setInvalidNode("text_123", "props", "标题不能为空");
|
|
||||||
editorService.deleteInvalidNode("text_123", "props"); // 仅清除属性表单错误
|
|
||||||
editorService.resetInvalidNodeId(); // 清空全部错误
|
|
||||||
```
|
|
||||||
|
|
||||||
订阅错误状态变化(非响应式消费方,如自定义工具栏):
|
|
||||||
|
|
||||||
```js
|
|
||||||
editorService.on("invalid-node-change", (invalidNodeIds) => {
|
|
||||||
console.log("校验错误节点数:", invalidNodeIds.size);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
> 响应式读取(如自定义组件树节点内容)请直接读取 `editorService.get('invalidNodeIds')`,其变化会触发 Vue 响应式更新。
|
|
||||||
|
|
||||||
## 保存拦截
|
|
||||||
|
|
||||||
开启校验联动后,业务可读取 `invalidNodeIds`,在存在校验错误时阻止保存并提示:
|
|
||||||
|
|
||||||
```js
|
|
||||||
import { editorService } from "@tmagic/editor";
|
|
||||||
import { tMagicMessage } from "@tmagic/design";
|
|
||||||
|
|
||||||
const checkInvalidNodes = (services) => {
|
|
||||||
const invalidNodeIds = services?.editorService.getInvalidNodeIds?.();
|
|
||||||
if (!invalidNodeIds || invalidNodeIds.size === 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const names = [...invalidNodeIds.keys()].map((id) => {
|
|
||||||
const node = services?.editorService.getNodeById(id);
|
|
||||||
return node?.name ? `${node.name}(${id})` : `${id}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
tMagicMessage.error(`以下组件存在配置校验错误,请修复后再保存:${names.join("、")}`);
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 在菜单保存按钮 / 预览保存的 handler 中:
|
|
||||||
if (!checkInvalidNodes(services)) return;
|
|
||||||
save();
|
|
||||||
```
|
|
||||||
|
|
||||||
playground 已内置该拦截逻辑,参见 `playground/src/pages/composables/use-editor-menu.ts`。
|
|
||||||
|
|
||||||
## 与历史记录的关系
|
|
||||||
|
|
||||||
错误标记会随 DSL 操作(`add` / `update` 等)一并写入历史快照。编辑器内部在「操作前」与「操作后」分别留存错误状态快照:
|
|
||||||
|
|
||||||
- **撤销(undo)**:还原到操作前的错误状态(撤销一个「校验失败」的改动后错误消失);
|
|
||||||
- **重做(redo)**:还原到操作后的错误状态(重做后错误恢复)。
|
|
||||||
|
|
||||||
整体替换 `root`(如加载新 DSL)后会自动清理不存在于新 DSL 中的失效节点错误记录,避免残留误报;删除节点时也会同步清理其子树相关的错误记录。
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
tmagic-editor的表单配置,核心就是使用了 @tmagic/form 来作为渲染器。@tmagic/form 是一个 npm 包,可以安装它,在你想使用的地方单独使用。
|
tmagic-editor的表单配置,核心就是使用了 @tmagic/form 来作为渲染器。@tmagic/form 是一个 npm 包,可以安装它,在你想使用的地方单独使用。
|
||||||
|
|
||||||
@tmagic/form 接受一个表单配置,详细配置可参考[表单 api](../../api/form/form-props.md)。字段校验(含 `typeMatch` 类型匹配)见[表单校验](../../form-config/rules.md)。
|
@tmagic/form 接受一个表单配置,详细配置可参考[表单 api](../../api/form/form-props.md)。
|
||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
@ -45,20 +45,6 @@ app.use(MagicForm);
|
|||||||
app.mount("#app");
|
app.mount("#app");
|
||||||
```
|
```
|
||||||
|
|
||||||
也可在安装时传入自定义 `typeMatch` 规则,详见[表单校验 - 扩展自定义 type 规则](../../form-config/rules.md#扩展自定义-type-规则):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
app.use(MagicForm, {
|
|
||||||
typeMatchRules: {
|
|
||||||
'my-field': (value, { message }) => {
|
|
||||||
if (typeof value !== 'string') {
|
|
||||||
return message || 'my-field 应为字符串';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
以上代码便完成了 @tmagic/form 的引入。需要注意的是,Element Plus 的样式文件需要单独引入。
|
以上代码便完成了 @tmagic/form 的引入。需要注意的是,Element Plus 的样式文件需要单独引入。
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default defineConfig([
|
|||||||
'*/**/public/**/*',
|
'*/**/public/**/*',
|
||||||
'*/**/types/**/*',
|
'*/**/types/**/*',
|
||||||
'*/**/*.config.ts',
|
'*/**/*.config.ts',
|
||||||
'./temp/**/*',
|
'./tepm/**/*',
|
||||||
'vite-env.d.ts',
|
'vite-env.d.ts',
|
||||||
]),
|
]),
|
||||||
...eslintConfig(path.join(path.dirname(fileURLToPath(import.meta.url)), 'tsconfig.json')),
|
...eslintConfig(path.join(path.dirname(fileURLToPath(import.meta.url)), 'tsconfig.json')),
|
||||||
|
|||||||
46
package.json
46
package.json
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.5",
|
||||||
"name": "tmagic",
|
"name": "tmagic",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@ -40,44 +40,44 @@
|
|||||||
"url": "https://github.com/Tencent/tmagic-editor.git"
|
"url": "https://github.com/Tencent/tmagic-editor.git"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^20.5.3",
|
"@commitlint/cli": "^20.1.0",
|
||||||
"@commitlint/config-conventional": "^20.5.3",
|
"@commitlint/config-conventional": "^20.0.0",
|
||||||
"@tmagic/eslint-config": "workspace:*",
|
"@tmagic/eslint-config": "workspace:*",
|
||||||
"@types/node": "24.0.10",
|
"@types/node": "24.0.10",
|
||||||
"@vitejs/plugin-vue": "^6.0.8",
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
"@vitest/coverage-v8": "^4.1.10",
|
"@vitest/coverage-v8": "^4.1.5",
|
||||||
"@vue/compiler-sfc": "catalog:",
|
"@vue/compiler-sfc": "catalog:",
|
||||||
"c8": "^10.1.3",
|
"c8": "^10.1.3",
|
||||||
"commitizen": "^4.3.2",
|
"commitizen": "^4.3.1",
|
||||||
"conventional-changelog-cli": "^5.0.0",
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
"cosmiconfig": "^9.0.2",
|
"cosmiconfig": "^ 9.0.0 ",
|
||||||
"cz-conventional-changelog": "^3.3.0",
|
"cz-conventional-changelog": "^3.3.0",
|
||||||
"element-plus": "^2.14.3",
|
"element-plus": "^2.11.8",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"eslint": "^10.7.0",
|
"eslint": "^10.3.0",
|
||||||
"execa": "^9.6.1",
|
"execa": "^9.6.0",
|
||||||
"happy-dom": "^20.10.6",
|
"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.4.0",
|
"jsdom": "^27.2.0",
|
||||||
"lint-staged": "^16.4.0",
|
"lint-staged": "^16.2.7",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"prettier": "^3.9.5",
|
"prettier": "^3.8.3",
|
||||||
"recast": "^0.23.12",
|
"recast": "^0.23.11",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rolldown": "^1.2.0",
|
"rolldown": "^1.0.1",
|
||||||
"rolldown-plugin-dts": "^0.26.0",
|
"rolldown-plugin-dts": "^0.25.1",
|
||||||
"sass-embedded": "^1.100.0",
|
"sass-embedded": "^1.99.0",
|
||||||
"semver": "^7.8.5",
|
"semver": "^7.7.3",
|
||||||
"serialize-javascript": "^7.0.7",
|
"serialize-javascript": "^7.0.0",
|
||||||
"shx": "^0.3.4",
|
"shx": "^0.3.4",
|
||||||
"typescript": "catalog:",
|
"typescript": "catalog:",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"vitepress": "2.0.0-alpha.18",
|
"vitepress": "^2.0.0-alpha.17",
|
||||||
"vitest": "^4.1.10",
|
"vitest": "^4.1.6",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-tsc": "^3.3.7"
|
"vue-tsc": "^3.2.9"
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"commitizen": {
|
"commitizen": {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.5",
|
||||||
"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,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.5",
|
||||||
"name": "@tmagic/core",
|
"name": "@tmagic/core",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -76,12 +76,10 @@ class IteratorContainer extends Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(config.items)) {
|
|
||||||
config.items?.forEach((element: MNode) => {
|
config.items?.forEach((element: MNode) => {
|
||||||
this.initNode(element, node, map);
|
this.initNode(element, node, map);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public setNodes(nodes: MNode[], index: number) {
|
public setNodes(nodes: MNode[], index: number) {
|
||||||
const map = this.nodes[index] || new Map();
|
const map = this.nodes[index] || new Map();
|
||||||
|
|||||||
@ -79,12 +79,10 @@ class Page extends Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(config.items)) {
|
|
||||||
config.items?.forEach((element: MComponent | MContainer) => {
|
config.items?.forEach((element: MComponent | MContainer) => {
|
||||||
this.initNode(element, node);
|
this.initNode(element, node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public getNode<T extends TMagicNode = TMagicNode>(
|
public getNode<T extends TMagicNode = TMagicNode>(
|
||||||
id: Id,
|
id: Id,
|
||||||
|
|||||||
@ -51,7 +51,7 @@ export const style2Obj = (style: string) => {
|
|||||||
|
|
||||||
export const fillBackgroundImage = (value: string) => {
|
export const fillBackgroundImage = (value: string) => {
|
||||||
if (value && !/^url/.test(value) && !/^linear-gradient/.test(value)) {
|
if (value && !/^url/.test(value) && !/^linear-gradient/.test(value)) {
|
||||||
return `url("${value}")`;
|
return `url(${value})`;
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -50,7 +50,7 @@ describe('style2Obj', () => {
|
|||||||
|
|
||||||
describe('fillBackgroundImage', () => {
|
describe('fillBackgroundImage', () => {
|
||||||
test('裸路径会包裹 url()', () => {
|
test('裸路径会包裹 url()', () => {
|
||||||
expect(fillBackgroundImage('a.png')).toBe('url("a.png")');
|
expect(fillBackgroundImage('a.png')).toBe('url(a.png)');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('已经是 url() 不重复包裹', () => {
|
test('已经是 url() 不重复包裹', () => {
|
||||||
@ -127,7 +127,7 @@ describe('transformStyle', () => {
|
|||||||
|
|
||||||
test('backgroundImage: browser 下补全 url()', () => {
|
test('backgroundImage: browser 下补全 url()', () => {
|
||||||
expect(transformStyle({ backgroundImage: 'a.png' }, 'browser')).toEqual({
|
expect(transformStyle({ backgroundImage: 'a.png' }, 'browser')).toEqual({
|
||||||
backgroundImage: 'url("a.png")',
|
backgroundImage: 'url(a.png)',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.5",
|
||||||
"name": "@tmagic/data-source",
|
"name": "@tmagic/data-source",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -38,7 +38,8 @@ export interface HttpDataSourceSchema extends DataSourceSchema {
|
|||||||
};
|
};
|
||||||
autoFetch?: boolean;
|
autoFetch?: boolean;
|
||||||
beforeRequest:
|
beforeRequest:
|
||||||
string | ((options: HttpOptions, content: { app: TMagicApp; dataSource: HttpDataSource }) => HttpOptions);
|
| string
|
||||||
|
| ((options: HttpOptions, content: { app: TMagicApp; dataSource: HttpDataSource }) => HttpOptions);
|
||||||
afterResponse:
|
afterResponse:
|
||||||
| string
|
| string
|
||||||
| ((response: any, content: { app: TMagicApp; dataSource: HttpDataSource; options: Partial<HttpOptions> }) => any);
|
| ((response: any, content: { app: TMagicApp; dataSource: HttpDataSource; options: Partial<HttpOptions> }) => any);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.5",
|
||||||
"name": "@tmagic/dep",
|
"name": "@tmagic/dep",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.5",
|
||||||
"name": "@tmagic/design",
|
"name": "@tmagic/design",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
<template>
|
|
||||||
<component class="tmagic-design-alert" :is="uiComponent" v-bind="uiProps">
|
|
||||||
<slot></slot>
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue';
|
|
||||||
|
|
||||||
import { getDesignConfig } from './config';
|
|
||||||
import type { AlertProps } from './types';
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: 'TMAlert',
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = defineProps<AlertProps>();
|
|
||||||
|
|
||||||
const ui = getDesignConfig('components')?.alert;
|
|
||||||
const uiComponent = ui?.component || 'el-alert';
|
|
||||||
|
|
||||||
const uiProps = computed<AlertProps>(() => ui?.props(props) || props);
|
|
||||||
</script>
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<component :is="uiComponent" :class="['tmagic-design-card', { 'tmagic-design-card--flat': isFlat }]" v-bind="uiProps">
|
<component class="tmagic-design-card" :is="uiComponent" v-bind="uiProps">
|
||||||
<template #header v-if="$slots.header">
|
<template #header v-if="$slots.header">
|
||||||
<slot name="header" class="header"></slot>
|
<slot name="header" class="header"></slot>
|
||||||
</template>
|
</template>
|
||||||
@ -14,7 +14,6 @@
|
|||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { getDesignConfig } from './config';
|
import { getDesignConfig } from './config';
|
||||||
import { isGlobalFlat } from './index';
|
|
||||||
import type { CardProps } from './types';
|
import type { CardProps } from './types';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -27,14 +26,5 @@ const ui = getDesignConfig('components')?.card;
|
|||||||
|
|
||||||
const uiComponent = ui?.component || 'el-card';
|
const uiComponent = ui?.component || 'el-card';
|
||||||
|
|
||||||
// 当祖先 `<MEditor>` / `<MForm>` 的 theme 是 `magic-admin` 时,整套设计语言默认走「无卡片感」,
|
const uiProps = computed<CardProps>(() => ui?.props(props) || props);
|
||||||
// 因此这里自动等价于 `flat=true`;调用方仍可显式传 `flat` 强制开启。
|
|
||||||
const isFlat = computed(() => !!props.flat || isGlobalFlat.value);
|
|
||||||
|
|
||||||
// 把 `flat` 从转发给底层 UI 库(el-card / t-card 等)的 props 中剥离:
|
|
||||||
// 它仅作用于本组件的视觉修饰类,不需要也不应该作为属性落到底层组件 / DOM 上。
|
|
||||||
const uiProps = computed<CardProps>(() => {
|
|
||||||
const { flat: _flat, ...rest } = props;
|
|
||||||
return ui?.props(rest as CardProps) || (rest as CardProps);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -7,39 +7,18 @@
|
|||||||
@update:modelValue="updateModelValue"
|
@update:modelValue="updateModelValue"
|
||||||
>
|
>
|
||||||
</component>
|
</component>
|
||||||
<MInput
|
|
||||||
v-if="isFlat && !isLargeStageContainer"
|
|
||||||
@update:modelValue="updateModelValue"
|
|
||||||
@change="changeHandler"
|
|
||||||
v-bind="uiProps"
|
|
||||||
class="tmagic-design-color-picker-input"
|
|
||||||
clearable
|
|
||||||
></MInput>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, type ComputedRef, inject } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import { getDesignConfig } from './config';
|
import { getDesignConfig } from './config';
|
||||||
import { isGlobalFlat } from './index';
|
|
||||||
import MInput from './Input.vue';
|
|
||||||
import type { ColorPickerProps } from './types';
|
import type { ColorPickerProps } from './types';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'TMColorPicker',
|
name: 'TMColorPicker',
|
||||||
});
|
});
|
||||||
|
|
||||||
const isFlat = computed(() => !!props.flat || isGlobalFlat.value);
|
|
||||||
|
|
||||||
// 祖先 `<MEditor>` 用 computed 包着 provide,值会随 props.isLargeStageContainer 变化。
|
|
||||||
// 类型标成 `ComputedRef<boolean>`,模板里 `<script setup>` 自动解包,仍可直接写
|
|
||||||
// `!isLargeStageContainer`;默认值也得包成 computed,保持形状一致,避免祖先没提供时
|
|
||||||
// 模板里对一个裸 `false` 和 `ComputedRef` 走两条不同的取值路径。
|
|
||||||
const isLargeStageContainer = inject<ComputedRef<boolean>>(
|
|
||||||
'isLargeStageContainer',
|
|
||||||
computed(() => false),
|
|
||||||
);
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ColorPickerProps>(), {
|
const props = withDefaults(defineProps<ColorPickerProps>(), {
|
||||||
showAlpha: false,
|
showAlpha: false,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
|
|||||||
@ -35,16 +35,6 @@ defineExpose({
|
|||||||
return form.value?.validate();
|
return form.value?.validate();
|
||||||
},
|
},
|
||||||
|
|
||||||
clearValidate(props?: string | string[]) {
|
|
||||||
if (typeof form.value?.clearValidate === 'function') {
|
|
||||||
return form.value?.clearValidate(props);
|
|
||||||
}
|
|
||||||
// tdesign 使用 clearValidate,element-plus 也是 clearValidate;此处兜底其它可能的命名
|
|
||||||
if (typeof form.value?.clearValidateState === 'function') {
|
|
||||||
return form.value?.clearValidateState();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resetFields() {
|
resetFields() {
|
||||||
if (typeof form.value?.resetFields === 'function') {
|
if (typeof form.value?.resetFields === 'function') {
|
||||||
return form.value?.resetFields();
|
return form.value?.resetFields();
|
||||||
|
|||||||
@ -1,65 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<component
|
<component class="tmagic-design-form-item" :is="uiComponent" v-bind="uiProps">
|
||||||
class="tmagic-design-form-item"
|
|
||||||
:class="{ 'has-extra-tips': adapterType === 'element-plus' && extraTips }"
|
|
||||||
:is="uiComponent"
|
|
||||||
v-bind="uiProps"
|
|
||||||
>
|
|
||||||
<template #label>
|
<template #label>
|
||||||
<slot name="label"></slot>
|
<slot name="label"></slot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #default>
|
<template #default>
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
<alert
|
<div v-if="adapterType === 'element-plus' && extra" v-html="extra" class="m-form-tip"></div>
|
||||||
v-if="adapterType === 'element-plus' && extraTips"
|
|
||||||
:title="extraTips"
|
|
||||||
type="warning"
|
|
||||||
show-icon
|
|
||||||
:closable="false"
|
|
||||||
></alert>
|
|
||||||
<div v-else-if="adapterType === 'element-plus' && extra" v-html="extra" class="m-form-tip"></div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="adapterType === 'element-plus'" #error="{ error }">
|
|
||||||
<div class="el-form-item__error">{{ resolveErrorText(error) }}</div>
|
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
import Alert from './Alert.vue';
|
|
||||||
import { getDesignConfig } from './config';
|
import { getDesignConfig } from './config';
|
||||||
import { stripValidateSuggestion } from './formValidateMessage';
|
|
||||||
import { isGlobalFlat } from './index';
|
|
||||||
import type { FormItemProps } from './types';
|
import type { FormItemProps } from './types';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'TMFormItem',
|
name: 'TMFormItem',
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<FormItemProps & { theme?: string }>();
|
const props = defineProps<FormItemProps>();
|
||||||
|
|
||||||
const ui = getDesignConfig('components')?.formItem;
|
const ui = getDesignConfig('components')?.formItem;
|
||||||
|
|
||||||
const uiComponent = ui?.component || 'el-form-item';
|
const uiComponent = ui?.component || 'el-form-item';
|
||||||
|
|
||||||
const adapterType = getDesignConfig('adapterType');
|
const adapterType = getDesignConfig('adapterType');
|
||||||
|
|
||||||
const formInline = inject<boolean>('formInline');
|
|
||||||
const formInRow = inject<boolean>('isInRow');
|
|
||||||
const uiProps = computed<FormItemProps>(() => {
|
const uiProps = computed<FormItemProps>(() => {
|
||||||
const { extra, extraTips, ...rest } = ui?.props(props) || props;
|
const { extra, ...rest } = ui?.props(props) || props;
|
||||||
if (isGlobalFlat.value && rest.labelPosition === undefined) {
|
|
||||||
return { ...rest, labelPosition: formInline || formInRow ? 'right' : 'left' };
|
|
||||||
}
|
|
||||||
return rest;
|
return rest;
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验错误文案中,「修改建议」仅用于错误汇总展示。
|
|
||||||
* form-item 行内错误只展示主错误描述,不展示修改建议。
|
|
||||||
*/
|
|
||||||
const resolveErrorText = (error?: string) => stripValidateSuggestion(error);
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -4,9 +4,10 @@
|
|||||||
<div
|
<div
|
||||||
v-if="popoverVisible || !destroyOnClose"
|
v-if="popoverVisible || !destroyOnClose"
|
||||||
v-show="popoverVisible"
|
v-show="popoverVisible"
|
||||||
|
class="tmagic-design-popper"
|
||||||
ref="popperElementRef"
|
ref="popperElementRef"
|
||||||
:tabindex="tabindex"
|
:tabindex="tabindex"
|
||||||
:class="['tmagic-design-popper', popperClass, themeClass]"
|
:class="popperClass"
|
||||||
:style="style"
|
:style="style"
|
||||||
@mouseenter.once="popperMouseenterHandler"
|
@mouseenter.once="popperMouseenterHandler"
|
||||||
>
|
>
|
||||||
@ -21,7 +22,7 @@ import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref
|
|||||||
import type { Instance } from '@popperjs/core';
|
import type { Instance } from '@popperjs/core';
|
||||||
import { createPopper } from '@popperjs/core';
|
import { createPopper } from '@popperjs/core';
|
||||||
|
|
||||||
import { useThemeClass, useZIndex } from './index';
|
import { useZIndex } from './index';
|
||||||
import type { PopoverProps } from './types';
|
import type { PopoverProps } from './types';
|
||||||
|
|
||||||
defineSlots<{
|
defineSlots<{
|
||||||
@ -45,12 +46,6 @@ const props = withDefaults(defineProps<PopoverProps>(), {
|
|||||||
closeOnClickOutside: true,
|
closeOnClickOutside: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
|
||||||
* 挂在 `Teleport` 出去的 popper 根节点上,让主题级 CSS 变量(`--el-color-primary` 等)
|
|
||||||
* 在 portal 节点上也能命中。详见 `@tmagic/design/theme.ts`。
|
|
||||||
*/
|
|
||||||
const themeClass = useThemeClass();
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
/** 受控模式(传入了 visible)下点击外部收起时触发,便于配合 v-model:visible。 */
|
/** 受控模式(传入了 visible)下点击外部收起时触发,便于配合 v-model:visible。 */
|
||||||
'update:visible': [_visible: boolean];
|
'update:visible': [_visible: boolean];
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 校验错误文案中,「主错误描述」与「修改建议」之间的分隔符。
|
|
||||||
*
|
|
||||||
* 约定:typeMatch 等校验器产出的文案形如
|
|
||||||
* `主错误描述${VALIDATE_SUGGESTION_SEPARATOR}修改建议`。
|
|
||||||
* - 行内 form-item 错误、组件树 tooltip 仅展示「主错误描述」;
|
|
||||||
* - 错误汇总(如属性面板报错弹窗)展示完整文案(含修改建议)。
|
|
||||||
*
|
|
||||||
* 该常量为「主错误描述 / 修改建议」这一隐式协议的唯一真源,
|
|
||||||
* 所有生产/消费该文案的位置都应复用这里的常量与 helper,避免魔法字符串散落。
|
|
||||||
*/
|
|
||||||
export const VALIDATE_SUGGESTION_SEPARATOR = '\n\n';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在「主错误描述」后追加「修改建议」;无建议时原样返回主错误描述。
|
|
||||||
*
|
|
||||||
* @param message 主错误描述
|
|
||||||
* @param suggestion 可选的修改建议(示例值等)
|
|
||||||
*/
|
|
||||||
export const appendValidateSuggestion = (message: string, suggestion?: string): string =>
|
|
||||||
suggestion ? `${message}${VALIDATE_SUGGESTION_SEPARATOR}${suggestion}` : message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 去掉校验文案中的「修改建议」部分,仅保留「主错误描述」。
|
|
||||||
*
|
|
||||||
* @param text 完整校验文案(可能形如 `主错误描述\n\n修改建议`)
|
|
||||||
*/
|
|
||||||
export const stripValidateSuggestion = (text?: string): string =>
|
|
||||||
String(text ?? '').split(VALIDATE_SUGGESTION_SEPARATOR)[0];
|
|
||||||
@ -8,8 +8,6 @@ import './theme/index.scss';
|
|||||||
|
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './config';
|
export * from './config';
|
||||||
export * from './theme';
|
|
||||||
export * from './formValidateMessage';
|
|
||||||
|
|
||||||
export { default as TMagicAutocomplete } from './Autocomplete.vue';
|
export { default as TMagicAutocomplete } from './Autocomplete.vue';
|
||||||
export { default as TMagicBadge } from './Badge.vue';
|
export { default as TMagicBadge } from './Badge.vue';
|
||||||
@ -95,10 +93,6 @@ export let useZIndex = (zIndexOverrides?: Ref<number>) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 与 form/utils/config、editor/utils/config 里的同名变量保持一致:用 ref 持有 flat 全局开关,
|
|
||||||
// const 引用本身不可变,规避 `no-mutable-exports` / `naming-convention` 对 `let` 模块变量的限制。
|
|
||||||
export const isGlobalFlat = ref(false);
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
install(app: App, options: DesignPluginOptions) {
|
install(app: App, options: DesignPluginOptions) {
|
||||||
tMagicMessage =
|
tMagicMessage =
|
||||||
@ -134,8 +128,6 @@ export default {
|
|||||||
useZIndex = options.useZIndex;
|
useZIndex = options.useZIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
isGlobalFlat.value = options.flat ?? false;
|
|
||||||
|
|
||||||
if (options.adapterType && globalThis.document?.documentElement) {
|
if (options.adapterType && globalThis.document?.documentElement) {
|
||||||
globalThis.document.documentElement.classList.add(`tmagic-adapter-${options.adapterType}`);
|
globalThis.document.documentElement.classList.add(`tmagic-adapter-${options.adapterType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,59 +0,0 @@
|
|||||||
/*
|
|
||||||
* 主题作用域穿透到 portal 节点的基建。
|
|
||||||
*
|
|
||||||
* `<MEditor>` / `<MForm>` 通过 provide 暴露当前的 `theme`,包含 `<Teleport>` 的组件
|
|
||||||
* 通过 `useThemeClass()` inject 得到主题修饰类,挂到传送目标根节点上即可让主题级
|
|
||||||
* CSS 变量(如 `--el-color-primary`)在 body 下的 portal 节点上也命中。
|
|
||||||
*
|
|
||||||
* 命名约定:portal 节点本身既不是 `m-editor` 也不是 `m-form`,因此使用独立的中性类
|
|
||||||
* `m-theme--<theme>`,避免语义混淆。同时把跨组件共享的主题级 CSS 变量统一挂在该类上,
|
|
||||||
* editor 根 / form 根 / portal 根都加上该类即可统一受用,editor / form 各自的内部
|
|
||||||
* 样式仍保留在 `.m-editor.m-editor--<theme>` / `.m-form.m-form--<theme>` 双类选择器
|
|
||||||
* 上,互不污染。
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ComputedRef, InjectionKey, Ref } from 'vue';
|
|
||||||
import { computed, inject } from 'vue';
|
|
||||||
|
|
||||||
/** 祖先 `<MEditor>` / `<MForm>` 暴露的当前 theme(一般来自 props.theme,经 MForm 时
|
|
||||||
* 若自身没设 theme,会透传外层 `<MEditor>` 的值,详见 Form.vue 中的 effectiveTheme)。 */
|
|
||||||
export const M_THEME_KEY: InjectionKey<Ref<string>> = Symbol('mTheme');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取得来自最近 `<MEditor>` / `<MForm>` 祖先 provide 的主题,拼成可直接套到 class 字符串
|
|
||||||
* 的格式:例如 `"m-theme--magic-admin"`。
|
|
||||||
*
|
|
||||||
* - 不在主题作用域里时返回空串,调用方可直接拼接,无需额外判空。
|
|
||||||
* - 与 `m-editor` / `m-form` 同名修饰类完全解耦:portal 节点只挂这一个中性主题类,
|
|
||||||
* 不会意外命中 editor / form 内部 DOM 的样式作用域。
|
|
||||||
*
|
|
||||||
* 使用场景:组件自身渲染了 `<Teleport>`(如 `TMagicPopover` / `FloatingBox` /
|
|
||||||
* `ContentMenu`),需要在传送目标根节点上挂主题类,让主题 CSS 变量能命中。
|
|
||||||
*/
|
|
||||||
export const useThemeClass = (): ComputedRef<string> => {
|
|
||||||
const theme = inject(M_THEME_KEY, null);
|
|
||||||
return computed(() => {
|
|
||||||
const t = theme?.value;
|
|
||||||
return t ? `m-theme--${t}` : '';
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 在组件内统一解析当前生效的 theme:以 `prop.theme` 为准,缺省时回退到祖先
|
|
||||||
* `<MEditor>` / `<MForm>` provide 的主题。
|
|
||||||
*
|
|
||||||
* 入参 `prop` 是任意结构,约束只看 `theme?: string`。一般直接把 `defineProps()` 的
|
|
||||||
* 返回值传进来即可(reactive proxy,computed 可正确跟踪外部更新);不要传
|
|
||||||
* `toRefs` 解构后的对象。
|
|
||||||
*
|
|
||||||
* 若 `prop.theme` 与祖先都没有,返回空串,调用方可直接拼接。
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* const props = defineProps<{ theme?: string }>();
|
|
||||||
* const theme = useTheme(props);
|
|
||||||
* // theme.value -> 'magic-admin' 或 ''
|
|
||||||
*/
|
|
||||||
export const useTheme = <T extends { theme?: string }>(prop: T): ComputedRef<string> => {
|
|
||||||
const injected = inject(M_THEME_KEY, null);
|
|
||||||
return computed(() => prop.theme || injected?.value || '');
|
|
||||||
};
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
// `TMagicCard` 的「铺平」模式:去掉卡片自身的视觉表达(背景 / 阴影 / 边框 / 圆角 /
|
|
||||||
// header 分隔线 / header & body 内边距),让卡片在视觉上与外层容器融为一体。
|
|
||||||
//
|
|
||||||
// 不动 body-style、不动折叠相关 prop —— 这些上层用法仍可正常工作。
|
|
||||||
// `is-always-shadow` 是 el-card 在 `shadow="always"` 时附加的类,需要单独再压一次。
|
|
||||||
.tmagic-design-card.tmagic-design-card--flat {
|
|
||||||
background: #fff;
|
|
||||||
box-shadow: none !important;
|
|
||||||
border-top: 0;
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
border-bottom: 0;
|
|
||||||
|
|
||||||
&.m-fields-group-list-item {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
&.is-always-shadow {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-card__header {
|
|
||||||
border-bottom: none;
|
|
||||||
padding: 16px;
|
|
||||||
&:hover {
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.el-card__body {
|
|
||||||
padding: 0 16px 0 16px;
|
|
||||||
// 这里每个form-item应该要有一个16px间距,但是除了tbody中的form-item
|
|
||||||
.tmagic-design-form-item:not(tbody .tmagic-design-form-item) {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
> .m-container-row {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tmagic-design-card--flat {
|
|
||||||
// .el-card__body {
|
|
||||||
// padding: 0;
|
|
||||||
// }
|
|
||||||
// .el-card__header {
|
|
||||||
// padding: 0 16px;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-split-title-card-container {
|
|
||||||
> .tmagic-design-card.tmagic-design-card--flat {
|
|
||||||
background-color: transparent;
|
|
||||||
> .el-card__header {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 24px;
|
|
||||||
}
|
|
||||||
> .el-card__body {
|
|
||||||
background-color: #fff;
|
|
||||||
padding-top: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
.tmagic-design-color-picker-input {
|
|
||||||
margin-left: 8px;
|
|
||||||
width: calc(var(--el-input-width, 100%) - 40px);
|
|
||||||
}
|
|
||||||
@ -1,3 +1 @@
|
|||||||
@use "./popover.scss";
|
@use "./popover.scss";
|
||||||
@use "./card.scss";
|
|
||||||
@use "./colorPicker.scss";
|
|
||||||
|
|||||||
@ -1,38 +0,0 @@
|
|||||||
@use "../../index.scss";
|
|
||||||
|
|
||||||
.m-form.m-form--magic-admin {
|
|
||||||
.el-collapse-item__header {
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 24px;
|
|
||||||
margin-top: 8px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tmagic-design-form-item {
|
|
||||||
&.has-extra-tips {
|
|
||||||
> .el-form-item__content {
|
|
||||||
gap: 10px;
|
|
||||||
> .tmagic-design-alert {
|
|
||||||
border-radius: 2px;
|
|
||||||
width: fit-content;
|
|
||||||
padding: 0 8px;
|
|
||||||
color: var(--el-color-primary, #0056ea);
|
|
||||||
background-color: var(--el-color-primary-light-9, rgb(236, 245, 255));
|
|
||||||
.el-icon.el-alert__icon {
|
|
||||||
fill: var(--el-color-primary, #0056ea);
|
|
||||||
color: var(--el-color-primary, #0056ea);
|
|
||||||
}
|
|
||||||
.el-alert__title {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -24,17 +24,6 @@ export interface BadgeProps {
|
|||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AlertProps {
|
|
||||||
title?: string;
|
|
||||||
/** ElAlert 实际支持的 type;与 ButtonProps.type 不同,没有 `primary` / `danger` */
|
|
||||||
type?: 'success' | 'warning' | 'info' | 'error' | 'primary';
|
|
||||||
description?: string;
|
|
||||||
closable?: boolean;
|
|
||||||
center?: boolean;
|
|
||||||
closeText?: string;
|
|
||||||
showIcon?: boolean;
|
|
||||||
effect?: 'light' | 'dark';
|
|
||||||
}
|
|
||||||
export interface ButtonProps {
|
export interface ButtonProps {
|
||||||
type?: string;
|
type?: string;
|
||||||
size?: FieldSize;
|
size?: FieldSize;
|
||||||
@ -50,11 +39,6 @@ export interface CardProps {
|
|||||||
bodyStyle?: Record<string, any>;
|
bodyStyle?: Record<string, any>;
|
||||||
shadow?: string;
|
shadow?: string;
|
||||||
header?: string;
|
header?: string;
|
||||||
/**
|
|
||||||
* 铺平模式:去除卡片自身的视觉(背景 / 阴影 / 边框 / 圆角 / header 分隔线 / header 与 body 内边距)。
|
|
||||||
* 用于已有外层容器或者使用方希望「无卡片感」的场景,TMagicCard 的折叠等 prop 仍可正常使用。
|
|
||||||
*/
|
|
||||||
flat?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CascaderProps {
|
export interface CascaderProps {
|
||||||
@ -113,7 +97,6 @@ export interface ColorPickerProps {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
showAlpha?: boolean;
|
showAlpha?: boolean;
|
||||||
size?: FieldSize;
|
size?: FieldSize;
|
||||||
flat?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DatePickerProps {
|
export interface DatePickerProps {
|
||||||
@ -202,9 +185,7 @@ export interface FormItemProps {
|
|||||||
labelWidth?: string | number;
|
labelWidth?: string | number;
|
||||||
rules?: any;
|
rules?: any;
|
||||||
extra?: string;
|
extra?: string;
|
||||||
extraTips?: string;
|
|
||||||
labelPosition?: 'top' | 'left' | 'right';
|
labelPosition?: 'top' | 'left' | 'right';
|
||||||
text?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InputProps {
|
export interface InputProps {
|
||||||
@ -253,9 +234,6 @@ export interface PaginationProps {
|
|||||||
|
|
||||||
export interface PopconfirmProps {
|
export interface PopconfirmProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
width?: string | number;
|
|
||||||
/** 浮层是否插入到 body,默认 true。设为 false 时浮层内联渲染,便于嵌套在 hover 浮层中避免父级因 mouseleave 收起。 */
|
|
||||||
teleported?: boolean;
|
|
||||||
placement?:
|
placement?:
|
||||||
| 'top'
|
| 'top'
|
||||||
| 'left'
|
| 'left'
|
||||||
@ -483,11 +461,6 @@ export interface Components {
|
|||||||
props: (props: BadgeProps) => BadgeProps;
|
props: (props: BadgeProps) => BadgeProps;
|
||||||
};
|
};
|
||||||
|
|
||||||
alert: {
|
|
||||||
component: DefineComponent<AlertProps, {}, any> | string;
|
|
||||||
props: (props: AlertProps) => AlertProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
autocomplete: {
|
autocomplete: {
|
||||||
component: DefineComponent<AutocompleteProps, {}, any> | string;
|
component: DefineComponent<AutocompleteProps, {}, any> | string;
|
||||||
props: (props: AutocompleteProps) => AutocompleteProps;
|
props: (props: AutocompleteProps) => AutocompleteProps;
|
||||||
@ -750,7 +723,6 @@ export interface Components {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DesignPluginOptions {
|
export interface DesignPluginOptions {
|
||||||
flat?: boolean;
|
|
||||||
adapterType?: string;
|
adapterType?: string;
|
||||||
message?: TMagicMessage;
|
message?: TMagicMessage;
|
||||||
messageBox?: TMagicMessageBox;
|
messageBox?: TMagicMessageBox;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.8.0-beta.14",
|
"version": "1.8.0-beta.5",
|
||||||
"name": "@tmagic/editor",
|
"name": "@tmagic/editor",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"sideEffects": [
|
"sideEffects": [
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
:page-bar-sort-options="pageBarSortOptions"
|
:page-bar-sort-options="pageBarSortOptions"
|
||||||
:page-filter-function="pageFilterFunction"
|
:page-filter-function="pageFilterFunction"
|
||||||
:hide-sidebar="hideSidebar"
|
:hide-sidebar="hideSidebar"
|
||||||
:theme="theme"
|
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<slot name="header"></slot>
|
<slot name="header"></slot>
|
||||||
@ -136,10 +135,9 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { computed, provide, ref } from 'vue';
|
import { provide } from 'vue';
|
||||||
|
|
||||||
import type { MApp } from '@tmagic/core';
|
import type { MApp } from '@tmagic/core';
|
||||||
import { M_THEME_KEY } from '@tmagic/design';
|
|
||||||
|
|
||||||
import Framework from './layouts/Framework.vue';
|
import Framework from './layouts/Framework.vue';
|
||||||
import TMagicNavMenu from './layouts/NavMenu.vue';
|
import TMagicNavMenu from './layouts/NavMenu.vue';
|
||||||
@ -160,7 +158,7 @@ import stageOverlayService from './services/stageOverlay';
|
|||||||
import storageService from './services/storage';
|
import storageService from './services/storage';
|
||||||
import uiService from './services/ui';
|
import uiService from './services/ui';
|
||||||
import keybindingConfig from './utils/keybinding-config';
|
import keybindingConfig from './utils/keybinding-config';
|
||||||
import { defaultEditorProps, EditorProps, ENABLE_PROPS_FORM_VALIDATE } from './editorProps';
|
import { defaultEditorProps, EditorProps } from './editorProps';
|
||||||
import { initServiceEvents, initServiceState } from './initService';
|
import { initServiceEvents, initServiceState } from './initService';
|
||||||
import type { TreeNodeData } from './type';
|
import type { TreeNodeData } from './type';
|
||||||
import type { EditorSlots, EventBus, Services, StageOptions } from './type';
|
import type { EditorSlots, EventBus, Services, StageOptions } from './type';
|
||||||
@ -229,14 +227,10 @@ const stageOptions: StageOptions = {
|
|||||||
|
|
||||||
stageOverlayService.set('stageOptions', stageOptions);
|
stageOverlayService.set('stageOptions', stageOptions);
|
||||||
|
|
||||||
const propsPanelRef = ref<InstanceType<typeof FormPanel> | null>(null);
|
|
||||||
|
|
||||||
provide('services', services);
|
provide('services', services);
|
||||||
|
|
||||||
provide('codeOptions', props.codeOptions);
|
provide('codeOptions', props.codeOptions);
|
||||||
provide('stageOptions', stageOptions);
|
provide('stageOptions', stageOptions);
|
||||||
/** 是否启用「属性配置表单校验」联动能力,供 PropsPanel / FormPanel 判断校验失败时是否仍更新节点并记录错误 */
|
|
||||||
provide(ENABLE_PROPS_FORM_VALIDATE, props.enablePropsFormValidate ?? false);
|
|
||||||
/**
|
/**
|
||||||
* 把顶层 `extendFormState` 提供给非 PropsPanel 链路上的组件使用(例如历史差异对话框 HistoryDiffDialog
|
* 把顶层 `extendFormState` 提供给非 PropsPanel 链路上的组件使用(例如历史差异对话框 HistoryDiffDialog
|
||||||
* 内部的 CompareForm)。这样所有依赖业务上下文的表单 filterFunction 都能拿到一致的扩展状态,
|
* 内部的 CompareForm)。这样所有依赖业务上下文的表单 filterFunction 都能拿到一致的扩展状态,
|
||||||
@ -244,20 +238,6 @@ provide(ENABLE_PROPS_FORM_VALIDATE, props.enablePropsFormValidate ?? false);
|
|||||||
*/
|
*/
|
||||||
provide('extendFormState', props.extendFormState);
|
provide('extendFormState', props.extendFormState);
|
||||||
|
|
||||||
// 用 computed 包一层再 provide,否则传下去的是 provide 那一刻的值快照,
|
|
||||||
// props.isLargeStageContainer 后续变化不会同步到子孙(如 @tmagic/design/ColorPicker)。
|
|
||||||
// 子孙侧 `inject<ComputedRef<boolean>>('isLargeStageContainer', computed(() => false))`
|
|
||||||
// 用 `.value` 或模板自动解包读取即可拿到最新值。
|
|
||||||
provide(
|
|
||||||
'isLargeStageContainer',
|
|
||||||
computed(() => props.isLargeStageContainer),
|
|
||||||
);
|
|
||||||
/**
|
|
||||||
* 提供 PropsPanel 主属性表单的 formState getter,供历史差异弹窗复用,
|
|
||||||
* 让 CompareForm 与 PropsPanel 的 filterFunction 上下文保持一致。
|
|
||||||
*/
|
|
||||||
provide('getPropsPanelFormState', () => propsPanelRef.value?.configForm?.formState);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把历史记录面板的自定义扩展 tab 提供给深层的 HistoryListPanel(它挂在 NavMenu 中,
|
* 把历史记录面板的自定义扩展 tab 提供给深层的 HistoryListPanel(它挂在 NavMenu 中,
|
||||||
* 以 markRaw component 形式渲染,无法直接通过 props 透传)。业务方可借此在历史记录
|
* 以 markRaw component 形式渲染,无法直接通过 props 透传)。业务方可借此在历史记录
|
||||||
@ -267,22 +247,10 @@ provide('historyListExtraTabs', props.historyListExtraTabs);
|
|||||||
|
|
||||||
provide<EventBus>('eventBus', new EventEmitter());
|
provide<EventBus>('eventBus', new EventEmitter());
|
||||||
|
|
||||||
/**
|
|
||||||
* 把当前主题以响应式 ref 形式 provide 给后代,供包含 `Teleport` 的组件
|
|
||||||
* (如 `TMagicPopover` / `FloatingBox` / `ContentMenu`)在传送目标上挂 `m-theme--<theme>`
|
|
||||||
* 类,让主题级 CSS 变量在 portal 节点上也能命中。详见 `@tmagic/design/theme.ts`。
|
|
||||||
*/
|
|
||||||
provide(
|
|
||||||
M_THEME_KEY,
|
|
||||||
computed(() => props.theme ?? ''),
|
|
||||||
);
|
|
||||||
|
|
||||||
const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
|
const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
|
||||||
propsPanelRef.value = e;
|
|
||||||
emit('props-panel-mounted', e);
|
emit('props-panel-mounted', e);
|
||||||
};
|
};
|
||||||
const propsPanelUnmountedHandler = () => {
|
const propsPanelUnmountedHandler = () => {
|
||||||
propsPanelRef.value = null;
|
|
||||||
emit('props-panel-unmounted');
|
emit('props-panel-unmounted');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,9 +4,7 @@
|
|||||||
v-model:visible="boxVisible"
|
v-model:visible="boxVisible"
|
||||||
v-model:width="width"
|
v-model:width="width"
|
||||||
v-model:height="codeBlockEditorHeight"
|
v-model:height="codeBlockEditorHeight"
|
||||||
:body-style="{ padding: '0 16px' }"
|
|
||||||
:title="content.name ? `${disabled ? '查看' : '编辑'}${content.name}` : '新增代码'"
|
:title="content.name ? `${disabled ? '查看' : '编辑'}${content.name}` : '新增代码'"
|
||||||
:framework-width="frameworkWidth"
|
|
||||||
:position="boxPosition"
|
:position="boxPosition"
|
||||||
:before-close="beforeClose"
|
:before-close="beforeClose"
|
||||||
>
|
>
|
||||||
@ -208,7 +206,6 @@ const closedHandler = () => {
|
|||||||
|
|
||||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
|
||||||
|
|
||||||
watch(boxVisible, (visible) => {
|
watch(boxVisible, (visible) => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
|||||||
@ -10,45 +10,100 @@
|
|||||||
:is-compare="true"
|
:is-compare="true"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
:label-width="labelWidth"
|
:label-width="labelWidth"
|
||||||
:extend-state="mergedExtendState"
|
:extend-state="extendState"
|
||||||
:show-diff="showDiff"
|
:show-diff="showDiff"
|
||||||
:self-diff-field-types="selfDiffFieldTypes"
|
|
||||||
:size="size"
|
|
||||||
></MForm>
|
></MForm>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, type Ref, type ShallowRef } from 'vue';
|
import { computed, inject, type Ref, ref, type ShallowRef, useTemplateRef, watch, watchEffect } from 'vue';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { type CodeBlockContent, HookType } from '@tmagic/core';
|
import { type CodeBlockContent, type DataSourceSchema, HookType, type MNode } from '@tmagic/core';
|
||||||
import { type FormConfig, type FormValue, MForm } from '@tmagic/form';
|
import { type FormConfig, type FormState, type FormValue, MForm } from '@tmagic/form';
|
||||||
|
|
||||||
import { useCompareForm } from '@editor/hooks/use-compare-form';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import type { CompareFormBaseProps } from '@editor/type';
|
import type { CompareCategory, CompareFormLoadConfig } from '@editor/type';
|
||||||
|
import { getCodeBlockFormConfig } from '@editor/utils/code-block';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MEditorCompareForm',
|
name: 'MEditorCompareForm',
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<
|
defineProps<{
|
||||||
CompareFormBaseProps & {
|
/** 当前值(修改后的值) */
|
||||||
|
value: Partial<MNode> | Partial<DataSourceSchema> | Partial<CodeBlockContent> | Record<string, any>;
|
||||||
/** 用于对比的旧值(修改前的值) */
|
/** 用于对比的旧值(修改前的值) */
|
||||||
lastValue?: CompareFormBaseProps['value'];
|
lastValue?: Partial<MNode> | Partial<DataSourceSchema> | Partial<CodeBlockContent> | Record<string, any>;
|
||||||
/** 需要走 self diff 的字段类型(例如 mod-cond)。 */
|
/**
|
||||||
selfDiffFieldTypes?: string[];
|
* 类型说明:
|
||||||
}
|
* - `category` 为 `node` 时,`type` 为节点组件的类型,例如 'text'、'button'、'page'、'container' 等
|
||||||
>(),
|
* - `category` 为 `data-source` 时,`type` 为数据源类型,例如 'base'、'http'
|
||||||
|
* - `category` 为 `code-block` 时,`type` 可不传
|
||||||
|
*/
|
||||||
|
type?: string;
|
||||||
|
/** 表单配置类别,决定从哪里取 FormConfig */
|
||||||
|
category?: CompareCategory;
|
||||||
|
/** 数据源代码块场景下的数据源类型(base/http),用于代码块表单中"执行时机"展示 */
|
||||||
|
dataSourceType?: string;
|
||||||
|
labelWidth?: string;
|
||||||
|
/**
|
||||||
|
* 外层容器高度。设置后表单内容超出时会在 CompareForm 内部出现滚动条,
|
||||||
|
* 避免 dialog / 面板使用方需要自行处理滚动。可传任意 CSS 长度,例如 `60vh` / `400px` / `100%`。
|
||||||
|
*/
|
||||||
|
height?: string;
|
||||||
|
/**
|
||||||
|
* 用户自定义注入到 MForm.formState 的扩展字段,与 Editor 顶层的 `extendFormState`、
|
||||||
|
* PropsPanel 的 `extend-state` 语义一致。表单 item 的 `display` / `disabled` 等
|
||||||
|
* filterFunction 经常依赖这里注入的字段(如 stage、自定义业务上下文等),
|
||||||
|
* 因此在差异对比场景下也需要透传,避免出现 `formState.xxx is undefined` 的运行时错误。
|
||||||
|
*/
|
||||||
|
extendState?: (_state: FormState) => Record<string, any> | Promise<Record<string, any>>;
|
||||||
|
/**
|
||||||
|
* 自定义 FormConfig 加载逻辑。传入后将接管内置的按 `category`(node/data-source/code-block)
|
||||||
|
* 取配置逻辑,调用方可根据业务自行返回(或异步返回)表单配置。可通过
|
||||||
|
* `ctx.defaultLoadConfig()` 复用默认结果再做二次加工。返回的 config 直接用于对比展示。
|
||||||
|
*/
|
||||||
|
loadConfig?: CompareFormLoadConfig;
|
||||||
|
}>(),
|
||||||
{
|
{
|
||||||
category: 'node',
|
category: 'node',
|
||||||
labelWidth: '120px',
|
labelWidth: '120px',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { config, currentValues, wrapperStyle, mergedExtendState, loadConfig, formRef, normalizeCodeBlockValue } =
|
const { propsService, dataSourceService, codeBlockService, editorService } = useServices();
|
||||||
useCompareForm(props);
|
const services = useServices();
|
||||||
|
|
||||||
|
const config = ref<FormConfig>([]);
|
||||||
|
|
||||||
|
/** vs-code 编辑器的 monaco 配置项,沿用 Editor 顶层 provide('codeOptions', ...) 的注入。 */
|
||||||
|
const codeOptions = inject<Record<string, any>>('codeOptions', {});
|
||||||
|
|
||||||
|
/** 将代码块的 content 字段统一成字符串,便于在表单/对比中展示 */
|
||||||
|
const normalizeCodeBlockValue = (
|
||||||
|
v: Partial<CodeBlockContent> | Record<string, any> | undefined,
|
||||||
|
): Record<string, any> => {
|
||||||
|
if (!v) return {};
|
||||||
|
const next: Record<string, any> = { ...v };
|
||||||
|
if (next.content && typeof next.content !== 'string') {
|
||||||
|
try {
|
||||||
|
next.content = next.content.toString();
|
||||||
|
} catch {
|
||||||
|
next.content = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentValues = computed<FormValue>(() => {
|
||||||
|
if (props.category === 'code-block') {
|
||||||
|
return normalizeCodeBlockValue(props.value as Partial<CodeBlockContent>);
|
||||||
|
}
|
||||||
|
return (props.value || {}) as FormValue;
|
||||||
|
});
|
||||||
|
|
||||||
const lastValuesProcessed = computed<FormValue>(() => {
|
const lastValuesProcessed = computed<FormValue>(() => {
|
||||||
if (props.category === 'code-block') {
|
if (props.category === 'code-block') {
|
||||||
@ -57,6 +112,18 @@ const lastValuesProcessed = computed<FormValue>(() => {
|
|||||||
return (props.lastValue || {}) as FormValue;
|
return (props.lastValue || {}) as FormValue;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 外层包裹层的样式:当传入 `height` 时启用固定高度 + 内部滚动,
|
||||||
|
* 这样滚动条会出现在 CompareForm 内部,避免父容器(如 Dialog)自身也产生滚动。
|
||||||
|
*/
|
||||||
|
const wrapperStyle = computed(() => {
|
||||||
|
if (!props.height) return undefined;
|
||||||
|
return {
|
||||||
|
height: props.height,
|
||||||
|
overflow: 'auto',
|
||||||
|
} as Record<string, string>;
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `code-select` 字段在历史数据中存在两种"语义为空"的形态:
|
* `code-select` 字段在历史数据中存在两种"语义为空"的形态:
|
||||||
* - 字符串 `''`(旧数据 / 用户从未配置过钩子);
|
* - 字符串 `''`(旧数据 / 用户从未配置过钩子);
|
||||||
@ -85,6 +152,100 @@ const showDiff = ({ curValue, lastValue, config }: { curValue: any; lastValue: a
|
|||||||
return !isEqual(curValue, lastValue);
|
return !isEqual(curValue, lastValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeStyleDisplayConfig = (formConfig: FormConfig): FormConfig =>
|
||||||
|
formConfig.map((item) => {
|
||||||
|
if (!('type' in item)) return item;
|
||||||
|
if (item?.type !== 'tab' || !Array.isArray(item.items)) return item;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
items: item.items.map((tabPane) => {
|
||||||
|
if (tabPane?.title !== '样式' || !Array.isArray(tabPane.items)) return tabPane;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...tabPane,
|
||||||
|
display: true,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置的默认 FormConfig 加载逻辑:按 `category` 从对应 service / 工具取配置。
|
||||||
|
* 作为 ctx.defaultLoadConfig 透传给自定义 `loadConfig`,方便复用与二次加工。
|
||||||
|
*/
|
||||||
|
const defaultLoadConfig = async (): Promise<FormConfig> => {
|
||||||
|
switch (props.category) {
|
||||||
|
case 'node': {
|
||||||
|
if (!props.type) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return removeStyleDisplayConfig(
|
||||||
|
await propsService.getPropsConfig(props.type, { node: props.value as unknown as MNode }),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case 'data-source': {
|
||||||
|
return dataSourceService.getFormConfig(props.type || 'base');
|
||||||
|
}
|
||||||
|
case 'code-block': {
|
||||||
|
return getCodeBlockFormConfig({
|
||||||
|
paramColConfig: codeBlockService.getParamsColConfig(),
|
||||||
|
// 通过传入 dataSourceType 间接表达"是数据源代码块"——在对比场景下 props.dataSourceType
|
||||||
|
// 由调用方按 step 上下文显式传入,未传则视为普通代码块,「执行时机」字段隐藏。
|
||||||
|
isDataSource: () => Boolean(props.dataSourceType),
|
||||||
|
dataSourceType: () => props.dataSourceType,
|
||||||
|
codeOptions,
|
||||||
|
// 对比模式只读,不需要校验/语法检查
|
||||||
|
editable: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadConfig = async () => {
|
||||||
|
if (props.loadConfig) {
|
||||||
|
config.value = await props.loadConfig({
|
||||||
|
category: props.category,
|
||||||
|
type: props.type,
|
||||||
|
dataSourceType: props.dataSourceType,
|
||||||
|
defaultLoadConfig,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
config.value = await defaultLoadConfig();
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
[() => props.category, () => props.type, () => props.dataSourceType, () => props.loadConfig],
|
||||||
|
() => {
|
||||||
|
loadConfig();
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const formRef = useTemplateRef<InstanceType<typeof MForm>>('form');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 把 services / stage 注入 MForm 的 formState,避免 propsService 注入的表单配置中
|
||||||
|
* 形如 `display: ({ services }) => services.uiService.get(...)` 的 filterFunction
|
||||||
|
* 在执行时拿不到 `formState.services` 而报错。
|
||||||
|
*
|
||||||
|
* 与 props-panel/FormPanel.vue 中的注入方式保持一致:
|
||||||
|
* - services:整个 useServices() 返回的服务集合;
|
||||||
|
* - stage:当前 editorService.get('stage') 的最新值。
|
||||||
|
*/
|
||||||
|
const stage = computed(() => editorService.get('stage'));
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (formRef.value) {
|
||||||
|
formRef.value.formState.stage = stage.value;
|
||||||
|
formRef.value.formState.services = services;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
defineExpose<{
|
defineExpose<{
|
||||||
form: ShallowRef<InstanceType<typeof MForm> | null>;
|
form: ShallowRef<InstanceType<typeof MForm> | null>;
|
||||||
config: Ref<FormConfig>;
|
config: Ref<FormConfig>;
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<div
|
<div
|
||||||
v-show="visible"
|
v-show="visible"
|
||||||
|
class="magic-editor-content-menu"
|
||||||
ref="menu"
|
ref="menu"
|
||||||
:class="['magic-editor-content-menu', themeClass]"
|
|
||||||
:style="menuStyle"
|
:style="menuStyle"
|
||||||
@mouseenter="mouseenterHandler()"
|
@mouseenter="mouseenterHandler()"
|
||||||
@contextmenu.prevent
|
@contextmenu.prevent
|
||||||
@ -39,7 +39,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, nextTick, onBeforeUnmount, onMounted, type Ref, ref, useTemplateRef } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, onMounted, type Ref, ref, useTemplateRef } from 'vue';
|
||||||
|
|
||||||
import { useThemeClass, useZIndex } from '@tmagic/design';
|
import { useZIndex } from '@tmagic/design';
|
||||||
|
|
||||||
import { MenuButton, MenuComponent } from '@editor/type';
|
import { MenuButton, MenuComponent } from '@editor/type';
|
||||||
|
|
||||||
@ -73,14 +73,6 @@ const menuEl = useTemplateRef<HTMLDivElement>('menu');
|
|||||||
const buttonRefs = useTemplateRef<InstanceType<typeof ToolButton>[]>('buttons');
|
const buttonRefs = useTemplateRef<InstanceType<typeof ToolButton>[]>('buttons');
|
||||||
const subMenuRef = useTemplateRef<any>('subMenu');
|
const subMenuRef = useTemplateRef<any>('subMenu');
|
||||||
const visible = ref(false);
|
const visible = ref(false);
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
|
||||||
* - 顶层 `ContentMenu` 渲染在编辑器子树中,加上去是冗余但无害;
|
|
||||||
* - 子菜单(递归 `<content-menu :is-sub-menu="true">` 被 `<teleport to="body">` 送到 body)
|
|
||||||
* 不在编辑器子树中,必须显式挂上主题类,主题级 CSS 变量才能命中。
|
|
||||||
*/
|
|
||||||
const themeClass = useThemeClass();
|
|
||||||
const subMenuData: Ref<(MenuButton | MenuComponent)[]> = ref<(MenuButton | MenuComponent)[]>([]);
|
const subMenuData: Ref<(MenuButton | MenuComponent)[]> = ref<(MenuButton | MenuComponent)[]>([]);
|
||||||
const zIndex = useZIndex();
|
const zIndex = useZIndex();
|
||||||
const curZIndex = ref<number>(0);
|
const curZIndex = ref<number>(0);
|
||||||
@ -128,42 +120,20 @@ const outsideClickHideHandler = (e: MouseEvent) => {
|
|||||||
hide();
|
hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 根据菜单实际尺寸修正位置,避免超出可视范围
|
|
||||||
const fixPosition = () => {
|
|
||||||
const menu = menuEl.value;
|
|
||||||
if (!menu || !visible.value) return;
|
|
||||||
|
|
||||||
const menuHeight = menu.clientHeight;
|
|
||||||
const menuWidth = menu.clientWidth;
|
|
||||||
|
|
||||||
let { top, left } = menuPosition.value;
|
|
||||||
|
|
||||||
if (top + menuHeight > document.body.clientHeight) {
|
|
||||||
top = Math.max(0, document.body.clientHeight - menuHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (left + menuWidth > document.body.clientWidth) {
|
|
||||||
left = Math.max(0, document.body.clientWidth - menuWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (top !== menuPosition.value.top || left !== menuPosition.value.left) {
|
|
||||||
menuPosition.value = { top, left };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setPosition = (e: { clientY: number; clientX: number }) => {
|
const setPosition = (e: { clientY: number; clientX: number }) => {
|
||||||
|
const menuHeight = menuEl.value?.clientHeight || 0;
|
||||||
|
|
||||||
|
let top = e.clientY;
|
||||||
|
if (menuHeight + e.clientY > document.body.clientHeight) {
|
||||||
|
top = document.body.clientHeight - menuHeight;
|
||||||
|
}
|
||||||
|
|
||||||
menuPosition.value = {
|
menuPosition.value = {
|
||||||
top: e.clientY,
|
top,
|
||||||
left: e.clientX,
|
left: e.clientX,
|
||||||
};
|
};
|
||||||
fixPosition();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 菜单大小动态变化(如菜单项更新)后重新修正位置
|
|
||||||
const resizeObserver = new ResizeObserver(() => {
|
|
||||||
fixPosition();
|
|
||||||
});
|
|
||||||
|
|
||||||
const show = (e?: { clientY: number; clientX: number }) => {
|
const show = (e?: { clientY: number; clientX: number }) => {
|
||||||
visible.value = true;
|
visible.value = true;
|
||||||
|
|
||||||
@ -208,18 +178,12 @@ const mouseenterHandler = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (menuEl.value) {
|
|
||||||
resizeObserver.observe(menuEl.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.isSubMenu) return;
|
if (props.isSubMenu) return;
|
||||||
|
|
||||||
globalThis.addEventListener('mousedown', outsideClickHideHandler, true);
|
globalThis.addEventListener('mousedown', outsideClickHideHandler, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
resizeObserver.disconnect();
|
|
||||||
|
|
||||||
if (props.isSubMenu) return;
|
if (props.isSubMenu) return;
|
||||||
|
|
||||||
globalThis.removeEventListener('mousedown', outsideClickHideHandler, true);
|
globalThis.removeEventListener('mousedown', outsideClickHideHandler, true);
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Teleport to="body" v-if="visible">
|
<Teleport to="body" v-if="visible">
|
||||||
<div
|
<div ref="target" class="m-editor-float-box" :style="{ ...style, zIndex: curZIndex }" @mousedown="nextZIndex">
|
||||||
ref="target"
|
|
||||||
:class="['m-editor-float-box', themeClass]"
|
|
||||||
v-bind="$attrs"
|
|
||||||
:style="{ ...style, zIndex: curZIndex }"
|
|
||||||
@mousedown="nextZIndex"
|
|
||||||
>
|
|
||||||
<div ref="title" class="m-editor-float-box-title">
|
<div ref="title" class="m-editor-float-box-title">
|
||||||
<slot name="title">
|
<slot name="title">
|
||||||
<span>{{ title }}</span>
|
<span>{{ title }}</span>
|
||||||
@ -15,7 +9,7 @@
|
|||||||
<TMagicButton link size="small" @click="closeHandler"><MIcon :icon="Close"></MIcon></TMagicButton>
|
<TMagicButton link size="small" @click="closeHandler"><MIcon :icon="Close"></MIcon></TMagicButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="m-editor-float-box-body" :style="{ height: `${bodyHeight}px`, ...bodyStyle }">
|
<div class="m-editor-float-box-body" :style="{ height: `${bodyHeight}px` }">
|
||||||
<slot name="body"></slot>
|
<slot name="body"></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -23,13 +17,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, type CSSProperties, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue';
|
import { computed, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue';
|
||||||
import { Close } from '@element-plus/icons-vue';
|
import { Close } from '@element-plus/icons-vue';
|
||||||
import VanillaMoveable from 'moveable';
|
import VanillaMoveable from 'moveable';
|
||||||
|
|
||||||
import { TMagicButton, useThemeClass, useZIndex } from '@tmagic/design';
|
import { TMagicButton, useZIndex } from '@tmagic/design';
|
||||||
|
|
||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
|
|
||||||
interface Position {
|
interface Position {
|
||||||
left: number;
|
left: number;
|
||||||
@ -44,30 +39,17 @@ const props = withDefaults(
|
|||||||
defineProps<{
|
defineProps<{
|
||||||
position?: Position;
|
position?: Position;
|
||||||
title?: string;
|
title?: string;
|
||||||
bodyStyle?: CSSProperties;
|
|
||||||
/** 浮窗初始样式,会与内部计算样式合并,外部传入优先 */
|
|
||||||
initialStyle?: CSSProperties;
|
|
||||||
/** 用于约束浮窗 left 的容器宽度,传入时按宽度收敛 left,避免超出右边界;默认取视窗宽度 */
|
|
||||||
frameworkWidth?: number;
|
|
||||||
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
beforeClose?: (_done: (_cancel?: boolean) => void) => void;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
position: () => ({ left: 0, top: 0 }),
|
position: () => ({ left: 0, top: 0 }),
|
||||||
initialStyle: () => ({}),
|
|
||||||
frameworkWidth: 0,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetEl = useTemplateRef<HTMLDivElement>('target');
|
const targetEl = useTemplateRef<HTMLDivElement>('target');
|
||||||
const titleEl = useTemplateRef<HTMLDivElement>('title');
|
const titleEl = useTemplateRef<HTMLDivElement>('title');
|
||||||
|
|
||||||
/**
|
|
||||||
* 主题修饰类(来自最近的 `<MEditor>` / `<MForm>` 祖先 provide)。
|
|
||||||
* 挂在 `Teleport` 出去的浮动面板根节点上,让主题级 CSS 变量在 portal 节点上也能命中。
|
|
||||||
*/
|
|
||||||
const themeClass = useThemeClass();
|
|
||||||
|
|
||||||
const zIndex = useZIndex();
|
const zIndex = useZIndex();
|
||||||
const curZIndex = ref<number>(0);
|
const curZIndex = ref<number>(0);
|
||||||
|
|
||||||
@ -84,11 +66,12 @@ const bodyHeight = computed(() => {
|
|||||||
return 'auto';
|
return 'auto';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { uiService } = useServices();
|
||||||
|
const frameworkWidth = computed(() => uiService.get('frameworkRect').width || 0);
|
||||||
const style = computed(() => {
|
const style = computed(() => {
|
||||||
let { left } = props.position;
|
let { left } = props.position;
|
||||||
const frameworkWidth = props.frameworkWidth || globalThis.window?.innerWidth || 0;
|
if (width.value) {
|
||||||
if (width.value && frameworkWidth) {
|
left = left + width.value > frameworkWidth.value ? frameworkWidth.value - width.value : left;
|
||||||
left = left + width.value > frameworkWidth ? frameworkWidth - width.value : left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -96,60 +79,11 @@ const style = computed(() => {
|
|||||||
top: `${props.position.top}px`,
|
top: `${props.position.top}px`,
|
||||||
width: width.value ? `${width.value}px` : 'auto',
|
width: width.value ? `${width.value}px` : 'auto',
|
||||||
height: height.value ? `${height.value}px` : 'auto',
|
height: height.value ? `${height.value}px` : 'auto',
|
||||||
...props.initialStyle,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
let moveable: VanillaMoveable | null = null;
|
let moveable: VanillaMoveable | null = null;
|
||||||
|
|
||||||
// 拖拽/缩放时用于覆盖 iframe 的遮罩,防止鼠标进入 iframe 区域后事件被 iframe 吞掉导致拖拽丢失
|
|
||||||
let dragMask: HTMLDivElement | null = null;
|
|
||||||
let dragMaskVisible = false;
|
|
||||||
|
|
||||||
// 兜底:正常情况下遮罩由 moveable 的 dragEnd/resizeEnd 移除;但某些极端场景
|
|
||||||
// (拖拽中鼠标移出窗口后松开、alt-tab 切走窗口失焦、标签页被隐藏等)结束事件不会触发,
|
|
||||||
// 会导致全屏透明遮罩残留、整个编辑器无法点击。这里挂全局 pointerup/blur/visibilitychange 强制收尾。
|
|
||||||
const bindDragMaskSafety = () => {
|
|
||||||
globalThis.window?.addEventListener('pointerup', hideDragMask, true);
|
|
||||||
globalThis.window?.addEventListener('blur', hideDragMask);
|
|
||||||
globalThis.document?.addEventListener('visibilitychange', hideDragMask);
|
|
||||||
};
|
|
||||||
|
|
||||||
const unbindDragMaskSafety = () => {
|
|
||||||
globalThis.window?.removeEventListener('pointerup', hideDragMask, true);
|
|
||||||
globalThis.window?.removeEventListener('blur', hideDragMask);
|
|
||||||
globalThis.document?.removeEventListener('visibilitychange', hideDragMask);
|
|
||||||
};
|
|
||||||
|
|
||||||
const showDragMask = () => {
|
|
||||||
if (dragMaskVisible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!dragMask) {
|
|
||||||
dragMask = globalThis.document.createElement('div');
|
|
||||||
dragMask.className = 'm-editor-float-box-drag-mask';
|
|
||||||
}
|
|
||||||
globalThis.document.body.appendChild(dragMask);
|
|
||||||
dragMaskVisible = true;
|
|
||||||
bindDragMaskSafety();
|
|
||||||
|
|
||||||
// 拖拽标题时,root 上的 @mousedown="nextZIndex" 会把浮窗 z-index 抬高,
|
|
||||||
// 若此时才读取会拿到旧值导致遮罩被浮窗(及其内部 iframe)盖住,故用 nextTick 在 z-index 稳定后再设置到浮窗之上
|
|
||||||
const setMaskZIndex = () => {
|
|
||||||
if (dragMask) {
|
|
||||||
dragMask.style.zIndex = `${curZIndex.value + 1}`;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
setMaskZIndex();
|
|
||||||
nextTick(setMaskZIndex);
|
|
||||||
};
|
|
||||||
|
|
||||||
const hideDragMask = () => {
|
|
||||||
unbindDragMaskSafety();
|
|
||||||
dragMask?.parentNode?.removeChild(dragMask);
|
|
||||||
dragMaskVisible = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const initMoveable = () => {
|
const initMoveable = () => {
|
||||||
moveable = new VanillaMoveable(globalThis.document.body, {
|
moveable = new VanillaMoveable(globalThis.document.body, {
|
||||||
className: 'm-editor-floating-box-moveable',
|
className: 'm-editor-floating-box-moveable',
|
||||||
@ -167,14 +101,6 @@ const initMoveable = () => {
|
|||||||
bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' },
|
bounds: { left: 0, top: 0, right: 0, bottom: 0, position: 'css' },
|
||||||
});
|
});
|
||||||
|
|
||||||
// 仅在真正发生拖拽/缩放位移时插入遮罩:moveable 的 dragStart/resizeStart 在 mousedown 时即触发,
|
|
||||||
// 若此时就盖遮罩,会盖住浮窗本身导致 mouseup 落在遮罩上、关闭按钮的 click 无法触发(点击关闭不了)。
|
|
||||||
// 改为在 drag/resize(实际位移)时才显示,纯点击不再触发遮罩。
|
|
||||||
moveable.on('drag', showDragMask);
|
|
||||||
moveable.on('resize', showDragMask);
|
|
||||||
moveable.on('dragEnd', hideDragMask);
|
|
||||||
moveable.on('resizeEnd', hideDragMask);
|
|
||||||
|
|
||||||
moveable.on('drag', (e) => {
|
moveable.on('drag', (e) => {
|
||||||
e.target.style.transform = e.transform;
|
e.target.style.transform = e.transform;
|
||||||
});
|
});
|
||||||
@ -189,7 +115,6 @@ const initMoveable = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const destroyMoveable = () => {
|
const destroyMoveable = () => {
|
||||||
hideDragMask();
|
|
||||||
moveable?.destroy();
|
moveable?.destroy();
|
||||||
moveable = null;
|
moveable = null;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -87,3 +87,70 @@ const scrollBy = (delta: number) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.m-editor-scroll-bar {
|
||||||
|
position: absolute;
|
||||||
|
background-color: transparent;
|
||||||
|
opacity: 0.3;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s linear,
|
||||||
|
opacity 0.2s linear;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
background-color: #aaa;
|
||||||
|
border-radius: 6px;
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
width: 100%;
|
||||||
|
height: 15px;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
height: 6px;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s linear,
|
||||||
|
height 0.2s ease-in-out;
|
||||||
|
bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
height: 100%;
|
||||||
|
width: 15px;
|
||||||
|
right: 5px;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
width: 6px;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s linear,
|
||||||
|
width 0.2s ease-in-out;
|
||||||
|
right: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
background-color: #eee;
|
||||||
|
opacity: 0.9;
|
||||||
|
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
background-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
height: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.vertical {
|
||||||
|
.m-editor-scroll-bar-thumb {
|
||||||
|
width: 11px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@ -1,55 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="m-editor-view-form-wrapper" :style="wrapperStyle">
|
|
||||||
<MForm
|
|
||||||
v-if="config.length"
|
|
||||||
ref="form"
|
|
||||||
class="m-editor-view-form"
|
|
||||||
:config="config"
|
|
||||||
:init-values="currentValues"
|
|
||||||
:disabled="disabled"
|
|
||||||
:label-width="labelWidth"
|
|
||||||
:extend-state="mergedExtendState"
|
|
||||||
:size="size"
|
|
||||||
></MForm>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Ref, type ShallowRef } from 'vue';
|
|
||||||
|
|
||||||
import { type FormConfig, MForm } from '@tmagic/form';
|
|
||||||
|
|
||||||
import { useCompareForm } from '@editor/hooks/use-compare-form';
|
|
||||||
import type { CompareFormBaseProps } from '@editor/type';
|
|
||||||
|
|
||||||
defineOptions({
|
|
||||||
name: 'MEditorViewForm',
|
|
||||||
});
|
|
||||||
|
|
||||||
const props = withDefaults(
|
|
||||||
defineProps<
|
|
||||||
CompareFormBaseProps & {
|
|
||||||
/** 是否禁用表单(默认只读展示)。 */
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
>(),
|
|
||||||
{
|
|
||||||
category: 'node',
|
|
||||||
labelWidth: '120px',
|
|
||||||
disabled: true,
|
|
||||||
// extendState 的默认值由 useCompareForm 内部兜底(props.extendState ?? ...),此处无需重复提供
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { config, currentValues, wrapperStyle, mergedExtendState, loadConfig, formRef } = useCompareForm(props);
|
|
||||||
|
|
||||||
defineExpose<{
|
|
||||||
form: ShallowRef<InstanceType<typeof MForm> | null>;
|
|
||||||
config: Ref<FormConfig>;
|
|
||||||
reload: () => Promise<void>;
|
|
||||||
}>({
|
|
||||||
form: formRef,
|
|
||||||
config,
|
|
||||||
reload: loadConfig,
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,5 +1,3 @@
|
|||||||
import type { InjectionKey } from 'vue';
|
|
||||||
|
|
||||||
import type { DataSourceSchema, EventOption, Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core';
|
import type { DataSourceSchema, EventOption, Id, MApp, MNode, MPage, MPageFragment } from '@tmagic/core';
|
||||||
import type { FormConfig, FormState } from '@tmagic/form';
|
import type { FormConfig, FormState } from '@tmagic/form';
|
||||||
import StageCore, {
|
import StageCore, {
|
||||||
@ -28,17 +26,7 @@ import type {
|
|||||||
TreeNodeData,
|
TreeNodeData,
|
||||||
} from './type';
|
} from './type';
|
||||||
|
|
||||||
/**
|
|
||||||
* 「属性配置表单校验」联动能力的 provide/inject 注入键。
|
|
||||||
* 使用 Symbol 避免与其它字符串键冲突,供 PropsPanel / FormPanel 注入判断校验失败时是否仍更新节点并记录错误。
|
|
||||||
*/
|
|
||||||
export const ENABLE_PROPS_FORM_VALIDATE: InjectionKey<boolean> = Symbol('enablePropsFormValidate');
|
|
||||||
|
|
||||||
export interface EditorProps {
|
export interface EditorProps {
|
||||||
/** 是否是大屏模拟器容器,大屏容器时,左侧属性面板会扩展为两列(正常3列),颜色表单不扩展两列等 */
|
|
||||||
isLargeStageContainer?: boolean;
|
|
||||||
|
|
||||||
theme?: string;
|
|
||||||
/** 页面初始值 */
|
/** 页面初始值 */
|
||||||
modelValue?: MApp;
|
modelValue?: MApp;
|
||||||
/** 左侧面板中的组件类型列表 */
|
/** 左侧面板中的组件类型列表 */
|
||||||
@ -103,11 +91,6 @@ export interface EditorProps {
|
|||||||
disabledFlashTip?: boolean;
|
disabledFlashTip?: boolean;
|
||||||
/** 禁用双击在浮层中单独编辑选中组件 */
|
/** 禁用双击在浮层中单独编辑选中组件 */
|
||||||
disabledStageOverlay?: boolean;
|
disabledStageOverlay?: boolean;
|
||||||
/**
|
|
||||||
* 是否启用「属性配置表单校验」联动能力:开启后属性/样式表单校验失败时仍更新节点,
|
|
||||||
* 并把错误信息集中记录到 editorService,用于组件树标红提示与保存拦截;默认 false(关闭)。
|
|
||||||
*/
|
|
||||||
enablePropsFormValidate?: boolean;
|
|
||||||
/** 禁用属性配置面板右下角显示源码的按钮 */
|
/** 禁用属性配置面板右下角显示源码的按钮 */
|
||||||
disabledShowSrc?: boolean;
|
disabledShowSrc?: boolean;
|
||||||
/** 禁用数据源 */
|
/** 禁用数据源 */
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="m-fields-code-select" :class="config.className">
|
<div class="m-fields-code-select" :class="config.className">
|
||||||
<TMagicCard :flat="config.flat">
|
<TMagicCard>
|
||||||
<MContainer
|
<MContainer
|
||||||
:config="codeConfig"
|
:config="codeConfig"
|
||||||
:size="size"
|
:size="size"
|
||||||
class="code-select-content"
|
|
||||||
:prop="prop"
|
:prop="prop"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:is-compare="isCompareMode"
|
:is-compare="isCompareMode"
|
||||||
@ -13,20 +12,16 @@
|
|||||||
@change="changeHandler"
|
@change="changeHandler"
|
||||||
>
|
>
|
||||||
</MContainer>
|
</MContainer>
|
||||||
<TMagicButton class="create-button fullWidth" :icon="Plus" :size="size" :disabled="disabled" @click="newHandler()"
|
|
||||||
>添加{{ config.text }}</TMagicButton
|
|
||||||
>
|
|
||||||
</TMagicCard>
|
</TMagicCard>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch } from 'vue';
|
import { computed, watch } from 'vue';
|
||||||
import { Plus } from '@element-plus/icons-vue';
|
|
||||||
import { isEmpty } from 'lodash-es';
|
import { isEmpty } from 'lodash-es';
|
||||||
|
|
||||||
import { HookCodeType, HookType } from '@tmagic/core';
|
import { HookCodeType, HookType } from '@tmagic/core';
|
||||||
import { TMagicButton, TMagicCard } from '@tmagic/design';
|
import { TMagicCard } from '@tmagic/design';
|
||||||
import type { CodeSelectConfig, ContainerChangeEventData, FieldProps, GroupListConfig } from '@tmagic/form';
|
import type { CodeSelectConfig, ContainerChangeEventData, FieldProps, GroupListConfig } from '@tmagic/form';
|
||||||
import { MContainer } from '@tmagic/form';
|
import { MContainer } from '@tmagic/form';
|
||||||
|
|
||||||
@ -58,23 +53,12 @@ const props = withDefaults(defineProps<FieldProps<CodeSelectConfig>>(), {});
|
|||||||
* 仅当存在历史值时才启用对比,避免 lastValues 缺失时退化为「全部新增」的空对比。
|
* 仅当存在历史值时才启用对比,避免 lastValues 缺失时退化为「全部新增」的空对比。
|
||||||
*/
|
*/
|
||||||
const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues));
|
const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues));
|
||||||
const newHandler = () => {
|
|
||||||
const defaultCode = {
|
|
||||||
codeType: HookCodeType.CODE,
|
|
||||||
codeId: '',
|
|
||||||
};
|
|
||||||
const name = props.config.name || '';
|
|
||||||
const hookData = props.model[name]?.hookData || [];
|
|
||||||
emit('change', defaultCode, {
|
|
||||||
modifyKey: `hookData.${hookData.length}`,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
const codeConfig = computed<GroupListConfig>(() => ({
|
const codeConfig = computed<GroupListConfig>(() => ({
|
||||||
type: 'group-list',
|
type: 'group-list',
|
||||||
name: 'hookData',
|
name: 'hookData',
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
expandAll: true,
|
expandAll: true,
|
||||||
addable: () => false,
|
|
||||||
title: (mForm, { model, index }: any) => {
|
title: (mForm, { model, index }: any) => {
|
||||||
if (model.codeType === HookCodeType.DATA_SOURCE_METHOD) {
|
if (model.codeType === HookCodeType.DATA_SOURCE_METHOD) {
|
||||||
if (Array.isArray(model.codeId)) {
|
if (Array.isArray(model.codeId)) {
|
||||||
@ -97,46 +81,48 @@ const codeConfig = computed<GroupListConfig>(() => ({
|
|||||||
|
|
||||||
return model.codeId || index;
|
return model.codeId || index;
|
||||||
},
|
},
|
||||||
titlePrefix: props.config.name === undefined ? undefined : String(props.config.name),
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
text: '代码类型',
|
type: 'row',
|
||||||
|
items: [
|
||||||
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'codeType',
|
name: 'codeType',
|
||||||
labelPosition: 'right',
|
span: 6,
|
||||||
rules: [{ typeMatch: true, trigger: 'change' }],
|
|
||||||
options: [
|
options: [
|
||||||
{ value: HookCodeType.CODE, text: '代码块' },
|
{ value: HookCodeType.CODE, text: '代码块' },
|
||||||
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
|
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
|
||||||
],
|
],
|
||||||
defaultValue: HookCodeType.CODE,
|
defaultValue: 'code',
|
||||||
onChange: (_mForm, v: HookCodeType, { setModel }) => {
|
onChange: (_mForm, v: HookCodeType, { setModel }) => {
|
||||||
if (v === HookCodeType.DATA_SOURCE_METHOD) {
|
if (v === HookCodeType.DATA_SOURCE_METHOD) {
|
||||||
setModel('codeId', []);
|
setModel('codeId', []);
|
||||||
} else {
|
} else {
|
||||||
setModel('codeId', '');
|
setModel('codeId', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'code-select-col',
|
type: 'code-select-col',
|
||||||
name: 'codeId',
|
name: 'codeId',
|
||||||
text: '代码块',
|
span: 18,
|
||||||
rules: [{ typeMatch: true, trigger: 'change' }],
|
labelWidth: 0,
|
||||||
|
|
||||||
display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
|
display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
|
||||||
notEditable: () => !codeBlockService.getEditStatus(),
|
notEditable: () => !codeBlockService.getEditStatus(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'data-source-method-select',
|
type: 'data-source-method-select',
|
||||||
name: 'codeId',
|
name: 'codeId',
|
||||||
text: '数据源字段',
|
span: 18,
|
||||||
rules: [{ typeMatch: true, trigger: 'change' }],
|
labelWidth: 0,
|
||||||
display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
|
display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
|
||||||
notEditable: () => !dataSourceService.get('editable'),
|
notEditable: () => !dataSourceService.get('editable'),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
|||||||
@ -107,12 +107,10 @@ const isCompareMode = computed(() => Boolean(props.isCompare && props.lastValues
|
|||||||
|
|
||||||
const notEditable = computed(() => filterFunction(mForm, props.config.notEditable, props));
|
const notEditable = computed(() => filterFunction(mForm, props.config.notEditable, props));
|
||||||
|
|
||||||
const codeBlockSidePanel = computed(() =>
|
const hasCodeBlockSidePanel = computed(() =>
|
||||||
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.CODE_BLOCK),
|
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.CODE_BLOCK),
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasCodeBlockSidePanel = computed(() => codeBlockSidePanel.value);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据代码块id获取代码块参数配置
|
* 根据代码块id获取代码块参数配置
|
||||||
* @param codeId 代码块ID
|
* @param codeId 代码块ID
|
||||||
@ -147,7 +145,6 @@ const selectConfig: SelectConfig = {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
name: props.name,
|
name: props.name,
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
text: props.config.text,
|
|
||||||
options: () => {
|
options: () => {
|
||||||
if (codeDsl.value) {
|
if (codeDsl.value) {
|
||||||
return map(codeDsl.value, (value, key) => ({
|
return map(codeDsl.value, (value, key) => ({
|
||||||
@ -194,10 +191,6 @@ const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData)
|
|||||||
};
|
};
|
||||||
|
|
||||||
const editCode = (id: string) => {
|
const editCode = (id: string) => {
|
||||||
const sideBarItem = codeBlockSidePanel.value;
|
|
||||||
if (sideBarItem) {
|
|
||||||
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.CODE_BLOCK);
|
|
||||||
}
|
|
||||||
eventBus?.emit('edit-code', id);
|
eventBus?.emit('edit-code', id);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -33,7 +33,7 @@ import { getDesignConfig, TMagicSelect } from '@tmagic/design';
|
|||||||
import type { CondOpSelectConfig, FieldProps } from '@tmagic/form';
|
import type { CondOpSelectConfig, FieldProps } from '@tmagic/form';
|
||||||
|
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import { getCondOpOptionsByFieldType, getFieldType } from '@editor/utils';
|
import { arrayOptions, eqOptions, getFieldType, numberOptions } from '@editor/utils';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFieldsCondOpSelect',
|
name: 'MFieldsCondOpSelect',
|
||||||
@ -51,9 +51,31 @@ const optionComponent = getDesignConfig('components')?.option;
|
|||||||
|
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
const [id, ...fieldNames] = [...(props.config.parentFields || []), ...props.model.field];
|
const [id, ...fieldNames] = [...(props.config.parentFields || []), ...props.model.field];
|
||||||
|
|
||||||
const ds = dataSourceService.getDataSourceById(id);
|
const ds = dataSourceService.getDataSourceById(id);
|
||||||
|
|
||||||
const type = getFieldType(ds, fieldNames);
|
const type = getFieldType(ds, fieldNames);
|
||||||
return getCondOpOptionsByFieldType(type);
|
|
||||||
|
if (type === 'array') {
|
||||||
|
return arrayOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'boolean' || type === 'null') {
|
||||||
|
return [
|
||||||
|
{ text: '是', value: 'is' },
|
||||||
|
{ text: '不是', value: 'not' },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'number') {
|
||||||
|
return [...eqOptions, ...numberOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'string') {
|
||||||
|
return [...arrayOptions, ...eqOptions];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...arrayOptions, ...eqOptions, ...numberOptions];
|
||||||
});
|
});
|
||||||
|
|
||||||
const fieldChangeHandler = (v: string) => {
|
const fieldChangeHandler = (v: string) => {
|
||||||
|
|||||||
@ -211,25 +211,11 @@ const onChangeHandler = (v: string[] = []) => {
|
|||||||
emit('change', v);
|
emit('change', v);
|
||||||
};
|
};
|
||||||
|
|
||||||
const dataSourceSidePanel = computed(() =>
|
const hasDataSourceSidePanel = computed(() =>
|
||||||
uiService.get('sideBarItems').find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
uiService.get('sideBarItems').find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasDataSourceSidePanel = computed(() => dataSourceSidePanel.value);
|
|
||||||
|
|
||||||
const editHandler = (id: string) => {
|
const editHandler = (id: string) => {
|
||||||
const sideBarItem = dataSourceSidePanel.value;
|
eventBus?.emit('edit-data-source', removeDataSourceFieldPrefix(id));
|
||||||
if (sideBarItem) {
|
|
||||||
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataSourceId = removeDataSourceFieldPrefix(id);
|
|
||||||
const fieldPath = selectFieldsId.value;
|
|
||||||
|
|
||||||
if (fieldPath.length) {
|
|
||||||
eventBus?.emit('edit-data-source-field', dataSourceId, [...fieldPath]);
|
|
||||||
} else {
|
|
||||||
eventBus?.emit('edit-data-source', dataSourceId);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div class="m-fields-data-source-field-select">
|
||||||
class="m-fields-data-source-field-select"
|
|
||||||
:class="{
|
|
||||||
[`data-source-field-${type}`]: !isSelectValid,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<FieldSelect
|
<FieldSelect
|
||||||
v-if="isSelectValid"
|
v-if="!disabledDataSource && (showDataSourceFieldSelect || !config.fieldConfig)"
|
||||||
:model-value="model[name]"
|
:model-value="model[name]"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:size="size"
|
:size="size"
|
||||||
@ -42,14 +37,15 @@
|
|||||||
:size="size"
|
:size="size"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click="onToggleDataSourceFieldSelectHandler"
|
@click="onToggleDataSourceFieldSelectHandler"
|
||||||
><MIcon :icon="dataSourceIcon"></MIcon>
|
><MIcon :icon="Coin"></MIcon
|
||||||
</TMagicButton>
|
></TMagicButton>
|
||||||
</TMagicTooltip>
|
</TMagicTooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, inject, ref, resolveComponent, watch } from 'vue';
|
import { computed, inject, ref, resolveComponent, watch } from 'vue';
|
||||||
|
import { Coin } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { DataSchema } from '@tmagic/core';
|
import { DataSchema } from '@tmagic/core';
|
||||||
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
import { TMagicButton, tMagicMessage, TMagicTooltip } from '@tmagic/design';
|
||||||
@ -65,8 +61,6 @@ import { DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX, removeDataSourceFieldPrefix } f
|
|||||||
import MIcon from '@editor/components/Icon.vue';
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
|
|
||||||
import dataSourceIcon from '../../icons/DatasourceIcon.vue';
|
|
||||||
|
|
||||||
import FieldSelect from './FieldSelect.vue';
|
import FieldSelect from './FieldSelect.vue';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
@ -81,9 +75,6 @@ const props = withDefaults(defineProps<FieldProps<DataSourceFieldSelectConfig>>(
|
|||||||
|
|
||||||
const showDataSourceFieldSelect = ref(false);
|
const showDataSourceFieldSelect = ref(false);
|
||||||
|
|
||||||
const isSelectValid = computed(
|
|
||||||
() => !disabledDataSource.value && (showDataSourceFieldSelect.value || !props.config.fieldConfig),
|
|
||||||
);
|
|
||||||
watch(
|
watch(
|
||||||
() => props.model[props.name],
|
() => props.model[props.name],
|
||||||
(value) => {
|
(value) => {
|
||||||
|
|||||||
@ -8,12 +8,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FloatingBox
|
<FloatingBox
|
||||||
:body-style="{ padding: '0 16px' }"
|
|
||||||
v-model:visible="addDialogVisible"
|
v-model:visible="addDialogVisible"
|
||||||
v-model:width="width"
|
v-model:width="width"
|
||||||
v-model:height="editorHeight"
|
v-model:height="editorHeight"
|
||||||
:title="fieldTitle"
|
:title="fieldTitle"
|
||||||
:framework-width="frameworkWidth"
|
|
||||||
:position="boxPosition"
|
:position="boxPosition"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
@ -30,12 +28,10 @@
|
|||||||
</FloatingBox>
|
</FloatingBox>
|
||||||
|
|
||||||
<FloatingBox
|
<FloatingBox
|
||||||
:body-style="{ padding: '0 16px' }"
|
|
||||||
v-model:visible="addFromJsonDialogVisible"
|
v-model:visible="addFromJsonDialogVisible"
|
||||||
v-model:width="width"
|
v-model:width="width"
|
||||||
v-model:height="editorHeight"
|
v-model:height="editorHeight"
|
||||||
title="快速添加数据定义"
|
title="快速添加数据定义"
|
||||||
:framework-width="frameworkWidth"
|
|
||||||
:position="boxPosition"
|
:position="boxPosition"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
@ -51,7 +47,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, type ComputedRef, inject, onMounted, provide, Ref, ref } from 'vue';
|
import { computed, inject, Ref, ref } from 'vue';
|
||||||
|
|
||||||
import type { DataSchema } from '@tmagic/core';
|
import type { DataSchema } from '@tmagic/core';
|
||||||
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
import { TMagicButton, tMagicMessage, tMagicMessageBox } from '@tmagic/design';
|
||||||
@ -105,16 +101,6 @@ const newHandler = () => {
|
|||||||
addDialogVisible.value = true;
|
addDialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const editField = (row: Record<string, any>, index: number) => {
|
|
||||||
fieldValues.value = {
|
|
||||||
...row,
|
|
||||||
index,
|
|
||||||
};
|
|
||||||
fieldTitle.value = `编辑${row.title}`;
|
|
||||||
calcBoxPosition();
|
|
||||||
addDialogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fieldChange = ({ index, ...value }: Record<string, any>, data: ContainerChangeEventData) => {
|
const fieldChange = ({ index, ...value }: Record<string, any>, data: ContainerChangeEventData) => {
|
||||||
addDialogVisible.value = false;
|
addDialogVisible.value = false;
|
||||||
|
|
||||||
@ -172,7 +158,13 @@ const fieldColumns: ColumnConfig[] = [
|
|||||||
{
|
{
|
||||||
text: '编辑',
|
text: '编辑',
|
||||||
handler: (row: Record<string, any>, index: number) => {
|
handler: (row: Record<string, any>, index: number) => {
|
||||||
editField(row, index);
|
fieldValues.value = {
|
||||||
|
...row,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
fieldTitle.value = `编辑${row.title}`;
|
||||||
|
calcBoxPosition();
|
||||||
|
addDialogVisible.value = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -362,30 +354,4 @@ const { height: editorHeight } = useEditorContentHeight();
|
|||||||
|
|
||||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的字段路径(字段名数组)。
|
|
||||||
* 当前层消费 path[0],并把剩余路径下发给嵌套字段,实现逐层打开。
|
|
||||||
*/
|
|
||||||
const editingFieldPath = inject<ComputedRef<string[]>>(
|
|
||||||
'editingDataSourceFieldPath',
|
|
||||||
computed(() => []),
|
|
||||||
);
|
|
||||||
|
|
||||||
provide(
|
|
||||||
'editingDataSourceFieldPath',
|
|
||||||
computed(() => editingFieldPath.value.slice(1)),
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const path = editingFieldPath.value;
|
|
||||||
if (!path.length) return;
|
|
||||||
|
|
||||||
const fields: Record<string, any>[] = props.model[props.name] || [];
|
|
||||||
const index = fields.findIndex((field) => field.name === path[0]);
|
|
||||||
if (index === -1) return;
|
|
||||||
|
|
||||||
editField(fields[index], index);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -30,10 +30,10 @@
|
|||||||
<template #suffix>
|
<template #suffix>
|
||||||
<Icon :icon="Coin" />
|
<Icon :icon="Coin" />
|
||||||
</template>
|
</template>
|
||||||
<template #default="slotProps">
|
<template #default="{ item }">
|
||||||
<div style="display: flex; flex-direction: column; line-height: 1.2em">
|
<div style="display: flex; flex-direction: column; line-height: 1.2em">
|
||||||
<div>{{ slotProps?.item?.text || '' }}</div>
|
<div>{{ item.text }}</div>
|
||||||
<span style="font-size: 10px; color: rgba(0, 0, 0, 0.6)">{{ slotProps?.item?.value || '' }}</span>
|
<span style="font-size: 10px; color: rgba(0, 0, 0, 0.6)">{{ item.value }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
@ -79,11 +79,11 @@ import { Coin } from '@element-plus/icons-vue';
|
|||||||
import type { DataSchema, DataSourceSchema } from '@tmagic/core';
|
import type { DataSchema, DataSourceSchema } from '@tmagic/core';
|
||||||
import { getDesignConfig, TMagicAutocomplete, TMagicInput, TMagicTag } from '@tmagic/design';
|
import { getDesignConfig, TMagicAutocomplete, TMagicInput, TMagicTag } from '@tmagic/design';
|
||||||
import type { DataSourceInputConfig, FieldProps } from '@tmagic/form';
|
import type { DataSourceInputConfig, FieldProps } from '@tmagic/form';
|
||||||
import { getKeysArray } from '@tmagic/utils';
|
import { getKeysArray, isNumber } from '@tmagic/utils';
|
||||||
|
|
||||||
import Icon from '@editor/components/Icon.vue';
|
import Icon from '@editor/components/Icon.vue';
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import { getDisplayField, resolveFieldByPath } from '@editor/utils/data-source';
|
import { getDisplayField } from '@editor/utils/data-source';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFieldsDataSourceInput',
|
name: 'MFieldsDataSourceInput',
|
||||||
@ -251,16 +251,34 @@ const fieldQuerySearch = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { fields } = resolveFieldByPath(ds.fields, keys, { skipNumberIndices: true });
|
let fields = ds.fields || [];
|
||||||
|
|
||||||
|
// 后面这些是字段
|
||||||
|
let key = keys.shift();
|
||||||
|
while (key) {
|
||||||
|
if (isNumber(key)) {
|
||||||
|
key = keys.shift();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const field of fields) {
|
||||||
|
if (field.name === key) {
|
||||||
|
fields = field.fields || [];
|
||||||
|
key = keys.shift();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (curCharIsDot(dotIndex)) {
|
if (curCharIsDot(dotIndex)) {
|
||||||
// 当前输入的是.
|
// 当前输入的是.
|
||||||
result = fields;
|
result = fields || [];
|
||||||
} else if (dotIndex > -1) {
|
} else if (dotIndex > -1) {
|
||||||
const queryName = queryString.substring(dotIndex + 1).toLowerCase();
|
const queryName = queryString.substring(dotIndex + 1).toLowerCase();
|
||||||
result = fields.filter(
|
result =
|
||||||
|
fields.filter(
|
||||||
(field) => field.name?.toLowerCase().includes(queryName) || field.title?.toLowerCase().includes(queryName),
|
(field) => field.name?.toLowerCase().includes(queryName) || field.title?.toLowerCase().includes(queryName),
|
||||||
);
|
) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(
|
cb(
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
></MCascader>
|
></MCascader>
|
||||||
|
|
||||||
<TMagicTooltip
|
<TMagicTooltip
|
||||||
v-if="model[name] && isCustomMethod && dataSourceSidePanel && !isCompare"
|
v-if="model[name] && isCustomMethod && hasDataSourceSidePanel && !isCompare"
|
||||||
:content="notEditable ? '查看' : '编辑'"
|
:content="notEditable ? '查看' : '编辑'"
|
||||||
>
|
>
|
||||||
<TMagicButton class="m-fields-select-action-button" :size="size" @click="editCodeHandler">
|
<TMagicButton class="m-fields-select-action-button" :size="size" @click="editCodeHandler">
|
||||||
@ -75,7 +75,7 @@ const props = withDefaults(defineProps<FieldProps<DataSourceMethodSelectConfig>>
|
|||||||
disabled: false,
|
disabled: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
const dataSourceSidePanel = computed(() =>
|
const hasDataSourceSidePanel = computed(() =>
|
||||||
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
(uiService.get('sideBarItems') || []).find((item) => item.$key === SideItemKey.DATA_SOURCE),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -208,17 +208,12 @@ const onParamsChangeHandler = (value: any, eventData: ContainerChangeEventData)
|
|||||||
};
|
};
|
||||||
|
|
||||||
const editCodeHandler = () => {
|
const editCodeHandler = () => {
|
||||||
const [id, methodName] = props.model[props.name];
|
const [id] = props.model[props.name];
|
||||||
|
|
||||||
const dataSource = dataSourceService.getDataSourceById(id);
|
const dataSource = dataSourceService.getDataSourceById(id);
|
||||||
|
|
||||||
if (!dataSource) return;
|
if (!dataSource) return;
|
||||||
|
|
||||||
const sideBarItem = dataSourceSidePanel.value;
|
eventBus?.emit('edit-data-source', id);
|
||||||
if (sideBarItem) {
|
|
||||||
uiService.set('sideBarActiveTabName', sideBarItem.text || sideBarItem.$key || SideItemKey.DATA_SOURCE);
|
|
||||||
}
|
|
||||||
|
|
||||||
eventBus?.emit('edit-data-source-method', id, methodName);
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, type ComputedRef, inject, nextTick, onMounted, ref, useTemplateRef } from 'vue';
|
import { computed, inject, nextTick, ref, useTemplateRef } from 'vue';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import type { CodeBlockContent } from '@tmagic/core';
|
import type { CodeBlockContent } from '@tmagic/core';
|
||||||
@ -52,29 +52,6 @@ const codeBlockEditorRef = useTemplateRef<InstanceType<typeof CodeBlockEditor>>(
|
|||||||
|
|
||||||
let editIndex = -1;
|
let editIndex = -1;
|
||||||
|
|
||||||
const editMethod = (method: CodeBlockContent, index: number) => {
|
|
||||||
let codeContent: string = '({ params, dataSource, app }) => {\n // place your code here\n}';
|
|
||||||
|
|
||||||
if (method.content) {
|
|
||||||
if (typeof method.content !== 'string') {
|
|
||||||
codeContent = method.content.toString();
|
|
||||||
} else {
|
|
||||||
codeContent = method.content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
codeConfig.value = {
|
|
||||||
...cloneDeep(method),
|
|
||||||
content: codeContent,
|
|
||||||
};
|
|
||||||
|
|
||||||
editIndex = index;
|
|
||||||
|
|
||||||
nextTick(() => {
|
|
||||||
codeBlockEditorRef.value?.show();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const methodColumns: ColumnConfig[] = [
|
const methodColumns: ColumnConfig[] = [
|
||||||
{
|
{
|
||||||
label: '名称',
|
label: '名称',
|
||||||
@ -100,7 +77,26 @@ const methodColumns: ColumnConfig[] = [
|
|||||||
{
|
{
|
||||||
text: '编辑',
|
text: '编辑',
|
||||||
handler: (method: CodeBlockContent, index: number) => {
|
handler: (method: CodeBlockContent, index: number) => {
|
||||||
editMethod(method, index);
|
let codeContent: string = '({ params, dataSource, app }) => {\n // place your code here\n}';
|
||||||
|
|
||||||
|
if (method.content) {
|
||||||
|
if (typeof method.content !== 'string') {
|
||||||
|
codeContent = method.content.toString();
|
||||||
|
} else {
|
||||||
|
codeContent = method.content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
codeConfig.value = {
|
||||||
|
...cloneDeep(method),
|
||||||
|
content: codeContent,
|
||||||
|
};
|
||||||
|
|
||||||
|
editIndex = index;
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
codeBlockEditorRef.value?.show();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -162,21 +158,4 @@ const submitCodeHandler = (value: CodeBlockContent, data: ContainerChangeEventDa
|
|||||||
|
|
||||||
codeBlockEditorRef.value?.hide();
|
codeBlockEditorRef.value?.hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 由 DataSourceConfigPanel 注入:打开数据源详情后需要直接打开的方法名 */
|
|
||||||
const editingMethodName = inject<ComputedRef<string | undefined>>(
|
|
||||||
'editingDataSourceMethodName',
|
|
||||||
computed(() => ''),
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const methodName = editingMethodName.value;
|
|
||||||
if (!methodName) return;
|
|
||||||
|
|
||||||
const methods: CodeBlockContent[] = props.model[props.name] || [];
|
|
||||||
const index = methods.findIndex((method) => method.name === methodName);
|
|
||||||
if (index === -1) return;
|
|
||||||
|
|
||||||
editMethod(methods[index], index);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -7,12 +7,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FloatingBox
|
<FloatingBox
|
||||||
:body-style="{ padding: '0 16px' }"
|
|
||||||
v-model:visible="addDialogVisible"
|
v-model:visible="addDialogVisible"
|
||||||
v-model:width="width"
|
v-model:width="width"
|
||||||
v-model:height="editorHeight"
|
v-model:height="editorHeight"
|
||||||
:title="drawerTitle"
|
:title="drawerTitle"
|
||||||
:framework-width="frameworkWidth"
|
|
||||||
:position="boxPosition"
|
:position="boxPosition"
|
||||||
>
|
>
|
||||||
<template #body>
|
<template #body>
|
||||||
@ -256,5 +254,4 @@ const addDialogVisible = defineModel<boolean>('visible', { default: false });
|
|||||||
const { height: editorHeight } = useEditorContentHeight();
|
const { height: editorHeight } = useEditorContentHeight();
|
||||||
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
|
||||||
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating);
|
||||||
const frameworkWidth = computed(() => uiService.get('frameworkRect')?.width || 0);
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -68,15 +68,12 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
titlePrefix: props.config.titlePrefix,
|
titlePrefix: props.config.titlePrefix,
|
||||||
expandAll: true,
|
expandAll: true,
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
flat: props.config.flat,
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'table',
|
type: 'table',
|
||||||
name: 'cond',
|
name: 'cond',
|
||||||
operateColWidth: props.config.operateColWidth,
|
operateColWidth: 80,
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
fixed: props.config.fixed,
|
|
||||||
flat: props.config.flat,
|
|
||||||
items: [
|
items: [
|
||||||
parentFields.value.length
|
parentFields.value.length
|
||||||
? {
|
? {
|
||||||
@ -101,11 +98,6 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
label: '字段',
|
label: '字段',
|
||||||
checkStrictly: false,
|
checkStrictly: false,
|
||||||
onChange: fieldOnChange,
|
onChange: fieldOnChange,
|
||||||
defaultValue: () => [],
|
|
||||||
rules: [
|
|
||||||
{ required: true, trigger: 'blur', message: '请选择字段' },
|
|
||||||
{ typeMatch: true, trigger: 'change' },
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type: 'data-source-field-select',
|
type: 'data-source-field-select',
|
||||||
@ -115,11 +107,6 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
checkStrictly: false,
|
checkStrictly: false,
|
||||||
dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
|
dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
|
||||||
onChange: fieldOnChange,
|
onChange: fieldOnChange,
|
||||||
defaultValue: () => [],
|
|
||||||
rules: [
|
|
||||||
{ required: true, trigger: 'blur', message: '请选择字段' },
|
|
||||||
{ typeMatch: true, trigger: 'change' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'cond-op-select',
|
type: 'cond-op-select',
|
||||||
@ -127,10 +114,6 @@ const config = computed<GroupListConfig>(() => ({
|
|||||||
label: '条件',
|
label: '条件',
|
||||||
width: 140,
|
width: 140,
|
||||||
name: 'op',
|
name: 'op',
|
||||||
rules: [
|
|
||||||
{ required: true, trigger: 'blur', message: '请选择条件' },
|
|
||||||
{ typeMatch: true, trigger: 'change' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '值',
|
label: '值',
|
||||||
|
|||||||
@ -12,21 +12,16 @@
|
|||||||
@change="onChangeHandler"
|
@change="onChangeHandler"
|
||||||
></MTable>
|
></MTable>
|
||||||
|
|
||||||
<div v-else class="fullWidth event-select-container">
|
<div v-else class="fullWidth">
|
||||||
<div class="event-select-header">
|
|
||||||
<div class="event-select-title">事件配置</div>
|
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
v-if="!isCompareMode && displayList.length > 0"
|
v-if="!isCompareMode"
|
||||||
class="create-button"
|
class="create-button"
|
||||||
text
|
|
||||||
type="primary"
|
type="primary"
|
||||||
:icon="Plus"
|
|
||||||
:size="size"
|
:size="size"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
@click="addEvent()"
|
@click="addEvent()"
|
||||||
>添加事件</TMagicButton
|
>添加事件</TMagicButton
|
||||||
>
|
>
|
||||||
</div>
|
|
||||||
<MPanel
|
<MPanel
|
||||||
v-for="entry in displayList"
|
v-for="entry in displayList"
|
||||||
:key="entry.index"
|
:key="entry.index"
|
||||||
@ -37,13 +32,10 @@
|
|||||||
:model="entry.cardItem"
|
:model="entry.cardItem"
|
||||||
:last-values="entry.lastCardItem"
|
:last-values="entry.lastCardItem"
|
||||||
:is-compare="isCompareMode"
|
:is-compare="isCompareMode"
|
||||||
:hide-expand="true"
|
|
||||||
:label-width="config.labelWidth || '100px'"
|
:label-width="config.labelWidth || '100px'"
|
||||||
@change="onChangeHandler"
|
@change="onChangeHandler"
|
||||||
>
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="event-item-header">
|
|
||||||
<div class="event-item-title">事件{{ Number(entry.index) + 1 }}</div>
|
|
||||||
<MFormContainer
|
<MFormContainer
|
||||||
class="fullWidth"
|
class="fullWidth"
|
||||||
:config="eventNameConfig"
|
:config="eventNameConfig"
|
||||||
@ -56,26 +48,16 @@
|
|||||||
@change="eventNameChangeHandler"
|
@change="eventNameChangeHandler"
|
||||||
></MFormContainer>
|
></MFormContainer>
|
||||||
<TMagicButton
|
<TMagicButton
|
||||||
class="event-item-delete-button"
|
|
||||||
v-if="!isCompareMode"
|
v-if="!isCompareMode"
|
||||||
|
style="color: #f56c6c"
|
||||||
link
|
link
|
||||||
:icon="Delete"
|
:icon="Delete"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:size="size"
|
:size="size"
|
||||||
@click="removeEvent(Number(entry.index))"
|
@click="removeEvent(Number(entry.index))"
|
||||||
></TMagicButton>
|
></TMagicButton>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</MPanel>
|
</MPanel>
|
||||||
|
|
||||||
<TMagicButton
|
|
||||||
v-if="!isCompareMode"
|
|
||||||
class="create-button fullWidth"
|
|
||||||
:icon="Plus"
|
|
||||||
:disabled="disabled"
|
|
||||||
@click="addEvent()"
|
|
||||||
>添加事件</TMagicButton
|
|
||||||
>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -83,12 +65,13 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
import { Delete } from '@element-plus/icons-vue';
|
import { Delete } from '@element-plus/icons-vue';
|
||||||
import { Plus } from '@element-plus/icons-vue';
|
|
||||||
import { has } from 'lodash-es';
|
import { has } from 'lodash-es';
|
||||||
|
|
||||||
|
import type { EventOption, MComponent, MContainer } from '@tmagic/core';
|
||||||
import { ActionType } from '@tmagic/core';
|
import { ActionType } from '@tmagic/core';
|
||||||
import { TMagicButton } from '@tmagic/design';
|
import { TMagicButton } from '@tmagic/design';
|
||||||
import type {
|
import type {
|
||||||
|
CascaderOption,
|
||||||
CodeSelectColConfig,
|
CodeSelectColConfig,
|
||||||
ContainerChangeEventData,
|
ContainerChangeEventData,
|
||||||
DataSourceMethodSelectConfig,
|
DataSourceMethodSelectConfig,
|
||||||
@ -101,15 +84,10 @@ import type {
|
|||||||
UISelectConfig,
|
UISelectConfig,
|
||||||
} from '@tmagic/form';
|
} from '@tmagic/form';
|
||||||
import { defineFormItem, MContainer as MFormContainer, MPanel, MTable } from '@tmagic/form';
|
import { defineFormItem, MContainer as MFormContainer, MPanel, MTable } from '@tmagic/form';
|
||||||
|
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX, traverseNode } from '@tmagic/utils';
|
||||||
|
|
||||||
import { useServices } from '@editor/hooks/use-services';
|
import { useServices } from '@editor/hooks/use-services';
|
||||||
import {
|
import { getCascaderOptionsFromFields } from '@editor/utils';
|
||||||
getCompActionAllowedValues,
|
|
||||||
getCompActionOptions,
|
|
||||||
getEventNameAllowedValues,
|
|
||||||
getEventNameOptions,
|
|
||||||
normalizeCompActionValue,
|
|
||||||
} from '@editor/utils';
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFieldsEventSelect',
|
name: 'MFieldsEventSelect',
|
||||||
@ -127,7 +105,7 @@ const { editorService, dataSourceService, eventsService, codeBlockService, props
|
|||||||
const eventNameConfig = computed(() => {
|
const eventNameConfig = computed(() => {
|
||||||
const defaultEventNameConfig = {
|
const defaultEventNameConfig = {
|
||||||
name: 'name',
|
name: 'name',
|
||||||
text: '事件类型',
|
text: '事件',
|
||||||
type: (mForm: FormState | undefined, { formValue }: any) => {
|
type: (mForm: FormState | undefined, { formValue }: any) => {
|
||||||
if (
|
if (
|
||||||
props.config.src !== 'component' ||
|
props.config.src !== 'component' ||
|
||||||
@ -137,27 +115,79 @@ const eventNameConfig = computed(() => {
|
|||||||
}
|
}
|
||||||
return 'select';
|
return 'select';
|
||||||
},
|
},
|
||||||
labelWidth: '70px',
|
labelWidth: '40px',
|
||||||
checkStrictly: () => props.config.src !== 'component',
|
checkStrictly: () => props.config.src !== 'component',
|
||||||
valueSeparator: '.',
|
valueSeparator: '.',
|
||||||
options: (_mForm: FormState, { formValue }: any) => getEventNameOptions(props.config.src, formValue),
|
options: (mForm: FormState, { formValue }: any) => {
|
||||||
rules: [
|
let events: EventOption[] | CascaderOption[] = [];
|
||||||
|
|
||||||
|
if (props.config.src === 'component') {
|
||||||
|
events = eventsService.getEvent(formValue.type);
|
||||||
|
|
||||||
|
if (formValue.type === 'page-fragment-container' && formValue.pageFragmentId) {
|
||||||
|
const pageFragment = editorService.get('root')?.items?.find((page) => page.id === formValue.pageFragmentId);
|
||||||
|
if (pageFragment) {
|
||||||
|
events = [
|
||||||
{
|
{
|
||||||
validator: ({ value, callback }: any, { formValue }: any) => {
|
label: pageFragment.name || '页面片容器',
|
||||||
const allowedNames = getEventNameAllowedValues(props.config as any, formValue);
|
value: pageFragment.id,
|
||||||
if (allowedNames && allowedNames.size > 0 && value && !allowedNames.has(value)) {
|
children: events,
|
||||||
return callback(`事件名(${value})不存在`);
|
},
|
||||||
|
];
|
||||||
|
pageFragment.items.forEach((node) => {
|
||||||
|
traverseNode<MComponent | MContainer>(node, (node) => {
|
||||||
|
const nodeEvents = (node.type && eventsService.getEvent(node.type)) || [];
|
||||||
|
|
||||||
|
events.push({
|
||||||
|
label: `${node.name}_${node.id}`,
|
||||||
|
value: `${node.id}`,
|
||||||
|
children: nodeEvents,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return events;
|
||||||
}
|
}
|
||||||
callback();
|
}
|
||||||
|
|
||||||
|
return events.map((option) => ({
|
||||||
|
text: option.label,
|
||||||
|
value: option.value,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.config.src === 'datasource') {
|
||||||
|
// 从数据源类型中获取到相关事件
|
||||||
|
events = dataSourceService.getFormEvent(formValue.type);
|
||||||
|
// 从数据源类型和实例中分别获取数据以追加数据变化的事件
|
||||||
|
const dataSource = dataSourceService.getDataSourceById(formValue.id);
|
||||||
|
const fields = dataSource?.fields || [];
|
||||||
|
if (fields.length > 0) {
|
||||||
|
return [
|
||||||
|
...events,
|
||||||
|
{
|
||||||
|
label: '数据变化',
|
||||||
|
value: DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX,
|
||||||
|
children: getCascaderOptionsFromFields(fields),
|
||||||
},
|
},
|
||||||
trigger: 'blur',
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return events;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
],
|
|
||||||
};
|
};
|
||||||
return { ...defaultEventNameConfig, ...props.config.eventNameConfig };
|
return { ...defaultEventNameConfig, ...props.config.eventNameConfig };
|
||||||
});
|
});
|
||||||
|
|
||||||
const actionTypeOptions = computed(() => {
|
// 联动类型
|
||||||
|
const actionTypeConfig = computed(() => {
|
||||||
|
const defaultActionTypeConfig = {
|
||||||
|
name: 'actionType',
|
||||||
|
text: '联动类型',
|
||||||
|
type: 'select',
|
||||||
|
defaultValue: ActionType.COMP,
|
||||||
|
options: () => {
|
||||||
const o: {
|
const o: {
|
||||||
text: string;
|
text: string;
|
||||||
label: string;
|
label: string;
|
||||||
@ -189,27 +219,7 @@ const actionTypeOptions = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return o;
|
return o;
|
||||||
});
|
|
||||||
|
|
||||||
// 联动类型
|
|
||||||
const actionTypeConfig = computed(() => {
|
|
||||||
const defaultActionTypeConfig = {
|
|
||||||
name: 'actionType',
|
|
||||||
text: '联动类型',
|
|
||||||
type: 'select',
|
|
||||||
labelPosition: 'left',
|
|
||||||
defaultValue: ActionType.COMP,
|
|
||||||
options: actionTypeOptions.value,
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '联动类型不能为空',
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
typeMatch: true,
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
return { ...defaultActionTypeConfig, ...props.config.actionTypeConfig };
|
return { ...defaultActionTypeConfig, ...props.config.actionTypeConfig };
|
||||||
});
|
});
|
||||||
@ -220,17 +230,10 @@ const targetCompConfig = computed(() => {
|
|||||||
name: 'to',
|
name: 'to',
|
||||||
text: '联动组件',
|
text: '联动组件',
|
||||||
type: 'ui-select',
|
type: 'ui-select',
|
||||||
labelPosition: 'left',
|
|
||||||
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
display: (_mForm, { model }) => model.actionType === ActionType.COMP,
|
||||||
onChange: (_MForm, _v, { setModel }) => {
|
onChange: (_MForm, _v, { setModel }) => {
|
||||||
setModel('method', '');
|
setModel('method', '');
|
||||||
},
|
},
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
typeMatch: true,
|
|
||||||
trigger: 'blur',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
|
return { ...defaultTargetCompConfig, ...props.config.targetCompConfig };
|
||||||
});
|
});
|
||||||
@ -240,7 +243,6 @@ const compActionConfig = computed(() => {
|
|||||||
const defaultCompActionConfig: DynamicTypeConfig = {
|
const defaultCompActionConfig: DynamicTypeConfig = {
|
||||||
name: 'method',
|
name: 'method',
|
||||||
text: '动作',
|
text: '动作',
|
||||||
labelPosition: 'left',
|
|
||||||
type: (mForm: FormState | undefined, { model }: any) => {
|
type: (mForm: FormState | undefined, { model }: any) => {
|
||||||
const to = editorService.getNodeById(model.to);
|
const to = editorService.getNodeById(model.to);
|
||||||
|
|
||||||
@ -252,20 +254,41 @@ const compActionConfig = computed(() => {
|
|||||||
},
|
},
|
||||||
checkStrictly: () => props.config.src !== 'component',
|
checkStrictly: () => props.config.src !== 'component',
|
||||||
display: (mForm: FormState | undefined, { model }: any) => model.actionType === ActionType.COMP,
|
display: (mForm: FormState | undefined, { model }: any) => model.actionType === ActionType.COMP,
|
||||||
options: (_mForm: FormState, { model }: any) => getCompActionOptions(model.to),
|
options: (mForm: FormState, { model }: any) => {
|
||||||
rules: [
|
const node = editorService.getNodeById(model.to);
|
||||||
{
|
if (!node?.type) return [];
|
||||||
trigger: 'blur',
|
|
||||||
validator: ({ value, callback }: any, { model }: any) => {
|
let methods: EventOption[] | CascaderOption[] = [];
|
||||||
const allowedMethods = getCompActionAllowedValues(props.config as any, model);
|
|
||||||
const normalized = normalizeCompActionValue(value);
|
methods = eventsService.getMethod(node.type, model.to);
|
||||||
if (allowedMethods && allowedMethods.size > 0 && normalized && !allowedMethods.has(normalized)) {
|
|
||||||
return callback(`动作名(${normalized})不存在`);
|
if (node.type === 'page-fragment-container' && node.pageFragmentId) {
|
||||||
|
const pageFragment = editorService.get('root')?.items?.find((page) => page.id === node.pageFragmentId);
|
||||||
|
if (pageFragment) {
|
||||||
|
methods = [];
|
||||||
|
pageFragment.items.forEach((node: MComponent | MContainer) => {
|
||||||
|
traverseNode<MComponent | MContainer>(node, (node) => {
|
||||||
|
const nodeMethods = (node.type && eventsService.getMethod(node.type, node.id)) || [];
|
||||||
|
|
||||||
|
if (nodeMethods.length) {
|
||||||
|
methods.push({
|
||||||
|
label: `${node.name}_${node.id}`,
|
||||||
|
value: `${node.id}`,
|
||||||
|
children: nodeMethods,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
callback();
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return methods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return methods.map((method) => ({
|
||||||
|
text: method.label,
|
||||||
|
value: method.value,
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
return { ...defaultCompActionConfig, ...props.config.compActionConfig };
|
return { ...defaultCompActionConfig, ...props.config.compActionConfig };
|
||||||
});
|
});
|
||||||
@ -339,7 +362,6 @@ const actionsConfig = computed(
|
|||||||
() =>
|
() =>
|
||||||
defineFormItem({
|
defineFormItem({
|
||||||
type: 'panel',
|
type: 'panel',
|
||||||
labelPosition: 'left',
|
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
type: 'group-list',
|
type: 'group-list',
|
||||||
@ -347,7 +369,6 @@ const actionsConfig = computed(
|
|||||||
expandAll: true,
|
expandAll: true,
|
||||||
enableToggleMode: false,
|
enableToggleMode: false,
|
||||||
titlePrefix: '动作',
|
titlePrefix: '动作',
|
||||||
labelPosition: 'left',
|
|
||||||
items: [
|
items: [
|
||||||
actionTypeConfig.value,
|
actionTypeConfig.value,
|
||||||
targetCompConfig.value,
|
targetCompConfig.value,
|
||||||
|
|||||||
@ -2,12 +2,11 @@
|
|||||||
<TMagicCollapse class="m-fields-style-setter" v-model="collapseValue">
|
<TMagicCollapse class="m-fields-style-setter" v-model="collapseValue">
|
||||||
<template v-for="(item, index) in list" :key="index">
|
<template v-for="(item, index) in list" :key="index">
|
||||||
<TMagicCollapseItem :name="`${index}`">
|
<TMagicCollapseItem :name="`${index}`">
|
||||||
<template #title>{{ item.title }}</template>
|
<template #title><MIcon :icon="Grid"></MIcon>{{ item.title }}</template>
|
||||||
<component
|
<component
|
||||||
v-if="item.component"
|
v-if="item.component"
|
||||||
:is="item.component"
|
:is="item.component"
|
||||||
:values="model[name]"
|
:values="model[name]"
|
||||||
:prop="prop || name"
|
|
||||||
:last-values="lastValues?.[name]"
|
:last-values="lastValues?.[name]"
|
||||||
:is-compare="isCompare"
|
:is-compare="isCompare"
|
||||||
:size="size"
|
:size="size"
|
||||||
@ -22,18 +21,21 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { shallowRef } from 'vue';
|
import { shallowRef } from 'vue';
|
||||||
|
import { Grid } from '@element-plus/icons-vue';
|
||||||
|
|
||||||
import { TMagicCollapse, TMagicCollapseItem } from '@tmagic/design';
|
import { TMagicCollapse, TMagicCollapseItem } from '@tmagic/design';
|
||||||
import type { ContainerChangeEventData, FieldProps } from '@tmagic/form';
|
import type { ContainerChangeEventData, FieldProps } from '@tmagic/form';
|
||||||
import type { StyleSchema } from '@tmagic/schema';
|
import type { StyleSchema } from '@tmagic/schema';
|
||||||
|
|
||||||
|
import MIcon from '@editor/components/Icon.vue';
|
||||||
|
|
||||||
import { Background, Border, Font, Layout, Position, Transform } from './pro/';
|
import { Background, Border, Font, Layout, Position, Transform } from './pro/';
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'MFieldsStyleSetter',
|
name: 'MFieldsStyleSetter',
|
||||||
});
|
});
|
||||||
|
|
||||||
defineProps<FieldProps<StyleSchema>>();
|
const props = defineProps<FieldProps<StyleSchema>>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
change: [v: any, eventData: ContainerChangeEventData];
|
change: [v: any, eventData: ContainerChangeEventData];
|
||||||
@ -75,6 +77,13 @@ const collapseValue = shallowRef(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const change = (v: any, eventData: ContainerChangeEventData) => {
|
const change = (v: any, eventData: ContainerChangeEventData) => {
|
||||||
|
eventData.changeRecords?.forEach((record) => {
|
||||||
|
if (props.prop) {
|
||||||
|
record.propPath = `${props.prop}.${record.propPath}`;
|
||||||
|
} else if (props.name) {
|
||||||
|
record.propPath = `${props.name}.${record.propPath}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
emit('change', v, eventData);
|
emit('change', v, eventData);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,31 +4,27 @@
|
|||||||
<div class="border-icon-container-row">
|
<div class="border-icon-container-row">
|
||||||
<div
|
<div
|
||||||
class="border-icon border-icon-top"
|
class="border-icon border-icon-top"
|
||||||
:class="{ active: direction === 'Top', configured: isConfigured('Top') }"
|
:class="{ active: direction === 'Top' }"
|
||||||
@click="selectDirection('Top')"
|
@click="selectDirection('Top')"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-icon-container-row">
|
<div class="border-icon-container-row">
|
||||||
<div
|
<div
|
||||||
class="border-icon border-icon-left"
|
class="border-icon border-icon-left"
|
||||||
:class="{ active: direction === 'Left', configured: isConfigured('Left') }"
|
:class="{ active: direction === 'Left' }"
|
||||||
@click="selectDirection('Left')"
|
@click="selectDirection('Left')"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div class="border-icon" :class="{ active: direction === '' }" @click="selectDirection()"></div>
|
||||||
class="border-icon center"
|
|
||||||
:class="{ active: direction === '', configured: isConfigured('') }"
|
|
||||||
@click="selectDirection()"
|
|
||||||
></div>
|
|
||||||
<div
|
<div
|
||||||
class="border-icon border-icon-right"
|
class="border-icon border-icon-right"
|
||||||
:class="{ active: direction === 'Right', configured: isConfigured('Right') }"
|
:class="{ active: direction === 'Right' }"
|
||||||
@click="selectDirection('Right')"
|
@click="selectDirection('Right')"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-icon-container-row">
|
<div class="border-icon-container-row">
|
||||||
<div
|
<div
|
||||||
class="border-icon border-icon-bottom"
|
class="border-icon border-icon-bottom"
|
||||||
:class="{ active: direction === 'Bottom', configured: isConfigured('Bottom') }"
|
:class="{ active: direction === 'Bottom' }"
|
||||||
@click="selectDirection('Bottom')"
|
@click="selectDirection('Bottom')"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@ -102,7 +98,7 @@ const emit = defineEmits<{
|
|||||||
addDiffCount: [];
|
addDiffCount: [];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = withDefaults(
|
withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
model: FormValue;
|
model: FormValue;
|
||||||
lastValues?: FormValue;
|
lastValues?: FormValue;
|
||||||
@ -113,11 +109,6 @@ const props = withDefaults(
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasValue = (value: unknown) => value !== undefined && value !== null && value !== '';
|
|
||||||
|
|
||||||
const isConfigured = (d: string) =>
|
|
||||||
['Width', 'Color', 'Style'].some((key) => hasValue(props.model?.[`border${d}${key}`]));
|
|
||||||
|
|
||||||
const change = (value: StyleSchema, eventData: ContainerChangeEventData) => {
|
const change = (value: StyleSchema, eventData: ContainerChangeEventData) => {
|
||||||
eventData.changeRecords?.forEach((record) => {
|
eventData.changeRecords?.forEach((record) => {
|
||||||
emit('change', record.value, {
|
emit('change', record.value, {
|
||||||
|
|||||||
@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M12 4C12.5523 4 13 4.44772 13 5V11C13 11.5523 12.5523 12 12 12H4C3.44772 12 3 11.5523 3 11V5C3 4.44772 3.44772 4 4 4H12ZM4.5 5C4.22386 5 4 5.22386 4 5.5V10.5C4 10.7761 4.22386 11 4.5 11H11.5C11.7761 11 12 10.7761 12 10.5V5.5C12 5.22386 11.7761 5 11.5 5H4.5Z"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="13.75"
|
|
||||||
y="7.75"
|
|
||||||
width="0.5"
|
|
||||||
height="11.5"
|
|
||||||
rx="0.25"
|
|
||||||
transform="rotate(90 13.75 7.75)"
|
|
||||||
stroke="black"
|
|
||||||
stroke-opacity="0.3"
|
|
||||||
style="stroke: black; stroke-opacity: 0.3"
|
|
||||||
stroke-width="0.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M12 3C12.5523 3 13 3.44772 13 4V10C13 10.5523 12.5523 11 12 11H4C3.44772 11 3 10.5523 3 10V4C3 3.44772 3.44772 3 4 3H12ZM4.5 4C4.22386 4 4 4.22386 4 4.5V9.5C4 9.77614 4.22386 10 4.5 10H11.5C11.7761 10 12 9.77614 12 9.5V4.5C12 4.22386 11.7761 4 11.5 4H4.5Z"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="13.75"
|
|
||||||
y="12.25"
|
|
||||||
width="0.5"
|
|
||||||
height="11.5"
|
|
||||||
rx="0.25"
|
|
||||||
transform="rotate(90 13.75 12.25)"
|
|
||||||
stroke="black"
|
|
||||||
stroke-opacity="0.3"
|
|
||||||
style="stroke: black; stroke-opacity: 0.3"
|
|
||||||
stroke-width="0.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M12 5C12.5523 5 13 5.44772 13 6V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V6C3 5.44772 3.44772 5 4 5H12ZM4.5 6C4.22386 6 4 6.22386 4 6.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V6.5C12 6.22386 11.7761 6 11.5 6H4.5Z"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="13.75"
|
|
||||||
y="3.25"
|
|
||||||
width="0.5"
|
|
||||||
height="11.5"
|
|
||||||
rx="0.25"
|
|
||||||
transform="rotate(90 13.75 3.25)"
|
|
||||||
stroke="white"
|
|
||||||
stroke-opacity="0.3"
|
|
||||||
style="stroke: white; stroke-opacity: 0.3"
|
|
||||||
stroke-width="0.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M12 8C12.5523 8 13 8.44772 13 9V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V9C3 8.44772 3.44772 8 4 8H12ZM4.5 9C4.22386 9 4 9.22386 4 9.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V9.5C12 9.22386 11.7761 9 11.5 9H4.5Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12 2C12.5523 2 13 2.44772 13 3V6C13 6.55228 12.5523 7 12 7H4C3.44772 7 3 6.55228 3 6V3C3 2.44772 3.44772 2 4 2H12ZM4.5 3C4.22386 3 4 3.22386 4 3.5V5.5C4 5.77614 4.22386 6 4.5 6H11.5C11.7761 6 12 5.77614 12 5.5V3.5C12 3.22386 11.7761 3 11.5 3H4.5Z"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="13.75"
|
|
||||||
y="10.25"
|
|
||||||
width="0.5"
|
|
||||||
height="11.5"
|
|
||||||
rx="0.25"
|
|
||||||
transform="rotate(90 13.75 10.25)"
|
|
||||||
stroke="black"
|
|
||||||
stroke-opacity="0.3"
|
|
||||||
style="stroke: black; stroke-opacity: 0.3"
|
|
||||||
stroke-width="0.5"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="13.75"
|
|
||||||
y="4.25"
|
|
||||||
width="0.5"
|
|
||||||
height="11.5"
|
|
||||||
rx="0.25"
|
|
||||||
transform="rotate(90 13.75 4.25)"
|
|
||||||
stroke="black"
|
|
||||||
stroke-opacity="0.3"
|
|
||||||
style="stroke: black; stroke-opacity: 0.3"
|
|
||||||
stroke-width="0.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M12 9C12.5523 9 13 9.44772 13 10V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V10C3 9.44772 3.44772 9 4 9H12ZM4.5 10C4.22386 10 4 10.2239 4 10.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V10.5C12 10.2239 11.7761 10 11.5 10H4.5Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M12 3C12.5523 3 13 3.44772 13 4V6C13 6.55228 12.5523 7 12 7H4C3.44772 7 3 6.55228 3 6V4C3 3.44772 3.44772 3 4 3H12ZM4.5 4C4.22386 4 4 4.22386 4 4.5V5.5C4 5.77614 4.22386 6 4.5 6H11.5C11.7761 6 12 5.77614 12 5.5V4.5C12 4.22386 11.7761 4 11.5 4H4.5Z"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="13.75"
|
|
||||||
y="14.25"
|
|
||||||
width="0.5"
|
|
||||||
height="11.5"
|
|
||||||
rx="0.25"
|
|
||||||
transform="rotate(90 13.75 14.25)"
|
|
||||||
stroke="black"
|
|
||||||
stroke-opacity="0.3"
|
|
||||||
style="stroke: black; stroke-opacity: 0.3"
|
|
||||||
stroke-width="0.5"
|
|
||||||
/>
|
|
||||||
<rect
|
|
||||||
x="13.75"
|
|
||||||
y="1.25"
|
|
||||||
width="0.5"
|
|
||||||
height="11.5"
|
|
||||||
rx="0.25"
|
|
||||||
transform="rotate(90 13.75 1.25)"
|
|
||||||
stroke="black"
|
|
||||||
stroke-opacity="0.3"
|
|
||||||
style="stroke: black; stroke-opacity: 0.3"
|
|
||||||
stroke-width="0.5"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
export { default as AlignItemsCenter } from './Center.vue';
|
|
||||||
export { default as AlignItemsFlexEnd } from './FlexEnd.vue';
|
|
||||||
export { default as AlignItemsFlexStart } from './FlexStart.vue';
|
|
||||||
export { default as AlignItemsSpaceAround } from './SpaceAround.vue';
|
|
||||||
export { default as AlignItemsSpaceBetween } from './SpaceBetween.vue';
|
|
||||||
@ -1,24 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
<path
|
<path
|
||||||
d="M0 0.625C0 0.279822 0.248731 0 0.555556 0H9.44444C9.75127 0 10 0.279822 10 0.625C10 0.970178 9.75127 1.25 9.44444 1.25H0.555556C0.248731 1.25 0 0.970178 0 0.625Z"
|
d="M912.526651 867.741144 555.540144 510.712681l356.986507-357.000833c11.171434-11.18576 11.171434-29.257348 0-40.443108-11.20111-11.18576-29.272697-11.18576-40.444131 0L515.096013 470.267527 158.096203 113.267716c-11.187807-11.159154-29.258371-11.159154-40.444131 0-11.186783 11.186783-11.186783 29.286 0 40.47176L474.623229 510.712681 117.623419 867.741144c-11.159154 11.172457-11.159154 29.216415 0 40.443108 11.18576 11.17348 29.284977 11.17348 40.47176 0l357.000833-357.027439 356.985484 357.027439c11.171434 11.17348 29.243021 11.17348 40.444131 0C923.698085 896.957559 923.725714 878.913601 912.526651 867.741144z"
|
||||||
fill-opacity="0.3"
|
fill="#5D5D5D"
|
||||||
/>
|
></path>
|
||||||
<path
|
|
||||||
d="M0 9.375C0 9.02982 0.248731 8.75 0.555556 8.75H9.44444C9.75127 8.75 10 9.02982 10 9.375C10 9.72018 9.75127 10 9.44444 10H0.555556C0.248731 10 0 9.72018 0 9.375Z"
|
|
||||||
fill-opacity="0.3"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M0.555556 4.375C0.248731 4.375 0 4.65482 0 5C0 5.34518 0.248731 5.625 0.555556 5.625H2.87307L3.42868 4.99994L2.87318 4.375H0.555556Z"
|
|
||||||
fill-opacity="0.3"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M9.44444 5.625H7.12699L6.57138 4.99994L7.12688 4.375H9.44444C9.75127 4.375 10 4.65482 10 5C10 5.34518 9.75127 5.625 9.44444 5.625Z"
|
|
||||||
fill-opacity="0.3"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
transform="translate(0 2.6072)"
|
|
||||||
d="M3.30542 0.183058C3.52237 -0.0610194 3.87413 -0.0610194 4.09109 0.183058C4.30805 0.427136 4.30805 0.822864 4.09109 1.06694L2.91256 2.39279L4.0911 3.71865C4.30806 3.96273 4.30806 4.35846 4.0911 4.60253C3.87414 4.84661 3.52239 4.84661 3.30543 4.60253L2.12688 3.27667L0.948393 4.60248C0.731435 4.84655 0.379676 4.84655 0.162719 4.60248C-0.0542395 4.3584 -0.0542395 3.96267 0.162719 3.71859L1.34121 2.39279L0.162731 1.067C-0.0542269 0.822922 -0.0542268 0.427194 0.162731 0.183116C0.379689 -0.0609612 0.731447 -0.0609612 0.948405 0.183116L2.12688 1.50891L3.30542 0.183058Z"
|
|
||||||
/>
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,13 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
<rect x="13" y="3" width="2" height="2" rx="0.5" transform="rotate(90 13 3)" fill-opacity="0.3" />
|
<path
|
||||||
<rect x="13" y="7" width="2" height="2" rx="0.5" transform="rotate(90 13 7)" fill-opacity="0.3" />
|
d="M884.736 102.4l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456C696.32 311.296 712.704 327.68 733.184 327.68l147.456 0c20.48 0 36.864-16.384 36.864-36.864L917.504 139.264C921.6 118.784 905.216 102.4 884.736 102.4zM884.736 290.816l-147.456 0L737.28 139.264l147.456 0L884.736 290.816z"
|
||||||
<rect x="13" y="11" width="2" height="2" rx="0.5" transform="rotate(90 13 11)" fill-opacity="0.3" />
|
></path>
|
||||||
<rect x="9" y="3" width="2" height="2" rx="0.5" transform="rotate(90 9 3)" />
|
<path
|
||||||
<rect x="9" y="7" width="2" height="2" rx="0.5" transform="rotate(90 9 7)" />
|
d="M884.736 696.32l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C921.6 712.704 905.216 696.32 884.736 696.32zM884.736 884.736l-147.456 0 0-147.456 147.456 0L884.736 884.736z"
|
||||||
<rect x="9" y="11" width="2" height="2" rx="0.5" transform="rotate(90 9 11)" fill-opacity="0.3" />
|
></path>
|
||||||
<rect x="5" y="3" width="2" height="2" rx="0.5" transform="rotate(90 5 3)" />
|
<path
|
||||||
<rect x="5" y="7" width="2" height="2" rx="0.5" transform="rotate(90 5 7)" />
|
d="M884.736 401.408l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C921.6 417.792 905.216 401.408 884.736 401.408zM884.736 585.728l-147.456 0 0-147.456 147.456 0L884.736 585.728z"
|
||||||
<rect x="5" y="11" width="2" height="2" rx="0.5" transform="rotate(90 5 11)" fill-opacity="0.3" />
|
></path>
|
||||||
|
<path
|
||||||
|
d="M585.728 401.408l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C622.592 417.792 606.208 401.408 585.728 401.408zM585.728 585.728l-147.456 0 0-147.456 147.456 0L585.728 585.728z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M585.728 102.4l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864L622.592 139.264C622.592 118.784 606.208 102.4 585.728 102.4zM585.728 290.816l-147.456 0L438.272 139.264l147.456 0L585.728 290.816z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M585.728 696.32l-147.456 0c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C622.592 712.704 606.208 696.32 585.728 696.32zM585.728 884.736l-147.456 0 0-147.456 147.456 0L585.728 884.736z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M290.816 696.32 139.264 696.32c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C327.68 712.704 311.296 696.32 290.816 696.32zM290.816 884.736 139.264 884.736l0-147.456 147.456 0L286.72 884.736z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M290.816 401.408 139.264 401.408c-20.48 0-36.864 16.384-36.864 36.864l0 147.456c0 20.48 16.384 36.864 36.864 36.864l147.456 0c20.48 0 36.864-16.384 36.864-36.864l0-147.456C327.68 417.792 311.296 401.408 290.816 401.408zM290.816 585.728 139.264 585.728l0-147.456 147.456 0L286.72 585.728z"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M290.816 102.4 139.264 102.4c-20.48 0-36.864 16.384-36.864 36.864l0 147.456C102.4 311.296 118.784 327.68 139.264 327.68l147.456 0C311.296 327.68 327.68 311.296 327.68 290.816L327.68 139.264C327.68 118.784 311.296 102.4 290.816 102.4zM290.816 290.816 139.264 290.816 139.264 139.264l147.456 0L286.72 290.816z"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
<rect x="3" y="3" width="2" height="10" rx="1" />
|
<path
|
||||||
<rect x="7" y="3" width="2" height="10" rx="1" />
|
d="M796.444444 170.666667h-227.555555v682.666666h227.555555V170.666667z m-56.888888 625.777777h-113.777778V227.555556h113.777778v568.888888zM455.111111 170.666667H227.555556v682.666666h227.555555V170.666667zM398.222222 796.444444H284.444444V227.555556h113.777778v568.888888zM910.222222 56.888889h56.888889v910.222222h-56.888889zM56.888889 56.888889h56.888889v910.222222H56.888889z"
|
||||||
<rect x="11" y="3" width="2" height="10" rx="1" fill-opacity="0.3" />
|
fill="#333333"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
<rect x="13" y="3" width="2" height="10" rx="1" transform="rotate(90 13 3)" />
|
<path
|
||||||
<rect x="13" y="7" width="2" height="10" rx="1" transform="rotate(90 13 7)" />
|
d="M170.666667 227.555556v227.555555h682.666666V227.555556H170.666667z m625.777777 56.888888v113.777778H227.555556V284.444444h568.888888zM170.666667 568.888889v227.555555h682.666666v-227.555555H170.666667z m625.777777 56.888889v113.777778H227.555556v-113.777778h568.888888zM56.888889 56.888889h910.222222v56.888889H56.888889zM56.888889 910.222222h910.222222v56.888889H56.888889z"
|
||||||
<rect x="13" y="11" width="2" height="10" rx="1" transform="rotate(90 13 11)" fill-opacity="0.3" />
|
fill="#333333"
|
||||||
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
<path
|
<path
|
||||||
d="M12 3C12.5523 3 13 3.44772 13 4V10C13 10.5523 12.5523 11 12 11H4C3.44772 11 3 10.5523 3 10V4C3 3.44772 3.44772 3 4 3H12ZM4.5 4C4.22386 4 4 4.22386 4 4.5V9.5C4 9.77614 4.22386 10 4.5 10H11.5C11.7761 10 12 9.77614 12 9.5V4.5C12 4.22386 11.7761 4 11.5 4H4.5Z"
|
d="M896 320H128V0h768v320z m0 320H128v-256h768v256z m-128 192l-256 192-256-192 192-0.032V704h128v128h192z"
|
||||||
/>
|
></path>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 12L8 15L5 12L11 12Z" fill-opacity="0.3" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
<path
|
<path
|
||||||
d="M12 5C12.5523 5 13 5.44772 13 6V12C13 12.5523 12.5523 13 12 13H4C3.44772 13 3 12.5523 3 12V6C3 5.44772 3.44772 5 4 5H12ZM4.5 6C4.22386 6 4 6.22386 4 6.5V11.5C4 11.7761 4.22386 12 4.5 12H11.5C11.7761 12 12 11.7761 12 11.5V6.5C12 6.22386 11.7761 6 11.5 6H4.5Z"
|
d="M896 704H128v320h768V704z m0-320H128v256h768v-256z m-128-192l-256-192-256 192 192 0.032V320h128V192h192z"
|
||||||
/>
|
></path>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M5 4L8 1L11 4L5 4Z" fill-opacity="0.3" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
|
||||||
<path
|
<path
|
||||||
d="M10 4C10.5523 4 11 4.44772 11 5V11C11 11.5523 10.5523 12 10 12H2C1.44772 12 1 11.5523 1 11V5C1 4.44772 1.44772 4 2 4H10ZM2.5 5C2.22386 5 2 5.22386 2 5.5V10.5C2 10.7761 2.22386 11 2.5 11H9.5C9.77614 11 10 10.7761 10 10.5V5.5C10 5.22386 9.77614 5 9.5 5H2.5Z"
|
d="M320 128v768H0V128h320z m320 0v768h-256V128h256z m192 128l192 256-192 256-0.032-192H704v-128h128V256z"
|
||||||
/>
|
></path>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 5L15 8L12 11L12 5Z" fill-opacity="0.3" />
|
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
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