@opentiny/tiny-engine-canvas 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.eslintrc.js +42 -0
  2. package/README.md +7 -0
  3. package/canvas.html +212 -0
  4. package/dist/index.js +48919 -0
  5. package/index.html +13 -0
  6. package/package.json +30 -0
  7. package/public/favicon.ico +0 -0
  8. package/src/Design.vue +53 -0
  9. package/src/assets/logo.png +0 -0
  10. package/src/canvas.js +34 -0
  11. package/src/components/builtin/CanvasBox.vue +22 -0
  12. package/src/components/builtin/CanvasCol.vue +89 -0
  13. package/src/components/builtin/CanvasCollection.js +278 -0
  14. package/src/components/builtin/CanvasCollection.vue +106 -0
  15. package/src/components/builtin/CanvasIcon.vue +30 -0
  16. package/src/components/builtin/CanvasImg.vue +18 -0
  17. package/src/components/builtin/CanvasPlaceholder.vue +26 -0
  18. package/src/components/builtin/CanvasRow.vue +67 -0
  19. package/src/components/builtin/CanvasRowColContainer.vue +42 -0
  20. package/src/components/builtin/CanvasSlot.vue +22 -0
  21. package/src/components/builtin/CanvasText.vue +18 -0
  22. package/src/components/builtin/builtin.json +955 -0
  23. package/src/components/builtin/helper.js +46 -0
  24. package/src/components/builtin/index.js +33 -0
  25. package/src/components/common/index.js +158 -0
  26. package/src/components/container/CanvasAction.vue +554 -0
  27. package/src/components/container/CanvasContainer.vue +244 -0
  28. package/src/components/container/CanvasDivider.vue +246 -0
  29. package/src/components/container/CanvasDragItem.vue +38 -0
  30. package/src/components/container/CanvasFooter.vue +86 -0
  31. package/src/components/container/CanvasMenu.vue +214 -0
  32. package/src/components/container/CanvasResize.vue +195 -0
  33. package/src/components/container/CanvasResizeBorder.vue +219 -0
  34. package/src/components/container/container.js +791 -0
  35. package/src/components/container/keyboard.js +147 -0
  36. package/src/components/container/shortCutPopover.vue +181 -0
  37. package/src/components/render/CanvasEmpty.vue +14 -0
  38. package/src/components/render/RenderMain.js +408 -0
  39. package/src/components/render/context.js +53 -0
  40. package/src/components/render/render.js +689 -0
  41. package/src/components/render/runner.js +140 -0
  42. package/src/i18n/en.json +5 -0
  43. package/src/i18n/zh.json +5 -0
  44. package/src/i18n.js +21 -0
  45. package/src/index.js +96 -0
  46. package/src/locale.js +19 -0
  47. package/src/lowcode.js +104 -0
  48. package/src/main.js +17 -0
  49. package/test/form.json +690 -0
  50. package/test/group.json +99 -0
  51. package/test/jsslot.json +427 -0
  52. package/vite.config.js +73 -0
package/index.html ADDED
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" href="/favicon.ico" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Vite App</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.js"></script>
12
+ </body>
13
+ </html>
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@opentiny/tiny-engine-canvas",
3
+ "version": "1.0.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "main": "dist/index.js",
13
+ "module": "dist/index.js",
14
+ "dependencies": {
15
+ "@opentiny/tiny-engine-common": "^1.0.0",
16
+ "@opentiny/tiny-engine-controller": "^1.0.0",
17
+ "@opentiny/tiny-engine-utils": "^1.0.0",
18
+ "@opentiny/tiny-engine-i18n-host": "^1.0.0",
19
+ "@opentiny/vue": "^3.8.0",
20
+ "@vueuse/core": "^9.6.0",
21
+ "vue": "3.2.45",
22
+ "vue-i18n": "9.2.2"
23
+ },
24
+ "devDependencies": {
25
+ "@vitejs/plugin-vue": "^4.2.3",
26
+ "@vitejs/plugin-vue-jsx": "^1.3.10",
27
+ "rollup-plugin-terser": "^7.0.2",
28
+ "vite": "^4.3.7"
29
+ }
30
+ }
Binary file
package/src/Design.vue ADDED
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <div style="height: calc(100vh - 100px)">
3
+ <canvas-container :controller="controller" @selected="nodeSelected"></canvas-container>
4
+ </div>
5
+ </template>
6
+
7
+ <script setup>
8
+ import CanvasContainer from './index'
9
+
10
+ import formJSON from '../test/form.json'
11
+
12
+ const controller = {
13
+ getPageSchema() {
14
+ return formJSON
15
+ },
16
+ getConfigureMap() {
17
+ return {
18
+ TinyTabs: {
19
+ nestingRule: {
20
+ childWhitelist: ['TinyTabItem'],
21
+ parentWhitelist: '',
22
+ descendantBlacklist: '',
23
+ ancestorWhitelist: ''
24
+ }
25
+ },
26
+ TinyForm: {
27
+ nestingRule: {
28
+ childWhitelist: ['TinyFormItem'],
29
+ parentWhitelist: '',
30
+ descendantBlacklist: '',
31
+ ancestorWhitelist: ''
32
+ }
33
+ }
34
+ }
35
+ },
36
+ getJSResource() {
37
+ return {
38
+ utils: { Select: 'select' }
39
+ }
40
+ },
41
+ addHistory() {},
42
+ closePlugin() {},
43
+ getMaterial() {}
44
+ }
45
+
46
+ const nodeSelected = () => {}
47
+ </script>
48
+
49
+ <style>
50
+ #app {
51
+ height: 100%;
52
+ }
53
+ </style>
Binary file
package/src/canvas.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Copyright (c) 2023 - present TinyEngine Authors.
3
+ * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license.
6
+ *
7
+ * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
8
+ * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
9
+ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
10
+ *
11
+ */
12
+
13
+ import { createApp } from 'vue'
14
+ import * as TinyVue from '@opentiny/vue'
15
+
16
+ import { RenderMain } from './index'
17
+
18
+ import { I18nInjectionKey, createI18n } from 'vue-i18n'
19
+
20
+ window.TinyLowcodeComponent = {}
21
+
22
+ Object.entries(TinyVue).forEach(([_key, component]) => {
23
+ const { name } = component
24
+ if (name) {
25
+ window.TinyLowcodeComponent[name] = component
26
+ }
27
+ })
28
+
29
+ const i18nHost = createI18n({
30
+ locale: 'zh_CN',
31
+ messages: {}
32
+ })
33
+
34
+ createApp(RenderMain).use(i18nHost).provide(I18nInjectionKey, i18nHost).mount('#app')
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <component :is="tag" v-bind="$attrs">
3
+ <slot>
4
+ <canvas-placeholder :placeholder="$attrs.placeholder"></canvas-placeholder>
5
+ </slot>
6
+ </component>
7
+ </template>
8
+
9
+ <script>
10
+ import CanvasPlaceholder from './CanvasPlaceholder.vue'
11
+ export default {
12
+ components: {
13
+ CanvasPlaceholder
14
+ },
15
+ props: {
16
+ tag: {
17
+ type: String,
18
+ default: 'div'
19
+ }
20
+ }
21
+ }
22
+ </script>
@@ -0,0 +1,89 @@
1
+ <template>
2
+ <div ref="colRef" class="canvas-col">
3
+ <slot>
4
+ <canvas-placeholder :placeholder="$attrs.placeholder"></canvas-placeholder>
5
+ </slot>
6
+ </div>
7
+ </template>
8
+
9
+ <script>
10
+ import { computed } from 'vue'
11
+ import CanvasPlaceholder from './CanvasPlaceholder.vue'
12
+ import { getStyleValue, alignMap, justAlignMap } from './helper'
13
+
14
+ export default {
15
+ components: {
16
+ CanvasPlaceholder
17
+ },
18
+ props: {
19
+ flexBasis: {
20
+ type: String,
21
+ default: '0px'
22
+ },
23
+ rowGap: {
24
+ type: [String, Number],
25
+ default: ''
26
+ },
27
+ colGap: {
28
+ type: [String, Number],
29
+ default: ''
30
+ },
31
+ align: {
32
+ type: [String, Number],
33
+ default: ''
34
+ },
35
+ justAlign: {
36
+ type: [String, Number],
37
+ default: ''
38
+ },
39
+ grow: {
40
+ type: Boolean,
41
+ default: true
42
+ },
43
+ shrink: {
44
+ type: Boolean,
45
+ default: true
46
+ },
47
+ widthType: {
48
+ type: String,
49
+ default: 'auto'
50
+ }
51
+ },
52
+ setup(props) {
53
+ const getFlex = () => {
54
+ const { flexBasis, grow, shrink, widthType } = props
55
+
56
+ if (widthType === 'fixed') {
57
+ return `0 0 ${getStyleValue(flexBasis)}`
58
+ }
59
+
60
+ return `${Number(grow)} ${Number(shrink)} ${getStyleValue(flexBasis)}`
61
+ }
62
+
63
+ const styles = computed(() => ({
64
+ flex: getFlex(props.flexBasis),
65
+ rowGap: getStyleValue(props.rowGap),
66
+ colGap: getStyleValue(props.colGap),
67
+ align: alignMap[props.align] || 'stretch',
68
+ justAlign: justAlignMap[props.justAlign] || 'flex-start'
69
+ }))
70
+
71
+ return {
72
+ styles
73
+ }
74
+ }
75
+ }
76
+ </script>
77
+
78
+ <style lang="less" scoped>
79
+ .canvas-col {
80
+ display: flex;
81
+ flex: v-bind('styles.flex');
82
+ flex-direction: column;
83
+ flex-wrap: nowrap;
84
+ row-gap: v-bind('styles.rowGap');
85
+ column-gap: v-bind('styles.colGap');
86
+ align-items: v-bind('styles.align');
87
+ justify-content: v-bind('styles.justAlign');
88
+ }
89
+ </style>
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Copyright (c) 2023 - present TinyEngine Authors.
3
+ * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license.
6
+ *
7
+ * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
8
+ * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
9
+ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
10
+ *
11
+ */
12
+
13
+ import { getController } from '../render/render'
14
+ import { api } from '../render/RenderMain'
15
+ import { useModal } from '@opentiny/tiny-engine-controller'
16
+
17
+ const NAME_PREFIX = {
18
+ loop: 'loop',
19
+ table: 'getTableData',
20
+ page: 'pageConfig',
21
+ grid: 'tinyGrid',
22
+ tree: 'tinyTree',
23
+ select: 'tinySelect'
24
+ }
25
+
26
+ const genRemoteMethodToLifeSetup = (variableName, sourceRef, pageSchema) => {
27
+ if (sourceRef.value.data?.option) {
28
+ const setupFn = pageSchema.lifeCycles?.setup?.value
29
+ const fetchBody = `
30
+ this.state.${variableName} = []
31
+ this.dataSourceMap.${sourceRef.value.name}.load().then(data=>{ this.state.${variableName}=data })`
32
+
33
+ if (!setupFn) {
34
+ pageSchema.lifeCycles = pageSchema.lifeCycles || {}
35
+ pageSchema.lifeCycles.setup = {
36
+ type: 'JSFunction',
37
+ value: `function setup({ props, state, watch, onMounted }) {${fetchBody}}`
38
+ }
39
+ } else {
40
+ pageSchema.lifeCycles.setup.value = setupFn.trim().replace(/\}$/, fetchBody + '}')
41
+ }
42
+ }
43
+ }
44
+
45
+ const removeState = (pageSchema, variableName) => {
46
+ delete pageSchema.state[variableName]
47
+
48
+ const { parse, traverse, generate } = getController().ast
49
+ const setupFn = pageSchema.lifeCycles?.setup?.value
50
+
51
+ try {
52
+ const ast = parse(setupFn)
53
+
54
+ traverse(ast, {
55
+ ExpressionStatement(path) {
56
+ path.toString().includes(variableName) && path.remove()
57
+ }
58
+ })
59
+
60
+ pageSchema.lifeCycles.setup.value = generate(ast).code
61
+ } catch (error) {
62
+ // do nothing
63
+ }
64
+ }
65
+
66
+ const setStateWithSourceRef = (pageSchema, variableName, sourceRef, data) => {
67
+ api.setState({ [variableName]: data })
68
+ pageSchema.state[variableName] = data
69
+
70
+ if (sourceRef.value.data?.option?.isSync) {
71
+ genRemoteMethodToLifeSetup(variableName, sourceRef, pageSchema)
72
+ }
73
+ }
74
+
75
+ const defaultHandlerTemplate = ({ node, sourceRef, schemaId, pageSchema }) => {
76
+ const genVarName = (schemaId) => `${NAME_PREFIX.loop}${schemaId}`
77
+
78
+ const updateNode = () => {
79
+ const { configure } = getController().getMaterial(node?.componentName)
80
+
81
+ if (!configure?.loop) {
82
+ return
83
+ }
84
+
85
+ const variableName = genVarName(schemaId)
86
+
87
+ setStateWithSourceRef(pageSchema, variableName, sourceRef, sourceRef.value.data?.data)
88
+
89
+ node.loop = {
90
+ type: 'JSExpression',
91
+ value: `this.state.${variableName}`
92
+ }
93
+ }
94
+
95
+ const clearBindVar = () => {
96
+ const variableName = genVarName(schemaId)
97
+
98
+ removeState(pageSchema, variableName)
99
+ }
100
+
101
+ return {
102
+ updateNode,
103
+ clearBindVar
104
+ }
105
+ }
106
+
107
+ const generateAssginColumns = (newColumns, oldColumns) => {
108
+ newColumns.forEach((item) => {
109
+ const targetColumn = oldColumns.find((value) => value.field === item.field)
110
+ if (targetColumn) {
111
+ Object.assign(item, targetColumn)
112
+ }
113
+ })
114
+ return newColumns
115
+ }
116
+
117
+ const askShouldImportData = ({ node, sourceRef }) => {
118
+ useModal().confirm({
119
+ message: '检测到表格存在配置的数据,是否需要引入?',
120
+ exec() {
121
+ const sourceColums = sourceRef.value?.data?.columns?.map(({ title, field }) => ({ title, field })) || []
122
+ // 这里需要找到对应列,然后进行列合并
123
+ node.props.columns = generateAssginColumns(sourceColums, node.props.columns)
124
+ },
125
+ cancel() {
126
+ node.props.columns = [...sourceRef.value.data?.columns]
127
+ }
128
+ })
129
+ }
130
+
131
+ const updateNodeHandler = ({ node, sourceRef, pageSchema, sourceName, methodName }) => {
132
+ if (!node || !node.props) {
133
+ return
134
+ }
135
+
136
+ // 如果使用了数据元则需要删除表格的data属性
137
+ delete node?.props?.data
138
+
139
+ if (node.props.columns.length) {
140
+ askShouldImportData({ node, sourceRef })
141
+ } else {
142
+ node.props.columns = [...sourceRef.value.data?.columns]
143
+ }
144
+
145
+ const pageConfig = {
146
+ attrs: {
147
+ currentPage: 1,
148
+ pageSize: 50,
149
+ pageSizes: [10, 20, 50],
150
+ total: 0,
151
+ layout: 'sizes,total, prev, pager, next, jumper'
152
+ }
153
+ }
154
+
155
+ node.props.pager = pageConfig
156
+
157
+ pageSchema.methods[methodName] = {
158
+ type: 'JSFunction',
159
+ value: `function ${methodName}({ page, sort, sortBy, filters}) {
160
+ /**
161
+ * @param {Object} sort 排序数据
162
+ * @param {Array} sortBy 排序方式
163
+ * @param {Object} page 分页数据
164
+ * @param {Array} filters 筛选数据
165
+ * @returns {Object} 返回一个promise对象,并且resolve格式为{ result: Array, page: { total: Number } }
166
+ */
167
+ return new Promise((resolve, reject) => {
168
+ this.dataSourceMap.${sourceName}.load().then((res) => {
169
+ // 如果按照数据源面板的建议格式编写dataHandler
170
+ // 那么dataSourceMap的res格式应该是:{ code: string, msg: string, data: {items: any[], total: number} }
171
+ resolve({ result: res.data.items, page: { total: res.data.total } });
172
+ });
173
+ });
174
+ }`
175
+ }
176
+ }
177
+
178
+ const extraHandlerMap = {
179
+ TinyGrid: ({ node, sourceRef, schemaId, pageSchema }) => {
180
+ const sourceName = sourceRef.value?.name
181
+ const methodName = `${NAME_PREFIX.table}${schemaId}`
182
+
183
+ node.props.fetchData = {
184
+ type: 'JSExpression',
185
+ value: `{ api: this.${methodName} }`
186
+ }
187
+
188
+ const updateNode = () => updateNodeHandler({ node, sourceRef, pageSchema, sourceName, methodName })
189
+
190
+ const clearBindVar = () => {
191
+ // 当数据源组件children字段为空时,及时清空创建的methods
192
+ delete pageSchema.methods[methodName]
193
+ }
194
+
195
+ return {
196
+ updateNode,
197
+ clearBindVar
198
+ }
199
+ },
200
+ TinyTree: ({ node, sourceRef, schemaId, pageSchema }) => {
201
+ const genVarName = (schemaId) => `${NAME_PREFIX.tree}${schemaId}`
202
+
203
+ const updateNode = () => {
204
+ const variableName = genVarName(schemaId)
205
+
206
+ const arrayToTree = (data) => {
207
+ const map = {}
208
+ const tree = []
209
+ let node = null
210
+ let i = 0
211
+
212
+ for (i = 0; i < data.length; i++) {
213
+ map[data[i].id] = data[i]
214
+ data[i].children = []
215
+ }
216
+
217
+ for (i = 0; i < data.length; i++) {
218
+ node = data[i]
219
+ if (node.pid !== '') {
220
+ map[node.pid]?.children?.push(node)
221
+ } else {
222
+ tree.push(node)
223
+ }
224
+ }
225
+
226
+ return tree
227
+ }
228
+
229
+ setStateWithSourceRef(pageSchema, variableName, sourceRef, arrayToTree(sourceRef.value.data?.data))
230
+
231
+ node.props.data = {
232
+ type: 'JSExpression',
233
+ value: `this.state.${variableName}`
234
+ }
235
+ }
236
+
237
+ const clearBindVar = () => {
238
+ const variableName = genVarName(schemaId)
239
+
240
+ removeState(pageSchema, variableName)
241
+ }
242
+
243
+ return {
244
+ updateNode,
245
+ clearBindVar
246
+ }
247
+ },
248
+ TinySelect: ({ node, sourceRef, schemaId, pageSchema }) => {
249
+ const genVarName = (schemaId) => `${NAME_PREFIX.select}${schemaId}`
250
+
251
+ const updateNode = () => {
252
+ const variableName = genVarName(schemaId)
253
+
254
+ setStateWithSourceRef(pageSchema, variableName, sourceRef, sourceRef.value.data?.data)
255
+
256
+ node.props.options = {
257
+ type: 'JSExpression',
258
+ value: `this.state.${variableName}`
259
+ }
260
+ }
261
+
262
+ const clearBindVar = () => {
263
+ const variableName = genVarName(schemaId)
264
+
265
+ removeState(pageSchema, variableName)
266
+ }
267
+
268
+ return {
269
+ updateNode,
270
+ clearBindVar
271
+ }
272
+ }
273
+ }
274
+
275
+ export const getHandler = ({ node, sourceRef, schemaId, pageSchema }) =>
276
+ extraHandlerMap[node.componentName]
277
+ ? extraHandlerMap[node.componentName]({ node, sourceRef, schemaId, pageSchema })
278
+ : defaultHandlerTemplate({ node, sourceRef, schemaId, pageSchema })
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <component :is="tag" v-bind="$attrs">
3
+ <slot>
4
+ <canvas-placeholder>{{ source?.name || '未选择数据源' }}</canvas-placeholder>
5
+ </slot>
6
+ </component>
7
+ </template>
8
+
9
+ <script>
10
+ import { ref, watch, computed, inject } from 'vue'
11
+ import { getController } from '../render/render'
12
+ import CanvasPlaceholder from './CanvasPlaceholder.vue'
13
+
14
+ import { getHandler } from './CanvasCollection.js'
15
+
16
+ export const fetchDataSourceDetail = (dataSourceId) =>
17
+ getController().request.get(`/app-center/api/sources/detail/${dataSourceId}`)
18
+
19
+ export default {
20
+ components: {
21
+ CanvasPlaceholder
22
+ },
23
+ props: {
24
+ tag: {
25
+ type: String,
26
+ default: 'div'
27
+ },
28
+ schema: {
29
+ type: Object,
30
+ default: () => ({})
31
+ },
32
+ dataSource: [String, Array, Number]
33
+ },
34
+ setup(props) {
35
+ const source = ref(null)
36
+ const pageSchema = inject('rootSchema')
37
+
38
+ if (props.dataSource) {
39
+ fetchDataSourceDetail(props.dataSource).then((res) => {
40
+ source.value = res
41
+ })
42
+ }
43
+
44
+ let handler
45
+
46
+ watch(
47
+ () => props.dataSource,
48
+ async (value) => {
49
+ if (value) {
50
+ source.value = await fetchDataSourceDetail(value)
51
+ if (props.schema?.children[0]) {
52
+ handler = getHandler({
53
+ sourceRef: source,
54
+ node: props.schema?.children[0],
55
+ schemaId: props.schema.id,
56
+ pageSchema
57
+ })
58
+ }
59
+ handler?.updateNode()
60
+ }
61
+ }
62
+ )
63
+
64
+ const isEmpty = computed(() => {
65
+ const { children } = props.schema || {}
66
+
67
+ return Array.isArray(children) ? !children.length : !children
68
+ })
69
+
70
+ watch(
71
+ () => isEmpty.value,
72
+ (value) => {
73
+ if (value) {
74
+ // 清除自动创建的state,method与setup逻辑
75
+ if (handler) {
76
+ handler.clearBindVar()
77
+ } else {
78
+ const schemaId = props.schema?.id
79
+
80
+ // 当页面初始化时handler是不存在的,所以需要通过数据源的schemaId(唯一性),去删除对应的方法
81
+ Object.keys(pageSchema.methods || {})?.some((item) => {
82
+ if (item.includes(schemaId)) {
83
+ delete pageSchema.methods[item]
84
+ return true
85
+ }
86
+ return false
87
+ })
88
+ }
89
+ } else {
90
+ if (props.schema?.children[0]) {
91
+ handler = getHandler({
92
+ sourceRef: source,
93
+ node: props.schema?.children[0],
94
+ schemaId: props.schema.id,
95
+ pageSchema
96
+ })
97
+ handler.updateNode()
98
+ }
99
+ }
100
+ }
101
+ )
102
+
103
+ return { source }
104
+ }
105
+ }
106
+ </script>
@@ -0,0 +1,30 @@
1
+ <script>
2
+ import * as SvgIcons from '@opentiny/vue-icon'
3
+ import { h } from 'vue'
4
+
5
+ export default {
6
+ props: {
7
+ name: {
8
+ type: String,
9
+ default: ''
10
+ }
11
+ },
12
+ setup(props) {
13
+ return () =>
14
+ h('span', { class: 'iconwarp' }, [
15
+ h(SvgIcons[props.name]?.(), { style: { width: '100%', height: '100%' } }) || 'error.'
16
+ ])
17
+ }
18
+ }
19
+ </script>
20
+
21
+ <style scoped>
22
+ .iconwarp {
23
+ display: inline-flex;
24
+ justify-content: center;
25
+ align-items: center;
26
+ fill: currentColor;
27
+ width: 1em;
28
+ height: 1em;
29
+ }
30
+ </style>
@@ -0,0 +1,18 @@
1
+ <template>
2
+ <img :src="src" :style="style" />
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ props: {
8
+ src: {
9
+ type: String,
10
+ default: ''
11
+ },
12
+ style: {
13
+ type: String,
14
+ default: ''
15
+ }
16
+ }
17
+ }
18
+ </script>