Compare commits

..

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

339 changed files with 6134 additions and 7500 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,16 +10,16 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
with: with:
fetch-depth: 0 fetch-depth: 0
ref: dev ref: dev
- name: Install pnpm - name: Install pnpm
uses: pnpm/action-setup@v4 uses: pnpm/action-setup@v2
- name: Set node version to 18 - name: Set node version to 18
uses: actions/setup-node@v4 uses: actions/setup-node@v2
with: with:
node-version: 18 node-version: 18
cache: 'pnpm' cache: 'pnpm'

2
.gitignore vendored
View File

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

View File

@ -1,143 +1,3 @@
## [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)
### Bug Fixes
* **core:** deep-state-observer update 的data与初始化时相同导致get data为空 ([701a9d6](https://github.com/Tencent/tmagic-editor/commit/701a9d6f3043f77048f6b9be3bc2305adb14d57e))
* **editor:** 修改数据源可能造成页面卡死 ([e9c6a3b](https://github.com/Tencent/tmagic-editor/commit/e9c6a3bb2fd5b3b4cecffcedef47a020dba0fde2))
* **editor:** 已经配置了的样式无法删除 ([e8461f9](https://github.com/Tencent/tmagic-editor/commit/e8461f91e62031f87dbc976f41e873f2bcc3c00c))
* **editor:** 编辑器组件销毁后重置依赖收集 ([51ca1e6](https://github.com/Tencent/tmagic-editor/commit/51ca1e60af1ca89e6b5966825e6c0ed3707f4582))
### Features
* **editor:** 优化浏览器变小时各列大小变化 ([877a4ea](https://github.com/Tencent/tmagic-editor/commit/877a4eaa49b6826e04426e162049db14f6a4f15e))
## [1.5.5](https://github.com/Tencent/tmagic-editor/compare/v1.5.4...v1.5.5) (2025-01-14) ## [1.5.5](https://github.com/Tencent/tmagic-editor/compare/v1.5.4...v1.5.5) (2025-01-14)

View File

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

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.
['^(@|@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,39 +0,0 @@
import js from '@eslint/js';
import stylistic from '@stylistic/eslint-plugin';
import stylisticTs from '@stylistic/eslint-plugin-ts';
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,
'@stylistic/ts': stylisticTs,
},
languageOptions: {
parser: parserTs,
parserOptions: {
project: tsconfigRootDir,
},
},
...tencentEslintTsConfig,
}),
pluginVue.configs['flat/essential'],
{ files: ['**/*.vue'], languageOptions: { parserOptions: { parser: tseslint.parser } } },
eslintPluginPrettierRecommended,
tencentEslintPrettierConfig,
ImportSortConfig,
]);

View File

@ -1,29 +0,0 @@
{
"name": "@tmagic/eslint-config",
"version": "0.0.1",
"main": "index.mjs",
"type": "module",
"repository": {
"directory": "eslint-config",
"type": "git",
"url": "https://github.com/Tencent/tmagic-editor.git"
},
"dependencies": {
"@eslint/js": "^9.24.0",
"@typescript-eslint/parser": "^8.30.1",
"@typescript-eslint/eslint-plugin": "^8.30.1",
"@stylistic/eslint-plugin": "^4.2.0",
"@stylistic/eslint-plugin-ts": "^4.2.0",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-vue": "^10.0.0",
"eslint-plugin-prettier": "^5.2.6",
"globals": "^16.0.0",
"typescript-eslint": "^8.30.1"
},
"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,23 +1,23 @@
{ {
"version": "1.5.14", "version": "1.5.5",
"name": "tmagic", "name": "tmagic",
"private": true, "private": true,
"type": "module", "type": "module",
"packageManager": "pnpm@9.15.4", "packageManager": "pnpm@9.12.3",
"scripts": { "scripts": {
"bootstrap": "pnpm i && pnpm build", "bootstrap": "pnpm i && pnpm build",
"clean:top": "rimraf */**/dist */**/types */dist coverage dwt* temp packages/cli/lib", "clean:top": "rimraf */**/dist */**/types */dist coverage dwt* temp",
"clean:modules": "rimraf node_modules **/node_modules **/**/node_modules", "clean:modules": "rimraf node_modules **/node_modules **/**/node_modules",
"clean:all": "pnpm clean:top && pnpm clean:modules", "clean:all": "pnpm clean:top && pnpm clean:modules",
"lint": "eslint --cache .", "lint": "eslint . --ext .js,.vue,.ts,.tsx",
"lint-fix": "eslint --fix --cache .", "lint-fix": "eslint . --fix --ext .vue,.js,.ts,.tsx",
"playground": "pnpm --filter \"runtime-vue3\" build:libs && pnpm --filter \"runtime-vue3\" --filter \"tmagic-playground\" dev", "playground": "pnpm --filter \"runtime-vue3\" build:libs && pnpm --filter \"runtime-vue3\" --filter \"tmagic-playground\" dev",
"pg": "pnpm playground", "pg": "pnpm playground",
"playground:vue2": "pnpm --filter \"runtime-vue2\" build:libs && pnpm --filter \"runtime-vue2\" --filter \"tmagic-playground\" dev:vue2", "playground:vue2": "pnpm --filter \"runtime-vue2\" build:libs && pnpm --filter \"runtime-vue2\" --filter \"tmagic-playground\" dev:vue2",
"pg:vue2": "pnpm playground:vue2", "pg:vue2": "pnpm playground:vue2",
"playground:react": "pnpm --filter \"runtime-react\" build:libs && pnpm --filter \"runtime-react\" --filter \"tmagic-playground\" dev:react", "playground:react": "pnpm --filter \"runtime-react\" build:libs && pnpm --filter \"runtime-react\" --filter \"tmagic-playground\" dev:react",
"pg:react": "pnpm playground:react", "pg:react": "pnpm playground:react",
"build": "pnpm build:dts && node scripts/build.mjs", "build": "pnpm build:dts && pnpm --filter \"@tmagic/*\" build",
"build:dts": "pnpm --filter \"@tmagic/cli\" build && tsc -p tsconfig.build-browser.json && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.build-vue.json && rollup -c rollup.dts.config.js && rimraf temp", "build:dts": "pnpm --filter \"@tmagic/cli\" build && tsc -p tsconfig.build-browser.json && vue-tsc --declaration --emitDeclarationOnly --project tsconfig.build-vue.json && rollup -c rollup.dts.config.js && rimraf temp",
"check:type": "tsc --incremental --noEmit -p tsconfig.check.json && vue-tsc --noEmit -p tsconfig.check-vue.json", "check:type": "tsc --incremental --noEmit -p tsconfig.check.json && vue-tsc --noEmit -p tsconfig.check-vue.json",
"build:playground": "pnpm --filter \"runtime-vue3\" build && pnpm --filter \"tmagic-playground\" build", "build:playground": "pnpm --filter \"runtime-vue3\" build && pnpm --filter \"tmagic-playground\" build",
@ -46,40 +46,45 @@
"@commitlint/cli": "^18.6.1", "@commitlint/cli": "^18.6.1",
"@commitlint/config-conventional": "^18.6.3", "@commitlint/config-conventional": "^18.6.3",
"@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-alias": "^5.1.1",
"@tmagic/eslint-config": "workspace: ^*",
"@types/node": "18.19.61", "@types/node": "18.19.61",
"@vitejs/plugin-vue": "^5.2.3", "@typescript-eslint/eslint-plugin": "^5.62.0",
"@vitest/coverage-v8": "^2.1.9", "@typescript-eslint/parser": "^5.62.0",
"@vue/compiler-sfc": "^3.5.13", "@vitejs/plugin-vue": "^5.2.1",
"@vitest/coverage-v8": "^2.1.4",
"c8": "^7.14.0", "c8": "^7.14.0",
"commitizen": "^4.3.1", "commitizen": "^4.3.1",
"conventional-changelog-cli": "^4.1.0", "conventional-changelog-cli": "^4.1.0",
"cosmiconfig": "^8.3.6", "cosmiconfig": "^8.3.6",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"element-plus": "^2.9.7", "element-plus": "^2.9.0",
"enquirer": "^2.4.1", "enquirer": "^2.4.1",
"eslint": "^9.25.0", "eslint": "^8.57.1",
"eslint-config-tencent": "^1.1.1",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-vue": "^9.30.0",
"execa": "^4.1.0", "execa": "^4.1.0",
"highlight.js": "^11.11.1", "highlight.js": "^11.10.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"jsdom": "^19.0.0", "jsdom": "^19.0.0",
"lint-staged": "^11.2.6", "lint-staged": "^11.2.6",
"minimist": "^1.2.8", "minimist": "^1.2.8",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"prettier": "^3.5.3", "prettier": "^2.8.8",
"recast": "^0.23.11", "recast": "^0.20.5",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^4.38.0", "rollup": "^4.24.3",
"rollup-plugin-dts": "^6.2.1", "rollup-plugin-dts": "^6.1.1",
"semver": "^7.7.1", "semver": "^7.6.3",
"serialize-javascript": "^6.0.2", "serialize-javascript": "^6.0.2",
"shx": "^0.3.4", "shx": "^0.3.4",
"typescript": "^5.8.2", "typescript": "^5.6.3",
"vite": "^6.2.4", "vite": "^6.0.3",
"vitepress": "^1.6.3", "vitepress": "^1.5.0",
"vitest": "^3.1.1", "vitest": "^2.1.8",
"vue": "^3.5.13", "vue": "^3.5.12",
"vue-tsc": "^2.2.8" "vue-tsc": "^2.1.10"
}, },
"config": { "config": {
"commitizen": { "commitizen": {
@ -87,7 +92,7 @@
} }
}, },
"lint-staged": { "lint-staged": {
"*.{js,ts,vue}": "eslint --fix --cache", "*.{js,ts,vue}": "eslint --fix",
"*.scss": "prettier --write" "*.scss": "prettier --write"
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"version": "1.5.14", "version": "1.5.5",
"name": "@tmagic/cli", "name": "@tmagic/cli",
"main": "lib/index.js", "main": "lib/index.js",
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
@ -31,7 +31,7 @@
"esbuild": "^0.21.5", "esbuild": "^0.21.5",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"recast": "^0.23.11", "recast": "^0.23.9",
"tslib": "^2.8.0" "tslib": "^2.8.0"
}, },
"devDependencies": { "devDependencies": {

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import path from 'node:path'; import path from 'path';
import fs from 'fs-extra'; import fs from 'fs-extra';

View File

@ -54,14 +54,11 @@ export interface NpmConfig {
} }
export interface ModuleMainFilePath { export interface ModuleMainFilePath {
componentPackage: Record<string, string>;
componentMap: Record<string, string>; componentMap: Record<string, string>;
pluginPakcage: Record<string, string>;
pluginMap: Record<string, string>; pluginMap: Record<string, string>;
configMap: Record<string, string>; configMap: Record<string, string>;
valueMap: Record<string, string>; valueMap: Record<string, string>;
eventMap: Record<string, string>; eventMap: Record<string, string>;
datasourcePackage: Record<string, string>;
datasourceMap: Record<string, string>; datasourceMap: Record<string, string>;
dsConfigMap: Record<string, string>; dsConfigMap: Record<string, string>;
dsValueMap: Record<string, string>; dsValueMap: Record<string, string>;

View File

@ -1,5 +1,5 @@
import fs from 'node:fs'; import fs from 'fs';
import path from 'node:path'; import path from 'path';
export const backupFile = (runtimeSource: string, file: string) => { export const backupFile = (runtimeSource: string, file: string) => {
const filePath = path.join(runtimeSource, file); const filePath = path.join(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'); isPlainObject(mod) && !!mod.__esModule && Object.prototype.hasOwnProperty.call(mod, 'default');
export const loadUserConfigCjs: UserConfigLoader = async (userConfigPath) => { export const loadUserConfigCjs: UserConfigLoader = async (userConfigPath) => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const required = require(userConfigPath); const required = require(userConfigPath);
return hasExportDefault(required) ? required.default : required; return hasExportDefault(required) ? required.default : required;
}; };

View File

@ -5,84 +5,33 @@ import { EntryType } from '../types';
export const prepareEntryFile = async (app: App) => { export const prepareEntryFile = async (app: App) => {
const { moduleMainFilePath, options } = app; const { moduleMainFilePath, options } = app;
const { dynamicImport, hooks, useTs = true } = options; const { componentFileAffix, dynamicImport, hooks, useTs = true } = options;
let contentMap: Record<string, string> = { let contentMap: Record<string, string> = {
'comp-entry': generateContent( 'comp-entry': generateContent(useTs, EntryType.COMPONENT, moduleMainFilePath.componentMap, componentFileAffix),
useTs,
EntryType.COMPONENT,
moduleMainFilePath.componentPackage,
moduleMainFilePath.componentMap,
),
'async-comp-entry': generateContent( 'async-comp-entry': generateContent(
useTs, useTs,
EntryType.COMPONENT, EntryType.COMPONENT,
moduleMainFilePath.componentPackage,
moduleMainFilePath.componentMap, moduleMainFilePath.componentMap,
componentFileAffix,
dynamicImport, dynamicImport,
), ),
'plugin-entry': generateContent( 'plugin-entry': generateContent(useTs, EntryType.PLUGIN, moduleMainFilePath.pluginMap),
useTs, 'async-plugin-entry': generateContent(useTs, EntryType.PLUGIN, moduleMainFilePath.pluginMap, '', dynamicImport),
EntryType.PLUGIN, 'config-entry': generateContent(useTs, EntryType.CONFIG, moduleMainFilePath.configMap),
moduleMainFilePath.pluginPakcage, 'value-entry': generateContent(useTs, EntryType.VALUE, moduleMainFilePath.valueMap),
moduleMainFilePath.pluginMap, 'event-entry': generateContent(useTs, EntryType.EVENT, moduleMainFilePath.eventMap),
), 'datasource-entry': generateContent(useTs, EntryType.DATASOURCE, moduleMainFilePath.datasourceMap),
'async-plugin-entry': generateContent(
useTs,
EntryType.PLUGIN,
moduleMainFilePath.pluginPakcage,
moduleMainFilePath.pluginMap,
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,
),
'async-datasource-entry': generateContent( 'async-datasource-entry': generateContent(
useTs, useTs,
EntryType.DATASOURCE, EntryType.DATASOURCE,
moduleMainFilePath.datasourcePackage,
moduleMainFilePath.datasourceMap, moduleMainFilePath.datasourceMap,
'',
dynamicImport, dynamicImport,
), ),
'ds-config-entry': generateContent( 'ds-config-entry': generateContent(useTs, EntryType.DS_CONFIG, moduleMainFilePath.dsConfigMap),
useTs, 'ds-value-entry': generateContent(useTs, EntryType.DS_VALUE, moduleMainFilePath.dsValueMap),
EntryType.DS_CONFIG, 'ds-event-entry': generateContent(useTs, EntryType.DS_EVENT, moduleMainFilePath.dsEventMap),
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,
),
}; };
if (typeof hooks?.beforeWriteEntry === 'function') { if (typeof hooks?.beforeWriteEntry === 'function') {
@ -95,7 +44,7 @@ export const prepareEntryFile = async (app: App) => {
app.writeTemp(fileName, content); app.writeTemp(fileName, content);
} else { } else {
fileName = `${file}.js`; 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); app.writeTemp(fileName, content);
}); });
@ -104,8 +53,8 @@ export const prepareEntryFile = async (app: App) => {
export const generateContent = ( export const generateContent = (
useTs: boolean, useTs: boolean,
type: EntryType, type: EntryType,
packageMap: Record<string, string> = {},
map: Record<string, string> = {}, map: Record<string, string> = {},
componentFileAffix = '',
dynamicImport = false, dynamicImport = false,
) => { ) => {
const list: string[] = []; const list: string[] = [];
@ -113,14 +62,14 @@ export const generateContent = (
Object.entries(map).forEach(([key, packagePath]) => { Object.entries(map).forEach(([key, packagePath]) => {
const name = makeCamelCase(key); const name = makeCamelCase(key);
if (dynamicImport) {
if ([EntryType.CONFIG, EntryType.EVENT, EntryType.VALUE].includes(type) && packagePath === packageMap[key]) { list.push(
importDeclarations.push(`import { ${type} as ${name} } from '${packageMap[key]}'`); `'${key}': () => import('${packagePath}${packagePath.endsWith(componentFileAffix) ? '' : componentFileAffix}')`,
list.push(`'${key}': ${name}`); );
} else if (dynamicImport) {
list.push(`'${key}': () => import('${packagePath}')`);
} else { } else {
importDeclarations.push(`import ${name} from '${packagePath}'`); importDeclarations.push(
`import ${name} from '${packagePath}${packagePath.endsWith(componentFileAffix) ? '' : componentFileAffix}'`,
);
list.push(`'${key}': ${name}`); list.push(`'${key}': ${name}`);
} }
}); });
@ -136,7 +85,6 @@ export const generateContent = (
}; };
export const prettyCode = (code: string) => 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') }), { recast.prettyPrint(recast.parse(code.replace(/\\/g, '/'), { parser: require('recast/parsers/typescript') }), {
tabWidth: 2, tabWidth: 2,
trailingComma: true, 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 = {}) { const npmInstall = function (dependencies: Record<string, string>, cwd: string, npmConfig: NpmConfig = {}) {
try { try {
const { client = 'npm', registry, installArgs = '', keepPackageJsonClean } = npmConfig; const { client = 'npm', registry, installArgs = '' } = npmConfig;
const install = { const install = {
npm: 'install', npm: 'install',
yarn: 'add', yarn: 'add',
pnpm: 'add', pnpm: 'add',
}[client]; }[client];
let packages = Object.entries(dependencies); const packages = Object.entries(dependencies)
.map(([name, version]) => (version ? `${name}@${version}` : name))
const newPackages = Object.entries(dependencies).filter(([name]) => { .join(' ');
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 installArgsString = `${installArgs ? ` ${installArgs}` : ''}`; const installArgsString = `${installArgs ? ` ${installArgs}` : ''}`;
const registryString = `${registry ? ` --registry ${registry}` : ''}`; const registryString = `${registry ? ` --registry ${registry}` : ''}`;
const command = `${client} ${install}${installArgsString} ${packageNames}${registryString}`; const command = `${client} ${install}${installArgsString} ${packages}${registryString}`;
execInfo(cwd); execInfo(cwd);
execInfo(command); execInfo(command);
@ -127,7 +110,6 @@ const typeAssertion = function ({
if (isFile(defaultFile)) { if (isFile(defaultFile)) {
const defaultCode = fs.readFileSync(defaultFile, { encoding: 'utf-8', flag: 'r' }); 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') }); const ast = recast.parse(defaultCode, { parser: require('recast/parsers/typescript') });
if ( if (
isDatasource( isDatasource(
@ -336,30 +318,25 @@ const parseEntry = function ({ ast, package: module, indexPath }: ParseEntryOpti
const tokens = getASTTokenByTraverse({ ast, indexPath }); const tokens = getASTTokenByTraverse({ ast, indexPath });
let { config, value, event, component } = tokens; let { config, value, event, component } = tokens;
if (typeof config === 'undefined') { if (!config) {
info(`${module} 表单配置文件声明缺失`); info(`${module} 表单配置文件声明缺失`);
} }
if (typeof value === 'undefined') { if (!value) {
info(`${module} 初始化数据文件声明缺失`); info(`${module} 初始化数据文件声明缺失`);
} }
if (typeof event === 'undefined') { if (!event) {
info(`${module} 事件声明文件声明缺失`); info(`${module} 事件声明文件声明缺失`);
} }
if (!component) {
info(`${module} 组件或数据源文件声明不合法`);
exit(1);
}
const reg = /^.*[/\\]node_modules[/\\](.*)/; const reg = /^.*[/\\]node_modules[/\\](.*)/;
if (config) {
[, config] = config.match(reg) || [, config]; [, config] = config.match(reg) || [, config];
}
if (value) {
[, value] = value.match(reg) || [, value]; [, value] = value.match(reg) || [, value];
}
if (component) {
[, component] = component.match(reg) || [, component]; [, component] = component.match(reg) || [, component];
}
if (event) {
[, event] = event.match(reg) || [, event]; [, event] = event.match(reg) || [, event];
}
return { return {
config, config,
@ -370,10 +347,10 @@ const parseEntry = function ({ ast, package: module, indexPath }: ParseEntryOpti
}; };
const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string }) => { const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string }) => {
let config: string | undefined; let config = '';
let value: string | undefined; let value = '';
let event: string | undefined; let event = '';
let component: string | undefined; let component = '';
const importSpecifiersMap: { [key: string]: string } = {}; const importSpecifiersMap: { [key: string]: string } = {};
const exportSpecifiersMap: { [key: string]: string | undefined } = {}; const exportSpecifiersMap: { [key: string]: string | undefined } = {};
@ -419,11 +396,7 @@ const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string
visitExportDefaultDeclaration(p) { visitExportDefaultDeclaration(p) {
const { node } = p; const { node } = p;
const { declaration } = node as any; 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); this.traverse(p);
}, },
}); });
@ -432,10 +405,7 @@ const getASTTokenByTraverse = ({ ast, indexPath }: { ast: any; indexPath: string
const exportValue = exportSpecifiersMap[exportName]; const exportValue = exportSpecifiersMap[exportName];
const importValue = importSpecifiersMap[exportName]; const importValue = importSpecifiersMap[exportName];
const connectValue = exportValue ? importSpecifiersMap[exportValue] : ''; const connectValue = exportValue ? importSpecifiersMap[exportValue] : '';
const filePath = path.resolve(path.dirname(indexPath), connectValue || importValue || exportValue || '');
const fileName = connectValue || importValue || exportValue || '';
const filePath = fileName ? path.resolve(path.dirname(indexPath), fileName) : '';
if (exportName === EntryType.VALUE) { if (exportName === EntryType.VALUE) {
value = filePath; 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 setPackages = (packages: ModuleMainFilePath, app: App, packagePath: string, cwd: string, key?: string) => {
const { options } = app; const { options } = app;
const { temp, componentFileAffix = '', datasoucreSuperClass } = options; const { temp, componentFileAffix, datasoucreSuperClass } = options;
let { name: moduleName } = splitNameVersion(packagePath); let { name: moduleName } = splitNameVersion(packagePath);
@ -519,7 +489,6 @@ const setPackages = (packages: ModuleMainFilePath, app: App, packagePath: string
.replace('\n', ''); .replace('\n', '');
const indexCode = fs.readFileSync(indexPath, { encoding: 'utf-8', flag: 'r' }); 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 ast: Ast = recast.parse(indexCode, { parser: require('recast/parsers/typescript') });
const result = typeAssertion({ ast, indexPath, componentFileAffix, datasoucreSuperClass }); const result = typeAssertion({ ast, indexPath, componentFileAffix, datasoucreSuperClass });
@ -544,45 +513,15 @@ const setPackages = (packages: ModuleMainFilePath, app: App, packagePath: string
if (!key) return; if (!key) return;
if (result.type === PackageType.COMPONENT || !result.type) { if (result.type === PackageType.COMPONENT) {
packages.componentPackage[key] = moduleName;
// 组件 // 组件
const entry = parseEntry({ ast, package: moduleName, indexPath }); const entry = parseEntry({ ast, package: moduleName, indexPath });
if (entry.component) { if (entry.component) packages.componentMap[key] = getRelativePath(entry.component, temp);
const packagePath = getRelativePath(entry.component, temp); if (entry.config) packages.configMap[key] = getRelativePath(entry.config, temp);
packages.componentMap[key] = `${packagePath}${ if (entry.event) packages.eventMap[key] = getRelativePath(entry.event, temp);
packagePath.endsWith(componentFileAffix) ? '' : componentFileAffix if (entry.value) packages.valueMap[key] = getRelativePath(entry.value, temp);
}`;
} 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;
}
}
} else if (result.type === PackageType.DATASOURCE) { } else if (result.type === PackageType.DATASOURCE) {
packages.datasourcePackage[key] = moduleName;
// 数据源 // 数据源
const entry = parseEntry({ ast, package: moduleName, indexPath }); 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.event) packages.dsEventMap[key] = getRelativePath(entry.event, temp);
if (entry.value) packages.dsValueMap[key] = getRelativePath(entry.value, temp); if (entry.value) packages.dsValueMap[key] = getRelativePath(entry.value, temp);
} else if (result.type === PackageType.PLUGIN) { } else if (result.type === PackageType.PLUGIN) {
packages.pluginPakcage[key] = moduleName;
// 插件 // 插件
packages.pluginMap[key] = getRelativePath(moduleName, temp); packages.pluginMap[key] = getRelativePath(moduleName, temp);
} }
@ -635,14 +573,11 @@ export const resolveAppPackages = (app: App): ModuleMainFilePath => {
} }
const packagesMap: ModuleMainFilePath = { const packagesMap: ModuleMainFilePath = {
componentPackage: {},
componentMap: {}, componentMap: {},
configMap: {}, configMap: {},
eventMap: {}, eventMap: {},
valueMap: {}, valueMap: {},
pluginPakcage: {},
pluginMap: {}, pluginMap: {},
datasourcePackage: {},
datasourceMap: {}, datasourceMap: {},
dsConfigMap: {}, dsConfigMap: {},
dsEventMap: {}, dsEventMap: {},

View File

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

View File

@ -1,5 +1,5 @@
{ {
"version": "1.5.14", "version": "1.5.5",
"name": "@tmagic/core", "name": "@tmagic/core",
"type": "module", "type": "module",
"main": "dist/tmagic-core.umd.cjs", "main": "dist/tmagic-core.umd.cjs",
@ -24,6 +24,9 @@
"resetcss.css" "resetcss.css"
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": {
"build": "vite build"
},
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@ -32,6 +35,9 @@
"type": "git", "type": "git",
"url": "https://github.com/Tencent/tmagic-editor.git" "url": "https://github.com/Tencent/tmagic-editor.git"
}, },
"keywords": [
"vue"
],
"dependencies": { "dependencies": {
"@tmagic/data-source": "workspace:*", "@tmagic/data-source": "workspace:*",
"@tmagic/dep": "workspace:*", "@tmagic/dep": "workspace:*",
@ -42,7 +48,10 @@
}, },
"devDependencies": { "devDependencies": {
"@types/events": "^3.0.0", "@types/events": "^3.0.0",
"@types/lodash-es": "^4.17.4" "@types/lodash-es": "^4.17.4",
"@types/node": "^18.19.0",
"rimraf": "^3.0.2",
"vite": "^6.0.3"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"

View File

@ -33,7 +33,6 @@ import { transformStyle as defaultTransformStyle } from './utils';
export interface AppOptionsConfig { export interface AppOptionsConfig {
ua?: string; ua?: string;
env?: Env;
config?: MApp; config?: MApp;
platform?: 'editor' | 'mobile' | 'tv' | 'pc'; platform?: 'editor' | 'mobile' | 'tv' | 'pc';
jsEngine?: JsEngine; jsEngine?: JsEngine;
@ -50,13 +49,7 @@ export interface AppOptionsConfig {
class App extends EventEmitter { class App extends EventEmitter {
[x: string]: any; [x: string]: any;
static nodeClassMap = new Map<string, typeof Node>(); public env: Env = new Env();
public static registerNode<T extends typeof Node = typeof Node>(type: string, NodeClass: T) {
App.nodeClassMap.set(type, NodeClass);
}
public env!: Env;
public dsl?: MApp; public dsl?: MApp;
public codeDsl?: CodeBlockDSL; public codeDsl?: CodeBlockDSL;
public dataSourceManager?: DataSourceManager; public dataSourceManager?: DataSourceManager;
@ -76,12 +69,7 @@ class App extends EventEmitter {
constructor(options: AppOptionsConfig) { constructor(options: AppOptionsConfig) {
super(); super();
if (options.env) {
this.setEnv(options.env);
} else {
this.setEnv(options.ua); this.setEnv(options.ua);
}
// 代码块描述内容在dsl codeBlocks字段 // 代码块描述内容在dsl codeBlocks字段
this.codeDsl = options.config?.codeBlocks; this.codeDsl = options.config?.codeBlocks;
options.platform && (this.platform = options.platform); options.platform && (this.platform = options.platform);
@ -129,12 +117,8 @@ class App extends EventEmitter {
} }
} }
public setEnv<T extends Env>(ua?: string | T) { public setEnv(ua?: string) {
if (!ua || typeof ua === 'string') {
this.env = new Env(ua); this.env = new Env(ua);
} else {
this.env = ua;
}
} }
public setDesignWidth(width: number) { public setDesignWidth(width: number) {
@ -160,12 +144,7 @@ class App extends EventEmitter {
this.dataSourceManager = createDataSourceManager(this, this.useMock); this.dataSourceManager = createDataSourceManager(this, this.useMock);
this.codeDsl = config.codeBlocks; this.codeDsl = config.codeBlocks;
this.setPage(curPage || this.page?.data?.id);
const pageId = curPage || this.page?.data?.id;
super.emit('dsl-change', { dsl: config, curPage: pageId });
this.setPage(pageId);
if (this.dataSourceManager) { if (this.dataSourceManager) {
const dataSourceList = Array.from(this.dataSourceManager.dataSourceMap.values()); const dataSourceList = Array.from(this.dataSourceManager.dataSourceMap.values());
@ -183,22 +162,19 @@ class App extends EventEmitter {
return; return;
} }
if (this.page) { if (pageConfig === this.page?.data) return;
if (pageConfig === this.page.data) return;
this.page.destroy(); this.page?.destroy();
}
this.page = new Page({ this.page = new Page({
config: pageConfig, config: pageConfig,
app: this, app: this,
}); });
if (this.eventHelper) { this.eventHelper?.removeNodeEvents();
this.eventHelper.removeNodeEvents(); this.page.nodes.forEach((node) => {
for (const [, node] of this.page.nodes) { this.eventHelper?.bindNodeEvents(node);
this.eventHelper.bindNodeEvents(node); });
}
}
super.emit('page-change', this.page); super.emit('page-change', this.page);
} }

View File

@ -30,11 +30,7 @@ class Env {
isWeb = false; isWeb = false;
isOpenHarmony = false; isOpenHarmony = false;
constructor(ua = globalThis.navigator?.userAgent ?? '', options: Record<string, boolean | string> = {}) { constructor(ua = globalThis.navigator.userAgent, options: Record<string, boolean | string> = {}) {
if (!ua) {
return;
}
this.isIphone = ua.indexOf('iPhone') >= 0; this.isIphone = ua.indexOf('iPhone') >= 0;
this.isIpad = /(iPad).*OS\s([\d_]+)/.test(ua); this.isIpad = /(iPad).*OS\s([\d_]+)/.test(ua);

View File

@ -32,11 +32,38 @@ import {
type EventActionItem, type EventActionItem,
type EventConfig, type EventConfig,
} from '@tmagic/schema'; } 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 type { default as TMagicApp } from './App';
import FlowState from './FlowState'; import FlowState from './FlowState';
import type { default as TMagicNode } from './Node'; import type { default as TMagicNode } from './Node';
import { COMMON_EVENT_PREFIX, isCommonMethod, triggerCommonMethod } from './utils';
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 { export default class EventHelper extends EventEmitter {
public app: TMagicApp; public app: TMagicApp;
@ -48,11 +75,19 @@ export default class EventHelper extends EventEmitter {
super(); super();
this.app = app; this.app = app;
if (app.jsEngine === 'browser') {
globalThis.document.body.addEventListener('click', this.commonClickEventHandler);
}
} }
public destroy() { public destroy() {
this.removeNodeEvents(); this.removeNodeEvents();
this.removeAllListeners(); this.removeAllListeners();
if (this.app.jsEngine === 'browser') {
globalThis.document.body.removeEventListener('click', this.commonClickEventHandler);
}
} }
public bindNodeEvents(node: TMagicNode) { public bindNodeEvents(node: TMagicNode) {
@ -208,6 +243,10 @@ export default class EventHelper extends EventEmitter {
const toNode = this.app.getNode(to); const toNode = this.app.getNode(to);
if (!toNode) throw `ID为${to}的组件不存在`; if (!toNode) throw `ID为${to}的组件不存在`;
if (isCommonMethod(methodName)) {
return triggerCommonMethod(methodName, toNode);
}
if (toNode.instance) { if (toNode.instance) {
if (typeof toNode.instance[methodName] === 'function') { if (typeof toNode.instance[methodName] === 'function') {
await toNode.instance[methodName](fromCpt, ...args); await toNode.instance[methodName](fromCpt, ...args);
@ -220,4 +259,17 @@ export default class EventHelper extends EventEmitter {
}); });
} }
} }
private commonClickEventHandler = (e: MouseEvent) => {
if (!e.target) {
return;
}
const node = getDirectComponent(e.target as HTMLElement, this.app);
const eventName = `${getCommonEventName('click')}_${node?.data.id}`;
if (node?.eventKeys.has(eventName)) {
this.emit(node.eventKeys.get(eventName)!, node);
}
};
} }

View File

@ -32,10 +32,6 @@ interface EventCache {
args: any[]; args: any[];
} }
interface Methods {
[key: string]: (...args: any[]) => any;
}
export interface NodeOptions { export interface NodeOptions {
config: MNode; config: MNode;
page?: Page; page?: Page;
@ -49,7 +45,7 @@ class Node extends EventEmitter {
[key: string]: any; [key: string]: any;
}; };
public events: EventConfig[] = []; public events: EventConfig[] = [];
public instance?: any = {}; public instance?: any;
public page?: Page; public page?: Page;
public parent?: Node; public parent?: Node;
public app: TMagicApp; public app: TMagicApp;
@ -73,7 +69,6 @@ class Node extends EventEmitter {
const { events, style } = data; const { events, style } = data;
this.events = events || []; this.events = events || [];
this.style = style || {}; this.style = style || {};
this.instance.config = data;
this.emit('update-data', data); this.emit('update-data', data);
} }
@ -81,22 +76,6 @@ class Node extends EventEmitter {
this.eventQueue.push(event); this.eventQueue.push(event);
} }
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 async runHookCode(hook: string, params?: Record<string, any>) { public async runHookCode(hook: string, params?: Record<string, any>) {
if (typeof this.data[hook] === 'function') { if (typeof this.data[hook] === 'function') {
// 兼容旧的数据格式 // 兼容旧的数据格式
@ -123,9 +102,8 @@ class Node extends EventEmitter {
const { codeType = HookCodeType.CODE, codeId, params: itemParams = {} } = item; const { codeType = HookCodeType.CODE, codeId, params: itemParams = {} } = item;
let functionContent: ((...args: any[]) => any) | string | undefined; let functionContent: ((...args: any[]) => any) | string | undefined;
const functionParams: { app: TMagicApp; node: Node; params: Record<string, any>; dataSource?: DataSource } = { const functionParams: { app: TMagicApp; params: Record<string, any>; dataSource?: DataSource } = {
app: this.app, app: this.app,
node: this,
params: params || itemParams, params: params || itemParams,
}; };
@ -148,7 +126,7 @@ class Node extends EventEmitter {
} }
private listenLifeSafe() { private listenLifeSafe() {
this.once('created', (instance: any) => { this.once('created', async (instance: any) => {
this.once('destroy', () => { this.once('destroy', () => {
this.instance = null; this.instance = null;
if (typeof this.data.destroy === 'function') { if (typeof this.data.destroy === 'function') {
@ -158,24 +136,12 @@ class Node extends EventEmitter {
this.listenLifeSafe(); this.listenLifeSafe();
}); });
if (instance) { this.instance = instance;
this.registerMethod(instance); await this.runHookCode('created');
if (instance.config) {
this.setData(instance.config);
}
}
this.runHookCode('created');
}); });
this.once('mounted', (instance: any) => { this.once('mounted', async (instance: any) => {
const handler = async () => { this.instance = instance;
if (instance) {
this.registerMethod(instance);
if (instance.config) {
this.setData(instance.config);
}
}
for (let eventConfig = this.eventQueue.shift(); eventConfig; eventConfig = this.eventQueue.shift()) { for (let eventConfig = this.eventQueue.shift(); eventConfig; eventConfig = this.eventQueue.shift()) {
if (typeof instance[eventConfig.method] === 'function') { if (typeof instance[eventConfig.method] === 'function') {
@ -183,9 +149,7 @@ class Node extends EventEmitter {
} }
} }
this.runHookCode('mounted'); await this.runHookCode('mounted');
};
handler();
}); });
} }
} }

View File

@ -18,7 +18,7 @@
import type { Id, MComponent, MContainer, MPage, MPageFragment } from '@tmagic/schema'; import type { Id, MComponent, MContainer, MPage, MPageFragment } from '@tmagic/schema';
import App from './App'; import type App from './App';
import IteratorContainer from './IteratorContainer'; import IteratorContainer from './IteratorContainer';
import type { default as TMagicNode } from './Node'; import type { default as TMagicNode } from './Node';
import Node from './Node'; import Node from './Node';
@ -53,7 +53,7 @@ class Page extends Node {
return; return;
} }
const node = new ((config.type && App.nodeClassMap.get(config.type)) || Node)({ const node = new Node({
config, config,
parent, parent,
page: this, page: this,

View File

@ -18,6 +18,8 @@
import { JsEngine } from '@tmagic/schema'; import { JsEngine } from '@tmagic/schema';
import { isNumber } from '@tmagic/utils'; import { isNumber } from '@tmagic/utils';
import type { default as TMagicNode } from './Node';
export const style2Obj = (style: string) => { export const style2Obj = (style: string) => {
if (typeof style !== 'string') { if (typeof style !== 'string') {
return style; 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_EVENT_PREFIX = 'magic:common:events:';
export const COMMON_METHOD_PREFIX = 'magic:common:actions:'; 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 { export interface EventOption {
label: string; label: string;
value: string; value: string;
} }
export const DEFAULT_EVENTS: EventOption[] = [{ label: '点击', value: `${COMMON_EVENT_PREFIX}click` }];
export const DEFAULT_METHODS: EventOption[] = [];

View File

@ -1,9 +1,8 @@
import { describe, expect, test } from 'vitest'; import { describe, expect, test } from 'vitest';
import { MApp, NodeType } from '@tmagic/schema'; import { MApp, NodeType, TMagicIteratorContainer } from '@tmagic/schema';
import App from '../src/App'; import App from '../src/App';
import TMagicIteratorContainer from '../src/IteratorContainer';
const createAppDsl = (pageLength: number, nodeLength = 0) => { const createAppDsl = (pageLength: number, nodeLength = 0) => {
const dsl: MApp = { const dsl: MApp = {

View File

@ -0,0 +1,46 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from 'vite';
import pkg from './package.json';
export default defineConfig({
build: {
cssCodeSplit: false,
sourcemap: false,
minify: false,
target: 'esnext',
lib: {
entry: 'src/index.ts',
name: 'TMagicCore',
fileName: 'tmagic-core',
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external(id: string) {
return Object.keys({
...pkg.dependencies,
...pkg.peerDependencies,
}).some((k) => new RegExp(`^${k}`).test(id));
},
},
},
});

View File

@ -1,5 +1,5 @@
{ {
"version": "1.5.14", "version": "1.5.5",
"name": "@tmagic/data-source", "name": "@tmagic/data-source",
"type": "module", "type": "module",
"main": "dist/tmagic-data-source.umd.cjs", "main": "dist/tmagic-data-source.umd.cjs",
@ -19,6 +19,9 @@
"src" "src"
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": {
"build": "vite build --mode=es && vite build --mode=umd"
},
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@ -46,6 +49,9 @@
}, },
"devDependencies": { "devDependencies": {
"@types/events": "^3.0.0", "@types/events": "^3.0.0",
"@types/lodash-es": "^4.17.4" "@types/lodash-es": "^4.17.4",
"@types/node": "^18.19.0",
"tsc-alias": "^1.8.5",
"vite": "^6.0.3"
} }
} }

View File

@ -1,4 +1,3 @@
/* eslint-disable @typescript-eslint/naming-convention */
/* /*
* Tencent is pleased to support the open source community by making TMagicEditor available. * Tencent is pleased to support the open source community by making TMagicEditor available.
* *
@ -33,16 +32,9 @@ import { compiledNodeField, compliedConditions, compliedIteratorItem, createIter
class DataSourceManager extends EventEmitter { class DataSourceManager extends EventEmitter {
private static dataSourceClassMap = new Map<string, typeof DataSource>(); private static dataSourceClassMap = new Map<string, typeof DataSource>();
private static ObservedDataClass: ObservedDataClass = SimpleObservedData; private static ObservedDataClass: ObservedDataClass = SimpleObservedData;
private static waitInitSchemaList = new Map<DataSourceManager, Record<string, DataSourceSchema[]>>();
public static register<T extends typeof DataSource = typeof DataSource>(type: string, dataSource: T) { public static register<T extends typeof DataSource = typeof DataSource>(type: string, dataSource: T) {
DataSourceManager.dataSourceClassMap.set(type, dataSource); DataSourceManager.dataSourceClassMap.set(type, dataSource);
DataSourceManager.waitInitSchemaList?.forEach((listMap, app) => {
const list = listMap[type] || [];
for (let config = list.shift(); config; config = list.shift()) {
app.addDataSource(config);
}
});
} }
public static getDataSourceClass(type: string) { public static getDataSourceClass(type: string) {
@ -63,8 +55,6 @@ class DataSourceManager extends EventEmitter {
constructor({ app, useMock, initialData }: DataSourceManagerOptions) { constructor({ app, useMock, initialData }: DataSourceManagerOptions) {
super(); super();
DataSourceManager.waitInitSchemaList.set(this, {});
this.app = app; this.app = app;
this.useMock = useMock; this.useMock = useMock;
@ -143,24 +133,7 @@ class DataSourceManager extends EventEmitter {
public async addDataSource(config?: DataSourceSchema) { public async addDataSource(config?: DataSourceSchema) {
if (!config) return; if (!config) return;
const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type); const DataSourceClass = DataSourceManager.dataSourceClassMap.get(config.type) || DataSource;
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];
}
return;
}
const ds = new DataSourceClass({ const ds = new DataSourceClass({
app: this.app, app: this.app,
@ -204,7 +177,7 @@ class DataSourceManager extends EventEmitter {
this.removeDataSource(schema.id); this.removeDataSource(schema.id);
this.addDataSource(cloneDeep(schema)); this.addDataSource(schema);
const newDs = this.get(schema.id); const newDs = this.get(schema.id);
if (newDs) { if (newDs) {
this.init(newDs); this.init(newDs);
@ -303,7 +276,6 @@ class DataSourceManager extends EventEmitter {
ds.destroy(); ds.destroy();
}); });
this.dataSourceMap.clear(); this.dataSourceMap.clear();
DataSourceManager.waitInitSchemaList.delete(this);
} }
public onDataChange(id: string, path: string, callback: (newVal: any) => void) { public onDataChange(id: string, path: string, callback: (newVal: any) => void) {
@ -316,6 +288,5 @@ class DataSourceManager extends EventEmitter {
} }
DataSourceManager.register('http', HttpDataSource as any); DataSourceManager.register('http', HttpDataSource as any);
DataSourceManager.register('base', DataSource as any);
export default DataSourceManager; export default DataSourceManager;

View File

@ -62,15 +62,13 @@ export default class DataSource<T extends DataSourceSchema = DataSourceSchema> e
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
const ObservedDataClass = options.ObservedDataClass || SimpleObservedData; const ObservedDataClass = options.ObservedDataClass || SimpleObservedData;
if (this.app.platform === 'editor') { if (this.app.platform === 'editor') {
const mocks = cloneDeep(options.schema.mocks || []);
// 编辑器中有mock使用mock没有使用默认值 // 编辑器中有mock使用mock没有使用默认值
this.mockData = mocks.find((mock) => mock.useInEditor)?.data || this.getDefaultData(); this.mockData = options.schema.mocks?.find((mock) => mock.useInEditor)?.data || this.getDefaultData();
data = cloneDeep(this.mockData); data = cloneDeep(this.mockData);
} else if (typeof options.useMock === 'boolean' && options.useMock) { } else if (typeof options.useMock === 'boolean' && options.useMock) {
const mocks = cloneDeep(options.schema.mocks || []);
// 设置了使用mock就使用mock数据 // 设置了使用mock就使用mock数据
this.mockData = mocks.find((mock) => mock.enable)?.data; this.mockData = options.schema.mocks?.find((mock) => mock.enable)?.data;
data = cloneDeep(this.mockData) || this.getDefaultData(); data = this.mockData || this.getDefaultData();
} else if (!options.initialData) { } else if (!options.initialData) {
data = this.getDefaultData(); data = this.getDefaultData();
} else { } else {

View File

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

View File

@ -0,0 +1,55 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import path from 'path';
import { defineConfig } from 'vite';
import pkg from './package.json';
export default defineConfig(({ mode }) => ({
resolve: {
alias: [{ find: /^@data-source/, replacement: path.join(__dirname, './src') }],
},
build: {
cssCodeSplit: false,
sourcemap: false,
minify: false,
target: 'esnext',
lib: {
entry: 'src/index.ts',
name: 'TMagicDataSource',
fileName: 'tmagic-data-source',
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external(id: string) {
if (mode === 'umd' && id === 'lodash-es') {
return false;
}
return Object.keys({
...pkg.dependencies,
...pkg.peerDependencies,
}).some((k) => new RegExp(`^${k}`).test(id));
},
},
},
}));

View File

@ -1,5 +1,5 @@
{ {
"version": "1.5.14", "version": "1.5.5",
"name": "@tmagic/dep", "name": "@tmagic/dep",
"type": "module", "type": "module",
"main": "dist/tmagic-dep.umd.cjs", "main": "dist/tmagic-dep.umd.cjs",
@ -19,6 +19,9 @@
"src" "src"
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": {
"build": "vite build"
},
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@ -27,6 +30,11 @@
"type": "git", "type": "git",
"url": "https://github.com/Tencent/tmagic-editor.git" "url": "https://github.com/Tencent/tmagic-editor.git"
}, },
"devDependencies": {
"@types/node": "^18.19.0",
"rimraf": "^3.0.2",
"vite": "^6.0.3"
},
"peerDependencies": { "peerDependencies": {
"@tmagic/schema": "workspace:*", "@tmagic/schema": "workspace:*",
"@tmagic/utils": "workspace:*", "@tmagic/utils": "workspace:*",

View File

@ -222,6 +222,7 @@ export default class Watcher {
for (const [key, value] of Object.entries(config)) { for (const [key, value] of Object.entries(config)) {
if (typeof value === 'undefined' || value === '') continue; if (typeof value === 'undefined' || value === '') continue;
if (key === 'id' || key === 'name') continue;
doCollect(key, value); doCollect(key, value);
} }
}; };

View File

@ -263,32 +263,20 @@ export const createDataSourceCondTarget = (ds: Pick<DataSourceSchema, 'id' | 'fi
isTarget: (key: string | number, value: any) => isDataSourceCondTarget(ds, key, value), isTarget: (key: string | number, value: any) => isDataSourceCondTarget(ds, key, value),
}); });
export const createDataSourceMethodTarget = ( export const createDataSourceMethodTarget = (ds: Pick<DataSourceSchema, 'id' | 'fields'>, initialDeps: DepData = {}) =>
ds: Pick<DataSourceSchema, 'id' | 'methods' | 'fields'>,
initialDeps: DepData = {},
) =>
new Target({ new Target({
type: DepTargetType.DATA_SOURCE_METHOD, type: DepTargetType.DATA_SOURCE_METHOD,
id: ds.id, id: ds.id,
initialDeps, initialDeps,
isTarget: (_key: string | number, value: any) => { isTarget: (_key: string | number, value: any) => {
// 使用data-source-method-select 可以配置出来 // 使用data-source-method-select 可以配置出来
if (!Array.isArray(value)) { if (!Array.isArray(value) || !ds) {
return false; return false;
} }
const [dsId, methodName] = value; const [dsId, ...keys] = value;
if (!methodName || dsId !== ds.id) { if (dsId !== ds.id || ds.fields?.find((field) => field.name === keys[0])) {
return false;
}
if (ds.methods?.find((method) => method.name === methodName)) {
return true;
}
// 配置的方法名称可能会是数据源类中定义的并不存在于methods中所以这里判断如果methodName如果是字段名称就表示配置的不是方法
if (ds.fields?.find((field) => field.name === methodName)) {
return false; return false;
} }

View File

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

View File

@ -0,0 +1,41 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from 'vite';
import pkg from './package.json';
export default defineConfig({
build: {
sourcemap: false,
minify: false,
target: 'esnext',
lib: {
entry: 'src/index.ts',
name: 'TMagicDep',
fileName: 'tmagic-dep',
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external(id: string) {
return Object.keys(pkg.peerDependencies).some((k) => new RegExp(`^${k}`).test(id));
},
},
},
});

View File

@ -1,5 +1,5 @@
{ {
"version": "1.5.14", "version": "1.5.5",
"name": "@tmagic/design", "name": "@tmagic/design",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [
@ -23,6 +23,9 @@
"src" "src"
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": {
"build": "vite build"
},
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@ -41,6 +44,13 @@
"dependencies": { "dependencies": {
"@popperjs/core": "^2.11.8" "@popperjs/core": "^2.11.8"
}, },
"devDependencies": {
"@types/node": "^18.19.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/compiler-sfc": "^3.5.12",
"rimraf": "^3.0.2",
"vite": "^6.0.3"
},
"peerDependencies": { "peerDependencies": {
"vue": ">=3.5.0", "vue": ">=3.5.0",
"typescript": "*" "typescript": "*"

View File

@ -42,7 +42,7 @@ const ui = getDesignConfig('components')?.autocomplete;
const uiComponent = ui?.component || 'el-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']); const emit = defineEmits(['change', 'select', 'update:modelValue']);

View File

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

View File

@ -22,7 +22,7 @@ const ui = getDesignConfig('components')?.button;
const uiComponent = ui?.component || 'el-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']); const emit = defineEmits(['click']);

View File

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

View File

@ -25,7 +25,7 @@ const ui = getDesignConfig('components')?.cascader;
const uiComponent = ui?.component || 'el-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>(); const cascader = ref<any>();

View File

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

View File

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

View File

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

View File

@ -32,7 +32,7 @@ const ui = getDesignConfig('components')?.dialog;
const uiComponent = ui?.component || 'el-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[]) => { const closeHandler = (...args: any[]) => {
emit('close', ...args); emit('close', ...args);

View File

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

View File

@ -40,7 +40,7 @@ const ui = getDesignConfig('components')?.drawer;
const uiComponent = ui?.component || 'el-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>(); const drawer = ref<any>();

View File

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

View File

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

View File

@ -26,7 +26,7 @@ const ui = getDesignConfig('components')?.form;
const uiComponent = ui?.component || 'el-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>(); const form = ref<any>();

View File

@ -23,5 +23,5 @@ const ui = getDesignConfig('components')?.formItem;
const uiComponent = ui?.component || 'el-form-item'; const uiComponent = ui?.component || 'el-form-item';
const uiProps = computed<FormItemProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
</script> </script>

View File

@ -17,5 +17,5 @@ defineOptions({
const ui = getDesignConfig('components')?.icon; const ui = getDesignConfig('components')?.icon;
const uiComponent = ui?.component || 'el-icon'; const uiComponent = ui?.component || 'el-icon';
const props = defineProps<IconProps>(); const props = defineProps<IconProps>();
const uiProps = computed<IconProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
</script> </script>

View File

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

View File

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

View File

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

View File

@ -20,7 +20,7 @@ const ui = getDesignConfig('components')?.optionGroup;
const uiComponent = ui?.component || 'el-option-group'; const uiComponent = ui?.component || 'el-option-group';
const uiProps = computed<OptionGroupProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
const optionGroup = ref<any>(); const optionGroup = ref<any>();
</script> </script>

View File

@ -27,7 +27,7 @@ const ui = getDesignConfig('components')?.pagination;
const uiComponent = ui?.component || 'el-pagination'; const uiComponent = ui?.component || 'el-pagination';
const uiProps = computed<PaginationProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
const handleSizeChange = (...args: any[]) => { const handleSizeChange = (...args: any[]) => {
emit('size-change', ...args); emit('size-change', ...args);

View File

@ -27,9 +27,9 @@ import type { PopoverProps } from './types';
defineSlots<{ defineSlots<{
/** 触发 Popover 显示的 HTML 元素 */ /** 触发 Popover 显示的 HTML 元素 */
reference(_props: {}): any; reference(props: {}): any;
/** Popover 内嵌 HTML 文本 */ /** Popover 内嵌 HTML 文本 */
default(_props: {}): any; default(props: {}): any;
}>(); }>();
defineOptions({ defineOptions({
@ -130,7 +130,7 @@ const mouseenterHandler = () => {
popoverVisible.value = true; popoverVisible.value = true;
}; };
let timer: ReturnType<typeof setTimeout> | null = null; let timer: NodeJS.Timeout | null = null;
const mouseleaveHandler = () => { const mouseleaveHandler = () => {
if (props.disabled) return; if (props.disabled) return;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,5 +26,5 @@ const ui = getDesignConfig('components')?.tabPane;
const uiComponent = ui?.component || 'el-tab-pane'; const uiComponent = ui?.component || 'el-tab-pane';
const uiProps = computed<TabPaneProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
</script> </script>

View File

@ -33,7 +33,7 @@ const ui = getDesignConfig('components')?.table;
const uiComponent = ui?.component || 'el-table'; const uiComponent = ui?.component || 'el-table';
const uiProps = computed<TableProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['select', 'sort-change', 'expand-change', 'cell-click']); const emit = defineEmits(['select', 'sort-change', 'expand-change', 'cell-click']);

View File

@ -23,5 +23,5 @@ const ui = getDesignConfig('components')?.tableColumn;
const uiComponent = ui?.component || 'el-table-column'; const uiComponent = ui?.component || 'el-table-column';
const uiProps = computed<TableColumnProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
</script> </script>

View File

@ -30,7 +30,7 @@ const ui = getDesignConfig('components')?.tabs;
const uiComponent = ui?.component || 'el-tabs'; const uiComponent = ui?.component || 'el-tabs';
const uiProps = computed<TabsProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits(['tab-click', 'tab-add', 'tab-remove', 'update:model-value']); const emit = defineEmits(['tab-click', 'tab-add', 'tab-remove', 'update:model-value']);

View File

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

View File

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

View File

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

View File

@ -35,7 +35,7 @@ const ui = getDesignConfig('components')?.tree;
const uiComponent = ui?.component || 'el-tree'; const uiComponent = ui?.component || 'el-tree';
const uiProps = computed<TreeProps>(() => ui?.props(props) || props); const uiProps = computed(() => ui?.props(props) || props);
const emit = defineEmits([ const emit = defineEmits([
'node-click', 'node-click',

View File

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

View File

@ -9,6 +9,7 @@ import './theme/index.scss';
export * from './types'; export * from './types';
export * from './config'; export * from './config';
/* eslint-disable @typescript-eslint/no-unused-vars */
export { default as TMagicAutocomplete } from './Autocomplete.vue'; export { default as TMagicAutocomplete } from './Autocomplete.vue';
export { default as TMagicBadge } from './Badge.vue'; export { default as TMagicBadge } from './Badge.vue';
export { default as TMagicButton } from './Button.vue'; export { default as TMagicButton } from './Button.vue';

View File

@ -0,0 +1,54 @@
/*
* Tencent is pleased to support the open source community by making TMagicEditor available.
*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import pkg from './package.json';
export default defineConfig({
plugins: [vue()],
build: {
cssCodeSplit: false,
sourcemap: false,
minify: false,
target: 'esnext',
lib: {
entry: 'src/index.ts',
name: 'TMagicDesign',
fileName: 'tmagic-design',
cssFileName: 'style',
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external(id: string) {
return Object.keys(pkg.peerDependencies).some((k) => new RegExp(`^${k}`).test(id));
},
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: 'Vue',
},
},
},
},
});

View File

@ -1,5 +1,5 @@
{ {
"version": "1.5.14", "version": "1.5.5",
"name": "@tmagic/editor", "name": "@tmagic/editor",
"type": "module", "type": "module",
"sideEffects": [ "sideEffects": [
@ -28,6 +28,9 @@
"src" "src"
], ],
"license": "Apache-2.0", "license": "Apache-2.0",
"scripts": {
"build": "vite build"
},
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@ -67,10 +70,17 @@
"devDependencies": { "devDependencies": {
"@types/events": "^3.0.0", "@types/events": "^3.0.0",
"@types/lodash-es": "^4.17.4", "@types/lodash-es": "^4.17.4",
"@types/node": "^18.19.0",
"@types/serialize-javascript": "^5.0.1", "@types/serialize-javascript": "^5.0.1",
"@types/sortablejs": "^1.15.8", "@types/sortablejs": "^1.15.8",
"@vitejs/plugin-vue": "^5.2.1",
"@vue/compiler-sfc": "^3.5.12",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"type-fest": "^4.10.3" "rimraf": "^3.0.2",
"sass": "^1.83.0",
"tsc-alias": "^1.8.5",
"type-fest": "^4.10.3",
"vite": "^6.0.3"
}, },
"peerDependencies": { "peerDependencies": {
"@tmagic/core": "workspace:*", "@tmagic/core": "workspace:*",

View File

@ -142,7 +142,7 @@ import codeBlockService from './services/codeBlock';
import componentListService from './services/componentList'; import componentListService from './services/componentList';
import dataSourceService from './services/dataSource'; import dataSourceService from './services/dataSource';
import depService from './services/dep'; import depService from './services/dep';
import editorService from './services/editor'; import editorService, { type EditorService } from './services/editor';
import eventsService from './services/events'; import eventsService from './services/events';
import historyService from './services/history'; import historyService from './services/history';
import keybindingService from './services/keybinding'; import keybindingService from './services/keybinding';
@ -153,9 +153,25 @@ import uiService from './services/ui';
import keybindingConfig from './utils/keybinding-config'; import keybindingConfig from './utils/keybinding-config';
import { defaultEditorProps, EditorProps } from './editorProps'; import { defaultEditorProps, EditorProps } from './editorProps';
import { initServiceEvents, initServiceState } from './initService'; import { initServiceEvents, initServiceState } from './initService';
import type { EditorSlots, EventBus, Services, StageOptions } from './type'; import type {
EventBus,
FrameworkSlots,
PropsPanelSlots,
Services,
SidebarSlots,
StageOptions,
WorkspaceSlots,
} from './type';
defineSlots<EditorSlots>(); defineSlots<
FrameworkSlots &
WorkspaceSlots &
SidebarSlots &
PropsPanelSlots & {
workspace(props: { editorService: EditorService }): any;
'workspace-content'(props: { editorService: EditorService }): any;
}
>();
defineOptions({ defineOptions({
name: 'MEditor', name: 'MEditor',

View File

@ -59,7 +59,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, nextTick, Ref, ref, useTemplateRef, watch } from 'vue'; import { computed, inject, Ref, ref, useTemplateRef } from 'vue';
import type { CodeBlockContent } from '@tmagic/core'; import type { CodeBlockContent } from '@tmagic/core';
import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design'; import { TMagicButton, TMagicDialog, tMagicMessage, tMagicMessageBox, TMagicTag } from '@tmagic/design';
@ -74,9 +74,9 @@ import {
import FloatingBox from '@editor/components/FloatingBox.vue'; import FloatingBox from '@editor/components/FloatingBox.vue';
import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height'; import { useEditorContentHeight } from '@editor/hooks/use-editor-content-height';
import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-position'; import { useNextFloatBoxPosition } from '@editor/hooks/use-next-float-box-position';
import { useServices } from '@editor/hooks/use-services';
import { useWindowRect } from '@editor/hooks/use-window-rect'; import { useWindowRect } from '@editor/hooks/use-window-rect';
import CodeEditor from '@editor/layouts/CodeEditor.vue'; import CodeEditor from '@editor/layouts/CodeEditor.vue';
import type { Services } from '@editor/type';
import { getEditorConfig } from '@editor/utils/config'; import { getEditorConfig } from '@editor/utils/config';
defineOptions({ defineOptions({
@ -95,11 +95,9 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
submit: [values: CodeBlockContent, eventData: ContainerChangeEventData]; submit: [values: CodeBlockContent, eventData: ContainerChangeEventData];
close: [];
open: [];
}>(); }>();
const { codeBlockService, uiService } = useServices(); const services = inject<Services>('services');
const { height: codeBlockEditorHeight } = useEditorContentHeight(); const { height: codeBlockEditorHeight } = useEditorContentHeight();
@ -194,7 +192,7 @@ const functionConfig = computed<FormConfig>(() => [
label: '描述', label: '描述',
name: 'extra', name: 'extra',
}, },
codeBlockService.getParamsColConfig() || defaultParamColConfig, services?.codeBlockService.getParamsColConfig() || defaultParamColConfig,
], ],
}, },
{ {
@ -233,7 +231,7 @@ const changeHandler = (values: CodeBlockContent) => {
changedValue.value = values; changedValue.value = values;
}; };
const beforeClose = (done: (_cancel?: boolean) => void) => { const beforeClose = (done: (cancel?: boolean) => void) => {
if (!changedValue.value) { if (!changedValue.value) {
done(); done();
return; return;
@ -261,17 +259,7 @@ const closedHandler = () => {
}; };
const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null)); const parentFloating = inject<Ref<HTMLDivElement | null>>('parentFloating', ref(null));
const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(uiService, parentFloating); const { boxPosition, calcBoxPosition } = useNextFloatBoxPosition(services?.uiService, parentFloating);
watch(boxVisible, (visible) => {
nextTick(() => {
if (!visible) {
emit('close');
} else {
emit('open');
}
});
});
defineExpose({ defineExpose({
async show() { async show() {

View File

@ -37,7 +37,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, nextTick, onBeforeUnmount, onMounted, type Ref, ref, useTemplateRef } from 'vue'; import { computed, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef } from 'vue';
import { useZIndex } from '@tmagic/design'; import { useZIndex } from '@tmagic/design';
@ -73,7 +73,7 @@ const menuEl = useTemplateRef<HTMLDivElement>('menu');
const buttonRefs = useTemplateRef<InstanceType<typeof ToolButton>[]>('buttons'); const buttonRefs = useTemplateRef<InstanceType<typeof ToolButton>[]>('buttons');
const subMenuRef = useTemplateRef<any>('subMenu'); const subMenuRef = useTemplateRef<any>('subMenu');
const visible = ref(false); const visible = ref(false);
const subMenuData: Ref<(MenuButton | MenuComponent)[]> = ref<(MenuButton | MenuComponent)[]>([]); const subMenuData = ref<(MenuButton | MenuComponent)[]>([]);
const zIndex = useZIndex(); const zIndex = useZIndex();
const curZIndex = ref<number>(0); const curZIndex = ref<number>(0);

View File

@ -17,14 +17,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue'; import { computed, inject, nextTick, onBeforeUnmount, provide, ref, useTemplateRef, watch } from 'vue';
import { Close } from '@element-plus/icons-vue'; import { Close } from '@element-plus/icons-vue';
import VanillaMoveable from 'moveable'; import VanillaMoveable from 'moveable';
import { TMagicButton, useZIndex } from '@tmagic/design'; import { TMagicButton, useZIndex } from '@tmagic/design';
import MIcon from '@editor/components/Icon.vue'; import MIcon from '@editor/components/Icon.vue';
import { useServices } from '@editor/hooks/use-services'; import type { Services } from '@editor/type';
interface Position { interface Position {
left: number; left: number;
@ -39,7 +39,7 @@ const props = withDefaults(
defineProps<{ defineProps<{
position?: Position; position?: Position;
title?: string; title?: string;
beforeClose?: (_done: (_cancel?: boolean) => void) => void; beforeClose?: (done: (cancel?: boolean) => void) => void;
}>(), }>(),
{ {
title: '', title: '',
@ -66,8 +66,8 @@ const bodyHeight = computed(() => {
return 'auto'; return 'auto';
}); });
const { uiService } = useServices(); const services = inject<Services>('services');
const frameworkWidth = computed(() => uiService.get('frameworkRect').width || 0); const frameworkWidth = computed(() => services?.uiService.get('frameworkRect').width || 0);
const style = computed(() => { const style = computed(() => {
let { left } = props.position; let { left } = props.position;
if (width.value) { if (width.value) {

View File

@ -93,9 +93,7 @@ const scrollBy = (delta: number) => {
position: absolute; position: absolute;
background-color: transparent; background-color: transparent;
opacity: 0.3; opacity: 0.3;
transition: transition: background-color 0.2s linear, opacity 0.2s linear;
background-color 0.2s linear,
opacity 0.2s linear;
.m-editor-scroll-bar-thumb { .m-editor-scroll-bar-thumb {
background-color: #aaa; background-color: #aaa;
@ -110,9 +108,7 @@ const scrollBy = (delta: number) => {
.m-editor-scroll-bar-thumb { .m-editor-scroll-bar-thumb {
height: 6px; height: 6px;
transition: transition: background-color 0.2s linear, height 0.2s ease-in-out;
background-color 0.2s linear,
height 0.2s ease-in-out;
bottom: 2px; bottom: 2px;
} }
} }
@ -124,9 +120,7 @@ const scrollBy = (delta: number) => {
.m-editor-scroll-bar-thumb { .m-editor-scroll-bar-thumb {
width: 6px; width: 6px;
transition: transition: background-color 0.2s linear, width 0.2s ease-in-out;
background-color 0.2s linear,
width 0.2s ease-in-out;
right: 2px; right: 2px;
} }
} }

View File

@ -27,7 +27,7 @@ const emit = defineEmits(['search']);
const filterText = ref(''); const filterText = ref('');
let timer: ReturnType<typeof setTimeout> | null = null; let timer: NodeJS.Timeout | null = null;
const filterTextChangeHandler = () => { const filterTextChangeHandler = () => {
timer && clearTimeout(timer); timer && clearTimeout(timer);
timer = setTimeout(() => { timer = setTimeout(() => {

View File

@ -45,7 +45,7 @@ const props = withDefaults(
centerClass?: string; centerClass?: string;
}>(), }>(),
{ {
minLeft: 1, minLeft: 46,
minRight: 1, minRight: 1,
minCenter: 5, minCenter: 5,
}, },
@ -65,21 +65,12 @@ const getCenterWidth = (l = 0, r = 0) => {
let center = clientWidth - left - right; let center = clientWidth - left - right;
if (center < props.minCenter) { if (center < props.minCenter) {
const diff = props.minCenter - center;
center = props.minCenter; center = props.minCenter;
if (right > center + props.minRight) {
if (right - diff < props.minRight) { right = clientWidth - left - center;
right = props.minRight;
} else { } else {
right -= diff; right = props.minRight;
}
left = clientWidth - right - center; left = clientWidth - right - center;
if (left < props.minLeft) {
left -= diff / 2;
right -= diff / 2;
} }
} }
return { return {
@ -95,8 +86,8 @@ const widthChange = (width: number) => {
} }
clientWidth = width; clientWidth = width;
let left = props.left || props.minLeft || 0; let left = props.left || 0;
let right = props.right || props.minRight || 0; let right = props.right || 0;
if (left > clientWidth) { if (left > clientWidth) {
left = clientWidth / 3; left = clientWidth / 3;

View File

@ -49,7 +49,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed, inject } from 'vue';
import { ArrowDown } from '@element-plus/icons-vue'; import { ArrowDown } from '@element-plus/icons-vue';
import { import {
@ -62,10 +62,8 @@ import {
TMagicTooltip, TMagicTooltip,
} from '@tmagic/design'; } from '@tmagic/design';
import { useServices } from '@editor/hooks/use-services';
import MIcon from '../components/Icon.vue'; import MIcon from '../components/Icon.vue';
import type { MenuButton, MenuComponent } from '../type'; import type { MenuButton, MenuComponent, Services } from '../type';
defineOptions({ defineOptions({
name: 'MEditorToolButton', name: 'MEditorToolButton',
@ -84,7 +82,7 @@ const props = withDefaults(
eventType: 'click', eventType: 'click',
}, },
); );
const services = useServices(); const services = inject<Services>('services');
const disabled = computed(() => { const disabled = computed(() => {
if (typeof props.data === 'string') return false; if (typeof props.data === 'string') return false;
@ -106,7 +104,7 @@ const display = computed(() => {
const buttonHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => { const buttonHandler = (item: MenuButton | MenuComponent, event: MouseEvent) => {
if (disabled.value) return; if (disabled.value) return;
if (typeof (item as MenuButton).handler === 'function') { if (typeof (item as MenuButton).handler === 'function' && services) {
(item as MenuButton).handler?.(services, event); (item as MenuButton).handler?.(services, event);
} }
}; };

View File

@ -38,9 +38,9 @@ import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
import TreeNode from './TreeNode.vue'; import TreeNode from './TreeNode.vue';
defineSlots<{ defineSlots<{
'tree-node-content'(_props: { data: TreeNodeData }): any; 'tree-node-content'(props: { data: TreeNodeData }): any;
'tree-node-label'(_props: { data: TreeNodeData }): any; 'tree-node-label'(props: { data: TreeNodeData }): any;
'tree-node-tool'(_props: { data: TreeNodeData }): any; 'tree-node-tool'(props: { data: TreeNodeData }): any;
}>(); }>();
defineOptions({ defineOptions({

View File

@ -15,7 +15,7 @@
class="tree-node" class="tree-node"
:class="{ selected, expanded }" :class="{ selected, expanded }"
:style="`padding-left: ${indent}px`" :style="`padding-left: ${indent}px`"
@contextmenu="nodeContextmenuHandler" @contextmenu="nodeContentmenuHandler"
@mouseenter="mouseenterHandler" @mouseenter="mouseenterHandler"
> >
<MIcon <MIcon
@ -72,9 +72,9 @@ import type { LayerNodeStatus, TreeNodeData } from '@editor/type';
import { updateStatus } from '@editor/utils/tree'; import { updateStatus } from '@editor/utils/tree';
defineSlots<{ defineSlots<{
'tree-node-label'(_props: { data: TreeNodeData }): any; 'tree-node-label'(props: { data: TreeNodeData }): any;
'tree-node-tool'(_props: { data: TreeNodeData }): any; 'tree-node-tool'(props: { data: TreeNodeData }): any;
'tree-node-content'(_props: { data: TreeNodeData }): any; 'tree-node-content'(props: { data: TreeNodeData }): any;
}>(); }>();
defineOptions({ defineOptions({
@ -139,7 +139,7 @@ const handleDragEnd = (event: DragEvent) => {
treeEmit?.('node-dragend', event, props.data); treeEmit?.('node-dragend', event, props.data);
}; };
const nodeContextmenuHandler = (event: MouseEvent) => { const nodeContentmenuHandler = (event: MouseEvent) => {
treeEmit?.('node-contextmenu', event, props.data); treeEmit?.('node-contextmenu', event, props.data);
}; };

View File

@ -16,7 +16,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, watch } from 'vue'; import { computed, inject, watch } from 'vue';
import { isEmpty } from 'lodash-es'; import { isEmpty } from 'lodash-es';
import { HookCodeType, HookType } from '@tmagic/core'; import { HookCodeType, HookType } from '@tmagic/core';
@ -24,7 +24,7 @@ import { TMagicCard } from '@tmagic/design';
import type { ContainerChangeEventData, FieldProps, FormItem, GroupListConfig } from '@tmagic/form'; import type { ContainerChangeEventData, FieldProps, FormItem, GroupListConfig } from '@tmagic/form';
import { MContainer } from '@tmagic/form'; import { MContainer } from '@tmagic/form';
import { useServices } from '@editor/hooks/use-services'; import type { Services } from '@editor/type';
defineOptions({ defineOptions({
name: 'MFieldsCodeSelect', name: 'MFieldsCodeSelect',
@ -34,7 +34,7 @@ const emit = defineEmits<{
change: [v: any, eventData: ContainerChangeEventData]; change: [v: any, eventData: ContainerChangeEventData];
}>(); }>();
const { dataSourceService, codeBlockService } = useServices(); const services = inject<Services>('services');
const props = withDefaults( const props = withDefaults(
defineProps< defineProps<
@ -59,7 +59,7 @@ const codeConfig = computed<GroupListConfig>(() => ({
return index; return index;
} }
const ds = dataSourceService.getDataSourceById(model.codeId[0]); const ds = services?.dataSourceService.getDataSourceById(model.codeId[0]);
return `${ds?.title} / ${model.codeId[1]}`; return `${ds?.title} / ${model.codeId[1]}`;
} }
@ -80,11 +80,13 @@ const codeConfig = computed<GroupListConfig>(() => ({
{ value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' }, { value: HookCodeType.DATA_SOURCE_METHOD, text: '数据源方法' },
], ],
defaultValue: 'code', defaultValue: 'code',
onChange: (_mForm, v: HookCodeType, { setModel }) => { onChange: (mForm, v: HookCodeType, { model, prop, changeRecords }) => {
if (v === HookCodeType.DATA_SOURCE_METHOD) { if (v === HookCodeType.DATA_SOURCE_METHOD) {
setModel('codeId', []); model.codeId = [];
changeRecords.push({ propPath: prop.replace('codeType', 'codeId'), value: [] });
} else { } else {
setModel('codeId', ''); model.codeId = '';
changeRecords.push({ propPath: prop.replace('codeType', 'codeId'), value: '' });
} }
return v; return v;
@ -95,16 +97,16 @@ const codeConfig = computed<GroupListConfig>(() => ({
name: 'codeId', name: 'codeId',
span: 18, span: 18,
labelWidth: 0, labelWidth: 0,
display: (_mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD, display: (mForm, { model }) => model.codeType !== HookCodeType.DATA_SOURCE_METHOD,
notEditable: () => !codeBlockService.getEditStatus(), notEditable: () => !services?.codeBlockService.getEditStatus(),
}, },
{ {
type: 'data-source-method-select', type: 'data-source-method-select',
name: 'codeId', name: 'codeId',
span: 18, span: 18,
labelWidth: 0, labelWidth: 0,
display: (_mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD, display: (mForm, { model }) => model.codeType === HookCodeType.DATA_SOURCE_METHOD,
notEditable: () => !dataSourceService.get('editable'), notEditable: () => !services?.dataSourceService.get('editable'),
}, },
], ],
}, },

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