Compare commits

..

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

604 changed files with 12543 additions and 19202 deletions

14
.eslintignore Normal file
View File

@ -0,0 +1,14 @@
dist
coverage
node_modules
dest
types
public/entry
public/runtime
comp-entry.ts
config-entry.ts
value-entry.ts
magic-admin/web/public/runtime
magic-admin/server/static

71
.eslintrc.cjs Normal file
View File

@ -0,0 +1,71 @@
module.exports = {
env: {
node: true,
browser: true,
es2021: true,
},
globals: {
describe: true,
it: true,
expect: true,
beforeEach: true,
},
extends: [
'eslint-config-tencent',
'eslint-config-tencent/ts',
'plugin:vue/vue3-essential',
'./prettier.cjs',
],
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 12,
parser: '@typescript-eslint/parser',
extraFileExtensions: ['.vue'],
sourceType: 'module',
},
plugins: [
'vue',
'@typescript-eslint',
'simple-import-sort'
],
ignorePatterns: ['.eslintrc.cjs'],
rules: {
'vue/no-mutating-props': 'off',
'vue/multi-word-component-names': 'off',
'no-param-reassign': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-require-imports': 'off',
"@typescript-eslint/no-misused-promises": [
"error",
{
"checksVoidReturn": false
}
],
'simple-import-sort/imports': [
"error", {
groups: [
['./polyfills'],
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
[
"^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)",
],
["^(node)(:.*|$)"],
// Packages. `react|vue` related packages come first.
["^(react|vue|vite)", "^@?\\w"],
["^(@tmagic)(/.*|$)"],
// Internal packages.
["^(@|@editor|@data-source)(/.*|$)"],
// Side effect imports.
["^\\u0000"],
// Parent imports. Put `..` last.
["^\\.\\.(?!/?$)", "^\\.\\./?$"],
// Other relative imports. Put same-folder imports and `.` last.
["^\\./(?=.*/)(?!/?$)", "^\\.(?!/?$)", "^\\./?$"],
// Style imports.
["^.+\\.s?css$"],
],
}
]
},
};

View File

@ -10,18 +10,18 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v2
with:
fetch-depth: 0
ref: dev
- name: Install pnpm
uses: pnpm/action-setup@v4
uses: pnpm/action-setup@v2
- name: Set node version to 22
uses: actions/setup-node@v4
- name: Set node version to 18
uses: actions/setup-node@v2
with:
node-version: 22
node-version: 18
cache: 'pnpm'
- run: pnpm bootstrap

2
.gitignore vendored
View File

@ -31,5 +31,3 @@ coverage
auto-imports.d.ts
components.d.ts
docs/.vitepress/cache
.eslintcache

View File

@ -1 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit $1

View File

@ -1 +1,6 @@
npx lint-staged && npm run check:type
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
pnpm check:type

View File

@ -1 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm test

View File

@ -1,561 +1,3 @@
## [1.7.2](https://github.com/Tencent/tmagic-editor/compare/v1.7.1...v1.7.2) (2025-12-09)
## [1.7.1](https://github.com/Tencent/tmagic-editor/compare/v1.7.0...v1.7.1) (2025-12-09)
### Bug Fixes
* **core:** getNode 添加stict参数来表示必须知道页面片容器id才会返回页面内的节点 ([83664cd](https://github.com/Tencent/tmagic-editor/commit/83664cd44019c3b5f05d2ad60bbb8fcf751f6b35))
* **form:** tabs组件子项配置了name后,配置生成的数据出错 ([b3ce1a3](https://github.com/Tencent/tmagic-editor/commit/b3ce1a3b930e9cbfc74b72bfb7dd9268fe341626))
* **tmagic-form:** runtime刷新导致root丢失 ([d039cee](https://github.com/Tencent/tmagic-editor/commit/d039cee9136e7e892161dafe25ff34ee95bce958))
### Features
* **vue-components:** 不再兼容vue2 ([756612e](https://github.com/Tencent/tmagic-editor/commit/756612eda51fa8079420203eaca585175b039e8b))
* **vue-runtime-help:** 去掉vue2的兼容 ([2a7ab4e](https://github.com/Tencent/tmagic-editor/commit/2a7ab4e916444a68c7cc05af4fa57ddd59994393))
# [1.7.0](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.5...v1.7.0) (2025-12-04)
### Bug Fixes
* **design:** dialog 默认参数 ([8d7c8fa](https://github.com/Tencent/tmagic-editor/commit/8d7c8fa725dba507c3a4874f879989ad9c050603))
* **form:** datetime如果设置为时间戳然后初始值是一个字符串的数字显示不正确 ([7e71c07](https://github.com/Tencent/tmagic-editor/commit/7e71c070f1ee9857fd5610000188ef1a222fade1))
* **form:** table拖拽排序后重新渲染组件 ([2667981](https://github.com/Tencent/tmagic-editor/commit/2667981e4c0c451ded88e06f412dd4bcb5ec41dc))
* **form:** 生成表单values时将initValus深拷贝,避免修改到传入的对象 ([b536eba](https://github.com/Tencent/tmagic-editor/commit/b536eba81c33f8e532179b69072ea7c5d4033235))
### Features
* **editor:** 代码编辑器支持配置自动高度 ([8d55d0c](https://github.com/Tencent/tmagic-editor/commit/8d55d0cd8d1059dcbca86fc5b72ec82f22c35d9c))
* **form:** table支持配置拖动操作中的按钮排序而不是拖动整行 ([69ac90f](https://github.com/Tencent/tmagic-editor/commit/69ac90fe22778fedb9e35b95595428395fd65cc5))
* **form:** table支持配置操作按钮的icon ([0dd7f54](https://github.com/Tencent/tmagic-editor/commit/0dd7f54ebc3518404653d6e43ee428e05566dd28))
# [1.7.0-beta.5](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.4...v1.7.0-beta.5) (2025-11-24)
### Bug Fixes
* **element-plus-adapter:** 构建时将vue源码构建入目标代码中,导致页面存在多个版本vue ([bb7ec0a](https://github.com/Tencent/tmagic-editor/commit/bb7ec0aa1b55f83f68d7021fb98090d8e555ed65))
* **tdesign-vue-next-adapter:** 没有extra时form item不渲染help节点 ([849b4dc](https://github.com/Tencent/tmagic-editor/commit/849b4dc319b03a54dc1cfcccc1f505e61e1ccd5b))
# [1.7.0-beta.4](https://github.com/Tencent/tmagic-editor/compare/v1.6.1...v1.7.0-beta.4) (2025-11-24)
### Bug Fixes
* **design,editro,element-plus-adapter,tdesign-vue-next-adapter:** elememt-plus表单渲染失败 ([9364025](https://github.com/Tencent/tmagic-editor/commit/93640257e90e6aa4708184a2513b80149887b0b9))
* **design:** select visible-change事件名写错 ([27555d6](https://github.com/Tencent/tmagic-editor/commit/27555d6b2f3c4c2022f77504bb4d8faf7c6f3e55))
* **editor:** 表单组件保持单向数据流 ([564a7f4](https://github.com/Tencent/tmagic-editor/commit/564a7f4271b9adb345d679f85929506e952c9cb3))
* **form:** daterange 配置names后配置失效 ([0f3dfcf](https://github.com/Tencent/tmagic-editor/commit/0f3dfcf5118e1247f351357a9eb05bfbdc7cc43b))
* **form:** dialog submit event获取到的changeRecords为空 ([cbec529](https://github.com/Tencent/tmagic-editor/commit/cbec52936db52d5c57c454468a11bea1dbbcd058))
* **form:** tabel复制行不生效 ([68c69ac](https://github.com/Tencent/tmagic-editor/commit/68c69ac4058bbe7adf00a73b21df2ec7adb713cc))
* **form:** 文本输入change出发太频繁 ([55a2869](https://github.com/Tencent/tmagic-editor/commit/55a28698183998e6dec73d8d41d5e5f029d17e7a))
* **playgournd:** 存在多个vue版本问题 ([da98846](https://github.com/Tencent/tmagic-editor/commit/da9884645fe51d3de65e461c2e434d3e221e56b5))
* **tdesign-vue-next-adapter:** select支持allowCreate ([1a08b16](https://github.com/Tencent/tmagic-editor/commit/1a08b16a399dae00e4b0d62d9d930514c84f669d))
* **vue-runtime-hlep:** 页面片销毁后需要取消app的事件监听 ([e36da82](https://github.com/Tencent/tmagic-editor/commit/e36da82d2928af6617c280939b02b6bc9f534b70))
### Features
* **core:** getNode未指定页面片容器id时取获得到的第一个 ([5dbe1fb](https://github.com/Tencent/tmagic-editor/commit/5dbe1fb6555188bb17aa5b3dd1d7205b8c4d9824))
* **design, element-plus-adapter, tdesign-vue-next-adapter:** 新增popconfirm组件 ([507e51a](https://github.com/Tencent/tmagic-editor/commit/507e51a2dc91defb67087a539d786d01ef95b9ad))
* **design, element-plus-adapter, tdesign-vue-next-adapter:** 添加adapterType, 完善tdesign useZIndex ([12e6dd1](https://github.com/Tencent/tmagic-editor/commit/12e6dd18b47c4e6a11f273cbbb138c5d0e33f0ca))
* **design, form, form-schema, tdesign-vue-next-adapter:** textarea支持rows配置 ([38192a6](https://github.com/Tencent/tmagic-editor/commit/38192a6d4854904d7c1303556ad6ef4b76b7cd21))
* **design, form, tdesign-vue-next-adapter:** 完善tdesign适配 ([ca0f8fc](https://github.com/Tencent/tmagic-editor/commit/ca0f8fc9887d9fcff31052239b396d02f258d1b4))
* **design, tdesign-vue-next-adapter, table, element-plus-adapter:** 完善tdesign适配 ([979b834](https://github.com/Tencent/tmagic-editor/commit/979b834facbabf50d711b76ca5180f07b319b432))
* **design, tdesign-vue-next-adapter:** formItem新增labelAlign prop ([0ecc116](https://github.com/Tencent/tmagic-editor/commit/0ecc11665235c136a2d9e7b361fe2ecab33ff5e3))
* **design,editor,element-plus-adapter,form,table,tdesign-vue-next-adapter:** 重构table组件,适配tdesign ([08b476e](https://github.com/Tencent/tmagic-editor/commit/08b476e04f36154ef58f1f3388bc2516742e37d8))
* **design,tdesign-vue-next-adapter:** input添加click事件 ([a96ca80](https://github.com/Tencent/tmagic-editor/commit/a96ca8092c51d1700943e58e197a5d1584297d76))
* **design,tdesign-vue,next-adapter:** textarea支持autosize ([acda22d](https://github.com/Tencent/tmagic-editor/commit/acda22d5cb4905ae5d1f4bd70efb38ee5820baeb))
* **design:** 在html中添加adapter类型class ([09dfaad](https://github.com/Tencent/tmagic-editor/commit/09dfaad2ccf9cd50491a8352e33416c7a17027c4))
* **element-plus-adapter, from, tdesign-vue-adapter:** button兼容type=default和type为空的情况 ([d017902](https://github.com/Tencent/tmagic-editor/commit/d0179028fbab5b485cb0b970d8d558da4f22d869))
* **form, design, form-schema:** table支持自定义title,table 表单组件支持配置title tip ([e418130](https://github.com/Tencent/tmagic-editor/commit/e418130a66c54b7fed533977b1558e7206d729fb))
* **form:** fieldset中checkbox新增name,trueValue,falseValue配置 ([51e9732](https://github.com/Tencent/tmagic-editor/commit/51e973289473af7fae14f56e3d2fed5db838c797))
* **form:** form dialog新增props ([bf6598c](https://github.com/Tencent/tmagic-editor/commit/bf6598c8718d5f2e3d276029bf924a7de7cb458b))
* **form:** form dialog新增show close/show cancel 配置 ([310054b](https://github.com/Tencent/tmagic-editor/commit/310054b7d63e688c349142c9b7767fc5f8a2f766))
* **form:** group list新增添加按钮配置 ([3b913c1](https://github.com/Tencent/tmagic-editor/commit/3b913c1af48aa6a47e632725b008061d7faca204))
* **form:** panel点击标题可以展开或者收缩内容 ([310aa47](https://github.com/Tencent/tmagic-editor/commit/310aa47c1df9a88304f264325c697b85e09287df))
* **form:** table操作列支持配置固定在左边还是右边 ([fa0e10f](https://github.com/Tencent/tmagic-editor/commit/fa0e10f6872ade5599957051e2e6923fa2d4ca66))
* **form:** table新增新增按钮配置 ([6a7f80c](https://github.com/Tencent/tmagic-editor/commit/6a7f80c48de0e85587c27f67fe0f132f7dfee000))
* **form:** text新增prepend, append不默认使用button ([5fe57cd](https://github.com/Tencent/tmagic-editor/commit/5fe57cd389e6c955049c2ee81f62c37c7a86930f))
* **form:** text组件配置的append.hander函数添加setModel/setFormValue方法 ([6f0498a](https://github.com/Tencent/tmagic-editor/commit/6f0498a9e7ee633310b4a08374409b3f1b7607b9))
* **form:** tip图标放到label中去 ([97affb2](https://github.com/Tencent/tmagic-editor/commit/97affb2bff856b271136b50dccd45d95e17abd6a))
* **form:** 新增flex-layout组件 ([630301b](https://github.com/Tencent/tmagic-editor/commit/630301bce213e3ff28a7fb50e9cc9b8bb0c2b756))
* **form:** 新增style,fieldStyle配置;tooltip支持配置placement;配置中的函数新增getFormValue方法 ([3a9c94a](https://github.com/Tencent/tmagic-editor/commit/3a9c94a6a6529a63e022955b2f7eee23bf125d81))
* **form:** 表单校验后的错误信息将name转换成text ([ec479b9](https://github.com/Tencent/tmagic-editor/commit/ec479b9296d5dbea0df121a4702a123518808e58))
* **form:** 表格拖动支持tdesign ([e2708b8](https://github.com/Tencent/tmagic-editor/commit/e2708b868b2fbb126f1d8d846e25335b6005cd86))
* **playground:** 支持UI组件库切换 ([11d2560](https://github.com/Tencent/tmagic-editor/commit/11d25603a8b5f8bc3d9abbbeedf9c298ca0e2c72))
* **table:** action支持配置disabled ([8809351](https://github.com/Tencent/tmagic-editor/commit/88093515373e1b431c83c0da9c28a2688421d151))
# [1.7.0-beta.3](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.2...v1.7.0-beta.3) (2025-10-31)
### Bug Fixes
* **editor:** 表单组件保持单向数据流 ([09663c8](https://github.com/Tencent/tmagic-editor/commit/09663c8320829dc52a4d4ffb65c73fe147a3f5fc))
### Features
* **design,tdesign-vue,next-adapter:** textarea支持autosize ([be75ac9](https://github.com/Tencent/tmagic-editor/commit/be75ac994f0c03a9bdae05bc433513278d32c905))
* **element-plus-adapter, from, tdesign-vue-adapter:** button兼容type=default和type为空的情况 ([2a29971](https://github.com/Tencent/tmagic-editor/commit/2a2997131873a4836303ac2411371dd78726f6f9))
* **form:** text组件配置的append.hander函数添加setModel/setFormValue方法 ([57a634a](https://github.com/Tencent/tmagic-editor/commit/57a634a687c53132f521a8ed35a649edec488738))
# [1.7.0-beta.2](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.1...v1.7.0-beta.2) (2025-10-28)
### Bug Fixes
* **form:** daterange 配置names后配置失效 ([d22e520](https://github.com/Tencent/tmagic-editor/commit/d22e520fbf6fc95c2c9600f02107d987b3a46fa7))
* **form:** dialog submit event获取到的changeRecords为空 ([02e1c7f](https://github.com/Tencent/tmagic-editor/commit/02e1c7f479e5370ba6ea2153b9547bbb0c090431))
### Features
* **design, element-plus-adapter, tdesign-vue-next-adapter:** 添加adapterType, 完善tdesign useZIndex ([b312b5a](https://github.com/Tencent/tmagic-editor/commit/b312b5a5bc827a8199d5a7b36418f0ad933be081))
* **design, tdesign-vue-next-adapter, table, element-plus-adapter:** 完善tdesign适配 ([fa20837](https://github.com/Tencent/tmagic-editor/commit/fa208372ab5c204b6cb34ff977cd133156513fda))
# [1.7.0-beta.1](https://github.com/Tencent/tmagic-editor/compare/v1.7.0-beta.0...v1.7.0-beta.1) (2025-10-24)
### Features
* **design, form, form-schema, tdesign-vue-next-adapter:** textarea支持rows配置 ([c717472](https://github.com/Tencent/tmagic-editor/commit/c7174726b3f017afb5666f9ccc343a0c98d8d1fa))
* **form:** 表单校验后的错误信息将name转换成text ([b247490](https://github.com/Tencent/tmagic-editor/commit/b2474909cff049b0800da5be3293ad35309c1b1e))
# [1.7.0-beta.0](https://github.com/Tencent/tmagic-editor/compare/v1.6.1...v1.7.0-beta.0) (2025-10-23)
### Bug Fixes
* **form:** tabel复制行不生效 ([6a2436f](https://github.com/Tencent/tmagic-editor/commit/6a2436fb99f15cdf662f39fd86952955fb78a9d2))
### Features
* **design, element-plus-adapter, tdesign-vue-next-adapter:** 新增popconfirm组件 ([3181f32](https://github.com/Tencent/tmagic-editor/commit/3181f32b389325ca1524c42af25279ceab0a9a09))
* **design, form, tdesign-vue-next-adapter:** 完善tdesign适配 ([8ce5f71](https://github.com/Tencent/tmagic-editor/commit/8ce5f71aa26ea3fb1516aa020fa65fefdaa9cc55))
* **design,editor,element-plus-adapter,form,table,tdesign-vue-next-adapter:** 重构table组件,适配tdesign ([5b16ec0](https://github.com/Tencent/tmagic-editor/commit/5b16ec00e1de45ed50f1c206de10ad48f9d71275))
* **form:** fieldset中checkbox新增name,trueValue,falseValue配置 ([3435661](https://github.com/Tencent/tmagic-editor/commit/3435661348efce00390d0a79361073e8fea8af33))
* **form:** text新增prepend, append不默认使用button ([cea6569](https://github.com/Tencent/tmagic-editor/commit/cea65690204dbe8ccf178f92fa7c47ad6fd24a8c))
* **form:** 新增flex-layout组件 ([3e76d34](https://github.com/Tencent/tmagic-editor/commit/3e76d34f59343b5c3e833d32a35fad152a5bf7fd))
* **form:** 新增style,fieldStyle配置;tooltip支持配置placement;配置中的函数新增getFormValue方法 ([1eeabc8](https://github.com/Tencent/tmagic-editor/commit/1eeabc8220649405746558d09bd648d2b650854d))
* **playground:** 支持UI组件库切换 ([dd3e901](https://github.com/Tencent/tmagic-editor/commit/dd3e901a3d4122e6f4043521ff776fc0df478f98))
* **table:** action支持配置disabled ([8809351](https://github.com/Tencent/tmagic-editor/commit/88093515373e1b431c83c0da9c28a2688421d151))
## [1.6.1](https://github.com/Tencent/tmagic-editor/compare/v1.6.0...v1.6.1) (2025-10-14)
### Bug Fixes
* **core:** 异步加载数据源时,数据源事件配置失效 ([1031595](https://github.com/Tencent/tmagic-editor/commit/1031595a976b33f8e5e3a71d8c00944d4777beb1))
* **form:** text与同行元素不对齐问题 ([cae11dc](https://github.com/Tencent/tmagic-editor/commit/cae11dce1290b9ea314c07daee30f1eb6f400681))
* 组件声明周期函数配置中配置数据源自有方法生效 ([e400175](https://github.com/Tencent/tmagic-editor/commit/e400175ffe89ab3105e50d2ffcd702d9d2a12970))
### Features
* **data-source, editor, schema, react-runtime-help, vue-components:** 新增条件成立时隐藏的配置功能 ([51f95ae](https://github.com/Tencent/tmagic-editor/commit/51f95aef6f649851222e09e7234648f4985d1ad8))
* **data-source:** 数据源数据变化事件监听响应支持立即执行 ([849b561](https://github.com/Tencent/tmagic-editor/commit/849b561933d74bcc68f75f15d67e0d252c5a8468))
* **editor:** 属性配置中的样式面板样式优化 ([81aa8f1](https://github.com/Tencent/tmagic-editor/commit/81aa8f151d765bf08d2f88c334ff7c58b38aab56))
# [1.6.0](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.6...v1.6.0) (2025-08-27)
### Bug Fixes
* **cli:** 在pnpm工作空间中的项目保持package.json不受配置中的packages影响 ([a10f9d2](https://github.com/Tencent/tmagic-editor/commit/a10f9d230d93064c28ff82f2c3aba2679f8fe495))
### Features
* **cli:** 支持tmagic.config.local配置文件会与tmagic.config配置合并可用于本地开发时的临时配置 ([9f35054](https://github.com/Tencent/tmagic-editor/commit/9f350541bf84a634499a9af53449015f34da26b5))
# [1.6.0-beta.6](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.5...v1.6.0-beta.6) (2025-08-15)
### Features
* **core:** 事件支持关联数据源自身方法 ([4c6118f](https://github.com/Tencent/tmagic-editor/commit/4c6118f50f8e78e8db157f9b32c49f5f068e3e9a))
# [1.6.0-beta.5](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.4...v1.6.0-beta.5) (2025-08-08)
### Bug Fixes
* **core:** 事件行为应该为串行执行 ([fc1c9fe](https://github.com/Tencent/tmagic-editor/commit/fc1c9feafd337ad8fa8a9b5b3501278779d32410))
# [1.6.0-beta.4](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.3...v1.6.0-beta.4) (2025-07-24)
### Bug Fixes
* **editor:** runtime url更新后恢复当前选中状态 ([c2830fc](https://github.com/Tencent/tmagic-editor/commit/c2830fca6b21648b51a486650caba3f9eb753c91))
* **editor:** 避免services plugin重复添加 ([cdb07df](https://github.com/Tencent/tmagic-editor/commit/cdb07dfaea13a1813caddd59ba7e8efbdb374b3c))
# [1.6.0-beta.3](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.2...v1.6.0-beta.3) (2025-07-24)
### Bug Fixes
* **editor:** runtimeUrl更新后需要重新设置runtime的dsl ([d31a544](https://github.com/Tencent/tmagic-editor/commit/d31a544a2312b4d78ae74eb600760df450946db8))
* **editor:** 修复代码块编辑器的更新内容后按下ctrl+s光标会偏移的问题 ([07b8f5f](https://github.com/Tencent/tmagic-editor/commit/07b8f5fe8303bad57490c7dd0eba00e435fbfad5))
# [1.6.0-beta.2](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.1...v1.6.0-beta.2) (2025-07-22)
### Features
* **core:** 异步加载dsl片段下事件触发时node可能不存在 ([01b88db](https://github.com/Tencent/tmagic-editor/commit/01b88dba251a1f7cc29947c2a80e15839e50fcca))
* **utile:** replaceChildNode找不多目标不报错 ([2605011](https://github.com/Tencent/tmagic-editor/commit/2605011df481d9edba5503d2ee3b3c9d4b76d0a3))
# [1.6.0-beta.1](https://github.com/Tencent/tmagic-editor/compare/v1.6.0-beta.0...v1.6.0-beta.1) (2025-07-17)
### Bug Fixes
* **data-source:** 页面未初始化好之前数据源数据变化后需要修改page.data ([305ea46](https://github.com/Tencent/tmagic-editor/commit/305ea4619fe15efbae069ef3f289c8742e8d51ee))
* **vue-components:** iterator-container 传递 page-fragment-contianer-id 参数 ([7799a5d](https://github.com/Tencent/tmagic-editor/commit/7799a5da618b0136f4b3c12315c69d21b27a5965))
* **vue-components:** page-fragment-container不标记为不是node节点 ([bf9fad1](https://github.com/Tencent/tmagic-editor/commit/bf9fad18b64521a9075a60f7e6e662b01d37f66f))
### Features
* **core:** Node中的instance初始为null用于判断是否与组件产生关联 ([291de20](https://github.com/Tencent/tmagic-editor/commit/291de2005de54b8eeba818de67ab3506885f5057))
* **editor:** 页面片容器内的组件不允许选中 ([ded24c8](https://github.com/Tencent/tmagic-editor/commit/ded24c8b4ff2c203260ec7633fc85325c98a565c))
* **vue-components:** page-fragment-container编辑器中不去除内部组件id不然会导致无法从app中获取dsl ([34bc223](https://github.com/Tencent/tmagic-editor/commit/34bc223f0241d9eef39450ebcef8a92b23c63f37))
* **vue-components:** 添加页面片容器id prop ([a43825c](https://github.com/Tencent/tmagic-editor/commit/a43825caa27695fcd85cc4b5351a00080fefcec0))
# [1.6.0-beta.0](https://github.com/Tencent/tmagic-editor/compare/v1.5.24...v1.6.0-beta.0) (2025-07-15)
### Bug Fixes
* **editor:** 依赖收集后没有同步到dsl中 ([aaf8046](https://github.com/Tencent/tmagic-editor/commit/aaf8046c63949ae733719409eb12109ef17b2cf1))
### Features
* **core,data-source,utils,react-runtime-help,vue-runtime-help:** 新增对页面片节点的管理 ([6a54720](https://github.com/Tencent/tmagic-editor/commit/6a547200681991ae8abefd0e3d72a603d21f12d4))
## [1.5.24](https://github.com/Tencent/tmagic-editor/compare/v1.5.23...v1.5.24) (2025-07-03)
### Bug Fixes
* **editor:** 初始化收集依赖后编译数据源不能使用源数据 ([84bf9ba](https://github.com/Tencent/tmagic-editor/commit/84bf9ba2f96829ba8e88b278bd49f2cb454605aa))
## [1.5.23](https://github.com/Tencent/tmagic-editor/compare/v1.5.22...v1.5.23) (2025-07-03)
### Bug Fixes
* **editor,react-runtime-help,vue-runtime-help:** 修复拖动页面顺序失效问题 ([b6a2604](https://github.com/Tencent/tmagic-editor/commit/b6a260471d9369e9b911f35d16ff0fca82a7d4fe))
* **editor:** 树组件搜索后不展开所有节点 ([c984c1a](https://github.com/Tencent/tmagic-editor/commit/c984c1a0cf0c934d41e5affb471d2ff2efa4bb97))
* **editor:** 防止快捷键重复注册 ([18e8df4](https://github.com/Tencent/tmagic-editor/commit/18e8df4a557cb845a8f1d2868ea4e8833a75941d))
* **editor:** 页面列表滚动条 ([727af10](https://github.com/Tencent/tmagic-editor/commit/727af1058d23e0843482536133954243c9f208d5))
### Features
* **core,data-source,dep,editor,schema:** 新增组件禁用代码块/数据源的配置开关 ([2f4a7a3](https://github.com/Tencent/tmagic-editor/commit/2f4a7a33b85015bfe5368bd10bf562514f07299c))
* **dep,schema:** 添加组件没用数据源或者代码块的配置key ([3f7d039](https://github.com/Tencent/tmagic-editor/commit/3f7d03959cdbafacc439df2bf3a61aa1e21cb8c2))
* **editor:** service新增removePlugin方法 ([8d6da37](https://github.com/Tencent/tmagic-editor/commit/8d6da3712efdc269cbaa47b003bf667bbce00ae7))
* **editor:** stage overlay 支持放大缩小 ([3268196](https://github.com/Tencent/tmagic-editor/commit/32681964b340fc8e68f8b6a6eb420043fd569b78))
* **editor:** 数据源属性/方法表格可以拖动列宽 ([222a96e](https://github.com/Tencent/tmagic-editor/commit/222a96e4652003b908c5c75378e5ddf2029fdd65))
* **editor:** 新增props-panel-unmounted事件 ([9e590c5](https://github.com/Tencent/tmagic-editor/commit/9e590c5cf73c9925f7aad562409f31c1ff273b18))
* **playground:** 新增按比例拖动组件大小快捷键 ([b78ef20](https://github.com/Tencent/tmagic-editor/commit/b78ef206022d69a8c3f81c55a301e5a4b5147641))
* **stage:** 新增禁用标尺配置 ([95d6263](https://github.com/Tencent/tmagic-editor/commit/95d6263f42ad13c2ac0560704695408634e55d91))
* **stage:** 添加updateMoveableOptions api用于更新选中框状态 ([bdd59cf](https://github.com/Tencent/tmagic-editor/commit/bdd59cff9b378461f905b04e250f8ad7f6ed21a0))
## [1.5.22](https://github.com/Tencent/tmagic-editor/compare/v1.5.21...v1.5.22) (2025-06-09)
### Bug Fixes
* **data-source,utils:** 不使用replaceAll,安卓10不支持 ([b1f020d](https://github.com/Tencent/tmagic-editor/commit/b1f020d53242981751c4e941a0fde9d61e9d6060))
* **vue-runtime-help:** 修复页面片中组件拖动位置异常问题 ([b72f4da](https://github.com/Tencent/tmagic-editor/commit/b72f4dae28961fd8246101998a2f4e78775dd996))
### Features
* **design:** dialog支持配置destroyOnClose ([cd4656e](https://github.com/Tencent/tmagic-editor/commit/cd4656e54f3627429427973525b9fc5bf10a7139))
* **editor:** 支持禁用数据源与代码块 ([6152a78](https://github.com/Tencent/tmagic-editor/commit/6152a78467b9fc96760da3b9a2bd4cc8cb8457b8))
* **editor:** 支持配置自定义创建Monaco editor函数 ([3097e8e](https://github.com/Tencent/tmagic-editor/commit/3097e8eddb61331cc249d1b18d74560bc8bfbefe))
* **editor:** 样式中的position选项加上中文 ([1614b62](https://github.com/Tencent/tmagic-editor/commit/1614b62d2a2bea8393386538ac57f0909bb06032))
* **eslint-conft:** 添加import-sort的本地目录 ([e01ce21](https://github.com/Tencent/tmagic-editor/commit/e01ce2128e98b512b3c3611615da4f97b1b9d356))
* **form,form-schema:** group list支持配置默认展开数量 ([6b4ca4b](https://github.com/Tencent/tmagic-editor/commit/6b4ca4b83a1a0a36ff94c30feb1845d957377b47))
## [1.5.21](https://github.com/Tencent/tmagic-editor/compare/v1.5.20...v1.5.21) (2025-06-04)
### Bug Fixes
* **data-source:** 没有异步加载数据源的情况下数据源init方法没有执行 ([c7b2734](https://github.com/Tencent/tmagic-editor/commit/c7b27340901c9728195084ca88525b9102fc4855))
### Features
* **editor,stage:** 支持切换runtimeUrl ([92534fc](https://github.com/Tencent/tmagic-editor/commit/92534fc9159b408f93e194ce119b45ff5cd5f95f))
* **editor:** 获取联动组件的方法函数中添加组件id参数 ([0196eb3](https://github.com/Tencent/tmagic-editor/commit/0196eb343ac782ca2ab7a9a69f09ab273a38fa8e))
* **form:** select before/after request配置函数支持异步 ([8142a7a](https://github.com/Tencent/tmagic-editor/commit/8142a7ab6da9503540070b443b24e3344a069858))
## [1.5.20](https://github.com/Tencent/tmagic-editor/compare/v1.5.19...v1.5.20) (2025-05-21)
### Bug Fixes
* **editor:** getTMagicApp中对runtime-ready可能存在多次事件监听 ([4448584](https://github.com/Tencent/tmagic-editor/commit/444858491da47c168ea2540cd718fabdca027781))
* **editor:** 新增数据源时,由于方法列表为空出现的报错 ([1e396d4](https://github.com/Tencent/tmagic-editor/commit/1e396d4a8d202c0324d7f4128b80fd476bdb4e65))
### Features
* **core:** 新增事件处理前后的钩子函数配置 ([b78d2fd](https://github.com/Tencent/tmagic-editor/commit/b78d2fda1fe474b7004db038a51773c326b24f4e))
* **data-source:** 新增判断所有数据源加载完成的方法 ([dc37d4e](https://github.com/Tencent/tmagic-editor/commit/dc37d4e8bd35d473c3c74a7b10b3d9e6af00d759))
* **data-source:** 由于存在异步加载数据源,新增所有数据源加载完毕事件 ([a35789c](https://github.com/Tencent/tmagic-editor/commit/a35789c0cf1c740df9fb16bc302cf1aec50cdf2b))
## [1.5.19](https://github.com/Tencent/tmagic-editor/compare/v1.5.18...v1.5.19) (2025-05-20)
### Bug Fixes
* **data-source:** 异步加载数据源时,初始化第一次编译时数据源可能未加载,需要优先设置数据源默认值 ([d628ffa](https://github.com/Tencent/tmagic-editor/commit/d628ffad26f897ee6144b3981bdcd11cb8dd5e9d))
* **data-source:** 异步加载数据源时,初始化第一次编译时数据源可能未加载,需要优先设置数据源默认值 ([fc88671](https://github.com/Tencent/tmagic-editor/commit/fc886715c7b465d7f5e18eb5bcd904963f6dd4a9))
* **editor:** 由于数据源可能有内部方法,所以去掉事件联动中数据源选项置灰的逻辑 ([1444eb6](https://github.com/Tencent/tmagic-editor/commit/1444eb6ae7455dc9abaf3d05ed8db02b410875e7))
### Features
* **core:** 支持统一设置node.store的初始值 ([abe27db](https://github.com/Tencent/tmagic-editor/commit/abe27db47afbedf997affcd9593c9a9e8e0a4a14))
## [1.5.18](https://github.com/Tencent/tmagic-editor/compare/v1.5.17...v1.5.18) (2025-05-14)
### Bug Fixes
* **editor:** 根节点修改在update事件中不要触发依赖收集 ([6b481c4](https://github.com/Tencent/tmagic-editor/commit/6b481c447309abefbf9ea12f80fa4f874c7ff2a0))
* **editor:** 选中组件时,如果组件处于所在容器可视范围外,不要将组件滚动至可视范围,而是滚动组件容器至可视范围 ([936eb08](https://github.com/Tencent/tmagic-editor/commit/936eb08a94ef8e237f55d2c9c7f0408f9bfb4f8e))
### Features
* **core:** 新增数据源初始数据配置字段 ([d1e99a4](https://github.com/Tencent/tmagic-editor/commit/d1e99a46a7a25e2ddef152aef634589361e39194))
* **editor:** eventsServic支持插件扩展 ([054d126](https://github.com/Tencent/tmagic-editor/commit/054d12601b243a02f54af5f8776d74b5c5497858))
* **editor:** 组件高级配置中添加display ([e0d5efb](https://github.com/Tencent/tmagic-editor/commit/e0d5efb02261069b48823cd8ce24232ea6f24b08))
## [1.5.17](https://github.com/Tencent/tmagic-editor/compare/v1.5.16...v1.5.17) (2025-05-06)
### Features
* **cli:** 指定组件不使用动态加载dynamicImport为true时有效 ([85284e5](https://github.com/Tencent/tmagic-editor/commit/85284e54f5874d2c7afe08e401b26ae6b21a153f))
* **editor,form,form-schema:** 新增form-schema将表单schema放入其中 ([50b74d1](https://github.com/Tencent/tmagic-editor/commit/50b74d10e6e52f14ac46547292eaba8fe9bbae11))
* **editor:** dsl初始化时收集依赖使用worker ([179b797](https://github.com/Tencent/tmagic-editor/commit/179b797c5c8b4cbcf76a1d32ec3e899efb32ae2a))
* **form:** select before/after request函数配置中增加prop字段 ([ed01cfc](https://github.com/Tencent/tmagic-editor/commit/ed01cfca870c726117c33478907f55d9fe66085b))
## [1.5.16](https://github.com/Tencent/tmagic-editor/compare/v1.5.15...v1.5.16) (2025-04-28)
### Bug Fixes
* **data-source:** 异步加载数据源后,数据源未初始化 ([378c756](https://github.com/Tencent/tmagic-editor/commit/378c75658d1b50454ceb8cf4072b09beffb86067))
### Features
* **core:** instance设定不再限制只有方法 ([e5bcd76](https://github.com/Tencent/tmagic-editor/commit/e5bcd762b290a6b58bfd6486628c828207e30c37))
* **core:** 添加error hander配置 ([6dc0073](https://github.com/Tencent/tmagic-editor/commit/6dc007388bb28e6501bcb026fce1e2881b87bd82))
* **editor:** 组件默认的名称id配置只要在业务不提供时才加入 ([2dc0bbc](https://github.com/Tencent/tmagic-editor/commit/2dc0bbc679a7ab1ee1b7d368973bcb4abed2c0bb))
## [1.5.15](https://github.com/Tencent/tmagic-editor/compare/v1.5.14...v1.5.15) (2025-04-25)
### Bug Fixes
* **editor:** getTMagicApp返回类型错误 ([ba05ecb](https://github.com/Tencent/tmagic-editor/commit/ba05ecbf608785d97c60adbdd6b7381597e6cb6f))
## [1.5.14](https://github.com/Tencent/tmagic-editor/compare/v1.5.13...v1.5.14) (2025-04-24)
### Bug Fixes
* **dep:** 配置了数据源内部方法无法收集到依赖 ([0e91133](https://github.com/Tencent/tmagic-editor/commit/0e911337b8f88e5e88fecf072395c38d2de1bc08))
* **editor:** 编辑数据源/代码块时,列表高亮 ([de52ff4](https://github.com/Tencent/tmagic-editor/commit/de52ff4fe100cb11b73865703b03a0cbcc4e92e7))
### Features
* **form:** onChange添加setModel参数用于修改model并添加至修改记录中 ([5e0e776](https://github.com/Tencent/tmagic-editor/commit/5e0e776d4004c9a4ad2ae53d7146353dc088a478))
* **utils:** 添加设置删除获取url参数的方法 ([105de9d](https://github.com/Tencent/tmagic-editor/commit/105de9d2c6ba69eb9e7ca9777f16441e5aa4c1f0))
* **vue-runtime-help:** app不存在时抛异常保证app一定存在避免多余的可选链 ([fd7e737](https://github.com/Tencent/tmagic-editor/commit/fd7e737e8c4f4958c8d59d5f844f40cf1509724f))
## [1.5.13](https://github.com/Tencent/tmagic-editor/compare/v1.5.12...v1.5.13) (2025-04-10)
### Bug Fixes
* **data-source:** 数据源初始化时机比注册早回出现死循环 ([840c2c3](https://github.com/Tencent/tmagic-editor/commit/840c2c3c7d267f999e229f7720cbbdc1bf5e2436))
* 修复表格拖拽排序与表单选字操作冲突的问题 ([f91eb41](https://github.com/Tencent/tmagic-editor/commit/f91eb415dbdf23c8f05bc21617de67aef7bd524c))
### Features
* **core:** 支持传入env ([0bcd7d0](https://github.com/Tencent/tmagic-editor/commit/0bcd7d075518523809b639e8128583c87b90aa89))
* **core:** 支持自定义Node类 ([18524d8](https://github.com/Tencent/tmagic-editor/commit/18524d89fb8086a34307ed17e54da520db92add2))
* **editor:** 编辑代码块/数据源时高亮列表中对应的项 ([54e00f2](https://github.com/Tencent/tmagic-editor/commit/54e00f28524e546cecd0691d19fbcb0bee4af35e))
## [1.5.12](https://github.com/Tencent/tmagic-editor/compare/v1.5.11...v1.5.12) (2025-03-27)
### Bug Fixes
* **design:** card没有slots时隐藏对应slot ([4ddc55a](https://github.com/Tencent/tmagic-editor/commit/4ddc55aa6de4bafb978a621ab9b69af1e0c36d78))
* **editor:** 数据源修改后,依赖重新收集不准确 ([9c7d711](https://github.com/Tencent/tmagic-editor/commit/9c7d711c167c5e8ee1e9d8a8e89d66d245070dee))
* **editor:** 获取关联组件的方法默认值 ([f5742c1](https://github.com/Tencent/tmagic-editor/commit/f5742c107a68389c0828de991e7f5e9745e20d67))
* **vue-components:** button删除调试代码text支持富文本 ([2a714ae](https://github.com/Tencent/tmagic-editor/commit/2a714ae9cc89b7c88528c8afe9270ad774d7755d))
### Features
* **cli:** 如果识别不要组件文件则默认从npm包的default导入 ([b715a87](https://github.com/Tencent/tmagic-editor/commit/b715a87f409856ed396b3e35eb4102776329531e))
## [1.5.11](https://github.com/Tencent/tmagic-editor/compare/v1.5.10...v1.5.11) (2025-03-11)
### Bug Fixes
* **dep:** 依赖收集不能过滤id/name ([0c207c7](https://github.com/Tencent/tmagic-editor/commit/0c207c7b834ae25c16038a8ca431154afb4ccda1))
* **dep:** 数据源方法收集判断 ([72108e0](https://github.com/Tencent/tmagic-editor/commit/72108e00193698c8e7087b115f36e10216a0f386))
### Features
* **vue-runtime-help:** useDsl/useEditorDsl添加app默认参数 ([f671c67](https://github.com/Tencent/tmagic-editor/commit/f671c670db84631abb1f7d9e4fbf432637de2947))
## [1.5.10](https://github.com/Tencent/tmagic-editor/compare/v1.5.9...v1.5.10) (2025-03-07)
### Bug Fixes
* **data-source:** 处理异步动态加载数据源时,初始化数据源还未加载完毕的情况 ([3c66319](https://github.com/Tencent/tmagic-editor/commit/3c66319b03c42305c66b0c6a87307fe747d9f90e))
* **editor:** 样式配置宽度不能为负 ([775fcf5](https://github.com/Tencent/tmagic-editor/commit/775fcf5693eef051e5899aceae7873c1afa0e60c))
* **stage:** 防止runtime重复注册 ([63fe6ec](https://github.com/Tencent/tmagic-editor/commit/63fe6ec68be444082cb16aac76d6b2fe9ee5724f))
### Features
* **core:** hook函数调用添加node参数 ([5ba2a73](https://github.com/Tencent/tmagic-editor/commit/5ba2a73c7035d84688be0b0f1c0b4467558518c3))
* **core, editor:** 删除公共的点击事件实现,由组件自行添加 ([c8e1cff](https://github.com/Tencent/tmagic-editor/commit/c8e1cffca9b12c124c34d75ee9187a03c6f61599))
* **core:** app添加dsl-change事件 ([a4d021d](https://github.com/Tencent/tmagic-editor/commit/a4d021d2fb1fec113496733380d99e0f9deb132f))
* **core:** node新增registerMethod方法用于组件注册供其他组件通过事件配置调用 ([ff07147](https://github.com/Tencent/tmagic-editor/commit/ff0714727044c41148babcec977ca89058169501))
* **reate-runtime-help,vue-runtime-help:** 新增组件状态hook ([6f5bb84](https://github.com/Tencent/tmagic-editor/commit/6f5bb84c04f9b4b5b2145ce6c559445563a60e6c))
* **runtime:** vue2 h传参与vue3不一样需要自定义render ([90abde5](https://github.com/Tencent/tmagic-editor/commit/90abde57cc01218b483c94faf29f88996fb75c20))
* **runtime:** 支持页面切换 ([4026c0c](https://github.com/Tencent/tmagic-editor/commit/4026c0c305937b060de21ec74355a1be46e25dbf))
* **vue-components,react-components:** 增加点击事件使用组件状态hook ([0736646](https://github.com/Tencent/tmagic-editor/commit/0736646c63067b3b19125b356f462d658673eaa7))
* **vue-container:** 支持自定义render ([91cde30](https://github.com/Tencent/tmagic-editor/commit/91cde30d75de866ade3f48f48b2c0d1251fe2b36))
## [1.5.9](https://github.com/Tencent/tmagic-editor/compare/v1.5.8...v1.5.9) (2025-02-20)
### Bug Fixes
* **editor:** 编辑器销毁不应该移除依赖收集任务的事件监听 ([6e2b5ae](https://github.com/Tencent/tmagic-editor/commit/6e2b5aea47cc1059d4b92a85b9801d7af4f9b3b9))
### Features
* **table:** formatter添加index信息 ([4d77b66](https://github.com/Tencent/tmagic-editor/commit/4d77b669baf68f3346aa64d52dea6f75712b1af4))
* **table:** 支持type: index的序号列 ([6f2c6b1](https://github.com/Tencent/tmagic-editor/commit/6f2c6b151d59aca8899742c724c44dbde7d01a89))
## [1.5.8](https://github.com/Tencent/tmagic-editor/compare/v1.5.7...v1.5.8) (2025-02-11)
### Bug Fixes
* **editor:** 组件配置列大小缓存不生效 ([6d82c0f](https://github.com/Tencent/tmagic-editor/commit/6d82c0f730ff0ad7dc36b39b7c0d2534672b05c3))
### Features
* **editor:** 优化依赖收集状态显示,新增剩余任务数量显示 ([413134b](https://github.com/Tencent/tmagic-editor/commit/413134b21d3966bf7f1dbb1da6e2596f2c582bff))
## [1.5.7](https://github.com/Tencent/tmagic-editor/compare/v1.5.6...v1.5.7) (2025-02-11)
## [1.5.6](https://github.com/Tencent/tmagic-editor/compare/v1.5.5...v1.5.6) (2025-02-10)

View File

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

1
commitlint.config.cjs Normal file
View File

@ -0,0 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };

View File

@ -1 +0,0 @@
export default { extends: ['@commitlint/config-conventional'] };

View File

@ -24,7 +24,7 @@ export default defineConfig({
footer: {
message: 'Powered by 腾讯视频会员平台技术中心',
copyright: 'Copyright (C) 2025 Tencent.'
copyright: 'Copyright (C) 2023 THL A29 Limited, a Tencent company.'
},
nav: [
@ -402,7 +402,6 @@ export default defineConfig({
},
resolve: {
alias:[
{ find: /^@tmagic\/form-schema/, replacement: path.join(__dirname, '../../packages/form-schema/src/index.ts') },
{ find: /^@tmagic\/form/, replacement: path.join(__dirname, '../../packages/form/src/index.ts') },
{ find: /^@tmagic\/utils/, replacement: path.join(__dirname, '../../packages/utils/src/index.ts') },
{ find: /^@tmagic\/schema/, replacement: path.join(__dirname, '../../packages/schema/src/index.ts') },

View File

@ -17,7 +17,11 @@
<slot name="highlight"></slot>
</div>
</div>
<div class="demo-block-control" ref="control" @click="isExpanded = !isExpanded">
<div
class="demo-block-control"
ref="control"
@click="isExpanded = !isExpanded"
>
<transition name="arrow-slide">
<i :class="[iconClass, hovering]"></i>
</transition>
@ -26,14 +30,23 @@
</transition>
<el-tooltip effect="dark" :content="'前往 codepen.io 运行此示例'" placement="right">
<transition name="text-slide">
<el-button size="small" type="primary" text class="control-button" @click.stop="goCodepen">
{{ type === 'form' ? '查看结果' : '在线运行' }}
<el-button
size="small"
type="primary"
text
class="control-button"
@click.stop="goCodepen"
>
{{type === 'form' ? '查看结果' : '在线运行'}}
</el-button>
</transition>
</el-tooltip>
</div>
<el-dialog v-model="resultVisible" title="result">
<el-dialog
v-model="resultVisible"
title="result"
>
<pre><code class="language-javascript hljs" v-html="result"></code></pre>
</el-dialog>
</div>
@ -47,9 +60,7 @@
transition: 0.2s;
&.hover {
box-shadow:
0 0 8px 0 rgba(232, 237, 250, 0.6),
0 2px 4px 0 rgba(232, 237, 250, 0.5);
box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6), 0 2px 4px 0 rgba(232, 237, 250, 0.5);
}
code {
@ -208,7 +219,9 @@ export function stripTemplate(content) {
}
export default {
props: ['type', 'config'],
props: [
'type', 'config'
],
data() {
return {
@ -280,15 +293,12 @@ export default {
},
text() {
return this.isStringConfig
? hljs.highlight('js', this.config).value
: hljs.highlight(
'js',
serialize(this.config, {
space: 2,
unsafe: true,
}).replace(/"(\w+)":\s/g, '$1: '),
).value;
return this.isStringConfig ?
hljs.highlight('js', this.config).value :
hljs.highlight('js', serialize(this.config, {
space: 2,
unsafe: true,
}).replace(/"(\w+)":\s/g, '$1: ')).value;
},
formConfig() {
@ -297,7 +307,7 @@ export default {
isStringConfig() {
return typeof this.config === 'string';
},
}
},
watch: {
@ -346,7 +356,7 @@ export default {
});
},
beforeUnmount() {
beforeDestroy() {
this.removeScrollHandler();
},
};

View File

@ -4,101 +4,34 @@
- **参数:**
- {[ComponentGroup](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/editor/src/type.ts#L355)[]} componentGroupList 组件列表配置
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
设置左侧面板的组件列表配置
:::tip
该方法通常由编辑器内部调用,开发者可以通过 [m-editor 的 componentGroupList prop](./props.md#componentgrouplist) 来配置组件列表
:::
- **示例:**
```js
import { componentListService } from '@tmagic/editor';
componentListService.setList([
{
title: '基础组件',
items: [
{
icon: 'text-icon',
text: '文本',
type: 'text',
},
{
icon: 'button-icon',
text: '按钮',
type: 'button',
},
],
},
]);
```
## getList
- **参数:**
- **参数:**
-
- **返回:**
- {[ComponentGroup](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/editor/src/type.ts#L355)[]} 组件列表配置
- `{Promise<void>}`
- **详情:**
获取当前的组件列表配置
- **示例:**
```js
import { componentListService } from '@tmagic/editor';
const list = componentListService.getList();
console.log(list);
```
## resetState
- **参数:**
- **返回:**
- `{void}`
- **详情:**
重置组件列表状态,清空所有配置
- **示例:**
```js
import { componentListService } from '@tmagic/editor';
componentListService.resetState();
```
## destroy
- **参数:**
- **参数:**
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
销毁 componentListService清空状态并移除所有事件监听和插件
- **示例:**
```js
import { componentListService } from '@tmagic/editor';
componentListService.destroy();
```

View File

@ -2,540 +2,30 @@
## get
- **参数:**
- `{StateKey}` name 状态键名
- **返回:**
- `{any}` 对应的状态值
- **详情:**
获取数据源服务的内部状态
可用的状态键:
- `datasourceTypeList`: 数据源类型列表
- `dataSources`: 当前数据源列表
- `editable`: 是否可编辑
- `configs`: 数据源表单配置
- `values`: 数据源默认值
- `events`: 数据源事件列表
- `methods`: 数据源方法列表
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const dataSources = dataSourceService.get('dataSources');
console.log(dataSources);
```
## set
- **参数:**
- `{StateKey}` name 状态键名
- `{any}` value 状态值
- **返回:**
- `{void}`
- **详情:**
设置数据源服务的内部状态
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.set('editable', false);
```
## getFormConfig
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型,默认为 'base'
- **返回:**
- {[FormConfig](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/form/src/schema.ts#L706)} 表单配置
- **详情:**
获取指定类型数据源的表单配置
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const config = dataSourceService.getFormConfig('http');
console.log(config);
```
## setFormConfig
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型
- {[FormConfig](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/form/src/schema.ts#L706)} config 表单配置
- **返回:**
- `{void}`
- **详情:**
设置指定类型数据源的表单配置
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.setFormConfig('http', [
{
name: 'url',
text: '请求地址',
type: 'text',
},
{
name: 'method',
text: '请求方法',
type: 'select',
options: [
{ text: 'GET', value: 'GET' },
{ text: 'POST', value: 'POST' },
],
},
]);
```
## getFormValue
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型,默认为 'base'
- **返回:**
- {Partial<[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)>} 数据源默认值
- **详情:**
获取指定类型数据源的默认值
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const defaultValue = dataSourceService.getFormValue('http');
console.log(defaultValue);
```
## setFormValue
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型
- {Partial<[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)>} value 数据源默认值
- **返回:**
- `{void}`
- **详情:**
设置指定类型数据源的默认值
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.setFormValue('http', {
type: 'http',
method: 'GET',
url: '',
});
```
## getFormEvent
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型,默认为 'base'
- **返回:**
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 事件列表
- **详情:**
获取指定类型数据源的事件列表
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const events = dataSourceService.getFormEvent('http');
console.log(events);
```
## setFormEvent
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} value 事件列表
- **返回:**
- `{void}`
- **详情:**
设置指定类型数据源的事件列表
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.setFormEvent('http', [
{ label: '请求成功', value: 'success' },
{ label: '请求失败', value: 'error' },
]);
```
## getFormMethod
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型,默认为 'base'
- **返回:**
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 方法列表
- **详情:**
获取指定类型数据源的方法列表
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const methods = dataSourceService.getFormMethod('http');
console.log(methods);
```
## setFormMethod
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` type 数据源类型
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} value 方法列表
- **返回:**
- `{void}`
- **详情:**
设置指定类型数据源的方法列表
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.setFormMethod('http', [
{ label: '发起请求', value: 'request' },
{ label: '重试', value: 'retry' },
]);
```
## add
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} config 数据源配置
- **返回:**
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} 添加后的数据源配置
- **详情:**
添加一个数据源如果配置中没有id或id已存在会自动生成新的id
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const newDs = dataSourceService.add({
type: 'http',
title: '用户信息',
url: '/api/user',
method: 'GET',
});
console.log(newDs.id); // 自动生成的id
```
## update
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} config 数据源配置
- `{Object}` options 可选配置
- {[ChangeRecord](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/form/src/schema.ts#L27-L39)[]} changeRecords 变更记录
- **返回:**
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221)} 更新后的数据源配置
- **详情:**
更新数据源
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const updatedDs = dataSourceService.update({
id: 'ds_123',
type: 'http',
title: '用户详情',
url: '/api/user/detail',
});
console.log(updatedDs);
```
## remove
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` id 数据源id
- **返回:**
- `{void}`
- **详情:**
删除指定id的数据源
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.remove('ds_123');
```
## createId
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- **返回:**
- `{string}` 生成的唯一id
- **详情:**
生成一个唯一的数据源id格式为 `ds_` + guid
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const id = dataSourceService.createId();
console.log(id); // 'ds_xxx-xxx-xxx'
```
## getDataSourceById
- **参数:**
- `{string}` id 数据源id
- **返回:**
- {[DataSourceSchema](https://github.com/Tencent/tmagic-editor/blob/5880dfbe15fcead63e9dc7c91900f8c4e7a574d8/packages/schema/src/index.ts#L221) | undefined} 数据源配置
- **详情:**
根据id获取数据源配置
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
const ds = dataSourceService.getDataSourceById('ds_123');
console.log(ds);
```
## copyWithRelated
- **参数:**
- {[MNode](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/schema/src/index.ts#L99) | [MNode](https://github.com/Tencent/tmagic-editor/blob/c143a5f7670ae61d80c1a2cfcc780cfb5259849d/packages/schema/src/index.ts#L99)[]} config 组件节点配置
- `{TargetOptions}` collectorOptions 可选的收集器配置
- **返回:**
- `{void}`
- **详情:**
复制组件时会带上组件关联的数据源,将关联的数据源存储到 localStorage
- **示例:**
```js
import { dataSourceService, editorService } from '@tmagic/editor';
const node = editorService.get('node');
dataSourceService.copyWithRelated(node);
```
## paste
- **参数:**
- **返回:**
- `{void}`
- **详情:**
粘贴数据源,从 localStorage 中读取复制的数据源并添加到当前页面
如果数据源id已存在则不会覆盖
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.paste();
```
## resetState
- **参数:**
- **返回:**
- `{void}`
- **详情:**
重置数据源服务状态,清空所有数据源
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.resetState();
```
## destroy
- **参数:**
- **返回:**
- `{void}`
- **详情:**
销毁 dataSourceService移除所有事件监听并重置状态
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.destroy();
```
## usePlugin
- **详情:**
相对于[use](#use), usePlugin支持更加灵活更加细致的扩展 上述方法中标记有`扩展支持: 是`的方法都支持使用usePlugin扩展
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为before可以用于修改传入参数after可以用于修改返回的值
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.usePlugin({
beforeAdd(config) {
console.log('添加前:', config);
return [config];
},
afterAdd(result, config) {
console.log('添加后:', result);
return result;
},
});
```
## removeAllPlugins
- **详情:**
删掉当前设置的所有扩展
- **示例:**
```js
import { dataSourceService } from '@tmagic/editor';
dataSourceService.removeAllPlugins();
```

View File

@ -1,244 +1,97 @@
# eventsService方法
# eventService方法
## init
- **参数:**
- {Record<string, { events: [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]; methods: [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[] }>} eventMethodList 事件方法列表配置
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
初始化事件服务,设置所有组件的事件和方法列表
:::tip
该方法通常由编辑器内部调用,开发者可以通过 [m-editor 的 eventMethodList prop](./props.md#eventmethodlist) 来配置
:::
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
eventsService.init({
page: {
events: [
{ label: '页面加载', value: 'load' },
{ label: '页面卸载', value: 'unload' },
],
methods: [
{ label: '刷新', value: 'refresh' },
{ label: '返回', value: 'back' },
],
},
button: {
events: [
{ label: '点击', value: 'click' },
],
methods: [],
},
});
```
## setEvents
- **参数:**
- {Record<string, [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]>} events 事件配置对象
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
批量设置多个组件类型的事件列表
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
eventsService.setEvents({
page: [
{ label: '页面加载', value: 'load' },
{ label: '页面显示', value: 'show' },
],
text: [
{ label: '点击', value: 'click' },
],
});
```
## setEvent
- **参数:**
- `{string}` type 组件类型
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} events 事件列表
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
设置指定组件类型的事件列表
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
eventsService.setEvent('button', [
{ label: '点击', value: 'click' },
{ label: '长按', value: 'longpress' },
]);
```
## getEvent
- **参数:**
- `{string}` type 组件类型
-
- **返回:**
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 事件列表
- `{Promise<void>}`
- **详情:**
获取指定组件类型的事件列表
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
const events = eventsService.getEvent('button');
console.log(events); // [{ label: '点击', value: 'click' }, ...]
```
## setMethods
- **参数:**
- {Record<string, [EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]>} methods 方法配置对象
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
批量设置多个组件类型的方法列表
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
eventsService.setMethods({
page: [
{ label: '刷新', value: 'refresh' },
{ label: '滚动到顶部', value: 'scrollToTop' },
],
video: [
{ label: '播放', value: 'play' },
{ label: '暂停', value: 'pause' },
],
});
```
## setMethod
- **参数:**
- `{string}` type 组件类型
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} methods 方法列表
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
设置指定组件类型的方法列表
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
eventsService.setMethod('video', [
{ label: '播放', value: 'play' },
{ label: '暂停', value: 'pause' },
{ label: '停止', value: 'stop' },
]);
```
## getMethod
- **参数:**
- `{string}` type 组件类型
-
- **返回:**
- {[EventOption](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/core/src/events.ts#L26-L29)[]} 方法列表
- `{Promise<void>}`
- **详情:**
获取指定组件类型的方法列表
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
const methods = eventsService.getMethod('video');
console.log(methods); // [{ label: '播放', value: 'play' }, ...]
```
## resetState
- **参数:**
- **返回:**
- `{void}`
- **详情:**
重置事件服务状态,清空所有事件和方法配置
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
eventsService.resetState();
```
## destroy
- **参数:**
- **参数:**
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
销毁 eventsService重置状态并移除所有事件监听和插件
- **示例:**
```js
import { eventsService } from '@tmagic/editor';
eventsService.destroy();
```

View File

@ -375,7 +375,7 @@ const stageContentMenu = ref([
```html
<template>
<m-editor
runtime-url="https://tencent.github.io/tmagic-editor/playground/runtime/vue/playground/index.html"
runtime-url="https://tencent.github.io/tmagic-editor/playground/runtime/vue3/playground/index.html"
></m-editor>
</template>
```
@ -415,26 +415,6 @@ const renderFunction = async (stage) => {
</script>
```
## renderType
- **详情:**
是用iframe渲染还是直接渲染
- **默认值:** `iframe`
- **类型:** `string`
'iframe' | 'native'
- **示例:**
```html
<template>
<m-editor render-type="native"></m-editor>
</template>
```
## autoScrollIntoView
- **详情:**
@ -675,7 +655,7 @@ const datasourceConfigs = {
- **默认值:** `{}`
- **类型:** ((config: [CustomizeMoveableOptionsCallbackConfig](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/stage/src/types.ts#L97-L109)) => MoveableOptions) | [MoveableOptions](https://daybrush.com/moveable/release/latest/doc/)
- **类型:** ((config?: [CustomizeMoveableOptionsCallbackConfig](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/stage/src/types.ts#L97-L109)) => MoveableOptions) | [MoveableOptions](https://daybrush.com/moveable/release/latest/doc/)
- **示例:**
@ -963,320 +943,3 @@ const updateDragEl = (el, target) => {
</template>
```
## guidesOptions
- **详情:**
画布标尺和参考线的配置选项
- **默认值:** `undefined`
- **类型:** `Partial<GuidesOptions>`
- **示例:**
```html
<template>
<m-editor :guides-options="guidesOptions"></m-editor>
</template>
<script setup>
const guidesOptions = {
// 标尺刻度单位
unit: 1,
// 标尺背景色
backgroundColor: '#f0f0f0',
// 标尺文字颜色
textColor: '#333',
// 参考线颜色
lineColor: '#ff0000',
};
</script>
```
## disabledPageFragment
- **详情:**
禁用页面片功能
页面片是可以在多个页面中复用的组件集合
- **默认值:** `false`
- **类型:** `boolean`
- **示例:**
```html
<template>
<m-editor :disabled-page-fragment="true"></m-editor>
</template>
```
## disabledStageOverlay
- **详情:**
禁用双击在浮层中单独编辑选中组件的功能
启用时,双击组件可以在浮层中单独编辑,避免其他组件干扰
- **默认值:** `false`
- **类型:** `boolean`
- **示例:**
```html
<template>
<m-editor :disabled-stage-overlay="true"></m-editor>
</template>
```
## disabledShowSrc
- **详情:**
禁用属性配置面板右下角"显示源码"的按钮
该按钮可以查看和编辑组件的 JSON 配置
- **默认值:** `false`
- **类型:** `boolean`
- **示例:**
```html
<template>
<m-editor :disabled-show-src="true"></m-editor>
</template>
```
## disabledDataSource
- **详情:**
禁用数据源功能
禁用后,左侧面板将不显示数据源选项卡
- **默认值:** `false`
- **类型:** `boolean`
- **示例:**
```html
<template>
<m-editor :disabled-data-source="true"></m-editor>
</template>
```
## disabledCodeBlock
- **详情:**
禁用代码块功能
禁用后,左侧面板将不显示代码块选项卡
- **默认值:** `false`
- **类型:** `boolean`
- **示例:**
```html
<template>
<m-editor :disabled-code-block="true"></m-editor>
</template>
```
## treeIndent
- **详情:**
组件树、代码块列表、数据源列表的缩进配置单位px
- **默认值:** `undefined`
- **类型:** `number`
- **示例:**
```html
<template>
<m-editor :tree-indent="20"></m-editor>
</template>
```
## treeNextLevelIndentIncrement
- **详情:**
组件树、代码块列表、数据源列表子节点缩进增量配置单位px
每一级子节点会在父节点缩进基础上增加该值
- **默认值:** `undefined`
- **类型:** `number`
- **示例:**
```html
<template>
<!-- 第一级缩进20px第二级缩进35px第三级缩进50px -->
<m-editor :tree-indent="20" :tree-next-level-indent-increment="15"></m-editor>
</template>
```
## customContentMenu
- **详情:**
用于自定义组件树与画布的右键菜单
该函数会在显示右键菜单前被调用,接收默认菜单项作为参数,返回最终显示的菜单项
- **默认值:** `(menus) => menus`
- **类型:** `(menus: (MenuButton | MenuComponent)[], data: { node?: MNode; page?: MPage; parent?: MContainer; stage?: StageCore }) => (MenuButton | MenuComponent)[]`
- **示例:**
```html
<template>
<m-editor :custom-content-menu="customContentMenu"></m-editor>
</template>
<script setup>
const customContentMenu = (menus, { node }) => {
// 为特定类型的组件添加自定义菜单
if (node?.type === 'container') {
menus.push({
type: 'button',
text: '清空容器',
handler: () => {
// 清空容器的逻辑
},
});
}
// 可以过滤掉某些菜单项
return menus.filter(menu => menu.text !== '删除');
};
</script>
```
## extendFormState
- **详情:**
扩展表单状态
用于在属性表单中注入自定义的状态数据,这些数据可以在表单配置的各个字段为函数时的第一个参数中获取
- **默认值:** `undefined`
- **类型:** `(state: FormState) => Record<string, any> | Promise<Record<string, any>>`
- **示例:**
```html
<template>
<m-editor :extend-form-state="extendFormState"></m-editor>
</template>
<script setup>
const extendFormState = async (state) => {
// 返回自定义的状态数据
return {
// 可以是同步数据
currentUser: {
name: 'Admin',
role: 'admin',
},
// 也可以是异步获取的数据
projectConfig: await fetchProjectConfig(),
};
};
</script>
```
:::tip
扩展的状态可以在表单配置中通过 `state` 访问,例如:
```js
{
name: 'title',
text: '标题',
// 根据扩展的状态动态设置
disabled: (state) => state.currentUser.role !== 'admin',
}
```
:::
## pageBarSortOptions
- **详情:**
页面标签栏的拖拽排序配置参数
用于配置页面标签的拖拽排序行为
- **默认值:** `undefined`
- **类型:** [PageBarSortOptions](https://github.com/Tencent/tmagic-editor/blob/master/packages/editor/src/type.ts)
- **示例:**
```html
<template>
<m-editor :page-bar-sort-options="sortOptions"></m-editor>
</template>
<script setup>
const sortOptions = {
// 是否启用拖拽排序
animation: 150,
// 拖拽手柄的class
handle: '.page-bar-item',
// 其他 sortablejs 配置
};
</script>
```
## pageFilterFunction
- **详情:**
页面搜索/过滤函数
用于自定义页面的搜索逻辑,在页面列表中输入关键词时会调用该函数进行过滤
- **默认值:** `undefined`
- **类型:** `(page: MPage | MPageFragment, keyword: string) => boolean`
- **示例:**
```html
<template>
<m-editor :page-filter-function="pageFilterFunction"></m-editor>
</template>
<script setup>
const pageFilterFunction = (page, keyword) => {
// 自定义搜索逻辑
// 不仅搜索页面名称,还搜索页面的其他属性
return (
page.name?.includes(keyword) ||
page.title?.includes(keyword) ||
page.id?.includes(keyword)
);
};
</script>
```

View File

@ -1,111 +1,29 @@
# Editor组件 slots
## header
- **详情:** 编辑器最顶部区域
- **默认:**
- **示例:**
```html
<template>
<m-editor>
<template #header>
<div class="custom-header">自定义头部内容</div>
</template>
</m-editor>
</template>
```
## nav
- **详情:** 编辑器顶部菜单栏
- **默认:** [NavMenu.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/NavMenu.vue)
- **插槽 Props**
- `editorService`: editorService 实例
:::warning
属性配置[menu](./props.md#menu)由默认组件接收如设置该slot[menu](./props.md#menu)配置将失效
:::
- **示例:**
```html
<template>
<m-editor>
<template #nav="{ editorService }">
<div class="custom-nav">
<button @click="save">保存</button>
</div>
</template>
</m-editor>
</template>
```
## content-before
- **详情:** 编辑器主要内容区域之前
- **默认:**
## src-code
- **详情:** 源码查看区域
- **默认:** 默认的代码编辑器
- **插槽 Props**
- `editorService`: editorService 实例
## sidebar
- **详情:** 左边栏
- **默认:** [Sidebar.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/sidebar/Sidebar.vue)
- **插槽 Props**
- `editorService`: editorService 实例
:::warning
属性配置[sidebar](./props.md#sidebar)由默认组件接收如设置该slot[sidebar](./props.md#sidebar)配置将失效
:::
- **示例:**
```html
<template>
<m-editor>
<template #sidebar="{ editorService }">
<div class="custom-sidebar">
<!-- 自定义侧边栏内容 -->
</div>
</template>
</m-editor>
</template>
```
## component-list
- **详情:** 左边栏中的组件列表
- **默认:** 默认的组件列表
- **插槽 Props**
- `componentGroupList`: 组件分组列表
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
## component-list-panel-header
- **详情:** 左边栏中的组件列表内上方位置
- **默认:**
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
@ -116,84 +34,24 @@
- **默认:** 图片加文案
- **插槽 Props**
- `component`: 组件配置对象
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
- **示例:**
```html
<template>
<m-editor>
<template #component-list-item="{ component }">
<div class="custom-item">
<span>{{ component.text }}</span>
</div>
</template>
</m-editor>
</template>
```
## layer-panel-header
- **详情:** 左边栏中的已选组件(组件树)内顶部位置
- **默认:**
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
## layer-node-content
- **详情:** 左边栏中的已选组件(组件树)节点完整内容
- **默认:** 组件名称加id和工具按钮
- **插槽 Props**
- `data`: 节点数据
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
## layer-node-label
- **详情:** 左边栏中的已选组件(组件树)节点标签部分
- **详情:** 左边栏中的已选组件(组件树)节点
- **默认:** 组件名称加id
- **插槽 Props**
- `data`: 节点数据
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
- **示例:**
```html
<template>
<m-editor>
<template #layer-node-label="{ data }">
<span>{{ data.type }} - {{ data.name }}</span>
</template>
</m-editor>
</template>
```
## layer-node-tool
- **详情:** 左边栏中的已选组件(组件树)节点右侧工具区域
- **默认:**
- **插槽 Props**
- `data`: 节点数据
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
@ -202,8 +60,6 @@
- **详情:** 左边栏中的代码块列表内顶部位置
- **默认:**
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
@ -212,44 +68,13 @@
- **详情:** 左边栏中的代码块列表中代码块右侧位置
- **默认:**
- **插槽 Props**
- `id`: 代码块id
- `data`: 代码块数据
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
## code-block-panel-search
## code-block-edit-panel-header
- **详情:** 左边栏中的代码块列表搜索框位置
- **默认:**
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
## data-source-panel-tool
- **详情:** 左边栏中的数据源列表中数据源右侧位置
- **默认:**
- **插槽 Props**
- `data`: 数据源数据
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
:::
## data-source-panel-search
- **详情:** 左边栏中的数据源列表搜索框位置
- **默认:**
- **详情:** 代码块弹窗编辑器中弹窗顶部区域
:::warning
如设置了[sidebar](#sidebar)插槽,此插槽将失效
@ -261,9 +86,6 @@
- **默认:** [Workspace.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/workspace/Workspace.vue)
- **插槽 Props**
- `editorService`: editorService 实例
## stage
- **详情:** 画布
@ -272,63 +94,15 @@
## workspace-content
- **详情:** 编辑器中间区域内,画布上方位置
- **默认:**
- **插槽 Props**
- `editorService`: editorService 实例
## page-bar
- **详情:** 编辑器中间区域底部页面标签栏
- **默认:** 默认的页面标签栏
## page-bar-add-button
- **详情:** 页面标签栏中的"添加页面"按钮
- **默认:** 默认的添加按钮
- **详情:** 编辑器中间区域内
## page-bar-title
- **详情:** 编辑器中间区域底部页面标题
- **默认:** 页面名称
- **插槽 Props**
- `page`: 页面配置对象
- **示例:**
```html
<template>
<m-editor>
<template #page-bar-title="{ page }">
<span>{{ page.name }} - {{ page.id }}</span>
</template>
</m-editor>
</template>
```
## page-bar-popover
- **详情:** 编辑器中间区域底部页面标题悬浮框内容
- **默认:** 页面详细信息
- **插槽 Props**
- `page`: 页面配置对象
## page-list-popover
- **详情:** 页面列表弹出框内容
- **默认:** 页面列表
- **插槽 Props**
- `list`: 页面列表
- **详情:** 编辑器中间区域底部页面标题悬浮框
## props-panel
@ -340,40 +114,8 @@
- **详情:** 编辑器右侧属性配置内顶部区域
- **默认:**
## content-after
- **详情:** 编辑器主要内容区域之后
- **默认:**
## footer
- **详情:** 编辑器底部区域
- **默认:**
## empty
- **详情:** 当前没有页面时,编辑器中间区域
- **默认:** [AddPageBox.vue](https://github.com/Tencent/tmagic-editor/blob/239b5d3efeae916a8cf3e3566d88063ecccc0553/packages/editor/src/layouts/AddPageBox.vue)
- **插槽 Props**
- `editorService`: editorService 实例
- **示例:**
```html
<template>
<m-editor>
<template #empty="{ editorService }">
<div class="custom-empty">
<p>暂无页面</p>
<button @click="createFirstPage">创建第一个页面</button>
</div>
</template>
</m-editor>
</template>
```

View File

@ -4,250 +4,114 @@
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- **参数:**
-
- **返回:**
- `{Storage}` Storage 对象
- `{Promise<void>}`
- **详情:**
获取数据存储对象,默认返回 localStorage
可以通过插件机制替换为其他存储对象(如 sessionStorage
- **示例:**
```js
import { storageService } from '@tmagic/editor';
const storage = storageService.getStorage();
console.log(storage); // localStorage
// 通过插件替换为 sessionStorage
storageService.usePlugin({
afterGetStorage() {
return window.sessionStorage;
},
});
```
## getNamespace
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- **参数:**
-
- **返回:**
- `{string}` 命名空间字符串
- `{Promise<void>}`
- **详情:**
获取存储项的命名空间,默认为 'tmagic'
命名空间用于区分不同应用的存储数据
- **示例:**
```js
import { storageService } from '@tmagic/editor';
const namespace = storageService.getNamespace();
console.log(namespace); // 'tmagic'
```
## clear
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- **参数:**
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
清空当前存储对象中的所有数据
- **示例:**
```js
import { storageService } from '@tmagic/editor';
storageService.clear();
```
## getItem
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` key 存储项的键名
- `{Options}` options 可选配置
- `namespace?: string` 自定义命名空间
- `protocol?: Protocol` 数据协议类型
-
- **返回:**
- `{any}` 存储的值,如果不存在返回 null
- `{Promise<void>}`
- **详情:**
获取存储项,支持多种数据类型的自动解析
支持的协议类型:
- `Protocol.OBJECT`: JavaScript 对象
- `Protocol.JSON`: JSON 格式
- `Protocol.NUMBER`: 数字类型
- `Protocol.BOOLEAN`: 布尔类型
- `Protocol.STRING`: 字符串类型
- **示例:**
```js
import { storageService } from '@tmagic/editor';
// 获取字符串
const str = storageService.getItem('myKey');
// 使用自定义命名空间
const value = storageService.getItem('key', { namespace: 'custom' });
// 指定协议类型
const num = storageService.getItem('count', { protocol: Protocol.NUMBER });
```
## key
- **参数:**
- `{number}` index 索引位置
-
- **返回:**
- `{string | null}` 指定位置的键名,不存在返回 null
- `{Promise<void>}`
- **详情:**
获取存储对象中指定索引位置的键名
- **示例:**
```js
import { storageService } from '@tmagic/editor';
const firstKey = storageService.key(0);
console.log(firstKey);
```
## removeItem
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` key 存储项的键名
- `{Options}` options 可选配置
- `namespace?: string` 自定义命名空间
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
移除指定的存储项
- **示例:**
```js
import { storageService } from '@tmagic/editor';
// 移除默认命名空间下的存储项
storageService.removeItem('myKey');
// 移除自定义命名空间下的存储项
storageService.removeItem('key', { namespace: 'custom' });
```
## setItem
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{string}` key 存储项的键名
- `{any}` value 要存储的值
- `{Options}` options 可选配置
- `namespace?: string` 自定义命名空间
- `protocol?: Protocol` 数据协议类型
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
设置存储项,自动序列化复杂数据类型
- **示例:**
```js
import { storageService, Protocol } from '@tmagic/editor';
// 存储字符串
storageService.setItem('name', 'tmagic');
// 存储对象
storageService.setItem('config', { a: 1, b: 2 }, { protocol: Protocol.OBJECT });
// 存储数字
storageService.setItem('count', 100, { protocol: Protocol.NUMBER });
// 使用自定义命名空间
storageService.setItem('key', 'value', { namespace: 'custom' });
```
## destroy
- **参数:**
- **参数:**
-
- **返回:**
- `{void}`
- `{Promise<void>}`
- **详情:**
销毁 storageService移除所有事件监听和插件
- **示例:**
```js
import { storageService } from '@tmagic/editor';
storageService.destroy();
```
## use
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
- **示例:**
```js
import { storageService } from '@tmagic/editor';
storageService.use({
getItem(key, options, next) {
console.log('获取存储项:', key);
return next();
},
});
```
## usePlugin
- **详情:**
@ -256,35 +120,9 @@ storageService.use({
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为before可以用于修改传入参数after可以用于修改返回的值
- **示例:**
```js
import { storageService } from '@tmagic/editor';
storageService.usePlugin({
beforeSetItem(key, value, options) {
console.log('设置前:', key, value);
return [key, value, options];
},
afterGetItem(result, key, options) {
console.log('获取后:', result);
return result;
},
});
```
## removeAllPlugins
- **详情:**
删掉当前设置的所有扩展
- **示例:**
```js
import { storageService } from '@tmagic/editor';
storageService.removeAllPlugins();
```

View File

@ -1,89 +1,12 @@
# uiService方法
## set
- **参数:**
- `{keyof UiState}` name 状态键名
- `{any}` value 状态值
- **返回:**
- `{void}`
- **详情:**
设置UI服务的状态
可用的状态键:
- `uiSelectMode`: UI选择模式
- `showSrc`: 是否显示源码
- `showStylePanel`: 是否显示样式面板
- `zoom`: 缩放比例
- `stageContainerRect`: 画布容器尺寸
- `stageRect`: 画布尺寸
- `columnWidth`: 列宽度配置
- `showGuides`: 是否显示参考线
- `showRule`: 是否显示标尺
- `propsPanelSize`: 属性面板尺寸
- `showAddPageButton`: 是否显示添加页面按钮
- `showPageListButton`: 是否显示页面列表按钮
- `hideSlideBar`: 是否隐藏侧边栏
- `sideBarItems`: 侧边栏项目
- `navMenuRect`: 导航菜单尺寸
- `frameworkRect`: 框架尺寸
- **示例:**
```js
import { uiService } from '@tmagic/editor';
// 设置缩放比例
uiService.set('zoom', 1.5);
// 设置画布尺寸
uiService.set('stageRect', { width: 375, height: 667 });
// 显示/隐藏参考线
uiService.set('showGuides', true);
// 显示/隐藏标尺
uiService.set('showRule', true);
```
## get
- **参数:**
- `{keyof UiState}` name 状态键名
- **返回:**
- `{any}` 对应的状态值
- **详情:**
获取UI服务的状态值
- **示例:**
```js
import { uiService } from '@tmagic/editor';
const zoom = uiService.get('zoom');
console.log('当前缩放:', zoom);
const stageRect = uiService.get('stageRect');
console.log('画布尺寸:', stageRect);
```
## zoom
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- `{number}` zoom 缩放增量(可以为负数)
- `{number}` zoom 缩放倍数
- **返回:**
@ -91,111 +14,24 @@ console.log('画布尺寸:', stageRect);
- **详情:**
调整缩放倍数最小为0.1
传入的值会被累加到当前缩放倍数上
- **示例:**
```js
import { uiService } from '@tmagic/editor';
// 放大0.1倍
await uiService.zoom(0.1);
// 缩小0.1倍
await uiService.zoom(-0.1);
// 当前缩放如果是1.0执行zoom(0.5)后变为1.5
await uiService.zoom(0.5);
```
设置缩放倍数最小为0.1
## calcZoom
- **[扩展支持](../../guide/editor-expand#行为扩展)** 是
- **参数:**
- **返回:**
- `{Promise<number>}` 计算出的缩放倍数
- `{Promise<number>}`
- **详情:**
计算"缩放以适应"的倍数
根据画布容器的尺寸和画布尺寸自动计算出合适的缩放比例,使画布完全显示在容器内
- **示例:**
```js
import { uiService } from '@tmagic/editor';
const fitZoom = await uiService.calcZoom();
console.log('适应缩放:', fitZoom);
// 应用缩放以适应
uiService.set('zoom', fitZoom);
```
## resetState
- **参数:**
- **返回:**
- `{void}`
- **详情:**
重置UI服务状态到初始值
- **示例:**
```js
import { uiService } from '@tmagic/editor';
uiService.resetState();
```
## destroy
- **参数:**
- **返回:**
- `{void}`
- **详情:**
销毁 uiService重置状态并移除所有事件监听和插件
- **示例:**
```js
import { uiService } from '@tmagic/editor';
uiService.destroy();
```
计算出缩放以适应的倍数
## use
使用中间件的方式扩展方法,上述方法中标记有`扩展支持: 是`的方法都支持使用use扩展
- **示例:**
```js
import { uiService } from '@tmagic/editor';
uiService.use({
async zoom(value, next) {
console.log('缩放前:', uiService.get('zoom'));
await next();
console.log('缩放后:', uiService.get('zoom'));
},
});
```
## usePlugin
- **详情:**
@ -204,36 +40,9 @@ uiService.use({
每个支持扩展的方法都支持定制before、after两个hook来干预原有方法的行为before可以用于修改传入参数after可以用于修改返回的值
- **示例:**
```js
import { uiService } from '@tmagic/editor';
uiService.usePlugin({
beforeZoom(value) {
console.log('缩放增量:', value);
return [value];
},
afterCalcZoom(result) {
console.log('计算的缩放:', result);
// 可以修改返回值
return result;
},
});
```
## removeAllPlugins
- **详情:**
删掉当前设置的所有扩展
- **示例:**
```js
import { uiService } from '@tmagic/editor';
uiService.removeAllPlugins();
```

View File

@ -2,109 +2,14 @@
## sort-change
- **参数:**
- `{ column, prop, order }` - 排序信息对象
- `column: Object` - 排序的列配置
- `prop: string` - 排序的列属性名
- `order: 'ascending' | 'descending' | null` - 排序方式
- **说明:** 当表格的排序条件发生变化时触发
- **示例:**
```js
const handleSortChange = ({ column, prop, order }) => {
console.log('排序变化:', prop, order);
};
```
## after-action
- **参数:**
- `action: string` - 操作类型
- `data: any` - 操作相关数据
- **说明:** 表格操作完成后触发
- **示例:**
```js
const handleAfterAction = (action, data) => {
console.log('操作完成:', action, data);
};
```
## select
- **参数:**
- `selection: Array<any>` - 当前选中的行数据数组
- `row: any` - 刚刚被选中的行数据
- **说明:** 当用户手动勾选某一行时触发
- **示例:**
```js
const handleSelect = (selection, row) => {
console.log('选中行:', row);
console.log('当前选中:', selection);
};
```
## select-all
- **参数:**
- `selection: Array<any>` - 当前选中的行数据数组
- **说明:** 当用户手动勾选全选 Checkbox 时触发
- **示例:**
```js
const handleSelectAll = (selection) => {
console.log('全选/取消全选:', selection.length);
};
```
## selection-change
- **参数:**
- `selection: Array<any>` - 当前选中的行数据数组
- **说明:** 当选择项发生变化时触发
- **示例:**
```js
const handleSelectionChange = (selection) => {
console.log('选中项变化:', selection);
};
```
## expand-change
- **参数:**
- `row: any` - 被展开/收起的行数据
- `expandedRows: Array<any>` - 当前所有展开的行数据数组
- **说明:** 当用户展开或收起某一行时触发(用于可展开表格)
- **示例:**
```js
const handleExpandChange = (row, expandedRows) => {
console.log('展开状态变化:', row);
console.log('当前展开行:', expandedRows);
};
```
## cell-click
- **参数:**
- `row: any` - 行数据
- `column: Object` - 列配置
- `cell: HTMLElement` - 单元格 DOM 元素
- `event: Event` - 原生事件对象
- **说明:** 当某个单元格被点击时触发
- **示例:**
```js
const handleCellClick = (row, column, cell, event) => {
console.log('单元格点击:', row, column.property);
};
```

View File

@ -2,40 +2,6 @@
## toggleRowSelection
- **参数:**
- `row: any` - 要切换选中状态的行数据
- `selected?: boolean` - 是否选中,不传则切换当前状态
- **说明:** 切换某一行的选中状态
- **示例:**
```js
tableRef.value.toggleRowSelection(row, true); // 选中
tableRef.value.toggleRowSelection(row, false); // 取消选中
tableRef.value.toggleRowSelection(row); // 切换状态
```
## toggleRowExpansion
- **参数:**
- `row: any` - 要展开/收起的行数据
- `expanded?: boolean` - 是否展开,不传则切换当前状态
- **说明:** 切换某一行的展开状态(用于可展开表格)
- **示例:**
```js
tableRef.value.toggleRowExpansion(row, true); // 展开
tableRef.value.toggleRowExpansion(row, false); // 收起
```
## clearSelection
- **参数:**
- **说明:** 清空所有选中的行
- **示例:**
```js
tableRef.value.clearSelection();
```

View File

@ -2,122 +2,123 @@
## data
- **详情:** 表格数据,数组格式
- **详情:**
- **默认值:** `[]`
- **类型:** `Array<any>`
- **示例:**
```js
[
{ id: 1, name: '张三', age: 20 },
{ id: 2, name: '李四', age: 25 }
]
```
- **默认值:**
- **类型:**
## columns
- **详情:** 表格列配置
- **详情:**
- **默认值:** `[]`
- **类型:** `Array<ColumnConfig>`
- **示例:**
```js
[
{ prop: 'name', label: '姓名', width: 120 },
{ prop: 'age', label: '年龄', width: 80 }
]
```
- **默认值:**
- **类型:**
## spanMethod
- **详情:** 合并行或列的计算方法
- **默认值:** `undefined`
- **类型:** `Function`
- **参数:**
- `{ row, column, rowIndex, columnIndex }`
- **默认值:**
- **返回值:** `[rowspan, colspan]``{ rowspan, colspan }`
- **示例:**
```js
({ rowIndex, columnIndex }) => {
if (rowIndex % 2 === 0) {
if (columnIndex === 0) {
return [1, 2];
} else if (columnIndex === 1) {
return [0, 0];
}
}
}
```
- **类型:**
## loading
- **详情:** 是否显示加载状态
- **详情:**
- **默认值:** `false`
- **类型:** `boolean`
- **默认值:**
- **类型:**
## showHeader
- **详情:** 是否显示表头
- **默认值:** `true`
- **类型:** `boolean`
- **默认值:**
- **类型:**
## bodyHeight
- **详情:** Table 的最大高度。合法的值为数字或者单位为 px 的高度
- **详情:** Table的最大高度。合法的值为数字或者单位为 px 的高度
- **默认值:** `undefined`
- **默认值:**
- **类型:** `string | number`
- **示例:**
```js
bodyHeight: 400
bodyHeight: '400px'
```
- **类型:**
## emptyText
- **详情:** 空数据时显示的文本内容
- **默认值:** `'暂无数据'`
- **默认值:**
- **类型:**
- **类型:** `string`
## defaultExpandAll
- **详情:** 是否默认展开所有行,当 Table 包含展开行存在或者为树形表格时有效
- **详情:** 是否默认展开所有行当Table包含展开行存在或者为树形表格时有效
- **默认值:**
- **类型:**
- **默认值:** `false`
- **类型:** `boolean`
## rowkeyName
- **详情:** 行数据的 Key用来优化 Table 的渲染
- **详情:**
- **默认值:** `'id'`
- **类型:** `string`
- **说明:** 在使用 reserve-selection 功能与显示树形数据时,该属性是必填的
- **默认值:**
- **类型:**
## border
- **详情:** 是否显示边框
- **详情:**
- **默认值:** `false`
- **类型:** `boolean`
- **默认值:**
- **类型:**

View File

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

View File

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

View File

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

View File

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

View File

@ -62,59 +62,6 @@ MenuButton 的[定义](https://github.com/Tencent/tmagic-editor/blob/239b5d3efea
### 二、左侧菜单栏
左侧菜单栏主要展示组件列表、组件树、代码块、数据源等内容。可以通过 `m-editor` 组件的 [sidebar](/api/editor/props.html#sidebar) `prop` 来进行配置。
#### 1. 自定义左侧面板
可以使用 `sidebar` slot 来完全自定义左侧面板:
```html
<m-editor>
<template #sidebar>
<your-sidebar></your-sidebar>
</template>
</m-editor>
```
#### 2. 扩展组件列表
通过 [componentGroupList](/api/editor/props.html#componentgrouplist) prop 配置组件分组和列表:
```js
const componentGroupList = [
{
title: '基础组件',
items: [
{
text: '文本',
type: 'text',
icon: 'text-icon'
},
{
text: '按钮',
type: 'button',
icon: 'button-icon'
}
]
},
{
title: '业务组件',
items: [
// 自定义业务组件
]
}
]
```
#### 3. 组件树扩展
组件树会自动根据页面配置生成,可以通过 `editorService` 监听组件树相关事件:
```js
editorService.on('select', (node) => {
console.log('选中组件:', node);
});
```
### 三、右侧属性配置栏
@ -171,50 +118,6 @@ propsService.usePlugin({
</m-editor>
```
### 四、中间工作区域
## 行为扩展
### 二、服务扩展
可以通过监听事件和使用插件来扩展 EditorService
```js
// 监听编辑器事件
editorService.on('add', (node) => {
console.log('添加组件:', node);
});
// 使用插件扩展
editorService.usePlugin({
beforeAdd(node) {
// 在添加组件前执行
return node;
},
afterAdd(node) {
// 在添加组件后执行
return node;
}
});
```
#### 2. PropsService 扩展
自定义属性配置的处理逻辑:
```js
propsService.usePlugin({
// 修改属性配置
beforeGetPropsConfig(type) {
console.log('获取配置前:', type);
},
afterGetPropsConfig(config, type) {
// 添加自定义配置
return config;
},
// 自定义配置填充逻辑
afterFillConfig(config, type) {
return config;
}
});
```

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,566 +0,0 @@
export default {
rules: {
/**
* 不要在中括号中添加空格
*/
'array-bracket-spacing': ['error', 'never'],
/**
* 数组的方法除了 forEach 之外回调函数必须有返回值
*/
'array-callback-return': 'warn',
/**
* 要求箭头函数体使用大括号
*/
'arrow-body-style': ['warn', 'as-needed'],
/**
* 要求箭头函数的参数使用圆括号
*/
'arrow-parens': [
'warn',
'as-needed',
{
requireForBlockBody: true,
},
],
/**
* 强制箭头函数的箭头前后使用一致的空格
*/
'arrow-spacing': 'warn',
/**
* 要求打开的块标志和同一行上的标志拥有一致的间距此规则还会在同一行关闭的块标记和前边的标记强制实施一致的间距
*/
'block-spacing': 'error',
/**
* 强制在代码块中使用一致的大括号风格
*/
'brace-style': 'error',
/**
* 使用驼峰命名法camelCase命名对象函数和实例
*/
camelcase: [
'error',
{
ignoreDestructuring: true,
properties: 'never',
},
],
/**
* 添加尾随逗号
*/
'comma-dangle': ['warn', 'always-multiline'],
/**
* 强制在逗号前后使用一致的空格
*/
'comma-spacing': [
'error',
{
before: false,
after: true,
},
],
/**
* 强制使用一致的逗号风格
*/
'comma-style': ['error', 'last'],
/**
* 强制在计算的属性的方括号中使用一致的空格
*/
'computed-property-spacing': ['warn', 'never'],
/**
* 禁止使用 foo['bar']必须写成 foo.bar
*/
'dot-notation': 'warn',
/**
* 要求或禁止文件末尾存在空行
*/
'eol-last': ['error', 'always'],
/**
* 必须使用 === !==禁止使用 == !=
*/
eqeqeq: ['warn', 'always'],
/**
* 要求或禁止在函数标识符和其调用之间有空格
*/
'func-call-spacing': ['error', 'never'],
/**
* 必须只使用函数声明或只使用函数表达式
*/
'func-style': ['off', 'expression'],
/**
* 强制在函数括号内使用一致的换行
*/
'function-paren-newline': ['warn', 'multiline'],
/**
* 强制 generator 函数中 * 号周围使用一致的空格
*/
'generator-star-spacing': [
'warn',
{
before: false,
after: true,
},
],
/**
* 限制变量名长度
*/
'id-length': 'off',
/**
* 强制隐式返回的箭头函数体的位置
*/
'implicit-arrow-linebreak': ['warn', 'beside'],
/**
* 使用 2 个空格缩进
*/
indent: [
'warn',
2,
{
SwitchCase: 1,
VariableDeclarator: 1,
outerIIFEBody: 1,
FunctionDeclaration: {
parameters: 1,
body: 1,
},
FunctionExpression: {
parameters: 1,
body: 1,
},
CallExpression: {
arguments: 1,
},
ArrayExpression: 1,
ObjectExpression: 1,
ImportDeclaration: 1,
flatTernaryExpressions: false,
ignoredNodes: [
'JSXElement',
'JSXElement > *',
'JSXAttribute',
'JSXIdentifier',
'JSXNamespacedName',
'JSXMemberExpression',
'JSXSpreadAttribute',
'JSXExpressionContainer',
'JSXOpeningElement',
'JSXClosingElement',
'JSXFragment',
'JSXOpeningFragment',
'JSXClosingFragment',
'JSXText',
'JSXEmptyExpression',
'JSXSpreadChild',
],
ignoreComments: false,
},
],
/**
* 强制在对象字面量的属性中键和值之间使用一致的间距
*/
'key-spacing': 'error',
/**
* 强制在关键字前后使用一致的空格
*/
'keyword-spacing': [
'error',
{
overrides: {
if: {
after: true,
},
for: {
after: true,
},
while: {
after: true,
},
else: {
after: true,
},
},
},
],
/**
* 只允许使用 unix LF 作为换行符Windows CRLF 不可以使用
*/
'linebreak-style': ['warn', 'unix'],
/**
* 强制一行的最大长度限制单行不能超过100个字符字符串和正则表达式除外
*/
'max-len': [
'off',
{
code: 120,
ignoreStrings: true,
ignoreUrls: true,
ignoreRegExpLiterals: true,
ignoreTemplateLiterals: true,
},
],
/**
* 只有在命名构造器或者类的时候才用帕斯卡拼命名法PascalCase即首字母大写
*/
'new-cap': [
'error',
{
newIsCap: true,
newIsCapExceptions: [],
capIsNew: false,
capIsNewExceptions: ['Immutable.Map', 'Immutable.Set', 'Immutable.List'],
properties: false,
},
],
/**
* 在编写多个方法链式调用(超过两个方法链式调用) 使用前导点强调这行是一个方法调用而不是一个语句
*/
'newline-per-chained-call': [
'warn',
{
ignoreChainWithDepth: 2,
},
],
/**
* 使用字面量语法创建数组
*/
'no-array-constructor': ['error'],
/**
* case default 的子句中如果存在声明 (例如. let, const, function, class)使用大括号来创建块级作用域
*/
'no-case-declarations': 'error',
/**
* 避免搞混箭头函数符号 (=>) 和比较运算符 (<=, >=)
*/
'no-confusing-arrow': 'warn',
/**
* 禁止对使用 const 定义的常量重新赋值
*/
'no-const-assign': 'error',
/**
* 禁止重复定义类的成员
*/
'no-dupe-class-members': 'error',
/**
* 禁止在 else 内使用 return必须改为提前结束
*/
'no-else-return': [
'warn',
{
allowElseIf: false,
},
],
/**
* 禁止使用 eval
*/
'no-eval': 'error',
/**
* 不要使用迭代器
* @reason 推荐使用 JavaScript 的高阶函数代替 for-in
*/
'no-iterator': 'warn',
/**
* 禁止在循环内的函数内部出现循环体条件语句中定义的变量
*/
'no-loop-func': 'error',
/**
* 禁止混合使用不同的操作符:
* - 禁止 `%`, `**` 之间混用
* - 禁止 `%` 与其它运算符之间混用
* - 禁止乘除运算符之间混用
* - 禁止位运算符之间的任何混用
* - 禁止比较运算符之间混用
* - 禁止 `&&`, `||` 之间混用
*/
'no-mixed-operators': [
'error',
{
groups: [
['%', '**'],
['%', '+'],
['%', '-'],
['%', '*'],
['%', '/'],
['&', '|', '<<', '>>', '>>>'],
['==', '!=', '===', '!=='],
['&&', '||'],
],
allowSamePrecedence: false,
},
],
/**
* 禁止连续赋值比如 foo = bar = 1
*/
'no-multi-assign': 'error',
/**
* 不要使用多个空行填充代码
*/
'no-multiple-empty-lines': 'error',
/**
* 禁止使用嵌套的三元表达式比如 a ? b : c ? d : e
*/
'no-nested-ternary': 'warn',
/**
* 禁止使用 new Function
* @reason 这和 eval 是等价的
*/
'no-new-func': 'error',
/**
* 禁止直接 new Object
*/
'no-new-object': 'error',
/**
* 禁止使用 new 来生成 String, Number Boolean
*/
'no-new-wrappers': 'warn',
/**
* 禁止对函数的参数重新赋值
*/
'no-param-reassign': [
'warn',
{
props: true,
ignorePropertyModificationsFor: [
'acc',
'accumulator',
'e',
'ctx',
'req',
'request',
'res',
'response',
'$scope',
'staticContext',
'state',
],
},
],
/**
* 禁止使用 ++ --
*/
'no-plusplus': [
'error',
{
allowForLoopAfterthoughts: true,
},
],
/**
* 禁止使用 hasOwnProperty, isPrototypeOf propertyIsEnumerable
*/
'no-prototype-builtins': 'error',
/**
* 计算指数时可以使用 ** 运算符
*/
'no-restricted-properties': [
'warn',
{
object: 'Math',
property: 'pow',
message: 'Please use ** instand',
},
],
/**
* 推荐使用 JavaScript 的高阶函数代替 for-in
*/
'no-restricted-syntax': [
'warn',
{
selector: 'ForInStatement',
message:
'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
},
{
selector: 'LabeledStatement',
message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
},
{
selector: 'WithStatement',
message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
},
],
/**
* 避免在行尾添加空格
*/
'no-trailing-spaces': 'error',
/**
* 变量应先声明再使用禁止引用任何未声明的变量除非你明确知道引用的变量存在于当前作用域链上
*/
'no-undef': ['error'],
/**
* 禁止变量名出现下划线
*/
'no-underscore-dangle': 'warn',
/**
* 必须使用 !a 替代 a ? false : true
*/
'no-unneeded-ternary': 'warn',
/**
* 已定义的变量必须使用
* 但不检查最后一个使用的参数之前的参数
* 也不检查 rest 属性的兄弟属性
*/
'no-unused-vars': [
'error',
{
args: 'after-used',
ignoreRestSiblings: true,
argsIgnorePattern: '^_.+',
varsIgnorePattern: '^_.+',
},
],
/**
* 禁止出现没必要的 constructor
*/
'no-useless-constructor': 'warn',
/**
* 禁止出现没必要的转义
*/
'no-useless-escape': 'error',
/**
* 禁止使用 var
*/
'no-var': 'error',
/**
* 禁止属性前有空白
*/
'no-whitespace-before-property': 'warn',
/**
* 强制单个语句的位置
*/
'nonblock-statement-body-position': ['error', 'beside'],
/**
* 强制在大括号中使用一致的空格
*/
'object-curly-spacing': ['warn', 'always'],
/**
* 将对象方法属性简写且间歇属性放在前面
*/
'object-shorthand': 'warn',
/**
* 禁止变量申明时用逗号一次申明多个
*/
'one-var': ['warn', 'never'],
/**
* 避免在赋值语句 = 前后换行如果你的代码单行长度超过了 max-len 定义的长度而不得不换行那么使用括号包裹
*/
'operator-linebreak': [
'error',
'before',
{
overrides: {
'=': 'none',
},
},
],
/**
* 要求或禁止块内填充
*/
'padded-blocks': ['error', 'never'],
/**
* 要求回调函数使用箭头函数
*/
'prefer-arrow-callback': 'warn',
/**
* 申明后不再被修改的变量必须使用 const 来申明
*/
'prefer-const': [
'error',
{
destructuring: 'any',
ignoreReadBeforeAssign: false,
},
],
/**
* 优先使用解构赋值
*/
'prefer-destructuring': [
'warn',
{
VariableDeclarator: {
array: false,
object: true,
},
AssignmentExpression: {
array: true,
object: false,
},
},
{
enforceForRenamedProperties: false,
},
],
/**
* 必须使用 ...args 而不是 arguments
*/
'prefer-rest-params': 'warn',
/**
* 必须使用 ... 而不是 apply比如 foo(...args)
*/
'prefer-spread': 'warn',
/**
* 必须使用模版字符串而不是字符串连接
*/
'prefer-template': 'error',
/**
* 要求对象字面量属性名称用引号括起来
*/
'quote-props': [
'error',
'as-needed',
{
keywords: false,
},
],
/**
* 使用单引号 '' 定义字符串
*/
quotes: [
'warn',
'single',
{
allowTemplateLiterals: false,
},
],
/**
* parseInt 必须传入第二个参数
*/
radix: 'warn',
/**
* 要加分号
*/
semi: ['error', 'always'],
/**
* 强制在块之前使用一致的空格
*/
'space-before-blocks': 'error',
/**
* 强制在 function 的左括号之前使用一致的空格
*/
'space-before-function-paren': [
'error',
{
anonymous: 'always',
named: 'never',
asyncArrow: 'always',
},
],
/**
* 强制在圆括号内使用一致的空格
*/
'space-in-parens': ['error', 'never'],
/**
* 要求操作符周围有空格
*/
'space-infix-ops': 'error',
/**
* 注释的斜线或 * 后必须有空格
*/
'spaced-comment': ['error', 'always'],
/**
* 要求或禁止模板字符串中的嵌入表达式周围空格的使用
*/
'template-curly-spacing': ['error', 'never'],
/**
* 要求立即执行的函数使用括号括起来
*/
'wrap-iife': ['error', 'outside'],
},
};

View File

@ -1,38 +0,0 @@
import simpleImportSort from 'eslint-plugin-simple-import-sort';
export default {
files: ['**/*.{js,mjs,cjs,ts,vue}'],
plugins: {
'simple-import-sort': simpleImportSort,
},
rules: {
'simple-import-sort/imports': [
'error',
{
groups: [
['./polyfills'],
['^(node)(:.*|$)'],
// Node.js builtins. You could also generate this regex if you use a `.js` config.
// For example: `^(${require("module").builtinModules.join("|")})(/|$)`
[
'^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)',
],
// Packages. `react|vue` related packages come first.
['^(react|vue|vite|vitest)', '^@?\\w'],
['^(@tencent)(/.*|$)'],
['^(@tmagic)(/.*|$)'],
// Internal packages.
['^(@|src|editor-page|@editor|@data-source)(/.*|$)'],
// Side effect imports.
['^\\u0000'],
// Parent imports. Put `..` last.
['^\\.\\.(?!/?$)', '^\\.\\./?$'],
// Other relative imports. Put same-folder imports and `.` last.
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
// Style imports.
['^.+\\.s?css$'],
],
},
],
},
};

View File

@ -1,37 +0,0 @@
import { rules } from 'eslint-plugin-import';
export default {
plugins: {
import: {
meta: { name: 'eslint-plugin-import' },
rules,
},
},
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
languageOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
rules: {
/**
* 导入语句前不允许有任何非导入语句
*/
'import/first': 'error',
/**
* 禁止重复导入模块
*/
'import/no-duplicates': 'error',
/**
* 禁止使用 let 导出
*/
'import/no-mutable-exports': 'warn',
/**
* 禁用导入的模块时使用 webpack 特有的语法感叹号
*/
'import/no-webpack-loader-syntax': 'warn',
/**
* 当只有一个导出时必须使用 export default 来导出
*/
'import/prefer-default-export': 'off',
},
};

View File

@ -1,376 +0,0 @@
export default {
files: ['**/*.ts', '**/*.tsx'],
rules: {
'brace-style': 'off',
'no-empty-function': 'off',
// https://github.com/typescript-eslint/typescript-eslint/issues/491
'no-invalid-this': 'off',
'no-magic-numbers': 'off',
'react/sort-comp': 'off',
'func-call-spacing': 'off',
'comma-spacing': 'off',
'dot-notation': 'off',
indent: 'off',
'keyword-spacing': 'off',
camelcase: 'off',
'no-underscore-dangle': 'off',
'no-array-constructor': 'off',
'no-dupe-class-members': 'off',
'no-undef': 'off',
'no-unused-vars': 'off',
'no-useless-constructor': 'off',
quotes: 'off',
semi: 'off',
'space-before-function-paren': 'off',
// https://github.com/typescript-eslint/typescript-eslint/issues/600
'spaced-comment': ['error', 'always', { markers: ['/'] }],
/**
* 重载的函数必须写在一起
* @reason 增加可读性
*/
'@typescript-eslint/adjacent-overload-signatures': 'error',
/** 同 JS 规则的 TS 版本 */
'@stylistic/ts/brace-style': 'error',
/** 同 JS 规则的 TS 版本 */
'@stylistic/comma-spacing': [
'error',
{
before: false,
after: true,
},
],
/**
* 类型断言必须使用 as Type禁止使用 <Type>禁止对对象字面量进行类型断言断言成 any 是允许的
* @reason <Type> 容易被理解为 jsx
*/
'@typescript-eslint/consistent-type-assertions': [
'error',
{
assertionStyle: 'as',
objectLiteralTypeAssertions: 'never',
},
],
/**
* 优先使用 interface 而不是 type
*/
'@typescript-eslint/consistent-type-definitions': 'off',
/** 同 JS 规则的 TS 版本 */
'@typescript-eslint/dot-notation': 'warn',
/**
* 必须设置类的成员的可访问性
* @reason 将不需要公开的成员设为私有的可以增强代码的可理解性对文档输出也很友好
*/
'@typescript-eslint/explicit-member-accessibility': 'off',
/**
* 要求或禁止在函数标识符和其调用之间有空格
*/
'@stylistic/ts/func-call-spacing': ['error', 'never'],
/** 同 JS 规则的 TS 版本 */
'@stylistic/ts/indent': [
'warn',
2,
{
SwitchCase: 1,
VariableDeclarator: 1,
outerIIFEBody: 1,
FunctionDeclaration: {
parameters: 1,
body: 1,
},
FunctionExpression: {
parameters: 1,
body: 1,
},
CallExpression: {
arguments: 1,
},
ArrayExpression: 1,
ObjectExpression: 1,
ImportDeclaration: 1,
flatTernaryExpressions: false,
ignoredNodes: [
'JSXElement',
'JSXElement > *',
'JSXAttribute',
'JSXIdentifier',
'JSXNamespacedName',
'JSXMemberExpression',
'JSXSpreadAttribute',
'JSXExpressionContainer',
'JSXOpeningElement',
'JSXClosingElement',
'JSXFragment',
'JSXOpeningFragment',
'JSXClosingFragment',
'JSXText',
'JSXEmptyExpression',
'JSXSpreadChild',
],
ignoreComments: false,
},
],
/** 同 JS 规则的 TS 版本 */
'@stylistic/ts/keyword-spacing': [
'error',
{
overrides: {
if: {
after: true,
},
for: {
after: true,
},
while: {
after: true,
},
else: {
after: true,
},
},
before: true,
after: true,
},
],
/**
* 指定类成员的排序规则
* @reason 优先级
* 1. static > instance
* 2. field > constructor > method
* 3. public > protected > private
*/
'@typescript-eslint/member-ordering': [
'error',
{
default: [
'public-static-field',
'protected-static-field',
'private-static-field',
'static-field',
'public-static-method',
'protected-static-method',
'private-static-method',
'static-method',
'public-instance-field',
'protected-instance-field',
'private-instance-field',
'public-field',
'protected-field',
'private-field',
'instance-field',
'field',
'constructor',
'public-instance-method',
'protected-instance-method',
'private-instance-method',
'public-method',
'protected-method',
'private-method',
'instance-method',
'method',
],
},
],
/**
* 接口中的方法必须用属性的方式定义
*/
'@typescript-eslint/method-signature-style': 'off',
/** 代替 JS 规则 camelCase 的 TS 规则 */
'@typescript-eslint/naming-convention': [
'warn',
{
selector: 'function',
format: ['camelCase', 'PascalCase'],
},
{
selector: 'variable',
format: ['camelCase', 'UPPER_CASE'],
},
{
selector: 'variable',
modifiers: ['global'],
format: ['camelCase', 'PascalCase', 'UPPER_CASE'],
},
{
selector: 'variable',
format: ['camelCase', 'PascalCase'],
types: ['function'],
},
{
selector: 'variable',
modifiers: ['exported'],
format: ['UPPER_CASE'],
types: ['boolean', 'string', 'number', 'array'],
},
{
selector: 'variable',
modifiers: ['exported'],
format: ['camelCase', 'PascalCase'],
types: ['function'],
},
{
selector: ['class', 'typeLike'],
format: ['PascalCase'],
},
{
selector: ['classMethod', 'classProperty'],
leadingUnderscore: 'forbid',
trailingUnderscore: 'forbid',
format: ['camelCase'],
},
],
/** 同 JS 规则的 TS 版本 */
'@typescript-eslint/no-array-constructor': 'error',
/** 同 JS 规则的 TS 版本 */
'@typescript-eslint/no-dupe-class-members': 'error',
/**
* 禁止定义空的接口
*/
'@typescript-eslint/no-empty-interface': 'error',
/**
* 禁止给一个初始化时直接赋值为 number, string 的变量显式的声明类型
* @reason 可以简化代码
*/
'@typescript-eslint/no-inferrable-types': 'warn',
/**
* 禁止对 promise 的误用详见示例
*/
'@typescript-eslint/no-misused-promises': [
'error',
{
checksConditionals: true,
},
],
/**
* 禁止使用 namespace 来定义命名空间
* @reason 使用 es6 引入模块才是更标准的方式
* 但是允许使用 declare namespace ... {} 来定义外部命名空间
*/
'@typescript-eslint/no-namespace': [
'error',
{
allowDeclarations: true,
allowDefinitionFiles: true,
},
],
/**
* 禁止在 optional chaining 之后使用 non-null 断言感叹号
* @reason optional chaining 后面的属性一定是非空的
*/
'@typescript-eslint/no-non-null-asserted-optional-chain': 'error',
/**
* 禁止给类的构造函数的参数添加修饰符
*/
'@typescript-eslint/no-parameter-properties': 'off',
/**
* 禁止使用 require
* @reason 统一使用 import 来引入模块特殊情况使用单行注释允许 require 引入
*/
'@typescript-eslint/no-require-imports': 'error',
/**
* 禁止将 this 赋值给其他变量除非是解构赋值
*/
'@typescript-eslint/no-this-alias': [
'error',
{
allowDestructuring: true,
},
],
/**
* 禁止无用的表达式
*/
'@typescript-eslint/no-unused-expressions': [
'error',
{
allowShortCircuit: true,
allowTernary: true,
allowTaggedTemplates: true,
},
],
/** 同 JS 规则的 TS 版本 */
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'after-used',
ignoreRestSiblings: true,
argsIgnorePattern: '^_.+',
varsIgnorePattern: '^_.+',
},
],
/**
* 禁止出现没必要的 constructor
*/
'@typescript-eslint/no-useless-constructor': 'warn',
/**
* 使用 for 循环遍历数组时如果索引仅用于获取成员则必须使用 for of 循环替代 for 循环
* @reason for of 循环更加易读
*/
'@typescript-eslint/prefer-for-of': 'warn',
/**
* 使用函数类型别名替代包含函数调用声明的接口
*/
'@typescript-eslint/prefer-function-type': 'warn',
/**
* 禁止使用 module 来定义命名空间
* @reason module 已成为 js 的关键字
*/
'@typescript-eslint/prefer-namespace-keyword': 'error',
/**
* 使用 optional chaining 替代 &&
*/
'@typescript-eslint/prefer-optional-chain': 'error',
/** 同 JS 规则的 TS 版本 */
'@stylistic/ts/quotes': [
'warn',
'single',
{
allowTemplateLiterals: false,
},
],
/** 同 JS 规则的 TS 版本 */
'@stylistic/ts/semi': ['error', 'always'],
/** 同 JS 规则的 TS 版本 */
'@stylistic/ts/space-before-function-paren': [
'error',
{
anonymous: 'always',
named: 'never',
asyncArrow: 'always',
},
],
/**
* 禁止使用三斜杠导入文件
* @reason 三斜杠是已废弃的语法但在类型声明文件中还是可以使用的
*/
'@typescript-eslint/triple-slash-reference': [
'error',
{
path: 'never',
types: 'always',
lib: 'always',
},
],
/**
* 在类型注释周围需要一致的间距
*/
'@stylistic/ts/type-annotation-spacing': 'error',
/**
* interface type 定义时必须声明成员的类型
*/
'@typescript-eslint/typedef': [
'error',
{
arrayDestructuring: false,
arrowParameter: false,
memberVariableDeclaration: false,
objectDestructuring: false,
parameter: false,
propertyDeclaration: true,
variableDeclaration: false,
},
],
/**
* 函数重载时若能通过联合类型将两个函数的类型声明合为一个则使用联合类型而不是两个函数声明
*/
'@typescript-eslint/unified-signatures': 'error',
},
};

View File

@ -1,37 +0,0 @@
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import parserTs from '@typescript-eslint/parser';
import { defineConfig } from 'eslint/config';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import pluginVue from 'eslint-plugin-vue';
import globals from 'globals';
import tseslint from 'typescript-eslint';
import tencentEslintBaseConfig from './flat/base.mjs';
import tencentEslintImportexport from './flat/import.mjs';
import ImportSortConfig from './flat/import-sort.mjs';
import tencentEslintPrettierConfig from './flat/prettier.mjs';
import tencentEslintTsConfig from './flat/ts.mjs';
export default (tsconfigRootDir) =>
defineConfig([
{ files: ['**/*.{js,mjs,cjs}'], plugins: { js }, extends: ['js/recommended'] },
{ files: ['**/*.{js,mjs,cjs,ts,vue}'], languageOptions: { globals: { ...globals.browser, ...globals.node } } },
...tseslint.config(tencentEslintBaseConfig, tencentEslintImportexport, tseslint.configs.base, {
plugins: {
'@stylistic': stylistic,
},
languageOptions: {
parser: parserTs,
parserOptions: {
project: tsconfigRootDir,
},
},
...tencentEslintTsConfig,
}),
pluginVue.configs['flat/essential'],
{ files: ['**/*.vue'], languageOptions: { parserOptions: { parser: tseslint.parser } } },
eslintPluginPrettierRecommended,
tencentEslintPrettierConfig,
ImportSortConfig,
]);

View File

@ -1,28 +0,0 @@
{
"name": "@tmagic/eslint-config",
"version": "0.0.3",
"main": "index.mjs",
"type": "module",
"repository": {
"directory": "eslint-config",
"type": "git",
"url": "https://github.com/Tencent/tmagic-editor.git"
},
"dependencies": {
"@eslint/js": "^9.34.0",
"@typescript-eslint/parser": "^8.41.0",
"@typescript-eslint/eslint-plugin": "^8.41.0 ",
"@stylistic/eslint-plugin": "^5.2.3",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-vue": "^10.4.0",
"eslint-plugin-prettier": "^5.5.4 ",
"globals": "^16.3.0",
"typescript-eslint": "^8.41.0"
},
"peerDependencies": {
"eslint": ">=9.24.0",
"prettier": ">=3.5.3"
}
}

View File

@ -1,41 +0,0 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { defineConfig, globalIgnores } from 'eslint/config';
import eslintConfig from '@tmagic/eslint-config';
export default defineConfig([
globalIgnores([
'./docs/**/*',
'./packages/cli/lib/**/*',
'*/**/auto-imports.d.ts',
'*/**/components.d.ts',
'*/**/dist/**/*',
'*/**/.tmagic/**/*',
'*/**/public/**/*',
'*/**/types/**/*',
'*/**/*.config.ts',
'vite-env.d.ts',
]),
...eslintConfig(path.join(path.dirname(fileURLToPath(import.meta.url)), 'tsconfig.json')),
{
files: ['**/*.vue'],
rules: {
'vue/no-mutating-props': 'off',
'vue/multi-word-component-names': 'off',
},
},
{
files: ['**/*.{js,mjs,cjs,ts,vue}'],
rules: {
'no-param-reassign': 'off',
},
},
{
files: ['**/*.tsx'],
rules: {
'@typescript-eslint/naming-convention': 'off',
},
},
]);

View File

@ -1,36 +1,39 @@
{
"version": "1.7.2",
"version": "1.5.6",
"name": "tmagic",
"private": true,
"type": "module",
"packageManager": "pnpm@10.18.2",
"packageManager": "pnpm@9.15.4",
"scripts": {
"bootstrap": "pnpm i && pnpm build",
"clean:top": "rimraf */**/dist */**/types */dist coverage dwt* temp packages/cli/lib",
"clean:modules": "rimraf node_modules **/node_modules **/**/node_modules",
"clean:all": "pnpm clean:top && pnpm clean:modules",
"lint": "eslint --cache .",
"lint-fix": "eslint --fix --cache .",
"playground": "pnpm --filter \"runtime-vue\" build:libs && pnpm --filter \"runtime-vue\" --filter \"tmagic-playground\" dev",
"lint": "eslint . --ext .js,.vue,.ts,.tsx",
"lint-fix": "eslint . --fix --ext .vue,.js,.ts,.tsx",
"playground": "pnpm --filter \"runtime-vue3\" build:libs && pnpm --filter \"runtime-vue3\" --filter \"tmagic-playground\" dev",
"pg": "pnpm playground",
"playground:vue2": "pnpm --filter \"runtime-vue2\" build:libs && pnpm --filter \"runtime-vue2\" --filter \"tmagic-playground\" dev:vue2",
"pg:vue2": "pnpm playground:vue2",
"playground:react": "pnpm --filter \"runtime-react\" build:libs && pnpm --filter \"runtime-react\" --filter \"tmagic-playground\" dev:react",
"pg:react": "pnpm playground:react",
"build": "pnpm build:dts && node scripts/build.mjs",
"build:dts": "pnpm --filter \"@tmagic/cli\" build && tsc -p tsconfig.build-browser.json && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.build-vue.json && rollup -c rollup.dts.config.js && rimraf temp",
"check:type": "tsc --incremental --noEmit -p tsconfig.check.json && vue-tsc --noEmit -p tsconfig.check-vue.json",
"build:playground": "pnpm --filter \"runtime-vue\" build && pnpm --filter \"tmagic-playground\" build",
"build:playground": "pnpm --filter \"runtime-vue3\" build && pnpm --filter \"tmagic-playground\" build",
"docs:dev": "vitepress dev docs",
"docs:serve": "vitepress serve docs",
"docs:build": "vitepress build docs",
"reinstall": "pnpm clean:all && pnpm bootstrap",
"test": "vitest run",
"coverage": "vitest run --coverage",
"prepare": "husky",
"prepare": "husky install",
"commit": "git-cz",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"release": "node scripts/release.mjs"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
"node": ">=18"
},
"workspaces": [
"packages/*"
@ -40,44 +43,49 @@
"url": "https://github.com/Tencent/tmagic-editor.git"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
"@commitlint/config-conventional": "^20.0.0",
"@rollup/plugin-alias": "^6.0.0",
"@tmagic/eslint-config": "workspace:*",
"@types/node": "24.0.10",
"@vitejs/plugin-vue": "^6.0.2",
"@vitest/coverage-v8": "^4.0.12",
"@vue/compiler-sfc": "catalog:",
"c8": "^10.1.3",
"@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3",
"@rollup/plugin-alias": "^5.1.1",
"@types/node": "18.19.61",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitest/coverage-v8": "^2.1.8",
"@vue/compiler-sfc": "^3.5.13",
"c8": "^7.14.0",
"commitizen": "^4.3.1",
"conventional-changelog-cli": "^5.0.0",
"cosmiconfig": "^ 9.0.0 ",
"conventional-changelog-cli": "^4.1.0",
"cosmiconfig": "^8.3.6",
"cz-conventional-changelog": "^3.3.0",
"element-plus": "^2.11.8",
"element-plus": "^2.9.3",
"enquirer": "^2.4.1",
"eslint": "^9.39.1",
"execa": "^9.6.0",
"eslint": "^8.57.1",
"eslint-config-tencent": "^1.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-vue": "^9.32.0",
"execa": "^4.1.0",
"highlight.js": "^11.11.1",
"husky": "^9.1.7",
"jsdom": "^27.2.0",
"lint-staged": "^16.2.7",
"husky": "^7.0.4",
"jsdom": "^19.0.0",
"lint-staged": "^11.2.6",
"minimist": "^1.2.8",
"picocolors": "^1.1.1",
"prettier": "^3.6.2",
"recast": "^0.23.11",
"prettier": "^2.8.8",
"recast": "^0.20.5",
"rimraf": "^3.0.2",
"rollup": "4.44.1",
"rollup-plugin-dts": "^6.2.3",
"sass-embedded": "^1.93.3",
"semver": "^7.7.3",
"serialize-javascript": "^7.0.0",
"rollup": "^4.31.0",
"rollup-plugin-dts": "^6.1.1",
"semver": "^7.6.3",
"serialize-javascript": "^6.0.2",
"shx": "^0.3.4",
"typescript": "catalog:",
"vite": "catalog:",
"vitepress": "^1.6.4",
"vitest": "^4.0.12",
"vue": "catalog:",
"vue-tsc": "^3.1.4"
"typescript": "^5.7.3",
"vite": "^6.0.10",
"vitepress": "^1.6.1",
"vitest": "^3.0.2",
"vue": "^3.5.13",
"vue-tsc": "^2.2.0"
},
"config": {
"commitizen": {
@ -85,7 +93,7 @@
}
},
"lint-staged": {
"*.{js,ts,vue}": "npm run lint-fix",
"*.{js,ts,vue}": "eslint --fix",
"*.scss": "prettier --write"
}
}

View File

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

View File

@ -1,26 +1,21 @@
import path from 'node:path';
import path from 'path';
import fs from 'fs-extra';
import { prepareEntryFile } from './utils/prepareEntryFile';
import { resolveAppPackages } from './utils/resolveAppPackages';
import { ModuleMainFilePath, UserConfig } from './types';
import { prepareEntryFile, resolveAppPackages } from './utils';
export default class Core {
// eslint-disable-next-line @typescript-eslint/no-require-imports
public version = require('../package.json').version;
public options: UserConfig;
public moduleMainFilePath: ModuleMainFilePath = {
componentPackage: {},
componentMap: {},
pluginPakcage: {},
pluginMap: {},
configMap: {},
valueMap: {},
eventMap: {},
datasourcePackage: {},
datasourceMap: {},
dsConfigMap: {},
dsValueMap: {},

View File

@ -29,7 +29,6 @@ export const cli = (defaultAppConfig: UserConfig): void => {
const program = cac('tmagic');
// display core version and cli version
// eslint-disable-next-line @typescript-eslint/no-require-imports
const versionCli = require('../package.json').version;
program.version(`tmagic/cli@${versionCli}`);

View File

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

View File

@ -54,14 +54,11 @@ export interface NpmConfig {
}
export interface ModuleMainFilePath {
componentPackage: Record<string, string>;
componentMap: Record<string, string>;
pluginPakcage: Record<string, string>;
pluginMap: Record<string, string>;
configMap: Record<string, string>;
valueMap: Record<string, string>;
eventMap: Record<string, string>;
datasourcePackage: Record<string, string>;
datasourceMap: Record<string, string>;
dsConfigMap: Record<string, string>;
dsValueMap: Record<string, string>;
@ -84,8 +81,6 @@ export interface UserConfig {
npmConfig?: NpmConfig;
/** 是否使用import()加载组件 */
dynamicImport?: boolean;
/** 指定组件不使用动态加载dynamicImport为true时有效 */
dynamicIgnore?: string[];
hooks?: {
beforeWriteEntry?: (genContentMap: Record<string, string>, app: Core) => Promise<Record<string, string>>;
};

View File

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

View File

@ -10,7 +10,6 @@ export const hasExportDefault = <T = any>(mod: unknown): mod is { default: T } =
isPlainObject(mod) && !!mod.__esModule && Object.prototype.hasOwnProperty.call(mod, 'default');
export const loadUserConfigCjs: UserConfigLoader = async (userConfigPath) => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const required = require(userConfigPath);
return hasExportDefault(required) ? required.default : required;
};

View File

@ -5,85 +5,33 @@ import { EntryType } from '../types';
export const prepareEntryFile = async (app: App) => {
const { moduleMainFilePath, options } = app;
const { dynamicImport, dynamicIgnore, hooks, useTs = true } = options;
const { componentFileAffix, dynamicImport, hooks, useTs = true } = options;
let contentMap: Record<string, string> = {
'comp-entry': generateContent(
useTs,
EntryType.COMPONENT,
moduleMainFilePath.componentPackage,
moduleMainFilePath.componentMap,
),
'comp-entry': generateContent(useTs, EntryType.COMPONENT, moduleMainFilePath.componentMap, componentFileAffix),
'async-comp-entry': generateContent(
useTs,
EntryType.COMPONENT,
moduleMainFilePath.componentPackage,
moduleMainFilePath.componentMap,
dynamicImport,
dynamicIgnore,
),
'plugin-entry': generateContent(
useTs,
EntryType.PLUGIN,
moduleMainFilePath.pluginPakcage,
moduleMainFilePath.pluginMap,
),
'async-plugin-entry': generateContent(
useTs,
EntryType.PLUGIN,
moduleMainFilePath.pluginPakcage,
moduleMainFilePath.pluginMap,
componentFileAffix,
dynamicImport,
),
'config-entry': generateContent(
useTs,
EntryType.CONFIG,
moduleMainFilePath.componentPackage,
moduleMainFilePath.configMap,
),
'value-entry': generateContent(
useTs,
EntryType.VALUE,
moduleMainFilePath.componentPackage,
moduleMainFilePath.valueMap,
),
'event-entry': generateContent(
useTs,
EntryType.EVENT,
moduleMainFilePath.componentPackage,
moduleMainFilePath.eventMap,
),
'datasource-entry': generateContent(
useTs,
EntryType.DATASOURCE,
moduleMainFilePath.datasourcePackage,
moduleMainFilePath.datasourceMap,
),
'plugin-entry': generateContent(useTs, EntryType.PLUGIN, moduleMainFilePath.pluginMap),
'async-plugin-entry': generateContent(useTs, EntryType.PLUGIN, moduleMainFilePath.pluginMap, '', dynamicImport),
'config-entry': generateContent(useTs, EntryType.CONFIG, moduleMainFilePath.configMap),
'value-entry': generateContent(useTs, EntryType.VALUE, moduleMainFilePath.valueMap),
'event-entry': generateContent(useTs, EntryType.EVENT, moduleMainFilePath.eventMap),
'datasource-entry': generateContent(useTs, EntryType.DATASOURCE, moduleMainFilePath.datasourceMap),
'async-datasource-entry': generateContent(
useTs,
EntryType.DATASOURCE,
moduleMainFilePath.datasourcePackage,
moduleMainFilePath.datasourceMap,
'',
dynamicImport,
),
'ds-config-entry': generateContent(
useTs,
EntryType.DS_CONFIG,
moduleMainFilePath.datasourcePackage,
moduleMainFilePath.dsConfigMap,
),
'ds-value-entry': generateContent(
useTs,
EntryType.DS_VALUE,
moduleMainFilePath.datasourcePackage,
moduleMainFilePath.dsValueMap,
),
'ds-event-entry': generateContent(
useTs,
EntryType.DS_EVENT,
moduleMainFilePath.datasourcePackage,
moduleMainFilePath.dsEventMap,
),
'ds-config-entry': generateContent(useTs, EntryType.DS_CONFIG, moduleMainFilePath.dsConfigMap),
'ds-value-entry': generateContent(useTs, EntryType.DS_VALUE, moduleMainFilePath.dsValueMap),
'ds-event-entry': generateContent(useTs, EntryType.DS_EVENT, moduleMainFilePath.dsEventMap),
};
if (typeof hooks?.beforeWriteEntry === 'function') {
@ -96,7 +44,7 @@ export const prepareEntryFile = async (app: App) => {
app.writeTemp(fileName, content);
} else {
fileName = `${file}.js`;
app.writeTemp(`${file}.d.ts`, 'const type: Record<string, any>;\n\nexport default type;');
app.writeTemp(`${file}.d.ts`, `const type: Record<string, any>;\n\nexport default type;`);
}
app.writeTemp(fileName, content);
});
@ -105,24 +53,23 @@ export const prepareEntryFile = async (app: App) => {
export const generateContent = (
useTs: boolean,
type: EntryType,
packageMap: Record<string, string> = {},
map: Record<string, string> = {},
componentFileAffix = '',
dynamicImport = false,
dynamicIgnore: string[] = [],
) => {
const list: string[] = [];
const importDeclarations: string[] = [];
Object.entries(map).forEach(([key, packagePath]) => {
const name = makeCamelCase(key);
if ([EntryType.CONFIG, EntryType.EVENT, EntryType.VALUE].includes(type) && packagePath === packageMap[key]) {
importDeclarations.push(`import { ${type} as ${name} } from '${packageMap[key]}'`);
list.push(`'${key}': ${name}`);
} else if (dynamicImport && !dynamicIgnore.includes(key)) {
list.push(`'${key}': () => import('${packagePath}')`);
if (dynamicImport) {
list.push(
`'${key}': () => import('${packagePath}${packagePath.endsWith(componentFileAffix) ? '' : componentFileAffix}')`,
);
} else {
importDeclarations.push(`import ${name} from '${packagePath}'`);
importDeclarations.push(
`import ${name} from '${packagePath}${packagePath.endsWith(componentFileAffix) ? '' : componentFileAffix}'`,
);
list.push(`'${key}': ${name}`);
}
});
@ -138,7 +85,6 @@ export const generateContent = (
};
export const prettyCode = (code: string) =>
// eslint-disable-next-line @typescript-eslint/no-require-imports
recast.prettyPrint(recast.parse(code.replace(/\\/g, '/'), { parser: require('recast/parsers/typescript') }), {
tabWidth: 2,
trailingComma: true,

View File

@ -38,37 +38,20 @@ const getRelativePath = (str: string, base: string) => (path.isAbsolute(str) ? p
const npmInstall = function (dependencies: Record<string, string>, cwd: string, npmConfig: NpmConfig = {}) {
try {
const { client = 'npm', registry, installArgs = '', keepPackageJsonClean } = npmConfig;
const { client = 'npm', registry, installArgs = '' } = npmConfig;
const install = {
npm: 'install',
yarn: 'add',
pnpm: 'add',
}[client];
let packages = Object.entries(dependencies);
const newPackages = Object.entries(dependencies).filter(([name]) => {
if (fs.existsSync(path.resolve(cwd, 'node_modules', name))) {
return false;
}
return true;
});
// keepPackageJsonClean会保留原始的package.json这样配置的packages就不会被写入dependencies中
// install 时会删除不在dependencies中的依赖所以需要install packages中配置的所有包
if (!keepPackageJsonClean || !newPackages.length) {
packages = newPackages;
}
if (!packages.length) {
return;
}
const packageNames = packages.map(([name, version]) => (version ? `${name}@${version}` : name)).join(' ');
const packages = Object.entries(dependencies)
.map(([name, version]) => (version ? `${name}@${version}` : name))
.join(' ');
const installArgsString = `${installArgs ? ` ${installArgs}` : ''}`;
const registryString = `${registry ? ` --registry ${registry}` : ''}`;
const command = `${client} ${install}${installArgsString} ${packageNames}${registryString}`;
const command = `${client} ${install}${installArgsString} ${packages}${registryString}`;
execInfo(cwd);
execInfo(command);
@ -127,7 +110,6 @@ const typeAssertion = function ({
if (isFile(defaultFile)) {
const defaultCode = fs.readFileSync(defaultFile, { encoding: 'utf-8', flag: 'r' });
// eslint-disable-next-line @typescript-eslint/no-require-imports
const ast = recast.parse(defaultCode, { parser: require('recast/parsers/typescript') });
if (
isDatasource(
@ -336,30 +318,25 @@ const parseEntry = function ({ ast, package: module, indexPath }: ParseEntryOpti
const tokens = getASTTokenByTraverse({ ast, indexPath });
let { config, value, event, component } = tokens;
if (typeof config === 'undefined') {
if (!config) {
info(`${module} 表单配置文件声明缺失`);
}
if (typeof value === 'undefined') {
if (!value) {
info(`${module} 初始化数据文件声明缺失`);
}
if (typeof event === 'undefined') {
if (!event) {
info(`${module} 事件声明文件声明缺失`);
}
if (!component) {
info(`${module} 组件或数据源文件声明不合法`);
exit(1);
}
const reg = /^.*[/\\]node_modules[/\\](.*)/;
if (config) {
[, config] = config.match(reg) || [, config];
}
if (value) {
[, value] = value.match(reg) || [, value];
}
if (component) {
[, component] = component.match(reg) || [, component];
}
if (event) {
[, event] = event.match(reg) || [, event];
}
[, config] = config.match(reg) || [, config];
[, value] = value.match(reg) || [, value];
[, component] = component.match(reg) || [, component];
[, event] = event.match(reg) || [, event];
return {
config,
@ -370,10 +347,10 @@ const parseEntry = function ({ ast, package: module, indexPath }: ParseEntryOpti
};
const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string }) => {
let config: string | undefined;
let value: string | undefined;
let event: string | undefined;
let component: string | undefined;
let config = '';
let value = '';
let event = '';
let component = '';
const importSpecifiersMap: { [key: string]: string } = {};
const exportSpecifiersMap: { [key: string]: string | undefined } = {};
@ -419,11 +396,7 @@ const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string
visitExportDefaultDeclaration(p) {
const { node } = p;
const { declaration } = node as any;
if (importSpecifiersMap[declaration.name]) {
component = path.resolve(path.dirname(indexPath), importSpecifiersMap[declaration.name]);
}
component = path.resolve(path.dirname(indexPath), importSpecifiersMap[declaration.name]);
this.traverse(p);
},
});
@ -432,10 +405,7 @@ const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string
const exportValue = exportSpecifiersMap[exportName];
const importValue = importSpecifiersMap[exportName];
const connectValue = exportValue ? importSpecifiersMap[exportValue] : '';
const fileName = connectValue || importValue || exportValue || '';
const filePath = fileName ? path.resolve(path.dirname(indexPath), fileName) : '';
const filePath = path.resolve(path.dirname(indexPath), connectValue || importValue || exportValue || '');
if (exportName === EntryType.VALUE) {
value = filePath;
@ -493,7 +463,7 @@ const getDependencies = (dependencies: Record<string, string>, packagePath: stri
const setPackages = (packages: ModuleMainFilePath, app: App, packagePath: string, cwd: string, key?: string) => {
const { options } = app;
const { temp, componentFileAffix = '', datasoucreSuperClass } = options;
const { temp, componentFileAffix, datasoucreSuperClass } = options;
let { name: moduleName } = splitNameVersion(packagePath);
@ -519,7 +489,6 @@ const setPackages = (packages: ModuleMainFilePath, app: App, packagePath: string
.replace('\n', '');
const indexCode = fs.readFileSync(indexPath, { encoding: 'utf-8', flag: 'r' });
// eslint-disable-next-line @typescript-eslint/no-require-imports
const ast: Ast = recast.parse(indexCode, { parser: require('recast/parsers/typescript') });
const result = typeAssertion({ ast, indexPath, componentFileAffix, datasoucreSuperClass });
@ -544,45 +513,15 @@ const setPackages = (packages: ModuleMainFilePath, app: App, packagePath: string
if (!key) return;
if (result.type === PackageType.COMPONENT || !result.type) {
packages.componentPackage[key] = moduleName;
if (result.type === PackageType.COMPONENT) {
// 组件
const entry = parseEntry({ ast, package: moduleName, indexPath });
if (entry.component) {
const packagePath = getRelativePath(entry.component, temp);
packages.componentMap[key] = `${packagePath}${
packagePath.endsWith(componentFileAffix) ? '' : componentFileAffix
}`;
} else {
packages.componentMap[key] = moduleName;
}
if (typeof entry.config === 'string') {
if (entry.config) {
packages.configMap[key] = getRelativePath(entry.config, temp);
} else {
packages.configMap[key] = moduleName;
}
}
if (typeof entry.event === 'string') {
if (entry.event) {
packages.eventMap[key] = getRelativePath(entry.event, temp);
} else {
packages.eventMap[key] = moduleName;
}
}
if (typeof entry.value === 'string') {
if (entry.value) {
packages.valueMap[key] = getRelativePath(entry.value, temp);
} else {
packages.valueMap[key] = moduleName;
}
}
if (entry.component) packages.componentMap[key] = getRelativePath(entry.component, temp);
if (entry.config) packages.configMap[key] = getRelativePath(entry.config, temp);
if (entry.event) packages.eventMap[key] = getRelativePath(entry.event, temp);
if (entry.value) packages.valueMap[key] = getRelativePath(entry.value, temp);
} else if (result.type === PackageType.DATASOURCE) {
packages.datasourcePackage[key] = moduleName;
// 数据源
const entry = parseEntry({ ast, package: moduleName, indexPath });
@ -591,7 +530,6 @@ const setPackages = (packages: ModuleMainFilePath, app: App, packagePath: string
if (entry.event) packages.dsEventMap[key] = getRelativePath(entry.event, temp);
if (entry.value) packages.dsValueMap[key] = getRelativePath(entry.value, temp);
} else if (result.type === PackageType.PLUGIN) {
packages.pluginPakcage[key] = moduleName;
// 插件
packages.pluginMap[key] = getRelativePath(moduleName, temp);
}
@ -601,20 +539,10 @@ const flattenPackagesConfig = (packages: (string | Record<string, string>)[]) =>
const packagesConfig: ([string] | [string, string])[] = [];
packages.forEach((item) => {
if (typeof item === 'object') {
for (const [key, packagePath] of Object.entries(item)) {
const index = packagesConfig.findIndex(([, k]) => {
return k === key;
});
if (index > -1) {
packagesConfig[index] = [packagePath, key];
} else {
packagesConfig.push([packagePath, key]);
}
}
Object.entries(item).forEach(([key, packagePath]) => {
packagesConfig.push([packagePath, key]);
});
} else if (typeof item === 'string') {
if (packagesConfig.find(([k]) => k === item)) {
return;
}
packagesConfig.push([item]);
}
});
@ -645,14 +573,11 @@ export const resolveAppPackages = (app: App): ModuleMainFilePath => {
}
const packagesMap: ModuleMainFilePath = {
componentPackage: {},
componentMap: {},
configMap: {},
eventMap: {},
valueMap: {},
pluginPakcage: {},
pluginMap: {},
datasourcePackage: {},
datasourceMap: {},
dsConfigMap: {},
dsEventMap: {},

View File

@ -7,7 +7,6 @@
"rootDir": "./src",
"outDir": "./lib",
"declaration": true,
"types": ["node"],
},
"include": ["./src"],

View File

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

View File

@ -1,7 +1,7 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -20,7 +20,7 @@ import { EventEmitter } from 'events';
import { isEmpty } from 'lodash-es';
import { createDataSourceManager, DataSourceManager, type DataSourceManagerData } from '@tmagic/data-source';
import { createDataSourceManager, DataSourceManager, ObservedDataClass } from '@tmagic/data-source';
import type { CodeBlockDSL, Id, JsEngine, MApp, RequestFunction } from '@tmagic/schema';
import Env from './Env';
@ -29,23 +29,31 @@ import Flexible from './Flexible';
import FlowState from './FlowState';
import Node from './Node';
import Page from './Page';
import { AppOptionsConfig, ErrorHandler, GetNodeOptions } from './type';
import { transformStyle as defaultTransformStyle } from './utils';
export interface AppOptionsConfig {
ua?: string;
config?: MApp;
platform?: 'editor' | 'mobile' | 'tv' | 'pc';
jsEngine?: JsEngine;
designWidth?: number;
curPage?: Id;
useMock?: boolean;
disabledFlexible?: boolean;
pageFragmentContainerType?: string | string[];
iteratorContainerType?: string | string[];
transformStyle?: (style: Record<string, any>) => Record<string, any>;
request?: RequestFunction;
DataSourceObservedData?: ObservedDataClass;
}
class App extends EventEmitter {
[x: string]: any;
static nodeClassMap = new Map<string, typeof Node>();
public static registerNode<T extends typeof Node = typeof Node>(type: string, NodeClass: T) {
App.nodeClassMap.set(type, NodeClass);
}
public env!: Env;
public env: Env = new Env();
public dsl?: MApp;
public codeDsl?: CodeBlockDSL;
public dataSourceManager?: DataSourceManager;
public page?: Page;
public pageFragments: Map<Id, Page> = new Map();
public useMock = false;
public platform = 'mobile';
public jsEngine: JsEngine = 'browser';
@ -55,27 +63,17 @@ class App extends EventEmitter {
public request?: RequestFunction;
public transformStyle: (style: Record<string, any>) => Record<string, any>;
public eventHelper?: EventHelper;
public errorHandler?: ErrorHandler;
public nodeStoreInitialData?: () => any;
private flexible?: Flexible;
constructor(options: AppOptionsConfig) {
super();
if (options.env) {
this.setEnv(options.env);
} else {
this.setEnv(options.ua);
}
this.errorHandler = options.errorHandler;
this.setEnv(options.ua);
// 代码块描述内容在dsl codeBlocks字段
this.codeDsl = options.config?.codeBlocks;
options.platform && (this.platform = options.platform);
options.jsEngine && (this.jsEngine = options.jsEngine);
options.nodeStoreInitialData && (this.nodeStoreInitialData = options.nodeStoreInitialData);
if (options.pageFragmentContainerType) {
const pageFragmentContainerType = Array.isArray(options.pageFragmentContainerType)
@ -104,11 +102,7 @@ class App extends EventEmitter {
}
if (this.platform !== 'editor') {
this.eventHelper = new EventHelper({
app: this,
beforeEventHandler: options.beforeEventHandler,
afterEventHandler: options.afterEventHandler,
});
this.eventHelper = new EventHelper({ app: this });
}
this.transformStyle =
@ -119,16 +113,12 @@ class App extends EventEmitter {
}
if (options.config) {
this.setConfig(options.config, options.curPage, options.dataSourceManagerInitialData);
this.setConfig(options.config, options.curPage);
}
}
public setEnv<T extends Env>(ua?: string | T) {
if (!ua || typeof ua === 'string') {
this.env = new Env(ua);
} else {
this.env = ua;
}
public setEnv(ua?: string) {
this.env = new Env(ua);
}
public setDesignWidth(width: number) {
@ -139,9 +129,8 @@ class App extends EventEmitter {
* dsl
* @param config dsl跟节点
* @param curPage id
* @param initialData
*/
public setConfig(config: MApp, curPage?: Id, initialData?: DataSourceManagerData) {
public setConfig(config: MApp, curPage?: Id) {
this.dsl = config;
if (!curPage && config.items.length) {
@ -152,28 +141,14 @@ class App extends EventEmitter {
this.dataSourceManager.destroy();
}
this.dataSourceManager = createDataSourceManager(this, this.useMock, initialData);
this.dataSourceManager = createDataSourceManager(this, this.useMock);
this.codeDsl = config.codeBlocks;
const pageId = curPage || this.page?.data?.id;
super.emit('dsl-change', { dsl: config, curPage: pageId });
this.pageFragments.forEach((page) => {
page.destroy();
});
this.pageFragments.clear();
this.setPage(pageId);
this.setPage(curPage || this.page?.data?.id);
if (this.dataSourceManager) {
if (this.dataSourceManager.isAllDataSourceRegistered()) {
this.eventHelper?.bindDataSourceEvents();
} else {
this.dataSourceManager.once('registered-all', () => {
this.eventHelper?.bindDataSourceEvents();
});
}
const dataSourceList = Array.from(this.dataSourceManager.dataSourceMap.values());
this.eventHelper?.bindDataSourceEvents(dataSourceList);
}
}
@ -187,27 +162,19 @@ class App extends EventEmitter {
return;
}
if (this.page) {
if (pageConfig === this.page.data) return;
this.page.destroy();
}
if (pageConfig === this.page?.data) return;
this.page?.destroy();
this.page = new Page({
config: pageConfig,
app: this,
});
if (this.eventHelper) {
this.eventHelper.removeNodeEvents();
for (const [, node] of this.page.nodes) {
this.eventHelper.bindNodeEvents(node);
}
for (const [, page] of this.pageFragments) {
for (const [, node] of page.nodes) {
this.eventHelper.bindNodeEvents(node);
}
}
}
this.eventHelper?.removeNodeEvents();
this.page.nodes.forEach((node) => {
this.eventHelper?.bindNodeEvents(node);
});
super.emit('page-change', this.page);
}
@ -230,8 +197,8 @@ class App extends EventEmitter {
}
}
public getNode<T extends Node = Node>(id: Id, options?: GetNodeOptions) {
return this.page?.getNode<T>(id, options);
public getNode<T extends Node = Node>(id: Id, iteratorContainerId?: Id[], iteratorIndex?: number[]) {
return this.page?.getNode<T>(id, iteratorContainerId, iteratorIndex);
}
public registerComponent(type: string, Component: any) {
@ -264,19 +231,11 @@ class App extends EventEmitter {
* @param eventConfig
* @returns void
*/
public async runCode(codeId: Id, params: Record<string, any>, args: any[], flowState?: FlowState, node?: Node) {
public async runCode(codeId: Id, params: Record<string, any>, args: any[], flowState?: FlowState) {
if (!codeId || isEmpty(this.codeDsl)) return;
const content = this.codeDsl?.[codeId]?.content;
if (typeof content === 'function') {
try {
await content({ app: this, params, eventParams: args, flowState, node });
} catch (e: any) {
if (this.errorHandler) {
this.errorHandler(e, undefined, { type: 'run-code', codeId, params, eventParams: args, flowState, node });
} else {
throw e;
}
}
await content({ app: this, params, eventParams: args, flowState });
}
}
@ -286,7 +245,6 @@ class App extends EventEmitter {
params: Record<string, any>,
args: any[],
flowState?: FlowState,
node?: Node,
) {
if (!dsId || !methodName) return;
@ -294,46 +252,25 @@ class App extends EventEmitter {
if (!dataSource) return;
try {
const methods = dataSource.methods || [];
const methods = dataSource.methods || [];
const method = methods.find((item) => item.name === methodName);
if (method && typeof method.content === 'function') {
await method.content({ app: this, params, dataSource, eventParams: args, flowState, node });
} else if (typeof dataSource[methodName] === 'function') {
await dataSource[methodName]();
}
} catch (e: any) {
if (this.errorHandler) {
this.errorHandler(e, dataSource, { type: 'data-source-method', params, eventParams: args, flowState, node });
} else {
throw e;
}
const method = methods.find((item) => item.name === methodName);
if (!method) return;
if (typeof method.content === 'function') {
await method.content({ app: this, params, dataSource, eventParams: args, flowState });
}
}
public destroy() {
this.removeAllListeners();
this.page?.destroy();
this.page = undefined;
this.pageFragments.forEach((page) => {
page.destroy();
});
this.pageFragments.clear();
this.flexible?.destroy();
this.flexible = undefined;
this.eventHelper?.destroy();
this.dsl = undefined;
this.dataSourceManager?.destroy();
this.dataSourceManager = undefined;
this.codeDsl = undefined;
this.components.clear();
this.nodeStoreInitialData = undefined;
}
}

View File

@ -1,7 +1,7 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -30,11 +30,7 @@ class Env {
isWeb = false;
isOpenHarmony = false;
constructor(ua = globalThis.navigator?.userAgent ?? '', options: Record<string, boolean | string> = {}) {
if (!ua) {
return;
}
constructor(ua = globalThis.navigator.userAgent, options: Record<string, boolean | string> = {}) {
this.isIphone = ua.indexOf('iPhone') >= 0;
this.isIpad = /(iPad).*OS\s([\d_]+)/.test(ua);

View File

@ -1,7 +1,7 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -31,63 +31,67 @@ import {
type DataSourceItemConfig,
type EventActionItem,
type EventConfig,
Id,
NODE_DISABLE_CODE_BLOCK_KEY,
NODE_DISABLE_DATA_SOURCE_KEY,
} from '@tmagic/schema';
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX } from '@tmagic/utils';
import { DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX, getIdFromEl } from '@tmagic/utils';
import type { default as TMagicApp } from './App';
import FlowState from './FlowState';
import type { default as TMagicNode } from './Node';
import { AfterEventHandler, BeforeEventHandler } from './type';
import { COMMON_EVENT_PREFIX, isCommonMethod, triggerCommonMethod } from './utils';
interface EventCache {
toId: Id;
method: string;
fromCpt: any;
args: any[];
handled?: boolean;
}
const getCommonEventName = (commonEventName: string) => {
if (commonEventName.startsWith(COMMON_EVENT_PREFIX)) return commonEventName;
return `${COMMON_EVENT_PREFIX}${commonEventName}`;
};
// 点击在组件内的某个元素上,需要向上寻找到当前组件
const getDirectComponent = (element: HTMLElement | null, app: TMagicApp): TMagicNode | undefined => {
if (!element) {
return;
}
const id = getIdFromEl()(element);
if (!id) {
return getDirectComponent(element.parentElement, app);
}
const node = app.getNode(
id,
element.dataset.tmagicIteratorContainerId?.split(','),
element.dataset.tmagicIteratorIndex?.split(',').map((i) => globalThis.parseInt(i, 10)),
);
return node;
};
export default class EventHelper extends EventEmitter {
public app: TMagicApp;
public eventQueue: EventCache[] = [];
private nodeEventList = new Map<(fromCpt: TMagicNode, ...args: any[]) => void, symbol>();
private dataSourceEventList = new Map<string, Map<string, (...args: any[]) => void>>();
private beforeEventHandler?: BeforeEventHandler;
private afterEventHandler?: AfterEventHandler;
constructor({
app,
beforeEventHandler,
afterEventHandler,
}: {
app: TMagicApp;
beforeEventHandler?: BeforeEventHandler;
afterEventHandler?: AfterEventHandler;
}) {
constructor({ app }: { app: TMagicApp }) {
super();
this.beforeEventHandler = beforeEventHandler;
this.afterEventHandler = afterEventHandler;
this.app = app;
if (app.jsEngine === 'browser') {
globalThis.document.body.addEventListener('click', this.commonClickEventHandler);
}
}
public destroy() {
this.removeNodeEvents();
this.removeAllListeners();
this.nodeEventList.clear();
this.dataSourceEventList.clear();
if (this.app.jsEngine === 'browser') {
globalThis.document.body.removeEventListener('click', this.commonClickEventHandler);
}
}
public bindNodeEvents(node: TMagicNode) {
node.events?.forEach((event, index) => {
if (!event.name) {
return;
}
let eventNameKey = `${event.name}_${node.data.id}`;
// 页面片容器可以配置页面片内组件的事件,形式为“${nodeId}.${eventName}”
@ -113,23 +117,21 @@ export default class EventHelper extends EventEmitter {
}
public removeNodeEvents() {
for (const handler of Array.from(this.nodeEventList.keys())) {
Array.from(this.nodeEventList.keys()).forEach((handler) => {
const name = this.nodeEventList.get(handler);
name && this.off(name, handler);
}
});
this.nodeEventList.clear();
}
public bindDataSourceEvents() {
const dataSourceList = Array.from(this.app.dataSourceManager?.dataSourceMap.values() || []);
public bindDataSourceEvents(dataSourceList: DataSource[]) {
this.removeDataSourceEvents(dataSourceList);
for (const dataSource of dataSourceList) {
dataSourceList.forEach((dataSource) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSource.id) ?? new Map<string, (args: any) => void>();
for (const event of dataSource.schema.events || []) {
(dataSource.schema.events || []).forEach((event) => {
const [prefix, ...path] = event.name?.split('.') || [];
if (!prefix) return;
const handler = (...args: any[]) => {
@ -143,9 +145,9 @@ export default class EventHelper extends EventEmitter {
// 数据源自定义事件
dataSource.on(prefix, handler);
}
}
});
this.dataSourceEventList.set(dataSource.id, dataSourceEvent);
}
});
}
public removeDataSourceEvents(dataSourceList: DataSource[]) {
@ -154,32 +156,24 @@ export default class EventHelper extends EventEmitter {
}
// 先清掉之前注册的事件,重新注册
for (const dataSource of dataSourceList) {
dataSourceList.forEach((dataSource) => {
const dataSourceEvent = this.dataSourceEventList.get(dataSource.id)!;
if (!dataSourceEvent) return;
for (const eventName of Array.from(dataSourceEvent.keys())) {
Array.from(dataSourceEvent.keys()).forEach((eventName) => {
const [prefix, ...path] = eventName.split('.');
if (prefix === DATA_SOURCE_FIELDS_CHANGE_EVENT_PREFIX) {
dataSource.offDataChange(path.join('.'), dataSourceEvent.get(eventName)!);
} else {
dataSource.off(prefix, dataSourceEvent.get(eventName)!);
}
}
}
});
});
this.dataSourceEventList.clear();
}
public getEventQueue() {
return this.eventQueue;
}
public addEventToQueue(event: EventCache) {
this.eventQueue.push(event);
}
/**
*
* @param eventsConfigIndex node.event中获取最新事件配置
@ -188,11 +182,6 @@ export default class EventHelper extends EventEmitter {
*/
private async eventHandler(config: EventConfig | number, fromCpt: TMagicNode | DataSource | undefined, args: any[]) {
const eventConfig = typeof config === 'number' ? (fromCpt as TMagicNode).events[config] : config;
if (typeof this.beforeEventHandler === 'function') {
this.beforeEventHandler({ eventConfig, source: fromCpt, args });
}
if (has(eventConfig, 'actions')) {
// EventConfig类型
const flowState = new FlowState();
@ -202,27 +191,15 @@ export default class EventHelper extends EventEmitter {
if (typeof config === 'number') {
// 事件响应中可能会有修改数据源数据的会更新dsl所以这里需要重新获取
const actionItem = ((fromCpt as TMagicNode).events[config] as EventConfig).actions[i];
await this.actionHandler(actionItem, fromCpt as TMagicNode, args, flowState);
this.actionHandler(actionItem, fromCpt as TMagicNode, args, flowState);
} else {
await this.actionHandler(actions[i], fromCpt as DataSource, args, flowState);
this.actionHandler(actions[i], fromCpt as DataSource, args, flowState);
}
}
flowState.reset();
} else {
try {
// 兼容DeprecatedEventConfig类型 组件动作
await this.compActionHandler(eventConfig as unknown as CompItemConfig, fromCpt as TMagicNode, args);
} catch (e: any) {
if (this.app.errorHandler) {
this.app.errorHandler(e, fromCpt, { type: 'action-handler', config: eventConfig, ...args });
} else {
throw e;
}
}
}
if (typeof this.afterEventHandler === 'function') {
this.afterEventHandler({ eventConfig, source: fromCpt, args });
// 兼容DeprecatedEventConfig类型 组件动作
await this.compActionHandler(eventConfig as unknown as CompItemConfig, fromCpt as TMagicNode, args);
}
}
@ -232,35 +209,20 @@ export default class EventHelper extends EventEmitter {
args: any[],
flowState: FlowState,
) {
try {
if (actionItem.actionType === ActionType.COMP) {
const compActionItem = actionItem as CompItemConfig;
// 组件动作
await this.compActionHandler(compActionItem, fromCpt, args);
} else if (actionItem.actionType === ActionType.CODE) {
if (fromCpt.data[NODE_DISABLE_CODE_BLOCK_KEY]) {
return;
}
const codeActionItem = actionItem as CodeItemConfig;
// 执行代码块
await this.app.runCode(codeActionItem.codeId, codeActionItem.params || {}, args, flowState);
} else if (actionItem.actionType === ActionType.DATA_SOURCE) {
if (fromCpt.data[NODE_DISABLE_DATA_SOURCE_KEY]) {
return;
}
if (actionItem.actionType === ActionType.COMP) {
const compActionItem = actionItem as CompItemConfig;
// 组件动作
await this.compActionHandler(compActionItem, fromCpt, args);
} else if (actionItem.actionType === ActionType.CODE) {
const codeActionItem = actionItem as CodeItemConfig;
// 执行代码块
await this.app.runCode(codeActionItem.codeId, codeActionItem.params || {}, args, flowState);
} else if (actionItem.actionType === ActionType.DATA_SOURCE) {
const dataSourceActionItem = actionItem as DataSourceItemConfig;
const dataSourceActionItem = actionItem as DataSourceItemConfig;
const [dsId, methodName] = dataSourceActionItem.dataSourceMethod;
const [dsId, methodName] = dataSourceActionItem.dataSourceMethod;
await this.app.runDataSourceMethod(dsId, methodName, dataSourceActionItem.params || {}, args, flowState);
}
} catch (e: any) {
if (this.app.errorHandler) {
this.app.errorHandler(e, fromCpt, { type: 'action-handler', config: actionItem, flowState, ...args });
} else {
throw e;
}
await this.app.runDataSourceMethod(dsId, methodName, dataSourceActionItem.params || {}, args, flowState);
}
}
@ -278,44 +240,36 @@ export default class EventHelper extends EventEmitter {
[to, methodName] = methodName;
}
const toNodes = [];
const toNode = this.app.getNode(to, { strict: true });
if (toNode) {
toNodes.push(toNode);
const toNode = this.app.getNode(to);
if (!toNode) throw `ID为${to}的组件不存在`;
if (isCommonMethod(methodName)) {
return triggerCommonMethod(methodName, toNode);
}
for (const [, page] of this.app.pageFragments) {
const node = page.getNode(to, { strict: true });
if (node) {
toNodes.push(node);
if (toNode.instance) {
if (typeof toNode.instance[methodName] === 'function') {
await toNode.instance[methodName](fromCpt, ...args);
}
}
if (toNodes.length === 0) {
this.addEventToQueue({
toId: to,
} else {
toNode.addEventToQueue({
method: methodName,
fromCpt,
args,
});
}
}
private commonClickEventHandler = (e: MouseEvent) => {
if (!e.target) {
return;
}
const instanceMethodPropmise = [];
for (const node of toNodes) {
if (node.instance) {
if (typeof node.instance[methodName] === 'function') {
instanceMethodPropmise.push(node.instance[methodName](fromCpt, ...args));
}
} else {
node.addEventToQueue({
method: methodName,
fromCpt,
args,
});
}
}
const node = getDirectComponent(e.target as HTMLElement, this.app);
await Promise.all(instanceMethodPropmise);
}
const eventName = `${getCommonEventName('click')}_${node?.data.id}`;
if (node?.eventKeys.has(eventName)) {
this.emit(node.eventKeys.get(eventName)!, node);
}
};
}

View File

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

View File

@ -1,7 +1,7 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,8 +18,9 @@
import { EventEmitter } from 'events';
import { DataSource } from '@tmagic/data-source';
import type { EventConfig, MNode } from '@tmagic/schema';
import { HookCodeType, HookType, NODE_DISABLE_CODE_BLOCK_KEY } from '@tmagic/schema';
import { HookCodeType, HookType } from '@tmagic/schema';
import type { default as TMagicApp } from './App';
import type Page from './Page';
@ -31,10 +32,6 @@ interface EventCache {
args: any[];
}
interface Methods {
[key: string]: (...args: any[]) => any;
}
export interface NodeOptions {
config: MNode;
page?: Page;
@ -48,11 +45,11 @@ class Node extends EventEmitter {
[key: string]: any;
};
public events: EventConfig[] = [];
public instance?: any = null;
public instance?: any;
public page?: Page;
public parent?: Node;
public app: TMagicApp;
public store;
public store = new Store();
public eventKeys = new Map<string, symbol>();
private eventQueue: EventCache[] = [];
@ -60,7 +57,6 @@ class Node extends EventEmitter {
constructor(options: NodeOptions) {
super();
this.store = new Store({ initialData: options.app.nodeStoreInitialData?.() || {} });
this.page = options.page;
this.parent = options.parent;
this.app = options.app;
@ -73,18 +69,6 @@ class Node extends EventEmitter {
const { events, style } = data;
this.events = events || [];
this.style = style || {};
try {
if (
this.instance &&
!Object.isFrozen(this.instance) &&
Object.getOwnPropertyDescriptor(this.instance, 'config')?.writable !== false &&
!this.instance.__isVue
) {
this.instance.config = data;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e: any) {}
this.emit('update-data', data);
}
@ -92,29 +76,6 @@ class Node extends EventEmitter {
this.eventQueue.push(event);
}
/**
* @deprecated use setInstance instead
*/
public registerMethod(methods: Methods) {
if (!methods) {
return;
}
if (!this.instance) {
this.instance = {};
}
for (const [key, fn] of Object.entries(methods)) {
if (typeof fn === 'function') {
this.instance[key] = fn;
}
}
}
public setInstance(instance: any) {
this.instance = instance;
}
public async runHookCode(hook: string, params?: Record<string, any>) {
if (typeof this.data[hook] === 'function') {
// 兼容旧的数据格式
@ -140,78 +101,55 @@ class Node extends EventEmitter {
for (const item of hookData.hookData) {
const { codeType = HookCodeType.CODE, codeId, params: itemParams = {} } = item;
if (codeType === HookCodeType.CODE && typeof codeId === 'string') {
await this.app.runCode(codeId, params || itemParams, [], undefined, this);
let functionContent: ((...args: any[]) => any) | string | undefined;
const functionParams: { app: TMagicApp; params: Record<string, any>; dataSource?: DataSource } = {
app: this.app,
params: params || itemParams,
};
if (codeType === HookCodeType.CODE && typeof codeId === 'string' && this.app.codeDsl?.[codeId]) {
functionContent = this.app.codeDsl[codeId].content;
} else if (codeType === HookCodeType.DATA_SOURCE_METHOD && Array.isArray(codeId) && codeId.length > 1) {
await this.app.runDataSourceMethod(codeId[0], codeId[1], params || itemParams, [], undefined, this);
const dataSource = this.app.dataSourceManager?.get(codeId[0]);
functionContent = dataSource?.methods.find((method) => method.name === codeId[1])?.content;
functionParams.dataSource = dataSource;
}
if (functionContent && typeof functionContent === 'function') {
await functionContent(functionParams);
}
}
}
public destroy() {
this.eventQueue.length = 0;
this.instance = null;
this.events = [];
this.style = {};
this.removeAllListeners();
}
private listenLifeSafe() {
this.once('created', (instance: any) => {
this.once('created', async (instance: any) => {
this.once('destroy', () => {
this.instance = null;
if (this.data[NODE_DISABLE_CODE_BLOCK_KEY] !== true) {
this.runHookCode('destroy');
if (typeof this.data.destroy === 'function') {
this.data.destroy(this);
}
this.listenLifeSafe();
});
if (instance) {
this.setInstance(instance);
}
if (this.data[NODE_DISABLE_CODE_BLOCK_KEY] !== true) {
this.runHookCode('created');
}
this.instance = instance;
await this.runHookCode('created');
});
this.once('mounted', (instance: any) => {
const handler = async () => {
if (instance) {
this.setInstance(instance);
this.once('mounted', async (instance: any) => {
this.instance = instance;
for (let eventConfig = this.eventQueue.shift(); eventConfig; eventConfig = this.eventQueue.shift()) {
if (typeof instance[eventConfig.method] === 'function') {
await instance[eventConfig.method](eventConfig.fromCpt, ...eventConfig.args);
}
}
for (let eventConfig = this.eventQueue.shift(); eventConfig; eventConfig = this.eventQueue.shift()) {
if (typeof instance[eventConfig.method] === 'function') {
await instance[eventConfig.method](eventConfig.fromCpt, ...eventConfig.args);
}
}
if (this.app.eventHelper) {
for (const eventConfig of this.app.eventHelper.getEventQueue()) {
for (const [, page] of this.app.pageFragments) {
const node = page.getNode(eventConfig.toId, { strict: true });
if (node && node === this) {
if (typeof instance[eventConfig.method] === 'function') {
await instance[eventConfig.method](eventConfig.fromCpt, ...eventConfig.args);
}
eventConfig.handled = true;
}
}
}
this.app.eventHelper.eventQueue = this.app.eventHelper
.getEventQueue()
.filter((eventConfig) => !eventConfig.handled);
}
if (this.data[NODE_DISABLE_CODE_BLOCK_KEY] !== true) {
this.runHookCode('mounted');
}
};
handler();
await this.runHookCode('mounted');
});
}
}

View File

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

View File

@ -1,9 +1,5 @@
export default class Store {
private data: any;
constructor({ initialData = {} }: { initialData?: any } = {}) {
this.data = initialData;
}
private data: Record<string, any> = {};
public set(key: string, value: any) {
this.data[key] = value;

View File

@ -1,7 +1,7 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -28,7 +28,7 @@ export * from '@tmagic/utils';
export { default as EventHelper } from './EventHelper';
export * from './utils';
export * from './type';
export { type AppOptionsConfig } from './App';
export { default as Env } from './Env';
export { default as Page } from './Page';
export { default as Node } from './Node';

View File

@ -1,53 +0,0 @@
import type { DataSource, DataSourceManagerData, ObservedDataClass } from '@tmagic/data-source';
import type { DataSourceSchema, EventConfig, Id, JsEngine, MApp, RequestFunction } from '@tmagic/schema';
import type Env from './Env';
import type TMagicNode from './Node';
export type ErrorHandler = (
err: Error,
node: DataSource<DataSourceSchema> | TMagicNode | undefined,
info: Record<string, any>,
) => void;
export interface AppOptionsConfig {
ua?: string;
env?: Env;
config?: MApp;
platform?: 'editor' | 'mobile' | 'tv' | 'pc';
jsEngine?: JsEngine;
designWidth?: number;
curPage?: Id;
useMock?: boolean;
disabledFlexible?: boolean;
pageFragmentContainerType?: string | string[];
iteratorContainerType?: string | string[];
transformStyle?: (style: Record<string, any>) => Record<string, any>;
request?: RequestFunction;
DataSourceObservedData?: ObservedDataClass;
dataSourceManagerInitialData?: DataSourceManagerData;
nodeStoreInitialData?: () => any;
errorHandler?: ErrorHandler;
beforeEventHandler?: BeforeEventHandler;
afterEventHandler?: AfterEventHandler;
}
export type BeforeEventHandler = (args: {
eventConfig: EventConfig;
source: TMagicNode | DataSource | undefined;
args: any[];
}) => void;
export type AfterEventHandler = (args: {
eventConfig: EventConfig;
source: TMagicNode | DataSource | undefined;
args: any[];
}) => void;
export interface GetNodeOptions {
iteratorContainerId?: Id[];
iteratorIndex?: number[];
pageFragmentContainerId?: Id;
/** 严格模式如果为true页面片中的节点必须指定pageFragmentContainerId为false时没有pageFragmentContainerId的时候获得第一个页面片容器中的节点 */
strict?: boolean;
}

View File

@ -1,7 +1,7 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -18,6 +18,8 @@
import { JsEngine } from '@tmagic/schema';
import { isNumber } from '@tmagic/utils';
import type { default as TMagicNode } from './Node';
export const style2Obj = (style: string) => {
if (typeof style !== 'string') {
return style;
@ -117,7 +119,47 @@ export const transformStyle = (style: Record<string, any> | string, jsEngine: Js
export const COMMON_EVENT_PREFIX = 'magic:common:events:';
export const COMMON_METHOD_PREFIX = 'magic:common:actions:';
export const CommonMethod = {
SHOW: 'show',
HIDE: 'hide',
SCROLL_TO_VIEW: 'scrollIntoView',
SCROLL_TO_TOP: 'scrollToTop',
};
export const isCommonMethod = (methodName: string) => methodName.startsWith(COMMON_METHOD_PREFIX);
export const triggerCommonMethod = (methodName: string, node: TMagicNode) => {
const { instance } = node;
if (!instance) return;
switch (methodName.replace(COMMON_METHOD_PREFIX, '')) {
case CommonMethod.SHOW:
instance.show();
break;
case CommonMethod.HIDE:
instance.hide();
break;
case CommonMethod.SCROLL_TO_VIEW:
instance.$el?.scrollIntoView({ behavior: 'smooth' });
break;
case CommonMethod.SCROLL_TO_TOP:
window.scrollTo({ top: 0, behavior: 'smooth' });
break;
default:
break;
}
};
export interface EventOption {
label: string;
value: string;
}
export const DEFAULT_EVENTS: EventOption[] = [{ label: '点击', value: `${COMMON_EVENT_PREFIX}click` }];
export const DEFAULT_METHODS: EventOption[] = [];

View File

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

View File

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

View File

@ -1,8 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* Copyright (C) 2025 Tencent. All rights reserved.
* Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -21,58 +20,27 @@ import EventEmitter from 'events';
import { cloneDeep } from 'lodash-es';
import type { DataSourceSchema, default as TMagicApp, DisplayCond, Id, MNode } from '@tmagic/core';
import {
compiledNode,
getDefaultValueFromFields,
NODE_CONDS_KEY,
NODE_CONDS_RESULT_KEY,
NODE_DISABLE_DATA_SOURCE_KEY,
} from '@tmagic/core';
import type { DataSourceSchema, default as TMagicApp, DisplayCond, Id, MNode, NODE_CONDS_KEY } from '@tmagic/core';
import { compiledNode } from '@tmagic/core';
import { SimpleObservedData } from './observed-data/SimpleObservedData';
import { DataSource, HttpDataSource } from './data-sources';
import { getDeps } from './depsCache';
import type {
ChangeEvent,
DataSourceManagerData,
DataSourceManagerOptions,
ObservedDataClass,
SchemaListMap,
} from './types';
import type { ChangeEvent, DataSourceManagerData, DataSourceManagerOptions, ObservedDataClass } from './types';
import { compiledNodeField, compliedConditions, compliedIteratorItem, createIteratorContentData } from './utils';
class DataSourceManager extends EventEmitter {
private static dataSourceClassMap = new Map<string, any>([
['base', DataSource],
['http', HttpDataSource],
]);
private static dataSourceClassMap = new Map<string, typeof DataSource>();
private static ObservedDataClass: ObservedDataClass = SimpleObservedData;
private static waitInitSchemaList = new Map<DataSourceManager, SchemaListMap>();
public static register<T extends typeof DataSource = typeof DataSource>(type: string, dataSource: T) {
DataSourceManager.dataSourceClassMap.set(type, dataSource);
DataSourceManager.waitInitSchemaList?.forEach((listMap, app) => {
const list = listMap[type] || [];
for (let config = list.shift(); config; config = list.shift()) {
const ds = app.addDataSource(config);
if (ds) {
app.init(ds);
}
}
});
}
public static getDataSourceClass(type: string) {
return DataSourceManager.dataSourceClassMap.get(type);
}
public static clearDataSourceClass() {
DataSourceManager.dataSourceClassMap.clear();
DataSourceManager.dataSourceClassMap.set('base', DataSource);
DataSourceManager.dataSourceClassMap.set('http', HttpDataSource);
}
public static registerObservedData(ObservedDataClass: ObservedDataClass) {
DataSourceManager.ObservedDataClass = ObservedDataClass;
}
@ -82,32 +50,53 @@ class DataSourceManager extends EventEmitter {
public dataSourceMap = new Map<string, DataSource & Record<string, any>>();
public data: DataSourceManagerData = {};
public initialData: DataSourceManagerData = {};
public useMock?: boolean = false;
constructor({ app, useMock, initialData }: DataSourceManagerOptions) {
super();
DataSourceManager.waitInitSchemaList.set(this, {});
this.app = app;
this.useMock = useMock;
if (initialData) {
this.initialData = initialData;
this.data = { ...initialData };
this.data = initialData;
}
app.dsl?.dataSources?.forEach((config) => {
this.addDataSource(config);
});
if (this.isAllDataSourceRegistered()) {
this.callDsInit();
} else {
this.on('registered-all', () => {
this.callDsInit();
const dataSourceList = Array.from(this.dataSourceMap);
if (typeof Promise.allSettled === 'function') {
Promise.allSettled<Record<string, any>>(dataSourceList.map(([, ds]) => this.init(ds))).then((values) => {
const data: DataSourceManagerData = {};
const errors: Record<string, Error> = {};
values.forEach((value, index) => {
const dsId = dataSourceList[index][0];
if (value.status === 'fulfilled') {
if (this.data[dsId]) {
data[dsId] = this.data[dsId];
} else {
delete data[dsId];
}
} else if (value.status === 'rejected') {
delete data[dsId];
errors[dsId] = value.reason;
}
});
this.emit('init', data, errors);
});
} else {
Promise.all<Record<string, any>>(dataSourceList.map(([, ds]) => this.init(ds)))
.then(() => {
this.emit('init', this.data);
})
.catch(() => {
this.emit('init', this.data);
});
}
}
@ -141,37 +130,17 @@ class DataSourceManager extends EventEmitter {
return this.dataSourceMap.get(id);
}
public addDataSource(config?: DataSourceSchema) {
public async addDataSource(config?: DataSourceSchema) {
if (!config) return;
const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type);
if (!DataSourceClass) {
let listMap = DataSourceManager.waitInitSchemaList.get(this);
if (!listMap) {
listMap = {};
DataSourceManager.waitInitSchemaList.set(this, listMap);
}
if (listMap[config.type]) {
listMap[config.type].push(config);
} else {
listMap[config.type] = [config];
}
// 保证初始化时的第一次编译有值
this.data[config.id] = this.initialData[config.id] ?? getDefaultValueFromFields(config.fields);
return;
}
const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type) || DataSource;
const ds = new DataSourceClass({
app: this.app,
schema: config,
request: this.app.request,
useMock: this.useMock,
initialData: this.initialData[config.id],
initialData: this.data[config.id],
ObservedDataClass: DataSourceManager.ObservedDataClass,
});
@ -182,12 +151,6 @@ class DataSourceManager extends EventEmitter {
ds.on('change', (changeEvent: ChangeEvent) => {
this.setData(ds, changeEvent);
});
if (this.isAllDataSourceRegistered()) {
this.emit('registered-all');
}
return ds;
}
public setData(ds: DataSource, changeEvent: ChangeEvent) {
@ -206,22 +169,20 @@ class DataSourceManager extends EventEmitter {
* @param {DataSourceSchema[]} schemas
*/
public updateSchema(schemas: DataSourceSchema[]) {
for (const schema of schemas) {
schemas.forEach((schema) => {
const ds = this.get(schema.id);
if (!ds) {
return;
}
this.removeDataSource(schema.id);
}
for (const schema of schemas) {
this.addDataSource(cloneDeep(schema));
const newDs = this.get(schema.id);
if (newDs) {
this.init(newDs);
}
}
});
}
/**
@ -231,12 +192,7 @@ class DataSourceManager extends EventEmitter {
* @param {boolean} deep items)false
* @returns {MNode} dsl
*/
public compiledNode(n: MNode, sourceId?: Id, deep = false) {
if (n[NODE_DISABLE_DATA_SOURCE_KEY]) {
return n;
}
const { items, ...node } = n;
public compiledNode({ items, ...node }: MNode, sourceId?: Id, deep = false) {
const newNode = cloneDeep(node);
if (items) {
@ -244,9 +200,8 @@ class DataSourceManager extends EventEmitter {
Array.isArray(items) && deep ? items.map((item) => this.compiledNode(item, sourceId, deep)) : items;
}
if (node.condResult === false || (typeof node.condResult === 'undefined' && node[NODE_CONDS_RESULT_KEY])) {
return newNode;
}
if (node.condResult === false) return newNode;
if (node.visible === false) return newNode;
// 编译函数这里作为参数,方便后续支持自定义编译
return compiledNode(
@ -262,24 +217,8 @@ class DataSourceManager extends EventEmitter {
* @param {{ [NODE_CONDS_KEY]?: DisplayCond[] }} node
* @returns {boolean}
*/
public compliedConds(
node: {
[NODE_CONDS_KEY]?: DisplayCond[];
[NODE_CONDS_RESULT_KEY]?: boolean;
[NODE_DISABLE_DATA_SOURCE_KEY]?: boolean;
},
data = this.data,
) {
if (node[NODE_DISABLE_DATA_SOURCE_KEY]) {
return true;
}
const result = compliedConditions(node, data);
if (!node[NODE_CONDS_RESULT_KEY]) {
return result;
}
return !result;
public compliedConds(node: { [NODE_CONDS_KEY]?: DisplayCond[] }) {
return compliedConditions(node, this.data);
}
/**
@ -291,11 +230,7 @@ class DataSourceManager extends EventEmitter {
*/
public compliedIteratorItemConds(
itemData: any,
node: {
[NODE_CONDS_KEY]?: DisplayCond[];
[NODE_CONDS_RESULT_KEY]?: boolean;
[NODE_DISABLE_DATA_SOURCE_KEY]?: boolean;
},
node: { [NODE_CONDS_KEY]?: DisplayCond[] },
dataSourceField: string[] = [],
) {
const [dsId, ...keys] = dataSourceField;
@ -303,7 +238,7 @@ class DataSourceManager extends EventEmitter {
if (!ds) return true;
const ctxData = createIteratorContentData(itemData, ds.id, keys, this.data);
return this.compliedConds(node, ctxData);
return compliedConditions(node, ctxData);
}
public compliedIteratorItems(itemData: any, nodes: MNode[], dataSourceField: string[] = []): MNode[] {
@ -334,19 +269,13 @@ class DataSourceManager extends EventEmitter {
);
}
public isAllDataSourceRegistered() {
return !this.app.dsl?.dataSources?.length || this.dataSourceMap.size === this.app.dsl.dataSources.length;
}
public destroy() {
this.removeAllListeners();
this.data = {};
this.initialData = {};
this.dataSourceMap.forEach((ds) => {
ds.destroy();
});
this.dataSourceMap.clear();
DataSourceManager.waitInitSchemaList.delete(this);
}
public onDataChange(id: string, path: string, callback: (newVal: any) => void) {
@ -356,40 +285,8 @@ class DataSourceManager extends EventEmitter {
public offDataChange(id: string, path: string, callback: (newVal: any) => void) {
return this.get(id)?.offDataChange(path, callback);
}
private callDsInit() {
const dataSourceList = Array.from(this.dataSourceMap);
if (typeof Promise.allSettled === 'function') {
Promise.allSettled<Record<string, any>>(dataSourceList.map(([, ds]) => this.init(ds))).then((values) => {
const data: DataSourceManagerData = {};
const errors: Record<string, Error> = {};
values.forEach((value, index) => {
const dsId = dataSourceList[index][0];
if (value.status === 'fulfilled') {
if (this.data[dsId]) {
data[dsId] = this.data[dsId];
} else {
delete data[dsId];
}
} else if (value.status === 'rejected') {
delete data[dsId];
errors[dsId] = value.reason;
}
});
this.emit('init', data, errors);
});
} else {
Promise.all<Record<string, any>>(dataSourceList.map(([, ds]) => this.init(ds)))
.then(() => {
this.emit('init', this.data);
})
.catch(() => {
this.emit('init', this.data);
});
}
}
}
DataSourceManager.register('http', HttpDataSource as any);
export default DataSourceManager;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -65,7 +65,3 @@ export interface ChangeEvent {
export type AsyncDataSourceResolveResult<T = typeof DataSource> = {
default: T;
};
export interface SchemaListMap {
[key: string]: DataSourceSchema[];
}

View File

@ -139,7 +139,6 @@ export const compliedDataSourceField = (value: any, data: DataSourceManagerData)
try {
return getValueByKeyPath(fields.join('.'), dsData);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
return value;
}
@ -149,10 +148,9 @@ export const compliedDataSourceField = (value: any, data: DataSourceManagerData)
};
export const template = (value: string, data?: DataSourceManagerData) =>
value.replace(dataSourceTemplateRegExp, (match, $1) => {
value.replaceAll(dataSourceTemplateRegExp, (match, $1) => {
try {
return getValueByKeyPath($1, data);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e: any) {
return match;
}

View File

@ -1,4 +1,4 @@
import { afterAll, describe, expect, test } from 'vitest';
import { describe, expect, test } from 'vitest';
import TMagicApp, { NodeType } from '@tmagic/core';
@ -28,10 +28,6 @@ const app = new TMagicApp({
},
});
afterAll(async () => {
DataSourceManager.clearDataSourceClass();
});
describe('DataSourceManager', () => {
const dsm = new DataSourceManager({
app,

View File

@ -47,7 +47,5 @@ describe('createDataSourceManager', () => {
test('instance', () => {
const manager = createDataSourceManager(new TMagicApp({ config: dsl }));
expect(manager).toBeInstanceOf(DataSourceManager);
DataSourceManager.clearDataSourceClass();
});
});

View File

@ -1,7 +1,5 @@
import { describe, expect, test } from 'vitest';
import { dataSourceTemplateRegExp } from '@tmagic/core';
import { compiledCondition, createIteratorContentData, template } from '@data-source/utils';
describe('compiledCondition', () => {
@ -41,122 +39,6 @@ describe('template', () => {
const value = template('xxx${aa.bb}123${aa1.bb1}dsf', { aa: { bb: 1 }, aa1: { bb1: 2 } });
expect(value).toBe('xxx11232dsf');
});
// 测试1: 普通字符串,没有模板变量
test('should return original string when no template variables', () => {
const value = 'This is a plain text';
const result = template(value);
expect(result).toBe(value);
});
// 测试2: 包含模板变量但未提供数据源
test('should keep template variables when no data provided', () => {
const value = 'Hello ${user.name}';
const result = template(value);
expect(result).toBe(value);
});
// 测试3: 包含模板变量且数据源中有对应值
test('should replace template variables with data source values', () => {
const value = 'Hello ${user.name}, your age is ${user.age}';
const data = {
user: {
name: 'John',
age: 30,
},
};
const result = template(value, data);
expect(result).toBe('Hello John, your age is 30');
});
// 测试4: 模板变量在数据源中不存在
test('should keep template variables when path not found in data', () => {
const value = 'Hello ${user.name}, your job is ${user.job}';
const data = {
user: {
name: 'John',
},
};
const result = template(value, data);
expect(result).toBe('Hello John, your job is undefined');
});
// 测试5: 多层嵌套的数据源路径
test('should handle deeply nested data paths', () => {
const value = 'User info: ${user.personal.details.address.city}';
const data = {
user: {
personal: {
details: {
address: {
city: 'Beijing',
},
},
},
},
};
const result = template(value, data);
expect(result).toBe('User info: Beijing');
});
// 测试6: 多个模板变量混合的情况
test('should handle multiple template variables mixed with text', () => {
const value = 'User: ${user.name}, Age: ${user.age}, Score: ${user.score}';
const data = {
user: {
name: 'Alice',
age: 25,
score: 95,
},
};
const result = template(value, data);
expect(result).toBe('User: Alice, Age: 25, Score: 95');
});
// 测试7: 数据源为 undefined 或 null
test('should handle undefined or null data source', () => {
const value = 'Test ${some.value}';
// 测试 undefined
const result1 = template(value, undefined);
expect(result1).toBe(value);
// 测试 null
const result2 = template(value, null as any);
expect(result2).toBe(value);
});
// 测试8: 空字符串输入
test('should handle empty string input', () => {
const value = '';
const result = template(value);
expect(result).toBe('');
});
// 测试9: 正则表达式匹配测试
test('should correctly match template patterns', () => {
// 测试正则表达式是否能正确匹配模板变量
const testString = '${a.b.c} ${x.y.z}';
const matches = testString.match(dataSourceTemplateRegExp);
expect(matches).not.toBeNull();
expect(matches?.length).toBeGreaterThan(0);
});
// 测试10: 复杂混合情况
test('should handle complex mixed cases', () => {
const value = 'User: ${user.name}, ${user.age} years old. ${not.found} ${user.address.city}';
const data = {
user: {
name: 'Bob',
age: 40,
address: {
city: 'Shanghai',
},
},
};
const result = template(value, data);
expect(result).toBe('User: Bob, 40 years old. ${not.found} Shanghai');
});
});
describe('createIteratorContentData', () => {

View File

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

View File

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

View File

@ -55,7 +55,8 @@ export const isIncludeArrayField = (keys: string[], fields: DataSchema[]) => {
// 字段类型为数组并且后面没有数字索引
return (
field?.type === 'array' &&
field &&
field.type === 'array' &&
// 不是整数
/^(?!\d+$).*$/.test(`${keys[index + 1]}`) &&
index < keys.length - 1
@ -262,32 +263,20 @@ export const createDataSourceCondTarget = (ds: Pick<DataSourceSchema, 'id' | 'fi
isTarget: (key: string | number, value: any) => isDataSourceCondTarget(ds, key, value),
});
export const createDataSourceMethodTarget = (
ds: Pick<DataSourceSchema, 'id' | 'methods' | 'fields'>,
initialDeps: DepData = {},
) =>
export const createDataSourceMethodTarget = (ds: Pick<DataSourceSchema, 'id' | 'fields'>, initialDeps: DepData = {}) =>
new Target({
type: DepTargetType.DATA_SOURCE_METHOD,
id: ds.id,
initialDeps,
isTarget: (_key: string | number, value: any) => {
// 使用data-source-method-select 可以配置出来
if (!Array.isArray(value)) {
if (!Array.isArray(value) || !ds) {
return false;
}
const [dsId, methodName] = value;
const [dsId, ...keys] = value;
if (!methodName || dsId !== ds.id) {
return false;
}
if (ds.methods?.find((method) => method.name === methodName)) {
return true;
}
// 配置的方法名称可能会是数据源类中定义的并不存在于methods中所以这里判断如果methodName如果是字段名称就表示配置的不是方法
if (ds.fields?.find((field) => field.name === methodName)) {
if (dsId !== ds.id || ds.fields?.find((field) => field.name === keys[0])) {
return false;
}

View File

@ -125,7 +125,7 @@ describe('utils', () => {
test('isUseDataSourceField', () => {
expect(utils.isUseDataSourceField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}dsID`, 'field'], 'dsID')).toBeTruthy();
expect(utils.isUseDataSourceField([`${DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX}dsID`, 'field'], 'dsID1')).toBeFalsy();
expect(utils.isUseDataSourceField(['abcdsID', 'field'], 'dsID')).toBeFalsy();
expect(utils.isUseDataSourceField([`abcdsID`, 'field'], 'dsID')).toBeFalsy();
expect(utils.isUseDataSourceField([123, 'field'], 123)).toBeFalsy();
});

View File

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

View File

@ -27,7 +27,7 @@
</template>
<script setup lang="ts">
import { computed, ref, useTemplateRef, watchEffect } from 'vue';
import { computed, ref, watchEffect } from 'vue';
import { getDesignConfig } from './config';
import type { AutocompleteProps } from './types';
@ -42,7 +42,7 @@ const ui = getDesignConfig('components')?.autocomplete;
const uiComponent = ui?.component || 'el-autocomplete';
const uiProps = computed<AutocompleteProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['change', 'select', 'update:modelValue']);
@ -58,13 +58,13 @@ const updateModelValue = (...args: any[]) => {
emit('update:modelValue', ...args);
};
const autocompleteRef = useTemplateRef<any>('autocomplete');
const autocomplete = ref<any>();
const input = ref<HTMLInputElement>();
const inputRef = ref<any>();
watchEffect(() => {
inputRef.value = autocompleteRef.value?.inputRef;
input.value = autocompleteRef.value?.inputRef.input;
inputRef.value = autocomplete.value?.inputRef;
input.value = autocomplete.value?.inputRef.input;
});
defineExpose({
@ -72,10 +72,10 @@ defineExpose({
input,
blur: () => {
autocompleteRef.value?.blur();
autocomplete.value?.blur();
},
focus: () => {
autocompleteRef.value?.focus();
autocomplete.value?.focus();
},
});
</script>

View File

@ -19,5 +19,5 @@ const props = defineProps<BadgeProps>();
const ui = getDesignConfig('components')?.badge;
const uiComponent = ui?.component || 'el-badge';
const uiProps = computed<BadgeProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
</script>

View File

@ -1,8 +1,5 @@
<template>
<component class="tmagic-design-button" :is="uiComponent" v-bind="uiProps" @click="clickHandler">
<template #icon v-if="$slots.icon">
<slot name="icon"></slot>
</template>
<template #default v-if="$slots.default">
<slot></slot>
</template>
@ -25,7 +22,7 @@ const ui = getDesignConfig('components')?.button;
const uiComponent = ui?.component || 'el-button';
const uiProps = computed<ButtonProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['click']);
@ -33,15 +30,3 @@ const clickHandler = (...args: any[]) => {
emit('click', ...args);
};
</script>
<style lang="scss">
.tmagic-design-button {
.t-button__text {
align-items: center;
}
+ .tmagic-design-button {
margin-left: 12px;
}
}
</style>

View File

@ -1,10 +1,10 @@
<template>
<component class="tmagic-design-card" :is="uiComponent" v-bind="uiProps">
<template #header v-if="$slots.header">
<template #header>
<slot name="header" class="header"></slot>
</template>
<template #default v-if="$slots.default">
<template #default>
<slot name="default"></slot>
</template>
</component>
@ -26,5 +26,5 @@ const ui = getDesignConfig('components')?.card;
const uiComponent = ui?.component || 'el-card';
const uiProps = computed<CardProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
</script>

View File

@ -25,7 +25,7 @@ const ui = getDesignConfig('components')?.cascader;
const uiComponent = ui?.component || 'el-cascader';
const uiProps = computed<CascaderProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const cascader = ref<any>();

View File

@ -6,7 +6,7 @@
@update:modelValue="updateModelValue"
@change="changeHandler"
>
<template #default v-if="$slots.default">
<template #default>
<slot></slot>
</template>
</component>
@ -31,7 +31,7 @@ const ui = getDesignConfig('components')?.checkbox;
const uiComponent = ui?.component || 'el-checkbox';
const uiProps = computed<CheckboxProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['change', 'update:modelValue']);

View File

@ -26,7 +26,7 @@ const ui = getDesignConfig('components')?.checkboxGroup;
const uiComponent = ui?.component || 'el-checkbox-group';
const uiProps = computed<CheckboxGroupProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['change', 'update:modelValue']);

View File

@ -20,5 +20,5 @@ const ui = getDesignConfig('components')?.col;
const uiComponent = ui?.component || 'el-col';
const uiProps = computed<ColProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
</script>

View File

@ -26,7 +26,7 @@ const ui = getDesignConfig('components')?.collapse;
const uiComponent = ui?.component || 'el-collapse';
const uiProps = computed<CollapseProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['change', 'update:modelValue']);

View File

@ -32,7 +32,7 @@ const ui = getDesignConfig('components')?.collapseItem;
const uiComponent = ui?.component || 'el-collapse-item';
const uiProps = computed<CollapseItemProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['change', 'update:modelValue']);

View File

@ -28,7 +28,7 @@ const ui = getDesignConfig('components')?.colorPicker;
const uiComponent = ui?.component || 'el-color-picker';
const uiProps = computed<ColorPickerProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['change', 'update:modelValue']);

View File

@ -27,7 +27,7 @@ const ui = getDesignConfig('components')?.datePicker;
const uiComponent = ui?.component || 'el-date-picker';
const uiProps = computed<DatePickerProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['change', 'update:modelValue']);

View File

@ -24,11 +24,7 @@ defineOptions({
name: 'TMDialog',
});
const props = withDefaults(defineProps<DialogProps>(), {
closeOnClickModal: true,
closeOnPressEscape: true,
showClose: true,
});
const props = defineProps<DialogProps>();
const emit = defineEmits(['close', 'update:modelValue']);
@ -36,7 +32,7 @@ const ui = getDesignConfig('components')?.dialog;
const uiComponent = ui?.component || 'el-dialog';
const uiProps = computed<DialogProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const closeHandler = (...args: any[]) => {
emit('close', ...args);

View File

@ -20,5 +20,5 @@ const ui = getDesignConfig('components')?.divider;
const uiComponent = ui?.component || 'el-divider';
const uiProps = computed<DividerProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
</script>

View File

@ -40,7 +40,7 @@ const ui = getDesignConfig('components')?.drawer;
const uiComponent = ui?.component || 'el-drawer';
const uiProps = computed<DrawerProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const drawer = ref<any>();

View File

@ -24,7 +24,7 @@ const ui = getDesignConfig('components')?.dropdown;
const uiComponent = ui?.component || 'el-dropdown';
const uiProps = computed<DropdownProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['command']);

View File

@ -20,5 +20,5 @@ const ui = getDesignConfig('components')?.dropdownItem;
const uiComponent = ui?.component || 'el-dropdown-item';
const uiProps = computed<DropdownItemProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
</script>

View File

@ -26,7 +26,7 @@ const ui = getDesignConfig('components')?.form;
const uiComponent = ui?.component || 'el-form';
const uiProps = computed<FormProps>(() => ui?.props(props) || props);
const uiProps = computed(() => ui?.props(props) || props);
const form = ref<any>();

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