diff --git a/.github/workflows/push-check.yml b/.github/workflows/push-check.yml
index 0acaab2bdd..03d23d21c5 100644
--- a/.github/workflows/push-check.yml
+++ b/.github/workflows/push-check.yml
@@ -4,40 +4,40 @@ on:
push:
branches: []
pull_request:
- branches: [develop,main, refactor/develop]
+ branches: [develop, main, refactor/develop, release/*]
jobs:
push-check:
- runs-on: ubuntu-latest # windows-latest || macos-latest
-
+ runs-on: ubuntu-latest # windows-latest || macos-latest
+
steps:
- - uses: actions/checkout@v4
- - name: Install pnpm
- uses: pnpm/action-setup@v4
- with:
- version: 9
- - uses: actions/setup-node@v4
- with:
- node-version: 18
+ - uses: actions/checkout@v4
+ - name: Install pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: 9
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 18
- - name: Install dependencies
- run: pnpm i
+ - name: Install dependencies
+ run: pnpm i
- - name: Get changed files
- id: get_changed_files
- uses: tj-actions/changed-files@v41
- with:
- files: |
- **.js
- **.vue
- **.jsx
- - name: Run ESLint
- run: npx eslint ${{steps.get_changed_files.outputs.all_changed_files}}
- - name: Run Build
- run: pnpm run build:plugin && pnpm run build:alpha > build-alpha.log 2>&1
+ - name: Get changed files
+ id: get_changed_files
+ uses: tj-actions/changed-files@v41
+ with:
+ files: |
+ **.js
+ **.vue
+ **.jsx
+ - name: Run ESLint
+ run: npx eslint ${{steps.get_changed_files.outputs.all_changed_files}}
+ - name: Run Build
+ run: pnpm run build:plugin && pnpm run build:alpha > build-alpha.log 2>&1
- - name: Upload build logs
- uses: actions/upload-artifact@v4
- with:
- name: build-alpha-log
- path: build-alpha.log
+ - name: Upload build logs
+ uses: actions/upload-artifact@v4
+ with:
+ name: build-alpha-log
+ path: build-alpha.log
diff --git a/designer-demo/package.json b/designer-demo/package.json
index 30822897f7..f39d5ae222 100644
--- a/designer-demo/package.json
+++ b/designer-demo/package.json
@@ -1,7 +1,7 @@
{
"name": "designer-demo",
"private": true,
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"type": "module",
"scripts": {
"dev": "cross-env VITE_THEME=light vite",
diff --git a/designer-demo/public/mock/bundle.json b/designer-demo/public/mock/bundle.json
index 358e84215b..328a4f2775 100644
--- a/designer-demo/public/mock/bundle.json
+++ b/designer-demo/public/mock/bundle.json
@@ -44,25 +44,13 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "type",
- "size"
- ]
+ "properties": ["type", "size"]
},
"contextMenu": {
- "actions": [
- "copy",
- "remove",
- "insert",
- "updateAttr",
- "bindEevent",
- "createBlock"
- ],
+ "actions": ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"],
"disable": []
},
- "invalidity": [
- ""
- ],
+ "invalidity": [""],
"clickCapture": true,
"framework": "Vue"
},
@@ -336,25 +324,13 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "type",
- "size"
- ]
+ "properties": ["type", "size"]
},
"contextMenu": {
- "actions": [
- "copy",
- "remove",
- "insert",
- "updateAttr",
- "bindEevent",
- "createBlock"
- ],
+ "actions": ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"],
"disable": []
},
- "invalidity": [
- ""
- ],
+ "invalidity": [""],
"clickCapture": true,
"framework": "Vue"
},
@@ -660,9 +636,7 @@
"isModal": false,
"isPopper": false,
"nestingRule": {
- "childWhitelist": [
- "ElFormItem"
- ],
+ "childWhitelist": ["ElFormItem"],
"parentWhitelist": "",
"descendantBlacklist": "",
"ancestorWhitelist": ""
@@ -671,25 +645,13 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "inline",
- "label-width"
- ]
+ "properties": ["inline", "label-width"]
},
"contextMenu": {
- "actions": [
- "copy",
- "remove",
- "insert",
- "updateAttr",
- "bindEevent",
- "createBlock"
- ],
+ "actions": ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"],
"disable": []
},
- "invalidity": [
- ""
- ],
+ "invalidity": [""],
"clickCapture": true,
"framework": "Vue"
},
@@ -1138,25 +1100,13 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "inline",
- "label-width"
- ]
+ "properties": ["inline", "label-width"]
},
"contextMenu": {
- "actions": [
- "copy",
- "remove",
- "insert",
- "updateAttr",
- "bindEevent",
- "createBlock"
- ],
+ "actions": ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"],
"disable": []
},
- "invalidity": [
- ""
- ],
+ "invalidity": [""],
"clickCapture": true,
"framework": "Vue"
},
@@ -1490,9 +1440,7 @@
"isModal": false,
"isPopper": false,
"nestingRule": {
- "childWhitelist": [
- "ElTableColumn"
- ],
+ "childWhitelist": ["ElTableColumn"],
"parentWhitelist": "",
"descendantBlacklist": "",
"ancestorWhitelist": ""
@@ -1501,25 +1449,13 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "inline",
- "label-width"
- ]
+ "properties": ["inline", "label-width"]
},
"contextMenu": {
- "actions": [
- "copy",
- "remove",
- "insert",
- "updateAttr",
- "bindEevent",
- "createBlock"
- ],
+ "actions": ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"],
"disable": []
},
- "invalidity": [
- ""
- ],
+ "invalidity": [""],
"clickCapture": true,
"framework": "Vue"
},
@@ -2750,25 +2686,13 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "inline",
- "label-width"
- ]
+ "properties": ["inline", "label-width"]
},
"contextMenu": {
- "actions": [
- "copy",
- "remove",
- "insert",
- "updateAttr",
- "bindEevent",
- "createBlock"
- ],
+ "actions": ["copy", "remove", "insert", "updateAttr", "bindEevent", "createBlock"],
"disable": []
},
- "invalidity": [
- ""
- ],
+ "invalidity": [""],
"clickCapture": true,
"framework": "Vue"
},
@@ -2914,19 +2838,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "size"
- ]
+ "properties": ["disabled", "size"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -3243,9 +3159,7 @@
"clickCapture": false,
"isModal": false,
"nestingRule": {
- "childWhitelist": [
- "TinyCarouselItem"
- ],
+ "childWhitelist": ["TinyCarouselItem"],
"parentWhitelist": "",
"descendantBlacklist": "",
"ancestorWhitelist": ""
@@ -3254,19 +3168,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "size"
- ]
+ "properties": ["disabled", "size"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -3418,14 +3324,7 @@
"name": {
"zh_CN": "标题"
},
- "component": [
- "h1",
- "h2",
- "h3",
- "h4",
- "h5",
- "h6"
- ],
+ "component": ["h1", "h2", "h3", "h4", "h5", "h6"],
"icon": "h16",
"description": "标题",
"docUrl": "",
@@ -3510,19 +3409,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "size"
- ]
+ "properties": ["disabled", "size"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -3879,19 +3770,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "size"
- ]
+ "properties": ["disabled", "size"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -4137,9 +4020,7 @@
],
"events": {},
"shortcuts": {
- "properties": [
- "src"
- ]
+ "properties": ["src"]
},
"contentMenu": {
"actions": []
@@ -4789,19 +4670,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "size"
- ]
+ "properties": ["disabled", "size"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -4947,19 +4820,118 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "label-width",
- "disabled"
- ]
+ "properties": ["label-width", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
+ }
+ }
+ },
+ {
+ "icon": "row",
+ "name": {
+ "zh_CN": "row"
+ },
+ "component": "TinyLayout",
+ "description": "定义 Layout 的行配置信息",
+ "docUrl": "",
+ "screenshot": "",
+ "tags": "",
+ "keywords": "",
+ "devMode": "proCode",
+ "npm": {
+ "package": "@opentiny/vue",
+ "exportName": "Layout",
+ "version": "3.14.0",
+ "destructuring": true,
+ "script": "https://unpkg.com/@opentiny/vue@~3.14/runtime/tiny-vue.mjs",
+ "css": "https://unpkg.com/@opentiny/vue-theme@~3.14/index.css"
+ },
+ "group": "component",
+ "priority": 5,
+ "schema": {
+ "properties": [
+ {
+ "label": {
+ "zh_CN": "基础信息"
+ },
+ "description": {
+ "zh_CN": "基础信息"
+ },
+ "content": [
+ {
+ "property": "cols",
+ "label": {
+ "text": {
+ "zh_CN": "总栅格数"
+ }
+ },
+ "cols": 12,
+ "widget": {
+ "component": "ButtonGroupConfigurator",
+ "props": {
+ "options": [
+ {
+ "label": "12",
+ "value": 12
+ },
+ {
+ "label": "24",
+ "value": 24
+ }
+ ]
+ }
+ },
+ "description": {
+ "zh_CN": "选择总栅格数"
+ },
+ "labelPosition": "none"
+ },
+ {
+ "property": "tag",
+ "label": {
+ "text": {
+ "zh_CN": "layout渲染的标签"
+ }
+ },
+ "required": false,
+ "readOnly": false,
+ "disabled": false,
+ "cols": 12,
+ "widget": {
+ "component": "InputConfigurator",
+ "props": {}
+ },
+ "description": {
+ "zh_CN": "定义Layout元素渲染后的标签,默认为 div"
+ }
+ }
+ ]
+ }
+ ]
+ },
+ "configure": {
+ "loop": true,
+ "condition": true,
+ "styles": true,
+ "isContainer": true,
+ "isModal": false,
+ "nestingRule": {
+ "childWhitelist": ["TinyRow", "TinyCol"],
+ "parentWhitelist": "",
+ "descendantBlacklist": "",
+ "ancestorWhitelist": ""
+ },
+ "isNullNode": false,
+ "isLayout": false,
+ "rootSelector": "",
+ "shortcuts": {
+ "properties": ["disabled"]
+ },
+ "contextMenu": {
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -5309,19 +5281,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "label-width",
- "disabled"
- ]
+ "properties": ["label-width", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -5441,9 +5405,7 @@
"isModal": false,
"nestingRule": {
"childWhitelist": "",
- "parentWhitelist": [
- "TinyForm"
- ],
+ "parentWhitelist": ["TinyForm"],
"descendantBlacklist": "",
"ancestorWhitelist": ""
},
@@ -5451,19 +5413,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "label",
- "rules"
- ]
+ "properties": ["label", "rules"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -5737,19 +5691,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "label",
- "rules"
- ]
+ "properties": ["label", "rules"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -6084,19 +6030,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "text",
- "size"
- ]
+ "properties": ["text", "size"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -6506,19 +6444,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "value",
- "disabled"
- ]
+ "properties": ["value", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -6751,19 +6681,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "visible",
- "width"
- ]
+ "properties": ["visible", "width"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -7164,19 +7086,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "multiple",
- "options"
- ]
+ "properties": ["multiple", "options"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -7371,19 +7285,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "mini"
- ]
+ "properties": ["disabled", "mini"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -7654,19 +7560,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "clearable",
- "mini"
- ]
+ "properties": ["clearable", "mini"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -7921,19 +7819,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "border",
- "disabled"
- ]
+ "properties": ["border", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -8117,19 +8007,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "text",
- "size"
- ]
+ "properties": ["text", "size"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -8334,19 +8216,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "type"
- ]
+ "properties": ["disabled", "type"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -8620,19 +8494,11 @@
"isLayout": false,
"rootSelector": ".tiny-dialog-box",
"shortcuts": {
- "properties": [
- "visible",
- "width"
- ]
+ "properties": ["visible", "width"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -8900,9 +8766,7 @@
"clickCapture": false,
"isModal": false,
"nestingRule": {
- "childWhitelist": [
- "TinyTabItem"
- ],
+ "childWhitelist": ["TinyTabItem"],
"parentWhitelist": [],
"descendantBlacklist": [],
"ancestorWhitelist": []
@@ -8911,19 +8775,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "size",
- "tab-style"
- ]
+ "properties": ["size", "tab-style"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -9020,9 +8876,7 @@
"isModal": false,
"nestingRule": {
"childWhitelist": "",
- "parentWhitelist": [
- "TinyTab"
- ],
+ "parentWhitelist": ["TinyTab"],
"descendantBlacklist": "",
"ancestorWhitelist": ""
},
@@ -9030,19 +8884,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "name",
- "title"
- ]
+ "properties": ["name", "title"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -9168,9 +9014,7 @@
"clickCapture": false,
"isModal": false,
"nestingRule": {
- "childWhitelist": [
- "TinyBreadcrumbItem"
- ],
+ "childWhitelist": ["TinyBreadcrumbItem"],
"parentWhitelist": [],
"descendantBlacklist": [],
"ancestorWhitelist": []
@@ -9179,18 +9023,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "separator"
- ]
+ "properties": ["separator"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -9267,9 +9104,7 @@
"isModal": false,
"nestingRule": {
"childWhitelist": "",
- "parentWhitelist": [
- "TinyBreadcrumb"
- ],
+ "parentWhitelist": ["TinyBreadcrumb"],
"descendantBlacklist": "",
"ancestorWhitelist": ""
},
@@ -9277,18 +9112,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "to"
- ]
+ "properties": ["to"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -9411,19 +9239,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "label-width",
- "disabled"
- ]
+ "properties": ["label-width", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -9530,19 +9350,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "label-width",
- "disabled"
- ]
+ "properties": ["label-width", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -9757,10 +9569,7 @@
"widget": {
"component": "JsSlotConfigurator",
"props": {
- "slots": [
- "header",
- "default"
- ]
+ "slots": ["header", "default"]
}
}
},
@@ -9823,8 +9632,7 @@
},
"description": {
"zh_CN": "单元格编辑渲染配置项,也可以是函数 Function(h, params)"
- },
- "labelPosition": "left"
+ }
},
{
"property": "filter",
@@ -9912,7 +9720,7 @@
"required": true,
"readOnly": false,
"disabled": false,
- "onChange": "this.delProp('data')",
+ "onChange": "function () { this.delProp('data') } ",
"cols": 12,
"widget": {
"component": "CodeConfigurator",
@@ -10433,15 +10241,10 @@
}
},
"shortcuts": {
- "properties": [
- "sortable",
- "columns"
- ]
+ "properties": ["sortable", "columns"]
},
"contentMenu": {
- "actions": [
- "create symbol"
- ]
+ "actions": ["create symbol"]
},
"onBeforeMount": "console.log('table on load'); this.pager = source.pager; this.fetchData = source.fetchData; this.data = source.data ;this.columns = source.columns"
},
@@ -10461,19 +10264,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "sortable",
- "columns"
- ]
+ "properties": ["sortable", "columns"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -10702,19 +10497,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "currentPage",
- "total"
- ]
+ "properties": ["currentPage", "total"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -11073,19 +10860,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "modelValue",
- "disabled"
- ]
+ "properties": ["modelValue", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -11416,19 +11195,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "data",
- "show-checkbox"
- ]
+ "properties": ["data", "show-checkbox"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -11631,19 +11402,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "active",
- "data"
- ]
+ "properties": ["active", "data"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -11860,19 +11623,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "disabled",
- "content"
- ]
+ "properties": ["disabled", "content"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -12363,19 +12118,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "visible",
- "width"
- ]
+ "properties": ["visible", "width"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -12786,19 +12533,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "value",
- "disabled"
- ]
+ "properties": ["value", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
},
@@ -13237,19 +12976,11 @@
"isLayout": false,
"rootSelector": "",
"shortcuts": {
- "properties": [
- "value",
- "disabled"
- ]
+ "properties": ["value", "disabled"]
},
"contextMenu": {
- "actions": [
- "create symbol"
- ],
- "disable": [
- "copy",
- "remove"
- ]
+ "actions": ["create symbol"],
+ "disable": ["copy", "remove"]
}
}
}
@@ -13634,10 +13365,7 @@
"schema": {
"componentName": "TinyCheckboxGroup",
"props": {
- "modelValue": [
- "name1",
- "name2"
- ],
+ "modelValue": ["name1", "name2"],
"type": "checkbox",
"options": [
{
@@ -14462,4 +14190,4 @@
]
}
}
-}
\ No newline at end of file
+}
diff --git a/mockServer/package.json b/mockServer/package.json
index 10c1e7c4f2..dfac7ce031 100644
--- a/mockServer/package.json
+++ b/mockServer/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-mock",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"publishConfig": {
"access": "public"
},
diff --git a/packages/block-compiler/.eslintrc.cjs b/packages/block-compiler/.eslintrc.cjs
new file mode 100644
index 0000000000..eb1014c689
--- /dev/null
+++ b/packages/block-compiler/.eslintrc.cjs
@@ -0,0 +1,27 @@
+const path = require('path')
+const { rules } = require('../../.eslintrc')
+
+module.exports = {
+ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
+ root: true,
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ projectService: true,
+ project: [path.join(__dirname, './tsconfig.json') ],
+ ecmaVersion: 'latest',
+ },
+ plugins: ['@typescript-eslint'],
+ env: {
+ browser: true,
+ es2015: true,
+ node: true
+ },
+ rules: {
+ ...rules,
+ // 允许 @ts-ignore
+ "@typescript-eslint/ban-ts-comment": "off",
+ // 允许非空断言
+ "@typescript-eslint/no-non-null-asserted-optional-chain": "off"
+ },
+ ignorePatterns: ['test/sample/*.vue', '.eslintrc.cjs']
+}
diff --git a/packages/block-compiler/README.md b/packages/block-compiler/README.md
new file mode 100644
index 0000000000..c840a7126b
--- /dev/null
+++ b/packages/block-compiler/README.md
@@ -0,0 +1,2 @@
+# @opentiny/tiny-engine 低代码引擎区编译器
+
diff --git a/packages/block-compiler/index.html b/packages/block-compiler/index.html
new file mode 100644
index 0000000000..f0dde4b8cc
--- /dev/null
+++ b/packages/block-compiler/index.html
@@ -0,0 +1,35 @@
+
+
+
+
+
+ block-compiler
+
+
+
+
+
+
+
+
diff --git a/packages/block-compiler/package.json b/packages/block-compiler/package.json
new file mode 100644
index 0000000000..15496257b6
--- /dev/null
+++ b/packages/block-compiler/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@opentiny/tiny-engine-block-compiler",
+ "version": "2.1.0",
+ "publishConfig": {
+ "access": "public"
+ },
+ "description": "block runtime compiler",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build"
+ },
+ "files": [
+ "dist"
+ ],
+ "type": "module",
+ "main": "dist/index.js",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/opentiny/tiny-engine",
+ "directory": "packages/block-compiler"
+ },
+ "bugs": {
+ "url": "https://github.com/opentiny/tiny-engine/issues"
+ },
+ "author": "OpenTiny Team",
+ "license": "MIT",
+ "homepage": "https://opentiny.design/tiny-engine",
+ "devDependencies": {
+ "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
+ "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
+ "@types/babel__core": "^7.20.5",
+ "@vitejs/plugin-vue": "^5.1.2",
+ "typescript": "~5.4.2",
+ "vite": "^5.4.2",
+ "vite-plugin-dts": "^4.3.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.26.0",
+ "@vue/babel-plugin-jsx": "^1.2.5",
+ "@vue/compiler-sfc": "^3.4.15",
+ "vue": "^3.4.15"
+ }
+}
diff --git a/packages/block-compiler/src/dev.ts b/packages/block-compiler/src/dev.ts
new file mode 100644
index 0000000000..1c6344e228
--- /dev/null
+++ b/packages/block-compiler/src/dev.ts
@@ -0,0 +1,78 @@
+// @ts-ignore
+import { createApp, defineAsyncComponent, h } from 'https://unpkg.com/vue@3.4.23/dist/vue.runtime.esm-browser.js'
+import { compile } from './index'
+import BlockFileName from '../test/sample/BlockFileName.vue?raw'
+import BlockHead from '../test/sample/BlockHead.vue?raw'
+import BlockMenu from '../test/sample/BlockMenu.vue?raw'
+import BlockTest from '../test/sample/BlockTest.vue?raw'
+import BlockJsxTest from '../test/sample/slotModelValueTest.vue?raw'
+
+const RenderMain = {
+ setup() {
+ const componentMap = compile(
+ [
+ {
+ fileName: 'BlockHead',
+ sourceCode: BlockHead
+ },
+ {
+ fileName: 'BlockFileName',
+ sourceCode: BlockFileName
+ },
+ {
+ fileName: 'BlockMenu',
+ sourceCode: BlockMenu
+ },
+ {
+ fileName: 'BlockTest',
+ sourceCode: BlockTest
+ },
+ {
+ fileName: 'BlockJsxTest',
+ sourceCode: BlockJsxTest
+ }
+ ],
+ {}
+ )
+
+ const blockComponents: { [key: string]: unknown } = {}
+
+ // @ts-ignore
+ window.getBlockComponentBlobUrl = (name) => {
+ return componentMap?.[name]?.blobURL
+ }
+
+ for (const [fileName, value] of Object.entries(componentMap)) {
+ blockComponents[fileName] = defineAsyncComponent(() => import(/* @vite-ignore */ value.blobURL))
+ }
+
+ const css = Object.values(componentMap)
+ .map((item) => item.style)
+ .join('')
+
+ const stylesheet = document.querySelector('#block-stylesheet')
+
+ if (stylesheet) {
+ stylesheet.remove()
+ } else {
+ const newStyleSheet = document.createElement('style')
+
+ newStyleSheet.innerHTML = css
+
+ document.head.appendChild(newStyleSheet)
+ }
+
+ return () =>
+ h('div', {}, [
+ h(blockComponents.BlockJsxTest),
+ h(blockComponents.BlockTest),
+ h(blockComponents.BlockHead),
+ h(blockComponents.BlockFileName),
+ h('span', {}, 'testtest')
+ ])
+ }
+}
+
+const App = createApp(RenderMain)
+
+App.mount(document.querySelector('#app')!)
diff --git a/packages/block-compiler/src/index.ts b/packages/block-compiler/src/index.ts
new file mode 100644
index 0000000000..6f2c340d8c
--- /dev/null
+++ b/packages/block-compiler/src/index.ts
@@ -0,0 +1,308 @@
+import { compileScript, compileTemplate, compileStyle, parse, babelParse, MagicString } from '@vue/compiler-sfc'
+import type { SFCParseResult, SFCDescriptor, BindingMetadata, CompilerOptions } from '@vue/compiler-sfc'
+import { testIsJsx, transformVueJsx } from './transformJsx.ts'
+
+const compileBlockStyle = (descriptor: SFCDescriptor, id: string) => {
+ const cssResArr = descriptor.styles.map((style) => {
+ const result = compileStyle({
+ id,
+ filename: descriptor.filename,
+ source: style.content,
+ scoped: style.scoped
+ })
+
+ return result.code || ''
+ })
+
+ return cssResArr.join('\n')
+}
+
+const compileBlockTemplate = (descriptor: SFCDescriptor, id: string, bindingMetadata: BindingMetadata | undefined) => {
+ const isJsx = testIsJsx(descriptor)
+ const expressionPlugins: CompilerOptions['expressionPlugins'] = []
+
+ if (isJsx) {
+ expressionPlugins.push('jsx')
+ }
+
+ const compileRes = compileTemplate({
+ id,
+ ast: descriptor.template?.ast,
+ source: descriptor.template?.content!,
+ filename: descriptor.filename,
+ scoped: descriptor.styles.some((styleItem) => styleItem.scoped),
+ slotted: descriptor.slotted,
+ compilerOptions: {
+ bindingMetadata,
+ expressionPlugins
+ }
+ })
+
+ const { errors } = compileRes
+ let { code } = compileRes
+
+ if (isJsx) {
+ code = transformVueJsx(code) || ''
+ }
+
+ return { code, errors }
+}
+
+interface compiledItem {
+ js: string
+ style: string
+ blobURL: string
+}
+
+export interface IResultMap {
+ [key: string]: compiledItem
+}
+
+const resolveRelativeImport = (code: string, globalGetterName = 'loadBlockComponent') => {
+ const magicStr = new MagicString(code)
+ const ast = babelParse(code, { sourceType: 'module', plugins: ['jsx'] }).program.body
+
+ let vueImportNode = null
+ let hasDefineAsyncComponent = false
+
+ for (const node of ast) {
+ if (node.type === 'ImportDeclaration') {
+ const source = node.source.value
+ if (source === 'vue' && !node.specifiers.find((spec) => spec.type === 'ImportNamespaceSpecifier')) {
+ vueImportNode = node
+ }
+ // 标识相对路径引入的 .vue 文件为区块,使用异步组件替换
+ if (source.startsWith('./') && node.source.value.endsWith('.vue')) {
+ hasDefineAsyncComponent = true
+ const fileName = node.source.value.replace(/^(\.\/+)/, '').slice(0, -4)
+ // 默认导出名
+ const defaultImportId = node.specifiers.find((spec) => spec.type === 'ImportDefaultSpecifier')?.local?.name
+
+ // 不存在默认导出,跳过
+ if (!defaultImportId) {
+ continue
+ }
+
+ // 声明异步组件 const Block = defineAsyncComponent(() => import(getBlockUrl(Block)))
+ magicStr.appendLeft(
+ node.start!,
+ `const ${defaultImportId} = defineAsyncComponent(() => window.${globalGetterName}('${fileName}'))`
+ )
+
+ // 移除 import Block from './Block.vue' 语句
+ magicStr.remove(node.start!, node.end!)
+ }
+ }
+ }
+
+ // TODO: 拿到类型声明,拆分到另一个函数
+ if (hasDefineAsyncComponent) {
+ // 存在异步引入组件
+ if (vueImportNode) {
+ const asyncSpec = vueImportNode.specifiers.find(
+ (spec) => spec.type === 'ImportSpecifier' && spec.local.name === 'defineAsyncComponent'
+ )
+
+ if (!asyncSpec) {
+ const firstRelativeSpec = vueImportNode.specifiers.find((spec) => spec.type === 'ImportSpecifier')
+
+ if (firstRelativeSpec) {
+ magicStr.appendLeft(firstRelativeSpec.start!, 'defineAsyncComponent, ')
+ } else {
+ magicStr.appendRight(vueImportNode.specifiers[0].end!, ', { defineAsyncComponent }')
+ }
+ }
+ } else {
+ magicStr.appendLeft(ast[0].start!, "import { defineAsyncComponent } from 'vue'\n")
+ }
+ }
+
+ return magicStr.toString()
+}
+
+const DEFAULT_COMPONENT_NAME = '__sfc__'
+
+const compileBlockScript = (descriptor: SFCDescriptor, id: string): [string, BindingMetadata | undefined] => {
+ const isJsx = testIsJsx(descriptor)
+ const expressionPlugins: CompilerOptions['expressionPlugins'] = []
+
+ if (isJsx) {
+ expressionPlugins.push('jsx')
+ }
+
+ // TODO: try catch
+ const compiledScript = compileScript(descriptor, {
+ genDefaultAs: DEFAULT_COMPONENT_NAME,
+ inlineTemplate: true,
+ id,
+ templateOptions: {
+ compilerOptions: {
+ expressionPlugins
+ }
+ }
+ })
+
+ let code = compiledScript.content
+
+ if (isJsx) {
+ code = transformVueJsx(code) || ''
+ }
+
+ return [code, compiledScript.bindings]
+}
+
+interface IParsedFileItem {
+ fileName: string
+ sourceCode: string
+ compilerParseResult: SFCParseResult
+ importedFiles: string[]
+ fileNameWithRelativePath: string
+}
+
+// 依次构建 script、template、style,然后组装成 import
+const compileFile = (file: IParsedFileItem): Omit => {
+ const descriptor = file.compilerParseResult.descriptor
+
+ // 编译 script
+ const [compiledScript, bindings] = compileBlockScript(descriptor, file.fileName)
+ let componentCode = `${compiledScript}`
+
+ // 编译 template
+ if (!descriptor.scriptSetup && descriptor.template) {
+ const { code: compiledTemplate } = compileBlockTemplate(descriptor, file.fileName, bindings)
+
+ componentCode += `\n ${compiledTemplate} \n ${DEFAULT_COMPONENT_NAME}.render = render`
+ }
+
+ const hasScoped = descriptor.styles.some((styleItem) => styleItem.scoped)
+
+ if (hasScoped) {
+ componentCode += `\n${DEFAULT_COMPONENT_NAME}.__scopeId='data-v-${file.fileName}'`
+ }
+
+ // 编译 style
+ const styleString = compileBlockStyle(descriptor, file.fileName)
+
+ return {
+ js: `${componentCode}\nexport default ${DEFAULT_COMPONENT_NAME}`,
+ style: styleString
+ }
+}
+
+// 解析依赖的文件
+const parseImportedFiles = (descriptor: SFCDescriptor): string[] => {
+ let scriptContent = ''
+
+ if (descriptor.script) {
+ scriptContent = descriptor.script.content
+ } else if (descriptor.scriptSetup) {
+ scriptContent = descriptor.scriptSetup.content
+ }
+
+ if (!scriptContent) {
+ return []
+ }
+
+ const ast = babelParse(scriptContent, { sourceFilename: descriptor.filename, sourceType: 'module', plugins: ['jsx'] })
+ .program.body
+ const res: string[] = []
+
+ for (const node of ast) {
+ if (node.type === 'ImportDeclaration') {
+ const source = node.source.value
+
+ // 相对路径依赖,区块嵌套的场景
+ if (source.startsWith('./')) {
+ res.push(node.source.value)
+ }
+ }
+ }
+
+ return res
+}
+
+const getJSBlobURL = (str: string) => {
+ const blob = new Blob([str], { type: 'application/javascript' })
+
+ return URL.createObjectURL(blob)
+}
+
+export interface IFileItem {
+ fileName: string
+ sourceCode: string
+}
+
+export type IFileList = IFileItem[]
+
+export interface IConfig {
+ compileCache?: Map
+ globalGetterName?: string
+}
+
+// TODO: 支持 importMap
+export const compile = (fileList: IFileList, config: IConfig) => {
+ const parsedFileList = fileList.map((fileItem) => {
+ const { fileName, sourceCode } = fileItem
+ // FIXME:这里解析的结果不能重复使用,因为可能会涉及修改引入的依赖
+ const { descriptor, errors } = parse(sourceCode, { filename: fileName })
+
+ if (errors) {
+ // TODO: 抛出错误
+ }
+
+ // TODO: 1. 当前仅支持 vue 文件编译,检查文件后缀,如果不是 .vue 结尾的,抛出错误
+ // TODO: 2. 检查 style lang,仅支持 css
+ // TODO: 3. 检查 template lang,当前不支持任何 template lang
+
+ // 解析依赖的文件
+ const importedFiles = parseImportedFiles(descriptor)
+
+ return {
+ fileName,
+ sourceCode,
+ compilerParseResult: {
+ descriptor,
+ errors
+ },
+ importedFiles,
+ fileNameWithRelativePath: `./${fileName}.vue`
+ }
+ })
+
+ const compiledFilesSet: Set = new Set()
+ const resultMap: IResultMap = {}
+
+ const compileCache = config?.compileCache || new Map()
+
+ for (const fileItem of parsedFileList) {
+ const fileName = fileItem.fileName
+ const cache = compileCache.get(fileName)
+ let js = ''
+ let style = ''
+
+ // 优先使用缓存
+ if (cache?.js && cache?.style) {
+ js = cache.js
+ style = cache.style
+ } else {
+ const compileRes = compileFile(fileItem)
+
+ js = compileRes.js
+ style = compileRes.style
+ }
+
+ const resolvedImportJs = resolveRelativeImport(js, config?.globalGetterName)
+
+ resultMap[fileName] = {
+ js: resolvedImportJs,
+ style,
+ blobURL: getJSBlobURL(resolvedImportJs)
+ }
+
+ compileCache.set(fileName, resultMap[fileName])
+
+ compiledFilesSet.add(fileItem.fileNameWithRelativePath)
+ }
+
+ return resultMap
+}
diff --git a/packages/block-compiler/src/transformJsx.ts b/packages/block-compiler/src/transformJsx.ts
new file mode 100644
index 0000000000..900ade2a73
--- /dev/null
+++ b/packages/block-compiler/src/transformJsx.ts
@@ -0,0 +1,18 @@
+import { transformSync } from '@babel/core'
+import vueJsx from '@vue/babel-plugin-jsx'
+import type { SFCDescriptor } from 'vue/compiler-sfc'
+
+export const testIsJsx = (descriptor: SFCDescriptor) => {
+ const lang = descriptor.script?.lang || descriptor?.scriptSetup?.lang || ''
+
+ return /jsx$/.test(lang)
+}
+
+export const transformVueJsx = (sourceCode: string) => {
+ return transformSync(sourceCode, {
+ babelrc: false,
+ plugins: [vueJsx],
+ sourceMaps: false,
+ configFile: false
+ })?.code
+}
diff --git a/packages/block-compiler/src/vite-env.d.ts b/packages/block-compiler/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/packages/block-compiler/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/packages/block-compiler/test/sample/BlockFileName.vue b/packages/block-compiler/test/sample/BlockFileName.vue
new file mode 100644
index 0000000000..11320f6952
--- /dev/null
+++ b/packages/block-compiler/test/sample/BlockFileName.vue
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+ TinyEngine 前端可视化设计器
+
+ TinyEngine 前端可视化设计器,为设计器开发者提供定制服务,在线构建出自己专属的设计器。
+
+
+
+
+
+
+
+
diff --git a/packages/block-compiler/test/sample/BlockHead.vue b/packages/block-compiler/test/sample/BlockHead.vue
new file mode 100644
index 0000000000..a07c77e3b4
--- /dev/null
+++ b/packages/block-compiler/test/sample/BlockHead.vue
@@ -0,0 +1,21 @@
+
+
+
这里是头部
+
+
+
+
+
+
+
diff --git a/packages/block-compiler/test/sample/BlockMenu.vue b/packages/block-compiler/test/sample/BlockMenu.vue
new file mode 100644
index 0000000000..c7f19c58e2
--- /dev/null
+++ b/packages/block-compiler/test/sample/BlockMenu.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/block-compiler/test/sample/BlockTest.vue b/packages/block-compiler/test/sample/BlockTest.vue
new file mode 100644
index 0000000000..7e19eb3350
--- /dev/null
+++ b/packages/block-compiler/test/sample/BlockTest.vue
@@ -0,0 +1,15 @@
+
+ block test aaaa
+
+
+
diff --git a/packages/block-compiler/test/sample/slotModelValueTest.vue b/packages/block-compiler/test/sample/slotModelValueTest.vue
new file mode 100644
index 0000000000..7f5645d329
--- /dev/null
+++ b/packages/block-compiler/test/sample/slotModelValueTest.vue
@@ -0,0 +1,89 @@
+
+
+
+
+
+
diff --git a/packages/block-compiler/tsconfig.json b/packages/block-compiler/tsconfig.json
new file mode 100644
index 0000000000..5e369f2d5c
--- /dev/null
+++ b/packages/block-compiler/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "emitDeclarationOnly": true,
+ "jsx": "preserve",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["./src/**/*.ts", "./src/**/*.tsx", "./src/**/*.vue"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/packages/block-compiler/tsconfig.node.json b/packages/block-compiler/tsconfig.node.json
new file mode 100644
index 0000000000..1020d544b9
--- /dev/null
+++ b/packages/block-compiler/tsconfig.node.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "emitDeclarationOnly": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["vite.config.ts", ".eslintrc.cjs"]
+}
diff --git a/packages/block-compiler/vite.config.ts b/packages/block-compiler/vite.config.ts
new file mode 100644
index 0000000000..c382ee2741
--- /dev/null
+++ b/packages/block-compiler/vite.config.ts
@@ -0,0 +1,38 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import dts from 'vite-plugin-dts'
+import path from 'node:path'
+import nodeGlobalsPolyfillPluginCjs from '@esbuild-plugins/node-globals-polyfill'
+import nodeModulesPolyfillPluginCjs from '@esbuild-plugins/node-modules-polyfill'
+
+// @ts-ignore
+const nodeGlobalsPolyfillPlugin = nodeGlobalsPolyfillPluginCjs.default
+// @ts-ignore
+const nodeModulesPolyfillPlugin = nodeModulesPolyfillPluginCjs.default
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [dts({ rollupTypes: true, tsconfigPath: './tsconfig.json' }), vue()],
+ optimizeDeps: {
+ esbuildOptions: {
+ plugins: [
+ nodeGlobalsPolyfillPlugin({
+ process: true,
+ buffer: true
+ }),
+ nodeModulesPolyfillPlugin()
+ ]
+ }
+ },
+ build: {
+ lib: {
+ entry: path.resolve(__dirname, './src/index.ts'),
+ name: 'block-compiler',
+ fileName: (_format, entryName) => `${entryName}.js`,
+ formats: ['es']
+ },
+ rollupOptions: {
+ external: ['@babel/core', '@vue/babel-plugin-jsx', 'vue', 'vue/compiler-sfc']
+ }
+ }
+})
diff --git a/packages/blockToWebComponentTemplate/package.json b/packages/blockToWebComponentTemplate/package.json
index 9116548583..5c6104fcf9 100644
--- a/packages/blockToWebComponentTemplate/package.json
+++ b/packages/blockToWebComponentTemplate/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-block-build",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"description": "translate block to webcomponent template",
"main": "./dist/web-components.es.js",
"type": "module",
diff --git a/packages/build/vite-config/package.json b/packages/build/vite-config/package.json
index ac2368b9bd..654ed3461f 100644
--- a/packages/build/vite-config/package.json
+++ b/packages/build/vite-config/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-vite-config",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"description": "",
"type": "module",
"main": "./index.js",
diff --git a/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js b/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js
index f9610e3d94..e53734e21e 100644
--- a/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js
+++ b/packages/build/vite-config/src/vite-plugins/devAliasPlugin.js
@@ -61,7 +61,8 @@ const getDevAlias = (useSourceAlias) => {
'@opentiny/tiny-engine-builtin-component': path.resolve(basePath, 'packages/builtinComponent/index.js'),
'@opentiny/tiny-engine-meta-register': path.resolve(basePath, 'packages/register/src/index.js'),
'@opentiny/tiny-engine-layout': path.resolve(basePath, 'packages/layout/index.js'),
- '@opentiny/tiny-engine-configurator': path.resolve(basePath, 'packages/configurator/src/index.js')
+ '@opentiny/tiny-engine-configurator': path.resolve(basePath, 'packages/configurator/src/index.js'),
+ '@opentiny/tiny-engine-block-compiler': path.resolve(basePath, 'packages/block-compiler/src/index.ts')
}
}
diff --git a/packages/build/vite-plugin-meta-comments/package.json b/packages/build/vite-plugin-meta-comments/package.json
index 36ca792ed2..2853da54c0 100644
--- a/packages/build/vite-plugin-meta-comments/package.json
+++ b/packages/build/vite-plugin-meta-comments/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-vite-plugin-meta-comments",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"description": "",
"type": "module",
"main": "dist/index.cjs",
diff --git a/packages/builtinComponent/index.js b/packages/builtinComponent/index.js
index 99242b77a4..d9976790dd 100644
--- a/packages/builtinComponent/index.js
+++ b/packages/builtinComponent/index.js
@@ -1,4 +1,6 @@
export { default as CanvasCol } from './src/components/CanvasCol.vue'
export { default as CanvasRow } from './src/components/CanvasRow.vue'
export { default as CanvasRowColContainer } from './src/components/CanvasRowColContainer.vue'
+export { default as CanvasFlexBox } from './src/components/CanvasFlexBox.vue'
+export { default as CanvasSection } from './src/components/CanvasSection.vue'
export { default as meta } from './src/meta'
diff --git a/packages/builtinComponent/package.json b/packages/builtinComponent/package.json
index 4248a2a535..3869da894f 100644
--- a/packages/builtinComponent/package.json
+++ b/packages/builtinComponent/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-builtin-component",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"description": "",
"main": "dist/index.mjs",
"module": "dist/index.mjs",
diff --git a/packages/builtinComponent/src/components/CanvasFlexBox.vue b/packages/builtinComponent/src/components/CanvasFlexBox.vue
new file mode 100644
index 0000000000..593443dc6b
--- /dev/null
+++ b/packages/builtinComponent/src/components/CanvasFlexBox.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
diff --git a/packages/builtinComponent/src/components/CanvasSection.vue b/packages/builtinComponent/src/components/CanvasSection.vue
new file mode 100644
index 0000000000..2d2994fa3f
--- /dev/null
+++ b/packages/builtinComponent/src/components/CanvasSection.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
diff --git a/packages/builtinComponent/src/meta/CanvasFlexBox.json b/packages/builtinComponent/src/meta/CanvasFlexBox.json
new file mode 100644
index 0000000000..625a3ac672
--- /dev/null
+++ b/packages/builtinComponent/src/meta/CanvasFlexBox.json
@@ -0,0 +1,221 @@
+{
+ "component": {
+ "icon": "Box",
+ "name": {
+ "zh_CN": "弹性容器"
+ },
+ "component": "CanvasFlexBox",
+ "schema": {
+ "slots": {},
+ "properties": [
+ {
+ "label": {
+ "zh_CN": "基础信息"
+ },
+ "description": {
+ "zh_CN": "基础信息"
+ },
+ "collapse": {
+ "number": 6,
+ "text": {
+ "zh_CN": "显示更多"
+ }
+ },
+ "content": [
+ {
+ "property": "flexDirection",
+ "type": "String",
+ "defaultValue": "row",
+ "bindState": true,
+ "label": {
+ "text": {
+ "zh_CN": "排列方向"
+ }
+ },
+ "cols": 12,
+ "rules": [],
+ "widget": {
+ "component": "SelectConfigurator",
+ "props": {
+ "options": [
+ {
+ "label": "水平,起点在左端",
+ "value": "row"
+ },
+ {
+ "label": "水平,起点在右端",
+ "value": "row-reverse"
+ },
+ {
+ "label": "垂直,起点在上沿",
+ "value": "column"
+ },
+ {
+ "label": "垂直,起点在下沿",
+ "value": "column-reverse"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "property": "gap",
+ "defaultValue": "8px",
+ "label": {
+ "text": {
+ "zh_CN": "间距"
+ }
+ },
+ "widget": {
+ "component": "InputConfigurator"
+ },
+ "description": {
+ "zh_CN": "控制容器内水平和垂直的间距"
+ },
+ "labelPosition": "left"
+ },
+ {
+ "property": "padding",
+ "defaultValue": "8px",
+ "label": {
+ "text": {
+ "zh_CN": "内边距"
+ }
+ },
+ "widget": {
+ "component": "InputConfigurator"
+ },
+ "labelPosition": "left"
+ },
+ {
+ "property": "justifyContent",
+ "type": "String",
+ "defaultValue": "flex-start",
+ "bindState": true,
+ "label": {
+ "text": {
+ "zh_CN": "水平对齐方式"
+ }
+ },
+ "cols": 12,
+ "rules": [],
+ "widget": {
+ "component": "SelectConfigurator",
+ "props": {
+ "options": [
+ {
+ "label": "左对齐",
+ "value": "flex-start"
+ },
+ {
+ "label": "右对齐",
+ "value": "flex-end"
+ },
+ {
+ "label": "居中",
+ "value": "center"
+ },
+ {
+ "label": "两端对齐,子元素间隔相等",
+ "value": "space-between"
+ },
+ {
+ "label": "子元素两侧间隔相等",
+ "value": "space-around"
+ }
+ ]
+ }
+ }
+ },
+ {
+ "property": "alignItems",
+ "type": "String",
+ "defaultValue": "center",
+ "bindState": true,
+ "label": {
+ "text": {
+ "zh_CN": "垂直对齐方式"
+ }
+ },
+ "cols": 12,
+ "rules": [],
+ "widget": {
+ "component": "SelectConfigurator",
+ "props": {
+ "options": [
+ {
+ "label": "交叉轴的中点对齐",
+ "value": "center"
+ },
+ {
+ "label": "交叉轴的起点对齐",
+ "value": "flex-start"
+ },
+ {
+ "label": "交叉轴的终点对齐",
+ "value": "flex-end"
+ },
+ {
+ "label": "以子元素第一行文字的基线对齐",
+ "value": "baseline"
+ },
+ {
+ "label": "占满容器高度",
+ "value": "stretch"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "events": {
+ "onClick": {
+ "label": {
+ "zh_CN": "点击事件"
+ },
+ "description": {
+ "zh_CN": "点击时触发的回调函数"
+ },
+ "type": "event",
+ "functionInfo": {
+ "params": [],
+ "returns": {}
+ },
+ "defaultValue": ""
+ }
+ },
+ "shortcuts": {
+ "properties": []
+ },
+ "contentMenu": {
+ "actions": []
+ }
+ },
+ "configure": {
+ "loop": true,
+ "isContainer": true,
+ "nestingRule": {
+ "childWhitelist": [],
+ "descendantBlacklist": []
+ }
+ }
+ },
+ "snippet": {
+ "name": {
+ "zh_CN": "弹性容器"
+ },
+ "screenshot": "",
+ "snippetName": "CanvasFlexBox",
+ "icon": "Box",
+ "schema": {
+ "componentName": "CanvasFlexBox",
+ "props": {
+ "flexDirection": "row",
+ "gap": "8px",
+ "padding": "8px"
+ }
+ }
+ }
+}
diff --git a/packages/builtinComponent/src/meta/CanvasRowColContainer.json b/packages/builtinComponent/src/meta/CanvasRowColContainer.json
index 3281b14a0e..4609c76b21 100644
--- a/packages/builtinComponent/src/meta/CanvasRowColContainer.json
+++ b/packages/builtinComponent/src/meta/CanvasRowColContainer.json
@@ -68,21 +68,21 @@
"schema": {
"componentName": "CanvasRowColContainer",
"props": {
- "rowGap": "20px"
+ "rowGap": "16px"
},
"children": [
{
"componentName": "CanvasRow",
"props": {
- "rowGap": "20px",
- "colGap": "20px"
+ "rowGap": "16px",
+ "colGap": "16px"
},
"children": [
{
"componentName": "CanvasCol",
"props": {
- "rowGap": "20px",
- "colGap": "20px",
+ "rowGap": "16px",
+ "colGap": "16px",
"grow": true,
"shrink": true,
"widthType": "auto"
diff --git a/packages/builtinComponent/src/meta/CanvasSection.json b/packages/builtinComponent/src/meta/CanvasSection.json
new file mode 100644
index 0000000000..44c2ffd837
--- /dev/null
+++ b/packages/builtinComponent/src/meta/CanvasSection.json
@@ -0,0 +1,71 @@
+{
+ "component": {
+ "icon": "Box",
+ "name": {
+ "zh_CN": "全宽居中布局"
+ },
+ "component": "CanvasSection",
+ "schema": {
+ "slots": {},
+ "properties": [
+ {
+ "label": {
+ "zh_CN": "基础信息"
+ },
+ "description": {
+ "zh_CN": "基础信息"
+ },
+ "collapse": {
+ "number": 6,
+ "text": {
+ "zh_CN": "显示更多"
+ }
+ },
+ "content": []
+ }
+ ],
+ "events": {
+ "onClick": {
+ "label": {
+ "zh_CN": "点击事件"
+ },
+ "description": {
+ "zh_CN": "点击时触发的回调函数"
+ },
+ "type": "event",
+ "functionInfo": {
+ "params": [],
+ "returns": {}
+ },
+ "defaultValue": ""
+ }
+ },
+ "shortcuts": {
+ "properties": []
+ },
+ "contentMenu": {
+ "actions": []
+ }
+ },
+ "configure": {
+ "loop": true,
+ "isContainer": true,
+ "nestingRule": {
+ "childWhitelist": [],
+ "descendantBlacklist": []
+ }
+ }
+ },
+ "snippet": {
+ "name": {
+ "zh_CN": "全宽居中布局"
+ },
+ "screenshot": "",
+ "snippetName": "CanvasSection",
+ "icon": "Box",
+ "schema": {
+ "componentName": "CanvasSection",
+ "props": {}
+ }
+ }
+}
diff --git a/packages/builtinComponent/src/meta/index.js b/packages/builtinComponent/src/meta/index.js
index 2480e70515..7521af69aa 100644
--- a/packages/builtinComponent/src/meta/index.js
+++ b/packages/builtinComponent/src/meta/index.js
@@ -1,16 +1,24 @@
import CanvasCol from './CanvasCol.json'
import CanvasRow from './CanvasRow.json'
import CanvasRowColContainer from './CanvasRowColContainer.json'
+import CanvasFlexBox from './CanvasFlexBox.json'
+import CanvasSection from './CanvasSection.json'
export default {
- components: [CanvasCol.component, CanvasRow.component, CanvasRowColContainer.component],
+ components: [
+ CanvasCol.component,
+ CanvasRow.component,
+ CanvasRowColContainer.component,
+ CanvasFlexBox.component,
+ CanvasSection.component
+ ],
snippets: [
{
group: 'layout',
label: {
zh_CN: '布局与容器'
},
- children: [CanvasRowColContainer.snippet]
+ children: [CanvasRowColContainer.snippet, CanvasFlexBox.snippet, CanvasSection.snippet]
}
]
}
diff --git a/packages/canvas/DesignCanvas/README.md b/packages/canvas/DesignCanvas/README.md
new file mode 100644
index 0000000000..044efb6936
--- /dev/null
+++ b/packages/canvas/DesignCanvas/README.md
@@ -0,0 +1,195 @@
+# schema 元服务相关API(Experimental)
+
+## 直接修改 schema 引用 & 调用通知更新
+
+使用示例:
+
+```javascript
+import { useCanvas, useMessage } from '@opentiny/tiny-engine-meta-register'
+
+const pageSchema = useCanvas().getPageSchema()
+
+pageSchema.css = "xxxx"
+
+useMessage().publish({ topic: 'schemaChange' })
+```
+
+注意:直接修改 schema 引用当前不能涉及到节点的增加、删除,不然会节点树 nodesMap 无法更新,导致画布无法选中新增的组件。
+
+
+> 注意:以下所有 API 皆为 Experimental 实验 API,请不要用在生产阶段
+
+## 导入/导出 schema
+
+> 这里的导入导出仅包含页面级别,不包含应用级别 schema
+
+**导入 schema:**
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+const data = { /*页面/区块 schema*/ }
+
+useCanvas().importSchema(data)
+```
+
+**导出 schema:**
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+useCanvas().exportSchema()
+```
+
+## 页面 schema相关操作
+
+> 主要描述对页面 schema 的增删查改操作
+
+### 获取当前页面/区块 schema:
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+useCanvas().getPageSchema()
+```
+
+### 获取当前选中节点 schema:
+
+```javascript
+import { useProperties } from '@opentiny/tiny-engine-meta-register'
+
+const schema = useProperties().getSchema()
+```
+
+### 根据 id 查询对应的 节点schema(schema 片段)
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+const schema = useCanvas().getNode('453254', false)
+```
+
+类型:
+
+```typescript
+/**
+ * 根据节点 id 获取 schema 片段
+ * id: schema id
+ * parent: 是否需要同时获取 parent 节点
+ */
+type getNode = (id: string, parent: boolean) => INode | { node: INode; parent: INode }
+```
+
+### 节点操作
+
+#### 插入节点
+
+使用示例:
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+useCanvas().operateNode({
+ type: 'insert',
+ parentId: '432423',
+ newNodeData: { componentName: 'div', props: {}, children: [] },
+ position: 'after',
+ referTargetNodeId: '898432'
+})
+```
+
+类型:
+
+```typescript
+interface IInsertOperation {
+ // 操作类型为 insert
+ type: 'insert';
+ // 要插入的节点的 父节点 id
+ parentId: string;
+ // 新节点数据
+ newNodeData: INode;
+ // 相对节点的 id,比如我们想要插入父节点 id 中 第 5 个 children 的后面,或者前面
+ referTargetNodeId: string;
+ // 相对节点的位置
+ position: 'after' | 'before';
+}
+```
+
+#### 删除节点
+
+使用示例:
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+useCanvas().operateNode({
+ type: 'delete',
+ id: '432423'
+})
+```
+
+类型:
+
+```typescript
+interface IDeleteOperation {
+ type: 'delete';
+ id: string;
+}
+```
+
+#### 修改节点 props
+
+使用示例:
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+useCanvas().operateNode({
+ type: 'changeProps',
+ id: '432423',
+ value: { text: 'TinyEngine' },
+ option: { overwrite: false }
+})
+```
+
+类型:
+
+```typescript
+interface IChangePropsOperation {
+ type: 'changeProps';
+ // 节点 id
+ id: string;
+ // 新的 props 值
+ value: Record;
+ // 操作类型:是否覆写
+ option: { overwrite: boolean; }
+}
+```
+
+#### 更新节点属性
+
+使用示例:
+
+```javascript
+import { useCanvas } from '@opentiny/tiny-engine-meta-register'
+
+useCanvas().operateNode({
+ type: 'updateAttributes',
+ id: '432423',
+ value: { props: { ... }, loop: { ... } },
+ overwrite: boolean
+})
+```
+
+类型:
+
+```typescript
+interface IUpdateAttrOperation {
+ type: 'updateAttributes';
+ id: string;
+ // 对节点的属性修改
+ value: Record;
+ // 是否是直接覆盖
+ overwrite: boolean;
+}
+```
diff --git a/packages/canvas/DesignCanvas/src/DesignCanvas.vue b/packages/canvas/DesignCanvas/src/DesignCanvas.vue
index e4bbe4708b..3aabe63e83 100644
--- a/packages/canvas/DesignCanvas/src/DesignCanvas.vue
+++ b/packages/canvas/DesignCanvas/src/DesignCanvas.vue
@@ -36,7 +36,8 @@ import {
getOptions,
getMetaApi,
META_SERVICE,
- META_APP
+ META_APP,
+ useNotify
} from '@opentiny/tiny-engine-meta-register'
import { constants } from '@opentiny/tiny-engine-utils'
import * as ast from '@opentiny/tiny-engine-common/js/ast'
@@ -72,7 +73,7 @@ export default {
const removeNode = (node) => {
const { pageState } = useCanvas()
- footData.value = useCanvas().canvasApi.value.getNodePath(node?.id)
+ footData.value = useCanvas().getNodePath(node?.id)
pageState.currentSchema = {}
pageState.properties = null
}
@@ -102,10 +103,11 @@ export default {
// 1. 页面或区块状态是未保存状态(尝试编辑)
// 2. 页面刷新或第一次进入页面(含从别的页面或区块切换到别的页面或区块)
// 3. 页面上已经有弹窗,不允许重复弹窗
+ // 4. 当前历史堆栈为0,且当前未保存状态和上一次未保存状态不一致,不重复弹窗
const showConfirm = !isSaved || pageSchema !== oldPageSchema
- if (!showConfirm || showModal) {
+ if (!showConfirm || showModal || (useHistory().historyState?.index === 0 && isSaved !== oldIsSaved)) {
return
}
@@ -129,7 +131,6 @@ export default {
useModal().confirm({
title: '提示',
message: renderMsg,
- status: 'info',
exec: callback,
cancel: callback,
hide: () => {
@@ -139,19 +140,21 @@ export default {
}
)
- const nodeSelected = (node, parent, type) => {
+ const nodeSelected = (node, parent, type, id) => {
const { toolbars } = useLayout().layoutState
if (type !== 'clickTree') {
useLayout().closePlugin()
}
- const { getSchema, getNodePath } = useCanvas().canvasApi.value
+ const { getSchema, getNodePath } = useCanvas()
+ const schemaItem = useCanvas().getNodeById(id)
+
+ const pageSchema = getSchema()
- const schema = getSchema()
// 如果选中的节点是画布,就设置成默认选中最外层schema
- useProperties().getProps(node || schema, parent)
- useCanvas().setCurrentSchema(node || schema)
- footData.value = getNodePath(node?.id)
+ useProperties().getProps(schemaItem || pageSchema, parent)
+ useCanvas().setCurrentSchema(schemaItem || pageSchema)
+ footData.value = getNodePath(schemaItem?.id)
toolbars.visiblePopover = false
}
@@ -218,13 +221,16 @@ export default {
// 需要在canvas/render或内置组件里使用的方法
getMaterial: useMaterial().getMaterial,
addHistory: useHistory().addHistory,
- registerBlock: useMaterial().registerBlock,
request: getMetaApi(META_SERVICE.Http).getHttp(),
getPageById: getMetaApi(META_APP.AppManage).getPageById,
getPageAncestors: usePage().getAncestors,
getBaseInfo: () => getMetaApi(META_SERVICE.GlobalService).getBaseInfo(),
addHistoryDataChangedCallback,
- ast
+ ast,
+ getBlockByName: useMaterial().getBlockByName,
+ useModal,
+ useMessage,
+ useNotify
},
isBlock,
CanvasLayout,
diff --git a/packages/canvas/DesignCanvas/src/api/useCanvas.js b/packages/canvas/DesignCanvas/src/api/useCanvas.js
index 2be05169b7..e025fd487d 100644
--- a/packages/canvas/DesignCanvas/src/api/useCanvas.js
+++ b/packages/canvas/DesignCanvas/src/api/useCanvas.js
@@ -11,11 +11,14 @@
*/
/* eslint-disable no-new-func */
-import { reactive, ref } from 'vue'
-import { constants } from '@opentiny/tiny-engine-utils'
-import { useHistory } from '@opentiny/tiny-engine-meta-register'
+import { reactive, ref, toRaw } from 'vue'
+import * as jsonDiffPatch from 'jsondiffpatch'
+import DiffMatchPatch from 'diff-match-patch'
+import { constants, utils } from '@opentiny/tiny-engine-utils'
+import { useHistory, getMetaApi, useMessage } from '@opentiny/tiny-engine-meta-register'
const { COMPONENT_NAME } = constants
+const { deepClone } = utils
const defaultPageState = {
currentVm: null,
@@ -53,6 +56,7 @@ const defaultSchema = {
const canvasApi = ref({})
const isCanvasApiReady = ref(false)
+const nodesMap = ref(new Map())
const initCanvasApi = (newCanvasApi) => {
canvasApi.value = newCanvasApi
@@ -60,11 +64,83 @@ const initCanvasApi = (newCanvasApi) => {
}
const pageState = reactive({ ...defaultPageState, loading: true })
+const rootSchema = ref([
+ {
+ id: 0,
+ componentName: 'div',
+ props: pageState.pageSchema?.props || {},
+ children: pageState.pageSchema?.children || []
+ }
+])
+
+const generateNodesMap = (nodes, parent) => {
+ nodes.forEach((nodeItem) => {
+ if (!nodeItem.id) {
+ nodeItem.id = utils.guid()
+ }
+
+ nodesMap.value.set(nodeItem.id, {
+ node: nodeItem,
+ parent
+ })
+
+ if (Array.isArray(nodeItem.children) && nodeItem.children.length) {
+ generateNodesMap(nodeItem.children, nodeItem)
+ }
+ })
+}
+
+const jsonDiffPatchInstance = jsonDiffPatch.create({
+ objectHash: function (obj, index) {
+ return obj.fileName || obj.id || `$$index:${index}`
+ },
+ arrays: {
+ detectMove: true,
+ includeValueOnMove: false
+ },
+ textDiff: {
+ diffMatchPatch: DiffMatchPatch,
+ minLength: 60
+ },
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ propertyFilter: function (name, context) {
+ return name.slice(0, 1) !== '$'
+ },
+ cloneDiffValues: false
+})
+
+const { publish } = useMessage()
+
// 重置画布数据
const resetCanvasState = async (state = {}) => {
+ const previousSchema = JSON.parse(JSON.stringify(pageState.pageSchema))
+
Object.assign(pageState, defaultPageState, state)
- await canvasApi.value?.setSchema(pageState.pageSchema)
+ nodesMap.value.clear()
+
+ if (pageState.pageSchema) {
+ if (!pageState.pageSchema.children) {
+ pageState.pageSchema.children = []
+ }
+
+ rootSchema.value = [
+ {
+ id: 0,
+ componentName: 'div',
+ props: pageState.pageSchema.props || {},
+ children: pageState.pageSchema.children
+ }
+ ]
+
+ nodesMap.value.set(0, { node: rootSchema.value, parent: pageState.pageSchema })
+
+ generateNodesMap(pageState.pageSchema.children, pageState.pageSchema)
+ }
+
+ const diffPatch = jsonDiffPatchInstance.diff(previousSchema, pageState.pageSchema)
+
+ publish({ topic: 'schemaImport', data: { current: pageState.pageSchema, previous: previousSchema, diffPatch } })
}
// 页面重置画布数据
@@ -80,11 +156,17 @@ const resetBlockCanvasState = async (state = {}) => {
await resetCanvasState(state)
}
-const getDefaultSchema = (componentName = 'Page', fileName = '') => ({
- ...defaultSchema,
- componentName,
- fileName
-})
+const getDefaultSchema = (componentName = 'Page', fileName = '') => {
+ const DEFAULT_PAGE = getMetaApi('engine.service.page')?.getDefaultPage() || { page_content: { props: {}, css: '' } }
+
+ return {
+ ...defaultSchema,
+ props: DEFAULT_PAGE.page_content?.props || {},
+ css: DEFAULT_PAGE.page_content?.css || '',
+ componentName,
+ fileName
+ }
+}
const setSaved = (flag = false) => {
pageState.isSaved = flag
@@ -97,10 +179,13 @@ const clearCanvas = () => {
const { fileName, componentName } = pageState.pageSchema || {}
resetCanvasState({
- pageSchema: { ...getDefaultSchema(componentName, fileName) }
+ pageSchema: { ...deepClone(getDefaultSchema(componentName, fileName)) }
})
setSaved(false)
+
+ canvasApi.value?.clearSelect?.()
+ canvasApi.value?.updateRect?.()
}
const isBlock = () => pageState.isBlock
@@ -109,12 +194,12 @@ const isBlock = () => pageState.isBlock
const initData = (schema = { ...defaultSchema }, currentPage) => {
if (schema.componentName === COMPONENT_NAME.Block) {
resetBlockCanvasState({
- pageSchema: schema,
+ pageSchema: toRaw(schema),
loading: false
})
} else {
resetPageCanvasState({
- pageSchema: schema,
+ pageSchema: toRaw(schema),
currentPage,
loading: false
})
@@ -145,6 +230,326 @@ const clearCurrentState = () => {
}
const getCurrentPage = () => pageState.currentPage
+const getNodeById = (id) => {
+ return nodesMap.value.get(id)?.node
+}
+
+const getNodeWithParentById = (id) => {
+ return nodesMap.value.get(id)
+}
+
+const delNode = (id) => {
+ nodesMap.value.delete(id)
+}
+
+const clearNodes = () => {
+ nodesMap.value.clear()
+}
+
+const setNode = (schema, parent) => {
+ schema.id = schema.id || utils.guid()
+
+ nodesMap.value.set(schema.id, { node: schema, parent })
+}
+
+const getNode = (id, parent) => {
+ return parent ? nodesMap.value.get(id) : nodesMap.value.get(id)?.node
+}
+
+const operationTypeMap = {
+ insert: (operation) => {
+ const { parentId, newNodeData, position, referTargetNodeId } = operation
+ const parentNode = getNode(parentId) || pageState.pageSchema
+
+ if (!parentNode) {
+ return {}
+ }
+
+ parentNode.children = parentNode.children || []
+
+ if (!newNodeData.id) {
+ newNodeData.id = utils.guid()
+ }
+
+ if (referTargetNodeId) {
+ const referenceNode = getNode(referTargetNodeId)
+ let index = parentNode.children.indexOf(referenceNode)
+
+ if (index === -1) {
+ index = 0
+ }
+
+ index = position === 'before' ? index : index + 1
+
+ parentNode.children.splice(index, 0, newNodeData)
+
+ setNode(newNodeData, parentNode)
+
+ // 递归构建 nodeMap
+ if (Array.isArray(newNodeData?.children) && newNodeData.children.length) {
+ const newNode = getNode(newNodeData.id)
+ generateNodesMap(newNodeData.children, newNode)
+ }
+
+ return {
+ current: newNodeData,
+ previous: undefined
+ }
+ }
+
+ if (position === 'before') {
+ parentNode.children.unshift(newNodeData)
+ } else {
+ parentNode.children.push(newNodeData)
+ }
+
+ setNode(newNodeData, parentNode)
+
+ // 递归构建 nodeMap
+ if (Array.isArray(newNodeData?.children) && newNodeData.children.length) {
+ const newNode = getNode(newNodeData.id)
+ generateNodesMap(newNodeData.children, newNode)
+ }
+
+ return {
+ current: newNodeData,
+ previous: undefined
+ }
+ },
+ delete: (operation) => {
+ const { id } = operation
+ const targetNode = getNode(id, true)
+
+ if (!targetNode) {
+ return
+ }
+
+ const { parent, node } = targetNode
+
+ const index = parent.children.indexOf(node)
+
+ if (index > -1) {
+ parent.children.splice(index, 1)
+ nodesMap.value.delete(node.id)
+ }
+
+ let children = [...(node.children || [])]
+
+ // 递归清理 nodesMap
+ while (children?.length) {
+ const len = children.length
+ children.forEach((item) => {
+ const nodeItem = getNode(item.id)
+ nodesMap.value.delete(item.id)
+
+ if (Array.isArray(nodeItem.children) && nodeItem.children.length) {
+ children.push(...nodeItem.children)
+ }
+ })
+
+ children = children.slice(len)
+ }
+
+ return {
+ current: undefined,
+ previous: node
+ }
+ },
+ changeProps: (operation) => {
+ const { id, value, option: changeOption } = operation
+ let { node } = getNode(id, true) || {}
+ const previous = deepClone(node)
+ const { overwrite = false } = changeOption || {}
+
+ if (!node) {
+ node = pageState.pageSchema
+ }
+
+ if (!node.props) {
+ node.props = {}
+ }
+
+ if (overwrite) {
+ node.props = value.props
+ } else {
+ Object.assign(node.props, value?.props || {})
+ }
+
+ return {
+ current: node,
+ previous
+ }
+ },
+ updateAttributes: (operation) => {
+ const { id, value, overwrite } = operation
+ const { id: _id, children, ...restAttr } = value
+ const node = getNode(id)
+
+ // 其他属性直接浅 merge
+ Object.assign(node, restAttr)
+
+ // 配置了 overwrite,需要将没有传入的属性进行删除(不包括 children)
+ if (overwrite) {
+ const { id: _unUsedId, children: _unUsedChildren, ...restOrigin } = node
+ const originKeys = Object.keys(restOrigin)
+ const newKeysSet = new Set(Object.keys(restAttr))
+
+ originKeys.forEach((key) => {
+ if (!newKeysSet.has(key)) {
+ delete node[key]
+ }
+ })
+ }
+
+ if (!Array.isArray(children)) {
+ // 非数组类型的 children,比如是直接的字符串作为 children
+ if (children || typeof children === 'string') {
+ node.children = children
+ }
+
+ return
+ }
+
+ const newChildren = children.map((item) => {
+ if (!item.id) {
+ item.id = utils.guid()
+ }
+
+ return item
+ })
+ // 传了 children 进来,需要找出来被删除的、新增的,剩下的是修改的。
+ const originChildrenIds = (node.children || []).filter(({ id }) => id).map(({ id }) => id)
+ const originChildrenSet = new Set(originChildrenIds)
+
+ const newChildrenSet = new Set(newChildren.map(({ id }) => id))
+ // 被删除的项
+ const deletedIds = originChildrenIds.filter((id) => !newChildrenSet.has(id))
+ const deletedIdsSet = new Set(deletedIds)
+
+ for (const id of deletedIds) {
+ operationTypeMap.delete({ id })
+ }
+
+ // 筛选出来新增的和修改的
+ const changedChildren = newChildren.filter(({ id }) => !deletedIdsSet.has(id))
+
+ changedChildren.forEach((childItem, index) => {
+ // 新增
+ if (!originChildrenSet.has(childItem.id)) {
+ operationTypeMap.insert({
+ parentId: id,
+ newNodeData: childItem,
+ position: 'after',
+ referTargetNodeId: changedChildren?.[index]?.id
+ })
+ return
+ }
+
+ // 直接改引用插入进来,但是没有构建对应的 Map,需要构建Map
+ if (!getNode(childItem.id)) {
+ setNode(childItem, node)
+
+ // 递归构建 nodeMap
+ if (Array.isArray(childItem?.children) && childItem.children.length) {
+ const newNode = getNode(childItem.id)
+ generateNodesMap(childItem.children, newNode)
+ }
+ }
+
+ // 递归修改
+ operationTypeMap.updateAttributes({ id: childItem.id, value: childItem })
+ })
+ }
+}
+
+const lastUpdateType = ref('')
+
+/**
+ * @experimental
+ * @param {*} operation
+ * @returns
+ */
+const operateNode = async (operation) => {
+ if (!operationTypeMap[operation.type]) {
+ return
+ }
+
+ operationTypeMap[operation.type](operation)
+
+ lastUpdateType.value = operation.type
+
+ publish({ topic: 'schemaChange', data: { operation } })
+
+ if (operation.type !== 'insert') {
+ // 这里 setTimeout 延时是需要等画布更新渲染完成,然后再更新,才能得到正确的组件 offset
+ setTimeout(() => {
+ canvasApi.value.updateRect?.()
+ }, 0)
+ }
+}
+
+// 获取传入的 schema 与最新 schema 的 diff
+const getSchemaDiff = (schema) => {
+ return jsonDiffPatchInstance.diff(schema, pageState.pageSchema)
+}
+
+const patchLatestSchema = (schema) => {
+ // 这里 pageSchema 需要 deepClone,不然 patch 的时候,会 patch 成同一个引用,造成画布无法更新
+ const diff = jsonDiffPatchInstance.diff(schema, deepClone(pageState.pageSchema))
+
+ if (diff) {
+ jsonDiffPatchInstance.patch(schema, diff)
+ }
+}
+
+const importSchema = (data) => {
+ let importData = data
+
+ if (typeof data === 'string') {
+ try {
+ importData = JSON.parse(data)
+ } catch (error) {
+ // eslint-disable-next-line no-console
+ console.error('[useCanvas.importSchema] Invalid data')
+ }
+ }
+
+ // JSON 格式校验
+ resetCanvasState({
+ pageSchema: importData
+ })
+
+ canvasApi.value?.clearSelect?.()
+}
+
+const exportSchema = () => {
+ return JSON.stringify(pageState.pageSchema)
+}
+
+const getSchema = () => {
+ return pageState.pageSchema || {}
+}
+
+const getNodePath = (id, nodes = []) => {
+ const { parent, node } = getNodeWithParentById(id) || {}
+
+ node && nodes.unshift({ name: node.componentName, node: id })
+
+ if (parent) {
+ parent && getNodePath(parent.id, nodes)
+ } else {
+ nodes.unshift({ name: 'BODY', node: id })
+ }
+
+ return nodes
+}
+
+const updateSchema = (data) => {
+ Object.assign(pageState.pageSchema, data)
+
+ publish({ topic: 'schemaChange', data: {} })
+}
+
export default function () {
return {
pageState,
@@ -163,6 +568,21 @@ export default function () {
getCurrentPage,
initCanvasApi,
canvasApi,
- isCanvasApiReady
+ isCanvasApiReady,
+ getNodeById,
+ getNodeWithParentById,
+ delNode,
+ clearNodes,
+ setNode,
+ getNode,
+ operateNode,
+ lastUpdateType,
+ getSchemaDiff,
+ patchLatestSchema,
+ importSchema,
+ exportSchema,
+ getSchema,
+ getNodePath,
+ updateSchema
}
}
diff --git a/packages/canvas/DesignCanvas/src/importMap.js b/packages/canvas/DesignCanvas/src/importMap.js
index 813fdb7dc1..9d8d4c536b 100644
--- a/packages/canvas/DesignCanvas/src/importMap.js
+++ b/packages/canvas/DesignCanvas/src/importMap.js
@@ -14,9 +14,14 @@ export function getImportMapData(overrideVersions = {}) {
const blockRequire = {
imports: {
'@opentiny/vue': `${VITE_CDN_DOMAIN}/@opentiny/vue@${importMapVersions.tinyVue}/runtime/tiny-vue.mjs`,
- '@opentiny/vue-icon': `${VITE_CDN_DOMAIN}/@opentiny/vue@${importMapVersions.tinyVue}/runtime/tiny-vue-icon.mjs`
+ '@opentiny/vue-icon': `${VITE_CDN_DOMAIN}/@opentiny/vue@${importMapVersions.tinyVue}/runtime/tiny-vue-icon.mjs`,
+ 'element-plus': `${VITE_CDN_DOMAIN}/element-plus@2.4.2/dist/index.full.mjs`,
+ '@opentiny/tiny-engine-builtin-component': `${VITE_CDN_DOMAIN}/@opentiny/tiny-engine-builtin-component@^2.0.0/dist/index.mjs`
},
- importStyles: [`${VITE_CDN_DOMAIN}/@opentiny/vue-theme@${importMapVersions.tinyVue}/index.css`]
+ importStyles: [
+ `${VITE_CDN_DOMAIN}/@opentiny/vue-theme@${importMapVersions.tinyVue}/index.css`,
+ `${VITE_CDN_DOMAIN}/element-plus@2.4.2/dist/index.css`
+ ]
}
// 以下内容由于物料协议不支持声明子依赖而@opentiny/vue需要依赖所以需要补充
diff --git a/packages/canvas/container/src/CanvasContainer.vue b/packages/canvas/container/src/CanvasContainer.vue
index 74cd1c21b6..39ede9acfc 100644
--- a/packages/canvas/container/src/CanvasContainer.vue
+++ b/packages/canvas/container/src/CanvasContainer.vue
@@ -29,9 +29,9 @@
+
+
diff --git a/packages/canvas/render/src/BlockLoading.vue b/packages/canvas/render/src/BlockLoading.vue
new file mode 100644
index 0000000000..b74bebdc27
--- /dev/null
+++ b/packages/canvas/render/src/BlockLoading.vue
@@ -0,0 +1,14 @@
+
+ 区块 {{ name }} 加载中
+
+
+
diff --git a/packages/canvas/render/src/RenderMain.ts b/packages/canvas/render/src/RenderMain.ts
index ff6a4e303f..0f401599b7 100644
--- a/packages/canvas/render/src/RenderMain.ts
+++ b/packages/canvas/render/src/RenderMain.ts
@@ -11,27 +11,22 @@
*/
import { provide, watch, defineComponent, PropType, ref, inject, onUnmounted, h, Ref } from 'vue'
-
-import { useBroadcastChannel } from '@vueuse/core'
-import { constants } from '@opentiny/tiny-engine-utils'
-
import { getDesignMode, setDesignMode, setController, useCustomRenderer, getController } from './canvas-function'
-import { setConfigure } from './material-function'
+import { removeBlockCompsCache, setConfigure } from './material-function'
import { useUtils, useBridge, useDataSourceMap, useGlobalState } from './application-function'
import { IPageSchema, useContext, usePageContext, useSchema } from './page-block-function'
import { api, setCurrentApi } from './canvas-function/canvas-api'
import { getPageAncestors } from './material-function/page-getter'
import CanvasEmpty from './canvas-function/CanvasEmpty.vue'
import { setCurrentPage } from './canvas-function/page-switcher'
-
-const { BROADCAST_CHANNEL } = constants
+import { useThrottleFn } from '@vueuse/core'
// global-context singleton
const { context: globalContext, setContext: setGlobalContext } = useContext()
-const { refreshKey, utils, getUtils, setUtils, updateUtils, deleteUtils } = useUtils(globalContext)
-const { bridge, setBridge, getBridge } = useBridge()
+const { refreshKey, utils, getUtils, setUtils } = useUtils(globalContext)
+const { bridge } = useBridge()
const { getDataSourceMap, setDataSourceMap } = useDataSourceMap()
-const { getGlobalState, setGlobalState, stores } = useGlobalState()
+const { setGlobalState, stores } = useGlobalState()
const updateGlobalContext = () => {
const context = {
utils,
@@ -46,20 +41,12 @@ const updateGlobalContext = () => {
setGlobalContext(context, true)
}
updateGlobalContext()
-const activePageContext = usePageContext()
+export const activePageContext = usePageContext()
const {
schema: activeSchema,
- getSchema,
setSchema,
- getState,
- setState,
- deleteState,
- getProps,
- setProps,
- getMethods,
- setMethods,
- setPagecss
+ setPageCss
} = useSchema(activePageContext, {
utils,
bridge,
@@ -67,44 +54,31 @@ const {
getDataSourceMap
})
const { getRenderer, setRenderer } = useCustomRenderer()
-const getNode = (id, parent) => (id ? activePageContext.getNode(id, parent) : activeSchema)
-const { getContext, getRoot, setNode, setCondition, getCondition, getConditions } = activePageContext
+const { setCondition } = activePageContext
+const updateCanvas = () => {
+ refreshKey.value++
+}
setCurrentApi({
getUtils,
- setUtils,
- updateUtils,
- deleteUtils,
- getBridge,
- setBridge,
- getMethods,
- setMethods,
setController,
setConfigure,
- getSchema,
- setSchema,
- getState,
- deleteState,
- setState,
- getProps,
- setProps,
- getContext,
- getNode,
- getRoot,
- setPagecss,
setCondition,
- getCondition,
- getConditions,
- getGlobalState,
- getDataSourceMap,
- setDataSourceMap,
- setGlobalState,
- setNode,
getRenderer,
setRenderer,
getDesignMode,
- setDesignMode
+ setDesignMode,
+ removeBlockCompsCache,
+ updateCanvas
})
+const throttleUpdateSchema = useThrottleFn(
+ () => {
+ window.host.patchLatestSchema(activeSchema)
+ },
+ 100,
+ true
+)
+
export default defineComponent({
props: {
entry: {
@@ -162,18 +136,83 @@ export default defineComponent({
onUnmounted(() => {
cancel()
})
+
+ window.host.subscribe({
+ topic: 'schemaChange',
+ subscriber: 'canvasRenderer',
+ callback: throttleUpdateSchema
+ })
+
+ window.host.subscribe({
+ topic: 'schemaImport',
+ subscriber: 'canvasRenderer',
+ callback: () => {
+ setSchema(window.host.getSchema())
+ }
+ })
+
+ watch(
+ () => activeSchema.css,
+ (value) => {
+ setPageCss(value)
+ }
+ )
+
+ const utilsWatchCanceler = window.host.watch(
+ () => window.host.appSchema?.utils,
+ (data) => {
+ setUtils(data)
+ },
+ {
+ immediate: true,
+ deep: true
+ }
+ )
+
+ const dataSourceWatchCanceler = window.host.watch(
+ () => window.host.appSchema?.dataSource,
+ (data) => {
+ setDataSourceMap(data)
+ },
+ {
+ immediate: true,
+ deep: true
+ }
+ )
+
+ const globalStateWatchCanceler = window.host.watch(
+ () => window.host.appSchema?.globalState,
+ (data) => {
+ setGlobalState(data)
+ },
+ {
+ immediate: true,
+ deep: true
+ }
+ )
+
+ onUnmounted(() => {
+ window.host.unsubscribe({
+ topic: 'schemaChange',
+ subscriber: 'canvasRenderer'
+ })
+
+ window.host.unsubscribe({
+ topic: 'schemaImport',
+ subscriber: 'canvasRenderer'
+ })
+
+ utilsWatchCanceler()
+ dataSourceWatchCanceler()
+ globalStateWatchCanceler()
+ })
}
let schema = activeSchema
let setCurrentSchema
- let setCurrentMethod = setMethods
if (pageContext.pageId && !props.active && !props.entry) {
// 注意顶层使用activeSchema和对应的api
- const {
- schema: inActiveSchema,
- setSchema: setInactiveSchema,
- setMethods: setInactiveMethods
- } = useSchema(pageContext, {
+ const { schema: inActiveSchema, setSchema: setInactiveSchema } = useSchema(pageContext, {
utils,
bridge,
stores,
@@ -181,30 +220,10 @@ export default defineComponent({
})
schema = inActiveSchema
setCurrentSchema = setInactiveSchema
- setCurrentMethod = setInactiveMethods
}
provide('rootSchema', schema)
- const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.SchemaLength })
- watch(
- () => schema?.children?.length,
- (length) => {
- post(length)
- }
- )
-
- // 这里监听schema.methods,为了保证methods上下文环境始终为最新
- watch(
- () => schema.methods,
- (value) => {
- setCurrentMethod(value, true)
- },
- {
- deep: true
- }
- )
-
if (!props.entry) {
watch(
[() => props.active, () => props.renderSchema],
diff --git a/packages/canvas/render/src/application-function/bridge.ts b/packages/canvas/render/src/application-function/bridge.ts
index 0acc6651c2..95ae6705b9 100644
--- a/packages/canvas/render/src/application-function/bridge.ts
+++ b/packages/canvas/render/src/application-function/bridge.ts
@@ -1,16 +1,7 @@
-import { reset } from '../data-utils'
-
export function useBridge() {
const bridge = {}
- const setBridge = (data, clear = false) => {
- clear && reset(bridge)
- Object.assign(bridge, data)
- }
- const getBridge = () => bridge
return {
- bridge,
- setBridge,
- getBridge
+ bridge
}
}
diff --git a/packages/canvas/render/src/application-function/global-state.ts b/packages/canvas/render/src/application-function/global-state.ts
index cd0d6810fe..9e8f84ae82 100644
--- a/packages/canvas/render/src/application-function/global-state.ts
+++ b/packages/canvas/render/src/application-function/global-state.ts
@@ -5,9 +5,6 @@ const Func = Function
export function useGlobalState() {
const globalState = ref([])
- const getGlobalState = () => {
- return globalState.value
- }
const setGlobalState = (data = []) => {
globalState.value = data
@@ -28,7 +25,6 @@ export function useGlobalState() {
})
return {
globalState,
- getGlobalState,
setGlobalState,
stores
}
diff --git a/packages/canvas/render/src/application-function/utils.ts b/packages/canvas/render/src/application-function/utils.ts
index c972b456de..6a92d3c2ee 100644
--- a/packages/canvas/render/src/application-function/utils.ts
+++ b/packages/canvas/render/src/application-function/utils.ts
@@ -18,10 +18,20 @@ export function useUtils(context: Record) {
const utils: Record = {}
const getUtils = () => utils
- const setUtils = (data: Array, clear = false, isForceRefresh = false) => {
- if (clear) {
- reset(utils)
+ const setUtils = (data: Array) => {
+ if (!Array.isArray(data)) {
+ return
}
+
+ // 筛选出来已经被删除的 key
+ const newKeys = new Set(data.map(({ name }) => name))
+ const currentKeys = Object.keys(utils)
+ const deletedUtilsKeys = currentKeys.filter((item) => !newKeys.has(item))
+
+ for (const key of deletedUtilsKeys) {
+ delete utils[key]
+ }
+
const utilsCollection = {}
// 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具
data?.forEach((item) => {
@@ -44,31 +54,13 @@ export function useUtils(context: Record) {
})
Object.assign(utils, utilsCollection)
- // 因为工具类并不具有响应式行为,所以需要通过修改key来强制刷新画布
- if (isForceRefresh) {
- refreshKey.value++
- }
- }
-
- const updateUtils = (data: Array) => {
- setUtils(data, false, true)
- }
-
- const deleteUtils = (data: Array) => {
- data?.forEach((item) => {
- if (utils[item.name]) {
- delete utils[item.name]
- }
- })
- setUtils([], false, true)
+ refreshKey.value++
}
return {
refreshKey,
utils,
getUtils,
- setUtils,
- updateUtils,
- deleteUtils
+ setUtils
}
}
diff --git a/packages/canvas/render/src/builtin/CanvasCollection.js b/packages/canvas/render/src/builtin/CanvasCollection.js
index b5cb227629..d5104ee996 100644
--- a/packages/canvas/render/src/builtin/CanvasCollection.js
+++ b/packages/canvas/render/src/builtin/CanvasCollection.js
@@ -11,8 +11,6 @@
*/
import { getController } from '../render'
-import { api } from '../RenderMain'
-import { useModal } from '@opentiny/tiny-engine-meta-register'
const NAME_PREFIX = {
loop: 'loop',
@@ -64,7 +62,6 @@ const removeState = (pageSchema, variableName) => {
}
const setStateWithSourceRef = (pageSchema, variableName, sourceRef, data) => {
- api.setState({ [variableName]: data })
pageSchema.state[variableName] = data
if (sourceRef.value.data?.option?.isSync) {
@@ -104,7 +101,7 @@ const defaultHandlerTemplate = ({ node, sourceRef, schemaId, pageSchema }) => {
}
}
-const generateAssginColumns = (newColumns, oldColumns) => {
+const generateAssignColumns = (newColumns, oldColumns) => {
newColumns.forEach((item) => {
const targetColumn = oldColumns.find((value) => value.field === item.field)
if (targetColumn) {
@@ -114,22 +111,38 @@ const generateAssginColumns = (newColumns, oldColumns) => {
return newColumns
}
-const askShouldImportData = ({ node, sourceRef }) => {
- useModal().confirm({
- message: '检测到表格存在配置的数据,是否需要引入?',
- exec() {
- const sourceColums = sourceRef.value?.data?.columns?.map(({ title, field }) => ({ title, field })) || []
- // 这里需要找到对应列,然后进行列合并
- node.props.columns = generateAssginColumns(sourceColums, node.props.columns)
- },
- cancel() {
- node.props.columns = [...(sourceRef.value.data?.columns || [])]
- }
- })
+const askShouldImportData = ({ node, sourceRef, updateKey }) => {
+ const { publish } = getController().useMessage()
+
+ getController()
+ .useModal()
+ .confirm({
+ message: '检测到表格存在配置的数据,是否需要引入?',
+ exec() {
+ try {
+ const sourceColumns = sourceRef.value?.data?.columns?.map(({ title, field }) => ({ title, field })) || []
+ // 这里需要找到对应列,然后进行列合并
+ node.props.columns = generateAssignColumns(sourceColumns, node.props.columns)
+
+ publish({ topic: 'schemaChange', data: {} })
+ updateKey.value++
+ } catch (error) {
+ getController().useNotify({
+ type: 'error',
+ message: '引入配置数据失败'
+ })
+ }
+ },
+ cancel() {
+ node.props.columns = [...(sourceRef.value.data?.columns || [])]
+
+ publish({ topic: 'schemaChange', data: {} })
+ }
+ })
}
-const updateNodeHandler = ({ node, sourceRef, pageSchema, sourceName, methodName }) => {
- if (!node || !node.props) {
+const updateNodeHandler = ({ node, sourceRef, pageSchema, sourceName, methodName, updateKey }) => {
+ if (!node || !node.props || !sourceName) {
return
}
@@ -137,7 +150,7 @@ const updateNodeHandler = ({ node, sourceRef, pageSchema, sourceName, methodName
delete node?.props?.data
if (node.props.columns.length) {
- askShouldImportData({ node, sourceRef })
+ askShouldImportData({ node, sourceRef, updateKey })
} else {
node.props.columns = [...(sourceRef.value.data?.columns || [])]
}
@@ -176,7 +189,7 @@ this.dataSourceMap.${sourceName}.load().then((res) => {
}
const extraHandlerMap = {
- TinyGrid: ({ node, sourceRef, schemaId, pageSchema }) => {
+ TinyGrid: ({ node, sourceRef, schemaId, pageSchema, updateKey }) => {
const sourceName = sourceRef.value?.name
const methodName = `${NAME_PREFIX.table}${schemaId}`
@@ -185,7 +198,7 @@ const extraHandlerMap = {
value: `{ api: this.${methodName} }`
}
- const updateNode = () => updateNodeHandler({ node, sourceRef, pageSchema, sourceName, methodName })
+ const updateNode = () => updateNodeHandler({ node, sourceRef, pageSchema, sourceName, methodName, updateKey })
const clearBindVar = () => {
// 当数据源组件children字段为空时,及时清空创建的methods
@@ -272,7 +285,7 @@ const extraHandlerMap = {
}
}
-export const getHandler = ({ node, sourceRef, schemaId, pageSchema }) =>
+export const getHandler = ({ node, sourceRef, schemaId, pageSchema, updateKey }) =>
extraHandlerMap[node.componentName]
- ? extraHandlerMap[node.componentName]({ node, sourceRef, schemaId, pageSchema })
+ ? extraHandlerMap[node.componentName]({ node, sourceRef, schemaId, pageSchema, updateKey })
: defaultHandlerTemplate({ node, sourceRef, schemaId, pageSchema })
diff --git a/packages/canvas/render/src/builtin/CanvasCollection.vue b/packages/canvas/render/src/builtin/CanvasCollection.vue
index d033924f34..138ea36026 100644
--- a/packages/canvas/render/src/builtin/CanvasCollection.vue
+++ b/packages/canvas/render/src/builtin/CanvasCollection.vue
@@ -1,17 +1,16 @@
-
+
- {{ source?.name || '未选择数据源' }}
+
diff --git a/packages/canvas/render/src/builtin/builtin.json b/packages/canvas/render/src/builtin/builtin.json
index 8597e0bebe..228e51296b 100644
--- a/packages/canvas/render/src/builtin/builtin.json
+++ b/packages/canvas/render/src/builtin/builtin.json
@@ -83,7 +83,7 @@
"content": [
{
"property": "name",
- "type": "String",
+ "type": "string",
"label": {
"text": {
"zh_CN": "插槽名称"
@@ -97,7 +97,7 @@
},
{
"property": "params",
- "type": "String",
+ "type": "string",
"defaultValue": "",
"label": {
"text": {
@@ -246,7 +246,7 @@
"content": [
{
"property": "condition",
- "type": "Boolean",
+ "type": "boolean",
"defaultValue": true,
"label": {
"text": {
@@ -262,7 +262,7 @@
},
{
"property": "style",
- "type": "String",
+ "type": "string",
"defaultValue": "",
"label": {
"text": {
@@ -278,7 +278,7 @@
},
{
"property": "dataSource",
- "type": "String",
+ "type": "string",
"defaultValue": "",
"bindState": false,
"label": {
@@ -332,7 +332,7 @@
"content": [
{
"property": "text",
- "type": "String",
+ "type": "string",
"defaultValue": "TinyEngine 前端可视化设计器,为设计器开发者提供定制服务,在线构建出自己专属的设计器。",
"label": {
"text": {
@@ -404,7 +404,7 @@
"content": [
{
"property": "name",
- "type": "String",
+ "type": "string",
"defaultValue": "IconDel",
"bindState": true,
"label": {
@@ -474,7 +474,7 @@
"content": [
{
"property": "src",
- "type": "String",
+ "type": "string",
"defaultValue": "",
"bindState": true,
"label": {
@@ -557,6 +557,7 @@
"schema": {
"componentName": "Text",
"props": {
+ "style": "display: inline-block;",
"text": "TinyEngine 前端可视化设计器,为设计器开发者提供定制服务,在线构建出自己专属的设计器。"
}
}
diff --git a/packages/canvas/render/src/canvas-function/custom-renderer.ts b/packages/canvas/render/src/canvas-function/custom-renderer.ts
index 8b7ba8edfe..7b0d4773c7 100644
--- a/packages/canvas/render/src/canvas-function/custom-renderer.ts
+++ b/packages/canvas/render/src/canvas-function/custom-renderer.ts
@@ -5,6 +5,7 @@ import renderer from '../render'
function defaultRenderer(schema, refreshKey, entry, active, isPage = true) {
// 渲染画布增加根节点,与出码和预览保持一致
const rootChildrenSchema = {
+ id: 0,
componentName: 'div',
// 手动添加一个唯一的属性,后续在画布选中此节点时方便处理额外的逻辑。由于没有修改schema,不会影响出码
props: { ...schema.props, 'data-id': 'root-container', 'data-page-active': active },
@@ -24,6 +25,7 @@ function defaultRenderer(schema, refreshKey, entry, active, isPage = true) {
}
return h(
+ // TODO: 这里顶层的 i18n-host 在不支持 webComponent 的区块之后,应该也不需要webComponent 的 i18n provider 了
'tiny-i18n-host',
{
locale: 'zh_CN',
diff --git a/packages/canvas/render/src/data-function/parser.ts b/packages/canvas/render/src/data-function/parser.ts
index 081b33d9ad..51b277091b 100644
--- a/packages/canvas/render/src/data-function/parser.ts
+++ b/packages/canvas/render/src/data-function/parser.ts
@@ -3,7 +3,7 @@ import { transformSync } from '@babel/core'
import i18nHost from '@opentiny/tiny-engine-i18n-host'
import { globalNotify } from '../canvas-function'
-import { collectionMethodsMap, customElements, getComponent, getIcon } from '../material-function'
+import { collectionMethodsMap, getComponent, getIcon } from '../material-function'
import { newFn } from '../data-utils'
import { renderDefault } from '../render'
@@ -61,8 +61,7 @@ const transformJSX = (code) => {
[
babelPluginJSX,
{
- pragma: 'h',
- isCustomElement: (name) => customElements[name]
+ pragma: 'h'
}
]
]
@@ -152,43 +151,39 @@ const parseJSXFunction = (data, _scope, ctx) => {
export const generateFn = (innerFn, context?) => {
return (...args) => {
// 如果有数据源标识,则表格的fetchData返回数据源的静态数据
- const sourceId = collectionMethodsMap[innerFn.realName || innerFn.name]
- if (sourceId) {
- return innerFn.call(context, ...args)
- } else {
- let result = null
-
- // 这里是为了兼容用户写法报错导致画布异常,但无法捕获promise内部的异常
- try {
- result = innerFn.call(context, ...args)
- } catch (error) {
- globalNotify({
- type: 'warning',
- title: `函数:${innerFn.name}执行报错`,
- message: error?.message || `函数:${innerFn.name}执行报错,请检查语法`
- })
- }
-
- // 这里注意如果innerFn返回的是一个promise则需要捕获异常,重新返回默认一条空数据
- if (result.then) {
- result = new Promise((resolve) => {
- result.then(resolve).catch((error) => {
- globalNotify({
- type: 'warning',
- title: '异步函数执行报错',
- message: error?.message || '异步函数执行报错,请检查语法'
- })
- // 这里需要至少返回一条空数据,方便用户使用表格默认插槽
- resolve({
- result: [{}],
- page: { total: 1 }
- })
+
+ let result = null
+
+ // 这里是为了兼容用户写法报错导致画布异常,但无法捕获promise内部的异常
+ try {
+ result = innerFn.call(context, ...args)
+ } catch (error) {
+ globalNotify({
+ type: 'warning',
+ title: `函数:${innerFn.name}执行报错`,
+ message: error?.message || `函数:${innerFn.name}执行报错,请检查语法`
+ })
+ }
+
+ // 这里注意如果innerFn返回的是一个promise则需要捕获异常,重新返回默认一条空数据
+ if (result.then) {
+ result = new Promise((resolve) => {
+ result.then(resolve).catch((error) => {
+ globalNotify({
+ type: 'warning',
+ title: '异步函数执行报错',
+ message: error?.message || '异步函数执行报错,请检查语法'
+ })
+ // 这里需要至少返回一条空数据,方便用户使用表格默认插槽
+ resolve({
+ result: [{}],
+ page: { total: 1 }
})
})
- }
-
- return result
+ })
}
+
+ return result
}
}
const parseJSFunction = (data, _scope, ctx) => {
diff --git a/packages/canvas/render/src/data-utils.ts b/packages/canvas/render/src/data-utils.ts
index 867256da4e..320ac0198f 100644
--- a/packages/canvas/render/src/data-utils.ts
+++ b/packages/canvas/render/src/data-utils.ts
@@ -10,3 +10,10 @@ export const newFn = (...argv) => {
const Fn = Function
return new Fn(...argv)
}
+
+export const getDeletedKeys = (objA, objB) => {
+ const keyA = Object.keys(objA)
+ const keyB = new Set(Object.keys(objB))
+
+ return keyA.filter((item) => !keyB.has(item))
+}
diff --git a/packages/canvas/render/src/lowcode.ts b/packages/canvas/render/src/lowcode.ts
index c3b5779030..de651513d9 100644
--- a/packages/canvas/render/src/lowcode.ts
+++ b/packages/canvas/render/src/lowcode.ts
@@ -14,7 +14,6 @@ import { getCurrentInstance, nextTick, provide, inject } from 'vue'
import { I18nInjectionKey } from 'vue-i18n'
import { api } from './RenderMain'
import { globalNotify } from './canvas-function'
-import { collectionMethodsMap } from './material-function'
import { generateFn } from './data-function'
export const lowcodeWrap = (props, context) => {
@@ -60,13 +59,13 @@ export const lowcodeWrap = (props, context) => {
const wrap = (fn) => {
if (typeof fn === 'function') {
- const fnName = fn.name
- if (fn.toString().includes('return this')) {
+ const fnString = fn.toString()
+
+ if (fnString.includes('return this')) {
return () => global
- } else if (fnName && collectionMethodsMap[fnName.slice(0, -1)]) {
- // 这里区块打包的时候会在方法名称后面多加一个字符串,所以此处需要截取下函数名称
- fn.realName = fnName.slice(0, -1)
- return generateFn(fn)
+ } else if (/this\.dataSourceMap\.[0-9a-zA-Z_]+\.load\(\)/.test(fnString)) {
+ const renderContext = (inject('pageContext') as Ref).value
+ return generateFn(fn, renderContext)
} else if (fn.name === 'setter' || fn.name === 'getter') {
// 这里需要保证在消费区块时,区块中的访问器函数可以正常执行
return (...args) => {
diff --git a/packages/canvas/render/src/material-function/material-getter.ts b/packages/canvas/render/src/material-function/material-getter.ts
index be9da1e82e..95bc1da415 100644
--- a/packages/canvas/render/src/material-function/material-getter.ts
+++ b/packages/canvas/render/src/material-function/material-getter.ts
@@ -1,8 +1,14 @@
-import { h } from 'vue'
-import { isHTMLTag, hyphenate } from '@vue/shared'
+import { h, defineAsyncComponent } from 'vue'
+import { isHTMLTag } from '@vue/shared'
import * as TinyVueIcon from '@opentiny/vue-icon'
-import { utils } from '@opentiny/tiny-engine-utils'
-import { CanvasRow, CanvasCol, CanvasRowColContainer } from '@opentiny/tiny-engine-builtin-component'
+
+import {
+ CanvasRow,
+ CanvasCol,
+ CanvasRowColContainer,
+ CanvasFlexBox,
+ CanvasSection
+} from '@opentiny/tiny-engine-builtin-component'
import {
CanvasBox,
CanvasCollection,
@@ -15,9 +21,8 @@ import {
CanvasRouterLink
} from '../builtin'
import { getController } from '../canvas-function/controller'
-import { generateCollection } from './support-collection'
+import BlockLoadError from '../BlockLoadError.vue'
-export const customElements = {}
export const Mapper = {
Icon: CanvasIcon,
Text: CanvasText,
@@ -27,6 +32,8 @@ export const Mapper = {
slot: CanvasSlot,
Template: CanvasBox,
Img: CanvasImg,
+ CanvasSection,
+ CanvasFlexBox,
CanvasRow,
CanvasCol,
CanvasRowColContainer,
@@ -42,91 +49,62 @@ const getBlock = (name) => {
return window.blocks?.[name]
}
-const { hyphenateRE } = utils
-const getPlainProps = (object: Record = {}) => {
- const { slot, ...rest } = object
- const props = {}
+const blockComponentsBlobUrlMap = new Map()
- if (slot) {
- rest.slot = slot.name || slot
- }
+// TODO: 这里的全局 getter 方法名,可以做成配置化
+const loadBlockComponent = async (name: string) => {
+ try {
+ if (blockComponentsBlobUrlMap.has(name)) {
+ return import(/* @vite-ignore */ blockComponentsBlobUrlMap.get(name))
+ }
- Object.entries(rest).forEach(([key, value]) => {
- let renderKey = key
+ const blocksBlob = (await getController().getBlockByName(name)) as Array<{ blobURL: string; style: string }>
- // html 标签属性会忽略大小写,所以传递包含大写的 props 需要转换为 kebab 形式的 props
- if (!/on[A-Z]/.test(renderKey) && hyphenateRE.test(renderKey)) {
- renderKey = hyphenate(renderKey)
- }
+ for (const [fileName, value] of Object.entries(blocksBlob)) {
+ blockComponentsBlobUrlMap.set(fileName, value.blobURL)
+
+ if (!value.style) {
+ continue
+ }
+
+ // 注册 CSS,以区块为维度
+ const stylesheet = document.querySelector(`#${fileName}`)
- if (['boolean', 'string', 'number'].includes(typeof value)) {
- props[renderKey] = value
- } else {
- // 如果传给webcomponent标签的是对象或者数组需要使用.prop修饰符,转化成h函数就是如下写法
- props[`.${renderKey}`] = value
+ if (stylesheet) {
+ stylesheet.innerHTML = value.style
+ } else {
+ const newStylesheet = document.createElement('style')
+ newStylesheet.innerHTML = value.style
+ newStylesheet.setAttribute('id', fileName)
+ document.head.appendChild(newStylesheet)
+ }
}
- })
- return props
-}
-const generateBlockContent = (schema) => {
- if (schema?.componentName === 'Collection') {
- generateCollection(schema)
+ return import(/* @vite-ignore */ blockComponentsBlobUrlMap.get(name))
+ } catch (error) {
+ // 加载错误提示
+ return h(BlockLoadError, { name })
}
- if (Array.isArray(schema?.children)) {
- schema.children.forEach((item) => {
- generateBlockContent(item)
- })
- }
-}
-const registerBlock = (componentName) => {
- getController()
- .registerBlock?.(componentName)
- .then((res) => {
- const blockSchema = res.content
-
- // 拿到区块数据,建立区块中数据源的映射关系
- generateBlockContent(blockSchema)
-
- // 如果区块的根节点有百分比高度,则需要特殊处理,把高度百分比传递下去,适配大屏应用
- if (/height:\s*?[\d|.]+?%/.test(blockSchema?.props?.style)) {
- const blockDoms = document.querySelectorAll(hyphenate(componentName))
- blockDoms.forEach((item) => {
- item.style.height = '100%'
- })
- }
- })
}
-export const wrapCustomElement = (componentName) => {
- const material = getController().getMaterial(componentName)
+window.loadBlockComponent = loadBlockComponent
- if (!Object.keys(material).length) {
- registerBlock(componentName)
- }
+const getBlockComponent = (name) => {
+ return defineAsyncComponent(() => loadBlockComponent(name))
+}
- customElements[componentName] = {
- name: componentName + '.ce',
- render() {
- return h(
- hyphenate(componentName),
- window.parent.TinyGlobalConfig.dslMode === 'Vue' ? getPlainProps(this.$attrs) : this.$attrs,
- this.$slots.default?.()
- )
- }
- }
+// 移除区块缓存
+export const removeBlockCompsCache = () => {
+ blockComponentsBlobUrlMap.forEach((_, fileName) => {
+ const stylesheet = document.querySelector(`#${fileName}`)
+ stylesheet?.remove?.()
+ })
- return customElements[componentName]
+ blockComponentsBlobUrlMap.clear()
}
export const getIcon = (name) => TinyVueIcon?.[name]?.() || ''
export const getComponent = (name) => {
- return (
- Mapper[name] ||
- getNative(name) ||
- getBlock(name) ||
- customElements[name] ||
- (isHTMLTag(name) ? name : wrapCustomElement(name))
- )
+ return Mapper[name] || getNative(name) || getBlock(name) || (isHTMLTag(name) ? name : getBlockComponent(name))
}
diff --git a/packages/canvas/render/src/page-block-function/context.ts b/packages/canvas/render/src/page-block-function/context.ts
index 38688819e0..3958920e4e 100644
--- a/packages/canvas/render/src/page-block-function/context.ts
+++ b/packages/canvas/render/src/page-block-function/context.ts
@@ -11,7 +11,6 @@
*/
import { shallowReactive } from 'vue'
-import { utils } from '@opentiny/tiny-engine-utils'
export function useContext() {
const context = shallowReactive({})
@@ -29,38 +28,6 @@ export function useContext() {
}
}
-export function useNodes() {
- const nodes = {}
-
- const setNode = (schema, parent) => {
- schema.id = schema.id || utils.guid()
- nodes[schema.id] = { node: schema, parent }
- }
-
- const getNode = (id, parent) => {
- return parent ? nodes[id] : nodes[id].node
- }
-
- const delNode = (id) => delete nodes[id]
-
- const clearNodes = () => {
- Object.keys(nodes).forEach(delNode)
- }
- const getRoot = (id) => {
- const { parent } = getNode(id, true)
-
- return parent?.id ? getRoot(parent.id) : parent
- }
-
- return {
- setNode,
- getNode,
- delNode,
- clearNodes,
- getRoot
- }
-}
-
export function useCondition() {
// 从大纲树控制隐藏
const conditions = shallowReactive({})
@@ -107,13 +74,11 @@ export function useCssScopeId() {
}
export function usePageContext() {
const contextExpose = useContext()
- const nodeExpose = useNodes()
const conditionExpose = useCondition()
const contextParentExpose = usePageContextParent()
const cssCopeIdExpose = useCssScopeId()
return {
...contextExpose,
- ...nodeExpose,
...conditionExpose,
...contextParentExpose,
...cssCopeIdExpose,
diff --git a/packages/canvas/render/src/page-block-function/css.ts b/packages/canvas/render/src/page-block-function/css.ts
index 28ed32ba15..f92bfcb0c6 100644
--- a/packages/canvas/render/src/page-block-function/css.ts
+++ b/packages/canvas/render/src/page-block-function/css.ts
@@ -1,6 +1,6 @@
import { initStyle } from '../material-function/page-getter'
import { getController } from '../render'
-export function setPagecss(css = '', pageId?) {
+export function setPageCss(css = '', pageId?) {
const cssPageId = pageId ?? getController().getBaseInfo().pageId
const key = `data-te-page-${cssPageId}`
initStyle(key, css)
diff --git a/packages/canvas/render/src/page-block-function/props.ts b/packages/canvas/render/src/page-block-function/props.ts
index 8d1986990b..9cc7fcde5e 100644
--- a/packages/canvas/render/src/page-block-function/props.ts
+++ b/packages/canvas/render/src/page-block-function/props.ts
@@ -8,8 +8,6 @@ export function useProps(generateAccessor: ReturnType['ge
Object.assign(props, data)
}
- const getProps = () => props
-
const initProps = (properties = []) => {
const props: Record = {}
const accessorFunctions: Array> = []
@@ -38,7 +36,6 @@ export function useProps(generateAccessor: ReturnType['ge
return {
props,
initProps,
- getProps,
setProps
}
}
diff --git a/packages/canvas/render/src/page-block-function/schema.ts b/packages/canvas/render/src/page-block-function/schema.ts
index f9734194b5..136f0db0b4 100644
--- a/packages/canvas/render/src/page-block-function/schema.ts
+++ b/packages/canvas/render/src/page-block-function/schema.ts
@@ -1,4 +1,4 @@
-import { reactive, watchEffect } from 'vue'
+import { reactive, watchEffect, watch } from 'vue'
import { reset } from '../data-utils'
import { useAccessorMap } from './accessor-map'
import { useState } from './state'
@@ -6,30 +6,50 @@ import { useProps } from './props'
import { useMethods } from './methods'
import { nextTick } from 'vue'
import { globalNotify } from '../canvas-function'
-import { setPagecss } from './css'
+import { setPageCss } from './css'
import type { IPageSchema, ISchemaChildrenItem } from '@opentiny/tiny-engine-dsl-vue'
export { IPageSchema, ISchemaChildrenItem }
export function useSchema(
- { context: globalContext, setContext, getContext, clearNodes, getNode },
+ { context: globalContext, setContext, getContext },
{ utils, bridge, stores, getDataSourceMap }
) {
const schema = reactive>({})
const { generateAccessor, stateAccessorMap, propsAccessorMap, generateStateAccessors } = useAccessorMap(globalContext)
- const { state, getState, setState, deleteState } = useState(schema, {
+ const { state, setState } = useState(schema, {
getContext,
generateStateAccessors
})
- const { props, initProps, getProps, setProps } = useProps(generateAccessor)
+ const { props, initProps, setProps } = useProps(generateAccessor)
const { methods, getMethods, setMethods } = useMethods({
setContext,
getContext
})
- const getSchema = () => schema
+ watch(
+ () => schema.state,
+ (value) => {
+ setState(value)
+ },
+ {
+ deep: true
+ }
+ )
+
+ // 这里监听schema.methods,为了保证methods上下文环境始终为最新
+ watch(
+ () => schema.methods,
+ (value) => {
+ setMethods(value, true)
+ },
+ {
+ deep: true
+ }
+ )
+
const setSchema = async (data: IPageSchema, pageId?: string) => {
const newSchema = JSON.parse(JSON.stringify(data || schema))
reset(schema)
@@ -71,9 +91,9 @@ export function useSchema(
// 这里setState(会触发画布渲染),是因为状态管理里面的变量会用到props、utils、bridge、stores、methods
setState(newSchema.state, true)
- clearNodes()
+
await nextTick()
- setPagecss(data.css, pageId)
+ setPageCss(data.css, pageId)
Object.assign(schema, newSchema)
// 当上下文环境设置完成之后再去处理区块属性访问器的watchEffect
@@ -99,7 +119,6 @@ export function useSchema(
}
return {
schema,
- getSchema,
setSchema,
...{
generateAccessor,
@@ -109,14 +128,11 @@ export function useSchema(
},
...{
state,
- getState,
- setState,
- deleteState
+ setState
},
...{
props,
initProps,
- getProps,
setProps
},
...{
@@ -125,9 +141,8 @@ export function useSchema(
setMethods
},
...{
- getContext,
- getNode
+ getContext
},
- setPagecss
+ setPageCss
}
}
diff --git a/packages/canvas/render/src/page-block-function/state.ts b/packages/canvas/render/src/page-block-function/state.ts
index d967b10512..8256b6ee45 100644
--- a/packages/canvas/render/src/page-block-function/state.ts
+++ b/packages/canvas/render/src/page-block-function/state.ts
@@ -1,20 +1,21 @@
import { shallowReactive } from 'vue'
-import { reset } from '../data-utils'
+import { getDeletedKeys } from '../data-utils'
import { isStateAccessor, parseData } from '../data-function'
export function useState(schema, { getContext, generateStateAccessors }) {
const state = shallowReactive({})
- const getState = () => state
-
- const deleteState = (variable: string) => {
- delete state[variable]
- }
const setState = (data, clear = false) => {
- clear && reset(state)
- if (!schema.state) {
- schema.state = data
+ if (typeof data !== 'object' || data === null) {
+ return
+ }
+
+ const deletedKeys = getDeletedKeys(state, data)
+
+ // 同步删除的 key
+ for (const key of deletedKeys) {
+ delete state[key]
}
Object.assign(state, parseData(data, {}, getContext()) || {})
@@ -35,8 +36,6 @@ export function useState(schema, { getContext, generateStateAccessors }) {
}
return {
state,
- getState,
- setState,
- deleteState
+ setState
}
}
diff --git a/packages/canvas/render/src/render.ts b/packages/canvas/render/src/render.ts
index e6c0afbaff..d07426be3b 100644
--- a/packages/canvas/render/src/render.ts
+++ b/packages/canvas/render/src/render.ts
@@ -10,13 +10,14 @@
*
*/
-import { defineComponent, h, inject, provide, Ref } from 'vue'
+import { defineComponent, h, inject, provide, Ref, Suspense } from 'vue'
import { NODE_UID as DESIGN_UIDKEY, NODE_TAG as DESIGN_TAGKEY, NODE_LOOP as DESIGN_LOOPID } from '../../common'
import { getDesignMode, DESIGN_MODE } from './canvas-function'
import { parseCondition, parseData, parseLoopArgs } from './data-function'
-import { blockSlotDataMap, getComponent, generateCollection, Mapper, configure } from './material-function'
+import { blockSlotDataMap, getComponent, Mapper, configure } from './material-function'
import { getPage } from './material-function/page-getter'
+import BlockLoading from './BlockLoading.vue'
export const renderDefault = (children, scope, parent) =>
children.map?.((child) =>
@@ -34,7 +35,7 @@ const stopEvent = (event) => {
return false
}
-const generateSlotGroup = (children, isCustomElm, schema) => {
+const generateSlotGroup = (children, schema) => {
const slotGroup = {}
children.forEach((child) => {
@@ -42,7 +43,6 @@ const generateSlotGroup = (children, isCustomElm, schema) => {
const slot = child.slot || props?.slot?.name || props?.slot || 'default'
const isNotEmptyTemplate = componentName === 'Template' && children.length
- isCustomElm && (child.props.slot = 'slot') // CE下需要给子节点加上slot标识
slotGroup[slot] = slotGroup[slot] || {
value: [],
params,
@@ -55,9 +55,9 @@ const generateSlotGroup = (children, isCustomElm, schema) => {
return slotGroup
}
-const renderSlot = (children, scope, schema, isCustomElm?) => {
+const renderSlot = (children, scope, schema) => {
if (children.some((a) => a.componentName === 'Template')) {
- const slotGroup = generateSlotGroup(children, isCustomElm, schema)
+ const slotGroup = generateSlotGroup(children, schema)
const slots = {}
Object.keys(slotGroup).forEach((slotName) => {
@@ -150,7 +150,7 @@ const injectPlaceHolder = (componentName, children) => {
return children
}
-const renderGroup = (children, scope, parent, pageContext) => {
+const renderGroup = (children, scope, pageContext) => {
return children.map?.((schema) => {
const { componentName, children, loop, loopArgs, condition, id } = schema
const loopList = parseData(loop, scope, pageContext.context)
@@ -163,8 +163,6 @@ const renderGroup = (children, scope, parent, pageContext) => {
loopArgs
})
- pageContext.setNode(schema, parent)
-
if (pageContext.conditions[id] === false || !parseCondition(condition, mergeScope, pageContext.context)) {
return null
}
@@ -190,20 +188,22 @@ const getChildren = (schema, mergeScope, pageContext) => {
const component = getComponent(componentName)
const isNative = typeof component === 'string'
- const isCustomElm = customElements[componentName]
const isGroup = checkGroup(componentName)
if (Array.isArray(renderChildren)) {
- if (isNative || isCustomElm) {
+ // children 空的场景,不能返回空数组,因为有部分组件会误以为使用了自定义插槽,从而无法渲染默认插槽内容,比如 TinyTree 组件
+ if (!renderChildren.length) {
+ return null
+ }
+
+ if (isNative) {
return renderDefault(renderChildren, mergeScope, schema)
- } else {
- return isGroup
- ? renderGroup(renderChildren, mergeScope, schema, pageContext)
- : renderSlot(renderChildren, mergeScope, schema, isCustomElm)
}
- } else {
- return parseData(renderChildren, mergeScope, pageContext.context)
+ return isGroup
+ ? renderGroup(renderChildren, mergeScope, pageContext)
+ : renderSlot(renderChildren, mergeScope, schema)
}
+ return parseData(renderChildren, mergeScope, pageContext.context)
}
function getRenderPageId(currentPageId, isPageStart) {
const pagePathFromRoot = (inject('page-ancestors') as Ref).value
@@ -239,8 +239,6 @@ export const renderer = defineComponent({
const { scope, schema, parent, ancestors } = this
const { componentName, loop, loopArgs, condition } = schema
const pageContext = this.currentPageContext
- // 处理数据源和表格fetchData的映射关系
- generateCollection(schema)
if (!componentName) {
return parseData(schema, scope, pageContext.context)
@@ -280,18 +278,28 @@ export const renderer = defineComponent({
mergeScope = mergeScope ? { ...mergeScope, ...slotData } : slotData
}
- // 给每个节点设置schema.id,并缓存起来
- pageContext.setNode(schema, parent)
-
if (pageContext.conditions[schema.id] === false || !parseCondition(condition, mergeScope, pageContext.context)) {
return null
}
- return h(
+ const Ele = h(
component,
getBindProps(schema, mergeScope, pageContext.context, pageContext),
getChildren(schema, mergeScope, pageContext)
)
+ // 区块加上 suspense 渲染,就可以在网络延时的时候显示加载中的字样或者动画,优化体验
+ if (schema.componentType === 'Block') {
+ return h(
+ Suspense,
+ {},
+ {
+ default: () => Ele,
+ fallback: () => h(BlockLoading, { name: componentName })
+ }
+ )
+ }
+
+ return Ele
}
return loopList?.length ? loopList.map(renderElement) : renderElement()
diff --git a/packages/canvas/render/src/runner.ts b/packages/canvas/render/src/runner.ts
index a1d033bfd5..25bf039435 100644
--- a/packages/canvas/render/src/runner.ts
+++ b/packages/canvas/render/src/runner.ts
@@ -15,7 +15,6 @@ import { addScript, addStyle, dynamicImportComponents, updateDependencies } from
import TinyI18nHost, { I18nInjectionKey } from '@opentiny/tiny-engine-common/js/i18n'
import Main, { api } from './RenderMain'
import lowcode from './lowcode'
-import { supportUmdBlock } from './supportUmdBlock'
type ITinyI18nHostI18nHost = typeof TinyI18nHost
interface IExtendsTinyI18nHost extends ITinyI18nHostI18nHost {
@@ -33,8 +32,6 @@ const initRenderContext = () => {
window.TinyLowcodeComponent = {}
window.TinyComponentLibs = {}
- supportUmdBlock()
-
document.addEventListener('updateDependencies', updateDependencies)
}
diff --git a/packages/canvas/render/src/supportUmdBlock.ts b/packages/canvas/render/src/supportUmdBlock.ts
deleted file mode 100644
index b761223e10..0000000000
--- a/packages/canvas/render/src/supportUmdBlock.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import * as Vue from 'vue'
-import * as VueI18n from 'vue-i18n'
-import * as TinyWebcomponentCore from '@opentiny/tiny-engine-webcomponent-core'
-import * as TinyVueIcon from '@opentiny/vue-icon'
-import TinyVue from '@opentiny/vue'
-import TinyI18nHost from '@opentiny/tiny-engine-common/js/i18n'
-import { camelize, capitalize } from '@vue/shared'
-import { blockSlotDataMap, getComponent } from './material-function'
-
-declare global {
- interface Window {
- Vue: typeof Vue
- VueI18n: typeof VueI18n
- TinyVue: typeof TinyVue
- TinyVueIcon: typeof TinyVueIcon
- TinyI18nHost: typeof TinyI18nHost
- TinyWebcomponentCore: typeof TinyWebcomponentCore
- }
-}
-// 和 @opentiny/tiny-engine-block-build 打包umd方式相适配
-export function supportUmdBlock() {
- // 不能采用new Proxy代理Vue的方案,在编译后的vue会报错警告,采用一下方案扩展用于注入一些区块加载逻辑
- window.Vue = {
- ...Vue,
- resolveComponent(...args) {
- // 此处先执行vue内部的解析组件的方法,如果可以拿到组件对象则直接返回,反之则去注册区块
- const component = Vue.resolveComponent(args[0])
- if (component && typeof component === 'string') {
- return getComponent(capitalize(camelize(args[0])))
- } else {
- return component
- }
- },
- // renderSlot方法第三个参数是作用域插槽传递的数据,格式{ data: vue.unref(state).componentData }
- renderSlot(...args) {
- // 获取当前vue的实例
- const instance = Vue.getCurrentInstance()
-
- // 获取当前区块名称
- const blockName = instance.attrs.dataTag as string
-
- const [, slotName, slotData] = args
-
- // 如果是作用域插槽,则获取作用域插槽传递过来的参数
- if (slotData) {
- if (blockSlotDataMap[blockName]) {
- blockSlotDataMap[blockName][slotName] = slotData
- } else {
- blockSlotDataMap[blockName] = { [slotName]: slotData }
- }
- }
-
- /**
- * vue源码中的renderSlot会忽略default插槽的名称,所以这里必须手动添加args第三个参数的name值
- * vue源码如右所示:if (name !== 'default') props.name = name; return createVNode('slot', props, fallback && fallback());
- **/
- if (slotName === 'default') {
- args[2] = args[2] || {}
- args[2].name = slotName
- }
-
- return Vue.renderSlot(...args)
- }
- }
-
- window.VueI18n = VueI18n
- window.TinyVue = TinyVue
- window.TinyVueIcon = TinyVueIcon
- window.TinyWebcomponentCore = TinyWebcomponentCore
- window.TinyI18nHost = TinyI18nHost
-}
diff --git a/packages/canvas/render/type.d.ts b/packages/canvas/render/type.d.ts
index c4123ba76a..e146c2ba24 100644
--- a/packages/canvas/render/type.d.ts
+++ b/packages/canvas/render/type.d.ts
@@ -8,5 +8,7 @@ export declare global {
scripts: Array
}
TinyGlobalConfig: Record
+ loadBlockComponent: (blockName: string) => Promise
+ host: any
}
}
diff --git a/packages/common/component/BindI18n.vue b/packages/common/component/BindI18n.vue
index c0f99ce0e3..eeb003de6f 100644
--- a/packages/common/component/BindI18n.vue
+++ b/packages/common/component/BindI18n.vue
@@ -4,31 +4,29 @@
-
-
- {{ item.key }}
- {{ item[currentLang] }}
-
+
-
-
- 创建新的多语言文案
-
- 解除关联
+
+ 解除关联
+
+ 创建新的多语言文案
+
+
@@ -43,9 +41,9 @@
-
-
国际化管理
-
添加并关联
+
+ 国际化管理
+ 添加并关联
@@ -57,15 +55,13 @@ import { useLayout, useTranslate } from '@opentiny/tiny-engine-meta-register'
import { PROP_DATA_TYPE } from '../js/constants'
import { utils } from '@opentiny/tiny-engine-utils'
import { Select, Option, Button, Input } from '@opentiny/vue'
-import { iconPlus } from '@opentiny/vue-icon'
export default {
components: {
TinySelect: Select,
TinyOption: Option,
TinyButton: Button,
- TinyInput: Input,
- IconPlus: iconPlus()
+ TinyInput: Input
},
inheritAttrs: false,
props: {
@@ -169,10 +165,7 @@ export default {
diff --git a/packages/common/component/BlockDeployDialog.vue b/packages/common/component/BlockDeployDialog.vue
index 27b4dcaab8..dd2b911f70 100644
--- a/packages/common/component/BlockDeployDialog.vue
+++ b/packages/common/component/BlockDeployDialog.vue
@@ -1,6 +1,6 @@
@@ -24,7 +25,7 @@
发布成功后保存最新数据
-
+
查看本次发布的改动点
-
+
@@ -71,7 +72,6 @@
-
-
diff --git a/packages/common/component/SelectAll.vue b/packages/common/component/SelectAll.vue
new file mode 100644
index 0000000000..3155c0db65
--- /dev/null
+++ b/packages/common/component/SelectAll.vue
@@ -0,0 +1,42 @@
+
+
+ {{ hiddenLabel ? '' : '全选' }}
+
+
+
+
diff --git a/packages/common/component/SvgButton.vue b/packages/common/component/SvgButton.vue
index 7008a42ba3..85b71d9baf 100644
--- a/packages/common/component/SvgButton.vue
+++ b/packages/common/component/SvgButton.vue
@@ -1,5 +1,5 @@
-
+
@@ -27,6 +27,10 @@ export default {
name: {
type: String,
default: 'add'
+ },
+ hoverBgColor: {
+ type: Boolean,
+ default: true
}
},
emits: ['click'],
@@ -45,17 +49,13 @@ export default {
width: 24px;
height: 24px;
font-size: 16px;
- color: var(--ti-lowcode-component-svg-button-color);
+ color: var(--te-common-icon-secondary);
border: 1px solid transparent;
border-radius: 4px;
display: inline-flex;
justify-content: center;
align-items: center;
cursor: pointer;
- &:hover {
- color: var(--ti-lowcode-component-svg-button-hover-color);
- background-color: var(--ti-lowcode-component-svg-button-hover-bg-color);
- }
&.active {
color: var(--ti-lowcode-component-svg-button-active-color);
background-color: var(--ti-lowcode-component-svg-button-active-bg-color);
@@ -68,4 +68,11 @@ export default {
outline: none;
}
}
+.svg-button-hover {
+ color: var(--te-common-icon-primary);
+ &:hover {
+ color: var(--te-common-icon-primary);
+ background-color: var(--te-common-bg-prompt);
+ }
+}
diff --git a/packages/common/component/index.js b/packages/common/component/index.js
index c547ee4cd5..0d40e5553d 100644
--- a/packages/common/component/index.js
+++ b/packages/common/component/index.js
@@ -35,6 +35,7 @@ export { default as BlockLinkField } from './BlockLinkField.vue'
export { default as BlockLinkEvent } from './BlockLinkEvent.vue'
export { default as BlockDescription } from './BlockDescription.vue'
export { default as PluginBlockList } from './PluginBlockList.vue'
+export { default as SelectAll } from './SelectAll.vue'
export { default as ButtonGroup } from './ButtonGroup.vue'
export { default as CloseIcon } from './CloseIcon.vue'
export { default as LifeCycles } from './LifeCycles.vue'
@@ -44,7 +45,6 @@ export { default as VueMonaco, setGlobalMonacoEditorTheme } from './VueMonaco.vu
export { default as PublicIcon } from './PublicIcon.vue'
export { default as BindI18n } from './BindI18n.vue'
export { default as BlockDeployDialog } from './BlockDeployDialog.vue'
-export { default as ProgressBar } from './ProgressBar.vue'
export { default as SearchEmpty } from './SearchEmpty.vue'
export { default as MetaDescription } from './MetaDescription.vue'
export { default as MetaList } from './MetaList.vue'
diff --git a/packages/common/component/toolbar-built-in/ToolbarBaseButton.vue b/packages/common/component/toolbar-built-in/ToolbarBaseButton.vue
index 3ddfd0c20c..57b6ad041a 100644
--- a/packages/common/component/toolbar-built-in/ToolbarBaseButton.vue
+++ b/packages/common/component/toolbar-built-in/ToolbarBaseButton.vue
@@ -1,15 +1,14 @@
-
-
+
+
+
-
-
- {{ content }}
-
-
+ {{ content }}
+
+
+
@@ -52,20 +45,19 @@ export default {
.svg-wrap {
position: relative;
- .dots {
+
+ .dot {
+ position: absolute;
width: 6px;
height: 6px;
background: var(--ti-lowcode-toolbar-dot-color);
border-radius: 50%;
- display: inline-block;
- position: absolute;
- top: -2px;
- right: -2px;
+ top: -3px;
+ right: 2px;
z-index: 100;
}
-}
-
-.save-title {
- margin: 0 6px;
+ .svg-icon.svg-icon.svg-icon {
+ color: var(--te-common-icon-primary);
+ }
}
diff --git a/packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue b/packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue
index b763a50591..2404f3903b 100644
--- a/packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue
+++ b/packages/common/component/toolbar-built-in/ToolbarBaseIcon.vue
@@ -8,7 +8,7 @@
>
-
+
@@ -17,7 +17,6 @@
diff --git a/packages/configurator/src/html-attributes-configurator/HtmlAttributesConfigurator.vue b/packages/configurator/src/html-attributes-configurator/HtmlAttributesConfigurator.vue
index e503e11cf9..e14ca64723 100644
--- a/packages/configurator/src/html-attributes-configurator/HtmlAttributesConfigurator.vue
+++ b/packages/configurator/src/html-attributes-configurator/HtmlAttributesConfigurator.vue
@@ -48,7 +48,7 @@
+
+
diff --git a/packages/engine-cli/template/designer/src/composable/http/index.js b/packages/engine-cli/template/designer/src/composable/http/index.js
index 26b7edae78..f11538395a 100644
--- a/packages/engine-cli/template/designer/src/composable/http/index.js
+++ b/packages/engine-cli/template/designer/src/composable/http/index.js
@@ -1,36 +1,135 @@
+import { createApp } from 'vue'
import { HttpService } from '@opentiny/tiny-engine'
+import { useBroadcastChannel } from '@vueuse/core'
+import { constants } from '@opentiny/tiny-engine-utils'
+import Login from './Login.vue'
+
+const LOGIN_EXPIRED_CODE = 401
+const { BROADCAST_CHANNEL } = constants
+
+const { post: globalNotify } = useBroadcastChannel({ name: BROADCAST_CHANNEL.Notify })
+
+const procession = {
+ promiseLogin: null,
+ mePromise: {}
+}
+let loginVM = null
+
+const showError = (url, message) => {
+ globalNotify({
+ type: 'error',
+ title: '接口报错',
+ message: `报错接口: ${url} \n报错信息: ${message ?? ''}`
+ })
+}
const preRequest = (config) => {
+ const isDevelopEnv = import.meta.env.MODE?.includes('dev')
+
+ if (isDevelopEnv && config.url.match(/\/generate\//)) {
+ config.baseURL = ''
+ }
+
+ const isVsCodeEnv = window.vscodeBridge
+
+ if (isVsCodeEnv) {
+ config.baseURL = ''
+ }
+
return config
}
const preResponse = (res) => {
+ if (res.data?.error) {
+ showError(res.config?.url, res?.data?.error?.message)
+
+ return Promise.reject(res.data.error)
+ }
+
return res.data?.data
}
+const openLogin = () => {
+ if (!window.lowcode) {
+ const loginDom = document.createElement('div')
+ document.body.appendChild(loginDom)
+ loginVM = createApp(Login).mount(loginDom)
+
+ window.lowcode = {
+ platformCenter: {
+ Session: {
+ rebuiltCallback: function () {
+ loginVM.closeLogin()
+
+ procession.mePromise.resolve('login ok')
+ procession.promiseLogin = null
+ procession.mePromise = {}
+ }
+ }
+ }
+ }
+ }
+
+ return new Promise((resolve, reject) => {
+ if (!procession.promiseLogin) {
+ procession.promiseLogin = loginVM.openLogin(procession, '/api/rebuildSession')
+ procession.promiseLogin.then((response) => {
+ HttpService.apis.request(response.config).then(resolve, reject)
+ })
+ }
+ })
+}
+
const errorResponse = (error) => {
- return Promise.reject(error.message)
+ // 用户信息失效时,弹窗提示登录
+ const { response } = error
+
+ if (response?.status === LOGIN_EXPIRED_CODE) {
+ // vscode 插件环境弹出输入框提示登录
+ if (window.vscodeBridge) {
+ return Promise.resolve(true)
+ }
+
+ // 浏览器环境弹出小窗登录
+ if (response?.headers['x-login-url']) {
+ return openLogin()
+ }
+ }
+
+ showError(error.config?.url, error?.message)
+
+ return response?.data.error ? Promise.reject(response.data.error) : Promise.reject(error.message)
}
const getConfig = (env = import.meta.env) => {
+ const baseURL = env.VITE_ORIGIN
+ // 仅在本地开发时,启用 withCredentials
+ const dev = env.MODE?.includes('dev')
+ // 获取租户 id
+ const getTenant = () => new URLSearchParams(location.search).get('tenant')
+
return {
- baseURL: env.VITE_ORIGIN,
+ baseURL,
+ withCredentials: dev,
headers: {
- 'x-lowcode-mode': env.MODE
+ ...(dev && { 'x-lowcode-mode': 'develop' }),
+ 'x-lowcode-org': getTenant()
}
}
}
-const options = {
- axiosConfig: getConfig(),
- enableMock: false,
- mockData: {},
- interceptors: {
- request: [preRequest],
- response: [[preResponse, errorResponse]]
+const customizeHttpService = () => {
+ const options = {
+ axiosConfig: getConfig(),
+ interceptors: {
+ request: [preRequest],
+ response: [[preResponse, errorResponse]]
+ }
}
-}
-HttpService.apis.setOptions(options)
+ HttpService.apis.setOptions(options)
+
+ return HttpService
+}
-export default HttpService
+export default customizeHttpService()
diff --git a/packages/engine-cli/template/designer/src/configurators/MyInputConfigurator.vue b/packages/engine-cli/template/designer/src/configurators/MyInputConfigurator.vue
index 47a9b11e53..26dbd6be85 100644
--- a/packages/engine-cli/template/designer/src/configurators/MyInputConfigurator.vue
+++ b/packages/engine-cli/template/designer/src/configurators/MyInputConfigurator.vue
@@ -57,7 +57,7 @@ export default {
font-size: 16px;
&:hover {
cursor: pointer;
- color: var(--ti-lowcode-dialog-font-color);
+ color: var(--te-common-text-primary);
}
}
diff --git a/packages/engine-cli/template/designer/src/preview.js b/packages/engine-cli/template/designer/src/preview.js
index 4e120d4c6c..eb7717c66a 100644
--- a/packages/engine-cli/template/designer/src/preview.js
+++ b/packages/engine-cli/template/designer/src/preview.js
@@ -14,6 +14,7 @@ import { initHook, HOOK_NAME, GenerateCodeService, Breadcrumb, Media, Lang } fro
import { initPreview } from '@opentiny/tiny-engine'
import 'virtual:svg-icons-register'
import '@opentiny/tiny-engine-theme'
+import { HttpService } from './composable'
const beforeAppCreate = () => {
initHook(HOOK_NAME.useEnv, import.meta.env)
@@ -23,7 +24,7 @@ initPreview({
registry: {
root: {
id: 'engine.root',
- metas: [GenerateCodeService]
+ metas: [HttpService, GenerateCodeService]
},
config: { id: 'engine.config', theme: 'light' },
toolbars: [Breadcrumb, Media, Lang]
diff --git a/packages/i18n/package.json b/packages/i18n/package.json
index 8dda4c5c03..6b12b45950 100644
--- a/packages/i18n/package.json
+++ b/packages/i18n/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-i18n-host",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"publishConfig": {
"access": "public"
},
diff --git a/packages/layout/package.json b/packages/layout/package.json
index e2827290ad..3e905a9a8c 100644
--- a/packages/layout/package.json
+++ b/packages/layout/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-layout",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"scripts": {
"build": "vite build"
},
diff --git a/packages/layout/src/DesignPlugins.vue b/packages/layout/src/DesignPlugins.vue
index da6240dd6f..27b7c9ee65 100644
--- a/packages/layout/src/DesignPlugins.vue
+++ b/packages/layout/src/DesignPlugins.vue
@@ -227,6 +227,9 @@ export default {
:deep(.tiny-tabs__nav.is-show-active-bar) .tiny-tabs__item {
margin-right: 0;
}
+ :deep(.tiny-tabs.tiny-tabs .tiny-tabs__header .tiny-tabs__nav-wrap-not-separator::after) {
+ background-color: transparent;
+ }
}
}
@@ -303,6 +306,13 @@ export default {
svg {
font-size: 18px;
}
+ .public-icon {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 26px;
+ height: 26px;
+ }
}
}
}
diff --git a/packages/layout/src/DesignSettings.vue b/packages/layout/src/DesignSettings.vue
index 8ad765762c..17e7d4f37e 100644
--- a/packages/layout/src/DesignSettings.vue
+++ b/packages/layout/src/DesignSettings.vue
@@ -2,7 +2,7 @@
-
+
@@ -15,14 +15,15 @@
diff --git a/packages/plugins/state/src/EditorI18nTool.vue b/packages/plugins/state/src/EditorI18nTool.vue
index 28efda959b..757f3ac974 100644
--- a/packages/plugins/state/src/EditorI18nTool.vue
+++ b/packages/plugins/state/src/EditorI18nTool.vue
@@ -28,7 +28,7 @@
-
+
@@ -41,6 +41,8 @@ import { Button, Popover, Tooltip } from '@opentiny/vue'
import { iconClose } from '@opentiny/vue-icon'
import { BindI18n } from '@opentiny/tiny-engine-common'
import { useTranslate } from '@opentiny/tiny-engine-meta-register'
+import { constants } from '@opentiny/tiny-engine-utils'
+const { OPEN_DELAY } = constants
export default {
components: {
@@ -115,7 +117,8 @@ export default {
onClosePopover,
createI18n,
handleChooseI18n,
- handleConfirm
+ handleConfirm,
+ OPEN_DELAY
}
}
}
diff --git a/packages/plugins/state/src/Main.vue b/packages/plugins/state/src/Main.vue
index 8d49ec06eb..94cd4ed340 100644
--- a/packages/plugins/state/src/Main.vue
+++ b/packages/plugins/state/src/Main.vue
@@ -22,10 +22,10 @@
- {{ activeName === STATE.CURRENT_STATE ? '添加变量' : '添加全局变量' }}
+
+
+ {{ activeName === STATE.CURRENT_STATE ? '添加变量' : '添加全局变量' }}
+
{
- const { getSchema } = useCanvas().canvasApi.value
+ const { getSchema } = useCanvas()
if (getSchema()) {
if (updateKey.value !== name && flag.value === OPTION_TYPE.UPDATE) {
@@ -193,7 +191,7 @@ export default {
const confirm = () => {
const { name } = state.createData
- const { setState, setGlobalState } = useCanvas().canvasApi.value
+ const { getSchema, updateSchema } = useCanvas()
if (!name || errorMessage.value) {
notifySaveError('变量名未填写或名称不符合规范,请按照提示修改后重试。')
@@ -216,8 +214,9 @@ export default {
isPanelShow.value = false
setSaved(false)
- // 触发画布渲染
- setState({ [name]: variable })
+ const schema = getSchema()
+ updateSchema({ state: { ...(schema.state || {}), [name]: variable } })
+
useHistory().addHistory()
} else {
const validateResult = validateMonacoEditorData(storeRef.value.getEditor(), 'state字段', { required: true })
@@ -248,7 +247,7 @@ export default {
const { id } = getMetaApi(META_SERVICE.GlobalService).getBaseInfo()
updateGlobalState(id, { global_state: storeList }).then((res) => {
isPanelShow.value = false
- setGlobalState(res.global_state || [])
+ useResource().appSchemaState.globalState = res.global_state || []
})
}
openCommon()
@@ -263,11 +262,13 @@ export default {
}
const remove = (key) => {
- const { deleteState, getSchema } = useCanvas().canvasApi.value
+ const { getSchema, updateSchema } = useCanvas()
delete state.dataSource[key]
- // 删除变量也需要同步触发画布渲染
- deleteState(key)
+
+ const schema = getSchema()
+ let { lifeCycles } = schema
+ const { [key]: deletedKey, ...restState } = schema.state
if (key.startsWith('datasource')) {
const pageSchema = getSchema()
@@ -280,9 +281,11 @@ export default {
*/
const pattern = new RegExp(`([\\s\\n]*\\/\\*\\* ${start} \\*\\/[\\s\\S]*\\/\\*\\* ${end} \\*\\/)`)
- pageSchema.lifeCycles.setup.value = pageSchema.lifeCycles.setup.value.replace(pattern, '')
+ lifeCycles.setup.value = pageSchema.lifeCycles.setup.value.replace(pattern, '')
}
+ updateSchema({ state: restState, lifeCycles })
+
// 如果删除的是当前编辑的状态变量,则需要关闭二级面板
if (state.createData.name === key) {
isPanelShow.value = false
@@ -292,8 +295,7 @@ export default {
}
const setGlobalStateToDataSource = () => {
- const { getGlobalState } = useCanvas().canvasApi.value
- const globalState = getGlobalState()
+ const globalState = useResource().appSchemaState.globalState
if (!globalState) {
state.dataSource = {}
@@ -301,20 +303,19 @@ export default {
return
}
- state.dataSource = getGlobalState().reduce((acc, store) => ({ ...acc, [store.id]: store }), {})
+ state.dataSource = globalState.reduce((acc, store) => ({ ...acc, [store.id]: store }), {})
}
const removeStore = (key) => {
- const storeListt = [...useResource().resState.globalState] || []
- const index = storeListt.findIndex((store) => store.id === key)
- const { setGlobalState } = useCanvas().canvasApi.value
+ const storeList = [...useResource().appSchemaState.globalState] || []
+ const index = storeList.findIndex((store) => store.id === key)
if (index !== -1) {
const { id } = getMetaApi(META_SERVICE.GlobalService).getBaseInfo()
- storeListt.splice(index, 1)
- updateGlobalState(id, { global_state: storeListt }).then((res) => {
- setGlobalState(res.global_state)
+ storeList.splice(index, 1)
+ updateGlobalState(id, { global_state: storeList }).then((res) => {
+ useResource().appSchemaState.globalState = res.global_state || []
setGlobalStateToDataSource()
})
@@ -330,15 +331,14 @@ export default {
}
const initDataSource = (tabsName = activeName.value) => {
- const { getSchema } = useCanvas().canvasApi.value
+ const { getSchema } = useCanvas()
if (tabsName === STATE.GLOBAL_STATE) {
setGlobalStateToDataSource()
} else {
const pageSchema = getSchema() || {}
- pageSchema.state = pageSchema?.state || {}
- state.dataSource = pageSchema.state
+ state.dataSource = pageSchema.state || {}
}
}
@@ -400,11 +400,20 @@ export default {
width: 100%;
.tiny-button {
width: 100%;
- border-color: var(--ti-lowcode-data-source-border-color);
+ border-color: var(--te-common-border-default);
+ &:hover {
+ border-color: var(--te-common-border-hover);
+ }
}
- .icon-plus {
+ .add-btn-icon {
margin-right: 4px;
+ font-size: 16px;
stroke: var(--ti-lowcode-chat-model-button-text);
+ color: var(--te-common-icon-secondary);
+ vertical-align: sub;
+ }
+ .add-btn-text {
+ display: inline-block;
}
}
@@ -421,7 +430,7 @@ export default {
}
.left-filter {
- margin-top: 12px;
+ margin-top: 4px;
padding: 0 10px;
}
@@ -445,6 +454,7 @@ export default {
height: 100%;
border-right: 1px solid var(--ti-lowcode-toolbar-border-color);
background: var(--ti-lowcode-common-component-bg);
+ box-shadow: 6px 0px 3px 0px var(--te-base-box-shadow-rgba-3);
position: absolute;
left: var(--base-left-panel-width);
top: 0;
@@ -457,7 +467,7 @@ export default {
padding: 0 12px;
font-size: 12px;
font-weight: 700;
- color: var(--ti-lowcode-data-source-color);
+ color: var(--te-common-text-primary);
background: var(--ti-lowcode-common-component-bg);
border-bottom: 1px solid var(--ti-lowcode-data-header-border-bottom-color);
.options-wrap {
diff --git a/packages/plugins/state/src/StateTips.vue b/packages/plugins/state/src/StateTips.vue
index 8599ba91bf..41d010e5e4 100644
--- a/packages/plugins/state/src/StateTips.vue
+++ b/packages/plugins/state/src/StateTips.vue
@@ -13,12 +13,12 @@
diff --git a/packages/plugins/tree/package.json b/packages/plugins/tree/package.json
index 18927a2ec8..a08bc95d2c 100644
--- a/packages/plugins/tree/package.json
+++ b/packages/plugins/tree/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-plugin-tree",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"publishConfig": {
"access": "public"
},
diff --git a/packages/plugins/tree/src/Main.vue b/packages/plugins/tree/src/Main.vue
index 6c87a84b4d..130cc688a0 100644
--- a/packages/plugins/tree/src/Main.vue
+++ b/packages/plugins/tree/src/Main.vue
@@ -29,7 +29,7 @@
@mouseleave="mouseleave(data.row)"
@click="checkElement(data.row)"
>
-
+
@@ -49,12 +49,12 @@
+
diff --git a/packages/vue-generator/test/testcases/sfc/accessor/schema.json b/packages/vue-generator/test/testcases/sfc/accessor/schema.json
new file mode 100644
index 0000000000..27a6c664dd
--- /dev/null
+++ b/packages/vue-generator/test/testcases/sfc/accessor/schema.json
@@ -0,0 +1,101 @@
+{
+ "componentName": "Page",
+ "fileName": "Accessor",
+ "css": "",
+ "props": {},
+ "children": [
+ {
+ "componentName": "Text",
+ "props": {
+ "className": "page-header",
+ "text": "测试 state accessor 出码场景"
+ }
+ }
+ ],
+ "state": {
+ "firstName": "Opentiny",
+ "lastName": "TinyEngine",
+ "nullValue": {
+ "defaultValue": null,
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.nullValue = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ },
+ "numberValue": {
+ "defaultValue": 0,
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.numberValue = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ },
+ "emptyStr": {
+ "defaultValue": "",
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.emptyStr = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ },
+ "strVal": {
+ "defaultValue": "i am str.",
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.strVal = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ },
+ "trueVal": {
+ "defaultValue": true,
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.trueVal = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ },
+ "falseVal": {
+ "defaultValue": false,
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.falseVal = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ },
+ "arrVal": {
+ "defaultValue": [1, "2", { "aaa": "aaa" }, [3, 4], true, false],
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.arrVal = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ },
+ "objVal": {
+ "defaultValue": {
+ "aaa": "aaa",
+ "arr": [1, "2", true, false, 0],
+ "ccc": { "bbb": "bbb" },
+ "d": 32432,
+ "e": "",
+ "f": null,
+ "g": false
+ },
+ "accessor": {
+ "getter": {
+ "type": "JSFunction",
+ "value": "function() { this.state.objVal = `${this.state.firstName} ${this.state.lastName}` }"
+ }
+ }
+ }
+ },
+ "lifeCycles": {},
+ "methods": {}
+}
diff --git a/packages/vue-generator/test/testcases/sfc/slotParams/block.schema.json b/packages/vue-generator/test/testcases/sfc/slotParams/block.schema.json
new file mode 100644
index 0000000000..df79707482
--- /dev/null
+++ b/packages/vue-generator/test/testcases/sfc/slotParams/block.schema.json
@@ -0,0 +1,47 @@
+{
+ "state": {
+ "personData": {
+ "name": "李华",
+ "age": "20",
+ "address": "china"
+ }
+ },
+ "methods": {},
+ "componentName": "Block",
+ "css": "",
+ "props": {},
+ "lifeCycles": {},
+ "children": [
+ {
+ "componentName": "Text",
+ "id": "52535213",
+ "props": {
+ "text": "请开启插槽"
+ }
+ },
+ {
+ "id": "42562b66",
+ "componentName": "div",
+ "props": {},
+ "children": [
+ {
+ "componentName": "Slot",
+ "id": "6153675d",
+ "props": {
+ "name": "test",
+ "params": [
+ {
+ "name": "testData",
+ "value": {
+ "type": "JSExpression",
+ "value": "this.state.personData"
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "fileName": "BlockSlotParams"
+}
diff --git a/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsBlock.vue b/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsBlock.vue
new file mode 100644
index 0000000000..d86979f72f
--- /dev/null
+++ b/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsBlock.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+
diff --git a/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsPage.vue b/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsPage.vue
new file mode 100644
index 0000000000..bfa219de2d
--- /dev/null
+++ b/packages/vue-generator/test/testcases/sfc/slotParams/expected/slotParamsPage.vue
@@ -0,0 +1,27 @@
+
+
+
+
+ {{ testData.name }}
+
+
+
+
+
diff --git a/packages/vue-generator/test/testcases/sfc/slotParams/page.schema.json b/packages/vue-generator/test/testcases/sfc/slotParams/page.schema.json
new file mode 100644
index 0000000000..316f5d6f14
--- /dev/null
+++ b/packages/vue-generator/test/testcases/sfc/slotParams/page.schema.json
@@ -0,0 +1,41 @@
+{
+ "state": {},
+ "methods": {},
+ "componentName": "Page",
+ "css": "",
+ "props": {},
+ "lifeCycles": {},
+ "children": [
+ {
+ "componentName": "BlockSlotParams",
+ "props": {},
+ "id": "63623253",
+ "componentType": "Block",
+ "children": [
+ {
+ "id": "54396632",
+ "componentName": "Template",
+ "props": {
+ "slot": {
+ "name": "test",
+ "params": ["testData"]
+ }
+ },
+ "children": [
+ {
+ "id": "5242615f",
+ "componentName": "Text",
+ "props": {
+ "text": {
+ "type": "JSExpression",
+ "value": "testData.name"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ],
+ "fileName": "slotParamsTest"
+}
diff --git a/packages/vue-generator/test/testcases/sfc/slotParams/slotParams.test.js b/packages/vue-generator/test/testcases/sfc/slotParams/slotParams.test.js
new file mode 100644
index 0000000000..9d7efa124a
--- /dev/null
+++ b/packages/vue-generator/test/testcases/sfc/slotParams/slotParams.test.js
@@ -0,0 +1,19 @@
+import { expect, test } from 'vitest'
+import { genSFCWithDefaultPlugin } from '@/generator/vue/sfc'
+import blockSchema from './block.schema.json'
+import pageSchema from './page.schema.json'
+import { formatCode } from '@/utils/formatCode'
+
+test('should generate slot and pass testData params', async () => {
+ const res = genSFCWithDefaultPlugin(blockSchema, [])
+ const formattedCode = formatCode(res, 'vue')
+
+ await expect(formattedCode).toMatchFileSnapshot('./expected/slotParamsBlock.vue')
+})
+
+test('should generate slot params', async () => {
+ const res = genSFCWithDefaultPlugin(pageSchema, [])
+ const formattedCode = formatCode(res, 'vue')
+
+ await expect(formattedCode).toMatchFileSnapshot('./expected/slotParamsPage.vue')
+})
diff --git a/packages/webcomponent/package.json b/packages/webcomponent/package.json
index feb146b7bb..2e25249ae7 100644
--- a/packages/webcomponent/package.json
+++ b/packages/webcomponent/package.json
@@ -1,6 +1,6 @@
{
"name": "@opentiny/tiny-engine-webcomponent-core",
- "version": "2.0.0-rc.4",
+ "version": "2.1.0",
"publishConfig": {
"access": "public"
},